diff --git a/src/app/HttpClient.cpp b/src/app/HttpClient.cpp new file mode 100644 index 0000000..5fc1e3d --- /dev/null +++ b/src/app/HttpClient.cpp @@ -0,0 +1,335 @@ +#include "HttpClient.h" +#include + +#define DEBUG_HTTP_CLIENT + +/* + * End of HttpClientHelper + */ + +HttpClient::HttpClient() : WiFiClient(), _connectionStatus(NO_ATTEMPT), _resource(NULL), _address(NULL), _port(0), _keepAlive(false), _maxRetries(5), _retries(0), _httpCode(HTTP_CODE::UNDEFINED_CODE), _bodyReadyToRead(false) +{ + +} + +HttpClient::HttpClient(const char *address, uint16_t port) : HttpClient() +{ + + if(address != NULL) + { + _address = (char *)malloc(strlen(address) * sizeof(char) + 1); + strcpy(_address, address); + } + + _port = port; + + connectByHostOrIp(); +} + +HttpClient::HttpClient(const char *address, const char *resource, uint16_t port) : HttpClient(address, port) +{ + if(resource != NULL) + { + _resource = (char *)malloc(strlen(resource) * sizeof(char) + 1); + strcpy(_resource, resource); + } +} + +HttpClient::HttpClient(const HttpClient &object) +{ + if(object._resource != NULL) + { + _resource = (char *)malloc(strlen(object._resource) * sizeof(char) + 1); + strcpy(_resource, object._resource); + } + else + _resource = NULL; + + if(object._address != NULL) + { + _address = (char *)malloc(strlen(object._address) * sizeof(char) + 1); + strcpy(_address, object._address); + } + else + _address = NULL; + + _connectionStatus = object._connectionStatus; + _keepAlive = object._keepAlive; + _maxRetries = object._maxRetries; + _retries = object._retries; + _port = object._port; + _httpCode = object._httpCode; + _bodyReadyToRead = object._bodyReadyToRead; +} + +HttpClient::~HttpClient() +{ + if(_resource != NULL)free(_resource); + if(_address != NULL)free(_address); +} + +boolean HttpClient::connectByHostOrIp() +{ + IPAddress ipAddress; + if(ipAddress.fromString(_address)) + { + _connectionStatus = connect(ipAddress, _port) == 1 ? SUCCESSFUL : FAILED; + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Correct ip address. Connection status : %d\n", _connectionStatus); + #endif + } + else + { + _connectionStatus = connect(_address, _port) == 1 ? SUCCESSFUL : FAILED; + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Probably a hostname. Connection status : %d\n", _connectionStatus); + #endif + } + + return _connectionStatus == SUCCESSFUL; +} + +boolean HttpClient::sendHttpQuery(const char *resource, HttpRequestMethod method, Dictionary *getData, Dictionary *postData) +{ + if(resource != NULL) //We overwrite the resource if it has been already defined + { + if(_resource != NULL) + free(_resource); + + _resource = (char *) malloc(strlen(resource) * sizeof(char) + 1); + strcpy(_resource, resource); + } + + //We check the result + return sendHttpQuery(method, getData, postData); +} + +boolean HttpClient::sendHttpQuery(HttpRequestMethod method, Dictionary *getData, Dictionary *postData) +{ + _httpCode = HTTP_CODE::UNDEFINED_CODE; + _bodyReadyToRead = false; + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Link status : %d\n", status()); + #endif + + if(!connected() || _connectionStatus == FAILED) + { + if(_retries == _maxRetries) return false; + if(_connectionStatus == FAILED) + { + stop(); + _retries++; + } + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Link broken, we try to reconnect : addr : %s port %u\nretries : %u\n", _address, _port, _retries); + #endif + + connectByHostOrIp(); + + if(_connectionStatus == FAILED) + { + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Failed to reconnect\n"); + #endif + stop(); + return false; + } + } + + if(connected()) + { + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Server is listening\n", status()); + #endif + + switch(method) + { + case HttpRequestMethod::GET: + //No Content-Length in this case + printf("GET %s", _resource == NULL ? "/" : _resource); + //Here we send the parameters + sendUriWithGetParams(getData); + sendHeader(); + break; + case HttpRequestMethod::POST: + //It is necessary to compute the content length + printf("POST %s", _resource == NULL ? "/" : _resource); + //Here we send the parameters + sendUriWithGetParams(getData); + sendHeader(HttpMIMEType::APPLICATION_X_WWW_FORM_URLENCODED, computeBodyLength(postData)); + sendPostData(postData); + break; + default: + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Http verb unspecified\n", status()); + #endif + if(!_keepAlive)stop(); + return false; + break; + } + } + + return true; +} + +HttpClient::HTTP_CODE HttpClient::isReplyAvailable() +{ + uint32_t bytesAvailable(available()); + uint8_t readBuffer[100]; + uint8_t safeSize; + + if(bytesAvailable && !_bodyReadyToRead) + { + safeSize = bytesAvailable > 99 ? 99 : bytesAvailable; + peekBytes(readBuffer, safeSize); + readBuffer[safeSize] = '\0'; + + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Data chunk from server size -> %u - %u : %s\n", safeSize, bytesAvailable, readBuffer); + #endif + + //We try to extract the http return code: + if(_httpCode == HTTP_CODE::UNDEFINED_CODE) + { + char *code = strchr((char *)readBuffer, ' '), *endP; + + if(code != NULL) + { + endP = strchr(code + 1, ' '); + if(endP != NULL) + { + *endP = '\0'; + + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Http code : %s\n", code + 1); + #endif + + + _httpCode = numberToHTTP_CODE(strtoul(code+1, NULL, 10)); + read(readBuffer, safeSize - 4); //To remove the peek bytes + } + } + } + + if(!_bodyReadyToRead) + { + char *endP = strstr((char *)readBuffer, "\r\n\r\n"); + + if(endP != NULL) + { + #ifdef DEBUG_HTTP_CLIENT + Serial.printf("Body found, bytes to discard : %u\n", (uint8_t *)endP - readBuffer + 4); + #endif + read(readBuffer, (uint8_t *)endP - readBuffer + 4); + _bodyReadyToRead = true; + } + else + { + read(readBuffer, safeSize - 3); + } + } + } + + if(_httpCode != HTTP_CODE::UNDEFINED_CODE && _bodyReadyToRead) + return _httpCode; + + return HTTP_CODE::UNDEFINED_CODE; +} + +uint16_t HttpClient::readHttpReply(uint8_t *buffer, uint32_t size) +{ + uint32_t bytesAvailable(available()); + uint8_t safeSize(0); + + if(bytesAvailable) + { + safeSize = bytesAvailable > size - 1 ? size - 1 : bytesAvailable; + + read(buffer, safeSize); + } + + buffer[safeSize] = '\0'; + + return safeSize; +} + +void HttpClient::sendUriWithGetParams(Dictionary *data) +{ + if(data == NULL)return; + + uint8_t count(data->count()); + + if(count == 0)return; + print("?"); + + for(int i(0); i < count; i++) + { + char str[2] = ""; + if(data->getAt(i) != NULL) + { + if(strlen(data->getAt(i)->getString()) > 0) + strcpy(str, "="); + } + printf("%s%s%s", data->getParameter(i), str, data->getAt(i) != NULL ? data->getAt(i)->getString() : ""); + + if(i < count - 1) + print("&"); + } +} + +void HttpClient::sendPostData(Dictionary *data) +{ + if(data == NULL)return; + + uint8_t count(data->count()); + + if(count == 0)return; + + for(int i(0); i < count; i++) + { + printf("%s=%s", data->getParameter(i), data->getAt(i) != NULL ? data->getAt(i)->getString() : ""); + + if(i < count - 1) + print("&"); + } +} + +void HttpClient::keepAlive(boolean enabled) +{ + _keepAlive = enabled; +} + +void HttpClient::sendHeader(HttpMIMEType contentType, uint64_t contentLength, HttpVersion httpVersion) +{ + char mime[255] = "", httpVer[15] = ""; + printf(" %s\r\nHost: %u.%u.%u.%u:%u\r\nConnection: %s\r\n", httpVersionToString(httpVersion, httpVer), remoteIP()[0], remoteIP()[1], remoteIP()[2], remoteIP()[3], remotePort(), _keepAlive ? "keep-alive" : "close"); + if(contentLength > 0) + { + printf("Content-Length: %u\r\n", contentLength); + printf("Content-Type: %s\r\n", httpMIMETypeToString(contentType, mime)); + } + + printf("\r\n"); +} + +uint64_t HttpClient::computeBodyLength(Dictionary *data) +{ + uint64_t length(0); + + if(data == NULL)return length; + + uint8_t count(data->count()); + + if(count == 0)return length; + + for(int i(0); i < count; i++) + { + length += data->getAt(i) != NULL ? strlen(data->getAt(i)->getString()) + 1 : 1; + length += strlen(data->getParameter(i)); + + if(i < count - 1) + length++; + } + + return length; +} diff --git a/src/app/HttpClient.h b/src/app/HttpClient.h new file mode 100644 index 0000000..e2b0580 --- /dev/null +++ b/src/app/HttpClient.h @@ -0,0 +1,68 @@ +#ifndef HTTPCLIENT_H +#define HTTPCLIENT_H + +#include +#include "HttpConstants.h" +#include "Dictionary.h" + +namespace HttpClientHelper +{ + +} + +class HttpClient : public WiFiClient, public HttpConstants +{ + public: + enum ConnectionStatus { NO_ATTEMPT = 0, SUCCESSFUL, FAILED }; + + HttpClient(); + HttpClient(const char *address, uint16_t port = 80); + HttpClient(const char *address, const char *resource, uint16_t port = 80); + + HttpClient(const HttpClient &object); + virtual ~HttpClient(); + + boolean sendHttpQuery(const char *ressource, HttpRequestMethod method = HttpRequestMethod::GET, Dictionary *getData = NULL, Dictionary *postData = NULL); + boolean sendHttpQuery(HttpRequestMethod method = HttpRequestMethod::GET, Dictionary *getData = NULL, Dictionary *postData = NULL); + void keepAlive(boolean enabled); + + HTTP_CODE isReplyAvailable(); + + uint16_t readHttpReply(uint8_t *buffer, uint32_t size); + protected: + private: + boolean connectByHostOrIp(); + void sendUriWithGetParams(Dictionary *data); + void sendPostData(Dictionary *data); + void sendHeader(HttpMIMEType contentType = HttpMIMEType::UNKNOWN_MIME, uint64_t contentLength = 0, HttpVersion httpVersion = HttpVersion::HTTP_1_1); + uint64_t computeBodyLength(Dictionary *data); + + ConnectionStatus _connectionStatus; + char *_resource; + char *_address; + uint16_t _port; + boolean _keepAlive; + uint8_t _maxRetries; + uint8_t _retries; + HTTP_CODE _httpCode; + boolean _bodyReadyToRead; +}; + +#endif //HTTPCLIENT_H + +/* + * TCP status codes : + * enum tcp_state { + CLOSED = 0, + LISTEN = 1, + SYN_SENT = 2, + SYN_RCVD = 3, + ESTABLISHED = 4, + FIN_WAIT_1 = 5, + FIN_WAIT_2 = 6, + CLOSE_WAIT = 7, + CLOSING = 8, + LAST_ACK = 9, + TIME_WAIT = 10 +}; + */