#include "WEBServerManager.h" #define DEBUG //#define DEBUG_RAW WEBServerManager::WEBServerManager(unsigned int port, SDCardManager *sdCardManager) : _wifiServer(port), _sdCardManager(sdCardManager), _httpRequestData({UNDEFINED, UNKNOWN, UNKNOWN_MIME, Dictionary(), Dictionary(), NULL,NULL}), _httpParserState(INIT), _clientState(WAITING_FOR_CLIENT), _port(port), _clientTimeout(0) { _wifiServer.begin(); } boolean WEBServerManager::addApiRoutine(const char *uri, boolean (*apiRoutine)(HttpRequestData&, WiFiClient* , void*), void *pData, HttpRequestMethod HRM) { return _apiDictionary.add(uri,new ApiRoutine({apiRoutine,pData, HRM})); } void WEBServerManager::clearApiRoutine() { _apiDictionary.clear(); } boolean WEBServerManager::removeApiRoutine(const char *uri) { _apiDictionary.remove(uri); } boolean WEBServerManager::runServer() { switch(_clientState) { case WAITING_FOR_CLIENT: _wifiClient.stopAll(); _wifiClient = _wifiServer.available(); if(_wifiClient) { _clientState = NEW; #ifdef DEBUG Serial.println("Client connected !!!"); #endif } break; case NEW: _clientState = NOT_HANDLED; break; case NOT_HANDLED: clearHttpRequestData(); parseQuery(&_wifiClient); #ifdef DEBUG Serial.println("Nothing more from client !!!"); #endif _clientState = QUERY_PARSED; break; case QUERY_PARSED: #ifdef DEBUG Serial.println("Sending response !!!"); #endif //We first check if it s an api call if(!sendPageToClientFromApiDictio(&_wifiClient)) { sendPageToClientFromSdCard(&_wifiClient); } _clientState = RESPONSE_SENT; break; case RESPONSE_SENT: #ifdef DEBUG Serial.println("Client handled !!!"); #endif _clientState = HANDLED; break; case HANDLED: _wifiClient.stopAll(); _clientState = WAITING_FOR_CLIENT; #ifdef DEBUG Serial.println("Client discarded !!!"); #endif break; default: break; } return true; } boolean WEBServerManager::parseQuery(WiFiClient *wifiClient) { char readChar(0), *parseBuffer(NULL), *parseKey(NULL), *parseValue(NULL); boolean smallTimeout(false), isKey(true); unsigned int shortTimeout = 100, longTimeout = 5000, activeTimeout = shortTimeout; /* Better way to read data char temp[2048]; temp[wifiClient->read((uint8_t*)temp,2040)] = '\0'; Serial.print(temp); */ _httpParserState = INIT; _clientTimeout = millis(); boolean slashesOrAntiSlashesOnly(true); while(wifiClient->available() || millis() - _clientTimeout < (smallTimeout ? activeTimeout : 10000)) { if(wifiClient->available()) { readChar = (char)wifiClient->read(); #ifdef DEBUG_RAW Serial.print(readChar); #endif //INIT, LINE_BREAK, HTTP_VERB_SECTION, HTTP_RESOURCE_SECTION, HTTP_VER_SECTION, BODY_SECTION, IGNORED, ERROR switch(_httpParserState) { case INIT: if(readChar >= 65 && readChar <= 90) { parseBuffer = addChar(parseBuffer, readChar); _httpParserState = HTTP_VERB_SECTION; } else _httpParserState = ERROR; break; case LINE_BREAK: if(readChar == '\n') _httpParserState = BODY_SECTION; else if(readChar != '\r') { #ifdef DEBUG Serial.print(readChar); #endif _httpParserState = PARAMETER_SECTION; } break; case HTTP_VERB_SECTION: if(readChar >= 65 && readChar <= 90) { parseBuffer = addChar(parseBuffer, readChar); _httpParserState = HTTP_VERB_SECTION; } else if (readChar == ' ') { //This is the end of the section _httpRequestData.HRM = getHttpVerbEnumValue(parseBuffer); free(parseBuffer);parseBuffer = NULL; _httpParserState = HTTP_RESOURCE_SECTION; } else _httpParserState = ERROR; break; case HTTP_RESOURCE_SECTION: if(_httpRequestData.HRM == POST)// Need to be changed, using Content-Length is more appropriate activeTimeout = longTimeout; if(readChar == '?' ) { free(_httpRequestData.httpResource);_httpRequestData.httpResource = NULL; _httpRequestData.httpResource = parseBuffer;parseBuffer = NULL; _httpParserState = HTTP_RESOURCE_PARAM_SECTION; } else if(readChar == ' ') { free(_httpRequestData.httpResource);_httpRequestData.httpResource = NULL; if(slashesOrAntiSlashesOnly) { free(parseBuffer);parseBuffer = NULL; _httpRequestData.httpResource = (char *) malloc(sizeof(char)*2); strcpy(_httpRequestData.httpResource,"/"); } else _httpRequestData.httpResource = parseBuffer;parseBuffer = NULL; _httpParserState = HTTP_VER_SECTION; } else { if(readChar != '/' && readChar != '\\') slashesOrAntiSlashesOnly = false; parseBuffer = addChar(parseBuffer, readChar); _httpParserState = HTTP_RESOURCE_SECTION; } break; case HTTP_RESOURCE_PARAM_SECTION: //index.web?var1=1&var2=2... if(readChar == ' ') { _httpRequestData.getParams.add(parseKey,new DictionaryHelper::StringEntity(parseValue)); free(parseKey);free(parseValue); parseKey = NULL;parseValue = NULL; _httpParserState = HTTP_VER_SECTION; } else if( readChar == '=') isKey = false; else if(readChar == '&') { isKey = true; _httpRequestData.getParams.add(parseKey, new DictionaryHelper::StringEntity(parseValue)); free(parseKey);free(parseValue); parseKey = NULL;parseValue = NULL; } else { if(isKey) parseKey = addChar(parseKey, readChar); else parseValue = addChar(parseValue, readChar); } break; case HTTP_VER_SECTION: if((readChar >= 48 && readChar <= 57) || readChar == '.') { parseBuffer = addChar(parseBuffer, readChar); _httpParserState = HTTP_VER_SECTION; } else if(readChar == '\n') { _httpRequestData.HV = getHttpVersionEnumValue(parseBuffer); free(parseBuffer);parseBuffer = NULL; _httpParserState = LINE_BREAK; } break; case BODY_SECTION: //parseBuffer = addChar(parseBuffer, readChar); #ifdef DEBUG Serial.print(readChar); #endif break; case PARAMETER_SECTION: //Here are all the http header params #ifdef DEBUG Serial.print(readChar); #endif if(readChar == '\n') { _httpParserState = LINE_BREAK; } break; case IGNORED: break; case ERROR: return false; break; //Not necessary default : break; } _clientTimeout = millis(); smallTimeout = true; } ESP.wdtFeed(); } if(parseBuffer != NULL) { if(strlen(parseBuffer) > 0) { _httpRequestData.httpBody = parseBuffer; parseBuffer = NULL; } } #ifdef DEBUG Serial.print("HTTP VERB : "); Serial.println(_httpRequestData.HRM); Serial.print("HTTP RESOURCE : "); Serial.println(_httpRequestData.httpResource); Serial.print("HTTP VERSION : "); Serial.println(_httpRequestData.HV); Serial.print("BODY CONTENT : "); Serial.println(_httpRequestData.httpBody); Serial.println("GET PARAMS :"); for(int i = 0; i < _httpRequestData.getParams.count(); i++) { Serial.print(_httpRequestData.getParams.getParameter(i));Serial.print(" : ");Serial.println(_httpRequestData.getParams(i)->getString()); } #endif return true; } unsigned int WEBServerManager::getPort() const { return _port; } boolean WEBServerManager::sendPageToClientFromSdCard(WiFiClient *wifiClient) { if(_sdCardManager != NULL) { File pageToSend; char readChar(0), *filePath(NULL), *header(NULL), sendBuffer[2048]; //We check what kind of http verb it is switch(_httpRequestData.HRM) { case GET: filePath = getFilePathByHttpResource(_httpRequestData.httpResource); if(filePath == NULL) { wifiClient->print(F("HTTP/1.1 500 OK\r\nContent-Type: text/html\r\n\r\n\r\n\r\n

Failed to malloc filePath

\r\n")); return false; } #ifdef DEBUG Serial.print("FILE PATH : "); Serial.println(filePath); #endif pageToSend = _sdCardManager->open(filePath); free(filePath);filePath = NULL; if(!pageToSend) { char *response(NULL); response = (char *) malloc(sizeof(char) * (strlen("HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n\r\n\r\n

Page not found for :

\r\n

\r\n") + strlen(_httpRequestData.httpResource) + 1)); if(response == NULL) { wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n\r\n\r\n

Failed to malloc filePath

\r\n")); return false; } sprintf(response, "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n\r\n\r\n

Page not found for :

\r\n

%s

\r\n", _httpRequestData.httpResource); wifiClient->print(response); free(response);response = NULL; pageToSend.close(); return false; } #ifdef DEBUG Serial.print("FILE SIZE : "); Serial.println(pageToSend.size()); Serial.print("FILE NAME : "); Serial.println(pageToSend.name()); Serial.print("FILE EXTENSION : "); Serial.println(getFileExtension(pageToSend.name())); #endif header = getHTTPHeader(getMIMETypeByExtension(getFileExtension(pageToSend.name())), pageToSend.size()); if(header == NULL) { wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n\r\n\r\n

Failed to malloc filePath

\r\n")); pageToSend.close(); return false; } wifiClient->print(header); free(header);header = NULL; while(pageToSend.available()) { if(wifiClient->write(sendBuffer, pageToSend.read(sendBuffer,2048)) == 0) break; } pageToSend.close(); break; default: //If not supported wifiClient->print(F("HTTP/1.1 405 Method Not Allowed\r\nContent-Type: text/html\r\n\r\n\r\n\r\n

Method Not Allowed

\r\n")); break; } }else { //Test if it does correspond to a user defined API call wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n\r\n\r\n

SDCardManager is NULL
Check code

\r\n")); } return true; } boolean WEBServerManager::sendPageToClientFromApiDictio(WiFiClient *wifiClient) { if(_apiDictionary.count() == 0 || _httpRequestData.httpResource == NULL) return false; ApiRoutine *ref = _apiDictionary(_httpRequestData.httpResource); if(ref == NULL) return false; if(ref->HRM == UNDEFINED) { return (*(ref->apiRoutine))(_httpRequestData, wifiClient, ref->pData); }else if(ref->HRM == _httpRequestData.HRM) { return (*(ref->apiRoutine))(_httpRequestData, wifiClient, ref->pData); } else return false; } WEBServerManager::HttpMIMEType WEBServerManager::getMIMETypeByExtension(const char *extension) { //TEXT_PLAIN, TEXT_CSS, TEXT_HTML, TEXT_JAVASCRIPT if(strcmp(extension,"web") == 0) return TEXT_HTML; else if(strcmp(extension,"css") == 0) return TEXT_CSS; else if(strcmp(extension,"js") == 0) return TEXT_JAVASCRIPT; else if(strcmp(extension,"png") == 0) return IMAGE_PNG; else if(strcmp(extension,"jpg") == 0) return IMAGE_JPEG; else if(strcmp(extension, "mp3") == 0) return AUDIO_MPEG; else return UNKNOWN_MIME; } char *WEBServerManager::getHTTPHeader(HttpMIMEType httpMIMEType, unsigned long size) { char *header = (char *) malloc(sizeof(char) + strlen("HTTP/1.1 200 OK\r\nContent-Type: \r\nContent-Length: \r\n\r\n") + 74/*Longest MIME-TYPE*/ + 10 /*Max unsigned long footprint*/ + 1); switch(httpMIMEType) { case TEXT_HTML: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/html",size); break; case TEXT_CSS: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/css",size); break; case TEXT_JAVASCRIPT: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/javascript",size); break; case IMAGE_PNG: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","image/png",size); break; case IMAGE_JPEG: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","image/jpeg",size); break; case TEXT_PLAIN: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/plain",size); break; case AUDIO_MPEG: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","audio/mpeg",size); break; case APPLICATION_OCTET_STREAM: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","application/octet-stream",size); break; default: sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","application/octet-stream",size); break; break; } return header; } char *WEBServerManager::getFileExtension(char *name) { if(name == NULL)return ""; char *ptr(name); int index(0); while(ptr[index] != '\0') { if(ptr[++index] == '.') { return (name + index +1); } } return ""; } char *WEBServerManager::getFilePathByHttpResource(const char *res) { char *filePath = (char*) malloc( sizeof(char) * (strlen(WWW_DIR)+ strlen(strcmp(res, "/") == 0 ? "/index.web":res) + 1) ); //Do not forget \0 if(filePath == NULL) return NULL; sprintf(filePath,"%s%s",WWW_DIR, strcmp(_httpRequestData.httpResource, "/") == 0 ? "/index.web":_httpRequestData.httpResource); return filePath; } WEBServerManager::HttpRequestMethod WEBServerManager::getHttpVerbEnumValue(const char *parseBuffer) { //UNDEFINED, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH if(strcmp(parseBuffer,"GET") == 0){return GET;} else if(strcmp(parseBuffer,"POST") == 0){return POST;} else if(strcmp(parseBuffer,"HEAD") == 0){return HEAD;} else if(strcmp(parseBuffer,"PUT") == 0){return PUT;} else if(strcmp(parseBuffer,"DELETE") == 0){return DELETE;} else if(strcmp(parseBuffer,"CONNECT") == 0){return CONNECT;} else if(strcmp(parseBuffer,"TRACE") == 0){return TRACE;} else if(strcmp(parseBuffer,"PATCH") == 0){return PATCH;} else if(strcmp(parseBuffer,"OPTIONS") == 0){return OPTIONS;} else return UNDEFINED; } WEBServerManager::HttpVersion WEBServerManager::getHttpVersionEnumValue(const char *parseBuffer) { //HTTP_0_9, HTTP_1_1, HTTP_1_0, HTTP_2_0 if(strcmp(parseBuffer,"1.1") == 0){return HTTP_1_1;} else if(strcmp(parseBuffer,"2.0") == 0){return HTTP_2_0;} else if(strcmp(parseBuffer,"1.0") == 0){return HTTP_1_0;} else if(strcmp(parseBuffer,"0.9") == 0){return HTTP_0_9;} else return UNKNOWN; } void WEBServerManager::clearHttpRequestData() { _httpRequestData.HRM = UNDEFINED; _httpRequestData.HV = UNKNOWN; _httpRequestData.HMT = UNKNOWN_MIME; free(_httpRequestData.httpResource);free(_httpRequestData.httpBody); _httpRequestData.httpResource = NULL;_httpRequestData.httpBody = NULL; _httpRequestData.getParams.dispose(); }