|
1 | 1 | import type { IncomingMessage, ServerResponse, OutgoingHttpHeaders } from 'node:http' |
2 | 2 | import type { Http2ServerRequest, Http2ServerResponse } from 'node:http2' |
3 | | -import { Readable } from 'node:stream' |
4 | 3 | import type { FetchCallback } from './types' |
5 | 4 | import './globals' |
| 5 | +import './response' |
| 6 | +import { requestPrototype } from './request' |
6 | 7 | import { writeFromReadableStream } from './utils' |
7 | 8 |
|
8 | 9 | const regBuffer = /^no$/i |
9 | 10 | const regContentType = /^(application\/json\b|text\/(?!event-stream\b))/i |
10 | 11 |
|
11 | | -const globalResponse = global.Response |
12 | | -const responsePrototype: Record<string, any> = { |
13 | | - getResponseCache() { |
14 | | - delete this.__cache |
15 | | - return (this.responseCache ||= new globalResponse(this.__body, this.__init)) |
16 | | - }, |
17 | | -} |
18 | | -;[ |
19 | | - 'body', |
20 | | - 'bodyUsed', |
21 | | - 'headers', |
22 | | - 'ok', |
23 | | - 'redirected', |
24 | | - 'statusText', |
25 | | - 'trailers', |
26 | | - 'type', |
27 | | - 'url', |
28 | | -].forEach((k) => { |
29 | | - Object.defineProperty(responsePrototype, k, { |
30 | | - get() { |
31 | | - return this.getResponseCache()[k] |
32 | | - }, |
33 | | - }) |
34 | | -}) |
35 | | -;['arrayBuffer', 'blob', 'clone', 'error', 'formData', 'json', 'redirect', 'text'].forEach((k) => { |
36 | | - Object.defineProperty(responsePrototype, k, { |
37 | | - value: function () { |
38 | | - return this.getResponseCache()[k]() |
39 | | - }, |
40 | | - }) |
41 | | -}) |
42 | | - |
43 | | -function newResponse(this: Response, body: BodyInit | null, init?: ResponseInit) { |
44 | | - ;(this as any).status = init?.status || 200 |
45 | | - ;(this as any).__body = body |
46 | | - ;(this as any).__init = init |
47 | | - if (typeof body === 'string' || body instanceof ReadableStream) { |
48 | | - ;(this as any).__cache = [body, (init?.headers || {}) as Record<string, string>] |
49 | | - } |
50 | | -} |
51 | | -newResponse.prototype = responsePrototype |
52 | | -Object.defineProperty(global, 'Response', { |
53 | | - value: newResponse, |
54 | | -}) |
55 | | - |
56 | | -function newRequestFromIncoming( |
57 | | - method: string, |
58 | | - url: string, |
59 | | - incoming: IncomingMessage | Http2ServerRequest |
60 | | -): Request { |
61 | | - const headerRecord: [string, string][] = [] |
62 | | - const len = incoming.rawHeaders.length |
63 | | - for (let i = 0; i < len; i += 2) { |
64 | | - headerRecord.push([incoming.rawHeaders[i], incoming.rawHeaders[i + 1]]) |
65 | | - } |
66 | | - |
67 | | - const init = { |
68 | | - method: method, |
69 | | - headers: headerRecord, |
70 | | - } as RequestInit |
71 | | - |
72 | | - if (!(method === 'GET' || method === 'HEAD')) { |
73 | | - // lazy-consume request body |
74 | | - init.body = Readable.toWeb(incoming) as ReadableStream<Uint8Array> |
75 | | - // node 18 fetch needs half duplex mode when request body is stream |
76 | | - ;(init as any).duplex = 'half' |
77 | | - } |
78 | | - |
79 | | - return new Request(url, init) |
80 | | -} |
81 | | - |
82 | | -const requestPrototype: Record<string, any> = { |
83 | | - getRequestCache() { |
84 | | - return (this.requestCache ||= newRequestFromIncoming(this.method, this.url, this.incoming)) |
85 | | - }, |
86 | | -} |
87 | | -;[ |
88 | | - 'body', |
89 | | - 'bodyUsed', |
90 | | - 'cache', |
91 | | - 'credentials', |
92 | | - 'destination', |
93 | | - 'headers', |
94 | | - 'integrity', |
95 | | - 'mode', |
96 | | - 'redirect', |
97 | | - 'referrer', |
98 | | - 'referrerPolicy', |
99 | | - 'signal', |
100 | | -].forEach((k) => { |
101 | | - Object.defineProperty(requestPrototype, k, { |
102 | | - get() { |
103 | | - return this.getRequestCache()[k] |
104 | | - }, |
105 | | - }) |
106 | | -}) |
107 | | -;['arrayBuffer', 'blob', 'clone', 'formData', 'json', 'text'].forEach((k) => { |
108 | | - Object.defineProperty(requestPrototype, k, { |
109 | | - value: function () { |
110 | | - return this.getRequestCache()[k]() |
111 | | - }, |
112 | | - }) |
113 | | -}) |
114 | | - |
115 | 12 | const handleFetchError = (e: unknown): Response => |
116 | 13 | new Response(null, { |
117 | 14 | status: |
@@ -218,10 +115,14 @@ export const getRequestListener = (fetchCallback: FetchCallback) => { |
218 | 115 | outgoing: ServerResponse | Http2ServerResponse |
219 | 116 | ) => { |
220 | 117 | let res |
| 118 | + |
| 119 | + // `fetchCallback()` requests a Request object, but global.Request is expensive to generate, |
| 120 | + // so generate a pseudo Request object with only the minimum required information. |
221 | 121 | const req = Object.create(requestPrototype) |
222 | 122 | req.method = incoming.method || 'GET' |
223 | 123 | req.url = `http://${incoming.headers.host}${incoming.url}` |
224 | 124 | req.incoming = incoming |
| 125 | + |
225 | 126 | try { |
226 | 127 | res = fetchCallback(req) as Response | Promise<Response> |
227 | 128 | if ('__cache' in res) { |
|
0 commit comments