diff --git a/src/listener.ts b/src/listener.ts index 8e92635..7f6e4c4 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -7,6 +7,7 @@ import { toRequestError, } from './request' import { cacheKey, getInternalBody, Response as LightweightResponse } from './response' +import type { InternalCache } from './response' import type { CustomErrorHandler, FetchCallback, HttpBindings } from './types' import { writeFromReadableStream, buildOutgoingHttpHeaders } from './utils' import { X_ALREADY_SENT } from './utils/response/constants' @@ -49,7 +50,10 @@ const responseViaCache = ( outgoing: ServerResponse | Http2ServerResponse ): undefined | Promise => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const [status, body, header] = (res as any)[cacheKey] + let [status, body, header] = (res as any)[cacheKey] as InternalCache + if (header instanceof Headers) { + header = buildOutgoingHttpHeaders(header) + } if (typeof body === 'string') { header['Content-Length'] = Buffer.byteLength(body) outgoing.writeHead(status, header) @@ -89,8 +93,7 @@ const responseViaResponseObject = async ( const resHeaderRecord: OutgoingHttpHeaders = buildOutgoingHttpHeaders(res.headers) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const internalBody = getInternalBody(res as any) + const internalBody = getInternalBody(res as Response) if (internalBody) { const { length, source, stream } = internalBody if (source instanceof Uint8Array && source.byteLength !== length) { diff --git a/src/response.ts b/src/response.ts index cbd629c..fe727ed 100644 --- a/src/response.ts +++ b/src/response.ts @@ -2,7 +2,6 @@ // Define lightweight pseudo Response class and replace global.Response with it. import type { OutgoingHttpHeaders } from 'node:http' -import { buildOutgoingHttpHeaders } from './utils' import { getResponseState } from './utils/internal' import type { InternalBody } from './utils/internal' @@ -10,17 +9,28 @@ const responseCache = Symbol('responseCache') const getResponseCache = Symbol('getResponseCache') export const cacheKey = Symbol('cache') +export type InternalCache = [ + number, + string | ReadableStream, + Record | Headers | OutgoingHttpHeaders, +] +interface LiteResponse { + [responseCache]?: globalThis.Response + [cacheKey]?: InternalCache +} + export const GlobalResponse = global.Response export class Response { #body?: BodyInit | null #init?: ResponseInit; [getResponseCache](): globalThis.Response { - delete (this as any)[cacheKey] - return ((this as any)[responseCache] ||= new GlobalResponse(this.#body, this.#init)) + delete (this as LiteResponse)[cacheKey] + return ((this as LiteResponse)[responseCache] ||= new GlobalResponse(this.#body, this.#init)) } constructor(body?: BodyInit | null, init?: ResponseInit) { + let headers: HeadersInit this.#body = body if (init instanceof Response) { const cachedGlobalResponse = (init as any)[responseCache] @@ -31,36 +41,43 @@ export class Response { return } else { this.#init = init.#init + // clone headers to avoid sharing the same object between parent and child + headers = new Headers((init.#init as ResponseInit).headers) } } else { this.#init = init } if (typeof body === 'string' || typeof (body as ReadableStream)?.getReader !== 'undefined') { - let headers = (init?.headers || { 'content-type': 'text/plain; charset=UTF-8' }) as - | Record - | Headers - | OutgoingHttpHeaders - if (headers instanceof Headers) { - headers = buildOutgoingHttpHeaders(headers) - } - + headers ||= init?.headers || { 'content-type': 'text/plain; charset=UTF-8' } ;(this as any)[cacheKey] = [init?.status || 200, body, headers] } } + + get headers(): Headers { + const cache = (this as LiteResponse)[cacheKey] as InternalCache + if (cache) { + if (!(cache[2] instanceof Headers)) { + cache[2] = new Headers(cache[2] as HeadersInit) + } + return cache[2] + } + return this[getResponseCache]().headers + } + + get status() { + return ( + ((this as LiteResponse)[cacheKey] as InternalCache | undefined)?.[0] ?? + this[getResponseCache]().status + ) + } + + get ok() { + const status = this.status + return status >= 200 && status < 300 + } } -;[ - 'body', - 'bodyUsed', - 'headers', - 'ok', - 'redirected', - 'status', - 'statusText', - 'trailers', - 'type', - 'url', -].forEach((k) => { +;['body', 'bodyUsed', 'redirected', 'statusText', 'trailers', 'type', 'url'].forEach((k) => { Object.defineProperty(Response.prototype, k, { get() { return this[getResponseCache]()[k]