Skip to content

Commit

Permalink
feat(request): Enable to create Request from light weight request. (#133
Browse files Browse the repository at this point in the history
)

* refactor(response): Object.defineProperty is done in response.ts, so there is no need to do it in globals.ts

* feat(request): Enable to create Request from light weight request.
  • Loading branch information
usualoma authored Jan 29, 2024
1 parent d35311d commit f2042d7
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 42 deletions.
5 changes: 0 additions & 5 deletions src/globals.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import crypto from 'node:crypto'
import { Response } from './response'

Object.defineProperty(global, 'Response', {
value: Response,
})

const webFetch = global.fetch

Expand Down
21 changes: 18 additions & 3 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -27,8 +44,6 @@ const newRequestFromIncoming = (
if (!(method === 'GET' || method === 'HEAD')) {
// lazy-consume request body
init.body = Readable.toWeb(incoming) as ReadableStream<Uint8Array>
// node 18 fetch needs half duplex mode when request body is stream
;(init as any).duplex = 'half'
}

return new Request(url, init)
Expand Down Expand Up @@ -83,7 +98,7 @@ const requestPrototype: Record<string | symbol, any> = {
},
})
})
Object.setPrototypeOf(requestPrototype, global.Request.prototype)
Object.setPrototypeOf(requestPrototype, Request.prototype)

export const newRequest = (incoming: IncomingMessage | Http2ServerRequest) => {
const req = Object.create(requestPrototype)
Expand Down
113 changes: 79 additions & 34 deletions test/request.test.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
})
4 changes: 4 additions & 0 deletions test/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 4 additions & 0 deletions test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ Object.defineProperty(global, 'Response', {
value: global.Response,
writable: true,
})
Object.defineProperty(global, 'Request', {
value: global.Request,
writable: true,
})

0 comments on commit f2042d7

Please sign in to comment.