Skip to content
Merged
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
11 changes: 8 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y clang-format
- name: Check formatting
run: |
files=$(git ls-files '*.h' '*.cpp' '*.ino')
# Exclude vendored third-party sources from formatting checks.
files=$(git ls-files '*.h' '*.cpp' '*.ino' | grep -v '^src/third_party/' || true)
echo "Checking clang-format on: $files"
diff_found=0
for f in $files; do
Expand All @@ -33,7 +34,11 @@ jobs:
run: pip install cpplint
- name: Run cpplint
run: |
cpplint --recursive --extensions=h,cpp src || true
# Keep third_party out of lint noise.
files=$(git ls-files src | grep -E '\\.(h|cpp)$' | grep -v '^src/third_party/' || true)
if [ -n "$files" ]; then
cpplint $files || true
fi
# We don't fail hard yet; adjust policy later
cppcheck:
runs-on: ubuntu-latest
Expand All @@ -45,7 +50,7 @@ jobs:
run: |
cppcheck --enable=warning,style,performance --inline-suppr \
--suppress=missingIncludeSystem \
-I src --quiet src || true
-I src --quiet src --exclude=src/third_party || true
version-sync:
runs-on: ubuntu-latest
steps:
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,17 @@ jobs:
path: |
~/.platformio/.cache
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
- name: Run native unit tests (UrlParser)
- name: Run native unit tests (UrlParser + gzip)
if: ${{ hashFiles('test/**') != '' }}
run: |
echo "Detected test files:" $(ls test || true)
pio test -e native -f test_urlparser_native -v
pio test -e native -f test_urlparser_native -f test_gzip_decode_native -v
- name: Skip notice (no tests found)
if: ${{ hashFiles('test/**') == '' }}
run: echo "No test directory present in this ref; skipping native tests."
- name: Compile ESP32 (gzip enabled)
run: |
pio run -e compile_test_gzip -v

python-parse-tests:
runs-on: ubuntu-latest
Expand Down
44 changes: 34 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

An 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.

> 🔐 **HTTPS Ready**: TLS/HTTPS is available via AsyncTCP + mbedTLS. Load a CA certificate or fingerprint before talking to real servers, or call `client.setTlsInsecure(true)` only for testing. See the *HTTPS / TLS configuration* section below.
> 🔐 **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).

## Features

Expand Down Expand Up @@ -167,12 +167,26 @@ void setKeepAlive(bool enable, uint16_t idleMs = 5000);

// Cookie jar helpers
void clearCookies();
void setAllowCookieDomainAttribute(bool enable);
void addAllowedCookieDomain(const char* domain);
void clearAllowedCookieDomains();
void setCookie(const char* name, const char* value, const char* path = "/", const char* domain = nullptr,
bool secure = false);

// Redirect header policy (when followRedirects is enabled)
void setRedirectHeaderPolicy(RedirectHeaderPolicy policy);
void addRedirectSafeHeader(const char* name);
void clearRedirectSafeHeaders();
```

Cookies are captured automatically from `Set-Cookie` responses and replayed on matching hosts/paths; call
`clearCookies()` to wipe the jar or `setCookie()` to pre-seed entries manually. Keep-alive pooling is off by default;
`clearCookies()` to wipe the jar or `setCookie()` to pre-seed entries manually.

By default, cookies set without a `Domain=` attribute are treated as **host-only** (sent only to the exact host that
set them). `Domain=` attributes that would widen scope are ignored unless explicitly allowlisted via
`setAllowCookieDomainAttribute(true)` + `addAllowedCookieDomain("example.com")`.

Keep-alive pooling is off by default;
enable it with `setKeepAlive(true, idleMs)` to reuse TCP/TLS connections for the same host/port (respecting server
`Connection: close` requests).

Expand Down Expand Up @@ -304,7 +318,8 @@ client.post("http://example.com/login", "user=demo", [](AsyncHttpResponse* respo

- 301/302/303 responses switch to `GET` automatically (body dropped).
- 307/308 keep the original method and body (stream bodies cannot be replayed automatically).
- Sensitive headers (`Authorization`, `Proxy-Authorization`) are stripped when the redirect crosses hosts.
- Cross-origin redirects default to forwarding only a small safe set of headers (e.g. `User-Agent`, `Accept`, etc.).
Use `setRedirectHeaderPolicy(...)` and `addRedirectSafeHeader(...)` if you need to forward additional headers.
- Redirects are triggered as soon as the headers arrive; the client skips downloading any subsequent 3xx body data.

See `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.
Expand Down Expand Up @@ -409,9 +424,11 @@ Highlights / limitations:

`https://` URLs now use the built-in AsyncTCP + mbedTLS transport. Supply trust material before making real requests:

- `client.setTlsCACert(caPem)` — load a PEM CA chain (null-terminated). Mandatory unless using fingerprint pinning or `setTlsInsecure(true)`.
- `client.setTlsCACert(caPem)` — load a PEM CA chain (null-terminated). Mandatory unless using fingerprint pinning (see `setTlsFingerprint(...)`).
- `client.setTlsClientCert(certPem, keyPem)` — optional mutual-TLS credentials (PEM).
- `client.setTlsFingerprint("AA:BB:...")` — 32-byte SHA-256 fingerprint pinning. Validated after the handshake in addition to CA checks.
- `client.setTlsInsecure(true)` — skips CA validation. By default this is only effective when a fingerprint is configured (pinning).
To allow fully insecure TLS (MITM-unsafe) for local debugging, build with `-DASYNC_HTTP_ALLOW_INSECURE_TLS=1`.
- `client.setTlsInsecure(true)` — disable CA validation (development only; do not ship with this enabled).
- `client.setTlsHandshakeTimeout(ms)` — default is 12s; tune for slow networks.

Expand Down Expand Up @@ -451,7 +468,7 @@ Common HTTPS errors:
- Redirects disabled by default; opt-in via `client.setFollowRedirects(...)`
- No long-lived keep-alive: default header `Connection: close`; no connection reuse currently.
- Manual timeout loop required if AsyncTCP version lacks `setTimeout` (call `client.loop()` in `loop()`).
- No specific content-encoding handling (gzip/deflate ignored if sent).
- No general content-encoding handling (br/deflate not supported); optional `gzip` decode is available via `ASYNC_HTTP_ENABLE_GZIP_DECODE`.

## Object lifecycle / Ownership

Expand Down Expand Up @@ -488,6 +505,7 @@ Single authoritative list (kept in sync with `HttpCommon.h`):
| -15 | TLS_CERT_INVALID | TLS certificate validation failed |
| -16 | TLS_FINGERPRINT_MISMATCH | TLS fingerprint pinning rejected the peer certificate |
| -17 | TLS_HANDSHAKE_TIMEOUT | TLS handshake exceeded the configured timeout |
| -18 | GZIP_DECODE_FAILED | Failed to decode gzip body (`Content-Encoding: gzip`) |
| >0 | (AsyncTCP) | Not used: transport errors are mapped to CONNECTION_FAILED |

Example mapping in a callback:
Expand Down Expand Up @@ -560,7 +578,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
- Global body chunk callback (per-request callback removed for API simplicity)
- Basic Auth helper (request->setBasicAuth)
- Query param builder (addQueryParam/finalizeQueryParams)
- Optional Accept-Encoding: gzip (no automatic decompression yet)
- Optional Accept-Encoding: gzip (+ optional transparent decode via `ASYNC_HTTP_ENABLE_GZIP_DECODE`)
- Separate connect timeout and total timeout
- Optional request queue limiting parallel connections (setMaxParallel)
- Soft response buffering guard (`setMaxBodySize`) to fail fast on oversized payloads
Expand All @@ -569,11 +587,17 @@ Contributions are welcome! Please feel free to submit a Pull Request.

### Gzip / Compression

Current: only the `Accept-Encoding: gzip` header can be added via `enableGzipAcceptEncoding(true)`.
The library DOES NOT yet decompress gzip payloads. If you don't want compressed responses, simply don't enable the header.
Default: only the `Accept-Encoding: gzip` header can be added via `enableGzipAcceptEncoding(true)`.

Optional decode: build with `-DASYNC_HTTP_ENABLE_GZIP_DECODE=1` to transparently inflate `Content-Encoding: gzip` responses (both in-memory body and `client.onBodyChunk(...)` stream).

Notes:

Important: calling `enableGzipAcceptEncoding(false)` does not remove the header if it was already added earlier on the same request instance. Create a new request without enabling it to avoid sending the header.
A future optional flag (`ASYNC_HTTP_ENABLE_GZIP_DECODE`) may add a tiny inflater (miniz/zlib) after flash/RAM impact is evaluated.
- If you don't want compressed responses, simply don't enable the header.
- `enableGzipAcceptEncoding(false)` removes `Accept-Encoding` from the request's header list (or call `request.removeHeader("Accept-Encoding")`).
- `Content-Length` (when present) refers to the *compressed* payload size; completion detection still follows the wire length.
- RAM impact: enabling gzip decode allocates an internal 32KB sliding window per active gzip-decoded response (plus small state).
- Integrity: the gzip trailer is verified (CRC32 + ISIZE); corrupted payloads raise `GZIP_DECODE_FAILED`.

### HTTPS quick reference

Expand Down
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ESPAsyncWebClient",
"version": "1.1.3",
"version": "1.1.4",
"description": "Asynchronous HTTP client library for ESP32 ",
"keywords": [
"http",
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=ESPAsyncWebClient
version=1.1.3
version=1.1.4
author=playmiel
maintainer=playmiel
sentence=Asynchronous HTTP client library for ESP32 microcontrollers
Expand Down
21 changes: 18 additions & 3 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ framework = arduino
platform_packages =
framework-arduinoespressif32@^3
; Run only Arduino-suitable tests on the device build
test_filter = test_parse_url, test_chunk_parse, test_keep_alive, test_cookies
test_filter = test_parse_url, test_chunk_parse, test_keep_alive, test_cookies, test_redirects
test_ignore = test_urlparser_native
lib_deps =
https://github.com/ESP32Async/AsyncTCP.git
Expand All @@ -38,6 +38,20 @@ build_src_filter = -<*> +<../src/> +<../test/compile_test_internal/compile_test.
lib_deps =
https://github.com/ESP32Async/AsyncTCP.git

[env:compile_test_gzip]
platform = espressif32
board = esp32dev
framework = arduino
platform_packages =
framework-arduinoespressif32@^3
build_flags =
${env.build_flags}
-DCOMPILE_TEST_ONLY
-DASYNC_HTTP_ENABLE_GZIP_DECODE=1
build_src_filter = -<*> +<../src/> +<../test/compile_test_internal/compile_test.cpp>
lib_deps =
https://github.com/ESP32Async/AsyncTCP.git


# Environment for testing with different AsyncTCP versions
[env:esp32dev_asynctcp_dev]
Expand Down Expand Up @@ -74,7 +88,8 @@ platform = native
; Only build the standalone URL parser for host tests to avoid Arduino deps
; Do not compile Arduino-based tests in native
test_ignore = test_parse_url, test_chunk_parse, test_redirects, test_cookies, test_keep_alive
build_src_filter = -<*> +<UrlParser.cpp>
build_src_filter = -<*> +<UrlParser.cpp> +<GzipDecoder.cpp> +<third_party/miniz/miniz_tinfl.c>
build_flags =
-I test/test_urlparser_native

-I src
-DASYNC_HTTP_ENABLE_GZIP_DECODE=1
Loading