ESP8266_swiss_army_board/src/app/WEBServerManager.cpp

541 lines
18 KiB
C++

#include "WEBServerManager.h"
//#define DEBUG
#define DEBUG_BODY
//#define DEBUG_PARAMETER
//#define DEBUG_CONTENT_LENGTH
//#define DEBUG_RAW
//#define DEBUG_FILEPATH
WEBServerManager::WEBServerManager(unsigned int port, SDCardManager *sdCardManager) : _wifiServer(port), _sdCardManager(sdCardManager), _httpRequestData({UNDEFINED, UNKNOWN, UNKNOWN_MIME, Dictionary<DictionaryHelper::StringEntity>(), Dictionary<DictionaryHelper::StringEntity>(), 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), *parseParameter(NULL), *contentLength(NULL);
boolean isKey(true), receivingDone(false);
unsigned int activeTimeout = 10000;
unsigned long dataBytesCounter = 0, dataBytes = 0;
/* 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 < activeTimeout)) && wifiClient->connected())
{
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')
{
if(_httpRequestData.HRM == GET)
{
#ifdef DEBUG
Serial.println("GET DONE");
#endif
receivingDone = true;
}
_httpParserState = BODY_SECTION;
}
else if(readChar != '\r')
{
if(parseParameter != NULL)
{
contentLength = strstr(parseParameter, "ent-Len");//Matches Content-Length short to save some RAM
if(contentLength != NULL)
{
dataBytes = strtol(contentLength+11,NULL,10);
#ifdef DEBUG_CONTENT_LENGTH
Serial.print("Data length : ");Serial.println(dataBytes);
#endif
}
#ifdef DEBUG_PARAMETER
Serial.println(parseParameter);
#endif
free(parseParameter);parseParameter = NULL;
}
parseParameter = addChar(parseParameter, readChar);
_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(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);
if(_httpRequestData.HRM != GET)
{
dataBytesCounter++;//Should be always true
}
#ifdef DEBUG_BODY
Serial.print(readChar);
#endif
break;
case PARAMETER_SECTION: //Here are all the http header params
if(readChar == '\n')
{
_httpParserState = LINE_BREAK;
}else
parseParameter = addChar(parseParameter, readChar);
break;
case IGNORED:
break;
case ERROR:
return false;
break; //Not necessary
default :
break;
}
//Exit condition
if(receivingDone) break;
if(_httpRequestData.HRM == POST && dataBytes != 0 && dataBytes == dataBytesCounter) break;
_clientTimeout = millis();
}
//yield(); //Likely causing a crash
ESP.wdtFeed();
}
if(parseBuffer != NULL)
{
if(strlen(parseBuffer) > 0)
{
_httpRequestData.httpBody = parseBuffer;
parseBuffer = NULL;
}
}
free(parseParameter);parseParameter = 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.getAt(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 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<p>Failed to malloc filePath</p>\r\n</html>"));
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<!DOCTYPE HTML>\r\n<html>\r\n<h1>Page not found for : </h1>\r\n<h4></h4>\r\n</html>") + 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<!DOCTYPE HTML>\r\n<html>\r\n<h1>Failed to malloc response</h1>\r\n</html>"));
return false;
}
sprintf(response, "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Page not found for : </h1>\r\n<h4>%s</h4>\r\n</html>", _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()*/ "dummy")), pageToSend.size());
if(header == NULL)
{
wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Failed to malloc header</h1>\r\n</html>"));
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<!DOCTYPE HTML>\r\n<html>\r\n<h1>Method Not Allowed</h1>\r\n</html>"));
break;
}
}else
{
wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>SDCardManager is NULL<br \\>Check code</h1>\r\n</html>"));
}
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,"htm") == 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(char *res)
{
uint16_t buffSize = strlen(WWW_DIR) + (strcmp(res, "/") == 0 ? 10:strlen(res)) + 1;//10 for /index.htm +1 for \0
char *filePath = (char*) malloc( sizeof(char) * buffSize);
if(filePath == NULL)
return NULL;
strcpy(filePath, WWW_DIR);
strcat(filePath, strcmp(res, "/") == 0 ? "/index.htm":res);
//sprintf(filePath,"%s%s",WWW_DIR, strcmp(res, "/") == 0 ? "/index.htm":res);
#ifdef DEBUG_FILEPATH
Serial.println(res);
Serial.print("Reserved space : ");Serial.println(buffSize);
Serial.print("Actual size : ");Serial.println(strlen(filePath));
Serial.println(filePath);
#endif
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();
_httpRequestData.postParams.dispose();
}