From dd083ffc21b823f677970e485e71de2aa8e21915 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sun, 27 Sep 2026 10:05:10 -0500 Subject: [PATCH] feat(web): delete a reply's temporary file once it is sent Co-Authored-By: Claude Opus 5.5 --- dWeb/HTTPReply.h | 3 +++ dWeb/Web.cpp | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dWeb/HTTPReply.h b/dWeb/HTTPReply.h index 1083122c7..9f832426f 100644 --- a/dWeb/HTTPReply.h +++ b/dWeb/HTTPReply.h @@ -32,6 +32,9 @@ struct HTTPReply { // When set on a 200 reply, this file is streamed from disk as the body (with contentType and headers) instead of // message, so large downloads never sit in memory std::string file{}; + // Delete `file` once it's being sent (a temporary file made for this reply). Where an open file can't be deleted + // (Windows) it stays, so whoever makes such files should also clear out old ones. + bool removeFile{}; // Set by Web::Defer: the handler answers later, from another thread (DeferredReply), so nothing is sent now std::shared_ptr deferred{}; }; diff --git a/dWeb/Web.cpp b/dWeb/Web.cpp index 9ef2dc369..a58a16bab 100644 --- a/dWeb/Web.cpp +++ b/dWeb/Web.cpp @@ -266,6 +266,13 @@ namespace { return reply.message.size(); } + // A temporary file the reply sent: gone once mongoose has it open (on Windows, left for its maker to clear out) + void RemoveSentFile(const HTTPReply& reply) { + if (!reply.removeFile || reply.file.empty()) return; + std::error_code ec; + std::filesystem::remove(reply.file, ec); + } + void CountRequest(const std::string& route, uint16_t status, TrafficClock::time_point started, uint64_t bytes) { const auto micros = std::chrono::duration_cast(TrafficClock::now() - started).count(); TrafficStats::Local().Http(TrafficStats::Now(), route, status, static_cast(std::max(micros, 0)), bytes); @@ -505,6 +512,7 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms SendReply(connection, reply, http_msg); CountRequest(trafficRoute, static_cast(reply.status), started, ReplyBytes(reply)); + RemoveSentFile(reply); } @@ -780,13 +788,14 @@ void Web::SendDeferredReplies() { for (auto& finished : g_Deferred.Drain()) { mg_connection* connection = mgr.conns; while (connection && connection->id != finished.connection) connection = connection->next; - if (!connection || connection->is_closing) continue; + if (!connection || connection->is_closing) { RemoveSentFile(finished.reply); continue; } // Clears is_resp once the reply is out, so mongoose reads the connection's next request again SendReply(connection, finished.reply, nullptr); if (const auto timing = g_DeferredTiming.find(finished.connection); timing != g_DeferredTiming.end()) { CountRequest(timing->second.route, static_cast(finished.reply.status), timing->second.started, ReplyBytes(finished.reply)); g_DeferredTiming.erase(timing); } + RemoveSentFile(finished.reply); if (finished.close) connection->is_draining = 1; } }