A simple web server mainly based on ideas from Jasper Schuurmans Netduino web server

Dependents:   RdBlindsServer SpideyWallWeb RdGasUseMonitor

A fast and reliable web server for MBED! http://robdobson.com/2015/08/a-reliable-mbed-webserver/

It has a very neat way to implement REST commands and can serve files from local storage (on LPC1768 for instance) and from SD cards. It also has a caching facility which is particularly useful for serving files from local storage.

The server can be run in the main() thread (and has a sub-2ms response time if this is done) or in a mbed-rtos thread which increases the response time to (a still respectable) 30ms or so.

The latest project that uses this is here - https://developer.mbed.org/users/Bobty/code/SpideyWallWeb/

int main (void)
{
    // Ethernet interface
    EthernetInterface::init();

    // Connect ethernet
    EthernetInterface::connect();

    // Init the web server
    pc.printf("Starting web server\r\n");
    char* baseWebFolder = "/sd/";  // should be /sd/ for SDcard files - not used for local file system
    RdWebServer webServer;
    
    // Add commands to handle the home page and favicon
    webServer.addCommand("", RdWebServerCmdDef::CMD_LOCALFILE, NULL, "index.htm", true);
    webServer.addCommand("favicon.ico", RdWebServerCmdDef::CMD_LOCALFILE, NULL, NULL, true);
    
    // Add the lightwall control commands
    webServer.addCommand("name", RdWebServerCmdDef::CMD_CALLBACK, &lightwallGetSystemName);
    webServer.addCommand("clear", RdWebServerCmdDef::CMD_CALLBACK, &lightwallClear);
    webServer.addCommand("rawfill", RdWebServerCmdDef::CMD_CALLBACK, &lightwallRawFill);
    webServer.addCommand("fill", RdWebServerCmdDef::CMD_CALLBACK, &lightwallFill);
    webServer.addCommand("showleds", RdWebServerCmdDef::CMD_CALLBACK, &lightwallShowLeds);
    
    // Start the server
    webServer.init(WEBPORT, &led4, baseWebFolder);
    webServer.run();

}

// Get system name - No arguments required
char* lightwallGetSystemName(int method, char*cmdStr, char* argStr, char* msgBuffer, int msgLen, 
                int contentLen, unsigned char* pPayload, int payloadLen, int splitPayloadPos)
{
    // Perform any required actions here ....

    // ...

    // Return the system name
    return systemName;
}

This server was originally based on a Netduino web server from Jasper Schuurmans but has been optimised for speed.

Files at this revision

API Documentation at this revision

Comitter:
Bobty
Date:
Mon Aug 31 15:00:41 2015 +0000
Parent:
23:0fc3d7b5e596
Child:
25:ffa1dddd3da4
Commit message:
Implemented handling of split-payloads in HTTP requests. This is done by sending a registered command each chunk of payload data as it arrives with an index indicating where the data fits in the original message.

Changed in this revision

RdWebServer.cpp Show annotated file Show diff for this revision Revisions of this file
RdWebServer.h Show annotated file Show diff for this revision Revisions of this file
--- a/RdWebServer.cpp	Mon Aug 31 08:54:17 2015 +0000
+++ b/RdWebServer.cpp	Mon Aug 31 15:00:41 2015 +0000
@@ -17,8 +17,6 @@
 #include "RdWebServer.h"
 
 // Limits - note particularly the MAX_FILENAME_LEN
-const int MAX_CMDSTR_LEN = 100;
-const int MAX_ARGSTR_LEN = 100;
 const int MAX_FILENAME_LEN = (MAX_ARGSTR_LEN + 20);
 
 // Constructor
@@ -26,6 +24,13 @@
 {
     _initOk = false;
     _pStatusLed = NULL;
+    _curSplitPayloadPos = -1;
+    _curCmdStr[0] = 0;
+    _curArgStr[0] = 0;
+    _bufferReceivedLen = 0;
+    _curContentLen = -1;
+    _curHttpMethod = METHOD_OTHER;
+    _curWebServerCmdDef = NULL;
 }
 
 // Destructor
@@ -101,6 +106,7 @@
         
         // While connected
         bool forcedClosed = false;
+        _curSplitPayloadPos = -1;
         while(clientSocketConn.is_connected() && !forcedClosed)
         {
             // Receive data
@@ -123,24 +129,11 @@
                 RD_DBG("clientSocketConn.receive() returned %d - ignoring -  is connected %d", rxLen, clientSocketConn.is_connected());
                 continue;
             }
-            if (rxLen > HTTPD_MAX_REQ_LENGTH)
-            {
-                RD_DBG("clientSocketConn.receive() returned %d - too long", rxLen);
-                formHTTPHeader("413 Request Entity Too Large", "text/plain", 0);
-                int sentRet = clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
-                continue;
-            }
             
             // Handle received message
             _buffer[rxLen] = '\0';
-            if (handleReceivedHttp(clientSocketConn))
-            {
-                // OK
-            }
-            else
-            {
-                // FAIL
-            }
+            _bufferReceivedLen = rxLen;
+            handleReceivedHttp(clientSocketConn);
             
             // Close connection if required
             if (_closeConnAfterSend)
@@ -157,33 +150,73 @@
 bool RdWebServer::handleReceivedHttp(TCPSocketConnection &clientSocketConn)
 {
     bool handledOk = false;
-    RD_DBG("Received Data: %d\n\r\n\r%.*s",strlen(_buffer),strlen(_buffer),_buffer);
-    int method = METHOD_OTHER;
+    
+    // Check for split payload
+    if (_curSplitPayloadPos != -1)
+    {
+        // Handle remaining parts of content
+        char* respStr = (_curWebServerCmdDef->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen, 
+                        _curContentLen, (unsigned char*)_buffer, _bufferReceivedLen, _curSplitPayloadPos);
+        RD_DBG("Received part of message - content %d - rx %d - splitAfter %d", _curContentLen, _bufferReceivedLen, _curSplitPayloadPos);
+        _curSplitPayloadPos += _bufferReceivedLen;
+        // Check if all received
+        if (_curSplitPayloadPos >= _curContentLen)
+        {
+            _curSplitPayloadPos = -1;
+            clientSocketConn.send_all(respStr, strlen(respStr));
+            RD_DBG("That was the end of message - content %d - rx %d", _curContentLen, _bufferReceivedLen);
+        }
+        return true;
+    }
+
+    // Get payload information    
+    int payloadLen = -1;
+    unsigned char* pPayload = getPayloadDataFromMsg(_buffer, _bufferReceivedLen, payloadLen);
+    
+    // Debug
+    int displayLen = _bufferReceivedLen;
+    if (payloadLen > 0)
+        displayLen = _bufferReceivedLen-payloadLen;
+    RD_DBG("\r\nNEW REQUEST RxLen %d ContentLen %d PayloadLen %d\n\r\n\r%.*s", _bufferReceivedLen, _curContentLen, payloadLen, displayLen, _buffer);
+    
+    // Get HTTP method
+    _curHttpMethod = METHOD_OTHER;
     if (strncmp(_buffer, "GET ", 4) == 0)
-        method = METHOD_GET;
+        _curHttpMethod = METHOD_GET;
     else if (strncmp(_buffer, "POST", 4) == 0)
-        method = METHOD_POST;
+        _curHttpMethod = METHOD_POST;
     else if (strncmp(_buffer, "OPTIONS", 7) == 0)
-        method = METHOD_OPTIONS;
+        _curHttpMethod = METHOD_OPTIONS;
 
-    char cmdStr[MAX_CMDSTR_LEN];
-    char argStr[MAX_ARGSTR_LEN];
-    if (extractCmdArgs(_buffer+3, cmdStr, MAX_CMDSTR_LEN, argStr, MAX_ARGSTR_LEN))
+    // See if there is a valid HTTP command
+    _curContentLen = -1;
+    if (extractCmdArgs(_buffer+3, _curCmdStr, MAX_CMDSTR_LEN, _curArgStr, MAX_ARGSTR_LEN, _curContentLen))
     {
-        RD_DBG("CmdStr %s", cmdStr);
-        RD_DBG("ArgStr %s", argStr);
+        RD_DBG("CmdStr %s", _curCmdStr);
+        RD_DBG("ArgStr %s", _curArgStr);
+
         bool cmdFound = false;
         for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it)
         {
-            RD_DBG("Testing <<%s>> with <<%s>>", (*it)->_pCmdStr, cmdStr);
-            if (strcasecmp((*it)->_pCmdStr, cmdStr) == 0)
-            {                                    
-                RD_DBG("FoundCmd <%s> Type %d", cmdStr, (*it)->_cmdType);
+            if (strcasecmp((*it)->_pCmdStr, _curCmdStr) == 0)
+            {
+                RD_DBG("FoundCmd <%s> Type %d", _curCmdStr, (*it)->_cmdType);
                 cmdFound = true;
                 if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
                 {
-                    char* respStr = ((*it)->_callback)(method, cmdStr, argStr, _buffer);
-                    clientSocketConn.send_all(respStr, strlen(respStr));
+                    char* respStr = ((*it)->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen, 
+                                    _curContentLen, pPayload, payloadLen, 0);
+                    // Handle split-payload situation
+                    if (_curContentLen > 0 && payloadLen < _curContentLen)
+                    {
+                        _curSplitPayloadPos = payloadLen;
+                        _curWebServerCmdDef = (*it);
+                    }
+                    else
+                    {
+                        clientSocketConn.send_all(respStr, strlen(respStr));
+                    }
+                    handledOk = true;
                 }
                 else if ( ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) ||
                           ((*it)->_cmdType == RdWebServerCmdDef::CMD_SDORUSBFILE) )
@@ -191,13 +224,13 @@
                     char combinedFileName[MAX_FILENAME_LEN];
                     strcpy(combinedFileName, (*it)->_substFileName);
                     if (strlen(combinedFileName) == 0)
-                        strcpy(combinedFileName, cmdStr);
+                        strcpy(combinedFileName, _curCmdStr);
                     else if (combinedFileName[strlen(combinedFileName)-1] == '*')
-                        strcpy(combinedFileName+strlen(combinedFileName)-1, argStr);
+                        strcpy(combinedFileName+strlen(combinedFileName)-1, _curArgStr);
                     if ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE)
-                        handledOk = handleLocalFileRequest(combinedFileName, argStr, clientSocketConn, (*it)->_bCacheIfPossible);
+                        handledOk = handleLocalFileRequest(combinedFileName, _curArgStr, clientSocketConn, (*it)->_bCacheIfPossible);
                     else
-                        handledOk = handleSDFileRequest(combinedFileName, argStr, clientSocketConn);
+                        handledOk = handleSDFileRequest(combinedFileName, _curArgStr, clientSocketConn);
 
                 }
                 break;
@@ -207,12 +240,16 @@
         if (!cmdFound)
         {
             char combinedFileName[MAX_FILENAME_LEN];
-            strcpy(combinedFileName, cmdStr);
+            strcpy(combinedFileName, _curCmdStr);
             strcat(combinedFileName, "/");
-            strcat(combinedFileName, argStr);
-            handledOk = handleSDFileRequest(cmdStr, argStr, clientSocketConn);
+            strcat(combinedFileName, _curArgStr);
+            handledOk = handleSDFileRequest(combinedFileName, _curArgStr, clientSocketConn);
         }
     }
+    else
+    {
+        RD_DBG("Cannot find command or args\r\n");
+    }
     return handledOk;
 }
 
@@ -507,14 +544,24 @@
 #endif
 
 // Extract arguments from command
-bool RdWebServer::extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen)
+bool RdWebServer::extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen, int& contentLen)
 {
+    contentLen = -1;
     *pCmdStr = '\0';
     *pArgStr = '\0';
     int cmdStrLen = 0;
     int argStrLen = 0;
     if (buf == NULL)
         return false;
+    // Check for Content-length header
+    char* contentLenText = "Content-Length:";
+    char* pContLen = strstr(buf, contentLenText);
+    if (pContLen)
+    {
+        if (*(pContLen + strlen(contentLenText)) != '\0')
+            contentLen = atoi(pContLen + strlen(contentLenText));
+    }
+    
     // Check for first slash
     char* pSlash1 = strchr(buf, '/');
     if (pSlash1 == NULL)
@@ -548,15 +595,19 @@
     return true;
 }
 
-unsigned char* RdWebServer::getPayloadDataFromMsg(char* msgBuf)
+unsigned char* RdWebServer::getPayloadDataFromMsg(char* msgBuf, int msgLen, int& payloadLen)
 {
+    payloadLen = -1;
     char* ptr = strstr(msgBuf, "\r\n\r\n");
     if (ptr)
+    {
+        payloadLen = msgLen - (ptr+4-msgBuf);
         return (unsigned char*) (ptr+4);
-    return (unsigned char*) "\0";
+    }
+    return NULL;
 }
 
-int RdWebServer::getPayloadLengthFromMsg(char* msgBuf)
+int RdWebServer::getContentLengthFromMsg(char* msgBuf)
 {
     char* ptr = strstr(msgBuf, "Content-Length:");
     if (ptr)
--- a/RdWebServer.h	Mon Aug 31 08:54:17 2015 +0000
+++ b/RdWebServer.h	Mon Aug 31 15:00:41 2015 +0000
@@ -53,6 +53,9 @@
 #define RD_ERR(x, ...)
 #endif
 
+// Length of strings
+const int MAX_CMDSTR_LEN = 100;
+const int MAX_ARGSTR_LEN = 100;
 const int SUBST_MAX_FNAME_LEN = 40;  // note local files on MBED are 8.3 but this might include other files
 
 extern RawSerial pc;
@@ -79,7 +82,8 @@
         int _nFileLen;
 };
 
-typedef char* (*CmdCallbackType)(int method, char*cmdStr, char* argStr, char* msgBuffer);
+typedef char* (*CmdCallbackType)(int method, char*cmdStr, char* argStr, char* msgBuffer, int msgLen, 
+                int contentLen, unsigned char* pPayload, int payloadLen, int splitPayloadPos);
 
 class RdWebServerCmdDef
 {
@@ -131,8 +135,8 @@
         void run();
         void addCommand(char* pCmdStr, int cmdType, CmdCallbackType callback = NULL, char* substFileName = NULL, bool cacheIfPossible = false);
         
-        static unsigned char* getPayloadDataFromMsg(char* msgBuf);
-        static int getPayloadLengthFromMsg(char* msgBuf);
+        static unsigned char* getPayloadDataFromMsg(char* msgBuf, int msgLen, int& payloadLen);
+        static int getContentLengthFromMsg(char* msgBuf);
 
     private :
         int _port;
@@ -141,10 +145,11 @@
         bool _initOk;
         std::vector<RdWebServerCmdDef*> _commands;
         std::vector<RdFileCacheEntry*> _cachedFiles;
-        bool extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen);
+        bool extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen, int& contentLen);
         char* _pBaseWebFolder;
         char _httpHeader[HTTPD_MAX_HDR_LENGTH+1];
         char _buffer[HTTPD_MAX_REQ_LENGTH+1];
+        int _bufferReceivedLen;
 
         bool handleLocalFileRequest(char* cmdStr, char* argStr, TCPSocketConnection &client, bool bCacheIfPossible);
         bool handleSDFileRequest(char* cmdStr, char* argStr, TCPSocketConnection &client);
@@ -159,6 +164,14 @@
         int _timeoutOnBlocking;
         bool _closeConnAfterSend;
         bool _closeConnOnReceiveFail;
+        
+        // Handling of split payloads on receipt (e.g. POST)
+        int _curSplitPayloadPos;
+        char _curCmdStr[MAX_CMDSTR_LEN];
+        char _curArgStr[MAX_ARGSTR_LEN];
+        int _curContentLen;
+        int _curHttpMethod;
+        RdWebServerCmdDef* _curWebServerCmdDef;
 
 };