返回 CodeWhale
bounded-body.ts
根目录 / web / lib / bounded-body.ts
1 export class BodyReadError extends Error {
2 constructor(readonly status: 400 | 413, message: string) {
3 super(message);
4 this.name = "BodyReadError";
5 }
6 }
7
8 /** Count raw bytes while reading; Content-Length is only an early rejection. */
9 export async function readBoundedBody(request: { body: ReadableStream<Uint8Array> | null; headers: Headers }, maxBytes: number): Promise<Uint8Array> {
10 const reject = async (status: 400 | 413, message: string): Promise<never> => {
11 try { await request.body?.cancel(message); } catch { /* Keep the original rejection. */ }
12 throw new BodyReadError(status, message);
13 };
14 const rawLength = request.headers.get("content-length");
15 if (rawLength !== null) {
16 if (!/^\d+$/.test(rawLength)) return reject(400, "invalid Content-Length");
17 if (Number(rawLength) > maxBytes) return reject(413, "payload too large");
18 }
19 if (!request.body) return new Uint8Array();
20
21 const reader = request.body.getReader();
22 const chunks: Uint8Array[] = [];
23 let total = 0;
24 try {
25 while (true) {
26 const { done, value } = await reader.read();
27 if (done) break;
28 total += value.byteLength;
29 if (total > maxBytes) throw new BodyReadError(413, "payload too large");
30 chunks.push(value);
31 }
32 } catch (cause) {
33 try { await reader.cancel("body rejected"); } catch { /* Preserve the read/size error. */ }
34 throw cause instanceof BodyReadError ? cause : new BodyReadError(400, "body read failed");
35 } finally {
36 reader.releaseLock();
37 }
38 const bytes = new Uint8Array(total);
39 let offset = 0;
40 for (const chunk of chunks) {
41 bytes.set(chunk, offset);
42 offset += chunk.byteLength;
43 }
44 return bytes;
45 }
46
46 lines TYPESCRIPT