From f1b8b75002fc4ca21ea725c4c81058a602107e0f Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 15 May 2026 14:23:19 +0200 Subject: [PATCH] stream WebDAV downloads in chunks to prevent client disconnects --- src/network/CrossPointWebServer.cpp | 26 ++++------------- src/network/HttpFileStreamer.cpp | 44 +++++++++++++++++++++++++++++ src/network/HttpFileStreamer.h | 8 ++++++ src/network/WebDAVHandler.cpp | 10 +++++-- 4 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 src/network/HttpFileStreamer.cpp create mode 100644 src/network/HttpFileStreamer.h diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 9d63b9eb..9aa114b0 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -15,6 +15,7 @@ #include "CrossPointSettings.h" #include "FontInstaller.h" +#include "HttpFileStreamer.h" #include "OpdsServerStore.h" #include "SdCardFontGlobals.h" #include "SdCardFontRegistry.h" @@ -681,27 +682,12 @@ void CrossPointWebServer::handleDownload() const { server->send(200, contentType.c_str(), ""); NetworkClient client = server->client(); - const size_t chunkSize = 4096; - uint8_t buffer[chunkSize]; - - bool downloadOk = true; - while (downloadOk && file.available()) { - int result = file.read(buffer, chunkSize); - if (result <= 0) break; - size_t bytesRead = static_cast(result); - size_t totalWritten = 0; - while (totalWritten < bytesRead) { - esp_task_wdt_reset(); - size_t wrote = client.write(buffer + totalWritten, bytesRead - totalWritten); - if (wrote == 0) { - downloadOk = false; - break; - } - totalWritten += wrote; - } - } + bool downloadOk = HttpFileStreamer::streamFileToClient(file, client); client.clear(); - file.close(); + + if (!downloadOk) { + LOG_DBG("WEB", "Download interrupted while streaming: %s", itemPath.c_str()); + } } // Diagnostic counters for upload performance analysis diff --git a/src/network/HttpFileStreamer.cpp b/src/network/HttpFileStreamer.cpp new file mode 100644 index 00000000..571b0cc9 --- /dev/null +++ b/src/network/HttpFileStreamer.cpp @@ -0,0 +1,44 @@ +#include "HttpFileStreamer.h" + +#include +#include + +namespace { +constexpr size_t DOWNLOAD_CHUNK_SIZE = 4096; +} + +namespace HttpFileStreamer { +bool streamFileToClient(FsFile& file, NetworkClient& client) { + auto* buffer = static_cast(malloc(DOWNLOAD_CHUNK_SIZE)); + if (!buffer) { + LOG_ERR("HTTP", "malloc failed: %zu bytes", DOWNLOAD_CHUNK_SIZE); + return false; + } + + bool ok = true; + while (ok) { + esp_task_wdt_reset(); + int result = file.read(buffer, DOWNLOAD_CHUNK_SIZE); + if (result < 0) { + ok = false; + break; + } + if (result == 0) break; + + size_t bytesRead = static_cast(result); + size_t totalWritten = 0; + while (totalWritten < bytesRead) { + esp_task_wdt_reset(); + size_t wrote = client.write(buffer + totalWritten, bytesRead - totalWritten); + if (wrote == 0) { + ok = false; + break; + } + totalWritten += wrote; + } + } + + free(buffer); + return ok; +} +} // namespace HttpFileStreamer diff --git a/src/network/HttpFileStreamer.h b/src/network/HttpFileStreamer.h new file mode 100644 index 00000000..91bb6276 --- /dev/null +++ b/src/network/HttpFileStreamer.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace HttpFileStreamer { +bool streamFileToClient(FsFile& file, NetworkClient& client); +} diff --git a/src/network/WebDAVHandler.cpp b/src/network/WebDAVHandler.cpp index 46ae4450..9dff1bf4 100644 --- a/src/network/WebDAVHandler.cpp +++ b/src/network/WebDAVHandler.cpp @@ -8,6 +8,8 @@ #include #include +#include "HttpFileStreamer.h" + namespace { const char* HIDDEN_ITEMS[] = {"System Volume Information", "XTCache"}; constexpr size_t HIDDEN_ITEMS_COUNT = sizeof(HIDDEN_ITEMS) / sizeof(HIDDEN_ITEMS[0]); @@ -330,8 +332,12 @@ void WebDAVHandler::handleGet(WebServer& s) { s.send(200, contentType.c_str(), ""); NetworkClient client = s.client(); - client.write(file); - file.close(); + bool downloadOk = HttpFileStreamer::streamFileToClient(file, client); + client.clear(); + + if (!downloadOk) { + LOG_DBG("DAV", "GET interrupted while streaming: %s", path.c_str()); + } } // ── HEAD ─────────────────────────────────────────────────────────────────────