ESP8266_swiss_army_board/src/app/WEBServerManager.cpp
2019-04-16 13:44:44 +02:00

498 lines
16 KiB
C++

#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<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);
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<!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 filePath</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())), 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 filePath</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
{
//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<!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,"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();
}