diff --git a/src/globals.ts b/src/globals.ts index 5a8d0ed..bc87a43 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,9 +1,4 @@ import crypto from 'node:crypto' -import { Response } from './response' - -Object.defineProperty(global, 'Response', { - value: Response, -}) const webFetch = global.fetch diff --git a/src/request.ts b/src/request.ts index 74565af..bd4a0ca 100644 --- a/src/request.ts +++ b/src/request.ts @@ -5,6 +5,23 @@ import type { IncomingMessage } from 'node:http' import { Http2ServerRequest } from 'node:http2' import { Readable } from 'node:stream' +export const GlobalRequest = global.Request +export class Request extends GlobalRequest { + constructor(input: string | Request, options?: RequestInit) { + if (typeof input === 'object' && getRequestCache in input) { + input = (input as any)[getRequestCache]() + } + if (options?.body instanceof ReadableStream) { + // node 18 fetch needs half duplex mode when request body is stream + ;(options as any).duplex = 'half' + } + super(input, options) + } +} +Object.defineProperty(global, 'Request', { + value: Request, +}) + const newRequestFromIncoming = ( method: string, url: string, @@ -27,8 +44,6 @@ const newRequestFromIncoming = ( if (!(method === 'GET' || method === 'HEAD')) { // lazy-consume request body init.body = Readable.toWeb(incoming) as ReadableStream - // node 18 fetch needs half duplex mode when request body is stream - ;(init as any).duplex = 'half' } return new Request(url, init) @@ -83,7 +98,7 @@ const requestPrototype: Record = { }, }) }) -Object.setPrototypeOf(requestPrototype, global.Request.prototype) +Object.setPrototypeOf(requestPrototype, Request.prototype) export const newRequest = (incoming: IncomingMessage | Http2ServerRequest) => { const req = Object.create(requestPrototype) diff --git a/test/request.test.ts b/test/request.test.ts index 16eab9e..9d852f1 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -1,42 +1,87 @@ import type { IncomingMessage } from 'node:http' -import { newRequest } from '../src/request' +import { newRequest, Request, GlobalRequest } from '../src/request' describe('Request', () => { - it('Compatibility with standard Request object', async () => { - const req = newRequest({ - method: 'GET', - url: '/', - headers: { - host: 'localhost', - }, - rawHeaders: ['host', 'localhost'], - } as IncomingMessage) - - expect(req).toBeInstanceOf(global.Request) - expect(req.method).toBe('GET') - expect(req.url).toBe('http://localhost/') - expect(req.headers.get('host')).toBe('localhost') - }) + describe('newRequest', () => { + it('Compatibility with standard Request object', async () => { + const req = newRequest({ + method: 'GET', + url: '/', + headers: { + host: 'localhost', + }, + rawHeaders: ['host', 'localhost'], + } as IncomingMessage) + + expect(req).toBeInstanceOf(global.Request) + expect(req.method).toBe('GET') + expect(req.url).toBe('http://localhost/') + expect(req.headers.get('host')).toBe('localhost') + }) + + it('Should resolve double dots in URL', async () => { + const req = newRequest({ + headers: { + host: 'localhost', + }, + url: '/static/../foo.txt', + } as IncomingMessage) + expect(req).toBeInstanceOf(global.Request) + expect(req.url).toBe('http://localhost/foo.txt') + }) - it('Should resolve double dots in URL', async () => { - const req = newRequest({ - headers: { - host: 'localhost', - }, - url: '/static/../foo.txt', - } as IncomingMessage) - expect(req).toBeInstanceOf(global.Request) - expect(req.url).toBe('http://localhost/foo.txt') + it('Should resolve double dots in host header', async () => { + const req = newRequest({ + headers: { + host: 'localhost/..', + }, + url: '/foo.txt', + } as IncomingMessage) + expect(req).toBeInstanceOf(global.Request) + expect(req.url).toBe('http://localhost/foo.txt') + }) }) - it('Should resolve double dots in host header', async () => { - const req = newRequest({ - headers: { - host: 'localhost/..', - }, - url: '/foo.txt', - } as IncomingMessage) - expect(req).toBeInstanceOf(global.Request) - expect(req.url).toBe('http://localhost/foo.txt') + describe('GlobalRequest', () => { + it('should be overrode by Request', () => { + expect(Request).not.toBe(GlobalRequest) + }) + + it('should be instance of GlobalRequest', () => { + const req = new Request('http://localhost/') + expect(req).toBeInstanceOf(GlobalRequest) + }) + + it('should be success to create instance from old light weight instance', async () => { + const req = newRequest({ + method: 'GET', + url: '/', + headers: { + host: 'localhost', + }, + rawHeaders: ['host', 'localhost'], + } as IncomingMessage) + const req2 = new Request(req, { + method: 'POST', + body: 'foo', + }) + expect(req2).toBeInstanceOf(GlobalRequest) + expect(await req2.text()).toBe('foo') + }) + + it('should set `duplex: "half"` automatically if body is a ReadableStream', async () => { + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode('bar')) + controller.close() + }, + }) + const req2 = new Request('http://localhost', { + method: 'POST', + body: stream, + }) + expect(req2).toBeInstanceOf(GlobalRequest) + expect(req2.text()).resolves.toBe('bar') + }) }) }) diff --git a/test/response.test.ts b/test/response.test.ts index 52c9c45..175a340 100644 --- a/test/response.test.ts +++ b/test/response.test.ts @@ -40,6 +40,10 @@ describe('Response', () => { server.close() }) + it('Should be overrode by Response', () => { + expect(Response).not.toBe(GlobalResponse) + }) + it('Compatibility with standard Response object', async () => { // response name not changed expect(Response.name).toEqual('Response') diff --git a/test/setup.ts b/test/setup.ts index b4c3fbd..da76d94 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -2,3 +2,7 @@ Object.defineProperty(global, 'Response', { value: global.Response, writable: true, }) +Object.defineProperty(global, 'Request', { + value: global.Request, + writable: true, +})