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 Aug 31 08:54:17 2015 +0000
Parent:
22:598a21373539
Child:
24:27800de38eab
Commit message:
Changed blocking options to default non-blocking; Detected OPTIONS HTTP method; Changed to always use keep-alive

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	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
--- a/RdWebServer.h	Sat Aug 29 06:08:00 2015 +0000
+++ b/RdWebServer.h	Mon Aug 31 08:54:17 2015 +0000
@@ -108,6 +108,7 @@
 };
 
 const int HTTPD_MAX_HDR_LENGTH = 255;
+// The FRDM-K64F has more memory than the LPC1768 so allow a larger buffer
 #ifdef MCU_MK64F12
 const int HTTPD_MAX_REQ_LENGTH = 2048;
 #warning("TCP Request Length 2048")
@@ -122,6 +123,7 @@
         static const int METHOD_OTHER = 0;
         static const int METHOD_GET = 1;
         static const int METHOD_POST = 2;
+        static const int METHOD_OPTIONS = 3;
         RdWebServer();
         virtual ~RdWebServer();