HTTP Server upon new mbed Ethernet Interface. Based on original code by Henry Leinen.

Dependencies:   EthernetInterface mbed-rtos mbed

Fork of HTTP_server by pablo gindel

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;
}