From da50aee52c4d318cfc4106e4144d24885b149cd3 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Mon, 2 Mar 2026 10:02:23 +0100 Subject: [PATCH] [web] disable buffering when downloading large files (#31874) * [web] disable buffering when downloading large files * [web] fix unit tests GitOrigin-RevId: c8b0381962814fa62425364f03457600daf287ef --- services/web/app/src/Features/Compile/ClsiCacheController.mjs | 4 ++++ services/web/app/src/Features/Compile/CompileController.mjs | 3 +++ services/web/app/src/Features/History/HistoryController.mjs | 3 +++ services/web/app/src/infrastructure/Response.mjs | 2 ++ .../unit/src/Downloads/ProjectDownloadsController.test.mjs | 2 ++ 5 files changed, 14 insertions(+) diff --git a/services/web/app/src/Features/Compile/ClsiCacheController.mjs b/services/web/app/src/Features/Compile/ClsiCacheController.mjs index adb77697d2..b87d290487 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheController.mjs +++ b/services/web/app/src/Features/Compile/ClsiCacheController.mjs @@ -105,6 +105,10 @@ async function _downloadFromCacheWithParams( const TEN_MINUTES_IN_MS = 10 * 60 * 1000 res.setTimeout(TEN_MINUTES_IN_MS) timer = setTimeout(() => ac.abort(), TEN_MINUTES_IN_MS) + + // Disable buffering in nginx + res.setHeader('X-Accel-Buffering', 'no') + try { res.writeHead(response.status) await pipeline( diff --git a/services/web/app/src/Features/Compile/CompileController.mjs b/services/web/app/src/Features/Compile/CompileController.mjs index 8919a4bec1..a0334988ff 100644 --- a/services/web/app/src/Features/Compile/CompileController.mjs +++ b/services/web/app/src/Features/Compile/CompileController.mjs @@ -632,6 +632,9 @@ const _CompileController = { clearTimeout(timeout) timeout = setTimeout(() => ac.abort(), TEN_MINUTES_IN_MS) + // Disable buffering in nginx + res.setHeader('X-Accel-Buffering', 'no') + res.writeHead(response.status) await pipeline(stream, res) timer.labels.status = 'success' diff --git a/services/web/app/src/Features/History/HistoryController.mjs b/services/web/app/src/Features/History/HistoryController.mjs index 2175b9114d..0887098dd6 100644 --- a/services/web/app/src/Features/History/HistoryController.mjs +++ b/services/web/app/src/Features/History/HistoryController.mjs @@ -89,6 +89,9 @@ async function requestBlob(method, req, res) { res.setHeader('Content-Type', 'application/octet-stream') setBlobCacheHeaders(res, hash) + // Disable buffering in nginx + res.setHeader('X-Accel-Buffering', 'no') + try { await pipeline(stream, res) } catch (err) { diff --git a/services/web/app/src/infrastructure/Response.mjs b/services/web/app/src/infrastructure/Response.mjs index 75fb7fab2f..73ffaf3a55 100644 --- a/services/web/app/src/infrastructure/Response.mjs +++ b/services/web/app/src/infrastructure/Response.mjs @@ -31,6 +31,8 @@ export function prepareZipAttachment(res, filename) { // res.attachment sets both content-type and content-disposition headers. res.attachment(filename) res.setHeader('X-Content-Type-Options', 'nosniff') + // Disable buffering in nginx + res.setHeader('X-Accel-Buffering', 'no') } export function zipAttachment(res, body, filename) { diff --git a/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs b/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs index 9c85e52e18..9da1a08708 100644 --- a/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs +++ b/services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs @@ -113,6 +113,7 @@ describe('ProjectDownloadsController', function () { ctx.res.headers.should.deep.equal({ 'Content-Disposition': `attachment; filename="project_name_with_accĂȘnts_and___special_characters.zip"`, 'Content-Type': 'application/zip', + 'X-Accel-Buffering': 'no', 'X-Content-Type-Options': 'nosniff', }) }) @@ -184,6 +185,7 @@ describe('ProjectDownloadsController', function () { 'Content-Disposition': 'attachment; filename="Overleaf Projects (2 items).zip"', 'Content-Type': 'application/zip', + 'X-Accel-Buffering': 'no', 'X-Content-Type-Options': 'nosniff', }) })