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