Feature/http client (#1973)

* Pass client parameter into two new begin() functions. Set other begin() functions deprecated. Updated library version to 1.2

* Added working HTTPS example on a public url with a certificate

* Remove two unnecessary tests in ::disconnect()

* Add a scoping block to BasicHttpsClient.ino to assure HTTPClient is destroyed before WiFiClientSecure

* Added check to handle mixup of old and present api properly

* Correct HTTPClient::setTimeout() to convert milliseconds to seconds. Correct WiFiClient::setTimeout() to call Stream::setTimeout() with seconds converted back to milliseconds. Remove inproper checks for _insecure.

* Added small comment because it looked like the Travis build did not finish
This commit is contained in:
Jeroen88
2018-11-19 16:57:23 +01:00
committed by Me No Dev
parent b70737d276
commit 01d22c8807
5 changed files with 351 additions and 51 deletions

View File

@ -22,17 +22,23 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Adapted in October 2018
*/
#include <Arduino.h>
#include <esp32-hal-log.h>
#ifdef HTTPCLIENT_1_1_COMPATIBLE
#include <WiFi.h>
#include <WiFiClientSecure.h>
#endif
#include <StreamString.h>
#include <base64.h>
#include "HTTPClient.h"
#ifdef HTTPCLIENT_1_1_COMPATIBLE
class TransportTraits
{
public:
@ -78,6 +84,7 @@ protected:
const char* _clicert;
const char* _clikey;
};
#endif // HTTPCLIENT_1_1_COMPATIBLE
/**
* constructor
@ -91,8 +98,8 @@ HTTPClient::HTTPClient()
*/
HTTPClient::~HTTPClient()
{
if(_tcp) {
_tcp->stop();
if(_client) {
_client->stop();
}
if(_currentHeaders) {
delete[] _currentHeaders;
@ -107,9 +114,81 @@ void HTTPClient::clear()
}
/**
* parsing the url for all needed parameters
* @param client Client&
* @param url String
* @param https bool
* @return success bool
*/
bool HTTPClient::begin(WiFiClient &client, String url) {
#ifdef HTTPCLIENT_1_1_COMPATIBLE
if(_tcpDeprecated) {
log_d("mix up of new and deprecated api");
_canReuse = false;
end();
}
#endif
_client = &client;
// check for : (http: or https:)
int index = url.indexOf(':');
if(index < 0) {
log_d("failed to parse protocol");
return false;
}
String protocol = url.substring(0, index);
if(protocol != "http" && protocol != "https") {
log_d("unknown protocol '%s'", protocol.c_str());
return false;
}
_port = (protocol == "https" ? 443 : 80);
return beginInternal(url, protocol.c_str());
}
/**
* directly supply all needed parameters
* @param client Client&
* @param host String
* @param port uint16_t
* @param uri String
* @param https bool
* @return success bool
*/
bool HTTPClient::begin(WiFiClient &client, String host, uint16_t port, String uri, bool https)
{
#ifdef HTTPCLIENT_1_1_COMPATIBLE
if(_tcpDeprecated) {
log_d("mix up of new and deprecated api");
_canReuse = false;
end();
}
#endif
_client = &client;
clear();
_host = host;
_port = port;
_uri = uri;
_protocol = (https ? "https" : "http");
return true;
}
#ifdef HTTPCLIENT_1_1_COMPATIBLE
bool HTTPClient::begin(String url, const char* CAcert)
{
_transportTraits.reset(nullptr);
if(_client && !_tcpDeprecated) {
log_d("mix up of new and deprecated api");
_canReuse = false;
end();
}
_port = 443;
if (!beginInternal(url, "https")) {
return false;
@ -125,8 +204,12 @@ bool HTTPClient::begin(String url, const char* CAcert)
*/
bool HTTPClient::begin(String url)
{
if(_client && !_tcpDeprecated) {
log_d("mix up of new and deprecated api");
_canReuse = false;
end();
}
_transportTraits.reset(nullptr);
_port = 80;
if (!beginInternal(url, "http")) {
return begin(url, (const char*)NULL);
@ -134,6 +217,7 @@ bool HTTPClient::begin(String url)
_transportTraits = TransportTraitsPtr(new TransportTraits());
return true;
}
#endif // HTTPCLIENT_1_1_COMPATIBLE
bool HTTPClient::beginInternal(String url, const char* expectedProtocol)
{
@ -182,8 +266,15 @@ bool HTTPClient::beginInternal(String url, const char* expectedProtocol)
return true;
}
#ifdef HTTPCLIENT_1_1_COMPATIBLE
bool HTTPClient::begin(String host, uint16_t port, String uri)
{
if(_client && !_tcpDeprecated) {
log_d("mix up of new and deprecated api");
_canReuse = false;
end();
}
clear();
_host = host;
_port = port;
@ -195,6 +286,12 @@ bool HTTPClient::begin(String host, uint16_t port, String uri)
bool HTTPClient::begin(String host, uint16_t port, String uri, const char* CAcert)
{
if(_client && !_tcpDeprecated) {
log_d("mix up of new and deprecated api");
_canReuse = false;
end();
}
clear();
_host = host;
_port = port;
@ -210,6 +307,12 @@ bool HTTPClient::begin(String host, uint16_t port, String uri, const char* CAcer
bool HTTPClient::begin(String host, uint16_t port, String uri, const char* CAcert, const char* cli_cert, const char* cli_key)
{
if(_client && !_tcpDeprecated) {
log_d("mix up of new and deprecated api");
_canReuse = false;
end();
}
clear();
_host = host;
_port = port;
@ -222,37 +325,60 @@ bool HTTPClient::begin(String host, uint16_t port, String uri, const char* CAcer
_transportTraits = TransportTraitsPtr(new TLSTraits(CAcert, cli_cert, cli_key));
return true;
}
#endif // HTTPCLIENT_1_1_COMPATIBLE
/**
* end
* called after the payload is handled
*/
void HTTPClient::end(void)
{
disconnect();
}
/**
* disconnect
* close the TCP socket
*/
void HTTPClient::disconnect()
{
if(connected()) {
if(_tcp->available() > 0) {
log_d("still data in buffer (%d), clean up.", _tcp->available());
_tcp->flush();
if(_client->available() > 0) {
log_d("still data in buffer (%d), clean up.\n", _client->available());
while(_client->available() > 0) {
_client->read();
}
}
if(_reuse && _canReuse) {
log_d("tcp keep open for reuse");
log_d("tcp keep open for reuse\n");
} else {
log_d("tcp stop");
_tcp->stop();
log_d("tcp stop\n");
_client->stop();
_client = nullptr;
#ifdef HTTPCLIENT_1_1_COMPATIBLE
if(_tcpDeprecated) {
_transportTraits.reset(nullptr);
_tcpDeprecated.reset(nullptr);
}
#endif
}
} else {
log_v("tcp is closed");
log_d("tcp is closed\n");
}
}
/**
* connected
* @return connected status
*/
bool HTTPClient::connected()
{
if(_tcp) {
return ((_tcp->available() > 0) || _tcp->connected());
if(_client) {
return ((_client->available() > 0) || _client->connected());
}
return false;
}
@ -309,8 +435,8 @@ void HTTPClient::setAuthorization(const char * auth)
void HTTPClient::setTimeout(uint16_t timeout)
{
_tcpTimeout = timeout;
if(connected() && !_secure) {
_tcp->setTimeout(timeout);
if(connected()) {
_client->setTimeout((timeout + 500) / 1000);
}
}
@ -398,7 +524,7 @@ int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
// send Payload if needed
if(payload && size > 0) {
if(_tcp->write(&payload[0], size) != size) {
if(_client->write(&payload[0], size) != size) {
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
}
@ -477,7 +603,7 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
int bytesRead = stream->readBytes(buff, readBytes);
// write it to Stream
int bytesWrite = _tcp->write((const uint8_t *) buff, bytesRead);
int bytesWrite = _client->write((const uint8_t *) buff, bytesRead);
bytesWritten += bytesWrite;
// are all Bytes a writen to stream ?
@ -485,11 +611,11 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
log_d("short write, asked for %d but got %d retry...", bytesRead, bytesWrite);
// check for write error
if(_tcp->getWriteError()) {
log_d("stream write error %d", _tcp->getWriteError());
if(_client->getWriteError()) {
log_d("stream write error %d", _client->getWriteError());
//reset write error for retry
_tcp->clearWriteError();
_client->clearWriteError();
}
// some time for the stream
@ -498,7 +624,7 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
int leftBytes = (readBytes - bytesWrite);
// retry to send the missed bytes
bytesWrite = _tcp->write((const uint8_t *) (buff + bytesWrite), leftBytes);
bytesWrite = _client->write((const uint8_t *) (buff + bytesWrite), leftBytes);
bytesWritten += bytesWrite;
if(bytesWrite != leftBytes) {
@ -510,8 +636,8 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
}
// check for write error
if(_tcp->getWriteError()) {
log_d("stream write error %d", _tcp->getWriteError());
if(_client->getWriteError()) {
log_d("stream write error %d", _client->getWriteError());
free(buff);
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
}
@ -561,8 +687,8 @@ int HTTPClient::getSize(void)
*/
WiFiClient& HTTPClient::getStream(void)
{
if (connected() && !_secure) {
return *_tcp;
if (connected()) {
return *_client;
}
log_w("getStream: not connected");
@ -577,7 +703,7 @@ WiFiClient& HTTPClient::getStream(void)
WiFiClient* HTTPClient::getStreamPtr(void)
{
if(connected()) {
return _tcp.get();
return _client;
}
log_w("getStreamPtr: not connected");
@ -617,7 +743,7 @@ int HTTPClient::writeToStream(Stream * stream)
if(!connected()) {
return returnError(HTTPC_ERROR_CONNECTION_LOST);
}
String chunkHeader = _tcp->readStringUntil('\n');
String chunkHeader = _client->readStringUntil('\n');
if(chunkHeader.length() <= 0) {
return returnError(HTTPC_ERROR_READ_TIMEOUT);
@ -654,7 +780,7 @@ int HTTPClient::writeToStream(Stream * stream)
// read trailing \r\n at the end of the chunk
char buf[2];
auto trailing_seq_len = _tcp->readBytes((uint8_t*)buf, 2);
auto trailing_seq_len = _client->readBytes((uint8_t*)buf, 2);
if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') {
return returnError(HTTPC_ERROR_READ_TIMEOUT);
}
@ -822,38 +948,46 @@ bool HTTPClient::connect(void)
if(connected()) {
log_d("already connected, try reuse!");
while(_tcp->available() > 0) {
_tcp->read();
while(_client->available() > 0) {
_client->read();
}
return true;
}
if (!_transportTraits) {
#ifdef HTTPCLIENT_1_1_COMPATIBLE
if(!_client) {
_tcpDeprecated = _transportTraits->create();
_client = _tcpDeprecated.get();
}
#endif
if (!_client) {
log_d("HTTPClient::begin was not called or returned error");
return false;
}
_tcp = _transportTraits->create();
// set Timeout for WiFiClient and for Stream::readBytesUntil() and Stream::readStringUntil()
_client->setTimeout((_tcpTimeout + 500) / 1000);
if (!_transportTraits->verify(*_tcp, _host.c_str())) {
log_d("transport level verify failed");
_tcp->stop();
return false;
}
if(!_tcp->connect(_host.c_str(), _port)) {
if(!_client->connect(_host.c_str(), _port)) {
log_d("failed connect to %s:%u", _host.c_str(), _port);
return false;
}
log_d(" connected to %s:%u", _host.c_str(), _port);
// set Timeout for readBytesUntil and readStringUntil
setTimeout(_tcpTimeout);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
if (_tcpDeprecated && !_transportTraits->verify(*_client, _host.c_str())) {
log_d("transport level verify failed");
_client->stop();
return false;
}
#endif
/*
#ifdef ESP8266
_tcp->setNoDelay(true);
_client->setNoDelay(true);
#endif
*/
return connected();
@ -907,7 +1041,7 @@ bool HTTPClient::sendHeader(const char * type)
header += _headers + "\r\n";
return (_tcp->write((const uint8_t *) header.c_str(), header.length()) == header.length());
return (_client->write((const uint8_t *) header.c_str(), header.length()) == header.length());
}
/**
@ -928,9 +1062,9 @@ int HTTPClient::handleHeaderResponse()
unsigned long lastDataTime = millis();
while(connected()) {
size_t len = _tcp->available();
size_t len = _client->available();
if(len > 0) {
String headerLine = _tcp->readStringUntil('\n');
String headerLine = _client->readStringUntil('\n');
headerLine.trim(); // remove \r
lastDataTime = millis();
@ -1026,7 +1160,7 @@ int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
while(connected() && (len > 0 || len == -1)) {
// get available data size
size_t sizeAvailable = _tcp->available();
size_t sizeAvailable = _client->available();
if(sizeAvailable) {
@ -1043,7 +1177,7 @@ int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
}
// read data
int bytesRead = _tcp->readBytes(buff, readBytes);
int bytesRead = _client->readBytes(buff, readBytes);
// write it to Stream
int bytesWrite = stream->write(buff, bytesRead);
@ -1124,7 +1258,7 @@ int HTTPClient::returnError(int error)
log_w("error(%d): %s", error, errorToString(error).c_str());
if(connected()) {
log_d("tcp stop");
_tcp->stop();
_client->stop();
}
}
return error;

View File

@ -27,6 +27,8 @@
#ifndef HTTPClient_H_
#define HTTPClient_H_
#define HTTPCLIENT_1_1_COMPATIBLE
#include <memory>
#include <Arduino.h>
#include <WiFiClient.h>
@ -117,8 +119,10 @@ typedef enum {
HTTPC_TE_CHUNKED
} transferEncoding_t;
#ifdef HTTPCLIENT_1_1_COMPATIBLE
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
#endif
class HTTPClient
{
@ -126,11 +130,20 @@ public:
HTTPClient();
~HTTPClient();
/*
* Since both begin() functions take a reference to client as a parameter, you need to
* ensure the client object lives the entire time of the HTTPClient
*/
bool begin(WiFiClient &client, String url);
bool begin(WiFiClient &client, String host, uint16_t port, String uri = "/", bool https = false);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
bool begin(String url);
bool begin(String url, const char* CAcert);
bool begin(String host, uint16_t port, String uri = "/");
bool begin(String host, uint16_t port, String uri, const char* CAcert);
bool begin(String host, uint16_t port, String uri, const char* CAcert, const char* cli_cert, const char* cli_key);
#endif
void end(void);
@ -181,6 +194,7 @@ protected:
};
bool beginInternal(String url, const char* expectedProtocol);
void disconnect();
void clear();
int returnError(int error);
bool connect(void);
@ -189,8 +203,12 @@ protected:
int writeToStreamDataBlock(Stream * stream, int len);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
TransportTraitsPtr _transportTraits;
std::unique_ptr<WiFiClient> _tcp;
std::unique_ptr<WiFiClient> _tcpDeprecated;
#endif
WiFiClient* _client;
/// request handling
String _host;