Working Multithreaded HTTP Server using WiFly module. Currently only supporting GET method.
Dependencies: WiFlyHTTPServer WiflyInterface mbed-rpc mbed-rtos mbed
RTOS Wifly HTTP Server
This sample application demonstrates how the HTTP Server can be used in a multithreaded (RTOS) Environment. However currently only the GET method is supported.
Revision 0:9c6ebc97c758, committed 2013-06-26
- Comitter:
- leihen
- Date:
- Wed Jun 26 21:13:55 2013 +0000
- Child:
- 1:40eadac4750b
- Commit message:
- Working Multithreaded HTTP Server
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/HttpServer.cpp Wed Jun 26 21:13:55 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; + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/HttpServer.h Wed Jun 26 21:13:55 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WiFlyHTTPServer.lib Wed Jun 26 21:13:55 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/leihen/code/WiFlyHTTPServer/#93ff322420b0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WiflyInterface.lib Wed Jun 26 21:13:55 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/leihen/code/WiflyInterface/#63a7dc9e45dd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/debug.h Wed Jun 26 21:13:55 2013 +0000 @@ -0,0 +1,16 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + + +#ifdef DEBUG +#define INFO(x, ...) std::printf("[INFO: %s:%d]"x"\r\n", __FILE__, __LINE__, ##__VA_ARGS__); +#define WARN(x, ...) std::printf("[WARN: %s:%d]"x"\r\n", __FILE__, __LINE__, ##__VA_ARGS__); +#define ERR(x, ...) std::printf("[ERR: %s:%d]"x"\r\n", __FILE__, __LINE__, ##__VA_ARGS__); +#else +#define INFO(x, ...) +#define WARN(x, ...) +#define ERR(x, ...) +#endif + + +#endif // __DEBUG_H__ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Wed Jun 26 21:13:55 2013 +0000 @@ -0,0 +1,32 @@ +#include "mbed.h" +#include "HttpServer.h" +#include "FsHandler.h" +#include "LocalFileSystem.h" + +DigitalOut myled(LED1); + +Serial pc(USBTX, USBRX, "pc"); + +static LocalFileSystem local("local"); + + +int main() { + pc.baud(460800); + + HTTPFsRequestHandler::mount("/local/", "/"); + + HttpServer svr(p9, p10, p30, p29, "Spawnpoint", "Quantenoptik1", WPA); + + svr.addHandler<HTTPFsRequestHandler>("/"); + // Start the server + if (!svr.start(80) ) { + error("SERVER FAILED TO START"); + } + + while(1) { + myled = 1; + wait(0.2); + myled = 0; + wait(0.2); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-rpc.lib Wed Jun 26 21:13:55 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed-rpc/#1ecadde1c929
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-rtos.lib Wed Jun 26 21:13:55 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed-rtos/#58b30ac3f00e
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed.bld Wed Jun 26 21:13:55 2013 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed/builds/b3110cd2dd17 \ No newline at end of file