File
Blob: worker/src/server/http.ts
| 1 | import { demand, OperationError } from './rpc.ts'; |
| 2 | import type { RpcResult } from './rpc.ts'; |
| 3 | import type { z } from 'zod'; |
| 4 | |
| 5 | export type HttpApp = { Bindings: Env; Variables: { requestId: string } }; |
| 6 | |
| 7 | export function parse<T>(schema: z.ZodType<T>, value: unknown, message = 'Invalid request.'): T { |
| 8 | const result = schema.safeParse(value); |
| 9 | // Zod issues may contain sensitive input: never send them to a log or client. |
| 10 | if (!result.success) throw new OperationError(400, message); |
| 11 | return result.data; |
| 12 | } |
| 13 | export async function body(request: Request): Promise<unknown> { |
| 14 | const reader = request.body?.getReader(); |
| 15 | demand(reader, 400, 'Expected a JSON request.'); |
| 16 | const chunks: Uint8Array[] = []; |
| 17 | let length = 0; |
| 18 | for (;;) { |
| 19 | const { value, done } = await reader.read(); |
| 20 | if (done) break; |
| 21 | length += value.byteLength; |
| 22 | if (length > 20000) { |
| 23 | await reader.cancel(); |
| 24 | throw new OperationError(413, 'Request too large.'); |
| 25 | } |
| 26 | chunks.push(value); |
| 27 | } |
| 28 | const bytes = new Uint8Array(length); |
| 29 | let offset = 0; |
| 30 | for (const chunk of chunks) { |
| 31 | bytes.set(chunk, offset); |
| 32 | offset += chunk.byteLength; |
| 33 | } |
| 34 | try { |
| 35 | return JSON.parse(new TextDecoder().decode(bytes)); |
| 36 | } catch { |
| 37 | throw new OperationError(400, 'Invalid JSON request.'); |
| 38 | } |
| 39 | } |
| 40 | export function json(value: unknown, status = 200, headers: Record<string, string> = {}): Response { |
| 41 | return Response.json(value, { status, headers: { 'Cache-Control': 'no-store', ...headers } }); |
| 42 | } |
| 43 | |
| 44 | export function rpcResponse<T>(result: RpcResult<T>, requestId: string): Response { |
| 45 | return result.ok |
| 46 | ? json(result.value, 200, { 'X-Request-Id': requestId }) |
| 47 | : json({ error: result.error, requestId }, result.status); |
| 48 | } |