Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -49,7 +50,10 @@ const responseViaCache = (
outgoing: ServerResponse | Http2ServerResponse
): undefined | Promise<undefined | void> => {
// 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)
Expand Down Expand Up @@ -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) {
Expand Down
63 changes: 40 additions & 23 deletions src/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,35 @@
// 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'

const responseCache = Symbol('responseCache')
const getResponseCache = Symbol('getResponseCache')
export const cacheKey = Symbol('cache')

export type InternalCache = [
number,
string | ReadableStream,
Record<string, string> | 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]
Expand All @@ -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<string, string>
| 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]
Expand Down