Link is an async HTTP client library for ESP32 with fetch-style requests and bounded memory.
Link helps Arduino ESP32 firmware communicate with APIs and backend services using thread-safe request submission, a bounded worker pool, explicit request/response limits, and result-based errors. Link owns HTTP/request lifecycle policy while Strata owns Link's memory placement and low-level FreeRTOS storage.
- Fetch-style requests - submit
get,post,getJson,postJson, orgetStreamwork from normal FreeRTOS tasks. - Concurrent workers - run more than one HTTP request at a time with a bounded worker pool.
- Consistent memory policy -
Strata::MemoryPolicycontrols Link-owned allocations and worker task stacks. - Bounded payloads - accepted URLs, bodies, headers, serialized JSON, callbacks, and streaming behavior have explicit limits.
- Strata-owned FreeRTOS storage - worker task stacks/TCBs, dispatch queue storage, and mutex control blocks use static Strata ownership.
- Clear errors - operations return
LinkResult; HTTP status codes remain separate from transport failures.
Link v0.2.0 requires Strata v0.1.2 and ArduinoJson v7.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
https://github.com/ZekStack/link.git#v0.2.0
bblanchon/ArduinoJson@>=7.0.0
build_flags =
-std=gnu++20
build_unflags =
-std=gnu++11Link's library.json pins Strata v0.1.2, so PlatformIO resolves it as a transitive dependency.
Link and Strata are not published to Arduino Library Manager yet. Install both repositories and ArduinoJson v7:
Arduino/libraries/Strata
Arduino/libraries/Link
Use Strata v0.1.2 or a compatible later release.
#include <Arduino.h>
#include <Link.h>
Link client;
void onResponse(const LinkResponse &response) {
if (!response) {
Serial.println(response.error.message);
return;
}
Serial.println(response.httpStatus);
Serial.println(response.body.c_str());
}
void setup() {
Serial.begin(115200);
LinkConfig config;
config.maxConcurrentRequests = 2;
config.maxResponseBodySize = 8192;
LinkResult initResult = client.init(config);
if (!initResult) {
Serial.println(initResult.message);
return;
}
client.get("https://example.com", onResponse);
}
void loop() {
delay(1000);
}Link uses the ZekStack-standard Strata configuration shape:
LinkConfig config;
config.memory.allocation = Strata::Placement::PreferExternal;
config.memory.taskStack = Strata::Placement::PreferExternal;memory.allocation controls movable Link-owned storage, including queued request slots, worker records, dispatch queue item storage, URLs, headers, request/response bodies, persistent origin data, redirect URLs, and Link-created parsed JSON response storage.
memory.taskStack controls worker task stack placement. Strata keeps task control blocks, queue control blocks, and recursive-mutex control blocks in internal memory.
The defaults preserve Link v0.1.1 behavior:
allocation = Strata::Placement::PreferExternal;
taskStack = Strata::Placement::PreferExternal;PreferExternal uses external memory when available and falls back to internal memory. RequireExternal fails with AllocationFailed rather than consuming internal memory.
Strict external-memory configuration is therefore explicit:
config.memory.allocation = Strata::Placement::RequireExternal;
config.memory.taskStack = Strata::Placement::RequireExternal;v0.2.0 intentionally removes the Link-specific stack enum instead of carrying compatibility aliases.
| Link v0.1.1 | Link v0.2.0 |
|---|---|
LinkStackType::Auto |
Strata::Placement::PreferExternal |
LinkStackType::Internal |
Strata::Placement::Internal |
LinkStackType::Psram |
Strata::Placement::RequireExternal |
config.stackType |
config.memory.taskStack |
| implicit PSRAM-preferred payload allocation | config.memory.allocation |
Important
Link callbacks run inside Link worker tasks. If maxConcurrentRequests > 1, multiple callbacks may run concurrently.
Link uses a Strata task-only dispatch queue. A successful submission owns a request slot and has a corresponding queue message. During shutdown, stop messages are appended after accepted request messages. Workers cancel/drain accepted requests, clean up persistent HTTP state, publish that they are ready for deletion, and suspend. deinit() then resets each Strata::FreeRTOS::Task from the caller task so Strata can safely release its static stack and task control block.
Other lifecycle rules remain unchanged:
- Protect shared application state touched from callbacks.
- Requests start in queue order but may complete out of order with multiple workers.
- User callbacks are never called while Link's runtime mutex is held.
- New submissions return
Stoppingafter shutdown begins. - Every accepted request receives exactly one terminal callback before successful
deinit()returns. - A timed-out
deinit()leaves Link inStoppingwith worker-owned storage intact so a later call can continue cleanup. - Do not call
deinit()or destroy aLinkinstance from one of its callbacks.
Link routes memory it owns through Strata. Two allocation domains remain intentionally outside this boundary:
- a caller-provided request
JsonDocument, which Link only reads/serializes during submission; - allocations internal to ESP-IDF's
esp_http_clientimplementation.
LinkJsonResponse::json, by contrast, is created by Link and uses Strata's ArduinoJson allocator with memory.allocation.
Allocation-backed response storage is move-only. Use explicit result-returning copyFrom() methods when duplication is required.
LinkDiagnostics retains the existing request/HTTP counters and also reports requested Strata policy plus observed storage regions:
LinkDiagnostics d = client.diagnostics();
Serial.println(Strata::toString(d.allocationPlacement));
Serial.println(Strata::toString(d.workerStackPlacement));
Serial.println(Strata::toString(d.requestSlotRegion));
Serial.println(Strata::toString(d.dispatchQueueStorageRegion));
Serial.printf(
"worker stacks: internal=%u external=%u unknown=%u\n",
static_cast<unsigned>(d.workerStacksInternal),
static_cast<unsigned>(d.workerStacksExternal),
static_cast<unsigned>(d.workerStacksUnknown));- HTTPS uses the ESP-IDF certificate bundle when available. If the project/core does not provide usable certificate bundle support, verified HTTPS fails with
TlsFailed. - Redirect following is limited to GET requests with absolute
http://orhttps://Locationheaders. Same-origin redirects are allowed by default; cross-origin and HTTPS-to-HTTP redirects require explicit opt-in. - Caller-supplied headers are stripped after an origin change. Intermediate redirect bodies are discarded.
- Request body views are copied into owned storage before submission returns, so the source only needs to remain valid for the submission call.
LinkJsonResponse::jsonand streaming chunk data are callback-scoped unless copied by the application.
| Document | Description |
|---|---|
docs/api.md |
Public classes, configuration, result types, diagnostics, and ownership. |
docs/callbacks.md |
Callback storage, binding, and execution context. |
docs/concurrency.md |
Dispatch queue, worker pool, lifecycle, and completion guarantees. |
docs/errors.md |
Error codes and HTTP status behavior. |
docs/json.md |
ArduinoJson helpers, Strata allocation, and JSON lifetime rules. |
docs/streaming.md |
Streaming downloads and cancellation. |
docs/memory.md |
Strata policy, bounded memory, diagnostics, and explicit copy behavior. |
docs/persistent-http.md |
Optional per-worker persistent HTTP clients. |
docs/release-validation.md |
Automated gates and physical v0.2.0 qualification. |
| Item | Support |
|---|---|
| Framework | Arduino ESP32 |
| Platform | espressif32 |
| Language | C++20 |
| Networking | ESP-IDF esp_http_client |
| HTTPS | ESP-IDF certificate bundle when available |
| Memory policy | Strata v0.1.2 |
| JSON | ArduinoJson >= 7.0.0 |
| Exceptions | Not used by Link |
| Status | 0.2.0 |
MIT - see LICENSE.md.
Part of the ZekStack ESP32 library stack.