21#include "esp_http_client.h"
22#include "esp_crt_bundle.h"
35struct EspHttpClientDeleter {
36 void operator()(esp_http_client_handle_t h)
const noexcept
38 if (h) esp_http_client_cleanup(h);
42using EspHttpClient = std::unique_ptr<
43 std::remove_pointer_t<esp_http_client_handle_t>, EspHttpClientDeleter>;
48 void* owner =
nullptr;
52 esp_http_client_handle_t client =
nullptr;
58 size_t body_cursor = 0;
60 size_t content_length = 0;
63constexpr size_t MAX_HTTP_SLOTS = 4;
64constexpr size_t MAX_HTTP_BODY_BYTES = 1 * 1024 * 1024;
79constexpr size_t MAX_HTTP_CONNS = MAX_HTTP_SLOTS;
80PoolConn s_pool[MAX_HTTP_CONNS];
81HttpSlot* s_active_slot =
nullptr;
84std::string originKey(
const char* url)
86 std::string u(url ? url :
"");
87 size_t scheme = u.find(
"://");
88 if (scheme == std::string::npos)
return u;
89 size_t path = u.find(
'/', scheme + 3);
90 return path == std::string::npos ? u : u.substr(0, path);
95bool appendBody(HttpSlot& slot,
const char* data,
size_t len)
97 if (!data || len == 0)
return true;
98 size_t needed = slot.body_len + len + 1;
99 if (needed > MAX_HTTP_BODY_BYTES)
return false;
100 if (needed > slot.body_cap) {
101 size_t new_cap = slot.body_cap ? slot.body_cap : 4096;
102 while (new_cap < needed) new_cap *= 2;
103 if (new_cap > MAX_HTTP_BODY_BYTES) new_cap = MAX_HTTP_BODY_BYTES;
104 char* raw = slot.body.release();
105 char* grown =
static_cast<char*
>(std::realloc(raw, new_cap));
107 slot.body.reset(raw);
110 slot.body.reset(grown);
111 slot.body_cap = new_cap;
113 std::memcpy(slot.body.get() + slot.body_len, data, len);
114 slot.body_len += len;
115 slot.body.get()[slot.body_len] =
'\0';
119esp_err_t http_event_handler(esp_http_client_event_t* evt)
123 HttpSlot* slot = s_active_slot;
124 if (!slot)
return ESP_OK;
125 if (evt->event_id == HTTP_EVENT_ON_DATA && evt->data && evt->data_len > 0) {
126 if (!appendBody(*slot,
static_cast<const char*
>(evt->data),
127 static_cast<size_t>(evt->data_len))) {
134HttpSlot* slotFor(
int handle) {
return s_slots.
lookup(handle); }
139void closeSlot(HttpSlot& slot)
141 if (slot.pool_idx >= 0 &&
static_cast<size_t>(slot.pool_idx) < MAX_HTTP_CONNS) {
142 s_pool[slot.pool_idx].in_use =
false;
147esp_http_client_method_t mapMethod(uint8_t m) {
149 case HTTP_GET:
return HTTP_METHOD_GET;
151 case HTTP_PUT:
return HTTP_METHOD_PUT;
153 default:
return HTTP_METHOD_GET;
161esp_http_client_handle_t
makeClient(uint8_t method,
const char* url, uint32_t timeout_ms)
163 esp_http_client_config_t cfg{};
165 cfg.method = mapMethod(method);
166 cfg.timeout_ms = timeout_ms ?
static_cast<int>(timeout_ms) : 5000;
167 cfg.event_handler = http_event_handler;
168 cfg.disable_auto_redirect =
false;
169 cfg.crt_bundle_attach = esp_crt_bundle_attach;
170 cfg.keep_alive_enable =
true;
171 cfg.buffer_size = 4096;
172 cfg.buffer_size_tx = 1024;
173 esp_http_client_handle_t h = esp_http_client_init(&cfg);
174 if (!h)
LOG_E(
"HTTP",
"esp_http_client_init failed for url='%s'", url);
182 HttpSlot* slot = s_slots.
allocate(slot_id);
189 std::string key = originKey(url);
192 for (
size_t i = 0; i < MAX_HTTP_CONNS; ++i) {
193 if (s_pool[i].client && !s_pool[i].in_use && s_pool[i].origin == key) {
194 esp_http_client_set_url(s_pool[i].client.get(), url);
195 esp_http_client_set_method(s_pool[i].client.get(), mapMethod(method));
196 s_pool[i].in_use =
true;
197 slot->pool_idx =
static_cast<int>(i);
198 slot->client = s_pool[i].client.get();
204 esp_http_client_handle_t h =
makeClient(method, url, timeout_ms);
212 for (
size_t i = 0; i < MAX_HTTP_CONNS; ++i) {
213 if (!s_pool[i].client) { idx =
static_cast<int>(i);
break; }
216 for (
size_t i = 0; i < MAX_HTTP_CONNS; ++i) {
217 if (!s_pool[i].in_use) { idx =
static_cast<int>(i);
break; }
221 s_pool[idx].client.reset(h);
222 s_pool[idx].origin = key;
223 s_pool[idx].in_use =
true;
224 slot->pool_idx = idx;
228 slot->owned.reset(h);
237 auto* slot = slotFor(handle);
239 return esp_http_client_set_header(slot->client, key, value) == ESP_OK
245 auto* slot = slotFor(handle);
247 return esp_http_client_set_post_field(slot->client,
248 reinterpret_cast<const char*
>(body),
249 static_cast<int>(len)) == ESP_OK
255 auto* slot = slotFor(handle);
257 s_active_slot = slot;
258 esp_err_t err = esp_http_client_perform(slot->client);
259 s_active_slot =
nullptr;
260 slot->status = esp_http_client_get_status_code(slot->client);
261 slot->content_length =
static_cast<size_t>(
262 esp_http_client_get_content_length(slot->client));
272 if (slot->status == 0) {
273 LOG_E(
"HTTP",
"perform failed: %s (0x%x)", esp_err_to_name(err), err);
275 LOG_W(
"HTTP",
"perform err %s with status=%d; body not drained",
276 esp_err_to_name(err), slot->status);
278 if (slot->pool_idx >= 0 &&
static_cast<size_t>(slot->pool_idx) < MAX_HTTP_CONNS) {
279 s_pool[slot->pool_idx] = PoolConn{};
283 slot->client =
nullptr;
285 if (slot->status == 0) {
289 LOG_I(
"HTTP",
"status=%d content_length=%zu", slot->status, slot->content_length);
295 auto* slot = slotFor(handle);
301 auto* slot = slotFor(handle);
303 size_t remaining = slot->body_len - slot->body_cursor;
304 size_t n = remaining < buf_size ? remaining : buf_size;
305 if (n) std::memcpy(buf, slot->body.get() + slot->body_cursor, n);
306 slot->body_cursor += n;
313 auto* slot = slotFor(handle);
314 return slot ? slot->content_length : 0;
319 auto* slot = slotFor(handle);
328 for (
size_t i = 0; i < MAX_HTTP_SLOTS; ++i) {
329 HttpSlot& slot = s_slots.
slots[i];
330 if (!slot.used || slot.owner != plugin)
continue;
331 LOG_W(
"HTTP",
"force-closing leaked HTTP slot %u",
static_cast<unsigned>(i + 1));
332 if (s_active_slot == &slot) s_active_slot =
nullptr;
339 for (
size_t i = 0; i < MAX_HTTP_CONNS; ++i) {
340 if (!s_pool[i].in_use) s_pool[i] = PoolConn{};
Fixed-capacity, 1-based slot table for host-API resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
int host_http_close(int handle)
Release a request handle.
int host_http_read_chunk(int handle, uint8_t *buf, size_t buf_size, size_t *out_len)
Stream one response chunk into buf.
int host_http_set_body(int handle, const uint8_t *body, size_t len)
Stage a request body before perform().
int host_http_status(int handle)
HTTP response status code, or negative on error.
int host_http_perform(int handle)
Send the request and read response headers.
size_t host_http_content_length(int handle)
Response Content-Length, or 0 when unknown / chunked.
int host_http_set_header(int handle, const char *key, const char *value)
Add a request header before perform().
int host_http_open(uint8_t method, const char *url, uint32_t timeout_ms)
Open an HTTP request.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_ERR_INVALID_ARG
#define HOST_ERR_NO_MEMORY
esp_http_client_handle_t makeClient(uint8_t method, const char *url, uint32_t timeout_ms)
void * plg_get_active_plugin(void)
void plg_http_on_unload(void *plugin)
::cdc::core::CStdUniquePtr< T > CStdUniquePtr
Thin alias layer that re-exports cdc::core RAII wrappers in the cdc::plugin_manager namespace for sou...
T * lookup(int id)
1-based lookup. Returns nullptr if id is out of range or slot unused.
T * allocate(int &out_id)