HTTP Server upon new mbed Ethernet Interface. Based on original code by Henry Leinen.
Dependencies: EthernetInterface mbed-rtos mbed
Fork of HTTP_server by
HTTPServer.cpp
- Committer:
- pabloxid
- Date:
- 2013-08-05
- Revision:
- 4:2a34139c7246
- Parent:
- 3:27b3a889b327
File content as of revision 4:2a34139c7246:
#include "mbed.h"
#include "HTTPServer.h"
RequestConfig rq_conf[] = {
{ "GET", HTTP_RT_GET },
{ "POST", HTTP_RT_POST}
};
HTTPServer::HTTPServer (int port, const char* _path) {
INFO("Binding to port %d...", port);
if (socketServer.bind (port) < 0) {
ERR("Failed to bind to port !\n");
error("Binding");
}
INFO("Listening ...");
if (socketServer.listen(1) < 0) {
ERR("Failed to listen !\n");
error("Listening");
}
INFO("Connected !");
// set into blocking operation
socketServer.set_blocking (true);
path = _path;
msg = NULL;
cliente = NULL;
}
HTTPServer::~HTTPServer() { };
int HTTPServer::poll () {
cliente = new TCPSocketConnection;
cliente->set_blocking (false, TIMEOUT);
int retvalue = socketServer.accept (*cliente);
if (retvalue == OK) {
// a new connection was received
INFO("Client (IP=%s) is connected !", cliente->get_address());
msg = new HTTPMsg; // estructura para decodificar y alojar el mensaje
retvalue = pollConnection (); // esto parsea y llena las cosas contenidas en msg
if (retvalue == OK) {
// Handle the request
INFO("Handling request !");
handleRequest (); // handling request
}
delete msg;
} else { // retvalue == ERROR
ERR("Error accepting client");
}
delete cliente;
INFO("Leaving polling thread\n");
return retvalue;
}
int HTTPServer::pollConnection () {
INFO("Waiting for new data in connection");
// Try receiving request line
int received = receiveLine ();
if (received == ERROR) {
// there was an error, probably the connection was closed, so close this connection as well
INFO("No more data available. Will close this connection now.");
return ERROR;
}
// Request has not yet been received, so try it
received = parseRequest ();
/*alternative (fast) parse request method:
* ret = sscanf(buffer, "%s %s HTTP/%*d.%*d", request, uri);
*/
if (received == ERROR) {
// Invalid content received, so close the connection
INFO("Invalid message received, so sending negative response and closing connection !");
tcpsend ("HTTP/1.1 400 BadRequest\n\rContent-Length: %d\n\rContent-Type: text\n\r\n\r\n\r", 0);
return ERROR;
}
// Request has been received, try receive the headers section
do {
received = receiveLine ();
if (received == ERROR) {return ERROR;}
// First check if we received an empty line;
// This would indicate the end of headers section.
if (received == EMPTY) {
// there was an empty line, so the headers section is complete
INFO("Request Header was received completely. Performing request.");
received = OK;
break;
} else {
// parse header field
if (parseHeader() != OK) {
WARN("Invalid message header received !");
}
}
} while (received > 0);
INFO("Leaving poll function!");
return received;
}
int HTTPServer::receiveLine () {
buffer[0] = 0;
int i;
// Try to receive up to the max number of characters
for (i=0; i<BUFFER_SIZE-1; i++) {
int c = cliente->receive (buffer+i, 1);
// Check that - if no character was currently received - the timeout period is reached.
if (c == 0 || c == -1) {
// no character was read, so check if operation timed out
ERR("Timeout occured in function 'receiveLine'.");
return ERROR;
}
// Check if line terminating character was received
if (buffer[i] == '\n') {break;}
}
// Terminate with \0
buffer[i] = 0;
// Trim for '\r' linefeed at the end
if (i>0 && buffer[i-1] == '\r') {
i--;
buffer[i] = 0;
}
// return number of characters received in the line or return -2 if an empty line was received
if (i==0 || (i==1 && buffer[0]=='\r')) {
// empty line received, so return -2
return EMPTY;
}
// retorna número de caracteres leidos
return i;
}
int HTTPServer::parseRequest () {
// Check if buffer content is not long enough.
if (strlen(buffer) < MIN_LONG) {
ERR("Buffer content is invalid or too short.");
return ERROR;
}
std::vector<std::string> args;
int argno = 0;
// decompose string into a list of arguments
int start = 0; // current starting char
int nLen = strlen(buffer)+1;
for (int i=0; i<nLen; i++) {
if ((buffer[i] == ' ') || (buffer[i] == '\n') || (buffer[i] == 0)) {
// new arg found
buffer[i] = 0;
if (argno++ == 1) {
// it's the uri
// parse the uri args
parseUriArgs (&buffer[start]);
}
INFO("Found argument \"%s\"", &buffer[start]);
args.push_back(&buffer[start]);
start = i+1;
}
}
// store the uri and the HTTP version
msg->uri = args[1];
msg->version = args[2];
// Find matching request type
for (int i=0; i<sizeof(rq_conf)/sizeof(RequestConfig) ; i++) {
if (args.at(0) == rq_conf[i].request_string) {
msg->request = rq_conf[i].request_type;
}
}
// init body section length
msg->body_length = 0;
return OK;
}
int HTTPServer::parseHeader () {
// Check if the buffer content is too short to be meaningful
if (strlen(buffer) < MIN_LONG) {return ERROR;}
// decompose string into a touple of <field name> : <field value>
int value_start = 0;
int buflen = strlen(buffer)+1;
for (int i=0; i<buflen; i++) {
if (buffer[i] == ':') {
// touple found
buffer[i] = 0;
value_start = i+1;
// headers storage is disabled; uncomment next line to enable
// msg->headers[buffer] = &buffer[value_start];
INFO("Header name=\"%s\" : value=\"%s\".", buffer, &buffer[value_start]);
// Look for "Content-Length" header
if (strcmp (buffer, "Content-Length") == 0) {
msg->body_length = atoi(&buffer[value_start]);
INFO ("Body section found. Length: %i", msg->body_length);
}
return OK;
}
}
ERR("Did not receive a valid header : \"%s\".", buffer);
return ERROR;
}
int HTTPServer::parseUriArgs (char *uri_buffer) {
// Check if the buffer content is too short to be meaningful
if (strlen(uri_buffer) < MIN_LONG) {return ERROR;}
int args_start = -1;
int value_start = -1;
int buflen = strlen(uri_buffer) + 1;
char* argname = NULL;
char* valuename = NULL;
for (int i=0; i<buflen; i++) {
if (args_start == -1) { // args section not yet found
if (uri_buffer[i] == '?') { // starts with a question mark, so got it
uri_buffer[i] = 0;
args_start = i; // set the start of the args section
INFO("Argument section found !");
}
} else { // search arg-value touples
if (argname == NULL) { // arg-name found ?
if (uri_buffer[i] == '=') {
// yes, separate the arg-name
uri_buffer[i] = 0;
argname = &uri_buffer[args_start];
value_start = i+1;
INFO("Argument name %s", argname);
}
} else { // search for end of value
if ((uri_buffer[i] == '&') || (uri_buffer[i] == 0) || (uri_buffer[i] == '\r') || (uri_buffer[i] == '\n')) {
buffer[i] = 0;
valuename = &uri_buffer[value_start];
INFO("Argument value %s", valuename);
msg->uri_args[argname] = valuename;
// reset all indicators
argname = NULL;
valuename = NULL;
}
}
}
}
return OK;
}
void HTTPServer::handleRequest () {
int err_;
switch (msg->request) {
case HTTP_RT_GET:
INFO("Dispatching GET Request.");
err_ = handleGetRequest();
break;
case HTTP_RT_POST:
INFO("Dispatching POST request.");
err_ = handlePostRequest();
break;
default:
INFO("Error in handleRequest, unhandled request type.");
err_ = 501; // HTTP_NotImplemented
break;
}
// if any of these functions returns a negative number, call the error handler
if (err_ > 0) {
handleError (err_);
}
}
int HTTPServer::handleGetRequest() {
int retval = OK; //success
INFO("Handling Get Request.");
// maping to root path
std::string reqPath = path + msg->uri.substr(1);
// Check if we received a directory with the local path
if (reqPath.substr(reqPath.length()-1, 1) == "/") {
// yes, we shall append the default page name
reqPath += "index.htm";
}
INFO("Mapping \"%s\" to \"%s\"", msg->uri.c_str(), reqPath.c_str());
FILE *file = fopen(reqPath.c_str(), "r");
if (file != NULL) {
// File was found and can be returned; first determine the size
fseek (file, 0, SEEK_END);
int size = ftell (file);
fseek (file, 0, SEEK_SET);
startResponse (200, size); // response: 200 = HTTP_Ok
while (!feof(file) && !ferror(file)) {
// TODO: handle filesystem errors too
int count = fread (buffer, 1, CHUNK_SIZE, file);
INFO("Processing Response (%d bytes)!", count);
tcpsend (buffer, count);
}
INFO("Ending Response !");
fclose (file);
} else {
retval = 404;
ERR("Requested file was not found !");
}
return retval;
}
int HTTPServer::handlePostRequest() {
// Try receive the body data, if there is any
if (msg->body_length > 0) {
char post_buffer [msg->body_length];
INFO("Receiving body data. (%i bytes)", msg->body_length);
int bytes_read = 0;
while (bytes_read < msg->body_length) {
int result = cliente->receive_all(post_buffer+bytes_read, msg->body_length-bytes_read);
if (result == ERROR) {
WARN("Error receiving body data.");
break;
}
bytes_read += result;
}
INFO("Body data received.");
// do something
// use the url_decode function :)
INFO("Done !\n");
return handleGetRequest();
} else {
ERR("POST data not found !");
}
return 404;
}
static const char hdrStandard[] = "DNT: 1\r\n"
"MaxAge: 0\r\n"
"Connection: Keep-Alive\r\n"
"Content-Type: text/html\r\n" // TODO: handle file types
"Server: mbed embedded\r\n"
"Accessible: 1\r\n"
"Pragma: no-cache\r\n"
"Cache-control: no-cache;no-store\r\n"
"Expires: 0\r\n"
"\r\n";
void HTTPServer::startResponse (int returnCode, int nLen) {
INFO("Starting response (%d bytes in total)!", nLen);
tcpsend ("HTTP/1.1 %d OK\r\n", returnCode);
tcpsend ("Content-Length: %d\r\n", nLen); // Add 2 chars for the terminating CR+LF
INFO("Sending standard headers !");
tcpsend (hdrStandard);
INFO("Done !");
}
static const char* errorPage = "<HTML><HEAD><META content=\"text/html\" http-equiv=Content-Type></HEAD><BODY><h1>Error</h1><P>HTTPServer Error<P></BODY></HTML>\r\n\r\n";
void HTTPServer::handleError (int errorCode) {
INFO("Handling error !");
tcpsend ("HTTP/1.1 %d Error\r\n", errorCode);
tcpsend ("Content-Length: %d\r\n", strlen(errorPage));
tcpsend ("Content-Type: text/html\r\nServer: mbed embedded\r\n\r\n");
tcpsend (errorPage); // TODO: better error page (handle error type)
INFO("Done !");
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// UTILS //
////////////////////////////////////////////////////////////////////////////////////////////////////
#include <ctype.h>
/* Converts a hex character to its integer value */
char from_hex (char ch) {
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}
/* Returns a url-decoded version of str */
void url_decode (char *str) {
char *lee = str, *escribe = str;
while (*lee) {
if (*lee == '%') {
if (lee[1] && lee[2]) {
*escribe++ = from_hex(lee[1])<<4 | from_hex(lee[2]);
lee += 2;
}
} else if (*lee == '+') {
*escribe++ = ' ';
} else {
*escribe++ = *lee;
}
lee++;
}
*escribe = 0;
}
