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.

Revision:
24:27800de38eab
Parent:
23:0fc3d7b5e596
Child:
25:ffa1dddd3da4
--- 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)