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:
14:4b83670854f0
Parent:
13:4f9c09d3da13
Child:
15:0865fa4b046a
diff -r 4f9c09d3da13 -r 4b83670854f0 RdWebServer.cpp
--- a/RdWebServer.cpp	Mon May 04 17:26:32 2015 +0000
+++ b/RdWebServer.cpp	Tue May 05 15:05:44 2015 +0000
@@ -11,6 +11,7 @@
 #define MAX_CMDSTR_LEN 100
 #define MAX_ARGSTR_LEN 100
 #define MAX_FILENAME_LEN (MAX_ARGSTR_LEN + 20)
+#define MAX_MIMETYPE_LEN 50
 
 RdWebServer::RdWebServer()
 {
@@ -58,8 +59,9 @@
     return true;
 }
 
-void RdWebServer::handleReceivedHttp(TCPSocketConnection &client)
+bool RdWebServer::handleReceivedHttp(TCPSocketConnection &client)
 {
+    bool closeConn = true;
     RD_DBG("Received Data: %d\n\r\n\r%.*s\n\r",strlen(_buffer),strlen(_buffer),_buffer);
     int method = METHOD_OTHER;
     if (strncmp(_buffer, "GET ", 4) == 0)
@@ -84,7 +86,7 @@
                 if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
                 {
                     char* respStr = ((*it)->_callback)(method, cmdStr, argStr);
-                    client.send(respStr, strlen(respStr));
+                    client.send_all(respStr, strlen(respStr));
                 }
                 else if ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE)
                 {
@@ -109,11 +111,11 @@
                         strcpy(combinedFileName, (*it)->_substFileName);
                         if (combinedFileName[strlen(combinedFileName)-1] == '*')
                             strcpy(combinedFileName+strlen(combinedFileName)-1, argStr);
-                        handleSDFileRequest(combinedFileName, argStr, client, _httpHeader);
+                        closeConn = handleSDFileRequest(combinedFileName, argStr, client, _httpHeader);
                     }
                     else
                     {
-                        handleSDFileRequest(cmdStr, argStr, client, _httpHeader);
+                        closeConn = handleSDFileRequest(cmdStr, argStr, client, _httpHeader);
                     }
                 }
                 break;
@@ -121,8 +123,9 @@
         }
         // If command not found see if it is a local file
         if (!cmdFound)
-            handleSDFileRequest(cmdStr, argStr, client, _httpHeader);
+            closeConn = handleSDFileRequest(cmdStr, argStr, client, _httpHeader);
     }
+    return closeConn;
 }
 
 void RdWebServer::run()
@@ -164,22 +167,27 @@
                 else if (rxLen == 0)
                 {
                     RD_WARN("received buffer is empty.\n\r");
-                    continue;                
+                    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");
-                    client.send(_httpHeader,strlen(_httpHeader));
-                    client.send(_buffer, rxLen);
-                    continue;
+                    client.send_all(_httpHeader,strlen(_httpHeader));
+                    client.send_all(_buffer, rxLen);
+                    break;
                 }
                 _buffer[rxLen] = '\0';
     
                 // Handle buffer
-                handleReceivedHttp(client);
-                
-                // Done
-                break;
+                if (handleReceivedHttp(client))
+                {
+                    break;
+                }
+                else
+                {
+                    connectLimitTimer.reset();
+                    connectLimitTimer.start();
+                }
             }
             
             // Connection now closed
@@ -197,8 +205,9 @@
     _commands.push_back(new RdWebServerCmdDef(pCmdStr, cmdType, callback, substFileName, cacheIfPossible));
 }
 
-void RdWebServer::handleSDFileRequest(char* inFileName, char* argStr, TCPSocketConnection &client, char* httpHeader)
+bool RdWebServer::handleSDFileRequest(char* inFileName, char* argStr, TCPSocketConnection &client, char* httpHeader)
 {
+    bool closeConn = true;
     const int HTTPD_MAX_FNAME_LENGTH = 127;
     char filename[HTTPD_MAX_FNAME_LENGTH+1];
     
@@ -213,21 +222,21 @@
         if (d != NULL) 
         {
             sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
-            client.send(httpHeader,strlen(httpHeader));
+            client.send_all(httpHeader,strlen(httpHeader));
             sprintf(httpHeader,"<html><head><title>Directory Listing</title></head><body><h1>%s</h1><ul>", inFileName);
-            client.send(httpHeader,strlen(httpHeader));
+            client.send_all(httpHeader,strlen(httpHeader));
             struct dirent *p;
             while((p = readdir(d)) != NULL) 
             {
                 RD_INFO("%s\r\n", p->d_name);
                 sprintf(httpHeader,"<li>%s</li>", p->d_name);
-                client.send(httpHeader,strlen(httpHeader));
+                client.send_all(httpHeader,strlen(httpHeader));
             }
         }
         closedir(d);
         RD_DBG("Directory closed\n");
         sprintf(httpHeader,"</ul></body></html>");
-        client.send(httpHeader,strlen(httpHeader));
+        client.send_all(httpHeader,strlen(httpHeader));
     }
     else
 #endif
@@ -239,32 +248,49 @@
         if (fp == NULL)
         {
             RD_WARN("Filename %s not found\r\n", filename);
-            sprintf(httpHeader,"HTTP/1.1 404 Not Found \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
-            client.send(httpHeader,strlen(httpHeader));
-            client.send(inFileName,strlen(inFileName));
+            sprintf(httpHeader,"HTTP/1.1 404 Not Found \r\nContent-Type: text\r\nContent-Length: 0\r\n\r\n");
+            client.send_all(httpHeader,strlen(httpHeader));
+            closeConn = false;
         }
         else
         {
             RD_INFO("Sending file %s\r\n", filename);
-            sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
-            client.send(httpHeader,strlen(httpHeader));
+            fseek(fp, 0L, SEEK_END);
+            int sz = ftell(fp);
+            fseek(fp, 0L, SEEK_SET);
+            sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n", sz);
+            client.send_all(httpHeader,strlen(httpHeader));
+            // MIME type
+            char *pDot = strrchr(filename, '.');
+            char mimeType[MAX_MIMETYPE_LEN+1];
+            if (pDot && (!strcmp(pDot, ".html") || !strcmp(pDot, ".htm")))
+                strcpy(mimeType, "Content-Type: text/html\r\n");
+            else if (pDot && !strcmp(pDot, ".css"))
+                strcpy(mimeType, "Content-Type: text/css\r\n");
+            if (pDot && !strcmp(pDot, ".js"))
+                strcpy(mimeType, "Content-Type: application/javascript\r\n");
+//            client.send_all(mimeType,strlen(mimeType));
+            RD_INFO("MIME TYPE %s", mimeType);
+            // Read file in blocks and send
             int rdCnt = 0;
             char fileBuf[1024];
             while ((rdCnt = fread(fileBuf, sizeof( char ), 1024, fp)) == 1024) 
             {
                 client.send_all(fileBuf, rdCnt);
             }
-            client.send(fileBuf, rdCnt);
+            client.send_all(fileBuf, rdCnt);
             fclose(fp);
+            closeConn = false;
         }
     }
+    return closeConn;
 }
 
 void RdWebServer::sendFromCache(RdFileCacheEntry* pCacheEntry, TCPSocketConnection &client, char* httpHeader)
 {
     RD_INFO("Sending file %s from cache %d bytes\r\n", pCacheEntry->_fileName, pCacheEntry->_nFileLen);
     sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
-    client.send(httpHeader,strlen(httpHeader));
+    client.send_all(httpHeader,strlen(httpHeader));
     char* pMem = pCacheEntry->_pFileContent;
     int nLenLeft = pCacheEntry->_nFileLen;
     int blkSize = 2048;
@@ -333,14 +359,14 @@
     {
         RD_WARN("Local file %s not found\r\n", localFilename);
         sprintf(httpHeader,"HTTP/1.1 404 Not Found \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
-        client.send(httpHeader,strlen(httpHeader));
-        client.send(reqFileNameStr,strlen(reqFileNameStr));
+        client.send_all(httpHeader,strlen(httpHeader));
+        client.send_all(reqFileNameStr,strlen(reqFileNameStr));
     }
     else
     {
         RD_INFO("Sending file %s from disk\r\n", localFilename);
         sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
-        client.send(httpHeader,strlen(httpHeader));
+        client.send_all(httpHeader,strlen(httpHeader));
         int rdCnt = 0;
         char fileBuf[2000];
         while ((rdCnt = fread(fileBuf, sizeof( char ), 2000, fp)) == 2000) 
@@ -355,7 +381,7 @@
 
     RD_WARN("Local file system not supported\r\n");
     sprintf(httpHeader,"HTTP/1.1 404 Not Found \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
-    client.send(httpHeader,strlen(httpHeader));
+    client.send_all(httpHeader,strlen(httpHeader));
     client.send_all(inFileName,strlen(inFileName));
 
 #endif