#include "HttpClient.h" #include //#define DEBUG_HTTP_CLIENT /* * End of HttpClientHelper */ HttpClient::HttpClient(const char *address, uint16_t port, uint32_t timeout) : WiFiClient(), _connectionStatus(NO_ATTEMPT), _resource(NULL), _pAddress(address), _port(0), _keepAlive(false), _maxRetries(-1), _retries(0), _isIp(false), _httpCode(HTTP_CODE::UNDEFINED_CODE), _httpCodeParsed(false) { _port = port; setTimeout(timeout); connectByHostOrIp(); } HttpClient::HttpClient(const char *address, const char *resource, uint16_t port, uint32_t timeout) : HttpClient(address, port, timeout) { 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; _pAddress = object._pAddress; _connectionStatus = object._connectionStatus; _keepAlive = object._keepAlive; _maxRetries = object._maxRetries; _retries = object._retries; _port = object._port; _httpCode = object._httpCode; _httpCodeParsed = object._httpCodeParsed; } HttpClient::~HttpClient() { if(_resource != NULL)free(_resource); } boolean HttpClient::connectByHostOrIp() { IPAddress ipAddress; #ifdef DEBUG_HTTP_CLIENT Serial.printf("About to connect\n"); #endif if(ipAddress.fromString(_pAddress)) { _isIp = true; _connectionStatus = connect(ipAddress, _port) == 1 ? SUCCESSFUL : FAILED; #ifdef DEBUG_HTTP_CLIENT Serial.printf("Correct ip address. Connection status : %d\n", _connectionStatus); #endif } else { _isIp = false; _connectionStatus = connect(_pAddress, _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, Dictionary *headerData) { 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, headerData); } boolean HttpClient::sendHttpQuery(HttpRequestMethod method, Dictionary *getData, Dictionary *postData, Dictionary *headerData) { //We reset this two flags _httpCode = HTTP_CODE::UNDEFINED_CODE; _httpCodeParsed = false; #ifdef DEBUG_HTTP_CLIENT if(_keepAlive) Serial.printf("Link status : %d\n", status()); #endif if(!connected() || _connectionStatus == FAILED) { if(_retries == _maxRetries) return false; if(_connectionStatus == FAILED) { stop(); if(_maxRetries != -1)_retries++; } #ifdef DEBUG_HTTP_CLIENT if(_keepAlive) Serial.printf("Link broken, we try to reconnect : addr : %s port %u\nretries : %u\n", _pAddress, _port, _retries); else Serial.printf("We start a new connection : %s port %u\nretries : %u\n", _pAddress, _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(HttpMIMEType::UNKNOWN_MIME, 0, headerData); 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), headerData); 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(uint16_t timeout) { uint32_t ts(millis()); char buffer[100]; char *bodyDelimiter(NULL); char *newLineDelimiter(NULL); if(!_httpCodeParsed)_httpCodeParsed = true; else { return _httpCode; } #ifdef DEBUG_HTTP_CLIENT Serial.println("Before timeout"); #endif //This is the loop where we parse the data while((available() || !bodyDelimiter) && millis() - ts < timeout) { int bytesCount(available()); if(bytesCount) { //If we do not have the HTTP response code yet, we parse it if(_httpCode == HTTP_CODE::UNDEFINED_CODE) { uint16_t safeSize = bytesCount > 99 ? 99 : bytesCount; uint8_t bytesRed = buffer[peekBytes((uint8_t*)buffer,safeSize)] = '\0'; //We look for the end of the first line ie : HTTP/1.1 200 OK\r\n char *pNewLine = strstr(buffer, "\r\n"); //If we found the new line, we can retrieve the code if(pNewLine) { #ifdef DEBUG_HTTP_CLIENT Serial.println("New line found"); #endif //We extract the code char *code = strchr((char *)buffer, ' '), *endP; if(code) { endP = strchr(code + 1, ' '); if(endP != NULL) { *endP = '\0'; #ifdef DEBUG_HTTP_CLIENT Serial.printf("Http code : %s\n", code + 1); #endif _httpCode = (HTTP_CODE)strtoul(code+1, NULL, 10); //We can now discard the first line #ifdef DEBUG_HTTP_CLIENT Serial.printf("First line length : %u\n",(pNewLine - buffer) + 2); #endif read((uint8_t *)buffer, (pNewLine - buffer) + 2); #ifdef DEBUG_HTTP_CLIENT bytesCount = available(); safeSize = bytesCount > 99 ? 99 : bytesCount; buffer[peekBytes((uint8_t*)buffer,safeSize)] = '\0'; Serial.printf("Next chunk is (size %u) : %s\n",safeSize,buffer); #endif } } else { #ifdef DEBUG_HTTP_CLIENT Serial.println("Code delimiter NOT found, leaving"); #endif break; } } else if(millis() - ts >= timeout)//Time out is over, received data is probably junk. { //we leave the loop; #ifdef DEBUG_HTTP_CLIENT Serial.println("New line NOT found, leaving"); #endif break; } } else//We found the HTTP code, now we discard all the header data { int safeSize = bytesCount > 99 ? 99 : bytesCount; buffer[peekBytes((uint8_t*)buffer,safeSize)] = '\0'; #ifdef DEBUG_HTTP_CLIENT Serial.printf("Peeked for (size %u) : %s\n",safeSize,buffer); #endif bodyDelimiter = strstr(buffer, "\r\n\r\n"); newLineDelimiter = strstr(buffer, "\r\n"); if(!bodyDelimiter) { if(newLineDelimiter) { read((uint8_t *)buffer, (newLineDelimiter - buffer) + 2); } else { read((uint8_t *)buffer, strlen(buffer)); } } else { #ifdef DEBUG_HTTP_CLIENT Serial.println("We found the body delimiter"); #endif read((uint8_t *)buffer, (bodyDelimiter - buffer) + 4); #ifdef DEBUG_HTTP_CLIENT safeSize = available() > 99 ? 99 : available(); buffer[peekBytes((uint8_t*)buffer,safeSize)] = '\0'; Serial.printf("Body chunk is : %s\n",buffer); #endif break; } } ts = millis(); } yield(); } #ifdef DEBUG_HTTP_CLIENT Serial.println("\nAfter timeout or all data is received"); #endif return _httpCode; } uint16_t HttpClient::readHttpBody(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, Dictionary *headerData, HttpVersion httpVersion) { char mime[255] = "", httpVer[15] = ""; //Host could be an IP address or a host name if(_isIp) 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"); else printf(" %s\r\nHost: %s\r\nConnection: %s\r\n", httpVersionToString(httpVersion, httpVer), _pAddress, _keepAlive ? "keep-alive" : "close"); if(contentLength > 0) { printf("Content-Length: %u\r\n", contentLength); printf("Content-Type: %s\r\n", httpMIMETypeToString(contentType, mime)); } //We send the headerData if not NULL if(headerData != NULL) { uint8_t count(headerData->count()); if(count > 0) { for(uint8_t i(0); i < count; i++) { printf("%s: %s\r\n", headerData->getParameter(i), headerData->getAt(i) != NULL ? headerData->getAt(i)->getString() : ""); } } } 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; } void HttpClient::setMaxRetries(int16_t retries) { _maxRetries = retries; }