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:
- 44:71f09e4255f4
- Parent:
- 43:3fc773c2986e
- Child:
- 45:360c7c1d07b7
--- a/SW_HTTPServer.cpp Mon Feb 02 03:01:00 2015 +0000 +++ b/SW_HTTPServer.cpp Sat Mar 26 20:38:59 2016 +0000 @@ -1,5 +1,5 @@ // -// @note Copyright © 2014 by Smartware Computing, all rights reserved. +// @note Copyright © 2014-2016 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 @@ -12,7 +12,7 @@ // #include "mbed.h" -#define DEBUG "HTTP" +//#define DEBUG "HTTP" #include <cstdio> #if (defined(DEBUG) && !defined(TARGET_LPC11U24)) #define DBG(x, ...) std::printf("[DBG %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); @@ -35,12 +35,13 @@ const char * DEFAULT_FILENAME = "index.htm"; // Header information to always send (must be \r\n terminated) -const char hdr_httpver[] = "HTTP/1.0"; // supported 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 char hdr_httpver[] = "HTTP/1.1"; // supported HTTP/1.1 protocol (sort of) +static const char nl[] = "\r\n"; // final \r\n for the termination of the header +// Header items that are sent if the user does not provide their own options. +static const char hdr_age[] = "Max-age: 0\r\n"; // expires right away +static const char hdr_server[] = "Server: Smart_Server v0.2\r\n"; // Server +static const char hdr_close[] = "Connection: close\r\n"; // tell the client the server closes the connection immediately static const struct { char *ext; @@ -59,9 +60,28 @@ {".pdf", "Content-Type: application/pdf\r\n" }, {".htm", "Content-Type: text/html\r\n" }, {".html","Content-Type: text/html\r\n" }, + {".xml", "Content-Type: text/xml\r\n" }, {0,0} }; +typedef struct { + char * queryType; + int notused; +} QueryMethod; + +// Be sure to include a trailing space. +static const QueryMethod QueryMethodList[] = { + {"GET ", 0}, + {"POST ", 0}, + {"HEAD ", 0}, + {"PUT ", 0}, + {"OPTION ", 0}, + {"DELETE ", 0}, + {"TRACE ", 0}, + {"CONNECT ", 0}, + {NULL, 0} +}; + #if 0 && defined(DEBUG) // Haven't learned anything from this in a long time, so disabled. // This uses standard library dynamic memory management, but for an @@ -105,6 +125,7 @@ strcpy(webroot, _webroot); if (strlen(webroot)>1 && webroot[strlen(webroot)-1] == '/') // remove trailing '/' webroot[strlen(webroot)-1] = '\0'; + filenameAliasList = NULL; maxheaderParams = _maxheaderParams; headerParams = (namevalue *)malloc(maxheaderParams * sizeof(namevalue)); @@ -128,12 +149,8 @@ server = new TCPSocketServer(); server->bind(port); server->listen(); - #ifndef CC3000_H - server->set_blocking(false, 10); - client.set_blocking(false, 100); //@TODO client is separate from server. any way to combine? - #else - #warning CC3000 detected - #endif + server->set_blocking(false); + client.set_blocking(false); //@TODO client is separate from server. any way to combine? ResetPerformanceData(); PerformanceTimer.start(); } @@ -151,6 +168,11 @@ webroot = NULL; } +void HTTPServer::RegisterFilenameAliasList(const namevalue * namevaluelist) +{ + filenameAliasList = namevaluelist; +} + int HTTPServer::GetMaxHeaderSize() { return maxheaderbytes; @@ -220,9 +242,9 @@ case ReceivingHeader: n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer)); + INFO("%sclient.receive() returned %d, from %s", (n<0) ? "*** " : "", n, client.get_address()); if (n < 0) { op = Sending; - INFO("*** client.receive() => %d", n); } else if (n) { bPtr[n] = '\0'; switch (ParseHeader(headerbuffer)) { @@ -265,11 +287,10 @@ break; case WaitingToClose: - INFO("Connection closed entry %u", (unsigned int)PerformanceTimer.read_us()); close_connection(); op = Idle; RecordPerformanceData(&perfData.ConnectionClosed, t_ref); - INFO("Connection closed exit %u", (unsigned int)PerformanceTimer.read_us()); + INFO("Connection closed exit: %u\r\n\r\n", (unsigned int)PerformanceTimer.read_us()); break; } } @@ -295,22 +316,54 @@ { if (bytes == -1) bytes = strlen(msg); - //INFO("Sending %d bytes", bytes); + INFO("Sending %d bytes", bytes); + //INFO("send:\r\n%s", msg); client.send((char *)msg, bytes); + INFO("client.send returned: %d", r); } +const char * HTTPServer::FindAlias(const HTTPServer::namevalue * haystack, const char * needle) +{ + while (haystack && haystack->name) { + if (strcmp(haystack->name,needle) == 0) + return haystack->value; + haystack++; + } + return needle; +} + +uint32_t HTTPServer::FileSize(const char * filename) +{ + uint32_t size = 0; + FILE * fh; + + filename = FindAlias(filenameAliasList, filename); + fh = fopen(filename, "r"); + if (fh) { + fseek(fh, 0, SEEK_END); // seek to end of file + size = ftell(fh); // get current file pointer + fclose(fh); + } + return size; +} bool HTTPServer::SendFile(const char * filename, const char * filetype) { FILE * fp; + INFO("SendFile(%s,...)", filename); + filename = FindAlias(filenameAliasList, filename); + INFO(" Alias(%s,...)", filename); fp = fopen(filename,"rb"); if (fp) { // can open it char *fbuffer = (char *)mymalloc(FILESEND_BUF_SIZE); int bytes; if (fbuffer) { - header(200, "OK", filetype); + char ContentLen[30]; + snprintf(ContentLen, sizeof(ContentLen), "Content-Length: %u\r\n", FileSize(filename)); + header(OK, "OK", filetype, ContentLen); + header(""); bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE,fp); while (bytes > 0) { send(fbuffer, bytes); @@ -318,12 +371,14 @@ } myfree(fbuffer); } else { - header(500, "Server Error", "Pragma: err - insufficient memory\r\n"); + header(Server_Error, "Server Error", NULL, "Pragma: err - insufficient memory\r\n"); + header(""); } fclose(fp); return true; } else { - header(404, "Not Found", "Pragma: err - Can't open file\r\n"); + header(Not_Found, "Not Found", NULL, "Pragma: err - Can't open file\r\n"); + header(""); return false; } } @@ -387,7 +442,7 @@ return NULL; } -HTTPServer::namevalue * HTTPServer::GetParameter(int index) +const HTTPServer::namevalue * HTTPServer::GetParameter(int index) { if (index < queryParamCount) return &queryParams[index]; @@ -454,25 +509,34 @@ } -void HTTPServer::header(int code, const char * code_text, const char * content_type, const char * optional_text) +void HTTPServer::header(HTTPServer::HeaderCodes 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); - INFO("header(%s)", http); + INFO("header(%d, %s, %s, ...)", code, code_text, content_type); + snprintf(http, sizeof(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); + if (*optional_text != '\0') + send(optional_text); + } else { + send(hdr_age); + send(hdr_server); + send(hdr_close); + header(""); } - send(hdr_close); - send(nl); } +void HTTPServer::header(const char * partialheader) +{ + if (!partialheader || *partialheader == '\0') + send(nl); + else + send(partialheader); +} bool HTTPServer::close_connection() { @@ -491,8 +555,10 @@ 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" + // Seems to be a valid "GET /QueryString HTTP/1.1" + // or "POST /upnp/control/metainfo1 HTTP/1.0" // or "...<needle>param..." qs = get + strlen(needle); // in case the needle didn't have space delimiters while (*qs == ' ') @@ -575,7 +641,7 @@ void HTTPServer::SendResponse() { INFO("SendResponse(%s) at %u", queryType, (unsigned int)PerformanceTimer.read_us()); - if (strcmp(queryType, "GET") == 0 || strcmp(queryType, "POST") == 0) { + if (strcmp(queryType, "GET ") == 0 || strcmp(queryType, "POST ") == 0) { if (!(queryString[0] == '.' && queryString[1] == '.')) { const char * fType; @@ -584,20 +650,25 @@ if (queryString[strlen(queryString)-1] == '/') { queryString = rewriteWithDefaultFile(queryString); } + INFO(" queryString: %s", queryString); // see if we support this file type fType = GetSupportedType(queryString); if (fType) { queryString = rewritePrependWebroot(queryString); + INFO(" SendFile(%s,%s)", queryString, fType); SendFile(queryString, fType); } else { - header(404, "Not Found", "Pragma: err - Unsupported type\r\n"); + header(Not_Found, "Not Found", NULL, "Pragma: err - Unsupported type\r\n"); + header(""); } } } else { - header(400, "Bad Request", "Pragma: err - Unsupported path\r\n"); + header(Bad_Request, "Bad Request", NULL, "Pragma: err - Unsupported path\r\n"); + header(""); } } else { - header(400, "Bad Request", "Pragma: err - Unsupported query type\r\n"); + header(Bad_Request, "Bad Request", NULL, "Pragma: err - Unsupported query type\r\n"); + header(""); } INFO(" SendResponse complete at %u", (unsigned int)PerformanceTimer.read_us()); @@ -630,7 +701,7 @@ // POST /dyn2 HTTP/1.2\r\nAccept: text/html, application/xhtml+xml, */*\r\n\r\n dblCR = strstr(buffer,"\r\n\r\n"); if (dblCR) { // Have to scan from the beginning in case split on \r - INFO("\r\n==\r\n%s==", buffer); + INFO("\r\n==\r\n%s\r\n==", buffer); char * soRec = buffer; // start of the next record of text char * eoRec = strchr(soRec, '\n'); // search for end of the current record @@ -642,10 +713,23 @@ *eoRec = '\0'; if (*(eoRec-1) == '\r') *(eoRec-1) = '\0'; + INFO("method: %s", soRec); + #if 1 + const QueryMethod * qm = QueryMethodList; + while (*qm->queryType) { + if (strstr(soRec, qm->queryType) == soRec) { + Extract(soRec, qm->queryType, &queryString); + if (queryString) { + queryType = (char *)mymalloc(strlen(qm->queryType)+1); + strcpy(queryType, qm->queryType); + } + } + qm++; + } + #else // Inspect the supported query types (GET, POST) and ignore (HEAD, PUT, OPTION, DELETE, TRACE, CONNECT] - // This is very clumsy - INFO("method: %s", soRec); - if (strstr(soRec, "GET ") == soRec) { + // This is presently very clumsy + if (strstr(soRec, "GET ") == soRec) { Extract(soRec, "GET", &queryString); if (queryString) { queryType = (char *)mymalloc(strlen("GET")+1); @@ -658,12 +742,13 @@ strcpy(queryType, "POST"); } } - + #endif + // 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 ":" - INFO("hpc:%d,mhp:%d, {%s}", headerParamCount, maxheaderParams, soRec); + //INFO("hpc:%d,mhp:%d, {%s}", headerParamCount, maxheaderParams, soRec); if (delim && (!chkSpace || (chkSpace && delim < chkSpace)) && headerParamCount < maxheaderParams) { @@ -677,6 +762,8 @@ } soRec = eoRec + 1; eoRec = strchr(soRec, '\n'); + if (soRec > dblCR) // Just walked past the end of the header + break; } if (queryString) { @@ -695,7 +782,7 @@ } else { ERR("queryString not found in (%s) [this should never happen]", soRec); } - advanceState = ACCEPT_COMPLETE; + advanceState = ACCEPT_COMPLETE; // Should be ACCEPT_CONTINUE and the stuff below moves out of here buffer[0] = 0; // This part parses the extra data on a POST method. @@ -705,96 +792,98 @@ // the requested 'Content-Length' amount of memory. int postBytes = atoi(GetHeaderValue("Content-Length")); CallBackResults acceptIt = ACCEPT_ERROR; - if (strcmp(queryType, "POST") == 0 && postBytes > 0 ) { - INFO("parse POST data %d bytes", postBytes); - if (postBytes) { - int ndxHandler = 0; - bool regHandled = false; - // Registered Dynamic Handler - // Callback and ask if they want to accept this data - for (ndxHandler=0; ndxHandler<handlercount; ndxHandler++) { - INFO("is '%s' a handler for '%s' ?", handlers[ndxHandler].path, queryString); - if (strcmp(handlers[ndxHandler].path, queryString) == 0) { - acceptIt = (*handlers[ndxHandler].callback)(this, CONTENT_LENGTH_REQUEST, queryString, queryParams, queryParamCount); - regHandled = true; - break; // only one callback per path allowed - } + INFO("Content-Length: %d", postBytes); + if (strcmp(queryType, "POST ") == 0 ) { + INFO("parse POST data %d bytes", postBytes); // We might have no idea how much data is coming... + int ndxHandler = 0; + bool regHandled = false; + // Registered Dynamic Handler + // Callback and ask if they want to accept this data + for (ndxHandler=0; ndxHandler<handlercount; ndxHandler++) { + INFO("is '%s' a handler for '%s' ?", handlers[ndxHandler].path, queryString); + if (strcmp(handlers[ndxHandler].path, queryString) == 0) { + acceptIt = (*handlers[ndxHandler].callback)(this, CONTENT_LENGTH_REQUEST, queryString, queryParams, queryParamCount); + regHandled = true; + break; // only one callback per path allowed } - INFO("reghandled: %d, acceptIt: %d", regHandled, acceptIt); - if (regHandled && acceptIt != ACCEPT_ERROR) { - // @todo need to refactor - if the thing is bigger than the buffer, - // then we can receive it a chunk at a time, and hand off - // the chunks to the callback. May need callbacks that - // are: DATA_TRANSFER: self-detect to extract the filename/object name, - // DATA_TRANSFER: subsequent chunk of data, - // DATA_TRANSFER_END: signals that last chunk is enclosed. - // - // If so, we'll make space for it - postQueryString = (char *)mymalloc(CHUNK_SIZE); - //INFO("Free space %d", Free()); - INFO("postQueryString %p", postQueryString); - if (postQueryString) { - int len = 0; - int ttlCount; - Timer escapePlan; - bool escape = false; - - INFO("Processing tail..."); - escapePlan.start(); - dblCR += 4; // There may be some after the double CR that we need - ttlCount = strlen(dblCR); - strcpy(postQueryString, dblCR); - INFO(" {%s}", postQueryString); - while (ttlCount < postBytes && !escape) { - INFO("ttlCount: %d < postBytes: %d, of chunk %d", ttlCount, postBytes, CHUNK_SIZE); - len = client.receive_all(postQueryString + ttlCount, CHUNK_SIZE - ttlCount); - if (len > 0) { - INFO(" len: %d, ttlCount: %d < postBytes %d, {%s}", len, ttlCount, postBytes, postQueryString + ttlCount); - ttlCount += len; - postQueryString[ttlCount] = '\0'; // Whether binary or ASCII, this is ok as it's after the data - INFO(" postBytes %d: [%s], [%d]", postBytes, postQueryString, ndxHandler); - escapePlan.reset(); - } else if (len < 0) { - INFO("*** connection closed ***"); - break; // no more data, before the plan - } else { // n == 0 - ; - } - if (escapePlan.read_ms() > HANG_TIMEOUT_MS) { - escape = true; - WARN("Escape plan activated."); - } + } + INFO("reghandled: %d, acceptIt: %d", regHandled, acceptIt); + if (regHandled && acceptIt != ACCEPT_ERROR) { + // @todo need to refactor - if the thing is bigger than the buffer, + // then we can receive it a chunk at a time, and hand off + // the chunks to the callback. May need callbacks that + // are: DATA_TRANSFER: self-detect to extract the filename/object name, + // DATA_TRANSFER: subsequent chunk of data, + // DATA_TRANSFER_END: signals that last chunk is enclosed. + // + // If so, we'll make space for it + postQueryString = (char *)mymalloc(CHUNK_SIZE); + //INFO("Free space %d", Free()); + INFO("postQueryString %p", postQueryString); + if (postQueryString) { + int len = 0; + int ttlCount; + Timer escapePlan; + bool escape = false; + + INFO("Processing tail..."); + escapePlan.start(); + dblCR += 4; // There may be some after the double CR that we need + ttlCount = strlen(dblCR); + strcpy(postQueryString, dblCR); + //INFO(" {%s}", postQueryString); + while ((!postBytes || ttlCount < postBytes) && !escape) { + INFO("ttlCount: %d < postBytes: %d, of max chunk alloc %d", ttlCount, postBytes, CHUNK_SIZE); + len = client.receive(postQueryString + ttlCount, CHUNK_SIZE - ttlCount); + if (len > 0) { + ttlCount += len; + postQueryString[ttlCount] = '\0'; // Whether binary or ASCII, this is ok as it's after the data + INFO(" postBytes %d: [%s], [%d]", postBytes, postQueryString, ndxHandler); + escapePlan.reset(); + } else if (len < 0) { + INFO("*** connection closed ***"); + break; // no more data, before the plan + } else { // n == 0 + ; } - postParamCount = 0; - INFO("post: %s", postQueryString); - ParseParameters(postParams, &postParamCount, maxPostParams, postQueryString); - acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER, queryString, queryParams, queryParamCount); - INFO("..processing exit"); - acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER_END, NULL, NULL, 0); - } else { - ERR("attempt to allocate %d failed.", CHUNK_SIZE); + if (escapePlan.read_ms() > HANG_TIMEOUT_MS) { // if no Content-Length, we wait... + escape = true; + WARN("Escape plan activated."); + } + if (postBytes > 0 && ttlCount >= postBytes) + break; } + //postParamCount = 0; + //INFO("post: %s", postQueryString); + //We're after the header, so there is "body" stuff which could be anything... + //but probably html or xml stuff... + //ParseParameters(postParams, &postParamCount, maxPostParams, postQueryString); + acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER, postQueryString, NULL, ttlCount); + INFO("..processing exit"); + acceptIt = (*handlers[ndxHandler].callback)(this, DATA_TRANSFER_END, NULL, NULL, 0); } else { - // Simply copy it to the bitbucket - WARN("to the bit bucket..."); - 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); - if (n < 0) { - ERR("to the bitbucket."); - break; - } - bytesToDump -= n; + ERR("attempt to allocate %d failed.", CHUNK_SIZE); + } + } else { + // Simply copy it to the bitbucket + WARN("No handler, so to the bit bucket it goes ..."); + 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); + if (n < 0) { + ERR("to the bitbucket."); + break; } - myfree(bitbucket); + bytesToDump -= n; } + myfree(bitbucket); } } }