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 24:27800de38eab, committed 2015-08-31
- Comitter:
- Bobty
- Date:
- Mon Aug 31 15:00:41 2015 +0000
- Parent:
- 23:0fc3d7b5e596
- Child:
- 25:ffa1dddd3da4
- Commit message:
- Implemented handling of split-payloads in HTTP requests. This is done by sending a registered command each chunk of payload data as it arrives with an index indicating where the data fits in the original message.
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 Aug 31 08:54:17 2015 +0000
+++ b/RdWebServer.cpp Mon Aug 31 15:00:41 2015 +0000
@@ -17,8 +17,6 @@
#include "RdWebServer.h"
// Limits - note particularly the MAX_FILENAME_LEN
-const int MAX_CMDSTR_LEN = 100;
-const int MAX_ARGSTR_LEN = 100;
const int MAX_FILENAME_LEN = (MAX_ARGSTR_LEN + 20);
// Constructor
@@ -26,6 +24,13 @@
{
_initOk = false;
_pStatusLed = NULL;
+ _curSplitPayloadPos = -1;
+ _curCmdStr[0] = 0;
+ _curArgStr[0] = 0;
+ _bufferReceivedLen = 0;
+ _curContentLen = -1;
+ _curHttpMethod = METHOD_OTHER;
+ _curWebServerCmdDef = NULL;
}
// Destructor
@@ -101,6 +106,7 @@
// While connected
bool forcedClosed = false;
+ _curSplitPayloadPos = -1;
while(clientSocketConn.is_connected() && !forcedClosed)
{
// Receive data
@@ -123,24 +129,11 @@
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", rxLen);
- formHTTPHeader("413 Request Entity Too Large", "text/plain", 0);
- int sentRet = clientSocketConn.send_all(_httpHeader,strlen(_httpHeader));
- continue;
- }
// Handle received message
_buffer[rxLen] = '\0';
- if (handleReceivedHttp(clientSocketConn))
- {
- // OK
- }
- else
- {
- // FAIL
- }
+ _bufferReceivedLen = rxLen;
+ handleReceivedHttp(clientSocketConn);
// Close connection if required
if (_closeConnAfterSend)
@@ -157,33 +150,73 @@
bool RdWebServer::handleReceivedHttp(TCPSocketConnection &clientSocketConn)
{
bool handledOk = false;
- RD_DBG("Received Data: %d\n\r\n\r%.*s",strlen(_buffer),strlen(_buffer),_buffer);
- int method = METHOD_OTHER;
+
+ // Check for split payload
+ if (_curSplitPayloadPos != -1)
+ {
+ // Handle remaining parts of content
+ char* respStr = (_curWebServerCmdDef->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen,
+ _curContentLen, (unsigned char*)_buffer, _bufferReceivedLen, _curSplitPayloadPos);
+ RD_DBG("Received part of message - content %d - rx %d - splitAfter %d", _curContentLen, _bufferReceivedLen, _curSplitPayloadPos);
+ _curSplitPayloadPos += _bufferReceivedLen;
+ // Check if all received
+ if (_curSplitPayloadPos >= _curContentLen)
+ {
+ _curSplitPayloadPos = -1;
+ clientSocketConn.send_all(respStr, strlen(respStr));
+ RD_DBG("That was the end of message - content %d - rx %d", _curContentLen, _bufferReceivedLen);
+ }
+ return true;
+ }
+
+ // Get payload information
+ int payloadLen = -1;
+ unsigned char* pPayload = getPayloadDataFromMsg(_buffer, _bufferReceivedLen, payloadLen);
+
+ // Debug
+ int displayLen = _bufferReceivedLen;
+ if (payloadLen > 0)
+ displayLen = _bufferReceivedLen-payloadLen;
+ RD_DBG("\r\nNEW REQUEST RxLen %d ContentLen %d PayloadLen %d\n\r\n\r%.*s", _bufferReceivedLen, _curContentLen, payloadLen, displayLen, _buffer);
+
+ // Get HTTP method
+ _curHttpMethod = METHOD_OTHER;
if (strncmp(_buffer, "GET ", 4) == 0)
- method = METHOD_GET;
+ _curHttpMethod = METHOD_GET;
else if (strncmp(_buffer, "POST", 4) == 0)
- method = METHOD_POST;
+ _curHttpMethod = METHOD_POST;
else if (strncmp(_buffer, "OPTIONS", 7) == 0)
- method = METHOD_OPTIONS;
+ _curHttpMethod = METHOD_OPTIONS;
- char cmdStr[MAX_CMDSTR_LEN];
- char argStr[MAX_ARGSTR_LEN];
- if (extractCmdArgs(_buffer+3, cmdStr, MAX_CMDSTR_LEN, argStr, MAX_ARGSTR_LEN))
+ // See if there is a valid HTTP command
+ _curContentLen = -1;
+ if (extractCmdArgs(_buffer+3, _curCmdStr, MAX_CMDSTR_LEN, _curArgStr, MAX_ARGSTR_LEN, _curContentLen))
{
- RD_DBG("CmdStr %s", cmdStr);
- RD_DBG("ArgStr %s", argStr);
+ RD_DBG("CmdStr %s", _curCmdStr);
+ RD_DBG("ArgStr %s", _curArgStr);
+
bool cmdFound = false;
for (std::vector<RdWebServerCmdDef*>::iterator it = _commands.begin() ; it != _commands.end(); ++it)
{
- RD_DBG("Testing <<%s>> with <<%s>>", (*it)->_pCmdStr, cmdStr);
- if (strcasecmp((*it)->_pCmdStr, cmdStr) == 0)
- {
- RD_DBG("FoundCmd <%s> Type %d", cmdStr, (*it)->_cmdType);
+ if (strcasecmp((*it)->_pCmdStr, _curCmdStr) == 0)
+ {
+ RD_DBG("FoundCmd <%s> Type %d", _curCmdStr, (*it)->_cmdType);
cmdFound = true;
if ((*it)->_cmdType == RdWebServerCmdDef::CMD_CALLBACK)
{
- char* respStr = ((*it)->_callback)(method, cmdStr, argStr, _buffer);
- clientSocketConn.send_all(respStr, strlen(respStr));
+ char* respStr = ((*it)->_callback)(_curHttpMethod, _curCmdStr, _curArgStr, _buffer, _bufferReceivedLen,
+ _curContentLen, pPayload, payloadLen, 0);
+ // Handle split-payload situation
+ if (_curContentLen > 0 && payloadLen < _curContentLen)
+ {
+ _curSplitPayloadPos = payloadLen;
+ _curWebServerCmdDef = (*it);
+ }
+ else
+ {
+ clientSocketConn.send_all(respStr, strlen(respStr));
+ }
+ handledOk = true;
}
else if ( ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE) ||
((*it)->_cmdType == RdWebServerCmdDef::CMD_SDORUSBFILE) )
@@ -191,13 +224,13 @@
char combinedFileName[MAX_FILENAME_LEN];
strcpy(combinedFileName, (*it)->_substFileName);
if (strlen(combinedFileName) == 0)
- strcpy(combinedFileName, cmdStr);
+ strcpy(combinedFileName, _curCmdStr);
else if (combinedFileName[strlen(combinedFileName)-1] == '*')
- strcpy(combinedFileName+strlen(combinedFileName)-1, argStr);
+ strcpy(combinedFileName+strlen(combinedFileName)-1, _curArgStr);
if ((*it)->_cmdType == RdWebServerCmdDef::CMD_LOCALFILE)
- handledOk = handleLocalFileRequest(combinedFileName, argStr, clientSocketConn, (*it)->_bCacheIfPossible);
+ handledOk = handleLocalFileRequest(combinedFileName, _curArgStr, clientSocketConn, (*it)->_bCacheIfPossible);
else
- handledOk = handleSDFileRequest(combinedFileName, argStr, clientSocketConn);
+ handledOk = handleSDFileRequest(combinedFileName, _curArgStr, clientSocketConn);
}
break;
@@ -207,12 +240,16 @@
if (!cmdFound)
{
char combinedFileName[MAX_FILENAME_LEN];
- strcpy(combinedFileName, cmdStr);
+ strcpy(combinedFileName, _curCmdStr);
strcat(combinedFileName, "/");
- strcat(combinedFileName, argStr);
- handledOk = handleSDFileRequest(cmdStr, argStr, clientSocketConn);
+ strcat(combinedFileName, _curArgStr);
+ handledOk = handleSDFileRequest(combinedFileName, _curArgStr, clientSocketConn);
}
}
+ else
+ {
+ RD_DBG("Cannot find command or args\r\n");
+ }
return handledOk;
}
@@ -507,14 +544,24 @@
#endif
// Extract arguments from command
-bool RdWebServer::extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen)
+bool RdWebServer::extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen, int& contentLen)
{
+ contentLen = -1;
*pCmdStr = '\0';
*pArgStr = '\0';
int cmdStrLen = 0;
int argStrLen = 0;
if (buf == NULL)
return false;
+ // Check for Content-length header
+ char* contentLenText = "Content-Length:";
+ char* pContLen = strstr(buf, contentLenText);
+ if (pContLen)
+ {
+ if (*(pContLen + strlen(contentLenText)) != '\0')
+ contentLen = atoi(pContLen + strlen(contentLenText));
+ }
+
// Check for first slash
char* pSlash1 = strchr(buf, '/');
if (pSlash1 == NULL)
@@ -548,15 +595,19 @@
return true;
}
-unsigned char* RdWebServer::getPayloadDataFromMsg(char* msgBuf)
+unsigned char* RdWebServer::getPayloadDataFromMsg(char* msgBuf, int msgLen, int& payloadLen)
{
+ payloadLen = -1;
char* ptr = strstr(msgBuf, "\r\n\r\n");
if (ptr)
+ {
+ payloadLen = msgLen - (ptr+4-msgBuf);
return (unsigned char*) (ptr+4);
- return (unsigned char*) "\0";
+ }
+ return NULL;
}
-int RdWebServer::getPayloadLengthFromMsg(char* msgBuf)
+int RdWebServer::getContentLengthFromMsg(char* msgBuf)
{
char* ptr = strstr(msgBuf, "Content-Length:");
if (ptr)
--- a/RdWebServer.h Mon Aug 31 08:54:17 2015 +0000
+++ b/RdWebServer.h Mon Aug 31 15:00:41 2015 +0000
@@ -53,6 +53,9 @@
#define RD_ERR(x, ...)
#endif
+// Length of strings
+const int MAX_CMDSTR_LEN = 100;
+const int MAX_ARGSTR_LEN = 100;
const int SUBST_MAX_FNAME_LEN = 40; // note local files on MBED are 8.3 but this might include other files
extern RawSerial pc;
@@ -79,7 +82,8 @@
int _nFileLen;
};
-typedef char* (*CmdCallbackType)(int method, char*cmdStr, char* argStr, char* msgBuffer);
+typedef char* (*CmdCallbackType)(int method, char*cmdStr, char* argStr, char* msgBuffer, int msgLen,
+ int contentLen, unsigned char* pPayload, int payloadLen, int splitPayloadPos);
class RdWebServerCmdDef
{
@@ -131,8 +135,8 @@
void run();
void addCommand(char* pCmdStr, int cmdType, CmdCallbackType callback = NULL, char* substFileName = NULL, bool cacheIfPossible = false);
- static unsigned char* getPayloadDataFromMsg(char* msgBuf);
- static int getPayloadLengthFromMsg(char* msgBuf);
+ static unsigned char* getPayloadDataFromMsg(char* msgBuf, int msgLen, int& payloadLen);
+ static int getContentLengthFromMsg(char* msgBuf);
private :
int _port;
@@ -141,10 +145,11 @@
bool _initOk;
std::vector<RdWebServerCmdDef*> _commands;
std::vector<RdFileCacheEntry*> _cachedFiles;
- bool extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen);
+ bool extractCmdArgs(char* buf, char* pCmdStr, int maxCmdStrLen, char* pArgStr, int maxArgStrLen, int& contentLen);
char* _pBaseWebFolder;
char _httpHeader[HTTPD_MAX_HDR_LENGTH+1];
char _buffer[HTTPD_MAX_REQ_LENGTH+1];
+ int _bufferReceivedLen;
bool handleLocalFileRequest(char* cmdStr, char* argStr, TCPSocketConnection &client, bool bCacheIfPossible);
bool handleSDFileRequest(char* cmdStr, char* argStr, TCPSocketConnection &client);
@@ -159,6 +164,14 @@
int _timeoutOnBlocking;
bool _closeConnAfterSend;
bool _closeConnOnReceiveFail;
+
+ // Handling of split payloads on receipt (e.g. POST)
+ int _curSplitPayloadPos;
+ char _curCmdStr[MAX_CMDSTR_LEN];
+ char _curArgStr[MAX_ARGSTR_LEN];
+ int _curContentLen;
+ int _curHttpMethod;
+ RdWebServerCmdDef* _curWebServerCmdDef;
};