Skip to content

Commit 40b26b7

Browse files
authored
Movable HTTPClient and fixing WiFiClient copy (#8237)
- =default for default ctor, destructor, move ctor and the assignment move - use `std::unique_ptr<WiFiClient>` instead of raw pointer to the client - implement `virtual std::unique_ptr<WiFiClient> WiFiClient::clone()` to safely copy the WiFiClientSecure instance, without accidentally slicing it (i.e. using the pointer with incorrect type, calling base WiFiClient virtual methods) - replace headers pointer array with `std::unique_ptr<T[]>` to simplify the move operations - substitute userAgent with the default one when it is empty (may be a subject to change though, b/c now there is a global static `String`) Allow HTTPClient to be placed inside of movable classes (e.g. std::optional, requested in the linked issue) or to be returned from functions. Class logic stays as-is, only the underlying member types are changed. Notice that WiFiClient connection object is now copied, and the internal ClientContext will be preserved even after the original WiFiClient object was destroyed. replaces #8236 resolves #8231 and, possibly #5734
1 parent b7a2f44 commit 40b26b7

File tree

5 files changed

+73
-46
lines changed

5 files changed

+73
-46
lines changed

Diff for: libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp

+18-32
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
#include <StreamDev.h>
2929
#include <base64.h>
3030

31+
// per https://github.com/esp8266/Arduino/issues/8231
32+
// make sure HTTPClient can be utilized as a movable class member
33+
static_assert(std::is_default_constructible_v<HTTPClient>, "");
34+
static_assert(!std::is_copy_constructible_v<HTTPClient>, "");
35+
static_assert(std::is_move_constructible_v<HTTPClient>, "");
36+
static_assert(std::is_move_assignable_v<HTTPClient>, "");
37+
38+
static const char defaultUserAgentPstr[] PROGMEM = "ESP8266HTTPClient";
39+
const String HTTPClient::defaultUserAgent = defaultUserAgentPstr;
40+
3141
static int StreamReportToHttpClientReport (Stream::Report streamSendError)
3242
{
3343
switch (streamSendError)
@@ -41,27 +51,6 @@ static int StreamReportToHttpClientReport (Stream::Report streamSendError)
4151
return 0; // never reached, keep gcc quiet
4252
}
4353

44-
/**
45-
* constructor
46-
*/
47-
HTTPClient::HTTPClient()
48-
: _client(nullptr), _userAgent(F("ESP8266HTTPClient"))
49-
{
50-
}
51-
52-
/**
53-
* destructor
54-
*/
55-
HTTPClient::~HTTPClient()
56-
{
57-
if(_client) {
58-
_client->stop();
59-
}
60-
if(_currentHeaders) {
61-
delete[] _currentHeaders;
62-
}
63-
}
64-
6554
void HTTPClient::clear()
6655
{
6756
_returnCode = 0;
@@ -80,8 +69,6 @@ void HTTPClient::clear()
8069
* @return success bool
8170
*/
8271
bool HTTPClient::begin(WiFiClient &client, const String& url) {
83-
_client = &client;
84-
8572
// check for : (http: or https:)
8673
int index = url.indexOf(':');
8774
if(index < 0) {
@@ -97,6 +84,8 @@ bool HTTPClient::begin(WiFiClient &client, const String& url) {
9784
}
9885

9986
_port = (protocol == "https" ? 443 : 80);
87+
_client = client.clone();
88+
10089
return beginInternal(url, protocol.c_str());
10190
}
10291

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

117106
clear();
118107
_host = host;
@@ -462,7 +451,7 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
462451
}
463452

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

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

565554
// transfer all of it, with timeout
566-
size_t transferred = stream->sendSize(_client, size);
555+
size_t transferred = stream->sendSize(_client.get(), size);
567556
if (transferred != size)
568557
{
569558
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", size, transferred);
@@ -613,7 +602,7 @@ WiFiClient& HTTPClient::getStream(void)
613602
WiFiClient* HTTPClient::getStreamPtr(void)
614603
{
615604
if(connected()) {
616-
return _client;
605+
return _client.get();
617606
}
618607

619608
DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n");
@@ -818,10 +807,7 @@ void HTTPClient::addHeader(const String& name, const String& value, bool first,
818807
void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount)
819808
{
820809
_headerKeysCount = headerKeysCount;
821-
if(_currentHeaders) {
822-
delete[] _currentHeaders;
823-
}
824-
_currentHeaders = new RequestArgument[_headerKeysCount];
810+
_currentHeaders = std::make_unique<RequestArgument[]>(_headerKeysCount);
825811
for(size_t i = 0; i < _headerKeysCount; i++) {
826812
_currentHeaders[i].key = headerKeys[i];
827813
}
@@ -963,7 +949,7 @@ bool HTTPClient::sendHeader(const char * type)
963949
DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
964950

965951
// transfer all of it, with timeout
966-
return StreamConstPtr(header).sendAll(_client) == header.length();
952+
return StreamConstPtr(header).sendAll(_client.get()) == header.length();
967953
}
968954

969955
/**

Diff for: libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h

+21-13
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626
#ifndef ESP8266HTTPClient_H_
2727
#define ESP8266HTTPClient_H_
2828

29-
#include <memory>
3029
#include <Arduino.h>
3130
#include <StreamString.h>
3231
#include <WiFiClient.h>
3332

33+
#include <memory>
34+
3435
#ifdef DEBUG_ESP_HTTP_CLIENT
3536
#ifdef DEBUG_ESP_PORT
3637
#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
@@ -151,13 +152,12 @@ typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
151152
class HTTPClient
152153
{
153154
public:
154-
HTTPClient();
155-
~HTTPClient();
155+
HTTPClient() = default;
156+
~HTTPClient() = default;
157+
HTTPClient(HTTPClient&&) = default;
158+
HTTPClient& operator=(HTTPClient&&) = default;
156159

157-
/*
158-
* Since both begin() functions take a reference to client as a parameter, you need to
159-
* ensure the client object lives the entire time of the HTTPClient
160-
*/
160+
// Note that WiFiClient's underlying connection *will* be captured
161161
bool begin(WiFiClient &client, const String& url);
162162
bool begin(WiFiClient &client, const String& host, uint16_t port, const String& uri = "/", bool https = false);
163163

@@ -235,7 +235,15 @@ class HTTPClient
235235
int handleHeaderResponse();
236236
int writeToStreamDataBlock(Stream * stream, int len);
237237

238-
WiFiClient* _client;
238+
// The common pattern to use the class is to
239+
// {
240+
// WiFiClient socket;
241+
// HTTPClient http;
242+
// http.begin(socket, "http://blahblah");
243+
// }
244+
// Make sure it's not possible to break things in an opposite direction
245+
246+
std::unique_ptr<WiFiClient> _client;
239247

240248
/// request handling
241249
String _host;
@@ -247,12 +255,14 @@ class HTTPClient
247255
String _uri;
248256
String _protocol;
249257
String _headers;
250-
String _userAgent;
251258
String _base64Authorization;
252259

260+
static const String defaultUserAgent;
261+
String _userAgent = defaultUserAgent;
262+
253263
/// Response handling
254-
RequestArgument* _currentHeaders = nullptr;
255-
size_t _headerKeysCount = 0;
264+
std::unique_ptr<RequestArgument[]> _currentHeaders;
265+
size_t _headerKeysCount = 0;
256266

257267
int _returnCode = 0;
258268
int _size = -1;
@@ -264,6 +274,4 @@ class HTTPClient
264274
std::unique_ptr<StreamString> _payload;
265275
};
266276

267-
268-
269277
#endif /* ESP8266HTTPClient_H_ */

Diff for: libraries/ESP8266WiFi/src/WiFiClient.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ WiFiClient::~WiFiClient()
101101
_client->unref();
102102
}
103103

104+
std::unique_ptr<WiFiClient> WiFiClient::clone() const {
105+
return std::make_unique<WiFiClient>(*this);
106+
}
107+
104108
WiFiClient::WiFiClient(const WiFiClient& other)
105109
{
106110
_client = other._client;

Diff for: libraries/ESP8266WiFi/src/WiFiClient.h

+12
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ class WiFiClient : public Client, public SList<WiFiClient> {
5252
WiFiClient(const WiFiClient&);
5353
WiFiClient& operator=(const WiFiClient&);
5454

55+
// b/c this is both a real class and a virtual parent of the secure client, make sure
56+
// there's a safe way to copy from the pointer without 'slicing' it; i.e. only the base
57+
// portion of a derived object will be copied, and the polymorphic behavior will be corrupted.
58+
//
59+
// this class still implements the copy and assignment though, so this is not yet enforced
60+
// (but, *should* be inside the Core itself, see httpclient & server)
61+
//
62+
// ref.
63+
// - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual
64+
// - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rh-copy
65+
virtual std::unique_ptr<WiFiClient> clone() const;
66+
5567
virtual uint8_t status();
5668
virtual int connect(IPAddress ip, uint16_t port) override;
5769
virtual int connect(const char *host, uint16_t port) override;

Diff for: libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.h

+18-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ class WiFiClientSecureCtx : public WiFiClient {
3939

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

42+
// TODO: usage is invalid b/c of deleted copy, but this will only trigger an error when it is actually used by something
43+
// TODO: don't remove just yet to avoid including the WiFiClient default implementation and unintentionally causing
44+
// a 'slice' that this method tries to avoid in the first place
45+
std::unique_ptr<WiFiClient> clone() const override {
46+
return std::unique_ptr<WiFiClient>(new WiFiClientSecureCtx(*this));
47+
}
48+
4249
int connect(IPAddress ip, uint16_t port) override;
4350
int connect(const String& host, uint16_t port) override;
4451
int connect(const char* name, uint16_t port) override;
@@ -231,13 +238,23 @@ class WiFiClientSecure : public WiFiClient {
231238
// Instead, all virtual functions call their counterpart in "WiFiClientecureCtx* _ctx"
232239
// which also derives from WiFiClient (this parent is the one which is eventually used)
233240

241+
// TODO: notice that this complicates the implementation by having two distinct ways the client connection is managed, consider:
242+
// - implementing the secure connection details in the ClientContext
243+
// (i.e. delegate the write & read functions there)
244+
// - simplify the inheritance chain by implementing base wificlient class and inherit the original wificlient and wificlientsecure from it
245+
// - abstract internals so it's possible to seamlessly =default copy and move with the instance *without* resorting to manual copy and initialization of each member
246+
247+
// TODO: prefer implementing virtual overrides in the .cpp (or, at least one of them)
248+
234249
public:
235250

236251
WiFiClientSecure():_ctx(new WiFiClientSecureCtx()) { _owned = _ctx.get(); }
237252
WiFiClientSecure(const WiFiClientSecure &rhs): WiFiClient(), _ctx(rhs._ctx) { if (_ctx) _owned = _ctx.get(); }
238253
~WiFiClientSecure() override { _ctx = nullptr; }
239254

240-
WiFiClientSecure& operator=(const WiFiClientSecure&) = default; // The shared-ptrs handle themselves automatically
255+
WiFiClientSecure& operator=(const WiFiClientSecure&) = default;
256+
257+
std::unique_ptr<WiFiClient> clone() const override { return std::unique_ptr<WiFiClient>(new WiFiClientSecure(*this)); }
241258

242259
uint8_t status() override { return _ctx->status(); }
243260
int connect(IPAddress ip, uint16_t port) override { return _ctx->connect(ip, port); }

0 commit comments

Comments
 (0)