diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index cd06f80687..69b27e90ed 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -41,6 +41,11 @@ namespace esphome::web_server_idf { static const char *const TAG = "web_server_idf"; +// Chunk size for streaming request bodies; matches the Arduino AsyncWebServer buffer size. +// Buffers of this size must live on the heap - the httpd task stack is too small. +static constexpr size_t RECV_CHUNK_SIZE = 1460; +static constexpr size_t YIELD_INTERVAL_BYTES = 16 * 1024; // Yield every 16KB to prevent watchdog + // Global instance to avoid guard variable (saves 8 bytes) // This is initialized at program startup before any threads namespace { @@ -184,9 +189,10 @@ esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) { return server->handle_multipart_upload_(r, content_type_char); #endif } else { - ESP_LOGW(TAG, "Unsupported content type for POST: %s", content_type_char); - // fallback to get handler to support backward compatibility - return AsyncWebServer::request_handler(r); + // Other content types (e.g. application/json) are delivered raw to a matching + // custom handler via handleBody(), like the Arduino AsyncWebServer does + auto *server = static_cast(r->user_ctx); + return server->handle_raw_body_(r, content_type_char); } } @@ -237,6 +243,51 @@ esp_err_t AsyncWebServer::request_handler_(AsyncWebServerRequest *request) const return ESP_ERR_NOT_FOUND; } +esp_err_t AsyncWebServer::handle_raw_body_(httpd_req_t *r, const char *content_type) { + AsyncWebServerRequest req(r); + AsyncWebHandler *handler = nullptr; + for (auto *h : this->handlers_) { + if (h->canHandle(&req)) { + handler = h; + break; + } + } + + if (handler == nullptr) { + ESP_LOGW(TAG, "Unsupported content type for POST: %s", content_type); + // fallback to get handler to support backward compatibility + return this->request_handler_(&req); + } + + const size_t total = r->content_len; + if (total > 0) { + auto buffer = std::make_unique_for_overwrite(RECV_CHUNK_SIZE); + size_t bytes_since_yield = 0; + + for (size_t index = 0; index < total;) { + int recv_len = httpd_req_recv(r, buffer.get(), std::min(total - index, RECV_CHUNK_SIZE)); + + if (recv_len <= 0) { + httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST, + nullptr); + return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL; + } + + handler->handleBody(&req, reinterpret_cast(buffer.get()), recv_len, index, total); + index += recv_len; + bytes_since_yield += recv_len; + + if (bytes_since_yield > YIELD_INTERVAL_BYTES) { + vTaskDelay(1); + bytes_since_yield = 0; + } + } + } + + handler->handleRequest(&req); + return ESP_OK; +} + AsyncWebServerRequest::~AsyncWebServerRequest() { delete this->rsp_; for (auto *param : this->params_) { @@ -893,9 +944,6 @@ void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *e #ifdef USE_WEBSERVER_OTA esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *content_type) { - static constexpr size_t MULTIPART_CHUNK_SIZE = 1460; // Match Arduino AsyncWebServer buffer size - static constexpr size_t YIELD_INTERVAL_BYTES = 16 * 1024; // Yield every 16KB to prevent watchdog - // Parse boundary and create reader const char *boundary_start; size_t boundary_len; @@ -949,12 +997,11 @@ esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *c } }); - // Use heap buffer - 1460 bytes is too large for the httpd task stack - auto buffer = std::make_unique_for_overwrite(MULTIPART_CHUNK_SIZE); + auto buffer = std::make_unique_for_overwrite(RECV_CHUNK_SIZE); size_t bytes_since_yield = 0; for (size_t remaining = r->content_len; remaining > 0;) { - int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, MULTIPART_CHUNK_SIZE)); + int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, RECV_CHUNK_SIZE)); if (recv_len <= 0) { httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST, diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index c631cd1453..8b5fd5b726 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -233,6 +233,7 @@ class AsyncWebServer { static esp_err_t request_post_handler(httpd_req_t *r); esp_err_t request_handler_(AsyncWebServerRequest *request) const; static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd); + esp_err_t handle_raw_body_(httpd_req_t *r, const char *content_type); #ifdef USE_WEBSERVER_OTA esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type); #endif