416 lines
11 KiB
C++
416 lines
11 KiB
C++
#include "HttpClient.h"
|
|
#include <IPAddress.h>
|
|
|
|
//#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<DictionaryHelper::StringEntity> *getData, Dictionary<DictionaryHelper::StringEntity> *postData, Dictionary<DictionaryHelper::StringEntity> *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<DictionaryHelper::StringEntity> *getData, Dictionary<DictionaryHelper::StringEntity> *postData, Dictionary<DictionaryHelper::StringEntity> *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<DictionaryHelper::StringEntity> *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<DictionaryHelper::StringEntity> *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<DictionaryHelper::StringEntity> *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<DictionaryHelper::StringEntity> *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;
|
|
}
|