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.
Diff: RdWebServer.cpp
- Revision:
- 28:99036ff32459
- Parent:
- 27:0c2d6f598ae5
--- 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);