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:
18:5de680c4cfcb
Parent:
17:080f2bed8b36
Child:
19:f67ac231b570
--- a/RdWebServer.cpp	Mon May 11 11:17:15 2015 +0000
+++ b/RdWebServer.cpp	Mon May 11 14:26:54 2015 +0000
@@ -4,20 +4,31 @@
    More details at http://robdobson.com/2013/10/moving-my-window-shades-control-to-mbed/
 */
 
-//#define RDWEB_DEBUG 5
+// Setting RDWEB_DEBUG to 4 causes all debugging to be shown
+#define RDWEB_DEBUG 1
+
+// 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 "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
 RdWebServer::RdWebServer()
 {
     _initOk = false;
     _pStatusLed = NULL;
 }
 
+// Destructor
 RdWebServer::~RdWebServer()
 {
     // Clean-up - probably never called as we're on a microcontroller!
@@ -25,6 +36,7 @@
         delete (*it);
 }
 
+// Init
 bool RdWebServer::init(int port, DigitalOut* pStatusLed, char* pBaseWebFolder)
 {
     // Settings
@@ -66,6 +78,7 @@
     return true;
 }
 
+// Run - never returns so run in a thread
 void RdWebServer::run()
 {
     // Check initialised ok
@@ -117,32 +130,22 @@
             {
                 RD_DBG("clientSocketConn.receive() returned %d - too long\r\n", rxLen);
                 formHTTPHeader("413 Request Entity Too Large", "text/plain", 0);
-//                sprintf(_httpHeader,"HTTP/1.1 413 Request Entity Too Large \r\nContent-Type: text\r\nContent-Length: 0\r\n%s\r\n", closeConnAfterSend ? closeConnStr : "");
                 int sentRet = clientSocketConn.send(_httpHeader,strlen(_httpHeader));
                 continue;
             }
-        
+            
+            // Handle received message
             RD_DBG("Received len %d\r\n", rxLen);
             _buffer[rxLen] = '\0';
             RD_DBG("%s\r\n", _buffer);
-            
-            // Send a response
-//            char* content = "HELLO\r\n";
-//            int sz = strlen(content);
-//            sprintf(_httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n%s\r\n", sz, closeConnAfterSend ? closeConnStr : "");
-//            int sentRet = clientSocketConn.send(_httpHeader,strlen(_httpHeader));
-//            int sentRet2 = clientSocketConn.send(content, strlen(content));
-//            RD_DBG("Sent %s header ret %d content ret %d now connected %d\r\n", content, sentRet, sentRet2, clientSocketConn.is_connected());
-
-                // Handle received message
-                if (handleReceivedHttp(clientSocketConn))
-                {
-                    // OK
-                }
-                else
-                {
-                    // FAIL
-                }
+            if (handleReceivedHttp(clientSocketConn))
+            {
+                // OK
+            }
+            else
+            {
+                // FAIL
+            }
             
             // Close connection if required
             if (_closeConnAfterSend)
@@ -154,60 +157,8 @@
         }
     }
 }
-            
-//            connectLimitTimer.reset();
-//            connectLimitTimer.start();
-//            while(true)
-//            {
-//                // Check connection timer - 10 seconds timeout on HTTP operation
-//                if (connectLimitTimer.read() >= 10)
-//                {
-//                    RD_WARN("Connection timed out\n\r");
-//                    break;
-//                }
-//                // Get received data
-//                int rxLen = clientSocketConn.receive(_buffer, HTTPD_MAX_REQ_LENGTH);
-//                if (rxLen == -1)
-//                {
-//                    RD_WARN("Nothing received\n\r");
-//                    continue;
-//                }
-//                else if (rxLen == 0)
-//                {
-//                    RD_WARN("received buffer is empty.\n\r");
-//                    break;                
-//                }
-//                else if (rxLen > HTTPD_MAX_REQ_LENGTH)
-//                {
-//                    sprintf(_httpHeader,"HTTP/1.1 413 Request Entity Too Large \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
-//                    clientSocketConn.send(_httpHeader,strlen(_httpHeader));
-//                    clientSocketConn.send(_buffer, rxLen);
-//                    break;
-//                }
-//                _buffer[rxLen] = '\0';
-//    
-//                // Handle buffer
-//                if (handleReceivedHttp(clientSocketConn))
-//                {
-//                    break;
-//                }
-//                else
-//                {
-//                    connectLimitTimer.reset();
-//                    connectLimitTimer.start();
-//                }
-//            }
-//            
-//            // Connection now closed
-//            RD_INFO("Connection closed ...\r\n");
-//            clientSocketConn.close();
-//            if (_pStatusLed != NULL)
-//                *_pStatusLed = false;
-//        }
-//        
-//    }
-//}
-//
+
+// Handle a request            
 bool RdWebServer::handleReceivedHttp(TCPSocketConnection &clientSocketConn)
 {
     bool handledOk = false;
@@ -268,11 +219,13 @@
     return handledOk;
 }
 
+// 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));
 }
 
+// Form a header to respond
 void RdWebServer::formHTTPHeader(const char* rsltCode, const char* contentType, int contentLen)
 {
     const char* closeConnStr = "\r\nConnection: Close";
@@ -281,7 +234,8 @@
     else
         sprintf(_httpHeader, "HTTP/1.1 %s\r\nContent-Type: %s%s\r\n\r\n", rsltCode, contentType, _closeConnAfterSend ? closeConnStr : "");
 }
-    
+
+// Use file extension to determine MIME type
 char* getMimeTypeStr(char* filename)
 {
     char* mimeType = "text/plain";
@@ -298,7 +252,8 @@
         mimeType = "image/jpeg";
     return mimeType;
 }
-    
+
+// Handle request for files/listing from SDCard
 bool RdWebServer::handleSDFileRequest(char* inFileName, char* argStr, TCPSocketConnection &clientSocketConn)
 {
     bool handledOk = false;
@@ -356,7 +311,7 @@
             fseek(fp, 0L, SEEK_SET);
             // Get mime type
             char* mimeType = getMimeTypeStr(filename);
-            RD_INFO("MIME TYPE %s", mimeType);
+            RD_DBG("MIME TYPE %s", mimeType);
             // Form header   
             formHTTPHeader("200 OK", mimeType, sz);
             clientSocketConn.send(_httpHeader,strlen(_httpHeader));
@@ -375,8 +330,8 @@
     return handledOk;
 }
 
+// Send a file from cache - used for local files only
 #ifdef SUPPORT_LOCAL_FILE_CACHE
-
 void RdWebServer::sendFromCache(RdFileCacheEntry* pCacheEntry, TCPSocketConnection &clientSocketConn)
 {
     RD_INFO("Sending file %s from cache %d bytes\r\n", pCacheEntry->_fileName, pCacheEntry->_nFileLen);
@@ -400,6 +355,7 @@
 }
 #endif
 
+// Handle a request for a local file
 bool RdWebServer::handleLocalFileRequest(char* inFileName, char* argStr, TCPSocketConnection &clientSocketConn, bool bCacheIfPossible)
 {
 
@@ -467,7 +423,7 @@
         fseek(fp, 0L, SEEK_SET);
         // Get mime type
         char* mimeType = getMimeTypeStr(localFilename);
-        RD_INFO("MIME TYPE %s", mimeType);
+        RD_DBG("MIME TYPE %s", mimeType);
         // Form header   
         formHTTPHeader("200 OK", mimeType, sz);
         clientSocketConn.send(_httpHeader,strlen(_httpHeader));
@@ -492,8 +448,8 @@
 #endif
 }
 
+// Read file into cache
 #ifdef SUPPORT_LOCAL_FILE_CACHE
-
 bool RdFileCacheEntry::readLocalFileIntoCache(char* fileName)
 {
 #ifdef SUPPORT_LOCAL_FILESYSTEM
@@ -552,6 +508,7 @@
 
 #endif
 
+// Extract arguments from command
 bool RdWebServer::extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen)
 {
     *pCmdStr = '\0';