Henry Leinen / Mbed 2 deprecated MultiThreadingHTTPServer

Dependencies:   WiFlyHTTPServer WiflyInterface mbed-rpc mbed-rtos mbed

HttpServer.cpp

Committer:
leihen
Date:
2013-06-26
Revision:
0:9c6ebc97c758

File content as of revision 0:9c6ebc97c758:

#include "mbed.h"
#include "HttpServer.h"

#define DEBUG
#include "debug.h"


#define EVENT_DATA_READY            0x05

DigitalOut  ledRX(LED4);


typedef struct {
    char c;
} message_t;

Queue<char, 256>    m_queue;

typedef struct {
    const char* method;
    msg_t   type;
} methodType_t;

const methodType_t supportedOps[] = {
    { "GET", msg_get },
    { "POST", msg_post },
    { "PUT", msg_put },
    { "HEAD", msg_head},
    { "CONNECT", msg_connect},
    { "DELETE", msg_delete},
    { "TRACE", msg_trace},
    { "OPTIONS", msg_options}
};


Queue<request_msg_t, 5>         m_requestQueue;     //  Do not allow more than 5 concurrent requests
MemoryPool<request_msg_t, 5>    m_requestPool;

map<string, string>     messageHeaders;

map<string, HTTPRequestHandler* (*)(const char*, const char*, HTTPConnection::HTTPMessage&), HttpServer::handlersComp>   HttpServer::m_lpHandlers;



/* Constructor will create and initialize all objects excep the threads */
HttpServer::HttpServer(PinName tx, PinName rx, PinName rst, PinName tcp_status, const char * ssid, const char * phrase, Security sec, Wifly::WiflyBaudrate_t baud)
    : Wifly(tx, rx, rst, tcp_status, ssid, phrase, sec, baud), m_listener(NULL), m_worker(NULL)
{
    INFO("Initializing wifly\n");
    //  Initialize the wifly wlan device
    reset();

    state.dhcp = true;
    INFO("Connecting to network...");
    //  Try join the network
    while(!join()) {
        INFO("Failed to connect. Trying again\n");
        reset();
    }
    INFO("connected\n");
}

HttpServer::~HttpServer()
{
    if (m_listener) {
        m_listener->terminate();
        delete m_listener;
    }
    if (m_worker) {
        m_worker->terminate();
        delete m_worker;
    }
}


bool HttpServer::start(int port)
{
    //  Bind to that port
    if (!bind(port)) {
        ERR("Failed to bind to port %d\n", port);
        return false;
    }

    //  Start the child threads
    m_worker = new Thread(HttpServer::worker_thread, NULL, osPriorityAboveNormal, DEFAULT_STACK_SIZE*4);
    if (m_worker == NULL) {
        ERR("Failed to start server thread !\n");
        return false;
    }

    m_listener = new Thread(&HttpServer::listen_thread, NULL, osPriorityAboveNormal, DEFAULT_STACK_SIZE*2);
    if (m_listener == NULL) {
        ERR("Failed to start listener thread !\n");
        m_worker->terminate();
        delete m_worker;
        m_worker = NULL;
        return false;
    }

    return true;
}





bool HttpServer::bind(int port)
{
    char cmd[20];

    // set TCP protocol
    setProtocol(TCP);

    // set local port
    sprintf(cmd, "set i l %d\r", port);
    if (!sendCommand(cmd, "AOK"))
        return false;

    // save
    if (!sendCommand("save\r", "Stor"))
        return false;

    // reboot
    reboot();

    // connect the network
    if (isDHCP()) {
        if (!sendCommand("join\r", "DHCP=ON", NULL, 10000))
            return false;
    } else {
        if (!sendCommand("join\r", "Associated", NULL, 10000))
            return false;
    }

    // exit
    exit();

    Thread::wait(200);
    flush();

    return true;
}

DigitalOut Led2(LED2);

void HttpServer::handler_rx(void)
{
    static char sequence = 0;
    //read characters
    while (wifi.readable()) {
        char c = LPC_UART3->RBR;
        ledRX = !ledRX;
        switch(sequence) {
            case 0 : if (c == 'G') sequence = 1; break;
            case 1 : if (c == 'E') sequence = 2; break;
            case 2 : if (c == 'T') sequence = 0; Led2 = !Led2;break;
            default: break;
        }
        m_queue.put((char*)(int)c);
    }
}


void HttpServer::attach_rx(bool callback)
{
    if (!callback)
        wifi.attach(NULL);
    else
        wifi.attach(this, &HttpServer::handler_rx);
}


bool HttpServer::join()
{
    return Wifly::join();
}

int HttpServer::send(const char * str, int len, const char * ACK, char * res, int timeout)
{
    return Wifly::send(str, len, ACK, res, timeout);
}

request_msg_t* HttpServer::checkMessageReceived(char *data)
{
    INFO("Checking for new HTTP request !\n");
    char *req = data;
    char *uri = NULL;
    char *ver = NULL;
    while( *data ) {
        if (*data == ' ') {
            *data = 0;
            if (uri == NULL) {
                uri = data+1;
            } else {
                ver = data+1;
                break;
            }
        }
        data++;
    }

    INFO("Detected : %s, %s, %s\n", req, uri, ver);

    if ((req != NULL) && (uri != NULL) && (ver != NULL) ) {
        for (int i = 0 ; i < sizeof(supportedOps) / sizeof(methodType_t) ; i++) {
            if (strcmp(supportedOps[i].method, req) == 0) {
                //  found the request
                INFO("Request valid !!!\n");
                request_msg_t* pmsg = m_requestPool.alloc();
                pmsg->requestType = supportedOps[i].type;
                strncpy(pmsg->requestUri, uri, 255);
                return pmsg;
            }
        }
    }

    INFO("Invalid request \"%s\"\n", req);
    return NULL;
}

void HttpServer::processMessageHeader(char* headerLine, char **fieldname, char **fieldvalue)
{
    *fieldname = headerLine;
    *fieldvalue = NULL;

    while( *headerLine ) {
        if (*headerLine == ':') {
            *headerLine++ = 0;
            while(*headerLine == ' ') headerLine++;
            *fieldvalue = headerLine;
            return;
        }
        headerLine++;
    }
    return ;
}


void HttpServer::listenForRequests()
{
    static char data[256];
    static int curPos = 0;
    int CRLF = 0;
    int         m_openConnections = 0;

    request_msg_t    *pMsg = NULL;
    INFO("Listener running\n");
    bool asteriskReceivedOnce = false;
    while(1) {
        osEvent evt = m_queue.get();
        if (evt.status == osEventMessage) {
            char c;
            c = (char)(int)evt.value.p;
            if ((c!='\n') && (c!='\r')) {
                data[curPos++] = c;
                data[curPos] = 0;
            }
            if (pMsg != NULL) {        //  request was detected and will further be processed completely
                //  check for CRLF
                if (c == '\n') {
                    CRLF++;
                    INFO("<CR>(%d)", CRLF);
                    if (CRLF == 2) {    //  all message headers received, so send message and be ready for new one
                        CRLF = 0;
                        //  SPAWN MESSAGE
                        INFO("REQUEST COMPLETE --> Handing over to worker thread !\n\n\n\n");
                        m_requestQueue.put(pMsg);
                        data[0] = 0;
                        curPos = 0;
                        asteriskReceivedOnce = false;
                        pMsg = NULL;
                    } else {            //  must be a new header
//                        char *name, *value;
//                        INFO("Processing Header !\"%s\"", data);
/*                        processMessageHeader(data, &name, &value);
                        if (strncmp(name, "Content-Length", 14 ) == 0) {
                            //  Data will be sent, be ready to receive
                        } else {
                            INFO("HEADER: Name=\"%s\", Value=\"%s\"", name, value);
                        }
*/                        data[0] = 0;
                        curPos = 0;
                    }
                } else {
                    if (c != '\r')
                        CRLF = 0;
                        else
                        INFO("<LF>");
                }
            } else if (c == '*') {
                CRLF = 0;
                if (asteriskReceivedOnce) {
                    //  could be an open, close or read command
                    if (curPos >= 6) {    // only need to process if data is large enough
                        if ( (data[curPos-6] == '*') && (data[curPos-5] == 'O') && (data[curPos-4] == 'P') && (data[curPos-3] == 'E') && (data[curPos-2] == 'N') && (data[curPos-1] == '*')) {
                            //  Add a connection
                            INFO("New connection opened (%d)...\n", ++m_openConnections);
                            data[0] = 0;
                            curPos = 0;
                        } else if ( (data[curPos-6] == '*') && (data[curPos-5] == 'C') && (data[curPos-4] == 'L') && (data[curPos-3] == 'O') && (data[curPos-2] == 'S') && (data[curPos-1] == '*')) {
                            //  close a connection
                            INFO("Connection was closed ...(%d)\n", --m_openConnections);
                            data[0] = 0;
                            curPos = 0;
                        }
                    }
                    asteriskReceivedOnce = false;
                } else {        //  set the indicator so that next time we'll check for valid connection commands
                    asteriskReceivedOnce = true;
                }
            } else {      //  first make sure that when no asterisk is received the asteriskReceivedOnce flag will be reset on each newline
                if (c == '\n') {
                    if (m_openConnections > 0) {
                        //  Check to see if we received a valid request
                        pMsg = checkMessageReceived(data);
                        if (pMsg == NULL) {
                            //  not received valid stuff, so discard
                            INFO("Unrecognised data received : \"%s\"\n", data);
                        } else {
                            INFO("New request detected ! : \"%s\"\n", data);
                        }
                    } else {
                        INFO("Unrecognised data detected : \"%s\"\n", data);
                    }
                    asteriskReceivedOnce = false;
                    data[0] = 0;
                    curPos = 0;
                    CRLF = 1;
                }
            }
        }
//        else {
            Thread::yield();
//        }
    }
}

void HttpServer::serveRequests()
{
    HTTPConnection::HTTPMessage     *myMessage = new HTTPConnection::HTTPMessage;
    
    INFO("Server running\n");

    while(1) {
        INFO("Listening for new request !");
        osEvent evt = m_requestQueue.get();
        if (evt.status == osEventMessage) {
            request_msg_t* pMsg = (request_msg_t*)evt.value.p;
            m_worker->set_priority(osPriorityBelowNormal);
            Thread::yield();
            switch(pMsg->requestType) {
                case    msg_get:
                    INFO("Server received GET message !");
                    myMessage->request = HTTP_RT_GET;
                    myMessage->uri = pMsg->requestUri;
                    HandleRequest(myMessage);
                    Thread::yield();
                    break;

                case    msg_post:
                case    msg_put:
                case    msg_head:
                case    msg_delete:
                case    msg_trace:
                case    msg_options:
                case    msg_connect:
                default:
                    break;
            }
            m_worker->set_priority(osPriorityNormal);
            m_requestPool.free(pMsg);
        }
        Thread::yield();
    }
}

bool HttpServer::parseRequest(char *request)
{
    //  dissect into :   path, file[, [arg, value]1..N ]   as  "/path/file?arg1=val1&arg2=val2...
    //  first check for questionmark sign to separate the file and path from any arguments
    char* path = request;
    char* file = NULL;
    char* arglist = NULL;

    char* lastPathSep = NULL;
    while(*request) {
        if (*request == '/' )
            lastPathSep = request;
        if (*request == '?') {
            *request++ = 0;
            arglist = request;
        }
    }

    if (arglist == NULL) {
        INFO("Request does not have parameters !");
    }

    if (lastPathSep == NULL)
        return false;               //  no path provided !!!!

    //  now, whatever is provided to the left including the slash is the 'path', the part to the right is the file. caution: the file may be left blank !
    if (lastPathSep != 0) {
        //  2 cases to handle :
        //  1. :      "/blah/" or "/blah/blub/"                 --> path = "/blah/", file = "index.html"
        //  2. :      "/blah/blub" or "/blah/blub/blubber"      --> path = "/blah/", file = "blub"
    } else {
        //  2 cases to handle :
        //  1. :      "/"       --> path = "/", file = "index.html"
        //  2. :      "/blah"   --> path = "/", file = "blah"
    }
    return true;
}


void HttpServer::listen_thread(const void *params)
{
    HttpServer* pSvr = (HttpServer*)params;

    pSvr->listenForRequests();
}

void HttpServer::worker_thread(const void * params)
{
    HttpServer* pSvr = (HttpServer*)params;

    pSvr->serveRequests();
}




static const char* szStdErrorPage = "<HTML><HEAD><META content=\"text/html\" http-equiv=Content-Type></HEAD><BODY><h1>Error 404</h1><P>This resource is not available<P></BODY></HTML>\r\n\r\n";

void HttpServer::StdErrorHandler(HTTPConnection::HTTPMessage& msg)
{
    char echoHeader[256];
    sprintf(echoHeader,"HTTP/1.0 404 Fail\r\nConnection: close\r\nContent-Length: %d\r\nContent-Type: text/html\r\nServer: mbed embedded\r\n\n\r",strlen(szStdErrorPage));
    
    Wifly::getInstance()->sendData(echoHeader, strlen(echoHeader));
    Wifly::getInstance()->sendData((char*)szStdErrorPage, strlen(szStdErrorPage));
}

void HttpServer::HandleRequest(HTTPConnection::HTTPMessage* pmsg)
{
    static std::string localPath;
    static std::map<std::string, HTTPRequestHandler*(*)(const char*, const char*, HTTPConnection::HTTPMessage&), handlersComp>::const_iterator it;

    INFO("Trying to handle request");
    //  Iterate through registered handlers and check if the handler's path is a subset of the requested uri.
    for (it = m_lpHandlers.begin() ; it != m_lpHandlers.end() ; it++) {
        //  check if this entries' path is fully contained at the beginning of the requested path
        std::string curpth = it->first;

        if (pmsg->uri.find(curpth) == 0) {
            // firts matching handler found, we just take it and we'll be happy
            localPath = pmsg->uri.substr(curpth.length());
            break;
        }
    }

    if (it == m_lpHandlers.end()) {
        //  There is no such handler, so return invalid
        INFO("Webrequest left unhandled.");

        m_pErrorHandler(*pmsg);
    } else {
        //  Valid handler was found
        INFO("Routing webrequest !");
        //  Instantiate the handler object (handling will be done from withing the object's constructor
        HTTPRequestHandler *phdl = (*it->second)(it->first.c_str(), localPath.c_str(), *pmsg);
        //  now we can delete the object, because handling is completed.
        if (phdl != NULL)
            delete phdl;
    }
}