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.

Files at this revision

API Documentation at this revision

Comitter:
leihen
Date:
Wed Jun 26 22:41:05 2013 +0000
Parent:
13:93ff322420b0
Commit message:
Moved the HttpServer module inside this library.

Changed in this revision

HttpServer.cpp Show annotated file Show diff for this revision Revisions of this file
HttpServer.h Show annotated file Show diff for this revision Revisions of this file
diff -r 93ff322420b0 -r 7f9fbfc18623 HttpServer.cpp
--- /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;
+    }
+}
+
diff -r 93ff322420b0 -r 7f9fbfc18623 HttpServer.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HttpServer.h	Wed Jun 26 22:41:05 2013 +0000
@@ -0,0 +1,132 @@
+#ifndef __HTTPSERVER_H__
+#define __HTTPSERVER_H__
+
+#include "Wifly.h"
+#include "rtos.h"
+#include "HTTPConnection.h"
+#include "HTTPRequestHandler.h"
+
+#include <string>
+#include <map>
+
+typedef enum {
+    msg_get,
+    msg_post,
+    msg_head,
+    msg_put,
+    msg_delete,
+    msg_trace,
+    msg_options,
+    msg_connect
+} msg_t;
+
+
+typedef struct {
+    msg_t                       requestType;
+    char                        requestUri[256];
+//    map<string, string>         messageHeaders;
+} request_msg_t;
+
+/** Typedefinition for a handler function
+*/
+typedef void (*HTTPRequestHandlerFunction)(HTTPConnection::HTTPMessage&);
+
+
+/** This is the non-blocking HTTP Server class. The idea behind this class is as follows:
+  * the user may instantiate the class and initialize it. Once the server is setup and 
+  * listening, the server will stay in an endless loop and keep on listening for new 
+  * connections and for new HTTP requests. Once a request is received it will be placed 
+  * in a queue. The queue itself will be handled in a separate task.
+  */
+class HttpServer : public Wifly
+{
+        Thread      *m_listener;
+        Thread      *m_worker;
+
+        request_msg_t*  checkMessageReceived(char *);
+        void            processMessageHeader(char* headerLine, char **fieldname, char **fieldvalue);        
+        
+    public:
+        HttpServer(PinName tx, PinName rx, PinName reset, PinName tcp_status, const char * ssid, const char * phrase, Security sec, Wifly::WiflyBaudrate_t baud = Wifly::Wifly_115200);
+        ~HttpServer();
+        
+        bool start(int port);
+        
+        virtual void handler_rx(void);
+        virtual void attach_rx(bool);
+        
+        virtual bool join();
+        virtual int send(const char * str, int len, const char * ACK = NULL, char * res = NULL, int timeout = DEFAULT_WAIT_RESP_TIMEOUT);
+        
+    public:
+        
+        
+        /**
+        * Structure which will allow to order the stored handlers according to their associated path.
+        */
+        struct handlersComp //Used to order handlers in the right way
+        {
+            bool operator() (const string& handler1, const string& handler2) const
+            {
+                //The first handler is longer than the second one
+                if (handler1.length() > handler2.length())
+                    return true; //Returns true if handler1 is to appear before handler2
+                else if (handler1.length() < handler2.length())
+                    return false;
+                else //To avoid the == case, sort now by address
+                    return ((&handler1)>(&handler2));
+            }
+        };
+        /** The standard error handler function.
+        * @param msg : Request message data.
+        * @param tcp : Socket to be used for responding.
+        */
+        static void StdErrorHandler(HTTPConnection::HTTPMessage& msg);
+
+
+        /** Internal function which processes a request and which will try to find the matching handler function
+        * for the given request. Please note that the function will search through the list of handlers, iterating
+        * from longest to shortest \c paths. If the registered \c path is a subset of the request the associated
+        * handler is considered as being a match.
+        * @param msg : Request message data. Contains the requested logical \c uri. 
+        * @param tcp : Socket to be used for communication with the client.
+        */
+        void HandleRequest(HTTPConnection::HTTPMessage* msg);
+    
+        /** Map of handler objects. Can be any object derived from \ref HTTPRequestHeader. Use the \ref addHandler function
+        * to register new handler objects.
+        */
+        static map<string, HTTPRequestHandler* (*)(const char*, const char*, HTTPConnection::HTTPMessage&), handlersComp>   m_lpHandlers;
+
+        /**
+        * Adds a request handler to the handlers list. You will have to use one of the existing implementations.
+        * With each handler a \c uri or \c path is associated. Whenever a request is received the server will
+        * walk through all registered handlers and check which \c path is matching.
+        * @param T : class which will be instanciated to serve these requests for the associated \b path.
+        * @param path : request uri starting with this \c path will be served using this handler.
+        */
+        template<typename T>
+        void addHandler(const char* path)
+        { m_lpHandlers[path] = &T::create; }
+    
+        /**
+        * Replaces the standard error Handler. The error Handler will be called everytime a request is not
+        * matching any of the registered \c paths or \c uris.
+        * @param hdlFunc: User specified handler function which will be used in error conditions.
+        */
+        void addErrorHandler(HTTPRequestHandlerFunction hdlFunc)
+        { m_pErrorHandler = hdlFunc!=NULL ?hdlFunc : StdErrorHandler; }    
+        HTTPRequestHandlerFunction m_pErrorHandler;
+
+    protected:
+        bool bind(int port);
+        void listenForRequests();
+        void serveRequests();
+        
+        bool parseRequest(char *request);
+        
+        static void listen_thread(const void * parms);
+        static void worker_thread(const void * parms);
+};
+
+#endif //   __HTTPSERVER_H__
\ No newline at end of file