From 45b7a6245c2980bb9781981dc7a1b0de079b9874 Mon Sep 17 00:00:00 2001 From: anschrammh Date: Thu, 10 Oct 2019 18:19:44 +0200 Subject: [PATCH] The board is now using the new WEBServer class which can handle more than one client and is non blocking even with big files --- src/app/TCPClient.cpp | 82 ++++++ src/app/TCPClient.h | 34 +++ src/app/TCPServer.h | 199 ++++++++++++++ src/app/WEBClient.cpp | 37 +++ src/app/WEBClient.h | 24 ++ src/app/WEBServer.h | 599 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 975 insertions(+) create mode 100644 src/app/TCPClient.cpp create mode 100644 src/app/TCPClient.h create mode 100644 src/app/TCPServer.h create mode 100644 src/app/WEBClient.cpp create mode 100644 src/app/WEBClient.h create mode 100644 src/app/WEBServer.h diff --git a/src/app/TCPClient.cpp b/src/app/TCPClient.cpp new file mode 100644 index 0000000..71fde85 --- /dev/null +++ b/src/app/TCPClient.cpp @@ -0,0 +1,82 @@ +#include "TCPClient.h" + +//#define DEBUG_CL + +TCPClient::TCPClient(WiFiClient client, uint8_t id, uint16_t dataBufferSize) : _client(client), +_clientState(NEW), +_error(OK), +_data(NULL), +_dataSize(0), +_dataBufferSize(dataBufferSize+1), +_newDataAvailable(false), +_id(id) +{ + #ifdef DEBUG_CL + Serial.println("TCPClient : Standard constructor called"); + #endif + + uint8_t *p = (uint8_t *) malloc(sizeof(uint8_t) * _dataBufferSize); + if(p != NULL) + _data = p; + else + _error = MALLOC_ERR; +} + +TCPClient::TCPClient(const TCPClient &Object) : _client(Object._client), +_clientState(Object._clientState), +_error(Object._error), +_data(NULL), +_dataSize(Object._dataSize), +_dataBufferSize(Object._dataBufferSize), +_newDataAvailable(Object._newDataAvailable), +_id(Object._id) +{ + #ifdef DEBUG_CL + Serial.println("TCPClient : Copy constructor called"); + #endif + + uint8_t *p = (uint8_t *) malloc(sizeof(uint8_t) * _dataBufferSize); + if(p != NULL) + { + _data = p; + memcpy(_data,Object._data,_dataSize); + } + else + _error = MALLOC_ERR; +} + +TCPClient::~TCPClient() +{ + #ifdef DEBUG_CL + Serial.println("TCPClient : Destructor called"); + #endif + + free(_data); +} + +bool TCPClient::operator==(TCPClient& Object) +{ + return this->_client == Object._client; +} + +bool TCPClient::closeConnection() +{ + _client.stop(); +} + +void TCPClient::freeDataBuffer(uint16_t size) +{ + if(size > _dataBufferSize-1) size = _dataBufferSize - 1; + + uint16_t secureSize = size > _dataSize ? _dataSize : size; + + if(*(_data + secureSize) == '\0') + { + #ifdef DEBUG_CL + Serial.println("Its the terminating string"); + #endif + } + + strcpy((char *)_data, (char *)_data + secureSize); + _dataSize -= secureSize; +} diff --git a/src/app/TCPClient.h b/src/app/TCPClient.h new file mode 100644 index 0000000..582c6bc --- /dev/null +++ b/src/app/TCPClient.h @@ -0,0 +1,34 @@ +#ifndef TCPCLIENT_H +#define TCPCLIENT_H + +#include + +//Forward class declaration +template class TCPServer; + +class TCPClient +{ + template + friend class TCPServer; + public: + TCPClient(WiFiClient client, uint8_t id, uint16_t dataBufferSize = 255); + TCPClient(const TCPClient &Object); + virtual ~TCPClient(); + bool operator==(TCPClient& Object); + bool closeConnection(); + protected: + enum ClientState {NEW, HANDLED, DISCARDED} _clientState; + enum Error {OK = 0, MALLOC_ERR = 1} _error; + + WiFiClient _client; + uint8_t *_data; //The actual data + uint16_t _dataSize; //The logic size of the data contained + uint16_t _dataBufferSize; //The physic size of the buffer + boolean _newDataAvailable; + uint8_t _id; + + void freeDataBuffer(uint16_t size); + private: +}; + +#endif //TCPCLIENT_H diff --git a/src/app/TCPServer.h b/src/app/TCPServer.h new file mode 100644 index 0000000..e237c81 --- /dev/null +++ b/src/app/TCPServer.h @@ -0,0 +1,199 @@ +#ifndef TCPSERVER_H +#define TCPSERVER_H + +#include +#include "List.h" +#include "TCPClient.h" + +#define MAX_CLIENT -1 +//#define DEBUG_TCPS + + +template +class TCPServer +{ + public: + TCPServer(unsigned int port = 80, uint8_t maxClient = MAX_CLIENT, uint16_t clientDataBufferSize = 255) : _wifiServer(port), _port(port), _serverStarted(true), _maxClient(maxClient), _clientDataBufferSize(clientDataBufferSize), _currentClient(NULL) + { + _wifiServer.begin(); + } + + virtual ~TCPServer() + { + _clientList.dispose(); + } + + uint8_t getMaxClient() + { + return _maxClient; + } + + unsigned int getPort() const + { + return _port; + } + + uint8_t getConnectedClientsCount() + { + return _clientList.count(); + } + + virtual void runServer() + { + handleNewClients(); + getClientData(); + } + + virtual void startServer() + { + if(!_serverStarted) + { + _wifiServer.begin(); + _serverStarted = true; + } + } + + virtual void stopServer() + { + if(_serverStarted) + { + _wifiServer.stop(); + _clientList.dispose(); + _serverStarted = false; + } + } + + protected: + virtual T* createNewClient(WiFiClient wc) + { + return new T(wc, freeClientId(), _clientDataBufferSize); + } + + virtual void handleNewClients() + { + WiFiClient wc; + + if(_maxClient == -1 || _maxClient > _clientList.count()) + { + wc = _wifiServer.available(); + } + + if(wc && wc.connected()) + { + T *clientPointer = createNewClient(wc); + _clientList.addFirst(clientPointer); + #ifdef DEBUG_TCPS + Serial.print("TCPServer : New client accepted : ");Serial.println(clientPointer->_id); + #endif + greetClient(clientPointer); + } + + _currentClient = _clientList.removeLastRef();//We pick a client in the list to process it's request + + if(_currentClient != NULL) + { + if(_currentClient->_error != TCPClient::OK) + { + _currentClient->closeConnection(); + delete _currentClient; _currentClient = NULL; + } + } + } + + virtual void greetClient(T *client) + { + client->_client.println(F("Successfully connected !")); + client->_client.print(F("Your are client with id : ")); + client->_client.println(client->_id); + } + + virtual void getClientData() + { + if(_currentClient == NULL) + { + return; + } + + uint32_t bytesAvailable((_currentClient->_client).available()); + + if(bytesAvailable) + { + uint16_t freeSpace = (_currentClient->_dataBufferSize-1/*for \0*/ - _currentClient->_dataSize); + + if(freeSpace != 0) + { + int amountToBeRead = bytesAvailable < freeSpace ? bytesAvailable : freeSpace; + + (_currentClient->_client).read(_currentClient->_data + _currentClient->_dataSize, amountToBeRead); + _currentClient->_dataSize += amountToBeRead; + _currentClient->_data[_currentClient->_dataSize] = '\0'; + _currentClient->_newDataAvailable = true; + } + } + else if(!(_currentClient->_client).connected()) + { + #ifdef DEBUG_TCPS + Serial.print("TCPServer : Client disconnected and can be discarded : ");Serial.println(_currentClient->_id); + #endif + _currentClient->_clientState = TCPClient::DISCARDED; + } + + if(_currentClient->_dataSize > 0) + { + processClientData(_currentClient);//We process the actual data + _currentClient->_newDataAvailable = false; + } + + if(_currentClient->_clientState == TCPClient::ClientState::DISCARDED) + { + _currentClient->closeConnection(); + #ifdef DEBUG_TCPS + Serial.print("TCPServer : Client was discarded : ");Serial.println(_currentClient->_id); + #endif + delete _currentClient; + _currentClient = NULL; + + return; + } + + _clientList.addFirst(_currentClient); + + } + + virtual void processClientData(T *client) + { + if(client->_dataSize > 0 && ((char) client->_data[0] != '\r' && (char) client->_data[0] != '\n')) + { + Serial.print("Client --> ");Serial.print(client->_id);Serial.print(" : ");Serial.print((char *)client->_data);Serial.println("#"); + } + + client->freeDataBuffer(client->_dataSize); + } + + uint8_t freeClientId() + { + int listCounter(_clientList.count()); + int freeId(0); + + for(int i(0); i < listCounter; i++) + { + if(_clientList.getRef(i)->_id == freeId) + { + freeId++; + i = -1; + } + } + return freeId; + } + + boolean _serverStarted; + uint8_t _maxClient; + unsigned int _port; + uint16_t _clientDataBufferSize; + WiFiServer _wifiServer; + T *_currentClient; //current client to be processed + List _clientList; + private: +}; + +#endif //TCPSERVER_H diff --git a/src/app/WEBClient.cpp b/src/app/WEBClient.cpp new file mode 100644 index 0000000..24ddaee --- /dev/null +++ b/src/app/WEBClient.cpp @@ -0,0 +1,37 @@ +#include "WEBClient.h" + +//#define DEBUG_WEBCL + +WEBClient::WEBClient(WiFiClient client, uint8_t id, uint16_t maxResourceBuffer, uint16_t maxBodyBuffer, uint16_t dataBufferSize) : TCPClient(client, id, dataBufferSize), _WEBClientState(WEBServer::WEBClientState::ACCEPTED), _httpParserState(WEBServer::HttpParserStatus::HTTP_VERB), _fileSentBytes(0) +{ + #ifdef DEBUG_WEBCL + Serial.println("WEBClient : Standard constructor called"); + #endif + + _httpRequestData.HRM = WEBServer::HttpRequestMethod::UNDEFINED; + _httpRequestData.HV = WEBServer::HttpVersion::UNKNOWN; + _httpRequestData.HMT = WEBServer::HttpMIMEType::UNKNOWN_MIME; + + _httpRequestData.getParamsDataPointer = NULL; + _httpRequestData.postParamsDataPointer = NULL; + + _httpRequestData.httpResource = NULL; + _httpRequestData.maxResourceBuffer = maxResourceBuffer; + _httpRequestData.httpBody = NULL; + _httpRequestData.maxBodyBuffer = maxBodyBuffer; +} + +WEBClient::~WEBClient() +{ + #ifdef DEBUG_WEBCL + Serial.println("WEBClient : Destructor called"); + #endif + clearHttpRequestData(); +} + +void WEBClient::clearHttpRequestData() +{ + free(_httpRequestData.httpResource);free(_httpRequestData.httpBody); + _httpRequestData.getParams.dispose(); + _httpRequestData.postParams.dispose(); +} diff --git a/src/app/WEBClient.h b/src/app/WEBClient.h new file mode 100644 index 0000000..b49c25c --- /dev/null +++ b/src/app/WEBClient.h @@ -0,0 +1,24 @@ +#ifndef WEBCLIENT_H +#define WEBCLIENT_H + +#include "WEBServer.h" +#include "TCPClient.h" +#include "Dictionary.h" + +class WEBClient : public TCPClient +{ + template + friend class WEBServer; + public: + WEBClient(WiFiClient client, uint8_t id, uint16_t maxResourceBuffer = 255, uint16_t maxBodyBuffer = 255, uint16_t dataBufferSize = 511); + virtual ~WEBClient(); + protected: + WEBServer::WEBClientState _WEBClientState; + private: + WEBServer::HttpRequestData _httpRequestData; + WEBServer::HttpParserStatus _httpParserState; + uint64_t _fileSentBytes; + void clearHttpRequestData(); +}; + +#endif //WEBCLIENT_H diff --git a/src/app/WEBServer.h b/src/app/WEBServer.h new file mode 100644 index 0000000..794404a --- /dev/null +++ b/src/app/WEBServer.h @@ -0,0 +1,599 @@ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include "TCPServer.h" +#include "Dictionary.h" +#include "SDCardManager.h" +//#define DEBUG_WEBS + +template +class WEBServer : public TCPServer +{ + public: + enum HttpRequestMethod {UNDEFINED, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH}; + enum HttpVersion {UNKNOWN, HTTP_0_9, HTTP_1_1, HTTP_1_0, HTTP_2_0}; + enum HttpMIMEType{UNKNOWN_MIME, TEXT_PLAIN, TEXT_CSS, TEXT_HTML, TEXT_JAVASCRIPT, APPLICATION_JSON, APPLICATION_X_WWW_FORM_URLENCODED, IMAGE_PNG, IMAGE_JPEG, AUDIO_MPEG, APPLICATION_OCTET_STREAM}; + enum HttpParserStatus {HTTP_VERB, HTTP_RESSOURCE, HTTP_VERSION, HTTP_PARAMS, POST_DATA}; + enum WEBClientState {ACCEPTED, QUERY_PARSED, RESPONSE_SENT, DONE}; + enum HTTP_CODE {_100, _101, _200, _400, _401, _403, _404, _405, _500, _501}; + + struct HttpRequestData + { + HttpRequestMethod HRM; + HttpVersion HV; + HttpMIMEType HMT; + + Dictionary getParams; + char *getParamsDataPointer; //Used in the getParams algorithm + Dictionary postParams; + char *postParamsDataPointer; //Used in the postParams algorithm + + char *httpResource; + uint16_t maxResourceBuffer; + char *httpBody; + uint16_t maxBodyBuffer; + }; + + WEBServer(unsigned int port = 80, SDCardManager *sdCardManager = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientDataBufferSize = 512) : TCPServer(port, maxClient, clientDataBufferSize), _sdCardManager(sdCardManager) {} + + boolean addApiRoutine(const char *uri, boolean (*apiRoutine)(HttpRequestData&, WiFiClient*, void*), void *pData, HttpRequestMethod HRM = UNDEFINED) + { + return _apiDictionary.add(uri, new ApiRoutine({apiRoutine, pData, HRM})); + } + + void clearApiRoutine() { _apiDictionary.clear(); }; + + boolean removeApiRoutine(const char* uri) + { + return _apiDictionary.remove(uri); + } + protected: + private: + virtual T* createNewClient(WiFiClient wc) + { + return new T(wc, TCPServer::freeClientId()); + } + + virtual void greetClient(T *client) + { + + } + + virtual void processClientData(T *client) + { + switch(client->_WEBClientState) + { + case ACCEPTED: + #ifdef DEBUG_WEBS + Serial.println("WEBServer : ACCEPTED"); + #endif + queryParser(client); + break; + case QUERY_PARSED: + #ifdef DEBUG_WEBS + Serial.println("WEBServer : QUERY_PARSED"); + #endif + sendDataToClient(client); + break; + case RESPONSE_SENT: + #ifdef DEBUG_WEBS + Serial.println("WEBServer : RESPONSE_SENT"); + #endif + client->_WEBClientState = WEBClientState::DONE; + break; + case DONE: + #ifdef DEBUG_WEBS + Serial.println("WEBServer : DONE"); + #endif + client->_clientState = TCPClient::ClientState::DISCARDED; + break; + } + } + + void queryParser(T *client) + { + switch(client->_httpParserState) + { + case HttpParserStatus::HTTP_VERB: + { + #ifdef DEBUG_WEBS + Serial.println((char *)client->_data); + #endif + + char *pVerb = strstr((char *)client->_data, " "); + + if(pVerb != NULL) + { + *pVerb = '\0'; + client->_httpRequestData.HRM = getHttpVerbEnumValue((char *)client->_data); + client->freeDataBuffer((pVerb - (char *)client->_data) +1); + + if(client->_httpRequestData.HRM == HttpRequestMethod::UNDEFINED) //Error 400 + { + sendInfoResponse(HTTP_CODE::_400, client, "The server could not understand the request due to invalid syntax"); + client->_clientState = TCPClient::ClientState::DISCARDED; + break; + } + + #ifdef DEBUG_WEBS + Serial.print("Verb : ");Serial.println(client->_httpRequestData.HRM); + Serial.println((char *)client->_data); + #endif + + client->_httpParserState = HttpParserStatus::HTTP_RESSOURCE; + } + else + { + sendInfoResponse(HTTP_CODE::_400, client, "The server could not understand the request due to invalid syntax"); + client->_clientState = TCPClient::ClientState::DISCARDED; + break; + } + } + break; + case HttpParserStatus::HTTP_RESSOURCE: + { + char *pRsrc = strstr((char *)client->_data, " "); + + if(pRsrc != NULL) + { + *pRsrc = '\0'; + uint16_t safeLength = pRsrc - (char *)client->_data <= client->_httpRequestData.maxResourceBuffer ? pRsrc - (char *)client->_data : client->_httpRequestData.maxResourceBuffer; + + #ifdef DEBUG_WEBS + Serial.print("Resrc length : ");Serial.println(safeLength); + #endif + + client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (safeLength+1) ); //for \0 + if(client->_httpRequestData.httpResource != NULL) + { + strncpy(client->_httpRequestData.httpResource, (char *)client->_data, safeLength); + client->_httpRequestData.httpResource[safeLength] = '\0'; + } + else //Error 500 + { + sendInfoResponse(HTTP_CODE::_500, client, "Failed to allocate memory for resources"); + client->_clientState = TCPClient::ClientState::DISCARDED; + break; + } + + client->freeDataBuffer(safeLength + 1); + + #ifdef DEBUG_WEBS + Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource); + Serial.println((char *)client->_data); + #endif + } + else //Resource is probably too long, so we truncate it + { + client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (client->_httpRequestData.maxResourceBuffer+1) ); //for \0 + if(client->_httpRequestData.httpResource != NULL) + { + strncpy(client->_httpRequestData.httpResource, (char *)client->_data, client->_httpRequestData.maxResourceBuffer); + client->_httpRequestData.httpResource[client->_httpRequestData.maxResourceBuffer] = '\0'; + } + else //Error 500 + { + sendInfoResponse(HTTP_CODE::_500, client, "Failed to allocate memory for resources"); + client->_clientState = TCPClient::ClientState::DISCARDED; + break; + } + + client->freeDataBuffer(client->_httpRequestData.maxResourceBuffer + 1); + } + client->_httpParserState = HttpParserStatus::HTTP_PARAMS; + } + break; + case HttpParserStatus::HTTP_VERSION: + { + char *pEndline = strstr((char *)client->_data, "\r\n"); + char *pVers = strstr((char *)client->_data, "HTTP/"); + + if(pEndline != NULL && pVers!= NULL) + { + *pEndline = '\0'; + client->_httpRequestData.HV = getHttpVersionEnumValue(pVers+5); + + #ifdef DEBUG_WEBS + Serial.print("Vers : ");Serial.println(pVers+5); + Serial.print("Vers : ");Serial.println(client->_httpRequestData.HV); + #endif + + client->freeDataBuffer((pEndline - (char *)client->_data)+2); + + #ifdef DEBUG_WEBS + Serial.println((char *)client->_data); + #endif + + client->_httpParserState = HttpParserStatus::POST_DATA; + } + } + break; + case HttpParserStatus::HTTP_PARAMS: //index.htm?var1=1&var2=2... + if(!httpParamParser(client)) + { + #ifdef DEBUG_WEBS + Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource); + Serial.println("Get params :"); + for(int i = 0; i < client->_httpRequestData.getParams.count(); i++) + { + Serial.print(client->_httpRequestData.getParams.getParameter(i));Serial.print(" : ");Serial.println(client->_httpRequestData.getParams.getAt(i)->getString()); + } + #endif + client->_httpParserState = HttpParserStatus::HTTP_VERSION; + } + break; + case HttpParserStatus::POST_DATA: + + client->_WEBClientState = WEBClientState::QUERY_PARSED; + break; + default : + sendInfoResponse(HTTP_CODE::_500, client, "WEB server error"); + client->_clientState = TCPClient::ClientState::DISCARDED; + break; + } + } + + boolean httpParamParser(T *client) + { + char *pGetParam = strchr((char *)client->_httpRequestData.httpResource, '?'); + + + if(pGetParam != NULL) //There are some params to be parsed + { + if(client->_httpRequestData.getParamsDataPointer == NULL) + { + client->_httpRequestData.getParamsDataPointer = pGetParam +1;//We save the starting position of the string to parse + } + + char *key = strchr(client->_httpRequestData.getParamsDataPointer, '='); + char *value = strchr(client->_httpRequestData.getParamsDataPointer, '&'); + + if(key == NULL && value == NULL) //Only the key is present + { + client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL)); + *pGetParam = '\0'; + return false; + } + else if(key != NULL && value != NULL) + { + if(key < value)*key = '\0'; + *value = '\0'; + + client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(key > value ? NULL : key + 1)); + strcpy(client->_httpRequestData.getParamsDataPointer,value+1); + + #ifdef DEBUG_WEBS + Serial.print("Params pointer : ");Serial.println(client->_httpRequestData.getParamsDataPointer); + #endif + } + else if(key != NULL && value == NULL) //Only one key/value pair present + { + *key = '\0'; + + client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(key+1)); + *pGetParam = '\0'; + return false; + } + else if(key == NULL && value != NULL) + { + *value = '\0'; + + client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL)); + strcpy(client->_httpRequestData.getParamsDataPointer,value+1); + } + } + else //nothing to parse or done + { + return false; + } + } + + void sendDataToClient(T *client) + { + if(!sendPageToClientFromApiDictio(client)) //Then we check if it is not a file that is requested + { + if(!sendPageToClientFromSdCard(client)) //If this function returns false, we close the connection with the client. An error occured (An error message has already been sent) or the whole file has been sent. + { + client->_WEBClientState = WEBClientState::RESPONSE_SENT; + } + } + else //If we found the api endpoint, we can close the connection with the client after the data has been sent. + client->_WEBClientState = WEBClientState::RESPONSE_SENT; + } + + boolean sendPageToClientFromApiDictio(T *client) + { + if(_apiDictionary.count() == 0 || client->_httpRequestData.httpResource == NULL) + return false; + + ApiRoutine *ref = _apiDictionary(client->_httpRequestData.httpResource); + + if(ref == NULL) + return false; + + if(ref->HRM == UNDEFINED) + { + return (*(ref->apiRoutine))(client->_httpRequestData, &(client->_client), ref->pData); + }else if(ref->HRM == client->_httpRequestData.HRM) + { + return (*(ref->apiRoutine))(client->_httpRequestData, &(client->_client), ref->pData); + } + else + return false; + } + + boolean sendPageToClientFromSdCard(T *client) + { + if(_sdCardManager != NULL) + { + File pageToSend; + char *filePath(NULL), *header(NULL), sendBuffer[2048]; + int readBytes(0); + //We check what kind of http verb it is + switch(client->_httpRequestData.HRM) + { + case GET: + filePath = getFilePathByHttpResource(client->_httpRequestData.httpResource); + if(filePath == NULL) + { + sendInfoResponse(HTTP_CODE::_500, client, "Failed to allocate memory for the filePath"); + return false; + } + + #ifdef DEBUG_WEBS + Serial.print("FILE PATH : "); + Serial.println(filePath); + #endif + + pageToSend = _sdCardManager->open(filePath); + free(filePath);filePath = NULL; + + //If we couldn't open the file + if(!pageToSend) + { + pageToSend.close(); + + char *response(NULL); + + response = (char *) malloc(sizeof(char) * (strlen_P((PGM_P)F("Resource : not found on this server")) + strlen(client->_httpRequestData.httpResource) + 1)); + if(response == NULL) + { + sendInfoResponse(HTTP_CODE::_500, client, "Failed to allocate memory for the response"); + return false; + } + + sprintf(response, "Resource : %s not found on this server", client->_httpRequestData.httpResource); + sendInfoResponse(HTTP_CODE::_404, client, response); + + free(response);response = NULL; + return false; + } + + #ifdef DEBUG_WEBS + 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 + + if(pageToSend.isDirectory()) //To DO : List the files present in the directory + { + pageToSend.close(); + + sendInfoResponse(HTTP_CODE::_403, client, "The file you want to access is a folder"); + return false; + } + + if(client->_fileSentBytes == 0) + { + header = getHTTPHeader(getMIMETypeByExtension(getFileExtension(pageToSend.name())), pageToSend.size()); + if(header == NULL) + { + sendInfoResponse(HTTP_CODE::_500, client, "Failed to allocate memory for the header"); + pageToSend.close(); + return false; + } + + client->_client.print(header); + free(header);header = NULL; + } + else + { + pageToSend.seek(client->_fileSentBytes); + } + + if(pageToSend.available()) + { + readBytes = pageToSend.read(sendBuffer,2048); + client->_client.write(sendBuffer, readBytes); + + client->_fileSentBytes += readBytes; //We save the number of bytes sent so that we can reopen the file to this position later on. + } + else + { + pageToSend.close(); + return false; + } + + pageToSend.close(); + break; + default: //If not supported + sendInfoResponse(HTTP_CODE::_500, client, "The method used is not allowed"); + break; + } + }else + { + sendInfoResponse(HTTP_CODE::_500, client, "Unable to access the SDCard"); + return false; + } + + return true; + } + + void sendInfoResponse(HTTP_CODE http_code, T *client, const char *message) + { + uint16_t code(0); + char codeLiteral[100]; + switch(http_code) + { + case _400: + code = 400; + strcpy_P(codeLiteral,(PGM_P)F("Bad Request")); + break; + case _404: + code = 404; + strcpy_P(codeLiteral,(PGM_P)F("Not Found")); + break; + case _403: + code = 403; + strcpy_P(codeLiteral,(PGM_P)F("Forbidden")); + break; + case _405: + code = 405; + strcpy_P(codeLiteral,(PGM_P)F("Method Not Allowed")); + break; + case _500: + code = 500; + strcpy_P(codeLiteral,(PGM_P)F("Internal Server Error")); + break; + default: + code = 000; + strcpy_P(codeLiteral,(PGM_P)F("Error Not Defined")); + break; + } + + client->_client.print(F("HTTP/1.1 "));client->_client.print(code);client->_client.print(F(" "));client->_client.print(codeLiteral);client->_client.print(F("\r\nContent-Type: text/html\r\nContent-Length: ")); + client->_client.print(strlen(message) + 59); + client->_client.print(F("\r\n\r\n\r\n\r\n

Error "));client->_client.print(code);client->_client.print(F("

")); + client->_client.print(message); + client->_client.print(F("

\r\n")); + } + + /*Static helper methods*/ + + static HttpRequestMethod getHttpVerbEnumValue(const char *parseBuffer) + { + if(parseBuffer == NULL)return HttpRequestMethod::UNDEFINED; + + //UNDEFINED, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH + if(strcmp(parseBuffer,"GET") == 0){return HttpRequestMethod::GET;} + else if(strcmp(parseBuffer,"POST") == 0){return HttpRequestMethod::POST;} + else if(strcmp(parseBuffer,"HEAD") == 0){return HttpRequestMethod::HEAD;} + else if(strcmp(parseBuffer,"PUT") == 0){return HttpRequestMethod::PUT;} + else if(strcmp(parseBuffer,"DELETE") == 0){return HttpRequestMethod::DELETE;} + else if(strcmp(parseBuffer,"CONNECT") == 0){return HttpRequestMethod::CONNECT;} + else if(strcmp(parseBuffer,"TRACE") == 0){return HttpRequestMethod::TRACE;} + else if(strcmp(parseBuffer,"PATCH") == 0){return HttpRequestMethod::PATCH;} + else if(strcmp(parseBuffer,"OPTIONS") == 0){return HttpRequestMethod::OPTIONS;} + else + return HttpRequestMethod::UNDEFINED; + } + + static HttpMIMEType getMIMETypeByExtension(const char *extension) + { + if(extension == NULL)return UNKNOWN_MIME; + + //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 *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; + } + + return header; + } + + static char *getFileExtension(char *name) + { + if(name == NULL)return NULL; + + char *p(strchr(name, '.')); + + return p != NULL ? p+1 : NULL; + } + + static char *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); + + #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; + } + + static HttpVersion getHttpVersionEnumValue(const char *parseBuffer) + { + //HTTP_0_9, HTTP_1_1, HTTP_1_0, HTTP_2_0 + if(strcmp(parseBuffer,"1.1") == 0){return HttpVersion::HTTP_1_1;} + else if(strcmp(parseBuffer,"2.0") == 0){return HttpVersion::HTTP_2_0;} + else if(strcmp(parseBuffer,"1.0") == 0){return HttpVersion::HTTP_1_0;} + else if(strcmp(parseBuffer,"0.9") == 0){return HttpVersion::HTTP_0_9;} + else + return HttpVersion::UNKNOWN; + } + + struct ApiRoutine + { + boolean (*apiRoutine)(HttpRequestData&, WiFiClient*, void*); + void *pData; + HttpRequestMethod HRM; + }; + + Dictionary _apiDictionary; + SDCardManager *_sdCardManager; +}; + +#endif //WEBSERVER_H