Single instance HTTP Server using WiFly Interface.

Dependents:   WiFlyHTTPServerSample MultiThreadingHTTPServer

This is my implementation for a HTTP Server using the WiFly Interface. Please note that this is still under development.

It may still contain several bugs. I have tested it using a 1768 on an application board plus RN-XV board.

Currently there is only a FileSystem implemented. Also it is limited to GET request.

I try to extend it further so it will be more useful.

Btw, it does NOT work with RTOS, which seems not to be the Problem of my library.

Do not Forget to Import the WiFly Interface into your Project when using this library.

Change History:

REV5: - added support for basic RPC GET request functionality.

REV4: - added argument parsing from the request uri. - documentation extended and updated.

Revision:
14:7f9fbfc18623
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HttpServer.cpp	Wed Jun 26 22:41:05 2013 +0000
@@ -0,0 +1,477 @@
+#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;
+    }
+}
+