Files
flynn/src/utils/httpBody.ts
T
2026-02-15 21:44:36 -08:00

66 lines
1.7 KiB
TypeScript

import type { IncomingMessage } from 'http';
export interface ReadRequestBodyOptions {
maxBytes: number;
}
export class RequestBodyTooLargeError extends Error {
readonly maxBytes: number;
readonly receivedBytes: number;
constructor(maxBytes: number, receivedBytes: number) {
super(`Request body too large (${receivedBytes} bytes > ${maxBytes} bytes)`);
this.name = 'RequestBodyTooLargeError';
this.maxBytes = maxBytes;
this.receivedBytes = receivedBytes;
}
}
/** Read the full request body with an explicit max-size limit. */
export function readRequestBody(req: IncomingMessage, opts: ReadRequestBodyOptions): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
let totalBytes = 0;
let settled = false;
const cleanup = () => {
req.off('data', onData);
req.off('end', onEnd);
req.off('error', onError);
};
const fail = (err: Error) => {
if (settled) {return;}
settled = true;
cleanup();
reject(err);
};
const onData = (chunk: Buffer | string) => {
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
totalBytes += buf.length;
if (totalBytes > opts.maxBytes) {
if (typeof req.destroy === 'function') {
req.destroy();
}
fail(new RequestBodyTooLargeError(opts.maxBytes, totalBytes));
return;
}
chunks.push(buf);
};
const onEnd = () => {
if (settled) {return;}
settled = true;
cleanup();
resolve(Buffer.concat(chunks).toString('utf-8'));
};
const onError = (err: Error) => fail(err);
req.on('data', onData);
req.on('end', onEnd);
req.on('error', onError);
});
}