feat(web): delete a reply's temporary file once it is sent

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 10:05:10 -05:00
parent a83ecdff2f
commit dd083ffc21
2 changed files with 13 additions and 1 deletions

View File

@@ -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<DeferredState> deferred{};
};

View File

@@ -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<std::chrono::microseconds>(TrafficClock::now() - started).count();
TrafficStats::Local().Http(TrafficStats::Now(), route, status, static_cast<uint64_t>(std::max<int64_t>(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<uint16_t>(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<uint16_t>(finished.reply.status), timing->second.started, ReplyBytes(finished.reply));
g_DeferredTiming.erase(timing);
}
RemoveSentFile(finished.reply);
if (finished.close) connection->is_draining = 1;
}
}