Skip to content

Movable HTTPClient and fixing WiFiClient copy #8237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 13, 2021
50 changes: 18 additions & 32 deletions libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@
#include <StreamDev.h>
#include <base64.h>

// per https://github.com/esp8266/Arduino/issues/8231
// make sure HTTPClient can be utilized as a movable class member
static_assert(std::is_default_constructible_v<HTTPClient>, "");
static_assert(!std::is_copy_constructible_v<HTTPClient>, "");
static_assert(std::is_move_constructible_v<HTTPClient>, "");
static_assert(std::is_move_assignable_v<HTTPClient>, "");

static const char defaultUserAgentPstr[] PROGMEM = "ESP8266HTTPClient";
const String HTTPClient::defaultUserAgent = defaultUserAgentPstr;

static int StreamReportToHttpClientReport (Stream::Report streamSendError)
{
switch (streamSendError)
Expand All @@ -41,27 +51,6 @@ static int StreamReportToHttpClientReport (Stream::Report streamSendError)
return 0; // never reached, keep gcc quiet
}

/**
* constructor
*/
HTTPClient::HTTPClient()
: _client(nullptr), _userAgent(F("ESP8266HTTPClient"))
{
}

/**
* destructor
*/
HTTPClient::~HTTPClient()
{
if(_client) {
_client->stop();
}
if(_currentHeaders) {
delete[] _currentHeaders;
}
}

void HTTPClient::clear()
{
_returnCode = 0;
Expand All @@ -80,8 +69,6 @@ void HTTPClient::clear()
* @return success bool
*/
bool HTTPClient::begin(WiFiClient &client, const String& url) {
_client = &client;

// check for : (http: or https:)
int index = url.indexOf(':');
if(index < 0) {
Expand All @@ -97,6 +84,8 @@ bool HTTPClient::begin(WiFiClient &client, const String& url) {
}

_port = (protocol == "https" ? 443 : 80);
_client = client.clone();

return beginInternal(url, protocol.c_str());
}

Expand All @@ -112,7 +101,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& url) {
*/
bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, const String& uri, bool https)
{
_client = &client;
_client = client.clone();

clear();
_host = host;
Expand Down Expand Up @@ -462,7 +451,7 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
}

// transfer all of it, with send-timeout
if (size && StreamConstPtr(payload, size).sendAll(_client) != size)
if (size && StreamConstPtr(payload, size).sendAll(_client.get()) != size)
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);

// handle Server Response (Header)
Expand Down Expand Up @@ -563,7 +552,7 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
}

// transfer all of it, with timeout
size_t transferred = stream->sendSize(_client, size);
size_t transferred = stream->sendSize(_client.get(), size);
if (transferred != size)
{
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", size, transferred);
Expand Down Expand Up @@ -613,7 +602,7 @@ WiFiClient& HTTPClient::getStream(void)
WiFiClient* HTTPClient::getStreamPtr(void)
{
if(connected()) {
return _client;
return _client.get();
}

DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n");
Expand Down Expand Up @@ -818,10 +807,7 @@ void HTTPClient::addHeader(const String& name, const String& value, bool first,
void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount)
{
_headerKeysCount = headerKeysCount;
if(_currentHeaders) {
delete[] _currentHeaders;
}
_currentHeaders = new RequestArgument[_headerKeysCount];
_currentHeaders = std::make_unique<RequestArgument[]>(_headerKeysCount);
for(size_t i = 0; i < _headerKeysCount; i++) {
_currentHeaders[i].key = headerKeys[i];
}
Expand Down Expand Up @@ -963,7 +949,7 @@ bool HTTPClient::sendHeader(const char * type)
DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());

// transfer all of it, with timeout
return StreamConstPtr(header).sendAll(_client) == header.length();
return StreamConstPtr(header).sendAll(_client.get()) == header.length();
}

/**
Expand Down
35 changes: 22 additions & 13 deletions libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
#ifndef ESP8266HTTPClient_H_
#define ESP8266HTTPClient_H_

#include <memory>
#include <Arduino.h>
#include <StreamString.h>
#include <WiFiClient.h>

#include <memory>

#ifdef DEBUG_ESP_HTTP_CLIENT
#ifdef DEBUG_ESP_PORT
#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
Expand Down Expand Up @@ -151,13 +152,12 @@ typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
class HTTPClient
{
public:
HTTPClient();
~HTTPClient();
HTTPClient() = default;
~HTTPClient() = default;
HTTPClient(HTTPClient&&) = default;
HTTPClient& operator=(HTTPClient&&) = default;

/*
* 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
*/
// Note that WiFiClient instance *will* be captured by the internal handler.
bool begin(WiFiClient &client, const String& url);
bool begin(WiFiClient &client, const String& host, uint16_t port, const String& uri = "/", bool https = false);

Expand Down Expand Up @@ -226,6 +226,15 @@ class HTTPClient
String value;
};

// TODO: the common pattern to use the class is to
// {
// WiFiClient socket;
// HTTPClient http;
// http.begin(socket, "http://blahblah");
// }
// in case wificlient supports seamless ref() / unref() of the underlying connection
// for both wificlient and wificlientsecure, this may be removed in favour of that approach.

bool beginInternal(const String& url, const char* expectedProtocol);
void disconnect(bool preserveClient = false);
void clear();
Expand All @@ -235,7 +244,7 @@ class HTTPClient
int handleHeaderResponse();
int writeToStreamDataBlock(Stream * stream, int len);

WiFiClient* _client;
std::unique_ptr<WiFiClient> _client;

/// request handling
String _host;
Expand All @@ -247,12 +256,14 @@ class HTTPClient
String _uri;
String _protocol;
String _headers;
String _userAgent;
String _base64Authorization;

static const String defaultUserAgent;
String _userAgent = defaultUserAgent;

/// Response handling
RequestArgument* _currentHeaders = nullptr;
size_t _headerKeysCount = 0;
std::unique_ptr<RequestArgument[]> _currentHeaders;
size_t _headerKeysCount = 0;

int _returnCode = 0;
int _size = -1;
Expand All @@ -264,6 +275,4 @@ class HTTPClient
std::unique_ptr<StreamString> _payload;
};



#endif /* ESP8266HTTPClient_H_ */
4 changes: 4 additions & 0 deletions libraries/ESP8266WiFi/src/WiFiClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ WiFiClient::~WiFiClient()
_client->unref();
}

std::unique_ptr<WiFiClient> WiFiClient::clone() const {
return std::make_unique<WiFiClient>(*this);
}

WiFiClient::WiFiClient(const WiFiClient& other)
{
_client = other._client;
Expand Down
6 changes: 6 additions & 0 deletions libraries/ESP8266WiFi/src/WiFiClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class WiFiClient : public Client, public SList<WiFiClient> {
WiFiClient(const WiFiClient&);
WiFiClient& operator=(const WiFiClient&);

// b/c wificlient is both a real class and the virtual base for secure client,
// make sure there's a safe way to clone the object without accidentally 'slicing' it
// (...which will happen when using normal copy ctor with the dereferenced pointer...)
// TODO: but, actually, implement secure handlers in the ClientContext instead, so normal copy works
virtual std::unique_ptr<WiFiClient> clone() const;

virtual uint8_t status();
virtual int connect(IPAddress ip, uint16_t port) override;
virtual int connect(const char *host, uint16_t port) override;
Expand Down
8 changes: 8 additions & 0 deletions libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class WiFiClientSecureCtx : public WiFiClient {

WiFiClientSecureCtx& operator=(const WiFiClientSecureCtx&) = delete;

// TODO: usage is invalid invalid, but this will trigger an error only when
// it is actually used by something and `=delete`ed ctor above is accessed
std::unique_ptr<WiFiClient> clone() const override {
return std::unique_ptr<WiFiClient>(new WiFiClientSecureCtx(*this));
}

int connect(IPAddress ip, uint16_t port) override;
int connect(const String& host, uint16_t port) override;
int connect(const char* name, uint16_t port) override;
Expand Down Expand Up @@ -239,6 +245,8 @@ class WiFiClientSecure : public WiFiClient {

WiFiClientSecure& operator=(const WiFiClientSecure&) = default; // The shared-ptrs handle themselves automatically

std::unique_ptr<WiFiClient> clone() const override { return std::unique_ptr<WiFiClient>(new WiFiClientSecure(*this)); }

uint8_t status() override { return _ctx->status(); }
int connect(IPAddress ip, uint16_t port) override { return _ctx->connect(ip, port); }
int connect(const String& host, uint16_t port) override { return _ctx->connect(host, port); }
Expand Down