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:
Sun May 03 18:59:42 2015 +0000
Parent:
10:b4b9d4d5e5be
Child:
12:c14ffd4ec125
Commit message:
Added debugging macros

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	Sun Feb 22 22:08:13 2015 +0000
+++ b/RdWebServer.cpp	Sun May 03 18:59:42 2015 +0000
@@ -33,23 +33,23 @@
     //setup tcp socket
     if(_socketSrv.bind(port)< 0) 
     {
-        pc.printf("TCP server bind fail\n\r");
+        RD_WARN("TCP server bind fail\n\r");
         return false;
     }
     else 
     {
-//        pc.printf("TCP server bind success\n\r");
+        RD_DBG("TCP server bind success\n\r");
         _serverIsListening = true;
     }
 
     if(_socketSrv.listen(1) < 0)
     {
-        pc.printf("TCP server listen fail\n\r");
+        RD_WARN("TCP server listen fail\n\r");
         return false;
     }
     else 
     {
-        pc.printf("TCP server is listening...\r\n");
+        RD_INFO("TCP server is listening...\r\n");
     }
     
     return true;
@@ -57,7 +57,7 @@
 
 void RdWebServer::handleReceivedHttp(TCPSocketConnection &client)
 {
-//    pc.printf("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\n\r",strlen(_buffer),strlen(_buffer),_buffer);
     int method = METHOD_OTHER;
     if (strncmp(_buffer, "GET ", 4) == 0)
         method = METHOD_GET;
@@ -68,15 +68,15 @@
     char argStr[MAX_ARGSTR_LEN];
     if (extractCmdArgs(_buffer+3, cmdStr, MAX_CMDSTR_LEN, argStr, MAX_ARGSTR_LEN))
     {
-//        pc.printf("CmdStr %s\n\r", cmdStr);
-//        pc.printf("ArgStr %s\n\r", argStr);
+        RD_DBG("CmdStr %s\n\r", cmdStr);
+        RD_DBG("ArgStr %s\n\r", argStr);
         bool cmdFound = false;
         for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it)
         {
-//                            pc.printf("Testing <<%s>> with <<%s>>\r\n", (*it)->_pCmdStr, cmdStr);
+            RD_DBG("Testing <<%s>> with <<%s>>\r\n", (*it)->_pCmdStr, cmdStr);
             if (strcasecmp((*it)->_pCmdStr, cmdStr) == 0)
             {                                    
-//                pc.printf("FoundCmd <%s> Type %d\n\r", cmdStr, (*it)->_cmdType);
+                RD_DBG("FoundCmd <%s> Type %d\n\r", cmdStr, (*it)->_cmdType);
                 cmdFound = true;
                 if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
                 {
@@ -113,15 +113,15 @@
 
     while (isListening())
     {
-        pc.printf("Waiting for TCP connection\r\n");
+        RD_INFO("Waiting for TCP connection\r\n");
         if(_socketSrv.accept(client)<0) 
         {
-            pc.printf("TCP Socket failed to accept connection\n\r");
+            RD_WARN("TCP Socket failed to accept connection\n\r");
         }
         else
         {
             client.set_blocking(false, 2000);
-            pc.printf("Connection from IP: %s\n\r",client.get_address());
+            RD_INFO("Connection from IP: %s\n\r",client.get_address());
             if (_pStatusLed != NULL)
                 *_pStatusLed = true;
             
@@ -132,7 +132,7 @@
                 // Check connection timer - 10 seconds timeout on HTTP operation
                 if (connectLimitTimer.read() >= 10)
                 {
-                    pc.printf("Connection timed out\n\r");
+                    RD_WARN("Connection timed out\n\r");
                     break;
                 }
                 // Get received data
@@ -143,7 +143,7 @@
                 }
                 else if (rxLen == 0)
                 {
-                    pc.printf("received buffer is empty.\n\r");
+                    RD_WARN("received buffer is empty.\n\r");
                     continue;                
                 }
                 else if (rxLen > HTTPD_MAX_REQ_LENGTH)
@@ -163,7 +163,7 @@
             }
             
             // Connection now closed
-            printf("Connection closed ...\r\n");
+            RD_INFO("Connection closed ...\r\n");
             client.close();
             if (_pStatusLed != NULL)
                 *_pStatusLed = false;
@@ -182,46 +182,48 @@
     const int HTTPD_MAX_FNAME_LENGTH = 127;
     char filename[HTTPD_MAX_FNAME_LENGTH+1];
     
-    pc.printf("Requesting file %s\n\r", inFileName);
+    RD_INFO("Requesting file %s\n\r", inFileName);
     
     if ((strlen(inFileName) > 0) && (inFileName[strlen(inFileName)-1] == '/'))
     {
-        pc.printf("Request directory %s%s\r\n", _pBaseWebFolder, inFileName);
+        RD_INFO("Request directory %s%s\r\n", _pBaseWebFolder, inFileName);
         sprintf(filename, "%s%s", _pBaseWebFolder, inFileName);
         DIR *d = opendir(filename);
-        if (d != NULL) {
+        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));
             sprintf(httpHeader,"<html><head><title>Directory Listing</title></head><body><h1>%s</h1><ul>", inFileName);
             client.send(httpHeader,strlen(httpHeader));
             struct dirent *p;
-            while((p = readdir(d)) != NULL) {
-                pc.printf("%s\r\n", p->d_name);
+            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));
             }
         }
         closedir(d);
-        // pc.printf("Directory closed\n");
+        RD_DBG("Directory closed\n");
         sprintf(httpHeader,"</ul></body></html>");
         client.send(httpHeader,strlen(httpHeader));
     }
     else
     {
         sprintf(filename, "%s%s", _pBaseWebFolder, inFileName);
-        pc.printf ("Filename %s\r\n", filename);
+        RD_INFO("Filename %s\r\n", filename);
             
         FILE* fp = fopen(filename, "r");
         if (fp == NULL)
         {
-            pc.printf ("Filename %s not found\r\n", filename);
+            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));
         }
         else
         {
-            pc.printf ("Sending file %s\r\n", filename);
+            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));
             int rdCnt = 0;
@@ -238,7 +240,7 @@
 
 void RdWebServer::sendFromCache(RdFileCacheEntry* pCacheEntry, TCPSocketConnection &client, char* httpHeader)
 {
-    pc.printf ("Sending file %s from cache %d bytes\r\n", pCacheEntry->_fileName, pCacheEntry->_nFileLen);
+    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));
     char* pMem = pCacheEntry->_pFileContent;
@@ -260,7 +262,7 @@
     char localFilename[HTTPD_MAX_FNAME_LENGTH+1];
     char reqFileNameStr[HTTPD_MAX_FNAME_LENGTH+1];
 
-    pc.printf("Requesting local file %s\n\r", inFileName);
+    RD_INFO("Requesting local file %s\n\r", inFileName);
     sprintf(reqFileNameStr, "/%s", inFileName);
     sprintf(localFilename, "/local/%s", inFileName);
         
@@ -304,14 +306,14 @@
     FILE* fp = fopen(localFilename, "r");
     if (fp == NULL)
     {
-        pc.printf ("Local file %s not found\r\n", localFilename);
+        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));
     }
     else
     {
-        pc.printf ("Sending file %s from disk\r\n", localFilename);
+        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));
         int rdCnt = 0;
@@ -327,33 +329,33 @@
 
 bool RdFileCacheEntry::readLocalFileIntoCache(char* fileName)
 {
-    pc.printf("Reading into cache %s\n\r", fileName);
+    RD_INFO("Reading into cache %s\n\r", fileName);
     LocalFileSystem local("local");
     FILE* fp = fopen(fileName, "r");
     if (fp == NULL)
     {
-        pc.printf("Failed to open file\n\r");
+        RD_WARN("Failed to open file\n\r");
         return false;
     }
-    pc.printf("Seeking\n\r");
+    RD_DBG("Seeking\n\r");
     fseek(fp, 0, SEEK_END);
     _nFileLen = (int)ftell(fp);
     _pFileContent = new char[_nFileLen];
-    pc.printf("Len %d Buf %08x\n\r", _nFileLen, _pFileContent);
+    RD_DBG("Len %d Buf %08x\n\r", _nFileLen, _pFileContent);
     if (!_pFileContent)
     {
-        pc.printf("Failed to allocate %lu\n\r", _nFileLen);
+        RD_WARN("Failed to allocate %lu\n\r", _nFileLen);
         fclose(fp);
         return false;
     }
-    pc.printf("Allocated\n\r");
+    RD_DBG("Allocated\n\r");
     memset(_pFileContent, 0, _nFileLen);
     fseek(fp, 0, SEEK_SET);
     char* pMem = _pFileContent;
     char fileBuf[100];
     int totCnt = 0;
     int rdCnt = 0;
-    pc.printf("Reading\n\r");
+    RD_DBG("Reading\n\r");
     while (true)
     {
         int toRead = _nFileLen - totCnt;
@@ -364,14 +366,14 @@
         rdCnt = fread(fileBuf, sizeof(char), toRead, fp);
         if (rdCnt <= 0)
             break;
-        pc.printf("Read %d tot %d of %d\n\r", rdCnt, totCnt, _nFileLen);
+        RD_DBG("Read %d tot %d of %d\n\r", rdCnt, totCnt, _nFileLen);
         memcpy(pMem, fileBuf, rdCnt);
         pMem += rdCnt;
         totCnt += rdCnt;
     }
-    pc.printf("Done read\n\r");
+    RD_DBG("Done read\n\r");
     fclose(fp);
-    pc.printf("Success in caching %d bytes (read %d)\n\r", _nFileLen, totCnt);
+    RD_DBG("Success in caching %d bytes (read %d)\n\r", _nFileLen, totCnt);
     _bCacheValid = true;
     return true;
 }
--- a/RdWebServer.h	Sun Feb 22 22:08:13 2015 +0000
+++ b/RdWebServer.h	Sun May 03 18:59:42 2015 +0000
@@ -10,6 +10,27 @@
 #include "mbed.h"
 #include "EthernetInterface.h"
 
+#if defined(DEBUG) && (DEBUG > 3)
+#define RD_DBG(x, ...) std::printf("[RD_DBG: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__);
+#else
+#define RD_DBG(x, ...)
+#endif
+#if defined(DEBUG) && (DEBUG > 2)
+#define RD_INFO(x, ...) std::printf("[RD_INFO: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__);
+#else
+#define RD_INFO(x, ...)
+#endif
+#if defined(DEBUG) && (DEBUG > 1)
+#define RD_WARN(x, ...) std::printf("[RD_WARNING: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__);
+#else
+#define RD_WARN(x, ...)
+#endif
+#if defined(DEBUG) && (DEBUG > 0)
+#define RD_ERR(x, ...) std::printf("[RD_ERR: %s:%d]" x "\r\n", __FILE__, __LINE__, ##__VA_ARGS__);
+#else
+#define RD_ERR(x, ...)
+#endif
+
 extern RawSerial pc;
 
 class RdFileCacheEntry