Skip to content
Merged

2.0 #19

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ jobs:
platform = espressif32
board = esp32dev
lib_deps =
https://github.com/ESP32Async/AsyncTCP.git
ESP32Async/AsyncTCP @ ^3.4.8
bblanchon/ArduinoJson@^6.21.0
EOF

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
platform: [esp32dev]
include:
- platform: esp32dev
async-tcp-repo: "https://github.com/ESP32Async/AsyncTCP.git"
async-tcp-repo: "ESP32Async/AsyncTCP @ ^3.4.8"
test-name: "latest"

steps:
Expand Down Expand Up @@ -178,7 +178,7 @@ jobs:
framework = arduino
$PLATFORM_PACKAGES
lib_deps =
https://github.com/ESP32Async/AsyncTCP.git
ESP32Async/AsyncTCP @ ^3.4.8
bblanchon/ArduinoJson@^6.21.0
EOF

Expand Down
86 changes: 52 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Add to your `platformio.ini`:

```ini
lib_deps =
https://github.com/ESP32Async/AsyncTCP.git
ESP32Async/AsyncTCP @ ^3.4.8
https://github.com/playmiel/ESPAsyncWebClient.git
platform_packages =
framework-arduinoespressif32@^3
Expand Down Expand Up @@ -70,7 +70,7 @@ void setup() {

// Make a simple GET request
client.get("http://httpbin.org/get",
[](AsyncHttpResponse* response) {
[](std::shared_ptr<AsyncHttpResponse> response) {
Serial.printf("Success! Status: %d\n", response->getStatusCode());
Serial.printf("Body: %s\n", response->getBody().c_str());
},
Expand Down Expand Up @@ -101,6 +101,23 @@ On ESP32, if AsyncTCP lacks native timeout support, you have two options:
If `ASYNC_TCP_HAS_TIMEOUT` is available in your AsyncTCP, neither is required for timeouts, but calling
`client.loop()` remains harmless.

## Migration v1 → v2

- `SuccessCallback` now receives `std::shared_ptr<AsyncHttpResponse>`.
- `request()` now takes `std::unique_ptr<AsyncHttpRequest>` and assumes ownership.
- `getBody()`, `getHeader()`, and `getStatusText()` return `String` by value.
- `HttpHeader` names are normalized to lowercase.
- Legacy void-return helpers (`*_legacy`, `ASYNC_HTTP_LEGACY_VOID_API`) were removed.
- `parseChunkSizeLine()` is now private.
- `BodyChunkCallback` data is only valid during the callback; copy if needed.

Example migration for advanced requests:

```cpp
std::unique_ptr<AsyncHttpRequest> request(new AsyncHttpRequest(HTTP_METHOD_GET, "http://example.com"));
client.request(std::move(request), onSuccess, onError);
```

## API Reference

### AsyncHttpClient Class
Expand All @@ -109,13 +126,13 @@ If `ASYNC_TCP_HAS_TIMEOUT` is available in your AsyncTCP, neither is required fo

```cpp
// GET request
void get(const char* url, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
uint32_t get(const char* url, SuccessCallback onSuccess, ErrorCallback onError = nullptr);

// POST request with data
void post(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
uint32_t post(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);

// PUT request with data
void put(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
uint32_t put(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);

// DELETE request
uint32_t del(const char* url, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
Expand All @@ -127,7 +144,7 @@ uint32_t head(const char* url, SuccessCallback onSuccess, ErrorCallback onError
uint32_t patch(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);

// Advanced request (custom method, headers, streaming, etc.)
uint32_t request(AsyncHttpRequest* request, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
uint32_t request(std::unique_ptr<AsyncHttpRequest> request, SuccessCallback onSuccess, ErrorCallback onError = nullptr);

// Abort a request by its ID
bool abort(uint32_t requestId);
Expand Down Expand Up @@ -193,7 +210,7 @@ enable it with `setKeepAlive(true, idleMs)` to reuse TCP/TLS connections for the
#### Callback Types

```cpp
typedef std::function<void(AsyncHttpResponse*)> SuccessCallback;
typedef std::function<void(std::shared_ptr<AsyncHttpResponse>)> SuccessCallback;
typedef std::function<void(HttpClientError, const char*)> ErrorCallback;
```

Expand All @@ -202,16 +219,16 @@ typedef std::function<void(HttpClientError, const char*)> ErrorCallback;
```cpp
// Response status
int getStatusCode() const;
const String& getStatusText() const;
String getStatusText() const;

// Response headers
const String& getHeader(const String& name) const;
String getHeader(const String& name) const;
const std::vector<HttpHeader>& getHeaders() const;
const String& getTrailer(const String& name) const;
String getTrailer(const String& name) const;
const std::vector<HttpHeader>& getTrailers() const;

// Response body
const String& getBody() const;
String getBody() const;
size_t getContentLength() const;

// Status helpers
Expand All @@ -223,7 +240,7 @@ bool isError() const; // 4xx+ status codes
Example of reading decoded chunk trailers:

```cpp
client.get("http://example.com/chunked", [](AsyncHttpResponse* response) {
client.get("http://example.com/chunked", [](std::shared_ptr<AsyncHttpResponse> response) {
for (const auto& trailer : response->getTrailers()) {
Serial.printf("Trailer %s: %s\n", trailer.name.c_str(), trailer.value.c_str());
}
Expand All @@ -234,21 +251,21 @@ client.get("http://example.com/chunked", [](AsyncHttpResponse* response) {

```cpp
// Create custom request
AsyncHttpRequest request(HTTP_METHOD_POST, "http://example.com/api");
std::unique_ptr<AsyncHttpRequest> request(new AsyncHttpRequest(HTTP_METHOD_POST, "http://example.com/api"));

// Set headers
request.setHeader("Content-Type", "application/json");
request.setHeader("Authorization", "Bearer token");
request.removeHeader("Accept-Encoding");
request->setHeader("Content-Type", "application/json");
request->setHeader("Authorization", "Bearer token");
request->removeHeader("Accept-Encoding");

// Set body
request.setBody("{\"key\":\"value\"}");
request->setBody("{\"key\":\"value\"}");

// Set timeout
request.setTimeout(10000);
request->setTimeout(10000);

// Execute
client.request(&request, onSuccess, onError);
client.request(std::move(request), onSuccess, onError);
```

HTTP method enums are now prefixed (`HTTP_METHOD_GET`, `HTTP_METHOD_POST`, etc.) to avoid collisions with
Expand All @@ -262,7 +279,7 @@ including `ESPAsyncWebServer.h` in the same translation unit).

```cpp
client.get("http://api.example.com/data",
[](AsyncHttpResponse* response) {
[](std::shared_ptr<AsyncHttpResponse> response) {
if (response->isSuccess()) {
Serial.println("Data received:");
Serial.println(response->getBody());
Expand All @@ -278,7 +295,7 @@ client.setHeader("Content-Type", "application/json");
String jsonData = "{\"sensor\":\"temperature\",\"value\":25.5}";

client.post("http://api.example.com/sensor", jsonData.c_str(),
[](AsyncHttpResponse* response) {
[](std::shared_ptr<AsyncHttpResponse> response) {
Serial.printf("Posted data, status: %d\n", response->getStatusCode());
}
);
Expand All @@ -301,17 +318,17 @@ client.setHeader("X-API-Key", "your-api-key");
client.setUserAgent("MyDevice/1.0");

// Or set per-request headers
AsyncHttpRequest* request = new AsyncHttpRequest(HTTP_METHOD_GET, "http://example.com");
std::unique_ptr<AsyncHttpRequest> request(new AsyncHttpRequest(HTTP_METHOD_GET, "http://example.com"));
request->setHeader("Authorization", "Bearer token");
client.request(request, onSuccess);
client.request(std::move(request), onSuccess);
```

### Following Redirects

```cpp
client.setFollowRedirects(true, 3); // follow at most 3 hops

client.post("http://example.com/login", "user=demo", [](AsyncHttpResponse* response) {
client.post("http://example.com/login", "user=demo", [](std::shared_ptr<AsyncHttpResponse> response) {
Serial.printf("Final location responded with %d\n", response->getStatusCode());
});
```
Expand Down Expand Up @@ -364,7 +381,7 @@ client.setHeader("Accept", "application/json");
### Per-Request Settings

```cpp
AsyncHttpRequest* request = new AsyncHttpRequest(HTTP_METHOD_POST, url);
std::unique_ptr<AsyncHttpRequest> request(new AsyncHttpRequest(HTTP_METHOD_POST, url));
request->setTimeout(30000); // 30 second timeout for this request
request->setHeader("Content-Type", "application/xml");
request->setBody(xmlData);
Expand All @@ -373,11 +390,11 @@ request->setBody(xmlData);
## Memory Management

- The library automatically manages memory for standard requests
- For advanced `AsyncHttpRequest` objects, the library takes ownership and will delete them
- Response objects are automatically cleaned up after callbacks complete
- For advanced requests, pass a `std::unique_ptr<AsyncHttpRequest>` to `request()`; ownership transfers to the client
- Success callbacks receive a `std::shared_ptr<AsyncHttpResponse>`; keep a copy if you need the response after the callback
- No manual memory management required for typical usage

> IMPORTANT: The `AsyncHttpResponse*` pointer passed to the success callback is ONLY valid during that callback. Do not store it or references to its internal `String` objects. Copy what you need.
> IMPORTANT: Body chunk data is only valid during `onBodyChunk(...)`. Copy it if you need to keep it.

### Body Streaming (experimental)

Expand All @@ -397,6 +414,7 @@ Parameters:
Notes:

- Invoked for every segment (chunk or contiguous data block)
- `data` is only valid during the callback; copy it if you need to retain it
- Unless `req->setNoStoreBody(true)` is enabled, the full body is still accumulated internally
- `final` is invoked just before the success callback
- Keep it lightweight (avoid blocking operations)
Expand Down Expand Up @@ -466,19 +484,19 @@ Common HTTPS errors:
- Chunked: trailers parsed and attached to `AsyncHttpResponse::getTrailers()`
- Full in-memory buffering (guard with `setMaxBodySize` or use no-store + chunk callback)
- Redirects disabled by default; opt-in via `client.setFollowRedirects(...)`
- No long-lived keep-alive: default header `Connection: close`; no connection reuse currently.
- Keep-alive pooling is disabled by default; enable it with `setKeepAlive(true, idleMs)`.
- Manual timeout loop required if AsyncTCP version lacks `setTimeout` (call `client.loop()` in `loop()`).
- No general content-encoding handling (br/deflate not supported); optional `gzip` decode is available via `ASYNC_HTTP_ENABLE_GZIP_DECODE`.

## Object lifecycle / Ownership

1. `AsyncHttpClient::makeRequest()` creates a dynamic `AsyncHttpRequest` (or you pass yours to `request()`).
2. `request()` allocates a `RequestContext`, an `AsyncHttpResponse` and an `AsyncClient`.
2. `request()` allocates a `RequestContext`, an `AsyncHttpResponse` and an `AsyncTransport`.
3. Once connected the fully built HTTP request is written (`buildHttpRequest()`).
4. Reception: headers buffered until `\r\n\r\n`, then body accumulation (or chunk decoding).
5. On complete success: success callback invoked with `AsyncHttpResponse*` (valid only during the callback).
6. On error or after success callback returns: `cleanup()` deletes `AsyncClient`, `AsyncHttpRequest`, `AsyncHttpResponse`, `RequestContext`.
7. Do **not** keep any pointer/reference after callback return (it will dangle).
5. On complete success: success callback invoked with `std::shared_ptr<AsyncHttpResponse>`.
6. On error or after success callback returns: `cleanup()` deletes the transport, `AsyncHttpRequest`, and `RequestContext`.
7. The response is freed when the last `shared_ptr` copy is released.

For very large bodies or future streaming options, a hook would be placed inside `handleData` after `headersComplete` before `appendBody`.

Expand Down Expand Up @@ -512,7 +530,7 @@ Example mapping in a callback:

```cpp
client.get("http://example.com",
[](AsyncHttpResponse* r) {
[](std::shared_ptr<AsyncHttpResponse> r) {
Serial.printf("OK %d %s\n", r->getStatusCode(), r->getStatusText().c_str());
},
[](HttpClientError e, const char* msg) {
Expand Down
2 changes: 1 addition & 1 deletion examples/arduino/CustomHeaders/CustomHeaders.ino
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ void setup() {

client.get(
"http://httpbin.org/headers",
[](AsyncHttpResponse* response) {
[](const std::shared_ptr<AsyncHttpResponse>& response) {
Serial.println("Request with custom headers successful!");
Serial.printf("Status: %d\n", response->getStatusCode());
},
Expand Down
8 changes: 5 additions & 3 deletions examples/arduino/MultipleRequests/MultipleRequests.ino
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ AsyncHttpClient client;
int requestCount = 0;
int responseCount = 0;

void onResponse(AsyncHttpResponse* response, const String& requestName) {
void onResponse(const std::shared_ptr<AsyncHttpResponse>& response, const String& requestName) {
responseCount++;
Serial.printf("[%s] Response %d received!\n", requestName.c_str(), responseCount);
Serial.printf("[%s] Status: %d %s\n", requestName.c_str(), response->getStatusCode(),
Expand All @@ -30,12 +30,14 @@ void setup() {

requestCount++;
client.get(
"http://httpbin.org/get", [](AsyncHttpResponse* response) { onResponse(response, "GET"); },
"http://httpbin.org/get",
[](const std::shared_ptr<AsyncHttpResponse>& response) { onResponse(response, "GET"); },
[](HttpClientError error, const char* message) { onError(error, message, "GET"); });

requestCount++;
client.post(
"http://httpbin.org/post", "data=test", [](AsyncHttpResponse* response) { onResponse(response, "POST"); },
"http://httpbin.org/post", "data=test",
[](const std::shared_ptr<AsyncHttpResponse>& response) { onResponse(response, "POST"); },
[](HttpClientError error, const char* message) { onError(error, message, "POST"); });
}

Expand Down
6 changes: 3 additions & 3 deletions examples/arduino/NoStoreToSD/NoStoreToSD.ino
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ static bool beginDownload(const char* url, const char* destinationPath) {

currentPath = destinationPath;

AsyncHttpRequest* request = new AsyncHttpRequest(HTTP_METHOD_GET, url);
std::unique_ptr<AsyncHttpRequest> request(new AsyncHttpRequest(HTTP_METHOD_GET, url));
request->setNoStoreBody(true); // only stream via onBodyChunk

uint32_t id = client.request(
request,
[](AsyncHttpResponse* response) {
std::move(request),
[](const std::shared_ptr<AsyncHttpResponse>& response) {
Serial.printf("Download complete (%d). Reported length: %u\r\n", response->getStatusCode(),
static_cast<unsigned int>(response->getContentLength()));
if (!plainQueued) {
Expand Down
2 changes: 1 addition & 1 deletion examples/arduino/PostWithData/PostWithData.ino
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void setup() {

client.post(
"http://httpbin.org/post", postData.c_str(),
[](AsyncHttpResponse* response) {
[](const std::shared_ptr<AsyncHttpResponse>& response) {
Serial.println("POST Success!");
Serial.printf("Status: %d\n", response->getStatusCode());
Serial.printf("Content-Type: %s\n", response->getHeader("Content-Type").c_str());
Expand Down
2 changes: 1 addition & 1 deletion examples/arduino/SimpleGet/SimpleGet.ino
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ void setup() {

client.get(
"http://httpbin.org/get",
[](AsyncHttpResponse* response) {
[](const std::shared_ptr<AsyncHttpResponse>& response) {
Serial.println("Success!");
Serial.printf("Status: %d\n", response->getStatusCode());
Serial.printf("Body: %s\n", response->getBody().c_str());
Expand Down
6 changes: 3 additions & 3 deletions examples/arduino/StreamingUpload/StreamingUpload.ino
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void setup() {
pattern.total = 10 * 1024;
pattern.sent = 0;

AsyncHttpRequest* req = new AsyncHttpRequest(HTTP_METHOD_POST, "http://httpbin.org/post");
std::unique_ptr<AsyncHttpRequest> req(new AsyncHttpRequest(HTTP_METHOD_POST, "http://httpbin.org/post"));
req->addQueryParam("mode", "stream");
req->addQueryParam("unit", "bytes");
req->finalizeQueryParams();
Expand All @@ -61,8 +61,8 @@ void setup() {
});
req->setBodyStream(pattern.total, patternProvider);
client.request(
req,
[](AsyncHttpResponse* resp) {
std::move(req),
[](const std::shared_ptr<AsyncHttpResponse>& resp) {
Serial.printf("UPLOAD DONE status=%d len=%u\n", resp->getStatusCode(), (unsigned)resp->getBody().length());
},
[](HttpClientError code, const char* msg) { Serial.printf("ERROR %d: %s\n", (int)code, msg); });
Expand Down
5 changes: 3 additions & 2 deletions examples/platformio/CompileTest/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ platform_packages =
monitor_speed = 115200

lib_deps =
https://github.com/playmiel/ESPAsyncWebClient.git

ESP32Async/AsyncTCP @ ^3.4.8

lib_extra_dirs = ../../../

build_flags =
-DCOMPILE_TEST_ONLY
4 changes: 2 additions & 2 deletions examples/platformio/CompileTest/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void testHttpMethodsCompilation() {

#ifndef COMPILE_TEST_ONLY
// Test callback signatures compilation
auto successCallback = [](AsyncHttpResponse* response) {
auto successCallback = [](const std::shared_ptr<AsyncHttpResponse>& response) {
Serial.printf("Success callback - Status: %d\n", response->getStatusCode());
Serial.printf("Body length: %d\n", response->getBody().length());

Expand Down Expand Up @@ -119,7 +119,7 @@ void loop() {
Serial.println("Executing one-time HTTP test...");
client.get(
"http://httpbin.org/get",
[](AsyncHttpResponse* response) {
[](const std::shared_ptr<AsyncHttpResponse>& response) {
Serial.printf("✓ GET request successful - Status: %d\n", response->getStatusCode());
},
[](HttpClientError error, const char* message) {
Expand Down
4 changes: 3 additions & 1 deletion examples/platformio/CustomHeaders/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ platform_packages =
monitor_speed = 115200

lib_deps =
https://github.com/playmiel/ESPAsyncWebClient.git
ESP32Async/AsyncTCP @ ^3.4.8

lib_extra_dirs = ../../../
Loading