A simple web server that can be bound to either the EthernetInterface or the WiflyInterface.
Dependents: Smart-WiFly-WebServer WattEye X10Svr SSDP_Server
Revision 39:0427544a5c08, committed 2014-09-01
- Comitter:
- WiredHome
- Date:
- Mon Sep 01 20:53:19 2014 +0000
- Parent:
- 38:c8fa31e6fe02
- Child:
- 40:02c49fadbb94
- Commit message:
- Removed requirement for WiFly module - permitting any Ethernet compatible interface.; Improved the processing of POST method.
Changed in this revision
SW_HTTPServer.cpp | Show annotated file Show diff for this revision Revisions of this file |
SW_HTTPServer.h | Show annotated file Show diff for this revision Revisions of this file |
--- a/SW_HTTPServer.cpp Sat Jul 26 19:49:13 2014 +0000 +++ b/SW_HTTPServer.cpp Mon Sep 01 20:53:19 2014 +0000 @@ -35,7 +35,7 @@ const char * DEFAULT_FILENAME = "index.htm"; // Header information to always send (must be \r\n terminated) -const char hdr_httpver[] = "HTTP/1.0"; // Wifly may not be able to support HTTP/1.1 protocol +const char hdr_httpver[] = "HTTP/1.0"; // supported HTTP/1.1 protocol const char hdr_age[] = "Max-age: 0\r\n"; // expires right away const char hdr_server[] = "Server: Smart_Server v0.1\r\n"; // Server const char hdr_close[] = "Connection: close\r\n"; // tell the client the server closes the connection immediately @@ -92,7 +92,6 @@ #endif HTTPServer::HTTPServer( - void * _wf, int port, const char * _webroot, int maxheaderParams, @@ -102,15 +101,21 @@ int _allocforheader, int _allocforfile) { - (void)_wf; webroot = (char *)malloc(strlen(_webroot)+1); strcpy(webroot, _webroot); if (strlen(webroot)>1 && webroot[strlen(webroot)-1] == '/') // remove trailing '/' webroot[strlen(webroot)-1] = '\0'; - maxqueryParams = _maxqueryParams; maxdynamicpages = _maxdynamicpages; headerParams = (namevalue *)malloc(maxheaderParams * sizeof(namevalue)); + + maxqueryParams = _maxqueryParams; queryParams = (namevalue *)malloc(maxqueryParams * sizeof(namevalue)); + queryParamCount = 0; + + maxPostParams = _maxqueryParams; // Same as Query params, but for post method + postParams = (namevalue *)malloc(maxPostParams * sizeof(namevalue)); + postParamCount = 0; + handlers = (handler *)malloc(maxdynamicpages * sizeof(handler)); headerbuffersize = _allocforheader; headerbuffer = (char *)malloc(headerbuffersize); @@ -118,7 +123,6 @@ queryType = NULL; queryString = NULL; postQueryString = NULL; - queryParamCount = 0; handlercount = 0; maxheaderbytes = 0; server = new TCPSocketServer(); @@ -336,6 +340,7 @@ return 16 * HexCharToInt(*p) + HexCharToInt(*(p+1)); } +// modifies in-place void HTTPServer::UnescapeString(char * encoded) { char *p; @@ -377,10 +382,38 @@ return NULL; } +HTTPServer::namevalue * HTTPServer::GetParameter(int index) +{ + if (index < queryParamCount) + return &queryParams[index]; + else + return NULL; +} + +const char * HTTPServer::GetPostParameter(const char * name) +{ + INFO("GetPostParameter(%s)", name); + for (int i=0; i<postParamCount; i++) { + INFO(" %d: %s = %s", i, postParams[i].name, postParams[i].value); + if (strcmp(postParams[i].name, name) == 0) { + INFO(" value {%s}", postParams[i].value); + return postParams[i].value; + } + } + return NULL; +} + +HTTPServer::namevalue * HTTPServer::GetPostParameter(int index) +{ + if (index < postParamCount) + return &postParams[index]; + else + return NULL; +} // this=that&who=what&more=stuff... // ^ ^ ^ -int HTTPServer::ParseParameters(char * pName) +int HTTPServer::ParseParameters(namevalue * qP, int * qpCount, int maxP, char * pName) { char * pVal; char * pNextName; @@ -390,14 +423,14 @@ if (pVal) *pVal = '\0'; do { - INFO("ParseParameters(%s)", pName); - queryParams[queryParamCount].name = pName; + INFO("ParseParameters(%s), qP:{%s}, qpCount: %d", pName, qP->name, *qpCount); + qP->name = pName; pVal = strchr(pName, '='); pNextName = strchr(pName,'&'); if (pVal) { if (pNextName == NULL || (pNextName && pNextName > pVal)) { *pVal++ = '\0'; - queryParams[queryParamCount].value = pVal; + qP->value = pVal; pName = pVal; } } @@ -407,11 +440,12 @@ } else { pName = NULL; } - INFO(" param{%s}={%s}", queryParams[queryParamCount].name, queryParams[queryParamCount].value); - queryParamCount++; - } while (pName && queryParamCount < maxqueryParams); - INFO(" count %d", queryParamCount); - return queryParamCount; + INFO(" param{%s}={%s}", qP->name, qP->value); + *qpCount += 1; + qP++; + } while (pName && *qpCount < maxP); + INFO(" count %d", *qpCount); + return *qpCount; } @@ -522,6 +556,7 @@ // If this queryString is in the list of registered handlers, call that for (int i=0; i<handlercount; i++) { if (strcmp(handlers[i].path, queryString) == 0) { + INFO("CheckDynamicHandlers - SEND_PAGE"); (*handlers[i].callback)(this, SEND_PAGE, queryString, queryParams, queryParamCount); regHandled = true; break; // we only execute the first one @@ -586,7 +621,7 @@ // GET /QueryString?this=that&sky=blue HTTP/1.1\r\n // GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Con // GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Control: max-age=0\r\n\r\n - // POST /dyn2 HTTP/1.2\r\nAccept: text/html, application/xhtml+xml, */*\r\n\r\nle + // POST /dyn2 HTTP/1.2\r\nAccept: text/html, application/xhtml+xml, */*\r\n\r\n dblCR = strstr(buffer,"\r\n\r\n"); if (dblCR) { // Have to scan from the beginning in case split on \r INFO("\r\n==\r\n%s==", buffer); @@ -611,7 +646,7 @@ strcpy(queryType, "GET"); } } else if (strstr(soRec, "POST ") == soRec) { - Extract(soRec, "POST", &queryString); + Extract(soRec, "POST", &queryString); if (queryString) { queryType = (char *)mymalloc(strlen("POST")+1); strcpy(queryType, "POST"); @@ -648,7 +683,7 @@ if (paramDelim) { *paramDelim++ = '\0'; UnescapeString(paramDelim); // everything after the '?' - ParseParameters(paramDelim); // pointing past the NULL, and there are queryParams here + ParseParameters(queryParams, &queryParamCount, maxqueryParams, paramDelim); // pointing past the NULL, and there are queryParams here } } else { ERR("queryString not found in (%s) [this should never happen]", soRec); @@ -704,12 +739,12 @@ strcpy(postQueryString, dblCR); INFO(" {%s}", postQueryString); while (ttlCount <= postBytes && !escape) { - INFO("ttlCount: %d < postBytes: %d, of chunk %d", ttlCount, postBytes, CHUNK_SIZE); + INFO("ttlCount: %d <= postBytes: %d, of chunk %d", ttlCount, postBytes, CHUNK_SIZE); len = client.receive_all(postQueryString + ttlCount, CHUNK_SIZE - ttlCount); if (len > 0) { - INFO(" len: %d, ttlCount: %d < postBytes %d, {%s}", len, ttlCount, postBytes, postQueryString); + INFO(" len: %d, ttlCount: %d < postBytes %d, {%s}", len, ttlCount, postBytes, postQueryString + ttlCount); ttlCount += len; - postQueryString[len] = '\0'; // Whether binary or ASCII, this is ok as it's after the data + postQueryString[ttlCount] = '\0'; // Whether binary or ASCII, this is ok as it's after the data INFO(" postBytes %d: [%s], [%d]", postBytes, postQueryString, ndxHandler); escapePlan.reset(); } else if (len < 0) { @@ -723,10 +758,12 @@ WARN("Escape plan activated."); } } - acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER, postQueryString, NULL, 0); + postParamCount = 0; + INFO("post: %s", postQueryString); + ParseParameters(postParams, &postParamCount, maxPostParams, postQueryString); + acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER, queryString, queryParams, queryParamCount); INFO("..processing exit"); acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER_END, NULL, NULL, 0); - myfree(postQueryString); } else { ERR("attempt to allocate %d failed.", CHUNK_SIZE); }
--- a/SW_HTTPServer.h Sat Jul 26 19:49:13 2014 +0000 +++ b/SW_HTTPServer.h Mon Sep 01 20:53:19 2014 +0000 @@ -2,9 +2,6 @@ #ifndef SW_HTTPSERVER_H #define SW_HTTPSERVER_H #include "mbed.h" -//#include "MODSERIAL.h" // would like to hook in mod serial for higher performance, less blocking -//#include "RawSerial.h" -//#include "Wifly.h" #include "TCPSocketServer.h" #include "TCPSocketConnection.h" @@ -28,7 +25,7 @@ #define MAX_HEADER_SIZE 1000 -/// HTTPServer is a simple web server using the WiFly module. +/// HTTPServer is a simple web server leveraging a network interface. /// /// While simple, it is a capable, web server. The basic mode /// of operation is for it to serve static web pages from an available @@ -45,7 +42,7 @@ /// or signaling outputs. /// /// @code -/// HTTPServer svr(&wifly, HTTP_SERVER_PORT, "/local", 15, 30, 10, &pc); +/// HTTPServer svr(HTTP_SERVER_PORT, "/local", 15, 30, 10, &pc); /// svr.RegisterHandler("/dyn1", SimpleDynamicPage); /// while (true) /// { @@ -56,9 +53,6 @@ /// This web server used nweb as a starting point, but expanded well beyond there. /// http://www.ibm.com/developerworks/systems/library/es-nweb/sidefile1.html /// -/// @note This server uses a modified version of the mbed WiflyInterface - there -/// were a number of performance issues identified and resolved in the local version. -/// /// Given: scheme://server:port/path?query_string#fragment_id /// @li scheme is "http" /// @li server is whatever IP the server has @@ -77,17 +71,19 @@ /// depending on the actions being performed and can span hundreds of msec. /// /// Limitations: -/// @li Supports only a single connection at a time. -/// A web page with served objects (img src=...) is rarely served properly. It -/// might trace to forcing the connection to close, but not yet sure. -/// Explore "Set Uart Rx Data Buffer" in WiFly manual 2.3.65. -/// This is a limitation of the Wifly module. No solution is forthcoming, -/// so a simple workaround is to use javascript to load the images after -/// the page loads. +/// @li When used with Wifly network interface it supports only a single +/// connection at a time. A web page with served objects (img src=...) +/// is rarely served properly. It might trace to forcing the connection to +/// close, but not yet sure. Explore "Set Uart Rx Data Buffer" in +/// WiFly manual 2.3.65. This is a limitation of the Wifly module. +/// No solution is forthcoming, so a crude workaround is to use javascript +/// to load the images after the page loads. /// @li Rapid requests for page objects (e.g. embedded images) are lost. Still /// working to understand this issue. /// /// Improvements: +/// @li removed the relationship to the Wifly module, which caused an API change +/// in the constructor by elimination of the first parameter. /// @li hunted down several lengthy operations - the speed of the file system /// and the "close" operation which requires <delay 0.25s>$$$<delay>close\r. /// @li parses the header similar to the query string, and then makes @@ -230,7 +226,6 @@ /** * Create the HTTPServer object. * - * @param wifly is the serial port with the wifly interface. This is not longer used. * @param port is the optional parameter for the port number to use, default is 80. * @param webroot is a file system path to the root folder for the web space. If any trailing '/' * is included (e.g. "/web/path/") it will be removed (to "/web/path"). @@ -244,7 +239,7 @@ * @param allocforfile is the memory allocation to support sending a file to the client. This is * typically sized to fit an ethernet frame. */ - HTTPServer(void * wifly, int port = 80, const char * webroot = "/", int maxheaderParams = 15, + HTTPServer(int port = 80, const char * webroot = "/", int maxheaderParams = 15, int maxqueryParams = 30, int maxdynamicpages = 10, PC * pc = NULL, int _allocforheader = MAX_HEADER_SIZE, int _allocforfile = FILESEND_BUF_SIZE); @@ -393,7 +388,7 @@ const char * GetSupportedType(const char * filename); /** - * search the available parameters for 'name' and if found, return the 'value' + * search the available query parameters for 'name' and if found, return the 'value' * * After the querystring is parsed, the server maintains an array of * name=value pairs. This Get function will search for the passed in name @@ -411,16 +406,73 @@ const char * GetParameter(const char * name); /** + * get a pointer to a name-value pair based on the index. + * + * @param index is the item being referenced + * @return pointer to the namevalue, or NULL + */ + namevalue * GetParameter(int index); + + /** + * Get the count of query parameters from the active transaction. + * + * @returns count of parameters. + */ + int GetParameterCount(void) + { + return queryParamCount; + }; + + /** + * search the available post parameters for 'name' and if found, return the 'value' + * + * After the post parameter string is parsed, the server maintains an array of + * name=value pairs. This Get function will search for the passed in name + * and provide access to the value. + * + * @code + * BusOut leds(LED1,LED2,LED3,LED4); + * ... + * leds = atoi(svr->GetPostParameter("leds")); + * @endcode + * + * @param name is the name to search for + * @return pointer to the value, or NULL + */ + const char * GetPostParameter(const char * name); + + /** + * get a pointer to a post parameter name-value pair based on the index. + * + * @param index is the item being referenced + * @return pointer to the namevalue, or NULL + */ + namevalue * GetPostParameter(int index); + + /** + * Get the count of post parameters from the active transaction. + * + * @returns count of parameters. + */ + int GetPostParameterCount(void) + { + return postParamCount; + }; + + /** * Parse the text string into name=value parameters. * * This will directly modify the referenced string. If there is a * #fragment_id on the end of the string, it will be removed. * - * @param pString is a pointer to the string. + * @param qP is a pointer to a namevalue set + * @param qpCount is a pointer to a counter of what is in the set + * @param maxP is the maximum number of parameters for which space has been allocated. + * @param pName is a pointer to the string. * @returns The total number of items that have been parsed, * which can include a count from a url query string. */ - int ParseParameters(char * pString); + int ParseParameters(namevalue * qP, int * qpCount, int maxP, char * pName); /** * Unescape string converts a coded string "in place" into a normal string. @@ -530,6 +582,10 @@ int maxqueryParams; int queryParamCount; + namevalue *postParams; // Same as Query params, but for post method + int maxPostParams; + int postParamCount; + namevalue *headerParams; // Header params Host: 192.168...\r\nConnection: keep-alive\r\n... int maxheaderParams; int headerParamCount; @@ -563,8 +619,8 @@ int handlercount; char * queryType; - char * queryString; - char * postQueryString; + char * queryString; // the query string [and 'GET' data] passed on the URL (e.g. ?name1=value1&name2=value2...) + char * postQueryString; // the post data /** * Extract the parameter from the record, by searching for the needle in the haystack.