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:
Sun May 11 21:16:42 2014 +0000
Parent:
36:1bb5fa6b109c
Child:
38:c8fa31e6fe02
Commit message:
Enhanced to support POST as well as GET methods.

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	Fri May 02 00:26:06 2014 +0000
+++ b/SW_HTTPServer.cpp	Sun May 11 21:16:42 2014 +0000
@@ -17,20 +17,6 @@
 
 #include "SW_HTTPServer.h"      // define DEBUG before this
 
-#if 0
-#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
-#define DBG(x, ...)  pc->printf("[DBG %s%4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
-#define WARN(x, ...) pc->printf("[WRN %s%4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
-#define ERR(x, ...)  pc->printf("[ERR %s%4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
-#define INFO(x, ...) pc->printf("[INF %s%4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
-#else
-#define DBG(x, ...)
-#define WARN(x, ...)
-#define ERR(x, ...)
-#define INFO(x, ...)
-#endif
-#endif
-
 #define CHUNK_SIZE 1500     // max size of a single chunk (probably limited by Ethernet to 1500)
 #define HANG_TIMEOUT_MS 250 // If we're waiting on the host, which may never respond, this is the timeout
 
@@ -368,17 +354,21 @@
 
 const char * HTTPServer::GetParameter(const char * name)
 {
+    INFO("GetParameter(%s)", name);
     for (int i=0; i<queryParamCount; i++) {
+        INFO("  %d: %s = %s", i, queryParams[i].name, queryParams[i].value);
         if (strcmp(queryParams[i].name, name) == 0) {
+            INFO("  value {%s}", queryParams[i].value);
             return queryParams[i].value;
         }
     }
     return NULL;
 }
 
+
 // this=that&who=what&more=stuff...
 // ^   ^    ^
-void HTTPServer::ParseParameters(char * pName)
+int HTTPServer::ParseParameters(char * pName)
 {
     char * pVal;
     char * pNextName;
@@ -388,6 +378,7 @@
     if (pVal)
         *pVal = '\0';
     do {
+        INFO("ParseParameters(%s)", pName);
         queryParams[queryParamCount].name = pName;
         pVal = strchr(pName, '=');
         pNextName = strchr(pName,'&');
@@ -398,14 +389,17 @@
                 pName = pVal;
             }
         }
-        queryParamCount++;
         if (pNextName) {
             pName = pNextName;
             *pName++ = '\0';
         } else {
             pName = NULL;
         }
+        INFO("  param{%s}={%s}", queryParams[queryParamCount].name, queryParams[queryParamCount].value);
+        queryParamCount++;
     } while (pName && queryParamCount < maxqueryParams);
+    INFO("  count %d", queryParamCount);
+    return queryParamCount;
 }
 
 
@@ -580,6 +574,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
     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);
@@ -596,14 +591,15 @@
                 *(eoRec-1) = '\0';
             // Inspect the supported query types (GET, POST) and ignore (HEAD, PUT, OPTION, DELETE, TRACE, CONNECT]
             // This is very clumsy
-            if (strstr(soRec, "GET ") == soRec) {
+            INFO("method: %s", soRec);
+             if (strstr(soRec, "GET ") == soRec) {
                 Extract(soRec, "GET", &queryString);
                 if (queryString) {
                     queryType = (char *)mymalloc(strlen("GET")+1);
                     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");
@@ -621,8 +617,8 @@
                 *delim++ = '\0';
                 headerParams[headerParamCount].name = soRec;
                 headerParams[headerParamCount].value = delim;
-                //INFO("%d: headerParams[%s] = {%s}", headerParamCount,
-                //     headerParams[headerParamCount].name, headerParams[headerParamCount].value);
+                INFO("%d: headerParams[%s] = {%s}", headerParamCount,
+                     headerParams[headerParamCount].name, headerParams[headerParamCount].value);
                 headerParamCount++;
             }
             soRec = eoRec + 1;
@@ -631,7 +627,7 @@
         
         if (queryString) {
             // We have enough to try to reply
-            INFO("create reply queryType{%s}, queryString{%s}", "GET", queryString);
+            INFO("create reply queryType{%s}, queryString{%s}", queryType, queryString);
             // parse queryParams - if any
             // /file.htm?name1=value1&name2=value2...
             // /file.htm?name1&name2=value2...
@@ -640,7 +636,7 @@
             if (paramDelim) {
                 *paramDelim++ = '\0';
                 UnescapeString(paramDelim);   // everything after the '?'
-                ParseParameters(paramDelim);  // pointing at the NULL, but there are queryParams beyond
+                ParseParameters(paramDelim);  // pointing past the NULL, and there are queryParams here
             }
         } else {
             ERR("queryString not found in (%s) [this should never happen]", soRec);
@@ -656,19 +652,21 @@
         int postBytes = atoi(GetHeaderValue("Content-Length"));
         CallBackResults acceptIt = ACCEPT_ERROR;
         if (strcmp(queryType, "POST") == 0 && postBytes > 0 ) {
+            INFO("parse POST data %d bytes", postBytes);
             if (postBytes) {
                 int ndxHandler = 0;
                 bool regHandled = false;
                 // Registered Dynamic Handler
                 // Callback and ask if they want to accept this data
                 for (ndxHandler=0; ndxHandler<handlercount; ndxHandler++) {
+                    INFO("is '%s' a handler for '%s' ?", handlers[ndxHandler].path, queryString);
                     if (strcmp(handlers[ndxHandler].path, queryString) == 0) {
                         acceptIt = (*handlers[ndxHandler].callback)(this, CONTENT_LENGTH_REQUEST, queryString, queryParams, queryParamCount);
                         regHandled = true;
                         break;      // only one callback per path allowed
                     }
                 }
-
+                INFO("reghandled: %d, acceptIt: %d", regHandled, acceptIt);
                 if (regHandled && acceptIt != ACCEPT_ERROR) {
                     // @todo need to refactor - if the thing is bigger than the buffer,
                     //       then we can receive it a chunk at a time, and hand off
@@ -679,22 +677,28 @@
                     //
                     // If so, we'll make space for it
                     postQueryString = (char *)mymalloc(CHUNK_SIZE);
-                    INFO("Free space %d", Free());
+                    //INFO("Free space %d", Free());
+                    INFO("postQueryString %p", postQueryString);
                     if (postQueryString) {
-                        int len;
-                        int ttlCount = 4;           // includes the doubleCR in the total count from Content-Length
+                        int len = 0;
+                        int ttlCount;
                         Timer escapePlan;
                         bool escape = false;
                         
+                        INFO("Processing tail...");
                         escapePlan.start();
-                        //INFO("Processing");
-                        while (ttlCount < postBytes && !escape) {
-                            len = client.receive_all(postQueryString, CHUNK_SIZE);
+                        dblCR += 4;     // There may be some after the double CR that we need
+                        ttlCount = strlen(dblCR);
+                        strcpy(postQueryString, dblCR);
+                        INFO("  {%s}", postQueryString);
+                        while (ttlCount <= postBytes && !escape) {
+                            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);
                                 ttlCount += len;
                                 postQueryString[len] = '\0';    // Whether binary or ASCII, this is ok as it's after the data
-                                //INFO("%d bytes is %d of %d: [%s]", len, ttlCount, postBytes, postQueryString);
-                                acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER, postQueryString, NULL, len);
+                                INFO("  postBytes %d: [%s], [%d]", postBytes, postQueryString, ndxHandler);
                                 escapePlan.reset();
                             } else if (len < 0) {
                                 INFO("*** connection closed ***");
@@ -707,7 +711,8 @@
                                 WARN("Escape plan activated.");
                             }
                         }
-                        //INFO("..processing exit");
+                        acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER, postQueryString, NULL, 0);
+                        INFO("..processing exit");
                         acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER_END, NULL, NULL, 0);
                         myfree(postQueryString);
                     } else {
@@ -715,6 +720,7 @@
                     }
                 } else {
                     // Simply copy it to the bitbucket
+                    WARN("to the bit bucket...");
                     int bytesToDump = postBytes;
                     char * bitbucket = (char *)mymalloc(201);
                     
--- a/SW_HTTPServer.h	Fri May 02 00:26:06 2014 +0000
+++ b/SW_HTTPServer.h	Sun May 11 21:16:42 2014 +0000
@@ -202,7 +202,9 @@
     /**
     * This is the prototype for custom handlers that are activated via a callback
     *
-    * This callback gets overloaded for a few purposes, which can be identified by the \see CallBackType parameter
+    * This callback gets overloaded for a few purposes, which can be identified by the 
+    * \see CallBackType parameter.
+    *
     * @li CONTENT_LENGTH_REQUEST - the server is asking the callback if it wants to receive the message,
     *       which may require significant memory. If the request is accepted, true should be returned.
     *       If the request is denied, false should be returned.
@@ -220,14 +222,17 @@
     *
     * @param svr is a handle to this class, so the callback has access to member functions
     * @param type is the callback type @see CallBackType
-    * @param path is the pointer to a large block of information being transferred
+    * @param path is the pointer to a large block of information being transferred. This pointer
+    *        references a dynamically managed resource, so any information of value must be 
+    *        extracted from here, and not referenced into this memory space.
     * @param queryParams is a pointer based on the callback type.
     * @param count is the number of items - for type = CONTENT_LENGTH_REQUEST this is the number of 
     *        name=value pars in the queryParams parameter, and for the DATA_TRANSFER this is the 
     *        number of bytes being passed in the path parameters.
     * @return one of the @see CallBackResults signals indicating error or successes
     */
-    typedef CallBackResults (* Handler)(HTTPServer * svr, CallBackType type, const char *path, const namevalue *queryParams, int queryParamCount);
+    typedef CallBackResults (* Handler)(HTTPServer * svr, CallBackType type, const char *path, 
+        const namevalue *queryParams, int queryParamCount);
 
     /**
     * Create the HTTPServer object.
@@ -236,16 +241,19 @@
     * @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").
-    * @param maxheaderParams defines the maximum number of parameters to extract from a header (Host: 192..\r\nConnection: keep-alive\r\n...)
-    * @param maxqueryParams defines the maximum number of query parameters to a dynamic function (and the memory to support them).
+    * @param maxheaderParams defines the maximum number of parameters to extract from a header 
+    *        (Host: 192..\r\nConnection: keep-alive\r\n...)
+    * @param maxqueryParams defines the maximum number of query parameters to a dynamic function 
+    *        (and the memory to support them).
     * @param maxdynamicpages defines the maximum number of dynamic pages that can be registered.
     * @param pc is the serial port for debug information (I should transform this to a log interface)
     * @param allocforheader is the memory allocation to support the largest expected header from a client
-    * @param allocforfile is the memory allocation to support sending a file to the client. This is typically sized to fit
-    *        an ethernet frame.
+    * @param allocforfile is the memory allocation to support sending a file to the client. This is 
+    *        typically sized to fit an ethernet frame.
     */
-    HTTPServer(Wifly * wifly, 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);
+    HTTPServer(Wifly * wifly, 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);
 
     /**
     * Destructor, which can clean up memory.
@@ -287,7 +295,8 @@
     * @param optional_text is a pointer to any other text that is part of the header, which must
     *        have \r\n termination.
     */
-    void header(int code = 404, const char * code_text = "Not Found", const char * content_type = NULL, const char * optional_text = NULL);
+    void header(int code = 404, const char * code_text = "Not Found", const char * content_type = NULL, 
+        const char * optional_text = NULL);
 
     /**
     * Send text to the client
@@ -326,7 +335,8 @@
     *   svr.RegisterHandler("/dyn1", SimpleDynamicPage);svr.RegisterHandler("/dyn1", SimpleDynamicPage);
     *   ...
     *
-    *   bool SimpleDynamicPage(HTTPServer *svr, HTTPServer::CallBackType type, const char * path, const HTTPServer::namevalue *queryParams, int queryParamCount) {
+    *   bool SimpleDynamicPage(HTTPServer *svr, HTTPServer::CallBackType type, const char * path, 
+    *       const HTTPServer::namevalue *queryParams, int queryParamCount) {
     *       char buf[100];
     *       bool ret = false;
     *
@@ -414,8 +424,10 @@
     * #fragment_id on the end of the string, it will be removed.
     *
     * @param pString 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.
     */
-    void ParseParameters(char * pString);
+    int ParseParameters(char * pString);
 
     /**
     * Unescape string converts a coded string "in place" into a normal string.