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-06-25
- Revision:
- 5:c9b27e718054
- Parent:
- 4:f34642902056
- Child:
- 7:99ad7a67f05e
File content as of revision 5:c9b27e718054:
// // @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 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_dnt[] = "DNT: 1\r\n"; // Do Not Track 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" }, {".txt", "Content-Type: plain/text\r\n" }, {".pdf", "Content-Type: application/pdf\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, int _allocforheader, int _allocforfile) { 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)); headerbuffersize = _allocforheader; headerbuffer = (char *)malloc(headerbuffersize); pc = _pc; queryType = NULL; queryString = NULL; hostString = NULL; contentLength = NULL; contentType = NULL; postQueryString = NULL; paramcount = 0; handlercount = 0; maxheaderbytes = 0; server = new TCPSocketServer(); server->bind(port); server->listen(); server->set_blocking(false, 0); client.set_blocking(false, 0); server->accept(client); ResetPerformanceData(); timer.start(); } HTTPServer::~HTTPServer() { free(webroot); webroot = NULL; } int HTTPServer::GetMaxHeaderSize() { return maxheaderbytes; } 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; } } // Poll() // // *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::Poll() { typedef enum { Idle, // waiting for a connection Receiving, // receiving data Sending, // send the response WaitingToClose // small timeout to close } state; static state op = Idle; static char * bPtr = headerbuffer; int n; static int t_ref; // reference point for the timer switch(op) { default: // not expected to arrive here op = Idle; break; case Idle: timer.reset(); bPtr = headerbuffer; if (0 == client.receive(bPtr, 0)) { // server->accept(client) == 0) { op = Receiving; t_ref = timer.read_us(); } break; case Receiving: n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer)); if (n < 0) { ; // pc->printf("*** client.receive error: %d\r\n", n); } else if (n) { bPtr[n] = '\0'; if (ParseHeader(headerbuffer)) { op = Sending; t_ref = RecordPerformanceData(&perfData.Header, t_ref); } bPtr += n; } break; case Sending: SendResponse(); op = WaitingToClose; RecordPerformanceData(&perfData.SendData, t_ref); break; case WaitingToClose: close_connection(); 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); } bool HTTPServer::SendFile(const char * filename, const char * filetype) { FILE * fp; fp = fopen(filename,"rb"); if (fp) { // can open it char *fbuffer = (char *)malloc(FILESEND_BUF_SIZE); int bytes; if (fbuffer) { header(200, "OK", filetype); 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); } else { header(500, "Server Error", "Pragma: err - insufficient memory\r\n"); } fclose(fp); return true; } else { header(404, "Not Found", "Pragma: err - Can't open file\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); } bool HTTPServer::GetRemoteAddr(char * str, int strSize) { bool res = false; char *p; if (strSize < 16) { // Can only guard it here w/o modifying Wifly class *str = '\0'; return res; } res = wifly->sendCommand("show z\r", NULL, str, strSize); 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'; res = true; } wifly->exit(); return res; } 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_dnt); 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; } } bool HTTPServer::CheckDynamicHandlers() { bool regHandled = false; // 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, SEND_PAGE, queryString, params, paramcount); regHandled = true; break; // we only execute the first one } } return regHandled; } void HTTPServer::SendResponse() { if (strcmp(queryType, "GET") == 0 || strcmp(queryType, "POST") == 0) { if (!(queryString[0] == '.' && queryString[1] == '.')) { const char * fType; if (!CheckDynamicHandlers()) { // 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"); } 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; } } bool HTTPServer::ParseHeader(char * buffer) { char * dblCR; bool advanceState = false; int bytecount; // Bad hack to have to do this here, but it isn't being set in the // underlying layer, and this is what allows it to properly "close" // when it is done. wifly->setConnectionState(true); // 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 #if 0 pc->printf("\r\n\r\nThe Header:\r\n%s\r\n\r\n", buffer); #endif char * soRec = buffer; // start of the next record of text char * eoRec = strchr(soRec, '\n'); // search for end of a record bytecount = strlen(buffer); if (bytecount > maxheaderbytes) maxheaderbytes = bytecount; 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"); } advanceState = true; 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); bool acceptIt = false; //pc->printf("Content-Length = %d\r\n", postBytes); if (strcmp(queryType, "POST") == 0 && postBytes > 0 ) { if (postBytes) { bool regHandled = false; // Registered Dynamic Handler // Callback and ask if they want to accept this data for (int i=0; i<handlercount; i++) { if (strcmp(handlers[i].path, queryString) == 0) { acceptIt = (*handlers[i].callback)(this, CONTENT_LENGTH_REQUEST, queryString, params, paramcount); regHandled = true; break; // we only execute the first one } } if (regHandled && acceptIt) { // If so, we'll make space for it 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 { // Simply copy it to the bitbucket int bytesToDump = postBytes; char * bitbucket = (char *)malloc(201); dblCR += 4; while (*dblCR && *dblCR <= ' ') dblCR++; bytesToDump -= strlen(dblCR); while (bytesToDump > 0) { int n = (bytesToDump > 200) ? 200 : bytesToDump; n = client.receive(bitbucket, n); bytesToDump -= n; } free(bitbucket); } } } } return advanceState; } void HTTPServer::GetPerformanceData(SW_PerformanceData * p) { memcpy(p, &perfData, sizeof(perfData)); } int HTTPServer::RecordPerformanceData(SW_PerformanceParam * param, int refTime) { int t_now = timer.read_us(); param->TotalTime_us += (t_now - refTime); param->Samples++; if ((t_now - refTime) > param->MaxTime_us) param->MaxTime_us = (t_now - refTime); return t_now; } void HTTPServer::ResetPerformanceData() { memset(&perfData, 0, sizeof(perfData)); }