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:
- 3:17928786bdb5
- Parent:
- 2:a29c32190037
- Child:
- 4:f34642902056
diff -r a29c32190037 -r 17928786bdb5 SW_HTTPServer.cpp --- a/SW_HTTPServer.cpp Sun Jun 02 18:32:05 2013 +0000 +++ b/SW_HTTPServer.cpp Mon Jun 24 20:02:01 2013 +0000 @@ -16,8 +16,6 @@ //#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 @@ -32,22 +30,30 @@ 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" }, + {".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) +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); @@ -56,16 +62,26 @@ 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(); - timer = new Timer(); - timer->start(); server->bind(port); server->listen(); - server->set_blocking(false); + server->set_blocking(false, 0); + client.set_blocking(false, 0); server->accept(client); + ResetPerformanceData(); + timer.start(); } HTTPServer::~HTTPServer() @@ -74,6 +90,11 @@ webroot = NULL; } +int HTTPServer::GetMaxHeaderSize() +{ + return maxheaderbytes; +} + bool HTTPServer::RegisterHandler(const char * path, Handler callback) { if (handlercount < maxdynamicpages && path && callback) { @@ -99,246 +120,49 @@ // void HTTPServer::Poll() { -#define MAXRECSIZE 4000 - 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; + 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) { - case Reply: - tmr.reset(); - tmr.start(); - op = Waiting; + 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 Waiting: - if (tmr.read_ms() > 10) { - //pc->printf("%06d delay expired, now send.\r\n", timer->read_ms()); - op = Sending; + 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: - 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, SEND_PAGE, 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; - } + SendResponse(); op = WaitingToClose; + RecordPerformanceData(&perfData.SendData, t_ref); 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 - #ifdef DEBUG - 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 - 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); - 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) { - #ifdef DEBUG - pc->printf("POSTDATA: %s\r\n", postQueryString); - #endif - 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); - } - } - } - } else { - // received partial, but not the double (\r\n\r\n) - bPtr += n; - } - } - break; - default: - // not expected to arrive here + close_connection(); op = Idle; break; } @@ -360,40 +184,39 @@ 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); + 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"); } - 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"); + header(404, "Not Found", "Pragma: err - Can't open file\r\n"); return false; } } @@ -455,7 +278,7 @@ // this=that&who=what&more=stuff... // ^ ^ ^ -void HTTPServer::ParseParameters(char * pName) +void HTTPServer::ParseParameters(char * pName) { char * pVal; char * pNextName; @@ -463,7 +286,7 @@ // Parse params pVal = strchr(pName, '#'); // If there is a '#fragment_id', we can ignore it if (pVal) - *pVal = '\0'; + *pVal = '\0'; do { //pc->printf("Parse(%s)\r\n", pName); //*pName++ = '\0'; // already '\0' on the first entry @@ -494,7 +317,11 @@ bool res = false; char *p; - res = wifly->sendCommand("show z\r", "NO", str, 5000); + if (size < 16) { // Can only guard it here w/o modifying Wifly class + *str = '\0'; + return; + } + res = wifly->sendCommand("show z\r", "NO", str, 500); 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. @@ -599,4 +426,221 @@ } } +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)); +} +