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:
- 2017-04-10
- Revision:
- 50:10db483f5154
- Parent:
- 49:cd391662f254
- Child:
- 51:758601b9bacd
File content as of revision 50:10db483f5154:
// // @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 // 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" //#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__); #define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define ERR(x, ...) std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #else #define DBG(x, ...) #define WARN(x, ...) #define ERR(x, ...) #define INFO(x, ...) #endif #include "SW_HTTPServer.h" // define DEBUG before this #define CHUNK_SIZE 1500 // max size of a single chunk (probably limited by Ethernet to 1500) #define HANG_TIMEOUT_MS 250 // If we're waiting on the host, which may never respond, this is the timeout const char * DEFAULT_FILENAME = "index.htm"; // Header information to always send (must be \r\n terminated) 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; 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" }, {".bmp", "Content-Type: image/bmp\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" }, {".xml", "Content-Type: text/xml\r\n" }, {".js", "Content-Type: text/javascript\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 // embedded system there are alternates that may make better sense - // search the web for embedded system malloc alternates. void * HTTPServer::MyMalloc(int x, int y) { pc->printf("[INF HTTP%4d] malloc(%d)\r\n", y, x); return malloc(x); } char HTTPServer::toP(void * x) { char * c = (char *) x; if (*c >= ' ' && *c < 0x7F) // isprint() return *c; else return '.'; } #define mymalloc(x) MyMalloc(x, __LINE__) #define myfree(x) \ pc->printf("[INF HTTP%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( int port, const char * _webroot, int _maxheaderParams, int _maxqueryParams, int _maxdynamicpages, int blockingtime, PC * _pc, int _allocforheader, int _allocforfile ) { webroot = (char *)malloc(strlen(_webroot)+1); 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)); maxqueryParams = _maxqueryParams; queryParams = (namevalue *)malloc(maxqueryParams * sizeof(namevalue)); queryParamCount = 0; maxPostParams = _maxqueryParams; // Same as Query params, but for post method postParams = (namevalue *)malloc(maxPostParams * sizeof(namevalue)); postParamCount = 0; maxdynamicpages = _maxdynamicpages; handlers = (handler *)malloc(maxdynamicpages * sizeof(handler)); headerbuffersize = _allocforheader; headerbuffer = (char *)malloc(headerbuffersize); pc = _pc; queryType = NULL; queryString = NULL; postQueryString = NULL; handlercount = 0; maxheaderbytes = 0; server = new TCPSocketServer(); server->bind(port); server->listen(); server->set_blocking(false, blockingtime); client.set_blocking(false, blockingtime); //@TODO client is separate from server. any way to combine? 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; } void HTTPServer::RegisterFilenameAliasList(const namevalue * namevaluelist) { filenameAliasList = namevaluelist; } 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 ReceivingHeader, // receiving header ReceivingPayload, // receiving a section after the Header 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 defined(DEBUG) static state lastOp = Reset; if (lastOp != op) { const char *states[] = {"Idle", "ReceivingHeader", "ReceivingPayload", "Sending", "WaitingToClose", "Reset"}; INFO("Poll: %s", 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 = ReceivingHeader; t_ref = RecordPerformanceData(&perfData.ConnectionAccepted, t_ref); INFO("Accepted at %u", (unsigned int)PerformanceTimer.read_us()); } else { //INFO("Timeout waiting for accept()"); } break; 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; } else if (n) { bPtr[n] = '\0'; switch (ParseHeader(headerbuffer)) { case ACCEPT_ERROR: break; case ACCEPT_COMPLETE: op = Sending; t_ref = RecordPerformanceData(&perfData.HeaderParsed, t_ref); INFO("Header Parsed at %u", (unsigned int)PerformanceTimer.read_us()); break; case ACCEPT_CONTINUE: op = ReceivingPayload; break; } bPtr += n; } break; case ReceivingPayload: // After the header, there is a payload that will be handled #if 1 n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer)); if (n < 0) { op = Sending; INFO("*** client.receive() => %d", n); } else if (n) { bPtr[n] = '\0'; INFO("*** payload size %d", n); } #else op = Sending; #endif break; case Sending: SendResponse(); op = WaitingToClose; t_ref = RecordPerformanceData(&perfData.ResponseSent, t_ref); INFO("Response Sent at %u", (unsigned int)PerformanceTimer.read_us()); break; case WaitingToClose: close_connection(); op = Idle; RecordPerformanceData(&perfData.ConnectionClosed, t_ref); INFO("Connection closed exit: %u\r\n\r\n", (unsigned int)PerformanceTimer.read_us()); 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); INFO("Sending %d bytes", bytes); //INFO("send:\r\n%s", msg); #ifdef DEBUG int r = #endif 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) { 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); bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE,fp); } myfree(fbuffer); } else { header(Server_Error, "Server Error", NULL, "Pragma: err - insufficient memory\r\n"); header(""); } fclose(fp); return true; } else { header(Not_Found, "Not Found", NULL, "Pragma: err - Can't open file\r\n"); header(""); 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)); } // modifies in-place 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) { INFO("GetParameter(%s)", name); for (int i=0; i<queryParamCount; i++) { INFO(" %d: %s = %s", i, queryParams[i].name, queryParams[i].value); if (strcmp(queryParams[i].name, name) == 0) { INFO(" value {%s}", queryParams[i].value); return queryParams[i].value; } } return NULL; } const HTTPServer::namevalue * HTTPServer::GetParameter(int index) { if (index < queryParamCount) return &queryParams[index]; else return NULL; } const char * HTTPServer::GetPostParameter(const char * name) { INFO("GetPostParameter(%s)", name); for (int i=0; i<postParamCount; i++) { INFO(" %d: %s = %s", i, postParams[i].name, postParams[i].value); if (strcmp(postParams[i].name, name) == 0) { INFO(" value {%s}", postParams[i].value); return postParams[i].value; } } return NULL; } HTTPServer::namevalue * HTTPServer::GetPostParameter(int index) { if (index < postParamCount) return &postParams[index]; else return NULL; } // this=that&who=what&more=stuff... // ^ ^ ^ int HTTPServer::ParseParameters(namevalue * qP, int * qpCount, int maxP, 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 { INFO("ParseParameters(%s), qpCount: %d", pName, *qpCount); qP->name = pName; pVal = strchr(pName, '='); pNextName = strchr(pName,'&'); if (pVal) { if (pNextName == NULL || (pNextName && pNextName > pVal)) { *pVal++ = '\0'; qP->value = pVal; pName = pVal; } } if (pNextName) { pName = pNextName; *pName++ = '\0'; } else { pName = NULL; } INFO(" param{%s}={%s}", qP->name, qP->value); *qpCount += 1; qP++; } while (pName && *qpCount < maxP); INFO(" count %d", *qpCount); return *qpCount; } void HTTPServer::header(HTTPServer::HeaderCodes code, const char * code_text, const char * content_type, const char * optional_text) { char http[100]; if (optional_text == NULL) optional_text = ""; INFO("header(%d, %s, %s, %s)", code, code_text, content_type); snprintf(http, sizeof(http), "%s %i %s\r\n", hdr_httpver, code, code_text); send(http); if (content_type) { send(content_type); } if (*optional_text != '\0') { send(optional_text); } else { if (strlen(hdr_age) + strlen(hdr_server) + strlen(hdr_close) + strlen(nl) < sizeof(http)) { strcpy(http, hdr_age); strcat(http, hdr_server); strcat(http, hdr_close); strcat(http, nl); send(http); } else { send(hdr_age); send(hdr_server); send(hdr_close); send(nl); } } } void HTTPServer::header(const char * partialheader) { if (!partialheader || *partialheader == '\0') send(nl); else send(partialheader); } bool HTTPServer::close_connection() { bool res; res = client.close(); INFO("close connection returned %d", res); 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 "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 == ' ') 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; INFO("Extract(%s) = %s", needle, container); 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) + 2); // save room for '/' if (temp) { char *lastC; *temp = '\0'; strcpy(temp, webroot); lastC = &temp[strlen(temp)-1]; if (*lastC == '/' && *queryString == '/') queryString++; // don't create two '/' else if (*lastC != '/' && *queryString != '/') strcat(temp, "/"); 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) { INFO("CheckDynamicHandlers - SEND_PAGE"); (*handlers[i].callback)(this, SEND_PAGE, queryString, queryParams, queryParamCount); regHandled = true; break; // we only execute the first one } } return regHandled; } void HTTPServer::SendResponse() { INFO("SendResponse(%s) at %u", queryType, (unsigned int)PerformanceTimer.read_us()); 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); } 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(Not_Found, "Not Found", NULL, "Pragma: err - Unsupported type\r\n"); header(""); } } } else { header(Bad_Request, "Bad Request", NULL, "Pragma: err - Unsupported path\r\n"); header(""); } } else { header(Bad_Request, "Bad Request", NULL, "Pragma: err - Unsupported query type\r\n"); header(""); } INFO(" SendResponse complete at %u", (unsigned int)PerformanceTimer.read_us()); if (queryType) { myfree(queryType); queryType = NULL; } if (queryString) { myfree(queryString); queryString = NULL; } if (postQueryString) { myfree(postQueryString); postQueryString = NULL; } INFO(" SendResponse free at %u", (unsigned int)PerformanceTimer.read_us()); } HTTPServer::CallBackResults HTTPServer::ParseHeader(char * buffer) { char * dblCR; CallBackResults advanceState = ACCEPT_ERROR; 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 // 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\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 headerParamCount = 0; bytecount = strlen(buffer); if (bytecount > maxheaderbytes) maxheaderbytes = bytecount; while (eoRec) { *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 presently 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"); } } #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); if (delim && (!chkSpace || (chkSpace && delim < chkSpace)) && headerParamCount < maxheaderParams) { *delim++ = '\0'; // replace ": " with null *delim++ = '\0'; headerParams[headerParamCount].name = soRec; headerParams[headerParamCount].value = delim; INFO("%d: headerParams[%s] = {%s}", headerParamCount, headerParams[headerParamCount].name, headerParams[headerParamCount].value); headerParamCount++; } soRec = eoRec + 1; eoRec = strchr(soRec, '\n'); if (soRec > dblCR) // Just walked past the end of the header break; } if (queryString) { // We have enough to try to reply INFO("create reply queryType{%s}, queryString{%s}", queryType, queryString); // 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(queryParams, &queryParamCount, maxqueryParams, paramDelim); // pointing past the NULL, and there are queryParams here } } else { ERR("queryString not found in (%s) [this should never happen]", soRec); } 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. // 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. int postBytes = atoi(GetHeaderValue("Content-Length")); CallBackResults acceptIt = ACCEPT_ERROR; 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 ((!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 ; } 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 { 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; } 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)); }