Skip to content

Commit dcfa945

Browse files
committed
perf: Stop "async" and return results synchronously when possible.
1 parent f0738ea commit dcfa945

1 file changed

Lines changed: 87 additions & 72 deletions

File tree

src/listener.ts

Lines changed: 87 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,83 @@ const requestPrototype: Record<string, any> = {
169169
},
170170
}
171171

172+
const handleError = (e: unknown): Response =>
173+
new Response(null, {
174+
status:
175+
e instanceof Error && (e.name === 'TimeoutError' || e.constructor.name === 'TimeoutError')
176+
? 504 // timeout error emits 504 timeout
177+
: 500,
178+
})
179+
180+
const responseViaResponseObject = async (
181+
res: Response | Promise<Response>,
182+
outgoing: ServerResponse | Http2ServerResponse
183+
) => {
184+
try {
185+
res = await res
186+
} catch (e: unknown) {
187+
res = handleError(e)
188+
}
189+
190+
const resHeaderRecord: OutgoingHttpHeaders = {}
191+
const cookies = []
192+
for (const [k, v] of res.headers) {
193+
if (k === 'set-cookie') {
194+
cookies.push(v)
195+
} else {
196+
resHeaderRecord[k] = v
197+
}
198+
}
199+
if (cookies.length > 0) {
200+
resHeaderRecord['set-cookie'] = cookies
201+
}
202+
203+
if (res.body) {
204+
try {
205+
/**
206+
* If content-encoding is set, we assume that the response should be not decoded.
207+
* Else if transfer-encoding is set, we assume that the response should be streamed.
208+
* Else if content-length is set, we assume that the response content has been taken care of.
209+
* Else if x-accel-buffering is set to no, we assume that the response should be streamed.
210+
* Else if content-type is not application/json nor text/* but can be text/event-stream,
211+
* we assume that the response should be streamed.
212+
*/
213+
if (
214+
resHeaderRecord['transfer-encoding'] ||
215+
resHeaderRecord['content-encoding'] ||
216+
resHeaderRecord['content-length'] ||
217+
// nginx buffering variant
218+
(resHeaderRecord['x-accel-buffering'] &&
219+
regBuffer.test(resHeaderRecord['x-accel-buffering'] as string)) ||
220+
!regContentType.test(resHeaderRecord['content-type'] as string)
221+
) {
222+
outgoing.writeHead(res.status, resHeaderRecord)
223+
await writeFromReadableStream(res.body, outgoing)
224+
} else {
225+
const buffer = await res.arrayBuffer()
226+
resHeaderRecord['content-length'] = buffer.byteLength
227+
outgoing.writeHead(res.status, resHeaderRecord)
228+
outgoing.end(new Uint8Array(buffer))
229+
}
230+
} catch (e: unknown) {
231+
const err = (e instanceof Error ? e : new Error('unknown error', { cause: e })) as Error & {
232+
code: string
233+
}
234+
if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {
235+
console.info('The user aborted a request.')
236+
} else {
237+
console.error(e)
238+
outgoing.destroy(err)
239+
}
240+
}
241+
} else {
242+
outgoing.writeHead(res.status, resHeaderRecord)
243+
outgoing.end()
244+
}
245+
}
246+
172247
export const getRequestListener = (fetchCallback: FetchCallback) => {
173-
return async (
248+
return (
174249
incoming: IncomingMessage | Http2ServerRequest,
175250
outgoing: ServerResponse | Http2ServerResponse
176251
) => {
@@ -182,79 +257,19 @@ export const getRequestListener = (fetchCallback: FetchCallback) => {
182257
} as unknown as Request
183258
Object.setPrototypeOf(req, requestPrototype)
184259
try {
185-
res = (await fetchCallback(req)) as Response
186-
} catch (e: unknown) {
187-
res = new Response(null, { status: 500 })
188-
if (e instanceof Error) {
189-
// timeout error emits 504 timeout
190-
if (e.name === 'TimeoutError' || e.constructor.name === 'TimeoutError') {
191-
res = new Response(null, { status: 504 })
192-
}
193-
}
194-
}
195-
196-
if ((res as any).__cache) {
197-
const [body, header] = (res as any).__cache
198-
header['content-length'] ||= '' + Buffer.byteLength(body)
199-
outgoing.writeHead(res.status, header)
200-
outgoing.end(body)
201-
return
202-
}
203-
204-
const resHeaderRecord: OutgoingHttpHeaders = {}
205-
const cookies = []
206-
for (const [k, v] of res.headers) {
207-
if (k === 'set-cookie') {
208-
cookies.push(v)
209-
} else {
210-
resHeaderRecord[k] = v
260+
res = fetchCallback(req) as Response | Promise<Response>
261+
if ("__cache" in res) {
262+
// response via cache
263+
const [body, header] = (res as any).__cache
264+
header['content-length'] ||= '' + Buffer.byteLength(body)
265+
outgoing.writeHead((res as Response).status, header)
266+
outgoing.end(body)
267+
return
211268
}
212-
}
213-
if (cookies.length > 0) {
214-
resHeaderRecord['set-cookie'] = cookies
269+
} catch (e: unknown) {
270+
res = handleError(e)
215271
}
216272

217-
if (res.body) {
218-
try {
219-
/**
220-
* If content-encoding is set, we assume that the response should be not decoded.
221-
* Else if transfer-encoding is set, we assume that the response should be streamed.
222-
* Else if content-length is set, we assume that the response content has been taken care of.
223-
* Else if x-accel-buffering is set to no, we assume that the response should be streamed.
224-
* Else if content-type is not application/json nor text/* but can be text/event-stream,
225-
* we assume that the response should be streamed.
226-
*/
227-
if (
228-
resHeaderRecord['transfer-encoding'] ||
229-
resHeaderRecord['content-encoding'] ||
230-
resHeaderRecord['content-length'] ||
231-
// nginx buffering variant
232-
(resHeaderRecord['x-accel-buffering'] &&
233-
regBuffer.test(resHeaderRecord['x-accel-buffering'] as string)) ||
234-
!regContentType.test(resHeaderRecord['content-type'] as string)
235-
) {
236-
outgoing.writeHead(res.status, resHeaderRecord)
237-
await writeFromReadableStream(res.body, outgoing)
238-
} else {
239-
const buffer = await res.arrayBuffer()
240-
resHeaderRecord['content-length'] = buffer.byteLength
241-
outgoing.writeHead(res.status, resHeaderRecord)
242-
outgoing.end(new Uint8Array(buffer))
243-
}
244-
} catch (e: unknown) {
245-
const err = (e instanceof Error ? e : new Error('unknown error', { cause: e })) as Error & {
246-
code: string
247-
}
248-
if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {
249-
console.info('The user aborted a request.')
250-
} else {
251-
console.error(e)
252-
outgoing.destroy(err)
253-
}
254-
}
255-
} else {
256-
outgoing.writeHead(res.status, resHeaderRecord)
257-
outgoing.end()
258-
}
273+
return responseViaResponseObject(res, outgoing)
259274
}
260275
}

0 commit comments

Comments
 (0)