A simple web server that can be bound to either the EthernetInterface or the WiflyInterface.
Dependents: Smart-WiFly-WebServer WattEye X10Svr SSDP_Server
SW_HTTPServer.cpp
- Committer:
- WiredHome
- Date:
- 2013-05-31
- Revision:
- 0:729320f63c5c
- Child:
- 2:a29c32190037
File content as of revision 0:729320f63c5c:
// // 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; } }