forked from khoih-prog/AsyncHTTPRequest_Generic
### Releases v1.1.0 1. Add HTTP PUT, PATCH, DELETE and HEAD methods. Check [Add support for sending PUT, PATCH, DELETE request](https://github.com/khoih-prog/AsyncHTTPRequest_Generic/issues/5) 2. Add Table of Contents 3. Add Version String
1359 lines
33 KiB
C++
1359 lines
33 KiB
C++
/****************************************************************************************************************************
|
|
AsyncHTTPRequest_Generic.cpp - Dead simple AsyncHTTPRequest for ESP8266, ESP32 and currently STM32 with built-in LAN8742A Ethernet
|
|
|
|
For ESP8266, ESP32 and STM32 with built-in LAN8742A Ethernet (Nucleo-144, DISCOVERY, etc)
|
|
|
|
AsyncHTTPRequest_STM32 is a library for the ESP8266, ESP32 and currently STM32 run built-in Ethernet WebServer
|
|
|
|
Based on and modified from asyncHTTPrequest Library (https://github.com/boblemaire/asyncHTTPrequest)
|
|
|
|
Built by Khoi Hoang https://github.com/khoih-prog/AsyncHTTPRequest_Generic
|
|
Licensed under MIT license
|
|
|
|
Copyright (C) <2018> <Bob Lemaire, IoTaWatt, Inc.>
|
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
|
|
as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
Version: 1.1.0
|
|
|
|
Version Modified By Date Comments
|
|
------- ----------- ---------- -----------
|
|
1.0.0 K Hoang 14/09/2020 Initial coding to add support to STM32 using built-in Ethernet (Nucleo-144, DISCOVERY, etc).
|
|
1.0.1 K Hoang 09/10/2020 Restore cpp code besides Impl.h code.
|
|
1.0.2 K Hoang 09/11/2020 Make Mutex Lock and delete more reliable and error-proof
|
|
1.1.0 K Hoang 23/12/2020 Add HTTP PUT, PATCH, DELETE and HEAD methods
|
|
*****************************************************************************************************************************/
|
|
|
|
#include "AsyncHTTPRequest_Debug_Generic.h"
|
|
#include "AsyncHTTPRequest_Generic.h"
|
|
|
|
|
|
//**************************************************************************************************************
|
|
AsyncHTTPRequest::AsyncHTTPRequest(): _readyState(readyStateUnsent), _HTTPcode(0), _chunked(false), _debug(DEBUG_IOTA_HTTP_SET)
|
|
, _timeout(DEFAULT_RX_TIMEOUT), _lastActivity(0), _requestStartTime(0), _requestEndTime(0), _URL(nullptr)
|
|
, _connectedHost(nullptr), _connectedPort(-1), _client(nullptr), _contentLength(0), _contentRead(0)
|
|
, _readyStateChangeCB(nullptr), _readyStateChangeCBarg(nullptr), _onDataCB(nullptr), _onDataCBarg(nullptr)
|
|
, _request(nullptr), _response(nullptr), _chunks(nullptr), _headers(nullptr)
|
|
{
|
|
#ifdef ESP32
|
|
threadLock = xSemaphoreCreateRecursiveMutex();
|
|
#endif
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
AsyncHTTPRequest::~AsyncHTTPRequest()
|
|
{
|
|
if (_client)
|
|
_client->close(true);
|
|
|
|
|
|
SAFE_DELETE(_URL)
|
|
SAFE_DELETE(_headers)
|
|
SAFE_DELETE(_request)
|
|
SAFE_DELETE(_response)
|
|
SAFE_DELETE(_chunks)
|
|
SAFE_DELETE_ARRAY(_connectedHost)
|
|
|
|
#ifdef ESP32
|
|
// KH add
|
|
if (threadLock)
|
|
{
|
|
vSemaphoreDelete(threadLock);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setDebug(bool debug)
|
|
{
|
|
if (_debug || debug)
|
|
{
|
|
_debug = true;
|
|
|
|
AHTTP_LOGDEBUG3("setDebug(", debug ? "on" : "off", ") version", ASYNC_HTTP_REQUEST_GENERIC_VERSION);
|
|
}
|
|
|
|
_debug = debug;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::debug()
|
|
{
|
|
return (_debug);
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::open(const char* method, const char* URL)
|
|
{
|
|
AHTTP_LOGDEBUG3("open(", method, ", url =", URL);
|
|
|
|
if (_readyState != readyStateUnsent && _readyState != readyStateDone)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_requestStartTime = millis();
|
|
|
|
SAFE_DELETE(_URL)
|
|
SAFE_DELETE(_headers)
|
|
SAFE_DELETE(_request)
|
|
SAFE_DELETE(_response)
|
|
SAFE_DELETE(_chunks)
|
|
|
|
_URL = nullptr;
|
|
_headers = nullptr;
|
|
_response = nullptr;
|
|
_request = nullptr;
|
|
_chunks = nullptr;
|
|
_chunked = false;
|
|
_contentRead = 0;
|
|
_readyState = readyStateUnsent;
|
|
|
|
|
|
if (strcmp(method, "GET") == 0)
|
|
{
|
|
_HTTPmethod = HTTPmethodGET;
|
|
}
|
|
else if (strcmp(method, "POST") == 0)
|
|
{
|
|
_HTTPmethod = HTTPmethodPOST;
|
|
}
|
|
// New in v1.1.0
|
|
else if (strcmp(method, "PUT") == 0)
|
|
{
|
|
_HTTPmethod = HTTPmethodPUT;
|
|
}
|
|
else if (strcmp(method, "PATCH") == 0)
|
|
{
|
|
_HTTPmethod = HTTPmethodPATCH;
|
|
}
|
|
else if (strcmp(method, "DELETE") == 0)
|
|
{
|
|
_HTTPmethod = HTTPmethodDELETE;
|
|
}
|
|
else if (strcmp(method, "HEAD") == 0)
|
|
{
|
|
_HTTPmethod = HTTPmethodHEAD;
|
|
}
|
|
//////
|
|
else
|
|
return false;
|
|
|
|
|
|
if (!_parseURL(URL))
|
|
{
|
|
return false;
|
|
}
|
|
if ( _client && _client->connected() && (strcmp(_URL->host, _connectedHost) != 0 || _URL->port != _connectedPort))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
char* hostName = new char[strlen(_URL->host) + 10];
|
|
|
|
if (hostName)
|
|
{
|
|
sprintf(hostName, "%s:%d", _URL->host, _URL->port);
|
|
_addHeader("host", hostName);
|
|
|
|
SAFE_DELETE_ARRAY(hostName)
|
|
|
|
_lastActivity = millis();
|
|
|
|
return _connect();
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::onReadyStateChange(readyStateChangeCB cb, void* arg)
|
|
{
|
|
_readyStateChangeCB = cb;
|
|
_readyStateChangeCBarg = arg;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setTimeout(int seconds)
|
|
{
|
|
AHTTP_LOGDEBUG1("setTimeout = ", seconds);
|
|
|
|
_timeout = seconds;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::send()
|
|
{
|
|
AHTTP_LOGDEBUG("send()");
|
|
|
|
MUTEX_LOCK(false)
|
|
|
|
if ( ! _buildRequest())
|
|
return false;
|
|
|
|
_send();
|
|
|
|
_unlock;
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::send(String body)
|
|
{
|
|
AHTTP_LOGDEBUG3("send(String)", body.substring(0, 16).c_str(), ", length =", body.length());
|
|
|
|
MUTEX_LOCK(false)
|
|
|
|
_addHeader("Content-Length", String(body.length()).c_str());
|
|
|
|
if ( ! _buildRequest())
|
|
{
|
|
_unlock;
|
|
|
|
return false;
|
|
}
|
|
|
|
_request->write(body);
|
|
_send();
|
|
|
|
_unlock;
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::send(const char* body)
|
|
{
|
|
AHTTP_LOGDEBUG3("send(char)", body, ", length =", strlen(body));
|
|
|
|
MUTEX_LOCK(false)
|
|
|
|
_addHeader("Content-Length", String(strlen(body)).c_str());
|
|
|
|
if ( ! _buildRequest())
|
|
{
|
|
_unlock;
|
|
|
|
return false;
|
|
}
|
|
|
|
_request->write(body);
|
|
_send();
|
|
|
|
_unlock;
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::send(const uint8_t* body, size_t len)
|
|
{
|
|
AHTTP_LOGDEBUG3("send(char)", (char*) body, ", length =", len);
|
|
|
|
MUTEX_LOCK(false)
|
|
|
|
_addHeader("Content-Length", String(len).c_str());
|
|
|
|
if ( ! _buildRequest())
|
|
{
|
|
_unlock;
|
|
|
|
return false;
|
|
}
|
|
|
|
_request->write(body, len);
|
|
_send();
|
|
|
|
_unlock;
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::send(xbuf* body, size_t len)
|
|
{
|
|
AHTTP_LOGDEBUG3("send(char)", body->peekString(16).c_str(), ", length =", len);
|
|
|
|
MUTEX_LOCK(false)
|
|
|
|
_addHeader("Content-Length", String(len).c_str());
|
|
|
|
if ( ! _buildRequest())
|
|
{
|
|
_unlock;
|
|
|
|
return false;
|
|
}
|
|
|
|
_request->write(body, len);
|
|
_send();
|
|
|
|
_unlock;
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::abort()
|
|
{
|
|
AHTTP_LOGDEBUG("abort()");
|
|
|
|
if (! _client)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MUTEX_LOCK_NR
|
|
|
|
_client->abort();
|
|
|
|
_unlock;
|
|
}
|
|
//**************************************************************************************************************
|
|
reqStates AsyncHTTPRequest::readyState()
|
|
{
|
|
return _readyState;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
int AsyncHTTPRequest::responseHTTPcode()
|
|
{
|
|
return _HTTPcode;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
String AsyncHTTPRequest::responseText()
|
|
{
|
|
AHTTP_LOGDEBUG("responseText()");
|
|
|
|
MUTEX_LOCK(String())
|
|
|
|
if ( ! _response || _readyState < readyStateLoading || ! available())
|
|
{
|
|
AHTTP_LOGDEBUG("responseText() no data");
|
|
|
|
_unlock;
|
|
|
|
return String();
|
|
}
|
|
|
|
String localString;
|
|
size_t avail = available();
|
|
|
|
if ( ! localString.reserve(avail))
|
|
{
|
|
AHTTP_LOGDEBUG("responseText() no buffer");
|
|
|
|
_HTTPcode = HTTPCODE_TOO_LESS_RAM;
|
|
_client->abort();
|
|
|
|
_unlock;
|
|
|
|
return String();
|
|
}
|
|
|
|
localString = _response->readString(avail);
|
|
_contentRead += localString.length();
|
|
|
|
AHTTP_LOGDEBUG3("responseText(char)", localString.substring(0, 16).c_str(), ", avail =", avail);
|
|
|
|
_unlock;
|
|
|
|
return localString;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
size_t AsyncHTTPRequest::responseRead(uint8_t* buf, size_t len)
|
|
{
|
|
if ( ! _response || _readyState < readyStateLoading || ! available())
|
|
{
|
|
AHTTP_LOGDEBUG("responseRead() no data");
|
|
|
|
return 0;
|
|
}
|
|
|
|
MUTEX_LOCK(0)
|
|
|
|
size_t avail = available() > len ? len : available();
|
|
_response->read(buf, avail);
|
|
|
|
AHTTP_LOGDEBUG3("responseRead(char)", (char*) buf, ", avail =", avail);
|
|
|
|
_contentRead += avail;
|
|
|
|
_unlock;
|
|
|
|
return avail;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
size_t AsyncHTTPRequest::available()
|
|
{
|
|
if (_readyState < readyStateLoading)
|
|
return 0;
|
|
|
|
if (_chunked && (_contentLength - _contentRead) < _response->available())
|
|
{
|
|
return _contentLength - _contentRead;
|
|
}
|
|
|
|
return _response->available();
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
size_t AsyncHTTPRequest::responseLength()
|
|
{
|
|
if (_readyState < readyStateLoading)
|
|
return 0;
|
|
|
|
return _contentLength;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::onData(onDataCB cb, void* arg)
|
|
{
|
|
AHTTP_LOGDEBUG("onData() CB set");
|
|
|
|
_onDataCB = cb;
|
|
_onDataCBarg = arg;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
uint32_t AsyncHTTPRequest::elapsedTime()
|
|
{
|
|
if (_readyState <= readyStateOpened)
|
|
return 0;
|
|
|
|
if (_readyState != readyStateDone)
|
|
{
|
|
return millis() - _requestStartTime;
|
|
}
|
|
|
|
return _requestEndTime - _requestStartTime;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
String AsyncHTTPRequest::version()
|
|
{
|
|
return String(ASYNC_HTTP_REQUEST_GENERIC_VERSION);
|
|
}
|
|
|
|
/*______________________________________________________________________________________________________________
|
|
|
|
PPPP RRRR OOO TTTTT EEEEE CCC TTTTT EEEEE DDDD
|
|
P P R R O O T E C C T E D D
|
|
PPPP RRRR O O T EEE C T EEE D D
|
|
P R R O O T E C C T E D D
|
|
P R R OOO T EEEEE CCC T EEEEE DDDD
|
|
_______________________________________________________________________________________________________________*/
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::_parseURL(const char* url)
|
|
{
|
|
return _parseURL(String(url));
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::_parseURL(String url)
|
|
{
|
|
SAFE_DELETE(_URL)
|
|
|
|
int hostBeg = 0;
|
|
|
|
_URL = new URL;
|
|
|
|
if (_URL)
|
|
{
|
|
_URL->scheme = new char[8];
|
|
|
|
if (! (_URL->scheme) )
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
strcpy(_URL->scheme, "HTTP://");
|
|
|
|
if (url.substring(0, 7).equalsIgnoreCase("HTTP://"))
|
|
{
|
|
hostBeg += 7;
|
|
}
|
|
else if (url.substring(0, 8).equalsIgnoreCase("HTTPS://"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int pathBeg = url.indexOf('/', hostBeg);
|
|
|
|
if (pathBeg < 0)
|
|
return false;
|
|
|
|
int hostEnd = pathBeg;
|
|
int portBeg = url.indexOf(':', hostBeg);
|
|
|
|
if (portBeg > 0 && portBeg < pathBeg)
|
|
{
|
|
_URL->port = url.substring(portBeg + 1, pathBeg).toInt();
|
|
hostEnd = portBeg;
|
|
}
|
|
|
|
_URL->host = new char[hostEnd - hostBeg + 1];
|
|
|
|
if (_URL->host == nullptr)
|
|
return false;
|
|
|
|
strcpy(_URL->host, url.substring(hostBeg, hostEnd).c_str());
|
|
|
|
int queryBeg = url.indexOf('?');
|
|
|
|
if (queryBeg < 0)
|
|
queryBeg = url.length();
|
|
|
|
_URL->path = new char[queryBeg - pathBeg + 1];
|
|
|
|
if (_URL->path == nullptr)
|
|
return false;
|
|
|
|
strcpy(_URL->path, url.substring(pathBeg, queryBeg).c_str());
|
|
|
|
_URL->query = new char[url.length() - queryBeg + 1];
|
|
|
|
if (_URL->query == nullptr)
|
|
return false;
|
|
|
|
strcpy(_URL->query, url.substring(queryBeg).c_str());
|
|
|
|
AHTTP_LOGDEBUG2("_parseURL(): scheme+host", _URL->scheme, _URL->host);
|
|
AHTTP_LOGDEBUG3("_parseURL(): port+path+query", _URL->port, _URL->path, _URL->query);
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::_connect()
|
|
{
|
|
AHTTP_LOGDEBUG("_connect()");
|
|
|
|
if ( ! _client)
|
|
{
|
|
_client = new AsyncClient();
|
|
|
|
if (! _client)
|
|
return false;
|
|
}
|
|
|
|
SAFE_DELETE_ARRAY(_connectedHost)
|
|
|
|
_connectedHost = new char[strlen(_URL->host) + 1];
|
|
|
|
if (_connectedHost == nullptr)
|
|
return false;
|
|
|
|
strcpy(_connectedHost, _URL->host);
|
|
_connectedPort = _URL->port;
|
|
|
|
_client->onConnect([](void *obj, AsyncClient * client)
|
|
{
|
|
((AsyncHTTPRequest*)(obj))->_onConnect(client);
|
|
}, this);
|
|
|
|
_client->onDisconnect([](void *obj, AsyncClient * client)
|
|
{
|
|
((AsyncHTTPRequest*)(obj))->_onDisconnect(client);
|
|
}, this);
|
|
|
|
_client->onPoll([](void *obj, AsyncClient * client)
|
|
{
|
|
((AsyncHTTPRequest*)(obj))->_onPoll(client);
|
|
}, this);
|
|
|
|
_client->onError([](void *obj, AsyncClient * client, uint32_t error)
|
|
{
|
|
((AsyncHTTPRequest*)(obj))->_onError(client, error);
|
|
}, this);
|
|
|
|
if ( ! _client->connected())
|
|
{
|
|
if ( ! _client->connect(_URL->host, _URL->port))
|
|
{
|
|
AHTTP_LOGDEBUG3("client.connect failed:", _URL->host, ",", _URL->port);
|
|
|
|
_HTTPcode = HTTPCODE_NOT_CONNECTED;
|
|
_setReadyState(readyStateDone);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_onConnect(_client);
|
|
}
|
|
|
|
_lastActivity = millis();
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::_buildRequest()
|
|
{
|
|
AHTTP_LOGDEBUG("_buildRequest()");
|
|
|
|
// Build the header.
|
|
if ( ! _request)
|
|
{
|
|
_request = new xbuf;
|
|
|
|
if ( ! _request)
|
|
return false;
|
|
}
|
|
|
|
|
|
// New in v1.1.0
|
|
_request->write(_HTTPmethodStringwithSpace[_HTTPmethod]);
|
|
//////
|
|
|
|
_request->write(_URL->path);
|
|
_request->write(_URL->query);
|
|
_request->write(" HTTP/1.1\r\n");
|
|
|
|
// New in v1.1.0
|
|
AHTTP_LOGDEBUG3(_HTTPmethodStringwithSpace[_HTTPmethod], _URL->path, _URL->query, " HTTP/1.1\r\n" );
|
|
//////
|
|
|
|
SAFE_DELETE(_URL)
|
|
|
|
_URL = nullptr;
|
|
header* hdr = _headers;
|
|
|
|
while (hdr)
|
|
{
|
|
_request->write(hdr->name);
|
|
_request->write(':');
|
|
_request->write(hdr->value);
|
|
_request->write("\r\n");
|
|
|
|
AHTTP_LOGDEBUG3(hdr->name, ":", hdr->value, "\r\n" );
|
|
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
SAFE_DELETE(_headers)
|
|
|
|
_headers = nullptr;
|
|
_request->write("\r\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
size_t AsyncHTTPRequest::_send()
|
|
{
|
|
if ( ! _request)
|
|
return 0;
|
|
|
|
AHTTP_LOGDEBUG1("_send(), _request->available =", _request->available());
|
|
|
|
if ( ! _client->connected() || ! _client->canSend())
|
|
{
|
|
AHTTP_LOGDEBUG("*can't send");
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t supply = _request->available();
|
|
size_t demand = _client->space();
|
|
|
|
if (supply > demand)
|
|
supply = demand;
|
|
|
|
size_t sent = 0;
|
|
uint8_t* temp = new uint8_t[100];
|
|
|
|
if (!temp)
|
|
return 0;
|
|
|
|
while (supply)
|
|
{
|
|
size_t chunk = supply < 100 ? supply : 100;
|
|
|
|
supply -= _request->read(temp, chunk);
|
|
sent += _client->add((char*)temp, chunk);
|
|
}
|
|
|
|
// KH, Must be delete [] temp;
|
|
SAFE_DELETE_ARRAY(temp)
|
|
|
|
if (_request->available() == 0)
|
|
{
|
|
//delete _request;
|
|
SAFE_DELETE(_request)
|
|
|
|
_request = nullptr;
|
|
}
|
|
|
|
_client->send();
|
|
|
|
AHTTP_LOGDEBUG1("*send", sent);
|
|
|
|
_lastActivity = millis();
|
|
|
|
return sent;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::_setReadyState(reqStates newState)
|
|
{
|
|
if (_readyState != newState)
|
|
{
|
|
_readyState = newState;
|
|
|
|
AHTTP_LOGDEBUG1("_setReadyState :", _readyState);
|
|
|
|
if (_readyStateChangeCB)
|
|
{
|
|
_readyStateChangeCB(_readyStateChangeCBarg, this, _readyState);
|
|
}
|
|
}
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::_processChunks()
|
|
{
|
|
while (_chunks->available())
|
|
{
|
|
AHTTP_LOGDEBUG3("_processChunks()", _chunks->peekString(16).c_str(), ", chunks available =", _chunks->available());
|
|
|
|
size_t _chunkRemaining = _contentLength - _contentRead - _response->available();
|
|
_chunkRemaining -= _response->write(_chunks, _chunkRemaining);
|
|
|
|
if (_chunks->indexOf("\r\n") == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
String chunkHeader = _chunks->readStringUntil("\r\n");
|
|
|
|
AHTTP_LOGDEBUG3("*getChunkHeader", chunkHeader.c_str(), ", chunkHeader length =", chunkHeader.length());
|
|
|
|
size_t chunkLength = strtol(chunkHeader.c_str(), nullptr, 16);
|
|
_contentLength += chunkLength;
|
|
|
|
if (chunkLength == 0)
|
|
{
|
|
char* connectionHdr = respHeaderValue("connection");
|
|
|
|
if (connectionHdr && (strcasecmp_P(connectionHdr, PSTR("disconnect")) == 0))
|
|
{
|
|
AHTTP_LOGDEBUG("*all chunks received - closing TCP");
|
|
|
|
_client->close();
|
|
}
|
|
else
|
|
{
|
|
AHTTP_LOGDEBUG("*all chunks received - no disconnect");
|
|
}
|
|
|
|
_requestEndTime = millis();
|
|
_lastActivity = 0;
|
|
_timeout = 0;
|
|
_setReadyState(readyStateDone);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*______________________________________________________________________________________________________________
|
|
|
|
EEEEE V V EEEEE N N TTTTT H H AAA N N DDDD L EEEEE RRRR SSS
|
|
E V V E NN N T H H A A NN N D D L E R R S
|
|
EEE V V EEE N N N T HHHHH AAAAA N N N D D L EEE RRRR SSS
|
|
E V V E N NN T H H A A N NN D D L E R R S
|
|
EEEEE V EEEEE N N T H H A A N N DDDD LLLLL EEEEE R R SSS
|
|
_______________________________________________________________________________________________________________*/
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::_onConnect(AsyncClient* client)
|
|
{
|
|
AHTTP_LOGDEBUG("_onConnect handler");
|
|
|
|
MUTEX_LOCK_NR
|
|
|
|
_client = client;
|
|
_setReadyState(readyStateOpened);
|
|
|
|
_response = new xbuf;
|
|
|
|
if (!_response)
|
|
{
|
|
_unlock;
|
|
|
|
return;
|
|
}
|
|
|
|
_contentLength = 0;
|
|
_contentRead = 0;
|
|
_chunked = false;
|
|
|
|
_client->onAck([](void* obj, AsyncClient * client, size_t len, uint32_t time)
|
|
{
|
|
((AsyncHTTPRequest*)(obj))->_send();
|
|
}, this);
|
|
|
|
_client->onData([](void* obj, AsyncClient * client, void* data, size_t len)
|
|
{
|
|
((AsyncHTTPRequest*)(obj))->_onData(data, len);
|
|
}, this);
|
|
|
|
if (_client->canSend())
|
|
{
|
|
_send();
|
|
}
|
|
|
|
_lastActivity = millis();
|
|
|
|
_unlock;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::_onPoll(AsyncClient* client)
|
|
{
|
|
MUTEX_LOCK_NR
|
|
|
|
if (_timeout && (millis() - _lastActivity) > (_timeout * 1000))
|
|
{
|
|
_client->close();
|
|
_HTTPcode = HTTPCODE_TIMEOUT;
|
|
|
|
AHTTP_LOGDEBUG("_onPoll timeout");
|
|
}
|
|
|
|
if (_onDataCB && available())
|
|
{
|
|
_onDataCB(_onDataCBarg, this, available());
|
|
}
|
|
|
|
_unlock;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::_onError(AsyncClient* client, int8_t error)
|
|
{
|
|
AHTTP_LOGDEBUG1("_onError handler error =", error);
|
|
|
|
_HTTPcode = error;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::_onDisconnect(AsyncClient* client)
|
|
{
|
|
AHTTP_LOGDEBUG("\n_onDisconnect handler");
|
|
|
|
MUTEX_LOCK_NR
|
|
|
|
if (_readyState < readyStateOpened)
|
|
{
|
|
_HTTPcode = HTTPCODE_NOT_CONNECTED;
|
|
}
|
|
else if (_HTTPcode > 0 &&
|
|
(_readyState < readyStateHdrsRecvd || (_contentRead + _response->available()) < _contentLength))
|
|
{
|
|
_HTTPcode = HTTPCODE_CONNECTION_LOST;
|
|
}
|
|
|
|
SAFE_DELETE(_client)
|
|
|
|
_client = nullptr;
|
|
|
|
SAFE_DELETE_ARRAY(_connectedHost)
|
|
|
|
_connectedHost = nullptr;
|
|
|
|
_connectedPort = -1;
|
|
_requestEndTime = millis();
|
|
_lastActivity = 0;
|
|
_setReadyState(readyStateDone);
|
|
|
|
_unlock;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::_onData(void* Vbuf, size_t len)
|
|
{
|
|
AHTTP_LOGDEBUG3("_onData handler", (char*) Vbuf, ", len =", len);
|
|
|
|
MUTEX_LOCK_NR
|
|
|
|
_lastActivity = millis();
|
|
|
|
// Transfer data to xbuf
|
|
if (_chunks)
|
|
{
|
|
_chunks->write((uint8_t*)Vbuf, len);
|
|
_processChunks();
|
|
}
|
|
else
|
|
{
|
|
_response->write((uint8_t*)Vbuf, len);
|
|
}
|
|
|
|
// if headers not complete, collect them. If still not complete, just return.
|
|
if (_readyState == readyStateOpened)
|
|
{
|
|
if ( ! _collectHeaders())
|
|
{
|
|
_unlock;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If there's data in the buffer and not Done, advance readyState to Loading.
|
|
if (_response->available() && _readyState != readyStateDone)
|
|
{
|
|
_setReadyState(readyStateLoading);
|
|
}
|
|
|
|
// If not chunked and all data read, close it up.
|
|
if ( ! _chunked && (_response->available() + _contentRead) >= _contentLength)
|
|
{
|
|
char* connectionHdr = respHeaderValue("connection");
|
|
|
|
if (connectionHdr && (strcasecmp_P(connectionHdr, PSTR("disconnect")) == 0))
|
|
{
|
|
AHTTP_LOGDEBUG("*all data received - closing TCP");
|
|
|
|
_client->close();
|
|
}
|
|
else
|
|
{
|
|
AHTTP_LOGDEBUG("*all data received - no disconnect");
|
|
}
|
|
|
|
_requestEndTime = millis();
|
|
_lastActivity = 0;
|
|
_timeout = 0;
|
|
_setReadyState(readyStateDone);
|
|
}
|
|
|
|
// If onData callback requested, do so.
|
|
if (_onDataCB && available())
|
|
{
|
|
_onDataCB(_onDataCBarg, this, available());
|
|
}
|
|
|
|
_unlock;
|
|
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::_collectHeaders()
|
|
{
|
|
AHTTP_LOGDEBUG("_collectHeaders()");
|
|
|
|
// Loop to parse off each header line. Drop out and return false if no \r\n (incomplete)
|
|
do
|
|
{
|
|
String headerLine = _response->readStringUntil("\r\n");
|
|
|
|
// If no line, return false.
|
|
if ( ! headerLine.length())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If empty line, all headers are in, advance readyState.
|
|
if (headerLine.length() == 2)
|
|
{
|
|
_setReadyState(readyStateHdrsRecvd);
|
|
}
|
|
// If line is HTTP header, capture HTTPcode.
|
|
else if (headerLine.substring(0, 7) == "HTTP/1.")
|
|
{
|
|
_HTTPcode = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt();
|
|
}
|
|
// Ordinary header, add to header list.
|
|
else
|
|
{
|
|
int colon = headerLine.indexOf(':');
|
|
|
|
if (colon != -1)
|
|
{
|
|
String name = headerLine.substring(0, colon);
|
|
name.trim();
|
|
String value = headerLine.substring(colon + 1);
|
|
value.trim();
|
|
_addHeader(name.c_str(), value.c_str());
|
|
}
|
|
}
|
|
} while (_readyState == readyStateOpened);
|
|
|
|
// If content-Length header, set _contentLength
|
|
header *hdr = _getHeader("Content-Length");
|
|
|
|
if (hdr)
|
|
{
|
|
_contentLength = strtol(hdr->value, nullptr, 10);
|
|
}
|
|
|
|
// If chunked specified, try to set _contentLength to size of first chunk
|
|
hdr = _getHeader("Transfer-Encoding");
|
|
|
|
if (hdr && strcasecmp_P(hdr->value, PSTR("chunked")) == 0)
|
|
{
|
|
AHTTP_LOGDEBUG("*transfer-encoding: chunked");
|
|
|
|
_chunked = true;
|
|
_contentLength = 0;
|
|
_chunks = new xbuf;
|
|
_chunks->write(_response, _response->available());
|
|
_processChunks();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*_____________________________________________________________________________________________________________
|
|
|
|
H H EEEEE AAA DDDD EEEEE RRRR SSS
|
|
H H E A A D D E R R S
|
|
HHHHH EEE AAAAA D D EEE RRRR SSS
|
|
H H E A A D D E R R S
|
|
H H EEEEE A A DDDD EEEEE R R SSS
|
|
______________________________________________________________________________________________________________*/
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setReqHeader(const char* name, const char* value)
|
|
{
|
|
if (_readyState <= readyStateOpened && _headers)
|
|
{
|
|
_addHeader(name, value);
|
|
}
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setReqHeader(const char* name, int32_t value)
|
|
{
|
|
if (_readyState <= readyStateOpened && _headers)
|
|
{
|
|
setReqHeader(name, String(value).c_str());
|
|
}
|
|
}
|
|
|
|
#if (ESP32 || ESP8266)
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setReqHeader(const char* name, const __FlashStringHelper* value)
|
|
{
|
|
if (_readyState <= readyStateOpened && _headers)
|
|
{
|
|
char* _value = _charstar(value);
|
|
_addHeader(name, _value);
|
|
|
|
SAFE_DELETE_ARRAY(_value)
|
|
}
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setReqHeader(const __FlashStringHelper *name, const char* value)
|
|
{
|
|
if (_readyState <= readyStateOpened && _headers)
|
|
{
|
|
char* _name = _charstar(name);
|
|
_addHeader(_name, value);
|
|
|
|
SAFE_DELETE_ARRAY(_name)
|
|
}
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setReqHeader(const __FlashStringHelper *name, const __FlashStringHelper* value)
|
|
{
|
|
if (_readyState <= readyStateOpened && _headers)
|
|
{
|
|
char* _name = _charstar(name);
|
|
char* _value = _charstar(value);
|
|
_addHeader(_name, _value);
|
|
|
|
SAFE_DELETE_ARRAY(_name)
|
|
SAFE_DELETE_ARRAY(_value)
|
|
}
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
void AsyncHTTPRequest::setReqHeader(const __FlashStringHelper *name, int32_t value)
|
|
{
|
|
if (_readyState <= readyStateOpened && _headers)
|
|
{
|
|
char* _name = _charstar(name);
|
|
setReqHeader(_name, String(value).c_str());
|
|
|
|
SAFE_DELETE_ARRAY(_name)
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
//**************************************************************************************************************
|
|
int AsyncHTTPRequest::respHeaderCount()
|
|
{
|
|
if (_readyState < readyStateHdrsRecvd)
|
|
return 0;
|
|
|
|
int count = 0;
|
|
header* hdr = _headers;
|
|
|
|
while (hdr)
|
|
{
|
|
count++;
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
char* AsyncHTTPRequest::respHeaderName(int ndx)
|
|
{
|
|
if (_readyState < readyStateHdrsRecvd)
|
|
return nullptr;
|
|
|
|
header* hdr = _getHeader(ndx);
|
|
|
|
if ( ! hdr)
|
|
return nullptr;
|
|
|
|
return hdr->name;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
char* AsyncHTTPRequest::respHeaderValue(const char* name)
|
|
{
|
|
if (_readyState < readyStateHdrsRecvd)
|
|
return nullptr;
|
|
|
|
header* hdr = _getHeader(name);
|
|
|
|
if ( ! hdr)
|
|
return nullptr;
|
|
|
|
return hdr->value;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
char* AsyncHTTPRequest::respHeaderValue(int ndx)
|
|
{
|
|
if (_readyState < readyStateHdrsRecvd)
|
|
return nullptr;
|
|
|
|
header* hdr = _getHeader(ndx);
|
|
|
|
if ( ! hdr)
|
|
return nullptr;
|
|
|
|
return hdr->value;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::respHeaderExists(const char* name)
|
|
{
|
|
if (_readyState < readyStateHdrsRecvd)
|
|
return false;
|
|
|
|
header* hdr = _getHeader(name);
|
|
|
|
if ( ! hdr)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#if (ESP32 || ESP8266)
|
|
|
|
//**************************************************************************************************************
|
|
char* AsyncHTTPRequest::respHeaderValue(const __FlashStringHelper *name)
|
|
{
|
|
if (_readyState < readyStateHdrsRecvd)
|
|
return nullptr;
|
|
|
|
char* _name = _charstar(name);
|
|
header* hdr = _getHeader(_name);
|
|
|
|
SAFE_DELETE_ARRAY(_name)
|
|
|
|
if ( ! hdr)
|
|
return nullptr;
|
|
|
|
return hdr->value;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
bool AsyncHTTPRequest::respHeaderExists(const __FlashStringHelper *name)
|
|
{
|
|
if (_readyState < readyStateHdrsRecvd)
|
|
return false;
|
|
|
|
char* _name = _charstar(name);
|
|
header* hdr = _getHeader(_name);
|
|
|
|
SAFE_DELETE_ARRAY(_name)
|
|
|
|
if ( ! hdr)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
//**************************************************************************************************************
|
|
String AsyncHTTPRequest::headers()
|
|
{
|
|
MUTEX_LOCK(String())
|
|
|
|
String _response = "";
|
|
header* hdr = _headers;
|
|
|
|
while (hdr)
|
|
{
|
|
_response += hdr->name;
|
|
_response += ':';
|
|
_response += hdr->value;
|
|
_response += "\r\n";
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
_response += "\r\n";
|
|
|
|
_unlock;
|
|
|
|
return _response;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
AsyncHTTPRequest::header* AsyncHTTPRequest::_addHeader(const char* name, const char* value)
|
|
{
|
|
MUTEX_LOCK(nullptr)
|
|
|
|
header* hdr = (header*) &_headers;
|
|
|
|
while (hdr->next)
|
|
{
|
|
if (strcasecmp(name, hdr->next->name) == 0)
|
|
{
|
|
header* oldHdr = hdr->next;
|
|
hdr->next = hdr->next->next;
|
|
oldHdr->next = nullptr;
|
|
|
|
SAFE_DELETE(oldHdr)
|
|
}
|
|
else
|
|
{
|
|
hdr = hdr->next;
|
|
}
|
|
}
|
|
|
|
hdr->next = new header;
|
|
|
|
if (hdr->next)
|
|
{
|
|
hdr->next->name = new char[strlen(name) + 1];
|
|
|
|
if (hdr->next->name)
|
|
strcpy(hdr->next->name, name);
|
|
else
|
|
{
|
|
SAFE_DELETE(hdr->next)
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
hdr->next->value = new char[strlen(value) + 1];
|
|
|
|
if (hdr->next->value)
|
|
strcpy(hdr->next->value, value);
|
|
else
|
|
{
|
|
SAFE_DELETE_ARRAY(hdr->next->name)
|
|
SAFE_DELETE(hdr->next)
|
|
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
_unlock;
|
|
|
|
return hdr->next;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
AsyncHTTPRequest::header* AsyncHTTPRequest::_getHeader(const char* name)
|
|
{
|
|
MUTEX_LOCK(nullptr)
|
|
|
|
header* hdr = _headers;
|
|
|
|
while (hdr)
|
|
{
|
|
if (strcasecmp(name, hdr->name) == 0)
|
|
break;
|
|
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
_unlock;
|
|
|
|
return hdr;
|
|
}
|
|
|
|
//**************************************************************************************************************
|
|
AsyncHTTPRequest::header* AsyncHTTPRequest::_getHeader(int ndx)
|
|
{
|
|
MUTEX_LOCK(nullptr)
|
|
|
|
header* hdr = _headers;
|
|
|
|
while (hdr)
|
|
{
|
|
if ( ! ndx--)
|
|
break;
|
|
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
_unlock;
|
|
|
|
return hdr;
|
|
}
|
|
|
|
#if (ESP32 || ESP8266)
|
|
|
|
//**************************************************************************************************************
|
|
char* AsyncHTTPRequest::_charstar(const __FlashStringHelper * str)
|
|
{
|
|
if ( ! str)
|
|
return nullptr;
|
|
|
|
char* ptr = new char[strlen_P((PGM_P)str) + 1];
|
|
|
|
if (ptr)
|
|
{
|
|
strcpy_P(ptr, (PGM_P)str);
|
|
}
|
|
|
|
// Rturn good ptr or nullptr
|
|
return ptr;
|
|
}
|
|
|
|
#endif
|