Fix FS drops connection if download is too slow

Only happens in HTTPS because seemingly somewhere in the stack up to
50mb of data is being buffered potentially triggering a timeout at a
higher level if not drained fast enough.

This commit also includes other streams related improvements.
This commit is contained in:
Zaid Abdulla committed 2022-11-01 09:13:36 -07:00
1 parent d53e10f62d
commit d1c01e0848
3 files changed
+48 -50

No files matched your search

+19 -17
View File
@@ -19,6 +19,7 @@
import * as url from "url";
import { IncomingMessage, ServerResponse } from "http";
import { pipeline, Readable } from "stream";
import { Collection, GridFSBucket } from "mongodb";
import { onConnect } from "./db";
import * as logger from "./logger";
@@ -34,21 +35,19 @@ onConnect(async (db) => {
});
const getFile = memoize(
(uploadDate: number, size: number, filename: string): Promise<Buffer> => {
return new Promise((resolve, reject) => {
const buffer = Buffer.allocUnsafe(size);
let i = 0;
const downloadStream = filesBucket.openDownloadStreamByName(filename);
downloadStream.on("error", reject);
downloadStream.on("data", (data: Buffer) => {
data.copy(buffer, i);
i += data.length;
});
downloadStream.on("end", () => {
if (i !== size) reject(new Error("File size mismatch"));
else resolve(buffer);
});
});
async (
uploadDate: number,
size: number,
filename: string
): Promise<Iterable<Buffer>> => {
const chunks: Buffer[] = [];
const downloadStream = filesBucket.openDownloadStreamByName(filename);
for await (const chunk of downloadStream) chunks.push(chunk);
// Node 12-14 don't throw error when stream is closed prematurely.
// However, we don't need to check for that since we're checking file size.
if (size !== chunks.reduce((a, b) => a + b.length, 0))
throw new Error("File size mismatch");
return chunks;
}
);
@@ -81,7 +80,7 @@ export async function listener(
return;
}
const buffer = await getFile(
const chunks = await getFile(
file["uploadDate"].getTime(),
file.length,
filename
@@ -92,6 +91,9 @@ export async function listener(
"Content-Length": file.length,
});
response.end(buffer);
pipeline(Readable.from(chunks), response, () => {
// Ignore errors resulting from client disconnecting
});
logger.accessInfo(log);
}