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-07-28
Revision:
2:dc9184e97328
Parent:
0:fcceff3299be
Child:
3:27b3a889b327

File content as of revision 2:dc9184e97328:

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

}

HTTPServer::~HTTPServer() { };

int HTTPServer::poll () {

    cliente = new TCPSocketConnection;
    cliente->set_blocking (false, TIMEOUT);

    if (socketServer.accept(*cliente) < 0) {
        INFO("No connection\n");
        return ERROR;
    }

    // a new connection was received
    INFO("Client (IP=%s) is connected !", cliente->get_address());

    msg = new HTTPMsg;  // estructura para decodificar y alojar el mensaje

    int c = pollConnection ();  // esto parsea y llena las cosas contenidas en msg

    if (c == OK) {
        // Handle the request
        // cliente->set_blocking (true);
        INFO("Handling request !");
        handleRequest ();
    }

    delete msg;
    delete cliente;

    INFO("Leaving polling thread\n");
    return c;
}

int HTTPServer::pollConnection () {

    int received = 0;
    INFO("Waiting for new data in connection");
    //  Try receiving request line
    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;
    }

    //  The Request has not yet been received so try it
    received = parse ();

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

    //  The request has been received, try receive the body
    do {
        received = receiveLine ();
        if (received == ERROR) {return ERROR;}
        //  First check if we received an empty line. This would indicate the end of the message or message body.
        if (received == EMPTY) {
            //  there was an empty line, so we can start with performing the request
            INFO("Request Header was received completely. Performing request.");
            received = 0;
            break;
        } else {
            /*  add message body
            if (parseHeader () != 0) {
                WARN("Invalid message header received !");
            }*/
        }
    } while (received > 0);   //

    INFO("Leaving poll function!");
    return received;
}

int HTTPServer::receiveLine () {

    buffer[0] = 0;

    if (!cliente->is_connected()) {
        error("NOT Connected anymore");
        return ERROR;
    }

    Timer tm;
    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
            if (tm.read_ms() > 2*TIMEOUT) {
                //  Operation timed out
                INFO("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::parse () {

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

    return OK;
}

/* esta rutina no se usa */

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;
            msg->headers[buffer] = &buffer[value_start];
            INFO("Header name=\"%s\" : value=\"%s\".", buffer, &buffer[value_start]);
            return OK;
        }
    }

    ERR("Did not recieve 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->args[argname] = valuename;
                    //  reset all indicators
                    argname = NULL;
                    valuename = NULL;
                }
            }
        }
    }

    return OK;
}

/* verificar qué parte de msg realmente se usa,
   eliminar el resto, incluyendo los procesos asociados */

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) {

        // asigna toda la memoria dinámica disponible para 'chunk'
        char * chunk = NULL;
        int chunk_sz = MAX_CHUNK_SIZE;
        while(chunk == NULL) {
            chunk_sz /= 2;
            chunk = (char*) malloc (chunk_sz);
            if (chunk_sz < MIN_CHUNK_SIZE) {
                error ("OutOfMemory");
            }
        }

        // 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)) {
            int count = fread (chunk, 1, chunk_sz , file);
            INFO("Processing Response (%d bytes)!", count);
            if (cliente->send_all (chunk, count) != count) {
                WARN ("Unsent bytes left !");                  // TODO: handle filesystem errors
            }
        }

        INFO("Ending Response !");

        free (chunk);
        fclose (file);

    } else {
        retval = 404;
        ERR("Requested file was not found !");
    }

    return retval;

}

int HTTPServer::handlePostRequest() {

    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"
                            "\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 !");

}