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 28:99036ff32459, committed 2016-02-08
- Comitter:
- Bobty
- Date:
- Mon Feb 08 13:47:29 2016 +0000
- Parent:
- 27:0c2d6f598ae5
- Child:
- 29:46998f2e458f
- Commit message:
- Restructured to allow support for alternative IP stacks - e.g. CC3000; Also removed dependency on Mutex when SD card not used; And removed dependency on std::vector as it seems unstable
Changed in this revision
--- a/RdWebServer.cpp Fri Oct 16 08:41:02 2015 +0000 +++ b/RdWebServer.cpp Mon Feb 08 13:47:29 2016 +0000 @@ -4,23 +4,15 @@ // http://www.schuurmans.cc/multi-threaded-web-server-for-netduino-plus // More details at http://robdobson.com/2013/10/moving-my-window-shades-control-to-mbed/ -// Setting RDWEB_DEBUG to 4 causes all debugging to be shown -#define RDWEB_DEBUG 2 - -// Change the settings below to support a local file system (not available on some MBEDs) -#define SUPPORT_LOCAL_FILESYSTEM 1 -#define SUPPORT_LOCAL_FILE_CACHE 1 - -// Change this to support display of files on the server -#define SUPPORT_FOLDER_VIEW 1 - +#include "RdWebServerDefs.h" #include "RdWebServer.h" // Limits - note particularly the MAX_FILENAME_LEN const int MAX_FILENAME_LEN = (MAX_ARGSTR_LEN + 20); // Constructor -RdWebServer::RdWebServer(Mutex* pSdCardMutex) +RdWebServer::RdWebServer(TCPSocketServer& tcpServerSocket, Mutex* pSdCardMutex) + : _serverSocket(tcpServerSocket) { _initOk = false; _pStatusLed = NULL; @@ -32,14 +24,17 @@ _curHttpMethod = METHOD_OTHER; _curWebServerCmdDef = NULL; _pSdCardMutex = pSdCardMutex; + _numWebServerCmds = 0; + _numWebServerCachedFiles = 0; } // Destructor RdWebServer::~RdWebServer() { // Clean-up - probably never called as we're on a microcontroller! - for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it) - delete (*it); + for (int i = 0; i < _numWebServerCmds; i++) + delete _pWebServerCmds[i]; + _numWebServerCmds = 0; } // Init @@ -62,7 +57,6 @@ _closeConnAfterSend = false; _closeConnOnReceiveFail = true; - // Setup tcp socket if(_serverSocket.bind(port)< 0) { RD_WARN("TCP server bind fail"); @@ -86,19 +80,17 @@ // Check initialised ok if (!_initOk) return; - + // Start accepting connections + RD_INFO("Waiting for TCP connection"); while (true) { + // Accept connection if available TCPSocketConnection clientSocketConn; - // Accept connection if available - RD_INFO("Waiting for TCP connection"); clientSocketConn.set_blocking(_blockingOnAccept, _timeoutOnBlocking); - if(_serverSocket.accept(clientSocketConn)<0) - { - RD_WARN("TCP Socket failed to accept connection"); + _serverSocket.accept(clientSocketConn); + if (!clientSocketConn.is_connected()) continue; - } // Connection RD_INFO("Connection from IP: %s", clientSocketConn.get_address()); @@ -147,6 +139,9 @@ forcedClosed = true; } } + + // Waiting again ... + RD_INFO("Waiting for TCP connection"); } } @@ -200,21 +195,22 @@ RD_DBG("ArgStr %s", _curArgStr); bool cmdFound = false; - for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it) + for (int wsCmdIdx = 0; wsCmdIdx < _numWebServerCmds; wsCmdIdx++) { - if (strcasecmp((*it)->_pCmdStr, _curCmdStr) == 0) + RdWebServerCmdDef* pCmd = _pWebServerCmds[wsCmdIdx]; + if (strcasecmp(pCmd->_pCmdStr, _curCmdStr) == 0) { - RD_DBG("FoundCmd <%s> Type %d", _curCmdStr, (*it)->_cmdType); + RD_DBG("FoundCmd <%s> Type %d", _curCmdStr, pCmd->_cmdType); cmdFound = true; - if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK) + if (pCmd->_cmdType == RdWebServerCmdDef::CMD_CALLBACK) { - char* respStr = ((*it)->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen, + char* respStr = (pCmd->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen, _curContentLen, pPayload, payloadLen, 0); // Handle split-payload situation if (_curContentLen > 0 && payloadLen < _curContentLen) { _curSplitPayloadPos = payloadLen; - _curWebServerCmdDef = (*it); + _curWebServerCmdDef = pCmd; } else { @@ -222,17 +218,17 @@ } handledOk = true; } - else if ( ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) || - ((*it)->_cmdType == RdWebServerCmdDef::CMD_SDORUSBFILE) ) + else if ( (pCmd->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) || + (pCmd->_cmdType == RdWebServerCmdDef::CMD_SDORUSBFILE) ) { char combinedFileName[MAX_FILENAME_LEN]; - strcpy(combinedFileName, (*it)->_substFileName); + strcpy(combinedFileName, pCmd->_substFileName); if (strlen(combinedFileName) == 0) strcpy(combinedFileName, _curCmdStr); else if (combinedFileName[strlen(combinedFileName)-1] == '*') strcpy(combinedFileName+strlen(combinedFileName)-1, _curArgStr); - if ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) - handledOk = handleLocalFileRequest(combinedFileName, _curArgStr, clientSocketConn, (*it)->_bCacheIfPossible); + if (pCmd->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) + handledOk = handleLocalFileRequest(combinedFileName, _curArgStr, clientSocketConn, pCmd->_bCacheIfPossible); else handledOk = handleSDFileRequest(combinedFileName, _curArgStr, clientSocketConn); @@ -263,7 +259,14 @@ // Add a command to the server void RdWebServer::addCommand(char* pCmdStr, int cmdType, CmdCallbackType callback, char* substFileName, bool cacheIfPossible) { - _commands.push_back(new RdWebServerCmdDef(pCmdStr, cmdType, callback, substFileName, cacheIfPossible)); + // Check for overflow + if (_numWebServerCmds >= MAX_WEB_SERVER_CMDS) + return; + + // Create new command definition and add + RdWebServerCmdDef* pNewCmdDef = new RdWebServerCmdDef(pCmdStr, cmdType, callback, substFileName, cacheIfPossible); + _pWebServerCmds[_numWebServerCmds] = pNewCmdDef; + _numWebServerCmds++; } // Form a header to respond @@ -297,6 +300,8 @@ // Handle request for files/listing from SDCard bool RdWebServer::handleSDFileRequest(char* inFileName, char* argStr, TCPSocketConnection &clientSocketConn) { +#ifdef SUPPORT_SD_FILESYSTEM + bool handledOk = false; const int HTTPD_MAX_FNAME_LENGTH = 127; char filename[HTTPD_MAX_FNAME_LENGTH+1]; @@ -388,6 +393,12 @@ } return handledOk; + +#else // support SUPPORT_SD_FILESYSTEM + + return false; + +#endif } // Send a file from cache - used for local files only @@ -433,19 +444,24 @@ { // Check if file already cached bool bTryToCache = true; - for (std::vector<RdFileCacheEntry*>::iterator it = _cachedFiles.begin() ; it != _cachedFiles.end(); ++it) + for (int wsCacheIdx = 0; wsCacheIdx < _numWebServerCachedFiles; wsCacheIdx++) { - if (strcmp(inFileName, (*it)->_fileName) == 0) + RdFileCacheEntry* pCachedFile = _pWebServerCachedFiles[wsCacheIdx]; + if (strcmp(inFileName, pCachedFile->_fileName) == 0) { - if ((*it)->_bCacheValid) + if (pCachedFile->_bCacheValid) { - sendFromCache(*it, clientSocketConn); + sendFromCache(pCachedFile, clientSocketConn); return true; } bTryToCache = false; // don't try to cache as cacheing must have already failed } } + // Check for cache full + if (_numWebServerCachedFiles >= MAX_WEB_SERVER_CACHED_FILES) + bTryToCache = false; + // See if we can cache the file if (bTryToCache) { @@ -453,8 +469,10 @@ if (pCacheEntry) { bool bCacheSuccess = pCacheEntry->readLocalFileIntoCache(localFilename); + // Store the cache entry even if reading failed (mem alloc or file not found, etc) so we don't try to cache again - _cachedFiles.push_back(pCacheEntry); + _pWebServerCachedFiles[_numWebServerCachedFiles] = pCacheEntry; + _numWebServerCachedFiles++; if (bCacheSuccess) { sendFromCache(pCacheEntry, clientSocketConn);
--- a/RdWebServer.h Fri Oct 16 08:41:02 2015 +0000 +++ b/RdWebServer.h Mon Feb 08 13:47:29 2016 +0000 @@ -7,10 +7,7 @@ #ifndef RD_WEB_SERVER #define RD_WEB_SERVER -#include <vector> - #include "mbed.h" -#include "EthernetInterface.h" // Debug level #ifdef RDWEB_DEBUG @@ -56,6 +53,8 @@ // Length of strings const int MAX_CMDSTR_LEN = 100; const int MAX_ARGSTR_LEN = 100; +const int MAX_WEB_SERVER_CMDS = 30; +const int MAX_WEB_SERVER_CACHED_FILES = 5; const int SUBST_MAX_FNAME_LEN = 40; // note local files on MBED are 8.3 but this might include other files extern RawSerial pc; @@ -113,14 +112,21 @@ const int HTTPD_MAX_HDR_LENGTH = 255; // The FRDM-K64F has more memory than the LPC1768 so allow a larger buffer -#ifdef MCU_MK64F12 +#if defined(MCU_MK64F12) const int HTTPD_MAX_REQ_LENGTH = 2048; #warning("TCP Request Length 2048") +#elif defined(TARGET_ARCH_BLE) +const int HTTPD_MAX_REQ_LENGTH = 1024; +#warning("TCP Request Length 1024") #else const int HTTPD_MAX_REQ_LENGTH = 1024; #warning("TCP Request Length 1024") #endif +class TCPSocketServer; +class TCPSocketConnection; +class Mutex; + class RdWebServer { public : @@ -128,7 +134,7 @@ static const int METHOD_GET = 1; static const int METHOD_POST = 2; static const int METHOD_OPTIONS = 3; - RdWebServer(Mutex* pSdCardMutex = NULL); + RdWebServer(TCPSocketServer& tcpServerSocket, Mutex* pSdCardMutex = NULL); virtual ~RdWebServer(); bool init(int port, DigitalOut* pStatusLed, char* pBaseWebFolder); @@ -141,10 +147,12 @@ private : int _port; DigitalOut* _pStatusLed; - TCPSocketServer _serverSocket; + TCPSocketServer& _serverSocket; bool _initOk; - std::vector<RdWebServerCmdDef*> _commands; - std::vector<RdFileCacheEntry*> _cachedFiles; + RdWebServerCmdDef* _pWebServerCmds[MAX_WEB_SERVER_CMDS]; + int _numWebServerCmds; + RdFileCacheEntry* _pWebServerCachedFiles[MAX_WEB_SERVER_CACHED_FILES]; + int _numWebServerCachedFiles; bool extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen, int& contentLen); char* _pBaseWebFolder; char _httpHeader[HTTPD_MAX_HDR_LENGTH+1];
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/RdWebServerDefs.h Mon Feb 08 13:47:29 2016 +0000 @@ -0,0 +1,25 @@ +// RdWebServer - Simple Web Server for MBED +// Copyright (C) Rob Dobson 2013-2016, MIT License +// Inspired by Jasper Schuurmans multi-threaded web server for Netduino which now seems to have gone from ... +// http://www.schuurmans.cc/multi-threaded-web-server-for-netduino-plus +// More details at http://robdobson.com/2013/10/moving-my-window-shades-control-to-mbed/ + +#ifndef RD_WEB_SERVER_DEFS +#define RD_WEB_SERVER_DEFS + +// Setting RDWEB_DEBUG to 4 causes all debugging to be shown +#define RDWEB_DEBUG 2 + +// Change the settings below to support a local file system (not available on some MBEDs) +//#define SUPPORT_LOCAL_FILESYSTEM 1 +//#define SUPPORT_LOCAL_FILE_CACHE 1 +//#define SUPPORT_SD_FILESYSTEM 1 + +// Change this to support display of files on the server +// #define SUPPORT_FOLDER_VIEW 1 + +#include "cc3000.h" +#include "TCPSocketConnection.h" +#include "TCPSocketServer.h" + +#endif