A simple web server that can be bound to either the EthernetInterface or the WiflyInterface.
Dependents: Smart-WiFly-WebServer WattEye X10Svr SSDP_Server
Diff: SW_HTTPServer.cpp
- Revision:
- 0:729320f63c5c
- Child:
- 2:a29c32190037
diff -r 000000000000 -r 729320f63c5c SW_HTTPServer.cpp --- /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; + } +} + +