498 lines
16 KiB
C++
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();
|
|
}
|