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-09-14
- Revision:
- 19:7677fce798fa
- Parent:
- 18:6199558632c0
- Child:
- 20:786aa5749007
File content as of revision 19:7677fce798fa:
// // @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 (must be \r\n terminated) const char hdr_httpver[] = "HTTP/1.0"; // Wifly may not be able to support HTTP/1.1 protocol const char hdr_age[] = "Max-age: 0\r\n"; // expires right away const char hdr_server[] = "Server: Smart_Server v0.1\r\n"; // Server const char hdr_close[] = "Connection: close\r\n"; // tell the client the server closes the connection immediately 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} }; #ifdef DEBUG // This uses standard library dynamic memory management, but for an // embedded system there are alternates that may make better sense - // search the web for embedded system malloc alternates. static void * MyMalloc(int x, int y) { std::printf("[%04d] malloc(%d)\r\n", y, x); return malloc(x); } static char toP(void * x) { char * c = (char *) x; if (*c >= ' ' && *c < 0x7F) return *c; else return '.'; } #define mymalloc(x) MyMalloc(x, __LINE__) #define myfree(x) \ pc->printf("[%4d] free(%02x %02x %02x %02x %02x ... %c%c%c%c%c)\r\n", __LINE__, \ *x, *(x+1), *(x+2), *(x+3), *(x+4), \ toP(x), toP(x+1), toP(x+2), toP(x+3), toP(x+4) ); \ free(x); #else #define mymalloc(x) malloc(x) #define myfree(x) free(x) #endif HTTPServer::HTTPServer( Wifly * _wf, int port, const char * _webroot, int maxheaderParams, int _maxqueryParams, int _maxdynamicpages, PC * _pc, int _allocforheader, int _allocforfile) { wifly = _wf; webroot = (char *)malloc(strlen(_webroot)+1); strcpy(webroot, _webroot); maxqueryParams = _maxqueryParams; maxdynamicpages = _maxdynamicpages; headerParams = (namevalue *)malloc(maxheaderParams * sizeof(namevalue)); queryParams = (namevalue *)malloc(maxqueryParams * sizeof(namevalue)); handlers = (handler *)malloc(maxdynamicpages * sizeof(handler)); headerbuffersize = _allocforheader; headerbuffer = (char *)malloc(headerbuffersize); pc = _pc; queryType = NULL; queryString = NULL; postQueryString = NULL; queryParamCount = 0; handlercount = 0; maxheaderbytes = 0; server = new TCPSocketServer(); server->bind(port); server->listen(); server->set_blocking(false, 10); ResetPerformanceData(); PerformanceTimer.start(); } HTTPServer::~HTTPServer() { int i; for (i=0; i<handlercount; i++) myfree(handlers[i].path); myfree(headerbuffer); myfree(handlers); myfree(queryParams); myfree(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 *)mymalloc(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 Reset } state; static state op = Idle; static char * bPtr = headerbuffer; int n; static unsigned int t_ref; // reference point for the PerformanceTimer #if 1 || defined(DEBUG) static state lastOp = Reset; if (lastOp != op) { const char *states[] = {"Idle", "Receiving", "Sending", "WaitingToClose", "Reset"}; pc->printf("Poll: %s\r\n", states[op]); lastOp = op; } #endif switch(op) { default: // not expected to arrive here op = Idle; break; case Idle: PerformanceTimer.reset(); t_ref = (unsigned int)PerformanceTimer.read_us(); bPtr = headerbuffer; if (0 == server->accept(client)) { op = Receiving; t_ref = RecordPerformanceData(&perfData.ConnectionAccepted, t_ref); #ifdef DEBUG pc->printf("Accepted at %u\r\n", (unsigned int)PerformanceTimer.read_us()); #endif } break; case Receiving: n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer)); if (n < 0) { #ifdef DEBUG pc->printf("*** client.receive() => %d\r\n", n); #endif } else if (n) { bPtr[n] = '\0'; if (ParseHeader(headerbuffer)) { op = Sending; t_ref = RecordPerformanceData(&perfData.HeaderParsed, t_ref); #ifdef DEBUG pc->printf("Header Parsed at %u\r\n", (unsigned int)PerformanceTimer.read_us()); #endif } bPtr += n; } break; case Sending: SendResponse(); op = WaitingToClose; t_ref = RecordPerformanceData(&perfData.ResponseSent, t_ref); #ifdef DEBUG pc->printf("Response Sent at %u\r\n", (unsigned int)PerformanceTimer.read_us()); #endif break; case WaitingToClose: #ifdef DEBUG pc->printf("Connection closed entry %u\r\n", (unsigned int)PerformanceTimer.read_us()); #endif close_connection(); op = Idle; RecordPerformanceData(&perfData.ConnectionClosed, t_ref); #ifdef DEBUG pc->printf("Connection closed exit %u\r\n", (unsigned int)PerformanceTimer.read_us()); #endif 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 *)mymalloc(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); } myfree(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<queryParamCount; i++) { if (strcmp(queryParams[i].name, name) == 0) { return queryParams[i].value; } } return NULL; } // this=that&who=what&more=stuff... // ^ ^ ^ void HTTPServer::ParseParameters(char * pName) { char * pVal; char * pNextName; // Parse queryParams pVal = strchr(pName, '#'); // If there is a '#fragment_id', we can ignore it if (pVal) *pVal = '\0'; do { queryParams[queryParamCount].name = pName; pVal = strchr(pName, '='); pNextName = strchr(pName,'&'); if (pVal) { if (pNextName == NULL || (pNextName && pNextName > pVal)) { *pVal++ = '\0'; queryParams[queryParamCount].value = pVal; pName = pVal; } } queryParamCount++; if (pNextName) { pName = pNextName; *pName++ = '\0'; } else { pName = NULL; } } while (pName && queryParamCount < maxqueryParams); } 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_close); send(nl); } bool HTTPServer::close_connection() { bool res; res = server->close(); #ifdef DEBUG pc->printf("close connection returned %d\r\n", res); #endif return res; } 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" // or "...<needle>param..." 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 myfree(*string); container = (char *)mymalloc(strlen(qs)); if (container) { strcpy(container, qs); eqs = strchr(container, ' '); if (eqs) *eqs = '\0'; *string = container; #ifdef DEBUG pc->printf("Extract(%s) = %s\r\n", needle, container); #endif ret = true; } else { *string = NULL; // something bad happened... no memory } } return ret; } char * HTTPServer::rewriteWithDefaultFile(char * queryString) { char * temp = (char *)mymalloc(strlen(queryString) + strlen(DEFAULT_FILENAME) + 1); if (temp) { *temp = '\0'; strcpy(temp, queryString); strcat(temp, DEFAULT_FILENAME); myfree(queryString); return temp; } else { return queryString; } } char * HTTPServer::rewritePrependWebroot(char * queryString) { char * temp = (char *)mymalloc(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); myfree(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, queryParams, queryParamCount); regHandled = true; break; // we only execute the first one } } return regHandled; } void HTTPServer::SendResponse() { #ifdef DEBUG pc->printf("SendResponse(%s) at %u\r\n", queryType, (unsigned int)PerformanceTimer.read_us()); #endif 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"); } #ifdef DEBUG pc->printf(" SendResponse complete at %u\r\n", (unsigned int)PerformanceTimer.read_us()); #endif if (queryType) { myfree(queryType); queryType = NULL; } if (queryString) { myfree(queryString); queryString = NULL; } if (postQueryString) { myfree(postQueryString); postQueryString = NULL; } #ifdef DEBUG pc->printf(" SendResponse free at %u\r\n", (unsigned int)PerformanceTimer.read_us()); #endif } bool HTTPServer::ParseHeader(char * buffer) { char * dblCR; bool advanceState = false; int bytecount; // Buffer could have partial, but the double \r\n is the key // GET /QueryString?this=that&sky=blue HTTP/1.1\r\n // GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Con // GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Control: max-age=0\r\n\r\n dblCR = strstr(buffer,"\r\n\r\n"); if (dblCR) { // Have to scan from the beginning in case split on \r #ifdef DEBUG pc->printf("==\r\n%s==\r\n", buffer); #endif char * soRec = buffer; // start of the next record of text char * eoRec = strchr(soRec, '\n'); // search for end of the current record headerParamCount = 0; bytecount = strlen(buffer); if (bytecount > maxheaderbytes) maxheaderbytes = bytecount; while (eoRec) { *eoRec = '\0'; if (*(eoRec-1) == '\r') *(eoRec-1) = '\0'; // Inspect the supported query types (GET, POST) and ignore (HEAD, PUT, OPTION, DELETE, TRACE, CONNECT] // This is very clumsy if (strstr(soRec, "GET ") == soRec) { Extract(soRec, "GET", &queryString); if (queryString) { queryType = (char *)mymalloc(strlen("GET")+1); strcpy(queryType, "GET"); } } else if (strstr(soRec, "POST ") == soRec) { Extract(soRec, "POST", &queryString); if (queryString) { queryType = (char *)mymalloc(strlen("POST")+1); strcpy(queryType, "POST"); } } // if there is a ": " delimiter, we have a header item to parse into name,value pair // "Connection: keep-alive" becomes "Connection" "keep-alive" char *delim = strstr(soRec, ": "); char *chkSpace = strchr(soRec, ' '); // a field-name has no space ahead of the ":" if (delim && (!chkSpace || (chkSpace && delim < chkSpace)) && headerParamCount < maxheaderParams) { *delim++ = '\0'; // replace ": " with null *delim++ = '\0'; headerParams[headerParamCount].name = soRec; headerParams[headerParamCount].value = delim; #ifdef DEBUG pc->printf("%d: headerParams[%s] = {%s}\r\n", headerParamCount, headerParams[headerParamCount].name, headerParams[headerParamCount].value); #endif headerParamCount++; } soRec = eoRec + 1; eoRec = strchr(soRec, '\n'); } if (queryString) { // We have enough to try to reply #ifdef DEBUG pc->printf("create reply queryType{%s}, queryString{%s}\r\n", "GET", queryString); #endif // parse queryParams - if any // /file.htm?name1=value1&name2=value2... // /file.htm?name1&name2=value2... queryParamCount = 0; char * paramDelim = strchr(queryString, '?'); if (paramDelim) { *paramDelim++ = '\0'; UnescapeString(paramDelim); // everything after the '?' ParseParameters(paramDelim); // pointing at the NULL, but there are queryParams beyond } } else { pc->printf("ERROR: queryString not found in (%s)\r\n", soRec); } advanceState = true; buffer[0] = 0; // 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(GetHeaderValue("Content-Length")); bool acceptIt = false; 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, queryParams, queryParamCount); regHandled = true; break; // we only execute the first one } } if (regHandled && acceptIt) { // If so, we'll make space for it postQueryString = (char *)mymalloc(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 *)mymalloc(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; } myfree(bitbucket); } } } } return advanceState; } const char * HTTPServer::GetHeaderValue(const char * hdr) { int i; for (i=0; i<headerParamCount; i++) { if (strcmp(hdr, headerParams[i].name) == 0) return headerParams[i].value; } return NULL; } void HTTPServer::GetPerformanceData(SW_PerformanceData * p) { memcpy(p, &perfData, sizeof(perfData)); } unsigned int HTTPServer::GetPerformanceClock() { return (unsigned int)PerformanceTimer.read_us(); } unsigned int HTTPServer::RecordPerformanceData(SW_PerformanceParam * param, unsigned int refTime) { unsigned int t_now = (unsigned int)PerformanceTimer.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)); }