Skip to content

Commit

Permalink
feat: support http2 (#126)
Browse files Browse the repository at this point in the history
* feat: support http2

* use `Http2ServerRequest.authority`

* use `requestCache.url` instead of `urlCacheKey`

* perf: avoid `new URL`

* apply suggestions
  • Loading branch information
tsctx committed Jan 23, 2024
1 parent 37bee15 commit 2cdb1b3
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 7 deletions.
26 changes: 19 additions & 7 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand All @@ -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 = {
Expand All @@ -34,19 +37,23 @@ const newRequestFromIncoming = (
const getRequestCache = Symbol('getRequestCache')
const requestCache = Symbol('requestCache')
const incomingKey = Symbol('incomingKey')
const urlKey = Symbol('urlKey')

const requestPrototype: Record<string | symbol, any> = {
get method() {
return this[incomingKey].method || 'GET'
},

get url() {
const url = `http://${this[incomingKey].headers.host}${this[incomingKey].url}`
return /\.\./.test(url) ? new URL(url).href : url
return this[urlKey]
},

[getRequestCache]() {
return (this[requestCache] ||= newRequestFromIncoming(this.method, this.url, this[incomingKey]))
return (this[requestCache] ||= newRequestFromIncoming(
this.method,
this[urlKey],
this[incomingKey]
))
},
}
;[
Expand Down Expand Up @@ -81,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
}
23 changes: 23 additions & 0 deletions test/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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', () => {
Expand Down

0 comments on commit 2cdb1b3

Please sign in to comment.