{"id":45689871,"url":"https://github.com/playmiel/espasyncwebclient","last_synced_at":"2026-02-24T16:03:42.336Z","repository":{"id":310035287,"uuid":"1013155538","full_name":"playmiel/ESPAsyncWebClient","owner":"playmiel","description":"ESPAsyncWebClient is a library designed for ESP32 microcontrollers that uses AsyncTCP to make HTTP requests completely asynchronously, thus avoiding blocking the main program loop.  It exposes a simple API for GET, POST, PUT and DELETE requests, with a callback mechanism to handle responses or errors, and the possibility of defining headers ect..","archived":false,"fork":false,"pushed_at":"2026-02-20T14:10:51.000Z","size":281,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-20T15:53:58.659Z","etag":null,"topics":["arduino","async","asyncwebserver","client","esp32","esp32-arduino","espasyncclient","htpp"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/playmiel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-03T12:51:21.000Z","updated_at":"2026-01-06T10:02:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"6706dcec-350e-47dc-b1dd-c4892083d38d","html_url":"https://github.com/playmiel/ESPAsyncWebClient","commit_stats":null,"previous_names":["playmiel/espasyncwebclient"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/playmiel/ESPAsyncWebClient","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playmiel%2FESPAsyncWebClient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playmiel%2FESPAsyncWebClient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playmiel%2FESPAsyncWebClient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playmiel%2FESPAsyncWebClient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/playmiel","download_url":"https://codeload.github.com/playmiel/ESPAsyncWebClient/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/playmiel%2FESPAsyncWebClient/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29790415,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T10:45:18.109Z","status":"ssl_error","status_checked_at":"2026-02-24T10:45:09.911Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","async","asyncwebserver","client","esp32","esp32-arduino","espasyncclient","htpp"],"created_at":"2026-02-24T16:03:37.783Z","updated_at":"2026-02-24T16:03:42.311Z","avatar_url":"https://github.com/playmiel.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ESPAsyncWebClient\n\n[![Build Examples](https://github.com/playmiel/ESPAsyncWebClient/actions/workflows/build.yml/badge.svg)](https://github.com/playmiel/ESPAsyncWebClient/actions/workflows/build.yml)\n[![Library Tests](https://github.com/playmiel/ESPAsyncWebClient/actions/workflows/test.yml/badge.svg)](https://github.com/playmiel/ESPAsyncWebClient/actions/workflows/test.yml)\n\n\nAn asynchronous HTTP client library for ESP32 microcontrollers, built on top of AsyncTCP. This library provides a simple and efficient way to make HTTP requests without blocking your main program execution.\n\n\u003e 🔐 **HTTPS Ready**: TLS/HTTPS is available via AsyncTCP + mbedTLS. Load a CA certificate or fingerprint before talking to real servers. `client.setTlsInsecure(true)` is intended for debug/pinning scenarios; fully insecure TLS requires an explicit build-time opt-in (see the *HTTPS / TLS configuration* section below).\n\n## Features\n\n- ✅ **Asynchronous HTTP requests** - Non-blocking HTTP operations\n- ✅ **HTTPS / TLS** - AsyncTCP + mbedTLS with CA, fingerprint and mutual-auth options\n- ✅ **Multiple HTTP methods** - GET, POST, PUT, DELETE, HEAD, PATCH support\n- ✅ **Custom headers** - Set global and per-request headers\n- ✅ **Callback-based responses** - Success and error callbacks\n- ✅ **Automatic cookies** - Captures `Set-Cookie` responses and replays them via `Cookie` on matching requests\n- ✅ **ESP32 only** – Arduino-ESP32 core 3.x required (core 2.x dropped; ESP8266 removed since 1.0.1)\n- ✅ **Simple API** - Easy to use with minimal setup\n- ✅ **Configurable timeouts** - Set custom timeout values\n- ✅ **Multiple simultaneous requests** - Handle multiple requests concurrently\n- ✅ **Chunked transfer decoding** - Validates framing and exposes parsed trailers\n- ✅ **Optional redirect following** - Follow 301/302/303 (converted to GET) and 307/308 (method preserved)\n- ✅ **Header \u0026 body guards** - Limits buffered headers (~2.8 KiB) and body (8 KiB) by default to avoid runaway responses\n- ✅ **Zero-copy streaming** - Combine `req-\u003esetNoStoreBody(true)` with `client.onBodyChunk(...)` to stream large payloads without heap spikes\n\n\u003e ⚠ Limitations: provide trust material for HTTPS (CA, fingerprint or insecure flag) and remember the full body is buffered in memory unless you opt into zero-copy streaming via `setNoStoreBody(true)`.\n\n## Installation\n\n### PlatformIO (Recommended)\n\nAdd to your `platformio.ini`:\n\n```ini\nlib_deps = \n    ESP32Async/AsyncTCP @ ^3.4.8\n    https://github.com/playmiel/ESPAsyncWebClient.git\nplatform_packages =\n    framework-arduinoespressif32@^3\n```\n\n### Arduino IDE\n\n1. Download this repository as ZIP\n2. In Arduino IDE: Sketch → Include Library → Add .ZIP Library\n3. Install the dependencies:\n   - For ESP32: [AsyncTCP by ESP32Async](https://github.com/ESP32Async/AsyncTCP)\n4. Make sure you have the ESP32 Arduino core 3.x installed from the Boards Manager (core 2.x is not supported)\n\n\n## Quick Start\n\n```cpp\n#include \u003cWiFi.h\u003e\n#include \u003cESPAsyncWebClient.h\u003e\n\nAsyncHttpClient client;\n\nvoid setup() {\n    Serial.begin(115200);\n    \n    // Connect to WiFi\n    WiFi.begin(\"your-ssid\", \"your-password\");\n    while (WiFi.status() != WL_CONNECTED) {\n        delay(1000);\n    }\n    \n    // Make a simple GET request\n    client.get(\"http://httpbin.org/get\", \n        [](std::shared_ptr\u003cAsyncHttpResponse\u003e response) {\n            Serial.printf(\"Success! Status: %d\\n\", response-\u003egetStatusCode());\n            Serial.printf(\"Body: %s\\n\", response-\u003egetBody().c_str());\n        },\n        [](HttpClientError error, const char* message) {\n            Serial.printf(\"Error: %s (%d)\\n\", httpClientErrorToString(error), (int)error);\n        }\n    );\n}\n\nvoid loop() {\n#if !ASYNC_TCP_HAS_TIMEOUT\n    // If your AsyncTCP does NOT provide native timeouts, you must drive timeouts manually\n    // unless you build with -DASYNC_HTTP_ENABLE_AUTOLOOP .\n    // Either:\n    //   - Define ASYNC_HTTP_ENABLE_AUTOLOOP (ESP32): a tiny FreeRTOS task will call client.loop() for you; or\n    //   - Call client.loop() periodically here yourself (recommended every ~10-20ms when busy).\n    // client.loop();\n#endif\n}\n```\n\nOn ESP32, if AsyncTCP lacks native timeout support, you have two options:\n\n- Define `-DASYNC_HTTP_ENABLE_AUTOLOOP`: the library creates a tiny FreeRTOS task that periodically calls\n    `client.loop()` in the background. This is convenient but introduces a background task; keep callbacks short.\n- Do not define it: call `client.loop()` periodically yourself from your sketch `loop()` to drive timeouts.\n\nIf `ASYNC_TCP_HAS_TIMEOUT` is available in your AsyncTCP, neither is required for timeouts, but calling\n`client.loop()` remains harmless.\n\n## Migration v1 → v2\n\n- `SuccessCallback` now receives `std::shared_ptr\u003cAsyncHttpResponse\u003e`.\n- `request()` now takes `std::unique_ptr\u003cAsyncHttpRequest\u003e` and assumes ownership.\n- `getBody()`, `getHeader()`, and `getStatusText()` return `String` by value.\n- `HttpHeader` names are normalized to lowercase.\n- Legacy void-return helpers (`*_legacy`, `ASYNC_HTTP_LEGACY_VOID_API`) were removed.\n- `parseChunkSizeLine()` is now private.\n- `BodyChunkCallback` data is only valid during the callback; copy if needed.\n\nExample migration for advanced requests:\n\n```cpp\nstd::unique_ptr\u003cAsyncHttpRequest\u003e request(new AsyncHttpRequest(HTTP_METHOD_GET, \"http://example.com\"));\nclient.request(std::move(request), onSuccess, onError);\n```\n\n## API Reference\n\n### AsyncHttpClient Class\n\n#### HTTP Methods\n\n```cpp\n// GET request\nuint32_t get(const char* url, SuccessCallback onSuccess, ErrorCallback onError = nullptr);\n\n// POST request with data\nuint32_t post(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);\n\n// PUT request with data\nuint32_t put(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);\n\n// DELETE request\nuint32_t del(const char* url, SuccessCallback onSuccess, ErrorCallback onError = nullptr);\n\n// HEAD request\nuint32_t head(const char* url, SuccessCallback onSuccess, ErrorCallback onError = nullptr);\n\n// PATCH request (with data)\nuint32_t patch(const char* url, const char* data, SuccessCallback onSuccess, ErrorCallback onError = nullptr);\n\n// Advanced request (custom method, headers, streaming, etc.)\nuint32_t request(std::unique_ptr\u003cAsyncHttpRequest\u003e request, SuccessCallback onSuccess, ErrorCallback onError = nullptr);\n\n// Abort a request by its ID\nbool abort(uint32_t requestId);\n```\n\n#### Configuration Methods\n\n```cpp\n// Set global default header\nvoid setHeader(const char* name, const char* value);\nvoid removeHeader(const char* name);\nvoid clearHeaders();\n\n// Set total request timeout (milliseconds)\nvoid setTimeout(uint32_t timeout);\n\n// Set connect phase timeout distinct from total timeout\nvoid setDefaultConnectTimeout(uint32_t ms);\n\n// Follow HTTP redirects (max hops clamps to \u003e=1). Disabled by default.\nvoid setFollowRedirects(bool enable, uint8_t maxHops = 3);\n\n// Abort if response headers exceed this many bytes (default ~2.8 KiB, 0 = unlimited)\nvoid setMaxHeaderBytes(size_t maxBytes);\n\n// Soft limit for buffered response bodies (default 8192 bytes, 0 = unlimited)\nvoid setMaxBodySize(size_t maxBytes);\n\n// Limit simultaneous active requests (0 = unlimited, others queued)\nvoid setMaxParallel(uint16_t maxParallel);\n\n// Set User-Agent string\nvoid setUserAgent(const char* userAgent);\n\n// Keep-alive connection pooling (idle timeout in ms, clamped to \u003e= 1000)\nvoid setKeepAlive(bool enable, uint16_t idleMs = 5000);\n\n// Cookie jar helpers\nvoid clearCookies();\nvoid setAllowCookieDomainAttribute(bool enable);\nvoid addAllowedCookieDomain(const char* domain);\nvoid clearAllowedCookieDomains();\nvoid setCookie(const char* name, const char* value, const char* path = \"/\", const char* domain = nullptr,\n               bool secure = false);\n\n// Redirect header policy (when followRedirects is enabled)\nvoid setRedirectHeaderPolicy(RedirectHeaderPolicy policy);\nvoid addRedirectSafeHeader(const char* name);\nvoid clearRedirectSafeHeaders();\n```\n\nCookies are captured automatically from `Set-Cookie` responses and replayed on matching hosts/paths; call\n`clearCookies()` to wipe the jar or `setCookie()` to pre-seed entries manually.\n\nBy default, cookies set without a `Domain=` attribute are treated as **host-only** (sent only to the exact host that\nset them). `Domain=` attributes that would widen scope are ignored unless explicitly allowlisted via\n`setAllowCookieDomainAttribute(true)` + `addAllowedCookieDomain(\"example.com\")`.\n\nKeep-alive pooling is off by default;\nenable it with `setKeepAlive(true, idleMs)` to reuse TCP/TLS connections for the same host/port (respecting server\n`Connection: close` requests).\n\n#### Callback Types\n\n```cpp\ntypedef std::function\u003cvoid(std::shared_ptr\u003cAsyncHttpResponse\u003e)\u003e SuccessCallback;\ntypedef std::function\u003cvoid(HttpClientError, const char*)\u003e ErrorCallback;\n```\n\n### AsyncHttpResponse Class\n\n```cpp\n// Response status\nint getStatusCode() const;\nString getStatusText() const;\n\n// Response headers\nString getHeader(const String\u0026 name) const;\nconst std::vector\u003cHttpHeader\u003e\u0026 getHeaders() const;\nString getTrailer(const String\u0026 name) const;\nconst std::vector\u003cHttpHeader\u003e\u0026 getTrailers() const;\n\n// Response body\nString getBody() const;\nsize_t getContentLength() const;\n\n// Status helpers\nbool isSuccess() const;    // 2xx status codes\nbool isRedirect() const;   // 3xx status codes\nbool isError() const;      // 4xx+ status codes\n```\n\nExample of reading decoded chunk trailers:\n\n```cpp\nclient.get(\"http://example.com/chunked\", [](std::shared_ptr\u003cAsyncHttpResponse\u003e response) {\n    for (const auto\u0026 trailer : response-\u003egetTrailers()) {\n        Serial.printf(\"Trailer %s: %s\\n\", trailer.name.c_str(), trailer.value.c_str());\n    }\n});\n```\n\n### AsyncHttpRequest Class (Advanced Usage)\n\n```cpp\n// Create custom request\nstd::unique_ptr\u003cAsyncHttpRequest\u003e request(new AsyncHttpRequest(HTTP_METHOD_POST, \"http://example.com/api\"));\n\n// Set headers\nrequest-\u003esetHeader(\"Content-Type\", \"application/json\");\nrequest-\u003esetHeader(\"Authorization\", \"Bearer token\");\nrequest-\u003eremoveHeader(\"Accept-Encoding\");\n\n// Set body\nrequest-\u003esetBody(\"{\\\"key\\\":\\\"value\\\"}\");\n\n// Set timeout\nrequest-\u003esetTimeout(10000);\n\n// Execute\nclient.request(std::move(request), onSuccess, onError);\n```\n\nHTTP method enums are now prefixed (`HTTP_METHOD_GET`, `HTTP_METHOD_POST`, etc.) to avoid collisions with\n`ESPAsyncWebServer`'s `HTTP_GET`/`HTTP_POST` values. Legacy aliases can be re-enabled by defining\n`ASYNC_HTTP_ENABLE_LEGACY_METHOD_ALIASES` before including `ESPAsyncWebClient.h` (only do this if you are not also\nincluding `ESPAsyncWebServer.h` in the same translation unit).\n\n## Examples\n\n### Simple GET Request\n\n```cpp\nclient.get(\"http://api.example.com/data\", \n    [](std::shared_ptr\u003cAsyncHttpResponse\u003e response) {\n        if (response-\u003eisSuccess()) {\n            Serial.println(\"Data received:\");\n            Serial.println(response-\u003egetBody());\n        }\n    }\n);\n```\n\n### POST with JSON Data\n\n```cpp\nclient.setHeader(\"Content-Type\", \"application/json\");\nString jsonData = \"{\\\"sensor\\\":\\\"temperature\\\",\\\"value\\\":25.5}\";\n\nclient.post(\"http://api.example.com/sensor\", jsonData.c_str(),\n    [](std::shared_ptr\u003cAsyncHttpResponse\u003e response) {\n        Serial.printf(\"Posted data, status: %d\\n\", response-\u003egetStatusCode());\n    }\n);\n```\n\n### Multiple Simultaneous Requests\n\n```cpp\n// These requests will be made concurrently\nclient.get(\"http://api1.example.com/data\", onSuccess1);\nclient.get(\"http://api2.example.com/data\", onSuccess2);\nclient.post(\"http://api3.example.com/data\", \"payload\", onSuccess3);\n```\n\n### Custom Headers\n\n```cpp\n// Set global headers (applied to all requests)\nclient.setHeader(\"X-API-Key\", \"your-api-key\");\nclient.setUserAgent(\"MyDevice/1.0\");\n\n// Or set per-request headers\nstd::unique_ptr\u003cAsyncHttpRequest\u003e request(new AsyncHttpRequest(HTTP_METHOD_GET, \"http://example.com\"));\nrequest-\u003esetHeader(\"Authorization\", \"Bearer token\");\nclient.request(std::move(request), onSuccess);\n```\n\n### Following Redirects\n\n```cpp\nclient.setFollowRedirects(true, 3); // follow at most 3 hops\n\nclient.post(\"http://example.com/login\", \"user=demo\", [](std::shared_ptr\u003cAsyncHttpResponse\u003e response) {\n    Serial.printf(\"Final location responded with %d\\n\", response-\u003egetStatusCode());\n});\n```\n\n- 301/302/303 responses switch to `GET` automatically (body dropped).\n- 307/308 keep the original method and body (stream bodies cannot be replayed automatically).\n- Cross-origin redirects default to forwarding only a small safe set of headers (e.g. `User-Agent`, `Accept`, etc.).\n  Use `setRedirectHeaderPolicy(...)` and `addRedirectSafeHeader(...)` if you need to forward additional headers.\n- Redirects are triggered as soon as the headers arrive; the client skips downloading any subsequent 3xx body data.\n\nSee `examples/arduino/NoStoreToSD/NoStoreToSD.ino` for a full download example using `setNoStoreBody(true)` and a global `onBodyChunk` handler that streams chunked and non-chunked responses to an SD card.\n\n## Error Handling\n\nError codes passed to error callbacks: see the single authoritative table in the “Error Codes” section below.\n\n```cpp\nclient.get(\"http://example.com\", onSuccess,\n    [](HttpClientError error, const char* message) {\n        switch(error) {\n            case CONNECTION_FAILED:\n                Serial.println(\"Connection failed\");\n                break;\n            case REQUEST_TIMEOUT:\n                Serial.println(\"Request timed out\");\n                break;\n            default:\n                Serial.printf(\"Network error: %s (%d)\\n\", httpClientErrorToString(error), (int)error);\n        }\n    }\n);\n```\n\n## Configuration\n\n### Global Settings\n\n```cpp\n// Set default timeout for all requests (10 seconds)\nclient.setTimeout(10000);\n\n// Set default User-Agent\nclient.setUserAgent(\"ESP32-IoT-Device/1.0\");\n\n// Set default headers applied to all requests\nclient.setHeader(\"X-Device-ID\", \"esp32-001\");\nclient.setHeader(\"Accept\", \"application/json\");\n```\n\n### Per-Request Settings\n\n```cpp\nstd::unique_ptr\u003cAsyncHttpRequest\u003e request(new AsyncHttpRequest(HTTP_METHOD_POST, url));\nrequest-\u003esetTimeout(30000);  // 30 second timeout for this request\nrequest-\u003esetHeader(\"Content-Type\", \"application/xml\");\nrequest-\u003esetBody(xmlData);\n```\n\n## Memory Management\n\n- The library automatically manages memory for standard requests\n- For advanced requests, pass a `std::unique_ptr\u003cAsyncHttpRequest\u003e` to `request()`; ownership transfers to the client\n- Success callbacks receive a `std::shared_ptr\u003cAsyncHttpResponse\u003e`; keep a copy if you need the response after the callback\n- No manual memory management required for typical usage\n\n\u003e IMPORTANT: Body chunk data is only valid during `onBodyChunk(...)`. Copy it if you need to keep it.\n\n### Body Streaming (experimental)\n\nRegister a global streaming callback via:\n\n```cpp\nclient.onBodyChunk([](const char* data, size_t len, bool final) {\n    // data may be nullptr \u0026 len==0 when final==true and no trailing bytes\n});\n```\n\nParameters:\n\n- `data`, `len`: received segment (for chunked: decoded chunk payload; for non-chunked: raw slice). When `final==true` and no extra bytes, `data` can be `nullptr`.\n- `final`: true when the whole response body is complete.\n\nNotes:\n\n- Invoked for every segment (chunk or contiguous data block)\n- `data` is only valid during the callback; copy it if you need to retain it\n- Unless `req-\u003esetNoStoreBody(true)` is enabled, the full body is still accumulated internally\n- `final` is invoked just before the success callback\n- Keep it lightweight (avoid blocking operations)\n\n\n### Content-Length and over / under delivery\n\nIf `Content-Length` is present, the response is considered complete once that many bytes have been received. Extra bytes (if a misbehaving server sends more) are ignored. Without `Content-Length`, completion is determined by connection close.\n\nConfigure `client.setMaxBodySize(maxBytes)` to abort early when the announced `Content-Length` or accumulated chunk data would exceed `maxBytes`, yielding `MAX_BODY_SIZE_EXCEEDED`. Pass `0` to disable the guard (this applies only when buffering the response body in memory).\n\nLikewise, guard against oversized or malicious header blocks via `client.setMaxHeaderBytes(limit)`. When the cumulative response headers exceed `limit` bytes before completion of `\\r\\n\\r\\n`, the request aborts with `HEADERS_TOO_LARGE`.\n\n### Transfer-Encoding: chunked\n\nChunked decoding validates frame boundaries and parses trailer headers for attachment to the response object.\n\nHighlights / limitations:\n\n- Trailer headers are parsed during chunked responses and available via `AsyncHttpResponse::getTrailers()`\n- Chunk extensions are ignored but accepted\n- Strict CRLF framing is required; malformed chunks raise `CHUNKED_DECODE_FAILED`\n\n### HTTPS / TLS configuration\n\n`https://` URLs now use the built-in AsyncTCP + mbedTLS transport. Supply trust material before making real requests:\n\n- `client.setTlsCACert(caPem)` — load a PEM CA chain (null-terminated). Mandatory unless using fingerprint pinning (see `setTlsFingerprint(...)`).\n- `client.setTlsClientCert(certPem, keyPem)` — optional mutual-TLS credentials (PEM).\n- `client.setTlsFingerprint(\"AA:BB:...\")` — 32-byte SHA-256 fingerprint pinning. Validated after the handshake in addition to CA checks.\n- `client.setTlsInsecure(true)` — skips CA validation. By default this is only effective when a fingerprint is configured (pinning).\n  To allow fully insecure TLS (MITM-unsafe) for local debugging, build with `-DASYNC_HTTP_ALLOW_INSECURE_TLS=1`.\n- `client.setTlsInsecure(true)` — disable CA validation (development only; do not ship with this enabled).\n- `client.setTlsHandshakeTimeout(ms)` — default is 12s; tune for slow networks.\n\nPer-request overrides are available via `AsyncHttpRequest::setTlsConfig(const AsyncHttpTLSConfig\u0026)` when a particular destination needs a different CA or timeout.\n\nCommon HTTPS errors:\n\n- `TLS_HANDSHAKE_FAILED` — TCP issues or protocol alerts during the handshake.\n- `TLS_CERT_INVALID` — CA verification failed (missing root, expired cert, wrong host).\n- `TLS_FINGERPRINT_MISMATCH` — fingerprint pinning rejected the peer certificate.\n- `TLS_HANDSHAKE_TIMEOUT` — handshake exceeded the configured timeout.\n- `HTTPS_NOT_SUPPORTED` — only triggered if the binary is built without TLS support (non-ESP32 targets).\n\n## Thread Safety\n\n- The library is designed for single-threaded use (Arduino main loop)\n- Callbacks are executed in the context of the network event loop\n- Keep callback functions lightweight and non-blocking\n\n## Dependencies\n\n- **ESP32**: [AsyncTCP by ESP32Async](https://github.com/ESP32Async/AsyncTCP)\n- **Arduino Core**: ESP32 (v3.0+)\n\n\u003e **Note**: ESP8266 was mentioned in early docs but is no longer supported as of 1.0.1. The code exclusively targets AsyncTCP (ESP32).\n\n## Supported Platforms\n\n- Current target: **ESP32** only\n- ESP8266: removed (no conditional code path retained)\n\n## Current Limitations (summary)\n\n- TLS requires explicit trust material (CA certificate, fingerprint, or insecure mode)\n- Chunked: trailers parsed and attached to `AsyncHttpResponse::getTrailers()`\n- Full in-memory buffering (guard with `setMaxBodySize` or use no-store + chunk callback)\n- Redirects disabled by default; opt-in via `client.setFollowRedirects(...)`\n- Keep-alive pooling is disabled by default; enable it with `setKeepAlive(true, idleMs)`.\n- Manual timeout loop required if AsyncTCP version lacks `setTimeout` (call `client.loop()` in `loop()`).\n- No general content-encoding handling (br/deflate not supported); optional `gzip` decode is available via `ASYNC_HTTP_ENABLE_GZIP_DECODE`.\n\n## Object lifecycle / Ownership\n\n1. `AsyncHttpClient::makeRequest()` creates a dynamic `AsyncHttpRequest` (or you pass yours to `request()`).\n2. `request()` allocates a `RequestContext`, an `AsyncHttpResponse` and an `AsyncTransport`.\n3. Once connected the fully built HTTP request is written (`buildHttpRequest()`).\n4. Reception: headers buffered until `\\r\\n\\r\\n`, then body accumulation (or chunk decoding).\n5. On complete success: success callback invoked with `std::shared_ptr\u003cAsyncHttpResponse\u003e`.\n6. On error or after success callback returns: `cleanup()` deletes the transport, `AsyncHttpRequest`, and `RequestContext`.\n7. The response is freed when the last `shared_ptr` copy is released.\n\nFor very large bodies or future streaming options, a hook would be placed inside `handleData` after `headersComplete` before `appendBody`.\n\n## Error Codes\n\nSingle authoritative list (kept in sync with `HttpCommon.h`):\n\n| Code | Enum | Meaning |\n|------|------|---------|\n| -1 | CONNECTION_FAILED | Failed to initiate TCP connection or transport error mapped from AsyncTCP |\n| -2 | HEADER_PARSE_FAILED | Invalid HTTP response headers |\n| -3 | CONNECTION_CLOSED | Connection closed before headers received |\n| -4 | REQUEST_TIMEOUT | Total request timeout exceeded |\n| -5 | HTTPS_NOT_SUPPORTED | TLS/HTTPS transport unavailable (unsupported target) |\n| -6 | CHUNKED_DECODE_FAILED | Failed to decode chunked body |\n| -7 | CONNECT_TIMEOUT | Connect phase timeout |\n| -8 | BODY_STREAM_READ_FAILED | Body streaming provider failed |\n| -9 | ABORTED | Aborted by user |\n| -10 | CONNECTION_CLOSED_MID_BODY | Connection closed after headers with body still missing bytes (truncated body) |\n| -11 | MAX_BODY_SIZE_EXCEEDED | Body exceeds configured maximum (`setMaxBodySize`) |\n| -12 | TOO_MANY_REDIRECTS | Redirect chain exceeded configured hop limit (`setFollowRedirects`) |\n| -13 | HEADERS_TOO_LARGE | Response headers exceeded configured limit (`setMaxHeaderBytes`) |\n| -14 | TLS_HANDSHAKE_FAILED | TLS handshake or channel failure |\n| -15 | TLS_CERT_INVALID | TLS certificate validation failed |\n| -16 | TLS_FINGERPRINT_MISMATCH | TLS fingerprint pinning rejected the peer certificate |\n| -17 | TLS_HANDSHAKE_TIMEOUT | TLS handshake exceeded the configured timeout |\n| -18 | GZIP_DECODE_FAILED | Failed to decode gzip body (`Content-Encoding: gzip`) |\n| \u003e0 | (AsyncTCP) | Not used: transport errors are mapped to CONNECTION_FAILED |\n\nExample mapping in a callback:\n\n```cpp\nclient.get(\"http://example.com\", \n  [](std::shared_ptr\u003cAsyncHttpResponse\u003e r) {\n      Serial.printf(\"OK %d %s\\n\", r-\u003egetStatusCode(), r-\u003egetStatusText().c_str());\n  },\n        [](HttpClientError e, const char* msg) {\n      switch (e) {\n          case CONNECTION_FAILED: Serial.println(\"TCP connect failed\"); break;\n          case HEADER_PARSE_FAILED: Serial.println(\"Bad HTTP header\"); break;\n          case CONNECTION_CLOSED: Serial.println(\"Closed before headers\"); break;\n          case CONNECTION_CLOSED_MID_BODY: Serial.println(\"Body truncated (closed mid-body)\"); break;\n          case REQUEST_TIMEOUT: Serial.println(\"Timeout\"); break;\n          case MAX_BODY_SIZE_EXCEEDED: Serial.println(\"Body exceeded guard\"); break;\n          case TOO_MANY_REDIRECTS: Serial.println(\"Redirect loop detected\"); break;\n          case HEADERS_TOO_LARGE: Serial.println(\"Headers exceeded guard\"); break;\n                    default: Serial.printf(\"Network error: %s (%d)\\n\", httpClientErrorToString(e), (int)e); break;\n      }\n  }\n);\n```\n\n## Testing\n\n### Dependency Testing\n\nTo test compatibility with different versions of AsyncTCP, use the provided test script:\n\n```bash\n./scripts/test-dependencies.sh\n```\n\nThis script tests compilation with:\n\n- AsyncTCP ESP32Async/main (development)\n- AsyncTCP ESP32Async stable\n\n### Manual Testing\n\nYou can also test individual environments:\n\n```bash\n# Test with development AsyncTCP\npio run -e esp32dev_asynctcp_dev\n\n# Test with stable AsyncTCP\npio run -e test_asynctcp_stable\n\n# Basic compilation test\npio run -e compile_test\n\n# Chunk decoder regression tests\npio test -e esp32dev -f test_chunk_parse\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Support\n\n- Streaming request body (no-copy) via setBodyStream\n- Global body chunk callback (per-request callback removed for API simplicity)\n- Basic Auth helper (request-\u003esetBasicAuth)\n- Query param builder (addQueryParam/finalizeQueryParams)\n- Optional Accept-Encoding: gzip (+ optional transparent decode via `ASYNC_HTTP_ENABLE_GZIP_DECODE`)\n- Separate connect timeout and total timeout\n- Optional request queue limiting parallel connections (setMaxParallel)\n- Soft response buffering guard (`setMaxBodySize`) to fail fast on oversized payloads\n- Request ID return (all helper methods now return a uint32_t identifier)\n- Zero-copy streaming mode: call `req-\u003esetNoStoreBody(true)` and rely on `client.onBodyChunk(...)` to consume data without buffering (a final `(nullptr, 0, true)` event fires once)\n\n### Gzip / Compression\n\nDefault: only the `Accept-Encoding: gzip` header can be added via `enableGzipAcceptEncoding(true)`.\n\nOptional decode: build with `-DASYNC_HTTP_ENABLE_GZIP_DECODE=1` to transparently inflate `Content-Encoding: gzip` responses (both in-memory body and `client.onBodyChunk(...)` stream).\n\nNotes:\n\n- If you don't want compressed responses, simply don't enable the header.\n- `enableGzipAcceptEncoding(false)` removes `Accept-Encoding` from the request's header list (or call `request.removeHeader(\"Accept-Encoding\")`).\n- `Content-Length` (when present) refers to the *compressed* payload size; completion detection still follows the wire length.\n- RAM impact: enabling gzip decode allocates an internal 32KB sliding window per active gzip-decoded response (plus small state).\n- Integrity: the gzip trailer is verified (CRC32 + ISIZE); corrupted payloads raise `GZIP_DECODE_FAILED`.\n\n### HTTPS quick reference\n\n- Call `client.setTlsCACert(caPem)` (or `request-\u003esetTlsConfig(...)`) before talking to production endpoints.\n- Use `client.setTlsInsecure(true)` only during development when no CA/fingerprint is available.\n- Fingerprint pinning (SHA-256) is optional via `client.setTlsFingerprint`.\n- Mutual TLS is supported via `client.setTlsClientCert(certPem, keyPem)`.\n- Errors are surfaced via `TLS_*` codes in the error callback; see the table below.\n\n### Advanced Example\n\nSee Arduino sketch at `examples/arduino/StreamingUpload/StreamingUpload.ino` or the PlatformIO project at `examples/platformio/StreamingUpload/src/main.cpp` for a streaming (no-copy) upload demonstrating:\n\n- `setBodyStream()`\n- Basic Auth (`setBasicAuth`)\n- Query params builder (`addQueryParam` / `finalizeQueryParams`)\n- Connection limiting (`setMaxParallel`)\n\n- Create an issue on GitHub for bug reports or feature requests\n- Check the examples directory for usage patterns\n- Review the API documentation above for detailed information\n\n## Changelog\n\nSee the GitHub Releases page for version history and changes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplaymiel%2Fespasyncwebclient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplaymiel%2Fespasyncwebclient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplaymiel%2Fespasyncwebclient/lists"}