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

Dependents:   Smart-WiFly-WebServer WattEye X10Svr SSDP_Server

Revision:
0:729320f63c5c
Child:
2:a29c32190037
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SW_HTTPServer.cpp	Fri May 31 03:13:39 2013 +0000
@@ -0,0 +1,563 @@
+//
+// note Copyright © 2013 by Smartware Computing, all rights reserved.
+//     Individuals may use this application for evaluation or non-commercial
+//     purposes. Within this restriction, changes may be made to this application
+//     as long as this copyright notice is retained. The user shall make
+//     clear that their work is a derived work, and not the original.
+//     Users of this application and sources accept this application "as is" and
+//     shall hold harmless Smartware Computing, for any undesired results while
+//     using this application - whether real or imagined.
+//
+// author David Smart, Smartware Computing
+//
+#include "mbed.h"
+#include "SW_HTTPServer.h"
+#include "Utility.h"
+
+//#define DEBUG
+
+#define FILESEND_BUF_SIZE 1200 // should keep this <= the ethernet frame size
+
+const char * DEFAULT_FILENAME = "index.htm";
+
+// Header information to always send
+const char hdr_httpver[] = "HTTP/1.1";              // typically HTTP/1.0 or HTTP/1.1
+const char hdr_age[] = "Max-age: 0\r\n";            // expires right away (must be \r\n terminated)
+const char hdr_server[] = "Server: Smart_Server v0.1\r\n"; // Server (must be \r\n terminated)
+const char hdr_close[] = "Connection: close\r\n";       // tell the client to close the connection (must be \r\n terminated)
+const char nl[] = "\r\n";                           // final \r\n for the termination of the header
+
+static const struct {
+    char *ext;
+    char *filetype;
+} extensions [] = {
+    {".gif", "Content-Type: image/gif\r\n"   },
+    {".jpg", "Content-Type: image/jpeg\r\n"  },
+    {".jpeg","Content-Type: image/jpeg\r\n"  },
+    {".ico", "Content-Type: image/x-icon\r\n"},
+    {".png", "Content-Type: image/png\r\n"   },
+    {".zip", "Content-Type: image/zip\r\n"   },
+    {".gz",  "Content-Type: image/gz\r\n"    },
+    {".tar", "Content-Type: image/tar\r\n"   },
+    {".htm", "Content-Type: text/html\r\n"   },
+    {".html","Content-Type: text/html\r\n"   },
+    {0,0}
+};
+
+HTTPServer::HTTPServer(Wifly * _wf, int port, const char * _webroot, int _maxparams, int _maxdynamicpages, PC * _pc)
+{
+    wifly = _wf;
+    webroot = (char *)malloc(strlen(_webroot)+1);
+    strcpy(webroot, _webroot);
+    maxparams = _maxparams;
+    maxdynamicpages = _maxdynamicpages;
+    params = (namevalue *)malloc(maxparams * sizeof(namevalue));
+    handlers = (handler *)malloc(maxdynamicpages * sizeof(handler));
+    pc = _pc;
+    paramcount = 0;
+    handlercount = 0;
+    server = new TCPSocketServer();
+    timer = new Timer();
+    timer->start();
+    server->bind(port);
+    server->listen();
+    server->set_blocking(false);
+    server->accept(client);
+}
+
+HTTPServer::~HTTPServer()
+{
+    free(webroot);
+    webroot = NULL;
+}
+
+bool HTTPServer::RegisterHandler(const char * path, Handler callback)
+{
+    if (handlercount < maxdynamicpages && path && callback) {
+        handlers[handlercount].path = (char *)malloc(strlen(path)+1);
+        memcpy(handlers[handlercount].path, path, strlen(path)+1);
+        handlers[handlercount].callback = callback;
+        handlercount++;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+// ip_process()
+//
+// *OPEN*GET /x=1 HTTP/1.1
+// Host: 192.168.1.140
+// Connection: keep-alive
+// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+// User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
+// Accept-Encoding: gzip,deflate,sdch
+// Accept-Language: en-US,en;q=0.8
+//
+void HTTPServer::ip_process()
+{
+#define MAXRECSIZE 2000
+    static char buffer[MAXRECSIZE];
+    static char * bPtr = buffer;
+
+    static char * queryType = NULL;
+    static char * queryString = NULL;
+    static char * hostString = NULL;
+    static char * contentLength = NULL;
+    static char * contentType = NULL;
+    static char * postQueryString = NULL;
+    static Timer tmr;
+    typedef enum { Idle, Receiving, Reply , Waiting, Sending, WaitingToClose } state;
+    static state op = Idle;
+    int n;
+
+    switch(op) {
+        case Reply:
+            tmr.reset();
+            tmr.start();
+            op = Waiting;
+            break;
+        case Waiting:
+            if (tmr.read_ms() > 10) {
+                //pc->printf("%06d delay expired, now send.\r\n", timer->read_ms());
+                op = Sending;
+            }
+            break;
+        case Sending:
+            if (strcmp(queryType, "GET") == 0
+            ||  strcmp(queryType, "POST") == 0) {
+                if (!(queryString[0] == '.' && queryString[1] == '.')) {
+                    const char * fType;
+                    bool regHandled = false;
+
+                    // Registered Dynamic Handler
+                    // 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) {
+                            (*handlers[i].callback)(this, queryString, params, paramcount);
+                            regHandled = true;
+                            break;      // we only execute the first one
+                        }
+                    }
+
+                    if (!regHandled) {
+                        // Otherwise, this queryString must be trying to reference a static file
+                        if (queryString[strlen(queryString)-1] == '/') {
+                            queryString = rewriteWithDefaultFile(queryString);
+                        }
+                        // see if we support this file type
+                        fType = GetSupportedType(queryString);
+                        if (fType) {
+                            queryString = rewritePrependWebroot(queryString);
+                            SendFile(queryString, fType);
+                        } else {
+                            //pc->printf("Unsupported file type %s\r\n", queryString);
+                            header(404, "Not Found", "Pragma: err - Unsupported type\r\n");
+                        }
+                    }
+                } else {
+                    //pc->printf("Unsupported path %s\r\n", queryString);
+                    header(400, "Bad Request", "Pragma: err - Unsupported path\r\n");
+                }
+            } else {
+                //pc->printf("Unsupported query type %s\r\n", queryType);
+                header(400, "Bad Request", "Pragma: err - Unsupported query type\r\n");
+            }
+            tmr.reset();
+            if (queryType) { 
+                free(queryType);
+                queryType = NULL;
+            }
+            if (queryString) {
+                free(queryString);
+                queryString = NULL;
+            }
+            if (hostString) {
+                free(hostString);
+                hostString = NULL;
+            }
+            if (contentLength) {
+                free(contentLength);
+                contentLength = NULL;
+            }
+            if (contentType) {
+                free(contentType);
+                contentType = NULL;
+            }
+            if (postQueryString) {
+                free(postQueryString);
+                postQueryString = NULL;
+            }
+            op = WaitingToClose;
+            break;
+        case WaitingToClose:
+            if (tmr.read_ms() > 10) {
+                //pc->printf("closing after %d\r\n", tmr.read_ms());
+                close_connection();
+                op = Idle;
+            }
+            break;
+        case Idle:
+            //if (server->accept(client) == 0)
+            //    op = Receiving;
+            //break;
+            // Idle and Receiving are the same until I figure out the accept method
+            // so it doesn't eat a few chars after the *OPEN*
+        case Receiving:
+            n = client.receive(bPtr, sizeof(buffer) - (bPtr - buffer));
+            if (n < 0) {
+                pc->printf("*** client.receive error: %d\r\n", n);
+            } else if (n) {
+                char * dblCR;
+                bPtr[n] = '\0';
+                wifly->setConnectionState(true);       // Bad hack to have to do this here....
+                //pc->printf("%s", bPtr);
+                // Buffer could have partial, but the double \r\n is the key
+                //      *OPEN*QueryType QueryString HTTP/1.1....
+                //          QueryType:= GET
+                //      *OPEN*GET /QueryString HTTP/1.1\r\n
+                //      *OPEN*GET /QueryString HTTP/1.1\r\nHeader stuf
+                //      *OPEN*GET /QueryString HTTP/1.1\r\nHeader stuff...\r\n\r\n
+                dblCR = strstr(buffer,"\r\n\r\n");
+                if (dblCR) {  // Have to scan from the beginning in case split on \r
+                    //pc->printf("\r\n\r\nThe Whole Header:\r\n%s\r\n\r\n", buffer);
+                    char * soRec = buffer;                  // start of the next record of text
+                    char * eoRec = strchr(soRec, '\n');     // search for end of a record
+                    while (eoRec) {
+                        *eoRec = '\0';
+                        if (*(eoRec-1) == '\r')
+                            *(eoRec-1) = '\0';
+                        if (!Extract(soRec, "*OPEN*", &queryType))
+                            Extract(soRec, "*CLOS*", &queryType);
+                        if (queryType)
+                            Extract(soRec, queryType, &queryString);
+                        Extract(soRec, "Host: ", &hostString);
+                        Extract(soRec, "Content-Length: ", &contentLength);
+                        Extract(soRec, "Content-Type: ", &contentType);
+                        soRec = eoRec + 1;
+                        eoRec = strchr(soRec, '\n');
+                    }
+                    if (queryString) {
+                        // We have enough to try to reply
+                        //pc->printf("create reply queryType{%s}, queryString{%s}\r\n", queryType, queryString);
+                        // parse params - if any
+                        // /file.htm?name1=value1&name2=value2...
+                        // /file.htm?name1&name2=value2...
+                        paramcount = 0;
+                        char * paramDelim = strchr(queryString, '?');
+                        if (paramDelim) {
+                            *paramDelim++ = '\0';
+                            UnescapeString(paramDelim);   // everything after the '?'
+                            ParseParameters(paramDelim);    // pointing at the NULL, but there are params beyond
+                        }
+                        //for (int i=0; i<paramcount; i++)
+                        //    pc->printf("param %d '%s'='%s'\r\n", i, params[i].name, params[i].value);
+                    } else {
+                        pc->printf("not found\r\n");
+                    }
+                    op = Reply;
+                    buffer[0] = 0;
+                    bPtr = buffer;
+                    
+                    // This part parses the extra data on a POST method.
+                    // Since there has to be a dynamic handler registered for this
+                    // it would make sense to move some of this responsibility to
+                    // that handler. It could then choose if it wanted to allocate
+                    // the requested 'Content-Length' amount of memory.
+                    // Should we check the 'Content-Type' to see if it is 
+                    //   'application/x-www-form-urlencoded'?
+                    int postBytes = atoi(contentLength);
+                    //pc->printf("Content-Length = %d\r\n", postBytes);
+                    if (strcmp(queryType, "POST") == 0 && postBytes > 0 ) {
+                        if (postBytes) {
+                            postQueryString = (char *)malloc(postBytes + 1);
+                            if (postQueryString) {
+                                char * offset;
+                                int len;
+                                
+                                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 ((len = strlen(postQueryString)) < postBytes) {
+                                    int n;
+                                    offset = postQueryString + len;
+                                    n = client.receive(offset, postBytes - len);
+                                    if (n >=0)
+                                        offset[n] = '\0';
+                                }
+                                if (len >= 0) {
+                                    UnescapeString(postQueryString);
+                                    ParseParameters(postQueryString);
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    // received partial, but not the double (\r\n\r\n)
+                    bPtr += n;
+                }
+            }
+            break;
+        default:
+            // not expected to arrive here
+            op = Idle;
+            break;
+    }
+}
+
+
+const char * HTTPServer::GetSupportedType(const char * filename)
+{
+    int i;
+    int buflen = strlen(filename);
+    int extlen;
+
+    for (i=0; extensions[i].ext != 0; i++) {
+        extlen = strlen(extensions[i].ext);
+        if ( !strncmp(&filename[buflen-extlen], extensions[i].ext, extlen)) {
+            return extensions[i].filetype;
+        }
+    }
+    return NULL;
+}
+
+void HTTPServer::send(const char * msg, int bytes)
+{
+    if (bytes == -1)
+        bytes = strlen(msg);
+    wifly->send(msg, bytes);
+#ifdef DEBUG
+//    pc->printf("%s", msg);
+#endif
+}
+
+bool HTTPServer::SendFile(const char * filename, const char * filetype)
+{
+    FILE * fp;
+    
+    fp  = fopen(filename,"rb");
+    // is supported, now try to open it
+    if (fp) { // can open it
+        char *fbuffer = (char *)malloc(FILESEND_BUF_SIZE);
+        int bytes;
+
+        //pc->printf("sending [%s]\r\n", filename);
+        header(200, "OK", filetype);
+        // now the file itself
+        bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE,fp);
+        while (bytes > 0) {
+            send(fbuffer, bytes);
+            bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE,fp);
+        }
+        free(fbuffer);
+        fclose(fp);
+        return true;
+    } else {
+        //pc->printf("Can't open %s\r\n", filename);
+        header(404, "Not Found", "Pragma: err - Can't open\r\n");
+        return false;
+    }
+}
+
+int HTTPServer::HexCharToInt(char c)
+{
+    if (c >= 'a' && c <= 'f')
+        return (c - 'a' + 10);
+    else if (c >= 'A' && c <= 'F')
+        return (c - 'A' + 10);
+    else if (c >= '0' && c <= '9')
+        return c - '0';
+    else
+        return 0;
+}
+
+char HTTPServer::HexPairToChar(char * p)
+{
+    return 16 * HexCharToInt(*p) + HexCharToInt(*(p+1));
+}
+
+void HTTPServer::UnescapeString(char * encoded)
+{
+    char *p;
+
+    // first convert '+' to ' '
+    p = strchr(encoded, '+');
+    while (p) {
+        *p = ' ';
+        p = strchr(encoded, '+');
+    }
+    // then convert hex '%xx' to char 'x'
+    p = strchr(encoded, '%');
+    while (p) {
+        if (strchr("0123456789ABCDEFabcdef", *(p+1))
+                && strchr("0123456789ABCDEFabcdef", *(p+2)) ) {
+            *p = HexPairToChar(p+1);
+            p++;    // advance past the %
+            char * a = p;
+            char * b = p + 2;
+            do {
+                *a++ = *b++;
+            } while (*b);
+            *a = '\0';
+        }
+        p = strchr(p, '%');
+    }
+}
+
+const char * HTTPServer::GetParameter(const char * name)
+{
+    for (int i=0; i<paramcount; i++) {
+        if (strcmp(params[i].name, name) == 0) {
+            return params[i].value;
+        }
+    }
+    return NULL;
+}
+
+// this=that&who=what&more=stuff...
+// ^   ^    ^
+void HTTPServer::ParseParameters(char * pName) 
+{
+    char * pVal;
+    char * pNextName;
+
+    // Parse params
+    pVal = strchr(pName, '#');      // If there is a '#fragment_id', we can ignore it
+    if (pVal)
+        *pVal = '\0';    
+    do {
+        //pc->printf("Parse(%s)\r\n", pName);
+        //*pName++ = '\0';   // already '\0' on the first entry
+        params[paramcount].name = pName;
+        pVal = strchr(pName, '=');
+        pNextName = strchr(pName,'&');
+        if (pVal) {
+            if (pNextName == NULL || (pNextName && pNextName > pVal)) {
+                *pVal++ = '\0';
+                params[paramcount].value = pVal;
+                pName = pVal;
+            }
+        }
+        //pc->printf("  [%s]=[%s]\r\n", params[paramcount].name, params[paramcount].value);
+        paramcount++;
+        if (pNextName) {
+            pName = pNextName;
+            *pName++ = '\0';
+        } else {
+            pName = NULL;
+        }
+    } while (pName && paramcount < maxparams);
+}
+
+
+void HTTPServer::GetRemoteAddr(char * str, int size)
+{
+    bool res = false;
+    char *p;
+
+    res = wifly->sendCommand("show z\r", "NO", str, 5000);
+    wait(0.2);  // how long to wait is fragile, but need the rest of the chars to come in...
+    if (res) {
+        p = strchr(str, '\n');  // truncate after the octets.
+        if (p) *p = '\0';
+        p = strchr(str, ' ');   // or a space
+        if (p) *p = '\0';
+        p = strchr(str, '<');   // or a <
+        if (p) *p = '\0';
+    }
+    wifly->exit();
+}
+
+
+void HTTPServer::header(int code, const char * code_text, const char * content_type, const char * optional_text)
+{
+    char http[100];
+
+    sprintf(http, "%s %i %s\r\n", hdr_httpver, code, code_text);
+    send(http);
+    send(hdr_age);
+    send(hdr_server);
+    if (content_type) {
+        send(content_type);
+    }
+    if (optional_text) {
+        send(optional_text);
+    }
+    send(hdr_close);
+    send(nl);
+}
+
+void HTTPServer::close_connection( )
+{
+#ifndef DEBUG
+    wifly->close();
+#else // DEBUG
+    if (!wifly->close())
+        pc->printf("Couldn't close connection\r\n");
+#endif
+}
+
+bool HTTPServer::Extract(char * haystack, char * needle, char ** string)
+{
+    bool ret = false;       // assume failure until proven otherwise
+    char * qs = NULL;
+    char * eqs = NULL;
+    char * container = NULL;
+    char * get = strstr(haystack, needle);    // what if not at the front?
+    if (get) {
+        // Seems to be a valid "...GET /QueryString HTTP/1.1"
+        qs = get + strlen(needle);  // in case the needle didn't have space delimiters
+        while (*qs == ' ')
+            qs++;
+        // /QueryString\0HTTP/1.1\0\0
+        if (*string)            // recycle old string when working a new one
+            free(*string);
+        container = (char *)malloc(strlen(qs));
+        if (container) {
+            strcpy(container, qs);
+            eqs = strchr(container, ' ');
+            if (eqs)
+                *eqs = '\0';
+            *string = container;
+            ret = true;
+        } else {
+            *string = NULL;     // something bad happened... no memory
+        }
+    }
+    return ret;
+}
+
+char * HTTPServer::rewriteWithDefaultFile(char * queryString)
+{
+    char * temp = (char *)malloc(strlen(queryString) + strlen(DEFAULT_FILENAME) + 1);
+
+    if (temp) {
+        *temp = '\0';
+        strcpy(temp, queryString);
+        strcat(temp, DEFAULT_FILENAME);
+        free(queryString);
+        return temp;
+    } else {
+        return queryString;
+    }
+}
+
+char * HTTPServer::rewritePrependWebroot(char * queryString)
+{
+    char * temp = (char *)malloc(strlen(webroot) + strlen(queryString) + 1);
+
+    if (temp) {
+        *temp = '\0';
+        strcpy(temp, webroot);
+        if (temp[strlen(temp)-1] == '/' && *queryString == '/')
+            temp[strlen(temp)-1] = '\0';
+        strcat(temp, queryString);
+        free(queryString);
+        return temp;
+    } else {
+        return queryString;
+    }
+}
+
+