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:
Tue May 12 22:16:25 2015 +0000
Parent:
18:5de680c4cfcb
Child:
20:e6c7db867593
Commit message:
Allow larger buffer on K64F (more RAM), tidied up diagnostics and added code to get message payload

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 14:26:54 2015 +0000
+++ b/RdWebServer.cpp	Tue May 12 22:16:25 2015 +0000
@@ -5,7 +5,7 @@
 */
 
 // Setting RDWEB_DEBUG to 4 causes all debugging to be shown
-#define RDWEB_DEBUG 1
+#define RDWEB_DEBUG 4
 
 // Change the settings below to support a local file system (not available on some MBEDs)
 //#define SUPPORT_LOCAL_FILESYSTEM 1
@@ -65,15 +65,15 @@
     _serverSocket.set_blocking(true);
     if(_serverSocket.bind(port)< 0) 
     {
-        RD_WARN("TCP server bind fail\n\r");
+        RD_WARN("TCP server bind fail");
         return false;
     }
     if(_serverSocket.listen(1) < 0)
     {
-        RD_WARN("TCP server listen fail\n\r");
+        RD_WARN("TCP server listen fail");
         return false;
     }
-    RD_INFO("TCP server is listening...\r\n");
+    RD_INFO("TCP server is listening...");
     _initOk = true;
     return true;
 }
@@ -90,16 +90,16 @@
     {
         TCPSocketConnection clientSocketConn;
         // Accept connection if available
-        RD_INFO("Waiting for TCP connection\r\n");
+        RD_INFO("Waiting for TCP connection");
         clientSocketConn.set_blocking(_blockingOnAccept, _timeoutOnBlocking);
         if(_serverSocket.accept(clientSocketConn)<0) 
         {
-            RD_WARN("TCP Socket failed to accept connection\n\r");
+            RD_WARN("TCP Socket failed to accept connection");
             continue;
         }
         
         // Connection
-        RD_INFO("Connection from IP: %s\n\r", clientSocketConn.get_address());
+        RD_INFO("Connection from IP: %s", clientSocketConn.get_address());
         if (_pStatusLed != NULL)
             *_pStatusLed = true;
         
@@ -112,32 +112,30 @@
             int rxLen = clientSocketConn.receive(_buffer, HTTPD_MAX_REQ_LENGTH);
             if (rxLen == -1)
             {
-                RD_DBG("clientSocketConn.receive() returned %d\r\n", rxLen);
+                RD_DBG("clientSocketConn.receive() returned %d", rxLen);
                 if (_closeConnOnReceiveFail)
                 {
                     int closeRet = clientSocketConn.close();
-                    RD_DBG("Failed receive connection close() ret %d is connected %d\r\n", closeRet, clientSocketConn.is_connected());
+                    RD_DBG("Failed receive connection close() ret %d is connected %d", closeRet, clientSocketConn.is_connected());
                     forcedClosed = true;
                 }
                 continue;
             }
             if (rxLen == 0)
             {
-                RD_DBG("clientSocketConn.receive() returned %d - ignoring -  is connected %d\r\n", rxLen, clientSocketConn.is_connected());
+                RD_DBG("clientSocketConn.receive() returned %d - ignoring -  is connected %d", rxLen, clientSocketConn.is_connected());
                 continue;
             }
             if (rxLen > HTTPD_MAX_REQ_LENGTH)
             {
-                RD_DBG("clientSocketConn.receive() returned %d - too long\r\n", rxLen);
+                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));
                 continue;
             }
             
             // Handle received message
-            RD_DBG("Received len %d\r\n", rxLen);
             _buffer[rxLen] = '\0';
-            RD_DBG("%s\r\n", _buffer);
             if (handleReceivedHttp(clientSocketConn))
             {
                 // OK
@@ -151,7 +149,7 @@
             if (_closeConnAfterSend)
             {
                 int closeRet = clientSocketConn.close();
-                RD_DBG("After send connection close() ret %d is connected %d\r\n", closeRet, clientSocketConn.is_connected());
+                RD_DBG("After send connection close() ret %d is connected %d", closeRet, clientSocketConn.is_connected());
                 forcedClosed = true;
             }
         }
@@ -162,7 +160,7 @@
 bool RdWebServer::handleReceivedHttp(TCPSocketConnection &clientSocketConn)
 {
     bool handledOk = false;
-    RD_DBG("Received Data: %d\n\r\n\r%.*s\n\r",strlen(_buffer),strlen(_buffer),_buffer);
+    RD_DBG("Received Data: %d\n\r\n\r%.*s",strlen(_buffer),strlen(_buffer),_buffer);
     int method = METHOD_OTHER;
     if (strncmp(_buffer, "GET ", 4) == 0)
         method = METHOD_GET;
@@ -173,19 +171,19 @@
     char argStr[MAX_ARGSTR_LEN];
     if (extractCmdArgs(_buffer+3, cmdStr, MAX_CMDSTR_LEN, argStr, MAX_ARGSTR_LEN))
     {
-        RD_DBG("CmdStr %s\n\r", cmdStr);
-        RD_DBG("ArgStr %s\n\r", argStr);
+        RD_DBG("CmdStr %s", cmdStr);
+        RD_DBG("ArgStr %s", argStr);
         bool cmdFound = false;
         for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it)
         {
-            RD_DBG("Testing <<%s>> with <<%s>>\r\n", (*it)->_pCmdStr, cmdStr);
+            RD_DBG("Testing <<%s>> with <<%s>>", (*it)->_pCmdStr, cmdStr);
             if (strcasecmp((*it)->_pCmdStr, cmdStr) == 0)
             {                                    
-                RD_DBG("FoundCmd <%s> Type %d\n\r", cmdStr, (*it)->_cmdType);
+                RD_DBG("FoundCmd <%s> Type %d", cmdStr, (*it)->_cmdType);
                 cmdFound = true;
                 if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
                 {
-                    char* respStr = ((*it)->_callback)(method, cmdStr, argStr);
+                    char* respStr = ((*it)->_callback)(method, cmdStr, argStr, _buffer);
                     clientSocketConn.send(respStr, strlen(respStr));
                 }
                 else if ( ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) ||
@@ -260,12 +258,12 @@
     const int HTTPD_MAX_FNAME_LENGTH = 127;
     char filename[HTTPD_MAX_FNAME_LENGTH+1];
     
-    RD_INFO("Requesting file %s\n\r", inFileName);
+    RD_INFO("Requesting file %s", inFileName);
     
 #ifdef SUPPORT_FOLDER_VIEW
     if ((strlen(inFileName) > 0) && (inFileName[strlen(inFileName)-1] == '/'))
     {
-        RD_INFO("Request directory %s%s\r\n", _pBaseWebFolder, inFileName);
+        RD_INFO("Request directory %s%s", _pBaseWebFolder, inFileName);
         sprintf(filename, "%s%s", _pBaseWebFolder, inFileName);
         DIR *d = opendir(filename);
         if (d != NULL) 
@@ -277,7 +275,7 @@
             struct dirent *p;
             while((p = readdir(d)) != NULL) 
             {
-                RD_INFO("%s\r\n", p->d_name);
+                RD_INFO("%s", p->d_name);
                 sprintf(_httpHeader,"<li>%s</li>", p->d_name);
                 clientSocketConn.send(_httpHeader,strlen(_httpHeader));
             }
@@ -292,19 +290,19 @@
 #endif
     {
         sprintf(filename, "%s%s", _pBaseWebFolder, inFileName);
-        RD_INFO("Filename %s\r\n", filename);
+        RD_INFO("Filename %s", filename);
             
         FILE* fp = fopen(filename, "r");
         if (fp == NULL)
         {
-            RD_WARN("Filename %s not found\r\n", filename);
+            RD_WARN("Filename %s not found", filename);
             formHTTPHeader("404 Not Found", "text/plain", 0);
             clientSocketConn.send(_httpHeader,strlen(_httpHeader));
             handledOk = true;
         }
         else
         {
-            RD_INFO("Sending file %s\r\n", filename);
+            RD_INFO("Sending file %s", filename);
             // Find file length
             fseek(fp, 0L, SEEK_END);
             int sz = ftell(fp);
@@ -334,7 +332,7 @@
 #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);
+    RD_INFO("Sending file %s from cache %d bytes", pCacheEntry->_fileName, pCacheEntry->_nFileLen);
     // Get mime type
     char* mimeType = getMimeTypeStr(pCacheEntry->_fileName);
     RD_INFO("MIME TYPE %s", mimeType);
@@ -365,7 +363,7 @@
     char localFilename[HTTPD_MAX_FNAME_LENGTH+1];
     char reqFileNameStr[HTTPD_MAX_FNAME_LENGTH+1];
 
-    RD_INFO("Requesting local file %s\n\r", inFileName);
+    RD_INFO("Requesting local file %s", inFileName);
     sprintf(reqFileNameStr, "/%s", inFileName);
     sprintf(localFilename, "/local/%s", inFileName);
         
@@ -409,14 +407,14 @@
     FILE* fp = fopen(localFilename, "r");
     if (fp == NULL)
     {
-        RD_WARN("Local file %s not found\r\n", localFilename);
+        RD_WARN("Local file %s not found", localFilename);
         formHTTPHeader("404 Not Found", "text/plain", 0);
         clientSocketConn.send(_httpHeader,strlen(_httpHeader));
         return true;
     }
     else
     {
-        RD_INFO("Sending file %s from disk\r\n", localFilename);
+        RD_INFO("Sending file %s from disk", localFilename);
         // Find file length
         fseek(fp, 0L, SEEK_END);
         int sz = ftell(fp);
@@ -440,7 +438,7 @@
     
 #else
 
-    RD_WARN("Local file system not supported\r\n");
+    RD_WARN("Local file system not supported");
     formHTTPHeader("404 Not Found", "text/plain", 0);
     clientSocketConn.send(_httpHeader,strlen(_httpHeader));
     return true;
@@ -454,33 +452,33 @@
 {
 #ifdef SUPPORT_LOCAL_FILESYSTEM
     
-    RD_INFO("Reading into cache %s\n\r", fileName);
+    RD_INFO("Reading into cache %s", fileName);
     LocalFileSystem local("local");
     FILE* fp = fopen(fileName, "r");
     if (fp == NULL)
     {
-        RD_WARN("Failed to open file\n\r");
+        RD_WARN("Failed to open file");
         return false;
     }
-    RD_DBG("Seeking\n\r");
+    RD_DBG("Seeking");
     fseek(fp, 0, SEEK_END);
     _nFileLen = (int)ftell(fp);
     _pFileContent = new char[_nFileLen];
-    RD_DBG("Len %d Buf %08x\n\r", _nFileLen, _pFileContent);
+    RD_DBG("Len %d Buf %08x", _nFileLen, _pFileContent);
     if (!_pFileContent)
     {
-        RD_WARN("Failed to allocate %lu\n\r", _nFileLen);
+        RD_WARN("Failed to allocate %lu", _nFileLen);
         fclose(fp);
         return false;
     }
-    RD_DBG("Allocated\n\r");
+    RD_DBG("Allocated");
     memset(_pFileContent, 0, _nFileLen);
     fseek(fp, 0, SEEK_SET);
     char* pMem = _pFileContent;
     char fileBuf[100];
     int totCnt = 0;
     int rdCnt = 0;
-    RD_DBG("Reading\n\r");
+    RD_DBG("Reading");
     while (true)
     {
         int toRead = _nFileLen - totCnt;
@@ -491,14 +489,14 @@
         rdCnt = fread(fileBuf, sizeof(char), toRead, fp);
         if (rdCnt <= 0)
             break;
-        RD_DBG("Read %d tot %d of %d\n\r", rdCnt, totCnt, _nFileLen);
+        RD_DBG("Read %d tot %d of %d", rdCnt, totCnt, _nFileLen);
         memcpy(pMem, fileBuf, rdCnt);
         pMem += rdCnt;
         totCnt += rdCnt;
     }
-    RD_DBG("Done read\n\r");
+    RD_DBG("Done read");
     fclose(fp);
-    RD_DBG("Success in caching %d bytes (read %d)\n\r", _nFileLen, totCnt);
+    RD_DBG("Success in caching %d bytes (read %d)", _nFileLen, totCnt);
     _bCacheValid = true;
     return true;
 #else
@@ -549,3 +547,11 @@
     }
     return true;
 }
+
+char* RdWebServer::getPayloadDataFromMsg(char* msgBuf)
+{
+    char* ptr = strstr(msgBuf, "\r\n\r\n");
+    if (ptr)
+        return ptr+4;
+    return "\0";
+}
--- a/RdWebServer.h	Mon May 11 14:26:54 2015 +0000
+++ b/RdWebServer.h	Tue May 12 22:16:25 2015 +0000
@@ -77,7 +77,7 @@
         int _nFileLen;
 };
 
-typedef char* (*CmdCallbackType)(int method, char*cmdStr, char* argStr);
+typedef char* (*CmdCallbackType)(int method, char*cmdStr, char* argStr, char* msgBuffer);
 
 class RdWebServerCmdDef
 {
@@ -106,7 +106,13 @@
 };
 
 const int HTTPD_MAX_HDR_LENGTH = 255;
-const int HTTPD_MAX_REQ_LENGTH = 1023;
+#ifdef MCU_MK64F12
+const int HTTPD_MAX_REQ_LENGTH = 2048;
+#warning("TCP Request Length 2048")
+#else
+const int HTTPD_MAX_REQ_LENGTH = 1024;
+#warning("TCP Request Length 1024")
+#endif
 
 class RdWebServer
 {
@@ -121,6 +127,8 @@
         void run();
         void addCommand(char* pCmdStr, int cmdType, CmdCallbackType callback = NULL, char* substFileName = NULL, bool cacheIfPossible = false);
         
+        static char* getPayloadDataFromMsg(char* msgBuf);
+
     private :
         int _port;
         DigitalOut* _pStatusLed;