A simple web server that can be bound to either the EthernetInterface or the WiflyInterface.

Dependents:   Smart-WiFly-WebServer WattEye X10Svr SSDP_Server

Files at this revision

API Documentation at this revision

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.