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:
23:0fc3d7b5e596
Parent:
22:598a21373539
Child:
24:27800de38eab
--- a/RdWebServer.cpp	Sat Aug 29 06:08:00 2015 +0000
+++ b/RdWebServer.cpp	Mon Aug 31 08:54:17 2015 +0000
@@ -44,30 +44,26 @@
     _pStatusLed = pStatusLed;
     _pBaseWebFolder = pBaseWebFolder;
 
-    // The sample web server on MBED site turns off blocking after the accept has happened
-    // I've tried this but it doesn't seem to yield a reliable server
-    _blockingOnAccept = true;
-    _blockingOnReceive = true;
+    // Non-blocking by default
+    _blockingOnAccept = false;
+    _blockingOnReceive = false;
     
     // This is the same as the default in the socket.cpp file
     _timeoutOnBlocking = 1500;
     
-    // Currently tested using close connection after send with a single file which works fine
     // If closeConnAfterSend is set false then the socket connection remains open and only
-    // one client can access the server at a time
-    // Need to test with the closeConnAfterSend seetting true when trying to download multiple files
-    // for a website as previous experience would indicate that requests might be missed in this scenario
-    // although all other settings may not have been the same
-    _closeConnAfterSend = true;
+    // one client can access the server until a time-out occurs - this should be ok for most applications
+    _closeConnAfterSend = false;
     _closeConnOnReceiveFail = true;
 
     // Setup tcp socket
-    _serverSocket.set_blocking(true);
     if(_serverSocket.bind(port)< 0) 
     {
         RD_WARN("TCP server bind fail");
         return false;
     }
+    
+    // Only one connection at a time as the server is single-threaded
     if(_serverSocket.listen(1) < 0)
     {
         RD_WARN("TCP server listen fail");
@@ -108,7 +104,8 @@
         while(clientSocketConn.is_connected() && !forcedClosed)
         {
             // Receive data
-            clientSocketConn.set_blocking(_blockingOnReceive, _timeoutOnBlocking);
+            if (_blockingOnReceive != _blockingOnAccept)
+                clientSocketConn.set_blocking(_blockingOnReceive, _timeoutOnBlocking);
             int rxLen = clientSocketConn.receive(_buffer, HTTPD_MAX_REQ_LENGTH);
             if (rxLen == -1)
             {
@@ -130,7 +127,7 @@
             {
                 RD_DBG("clientSocketConn.receive() returned %d - too long", rxLen);
                 formHTTPHeader("413 Request Entity Too Large", "text/plain", 0);
-                int sentRet = clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+                int sentRet = clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
                 continue;
             }
             
@@ -164,8 +161,10 @@
     int method = METHOD_OTHER;
     if (strncmp(_buffer, "GET ", 4) == 0)
         method = METHOD_GET;
-    if (strncmp(_buffer, "POST", 4) == 0)
+    else if (strncmp(_buffer, "POST", 4) == 0)
         method = METHOD_POST;
+    else if (strncmp(_buffer, "OPTIONS", 7) == 0)
+        method = METHOD_OPTIONS;
 
     char cmdStr[MAX_CMDSTR_LEN];
     char argStr[MAX_ARGSTR_LEN];
@@ -184,7 +183,7 @@
                 if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
                 {
                     char* respStr = ((*it)->_callback)(method, cmdStr, argStr, _buffer);
-                    clientSocketConn.send(respStr, strlen(respStr));
+                    clientSocketConn.send_all(respStr, strlen(respStr));
                 }
                 else if ( ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) ||
                           ((*it)->_cmdType == RdWebServerCmdDef::CMD_SDORUSBFILE) )
@@ -226,7 +225,7 @@
 // Form a header to respond
 void RdWebServer::formHTTPHeader(const char* rsltCode, const char* contentType, int contentLen)
 {
-    const char* closeConnStr = "\r\nConnection: Close";
+    const char* closeConnStr = "\r\nConnection: keep-alive";
     if(contentLen != -1)
         sprintf(_httpHeader, "HTTP/1.1 %s\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: %s\r\nContent-Length: %d%s\r\n\r\n", rsltCode, contentType, contentLen, _closeConnAfterSend ? closeConnStr : "");
     else
@@ -269,21 +268,21 @@
         if (d != NULL) 
         {
             formHTTPHeader("200 OK", "text/html", -1);
-            clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+            clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
             sprintf(_httpHeader,"<html><head><title>Directory Listing</title></head><body><h1>%s</h1><ul>", inFileName);
-            clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+            clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
             struct dirent *p;
             while((p = readdir(d)) != NULL) 
             {
                 RD_INFO("%s", p->d_name);
                 sprintf(_httpHeader,"<li>%s</li>", p->d_name);
-                clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+                clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
             }
         }
         closedir(d);
         RD_DBG("Directory closed\n");
         sprintf(_httpHeader,"</ul></body></html>");
-        clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+        clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
         handledOk = true;
     }
     else
@@ -297,7 +296,7 @@
         {
             RD_WARN("Filename %s not found", filename);
             formHTTPHeader("404 Not Found", "text/plain", 0);
-            clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+            clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
             handledOk = true;
         }
         else
@@ -312,15 +311,15 @@
             RD_DBG("MIME TYPE %s", mimeType);
             // Form header   
             formHTTPHeader("200 OK", mimeType, sz);
-            clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+            clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
             // Read file in blocks and send
             int rdCnt = 0;
             char fileBuf[1024];
             while ((rdCnt = fread(fileBuf, sizeof( char ), 1024, fp)) == 1024) 
             {
-                clientSocketConn.send(fileBuf, rdCnt);
+                clientSocketConn.send_all(fileBuf, rdCnt);
             }
-            clientSocketConn.send(fileBuf, rdCnt);
+            clientSocketConn.send_all(fileBuf, rdCnt);
             fclose(fp);
             handledOk = true;
         }
@@ -338,7 +337,7 @@
     RD_INFO("MIME TYPE %s", mimeType);
     // Form header
     formHTTPHeader("200 OK", mimeType, pCacheEntry->_nFileLen);
-    clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+    clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
     char* pMem = pCacheEntry->_pFileContent;
     int nLenLeft = pCacheEntry->_nFileLen;
     int blkSize = 2048;
@@ -346,7 +345,7 @@
     {
         if (blkSize > nLenLeft)
             blkSize = nLenLeft;
-        clientSocketConn.send(pMem, blkSize);
+        clientSocketConn.send_all(pMem, blkSize);
         pMem += blkSize;
         nLenLeft -= blkSize;
     }
@@ -409,7 +408,7 @@
     {
         RD_WARN("Local file %s not found", localFilename);
         formHTTPHeader("404 Not Found", "text/plain", 0);
-        clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+        clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
         return true;
     }
     else
@@ -424,14 +423,14 @@
         RD_DBG("MIME TYPE %s", mimeType);
         // Form header   
         formHTTPHeader("200 OK", mimeType, sz);
-        clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+        clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
         int rdCnt = 0;
         while ((rdCnt = fread(_httpHeader, sizeof( char ), sizeof(_httpHeader), fp)) == sizeof(_httpHeader)) 
         {
-            clientSocketConn.send(_httpHeader, rdCnt);
+            clientSocketConn.send_all(_httpHeader, rdCnt);
         }
         if (rdCnt != 0)
-            clientSocketConn.send(_httpHeader, rdCnt);
+            clientSocketConn.send_all(_httpHeader, rdCnt);
         fclose(fp);
         return true;
     }
@@ -440,7 +439,7 @@
 
     RD_WARN("Local file system not supported");
     formHTTPHeader("404 Not Found", "text/plain", 0);
-    clientSocketConn.send(_httpHeader,strlen(_httpHeader));
+    clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
     return true;
 
 #endif