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 Feb 08 13:47:29 2016 +0000
Parent:
27:0c2d6f598ae5
Child:
29:46998f2e458f
Commit message:
Restructured to allow support for alternative IP stacks - e.g. CC3000; Also removed dependency on Mutex when SD card not used; And removed dependency on std::vector as it seems unstable

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
RdWebServerDefs.h Show annotated file Show diff for this revision Revisions of this file
--- 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);
--- a/RdWebServer.h	Fri Oct 16 08:41:02 2015 +0000
+++ b/RdWebServer.h	Mon Feb 08 13:47:29 2016 +0000
@@ -7,10 +7,7 @@
 #ifndef RD_WEB_SERVER
 #define RD_WEB_SERVER
 
-#include <vector>
-
 #include "mbed.h"
-#include "EthernetInterface.h"
 
 // Debug level
 #ifdef RDWEB_DEBUG 
@@ -56,6 +53,8 @@
 // Length of strings
 const int MAX_CMDSTR_LEN = 100;
 const int MAX_ARGSTR_LEN = 100;
+const int MAX_WEB_SERVER_CMDS = 30;
+const int MAX_WEB_SERVER_CACHED_FILES = 5;
 const int SUBST_MAX_FNAME_LEN = 40;  // note local files on MBED are 8.3 but this might include other files
 
 extern RawSerial pc;
@@ -113,14 +112,21 @@
 
 const int HTTPD_MAX_HDR_LENGTH = 255;
 // The FRDM-K64F has more memory than the LPC1768 so allow a larger buffer
-#ifdef MCU_MK64F12
+#if defined(MCU_MK64F12)
 const int HTTPD_MAX_REQ_LENGTH = 2048;
 #warning("TCP Request Length 2048")
+#elif defined(TARGET_ARCH_BLE)
+const int HTTPD_MAX_REQ_LENGTH = 1024;
+#warning("TCP Request Length 1024")
 #else
 const int HTTPD_MAX_REQ_LENGTH = 1024;
 #warning("TCP Request Length 1024")
 #endif
 
+class TCPSocketServer;
+class TCPSocketConnection;
+class Mutex;
+
 class RdWebServer
 {
     public :
@@ -128,7 +134,7 @@
         static const int METHOD_GET = 1;
         static const int METHOD_POST = 2;
         static const int METHOD_OPTIONS = 3;
-        RdWebServer(Mutex* pSdCardMutex = NULL);
+        RdWebServer(TCPSocketServer& tcpServerSocket, Mutex* pSdCardMutex = NULL);
         virtual ~RdWebServer();
         
         bool init(int port, DigitalOut* pStatusLed, char* pBaseWebFolder);
@@ -141,10 +147,12 @@
     private :
         int _port;
         DigitalOut* _pStatusLed;
-        TCPSocketServer _serverSocket;
+        TCPSocketServer& _serverSocket;
         bool _initOk;
-        std::vector<RdWebServerCmdDef*> _commands;
-        std::vector<RdFileCacheEntry*> _cachedFiles;
+        RdWebServerCmdDef* _pWebServerCmds[MAX_WEB_SERVER_CMDS];
+        int _numWebServerCmds;
+        RdFileCacheEntry* _pWebServerCachedFiles[MAX_WEB_SERVER_CACHED_FILES];
+        int _numWebServerCachedFiles;
         bool extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen, int& contentLen);
         char* _pBaseWebFolder;
         char _httpHeader[HTTPD_MAX_HDR_LENGTH+1];
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RdWebServerDefs.h	Mon Feb 08 13:47:29 2016 +0000
@@ -0,0 +1,25 @@
+// RdWebServer - Simple Web Server for MBED
+// Copyright (C) Rob Dobson 2013-2016, MIT License
+// Inspired by Jasper Schuurmans multi-threaded web server for Netduino which now seems to have gone from ...
+// 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/
+
+#ifndef RD_WEB_SERVER_DEFS
+#define RD_WEB_SERVER_DEFS
+
+// 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
+//#define SUPPORT_SD_FILESYSTEM 1
+
+// Change this to support display of files on the server
+// #define SUPPORT_FOLDER_VIEW 1
+
+#include "cc3000.h"
+#include "TCPSocketConnection.h"
+#include "TCPSocketServer.h"
+
+#endif