Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
37 lines
1.5 KiB
TypeScript
37 lines
1.5 KiB
TypeScript
import { createServer, type IncomingHttpHeaders } from 'node:http'
|
|
import type { AddressInfo } from 'node:net'
|
|
|
|
/** Serves a `Request → Response` handler over a real socket (test helper). */
|
|
export async function serve(handler: (req: Request) => Promise<Response>): Promise<{ url: string; close: () => Promise<void> }> {
|
|
const server = createServer(async (req, res) => {
|
|
const chunks: Buffer[] = []
|
|
for await (const c of req) chunks.push(c as Buffer)
|
|
const headers = Object.entries(req.headers as IncomingHttpHeaders).flatMap(([k, v]) => (v === undefined ? [] : [[k, String(v)] as [string, string]]))
|
|
const request = new Request(`http://localhost${req.url}`, {
|
|
method: req.method,
|
|
headers,
|
|
body: req.method === 'GET' || req.method === 'HEAD' ? undefined : Buffer.concat(chunks),
|
|
})
|
|
const response = await handler(request)
|
|
res.writeHead(response.status, Object.fromEntries(response.headers))
|
|
if (!response.body) return void res.end()
|
|
const reader = response.body.getReader()
|
|
res.on('close', () => void reader.cancel().catch(() => {}))
|
|
try {
|
|
for (;;) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
res.write(value)
|
|
}
|
|
} finally {
|
|
res.end()
|
|
}
|
|
})
|
|
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve))
|
|
const { port } = server.address() as AddressInfo
|
|
return {
|
|
url: `http://127.0.0.1:${port}`,
|
|
close: () => new Promise((resolve) => (server.closeAllConnections(), server.close(() => resolve()))),
|
|
}
|
|
}
|