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:
Fri Jan 03 19:12:22 2014 +0000
Parent:
32:7ded9bacb546
Child:
34:019212c05980
Commit message:
Numerous changes related to debug, and cache management, as well as for routing transactions from a POST command.

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	Mon Dec 30 23:03:37 2013 +0000
+++ b/SW_HTTPServer.cpp	Fri Jan 03 19:12:22 2014 +0000
@@ -12,12 +12,27 @@
 //
 #include "mbed.h"
 
+#define DEBUG "HTTP"
+#include "Utility.h"
+
 #include "SW_HTTPServer.h"      // define DEBUG before this
 
-#define DEBUG "httpd"
-#include "Utility.h"
+#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 CHUNKSIZE 1600      // When receiving and handing partial to the app, this is the chunk size
+#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
 
 const char * DEFAULT_FILENAME = "index.htm";
 
@@ -37,6 +52,7 @@
     {".jpg", "Content-Type: image/jpeg\r\n"         },
     {".jpeg","Content-Type: image/jpeg\r\n"         },
     {".ico", "Content-Type: image/x-icon\r\n"       },
+    {".bmp", "Content-Type: image/bmp\r\n"          },
     {".png", "Content-Type: image/png\r\n"          },
     {".zip", "Content-Type: image/zip\r\n"          },
     {".gz",  "Content-Type: image/gz\r\n"           },
@@ -107,10 +123,12 @@
     queryParamCount = 0;
     handlercount = 0;
     maxheaderbytes = 0;
+    INFO("HTTPServer::HTTPServer");
     server = new TCPSocketServer();
     server->bind(port);
     server->listen();
     server->set_blocking(false, 10);
+    client.set_blocking(false, 100);             //@TODO client is separate from server. any way to combine?
     ResetPerformanceData();
     PerformanceTimer.start();
 }
@@ -220,7 +238,18 @@
 
         case ReceivingPayload:
             // After the header, there is a payload that will be handled
+#if 1
+            n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer));
+            if (n < 0) {
+                op = Sending;
+                INFO("*** client.receive() => %d", n);
+            } else if (n) {
+                bPtr[n] = '\0';
+                INFO("*** payload size %d", n);
+            }
+#else
             op = Sending;
+#endif
             break;
 
         case Sending:
@@ -645,38 +674,45 @@
                     // @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
                     //       the chunks to the callback. May need callbacks that
-                    //       are: START: extract the filename/object name,
-                    //            NEXT:  a chunk of data,
-                    //            END:   signals that all chunks were delivered.
+                    //       are: DATA_TRANSFER: self-detect to extract the filename/object name,
+                    //            DATA_TRANSFER: subsequent chunk of data,
+                    //            DATA_TRANSFER_END:   signals that last chunk is enclosed.
                     //
                     // If so, we'll make space for it
-                    postQueryString = (char *)mymalloc(CHUNKSIZE);
+                    postQueryString = (char *)mymalloc(CHUNK_SIZE);
                     INFO("Free space %d", Free());
                     if (postQueryString) {
-                        int ttlReceived = 0;
-
-                        INFO("Processing");
-                        dblCR += 4;                         // If we slurped up any of the POST,
-                        while (*dblCR && *dblCR <= ' ')
-                            dblCR++;
-                        strcpy(postQueryString, dblCR);     // copy that in and then get the rest
-                        while (ttlReceived < postBytes) {
-                            int len;
-                            wait_ms(4);
-                            len = client.receive(postQueryString, CHUNKSIZE);
-                            if (len >=0) {
-                                INFO("Passing %d bytes (%d of %d) in [%s]", len, ttlReceived, postBytes, postQueryString);
-                                ttlReceived += len;
+                        int len;
+                        int ttlCount = 4;           // includes the doubleCR in the total count from Content-Length
+                        Timer escapePlan;
+                        bool escape = false;
+                        
+                        escapePlan.start();
+                        //INFO("Processing");
+                        while (ttlCount < postBytes && !escape) {
+                            len = client.receive_all(postQueryString, CHUNK_SIZE);
+                            if (len > 0) {
+                                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);
+                                escapePlan.reset();
                             } else if (len < 0) {
-                                INFO("*** receive returned %d ***", len);
-                                break;      // no more data
+                                INFO("*** connection closed ***");
+                                break;      // no more data, before the plan
+                            } else { // n == 0
+                                ;
+                            }
+                            if (escapePlan.read_ms() > HANG_TIMEOUT_MS) {
+                                escape = true;
+                                WARN("Escape plan activated.");
                             }
                         }
+                        //INFO("..processing exit");
+                        acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER_END, NULL, NULL, 0);
                         myfree(postQueryString);
-                        INFO("done.");
                     } else {
-                        ERR("HTTPd: attempt to allocate %d bytes failed.", CHUNKSIZE);
+                        ERR("attempt to allocate %d failed.", CHUNK_SIZE);
                     }
                 } else {
                     // Simply copy it to the bitbucket
--- a/SW_HTTPServer.h	Mon Dec 30 23:03:37 2013 +0000
+++ b/SW_HTTPServer.h	Fri Jan 03 19:12:22 2014 +0000
@@ -188,6 +188,7 @@
     typedef enum CALLBACKTYPE {
         CONTENT_LENGTH_REQUEST, ///< ask the client if they wish to accept the data, typically from a POST event
         DATA_TRANSFER,          ///< used when submitting a file via a form
+        DATA_TRANSFER_END,      ///< used when all data has been given to the client (may be used to close the file)
         SEND_PAGE,              ///< the activated method should now send the page
     } CallBackType;
 
@@ -211,15 +212,19 @@
     *       When the callback returns, it should always indicate true that it has sent the page.
     *
     * @note The queryParams pointer purpose depends on the callback type.
-    *       For CONTENT_LENGTH_REQUEST, the queryParams pointer points to the name=value pairs from the 
+    *       For CONTENT_LENGTH_REQUEST, the pointer points to the name=value pairs from the 
     *           header.
-    *       For DATA_TRANSFER, the queryParams pointer points to the start of the chunk of data, queryParamCount is # bytes.
+    *       For DATA_TRANSFER, the pointer points to the start of the actual data.
     *       For SEND_PAGE, ... <to be determined>
     *
     * @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 queryParams is a pointer based on the callback type.
-    * @queryParamCount is the number of parameters or bytes.
-    * @return true if command was accepted
+    * @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);