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:
28:99036ff32459
Parent:
27:0c2d6f598ae5
--- a/RdWebServer.cpp	Fri Oct 16 08:41:02 2015 +0000
+++ b/RdWebServer.cpp	Mon Feb 08 13:47:29 2016 +0000
@@ -4,23 +4,15 @@
 // http://www.schuurmans.cc/multi-threaded-web-server-for-netduino-plus
 // More details at http://robdobson.com/2013/10/moving-my-window-shades-control-to-mbed/
 
-// Setting RDWEB_DEBUG to 4 causes all debugging to be shown
-#define RDWEB_DEBUG 2
-
-// Change the settings below to support a local file system (not available on some MBEDs)
-#define SUPPORT_LOCAL_FILESYSTEM 1
-#define SUPPORT_LOCAL_FILE_CACHE 1
-
-// Change this to support display of files on the server
-#define SUPPORT_FOLDER_VIEW 1
-
+#include "RdWebServerDefs.h"
 #include "RdWebServer.h"
 
 // Limits - note particularly the MAX_FILENAME_LEN
 const int MAX_FILENAME_LEN = (MAX_ARGSTR_LEN + 20);
 
 // Constructor
-RdWebServer::RdWebServer(Mutex* pSdCardMutex)
+RdWebServer::RdWebServer(TCPSocketServer& tcpServerSocket, Mutex* pSdCardMutex)
+    : _serverSocket(tcpServerSocket)
 {
     _initOk = false;
     _pStatusLed = NULL;
@@ -32,14 +24,17 @@
     _curHttpMethod = METHOD_OTHER;
     _curWebServerCmdDef = NULL;
     _pSdCardMutex = pSdCardMutex;
+    _numWebServerCmds = 0;
+    _numWebServerCachedFiles = 0;
 }
 
 // Destructor
 RdWebServer::~RdWebServer()
 {
     // Clean-up - probably never called as we're on a microcontroller!
-    for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it)
-        delete (*it);
+    for (int i = 0; i < _numWebServerCmds; i++)
+        delete _pWebServerCmds[i];
+    _numWebServerCmds = 0;
 }
 
 // Init
@@ -62,7 +57,6 @@
     _closeConnAfterSend = false;
     _closeConnOnReceiveFail = true;
 
-    // Setup tcp socket
     if(_serverSocket.bind(port)< 0) 
     {
         RD_WARN("TCP server bind fail");
@@ -86,19 +80,17 @@
     // Check initialised ok
     if (!_initOk)
         return;
-    
+
     // Start accepting connections
+    RD_INFO("Waiting for TCP connection");
     while (true)
     {
+        // Accept connection if available
         TCPSocketConnection clientSocketConn;
-        // Accept connection if available
-        RD_INFO("Waiting for TCP connection");
         clientSocketConn.set_blocking(_blockingOnAccept, _timeoutOnBlocking);
-        if(_serverSocket.accept(clientSocketConn)<0) 
-        {
-            RD_WARN("TCP Socket failed to accept connection");
+        _serverSocket.accept(clientSocketConn);
+        if (!clientSocketConn.is_connected())
             continue;
-        }
         
         // Connection
         RD_INFO("Connection from IP: %s", clientSocketConn.get_address());
@@ -147,6 +139,9 @@
                 forcedClosed = true;
             }
         }
+        
+        // Waiting again ...
+        RD_INFO("Waiting for TCP connection");
     }
 }
 
@@ -200,21 +195,22 @@
         RD_DBG("ArgStr %s", _curArgStr);
 
         bool cmdFound = false;
-        for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it)
+        for (int wsCmdIdx = 0; wsCmdIdx < _numWebServerCmds; wsCmdIdx++)
         {
-            if (strcasecmp((*it)->_pCmdStr, _curCmdStr) == 0)
+            RdWebServerCmdDef* pCmd = _pWebServerCmds[wsCmdIdx];
+            if (strcasecmp(pCmd->_pCmdStr, _curCmdStr) == 0)
             {
-                RD_DBG("FoundCmd <%s> Type %d", _curCmdStr, (*it)->_cmdType);
+                RD_DBG("FoundCmd <%s> Type %d", _curCmdStr, pCmd->_cmdType);
                 cmdFound = true;
-                if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
+                if (pCmd->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
                 {
-                    char* respStr = ((*it)->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen, 
+                    char* respStr = (pCmd->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen, 
                                     _curContentLen, pPayload, payloadLen, 0);
                     // Handle split-payload situation
                     if (_curContentLen > 0 && payloadLen < _curContentLen)
                     {
                         _curSplitPayloadPos = payloadLen;
-                        _curWebServerCmdDef = (*it);
+                        _curWebServerCmdDef = pCmd;
                     }
                     else
                     {
@@ -222,17 +218,17 @@
                     }
                     handledOk = true;
                 }
-                else if ( ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) ||
-                          ((*it)->_cmdType == RdWebServerCmdDef::CMD_SDORUSBFILE) )
+                else if ( (pCmd->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) ||
+                          (pCmd->_cmdType == RdWebServerCmdDef::CMD_SDORUSBFILE) )
                 {
                     char combinedFileName[MAX_FILENAME_LEN];
-                    strcpy(combinedFileName, (*it)->_substFileName);
+                    strcpy(combinedFileName, pCmd->_substFileName);
                     if (strlen(combinedFileName) == 0)
                         strcpy(combinedFileName, _curCmdStr);
                     else if (combinedFileName[strlen(combinedFileName)-1] == '*')
                         strcpy(combinedFileName+strlen(combinedFileName)-1, _curArgStr);
-                    if ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE)
-                        handledOk = handleLocalFileRequest(combinedFileName, _curArgStr, clientSocketConn, (*it)->_bCacheIfPossible);
+                    if (pCmd->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE)
+                        handledOk = handleLocalFileRequest(combinedFileName, _curArgStr, clientSocketConn, pCmd->_bCacheIfPossible);
                     else
                         handledOk = handleSDFileRequest(combinedFileName, _curArgStr, clientSocketConn);
 
@@ -263,7 +259,14 @@
 // Add a command to the server
 void RdWebServer::addCommand(char* pCmdStr, int cmdType, CmdCallbackType callback, char* substFileName, bool cacheIfPossible)
 {
-    _commands.push_back(new RdWebServerCmdDef(pCmdStr, cmdType, callback, substFileName, cacheIfPossible));
+    // Check for overflow
+    if (_numWebServerCmds >= MAX_WEB_SERVER_CMDS)
+        return;
+        
+    // Create new command definition and add
+    RdWebServerCmdDef* pNewCmdDef = new RdWebServerCmdDef(pCmdStr, cmdType, callback, substFileName, cacheIfPossible);
+    _pWebServerCmds[_numWebServerCmds] = pNewCmdDef;
+    _numWebServerCmds++;
 }
 
 // Form a header to respond
@@ -297,6 +300,8 @@
 // Handle request for files/listing from SDCard
 bool RdWebServer::handleSDFileRequest(char* inFileName, char* argStr, TCPSocketConnection &clientSocketConn)
 {
+#ifdef SUPPORT_SD_FILESYSTEM
+
     bool handledOk = false;
     const int HTTPD_MAX_FNAME_LENGTH = 127;
     char filename[HTTPD_MAX_FNAME_LENGTH+1];
@@ -388,6 +393,12 @@
 
     }
     return handledOk;
+    
+#else // support SUPPORT_SD_FILESYSTEM
+
+    return false;
+    
+#endif
 }
 
 // Send a file from cache - used for local files only
@@ -433,19 +444,24 @@
     {
         // Check if file already cached
         bool bTryToCache = true;
-        for (std::vector<RdFileCacheEntry*>::iterator it = _cachedFiles.begin() ; it != _cachedFiles.end(); ++it)
+        for (int wsCacheIdx = 0; wsCacheIdx < _numWebServerCachedFiles; wsCacheIdx++)
         {
-            if (strcmp(inFileName, (*it)->_fileName) == 0)
+            RdFileCacheEntry* pCachedFile = _pWebServerCachedFiles[wsCacheIdx];
+            if (strcmp(inFileName, pCachedFile->_fileName) == 0)
             {
-                if ((*it)->_bCacheValid)
+                if (pCachedFile->_bCacheValid)
                 {
-                    sendFromCache(*it, clientSocketConn);
+                    sendFromCache(pCachedFile, clientSocketConn);
                     return true;
                 }
                 bTryToCache = false; // don't try to cache as cacheing must have already failed
             }
         }
         
+        // Check for cache full
+        if (_numWebServerCachedFiles >= MAX_WEB_SERVER_CACHED_FILES)
+            bTryToCache = false;
+            
         // See if we can cache the file
         if (bTryToCache)
         {
@@ -453,8 +469,10 @@
             if (pCacheEntry)
             {
                 bool bCacheSuccess = pCacheEntry->readLocalFileIntoCache(localFilename);
+
                 // Store the cache entry even if reading failed (mem alloc or file not found, etc) so we don't try to cache again
-                _cachedFiles.push_back(pCacheEntry);
+                _pWebServerCachedFiles[_numWebServerCachedFiles] = pCacheEntry;
+                _numWebServerCachedFiles++;
                 if (bCacheSuccess)
                 {
                     sendFromCache(pCacheEntry, clientSocketConn);