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.

HttpServer.cpp

Committer:
leihen
Date:
2013-06-26
Revision:
14:7f9fbfc18623

File content as of revision 14:7f9fbfc18623:

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