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 May 11 14:26:54 2015 +0000
Parent:
17:080f2bed8b36
Child:
19:f67ac231b570
Commit message:
Tidied up - still resets connection when it receives two requests in very short succession - not sure if this is a bug in lwip or something to do with socket limits or lack of threading

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 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';
--- a/RdWebServer.h	Mon May 11 11:17:15 2015 +0000
+++ b/RdWebServer.h	Mon May 11 14:26:54 2015 +0000
@@ -10,6 +10,7 @@
 #include "mbed.h"
 #include "EthernetInterface.h"
 
+// Debug level
 #ifdef RDWEB_DEBUG 
 #if (RDWEB_DEBUG > 3)
 #define RD_DBG(x, ...) std::printf("[RD_DBG: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__);
@@ -50,15 +51,10 @@
 #define RD_ERR(x, ...)
 #endif
 
-//#define SUPPORT_LOCAL_FILESYSTEM 1
-//#define SUPPORT_FOLDER_VIEW 1
-//#define SUPPORT_LOCAL_FILE_CACHE 1
 const int SUBST_MAX_FNAME_LEN = 40;  // note local files on MBED are 8.3 but this might include other files
 
 extern RawSerial pc;
 
-#ifdef SUPPORT_LOCAL_FILE_CACHE
-
 class RdFileCacheEntry
 {
     public:
@@ -81,8 +77,6 @@
         int _nFileLen;
 };
 
-#endif
-
 typedef char* (*CmdCallbackType)(int method, char*cmdStr, char* argStr);
 
 class RdWebServerCmdDef
@@ -133,9 +127,7 @@
         TCPSocketServer _serverSocket;
         bool _initOk;
         std::vector<RdWebServerCmdDef*> _commands;
-#ifdef SUPPORT_LOCAL_FILE_CACHE
         std::vector<RdFileCacheEntry*> _cachedFiles;
-#endif
         bool extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen);
         char* _pBaseWebFolder;
         char _httpHeader[HTTPD_MAX_HDR_LENGTH+1];
@@ -144,9 +136,7 @@
         bool handleLocalFileRequest(char* cmdStr, char* argStr, TCPSocketConnection &client, bool bCacheIfPossible);
         bool handleSDFileRequest(char* cmdStr, char* argStr, TCPSocketConnection &client);
         void handleCGIRequest(char* pUriStr, char* pQueryStr);
-#ifdef SUPPORT_LOCAL_FILE_CACHE
         void sendFromCache(RdFileCacheEntry* pCacheEntry, TCPSocketConnection &client);
-#endif
         bool handleReceivedHttp(TCPSocketConnection &client);
         void formHTTPHeader(const char* rsltCode, const char* contentType, int contentLen);