From f4f87627532473319ad74815428758d41792163b Mon Sep 17 00:00:00 2001 From: Ritesh Kudkelwar Date: Thu, 2 Oct 2025 20:33:07 +0530 Subject: [PATCH 1/5] Add chunkedResponseModeStart, sendChunk, chunkedResponseFinalize to WebServer library for chunked HTTP responses --- libraries/WebServer/src/WebServer.cpp | 72 +++++++++++++++++++++++++++ libraries/WebServer/src/WebServer.h | 8 +++ 2 files changed, 80 insertions(+) diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 7523e40259b..9927addd195 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -551,6 +551,78 @@ void WebServer::enableETag(bool enable, ETagFunction fn) { _eTagFunction = fn; } +void WebServer::chunkResponseBegin(const char* contentType) { + if (_chunkedResponseActive) { + log_e("Already in chunked response mode"); + return; + } + + if (strchr(contentType, '\r') || strchr(contentType, '\n')) { + log_e("Invalid character in content type"); + return; + } + + _chunkedResponseActive = true; + _chunkedClient = _currentClient; + + _contentLength = CONTENT_LENGTH_UNKNOWN; + + String header; + _prepareHeader(header, 200, contentType, 0); + _currentClientWrite(header.c_str(), header.length()); + + _chunkedResponseActive = true; + _chunkedClient = _currentClient; +} + +void WebServer::chunkWrite(const char* data, size_t length) { + if (!_chunkedResponseActive) + { + log_e("Chunked response has not been started"); + return; + } + + char chunkSize[11]; + snprintf(chunkSize, sizeof(chunkSize), "%zx\r\n", length); + + if (_chunkedClient.write(chunkSize) != strlen(chunkSize)) { + log_e("Failed to write chunk size"); + _chunkedResponseActive = false; + return; + } + + if (_chunkedClient.write((const uint8_t*)data, length) != length) { + log_e("Failed to write chunk data"); + _chunkedResponseActive = false; + return; + } + + if (_chunkedClient.write("\r\n") != 2) { + log_e("Failed to write chunk terminator"); + _chunkedResponseActive = false; + return; + } +} + +void WebServer::chunkResponseEnd() { + if (!_chunkedResponseActive) + { + log_e("Chunked response has not been started"); + return; + } + + if (_chunkedClient.write("0\r\n\r\n", 5) != 5) { + log_e("Failed to write terminating chunk"); + } + + _chunkedClient.flush(); + _chunkedResponseActive = false; + _chunked = false; + _chunkedClient = NetworkClient(); + + _clearResponseHeaders(); +} + void WebServer::_prepareHeader(String &response, int code, const char *content_type, size_t contentLength) { _responseCode = code; diff --git a/libraries/WebServer/src/WebServer.h b/libraries/WebServer/src/WebServer.h index 8daf12c5c30..64b1c229107 100644 --- a/libraries/WebServer/src/WebServer.h +++ b/libraries/WebServer/src/WebServer.h @@ -115,6 +115,10 @@ class WebServer { const String AuthTypeDigest = F("Digest"); const String AuthTypeBasic = F("Basic"); + void chunkResponseBegin(const char* contentType = "text/plain"); + void chunkWrite(const char* data, size_t length); + void chunkResponseEnd(); + /* Callbackhandler for authentication. The extra parameters depend on the * HTTPAuthMethod mode: * @@ -241,6 +245,10 @@ class WebServer { static String responseCodeToString(int code); +private: + bool _chunkedResponseActive = false; + NetworkClient _chunkedClient; // Store by value, no dangling pointer + protected: virtual size_t _currentClientWrite(const char *b, size_t l) { return _currentClient.write(b, l); From c1b8036ab8e8f5d0df33c44379701318037a7ded Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 4 Oct 2025 16:30:54 -0300 Subject: [PATCH 2/5] feat(webserver): Chunk example for ESP32 HTTP server This example demonstrates how to send an HTTP response using chunks with an ESP32 server. It includes setup for WiFi, MDNS, and handles HTTP requests with chunked responses. --- .../examples/ChunkWriting/ChunkWriting.ino | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino diff --git a/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino b/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino new file mode 100644 index 00000000000..53103c12449 --- /dev/null +++ b/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino @@ -0,0 +1,77 @@ +/* + * This example demonstrates how to send an HTTP response using chunks + * It will create an HTTP Server (port 80) associated with an a MDNS service + * Access the HTTP server using a Web Browser: + * URL can be composed using the MDNS name "esp32_chunk_resp.local" + * http://esp32_chunk_resp.local/ + * or the IP Address that will be printed out, such as for instance 192.168.1.10 + * http://192.168.1.10/ + * + * ESP32 Server response can also be viewed using the curl command: + * curl -i esp32_chunk_resp.local:80 + * curl -i --raw esp32_chunk_resp.local:80 + */ + + +#include +#include +#include +#include + +const char *ssid = "........"; +const char *password = "........"; + +WebServer server(80); + +void handleChunks() { + uint8_t countDown = 10; + //server.chunkResponseBegin("Transfer-Encoding: chunked"); + server.chunkResponseBegin(); + char countContent[8]; + while (countDown) { + sprintf(countContent, "%d...\r\n", countDown--); + server.chunkWrite(countContent, strlen(countContent)); + // count down shall show up in the browser only after about 5 seconds when finishing the whole transmission + // using "curl -i esp32_chunk_resp.local:80", it will show the count down as it sends each chunk + delay(500); + } + server.chunkWrite("DONE!", 5); + server.chunkResponseEnd(); +} + +void setup(void) { + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Use the URL: http://esp32_chunk_resp.local/ + if (MDNS.begin("esp32_chunk_resp")) { + Serial.println("MDNS responder started"); + } + + server.on("/", handleChunks); + + server.onNotFound([]() { + server.send(404, "text/plain", "Page not found"); + }); + + server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + server.handleClient(); + delay(2); //allow the cpu to switch to other tasks +} From 0310c973a35be4a153f6699444cf6ac4e31e10c3 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 4 Oct 2025 16:31:53 -0300 Subject: [PATCH 3/5] feat(webserver): Add ci.json for ChunkWriting example configuration --- libraries/WebServer/examples/ChunkWriting/ci.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 libraries/WebServer/examples/ChunkWriting/ci.json diff --git a/libraries/WebServer/examples/ChunkWriting/ci.json b/libraries/WebServer/examples/ChunkWriting/ci.json new file mode 100644 index 00000000000..618e46bd244 --- /dev/null +++ b/libraries/WebServer/examples/ChunkWriting/ci.json @@ -0,0 +1,6 @@ +{ + "requires_any": [ + "CONFIG_SOC_WIFI_SUPPORTED=y", + "CONFIG_ESP_WIFI_REMOTE_ENABLED=y" + ] +} From 078e882e2048638c3f03366cd665022820d8dcdd Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sat, 4 Oct 2025 16:33:49 -0300 Subject: [PATCH 4/5] fix(example): not necessary code line --- libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino b/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino index 53103c12449..d25bbcdae7f 100644 --- a/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino +++ b/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino @@ -25,7 +25,6 @@ WebServer server(80); void handleChunks() { uint8_t countDown = 10; - //server.chunkResponseBegin("Transfer-Encoding: chunked"); server.chunkResponseBegin(); char countContent[8]; while (countDown) { From dfaa04bc16561b16303a7e45cbdb8e0a809b5478 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:16:02 +0000 Subject: [PATCH 5/5] ci(pre-commit): Apply automatic fixes --- .../examples/ChunkWriting/ChunkWriting.ino | 7 +++---- libraries/WebServer/src/WebServer.cpp | 14 ++++++-------- libraries/WebServer/src/WebServer.h | 6 +++--- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino b/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino index d25bbcdae7f..50c29322302 100644 --- a/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino +++ b/libraries/WebServer/examples/ChunkWriting/ChunkWriting.ino @@ -2,17 +2,16 @@ * This example demonstrates how to send an HTTP response using chunks * It will create an HTTP Server (port 80) associated with an a MDNS service * Access the HTTP server using a Web Browser: - * URL can be composed using the MDNS name "esp32_chunk_resp.local" + * URL can be composed using the MDNS name "esp32_chunk_resp.local" * http://esp32_chunk_resp.local/ - * or the IP Address that will be printed out, such as for instance 192.168.1.10 + * or the IP Address that will be printed out, such as for instance 192.168.1.10 * http://192.168.1.10/ * - * ESP32 Server response can also be viewed using the curl command: + * ESP32 Server response can also be viewed using the curl command: * curl -i esp32_chunk_resp.local:80 * curl -i --raw esp32_chunk_resp.local:80 */ - #include #include #include diff --git a/libraries/WebServer/src/WebServer.cpp b/libraries/WebServer/src/WebServer.cpp index 9927addd195..e67fcec05e4 100644 --- a/libraries/WebServer/src/WebServer.cpp +++ b/libraries/WebServer/src/WebServer.cpp @@ -551,7 +551,7 @@ void WebServer::enableETag(bool enable, ETagFunction fn) { _eTagFunction = fn; } -void WebServer::chunkResponseBegin(const char* contentType) { +void WebServer::chunkResponseBegin(const char *contentType) { if (_chunkedResponseActive) { log_e("Already in chunked response mode"); return; @@ -575,12 +575,11 @@ void WebServer::chunkResponseBegin(const char* contentType) { _chunkedClient = _currentClient; } -void WebServer::chunkWrite(const char* data, size_t length) { - if (!_chunkedResponseActive) - { +void WebServer::chunkWrite(const char *data, size_t length) { + if (!_chunkedResponseActive) { log_e("Chunked response has not been started"); return; - } + } char chunkSize[11]; snprintf(chunkSize, sizeof(chunkSize), "%zx\r\n", length); @@ -591,7 +590,7 @@ void WebServer::chunkWrite(const char* data, size_t length) { return; } - if (_chunkedClient.write((const uint8_t*)data, length) != length) { + if (_chunkedClient.write((const uint8_t *)data, length) != length) { log_e("Failed to write chunk data"); _chunkedResponseActive = false; return; @@ -605,8 +604,7 @@ void WebServer::chunkWrite(const char* data, size_t length) { } void WebServer::chunkResponseEnd() { - if (!_chunkedResponseActive) - { + if (!_chunkedResponseActive) { log_e("Chunked response has not been started"); return; } diff --git a/libraries/WebServer/src/WebServer.h b/libraries/WebServer/src/WebServer.h index 64b1c229107..498bcb5806c 100644 --- a/libraries/WebServer/src/WebServer.h +++ b/libraries/WebServer/src/WebServer.h @@ -115,8 +115,8 @@ class WebServer { const String AuthTypeDigest = F("Digest"); const String AuthTypeBasic = F("Basic"); - void chunkResponseBegin(const char* contentType = "text/plain"); - void chunkWrite(const char* data, size_t length); + void chunkResponseBegin(const char *contentType = "text/plain"); + void chunkWrite(const char *data, size_t length); void chunkResponseEnd(); /* Callbackhandler for authentication. The extra parameters depend on the @@ -247,7 +247,7 @@ class WebServer { private: bool _chunkedResponseActive = false; - NetworkClient _chunkedClient; // Store by value, no dangling pointer + NetworkClient _chunkedClient; // Store by value, no dangling pointer protected: virtual size_t _currentClientWrite(const char *b, size_t l) {