From a6e03a658f6bce4eff4b262092641c9c349ecc63 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:56:06 +0900 Subject: [PATCH 1/5] feat: support http2 --- src/request.ts | 18 ++++++++++++------ test/server.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/request.ts b/src/request.ts index 4f2284f..2700c97 100644 --- a/src/request.ts +++ b/src/request.ts @@ -2,7 +2,7 @@ // Define prototype for lightweight pseudo Request object import type { IncomingMessage } from 'node:http' -import type { Http2ServerRequest } from 'node:http2' +import { Http2ServerRequest } from 'node:http2' import { Readable } from 'node:stream' const newRequestFromIncoming = ( @@ -11,9 +11,12 @@ const newRequestFromIncoming = ( incoming: IncomingMessage | Http2ServerRequest ): Request => { const headerRecord: [string, string][] = [] - const len = incoming.rawHeaders.length - for (let i = 0; i < len; i += 2) { - headerRecord.push([incoming.rawHeaders[i], incoming.rawHeaders[i + 1]]) + const rawHeaders = incoming.rawHeaders + for (let i = 0; i < rawHeaders.length; i += 2) { + const {[i]: key, [i + 1]: value} = rawHeaders + if (key.charCodeAt(0) !== /*:*/ 0x3a) { + headerRecord.push([key, value]) + } } const init = { @@ -34,6 +37,7 @@ const newRequestFromIncoming = ( const getRequestCache = Symbol('getRequestCache') const requestCache = Symbol('requestCache') const incomingKey = Symbol('incomingKey') +const urlCacheKey = Symbol('urlCacheKey') const requestPrototype: Record = { get method() { @@ -41,8 +45,10 @@ const requestPrototype: Record = { }, get url() { - const url = `http://${this[incomingKey].headers.host}${this[incomingKey].url}` - return /\.\./.test(url) ? new URL(url).href : url + if (this[urlCacheKey]) return this[urlCacheKey] + const req = this[incomingKey] + const url = `http://${req instanceof Http2ServerRequest ? req.headers[':authority'] : req.headers.host}${req.url}` + return (this[urlCacheKey] = new URL(url).href) }, [getRequestCache]() { diff --git a/test/server.test.ts b/test/server.test.ts index dcfd4ef..a7a111c 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -462,6 +462,12 @@ describe('SSL', () => { describe('HTTP2', () => { const app = new Hono() app.get('/', (c) => c.text('Hello! Node!')) + app.get('/headers', (c) => { + // call newRequestFromIncoming + c.req.header('Accept') + return c.text('Hello! Node!') + }) + app.get('/url', (c) => c.text(c.req.url)) const server = createAdaptorServer({ fetch: app.fetch, @@ -475,6 +481,23 @@ describe('HTTP2', () => { expect(res.headers['content-type']).toMatch(/text\/plain/) expect(res.text).toBe('Hello! Node!') }) + + it('Should return 200 response - GET /headers', async () => { + // @ts-expect-error: @types/supertest is not updated yet + const res = await request(server, { http2: true }).get('/headers').trustLocalhost() + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.text).toBe('Hello! Node!') + }) + + // Use :authority as the host for the url. + it('Should return 200 response - GET /url', async () => { + // @ts-expect-error: @types/supertest is not updated yet + const res = await request(server, { http2: true }).get('/url').trustLocalhost() + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(new URL(res.text).hostname).toBe('127.0.0.1') + }) }) describe('Hono compression', () => { From 68dab3ca0380428d6df462011251579f82cbd7f9 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:24:32 +0900 Subject: [PATCH 2/5] use `Http2ServerRequest.authority` --- src/request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/request.ts b/src/request.ts index 2700c97..ab16ff1 100644 --- a/src/request.ts +++ b/src/request.ts @@ -47,7 +47,7 @@ const requestPrototype: Record = { get url() { if (this[urlCacheKey]) return this[urlCacheKey] const req = this[incomingKey] - const url = `http://${req instanceof Http2ServerRequest ? req.headers[':authority'] : req.headers.host}${req.url}` + const url = `http://${req instanceof Http2ServerRequest ? req.authority : req.headers.host}${req.url}` return (this[urlCacheKey] = new URL(url).href) }, From fae306576151905c9acdc886f7aacad6e4cbb79d Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:27:39 +0900 Subject: [PATCH 3/5] use `requestCache.url` instead of `urlCacheKey` --- src/request.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/request.ts b/src/request.ts index ab16ff1..3264ea0 100644 --- a/src/request.ts +++ b/src/request.ts @@ -37,7 +37,6 @@ const newRequestFromIncoming = ( const getRequestCache = Symbol('getRequestCache') const requestCache = Symbol('requestCache') const incomingKey = Symbol('incomingKey') -const urlCacheKey = Symbol('urlCacheKey') const requestPrototype: Record = { get method() { @@ -45,10 +44,10 @@ const requestPrototype: Record = { }, get url() { - if (this[urlCacheKey]) return this[urlCacheKey] + if (this[requestCache]) return this[requestCache].url const req = this[incomingKey] const url = `http://${req instanceof Http2ServerRequest ? req.authority : req.headers.host}${req.url}` - return (this[urlCacheKey] = new URL(url).href) + return new URL(url).href }, [getRequestCache]() { From e213fc62744a99ccd54b1f0b08c1eb40cd56fe42 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:53:14 +0900 Subject: [PATCH 4/5] perf: avoid `new URL` --- src/request.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/request.ts b/src/request.ts index 3264ea0..9c7ec49 100644 --- a/src/request.ts +++ b/src/request.ts @@ -7,7 +7,6 @@ import { Readable } from 'node:stream' const newRequestFromIncoming = ( method: string, - url: string, incoming: IncomingMessage | Http2ServerRequest ): Request => { const headerRecord: [string, string][] = [] @@ -31,7 +30,7 @@ const newRequestFromIncoming = ( ;(init as any).duplex = 'half' } - return new Request(url, init) + return new Request(`http://${incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host}${incoming.url}`, init) } const getRequestCache = Symbol('getRequestCache') @@ -46,12 +45,11 @@ const requestPrototype: Record = { get url() { if (this[requestCache]) return this[requestCache].url const req = this[incomingKey] - const url = `http://${req instanceof Http2ServerRequest ? req.authority : req.headers.host}${req.url}` - return new URL(url).href + return new URL(`http://${req instanceof Http2ServerRequest ? req.authority : req.headers.host}${req.url}`).href }, [getRequestCache]() { - return (this[requestCache] ||= newRequestFromIncoming(this.method, this.url, this[incomingKey])) + return (this[requestCache] ||= newRequestFromIncoming(this.method, this[incomingKey])) }, } ;[ From 988cb87e5d85172e6e7881ccfaba27711186ab15 Mon Sep 17 00:00:00 2001 From: tsctx <91457664+tsctx@users.noreply.github.com> Date: Tue, 23 Jan 2024 22:37:30 +0900 Subject: [PATCH 5/5] apply suggestions --- src/request.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/request.ts b/src/request.ts index 9c7ec49..74565af 100644 --- a/src/request.ts +++ b/src/request.ts @@ -7,12 +7,13 @@ import { Readable } from 'node:stream' const newRequestFromIncoming = ( method: string, + url: string, incoming: IncomingMessage | Http2ServerRequest ): Request => { const headerRecord: [string, string][] = [] const rawHeaders = incoming.rawHeaders for (let i = 0; i < rawHeaders.length; i += 2) { - const {[i]: key, [i + 1]: value} = rawHeaders + const { [i]: key, [i + 1]: value } = rawHeaders if (key.charCodeAt(0) !== /*:*/ 0x3a) { headerRecord.push([key, value]) } @@ -30,12 +31,13 @@ const newRequestFromIncoming = ( ;(init as any).duplex = 'half' } - return new Request(`http://${incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host}${incoming.url}`, init) + return new Request(url, init) } const getRequestCache = Symbol('getRequestCache') const requestCache = Symbol('requestCache') const incomingKey = Symbol('incomingKey') +const urlKey = Symbol('urlKey') const requestPrototype: Record = { get method() { @@ -43,13 +45,15 @@ const requestPrototype: Record = { }, get url() { - if (this[requestCache]) return this[requestCache].url - const req = this[incomingKey] - return new URL(`http://${req instanceof Http2ServerRequest ? req.authority : req.headers.host}${req.url}`).href + return this[urlKey] }, [getRequestCache]() { - return (this[requestCache] ||= newRequestFromIncoming(this.method, this[incomingKey])) + return (this[requestCache] ||= newRequestFromIncoming( + this.method, + this[urlKey], + this[incomingKey] + )) }, } ;[ @@ -84,5 +88,10 @@ Object.setPrototypeOf(requestPrototype, global.Request.prototype) export const newRequest = (incoming: IncomingMessage | Http2ServerRequest) => { const req = Object.create(requestPrototype) req[incomingKey] = incoming + req[urlKey] = new URL( + `http://${incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host}${ + incoming.url + }` + ).href return req }