Skip to content

Commit

Permalink
Merge branch 'next' into feat/jsr
Browse files Browse the repository at this point in the history
  • Loading branch information
yusukebe committed May 24, 2024
2 parents 4068967 + cd667e5 commit 78d9cc5
Show file tree
Hide file tree
Showing 56 changed files with 2,298 additions and 177 deletions.
54 changes: 50 additions & 4 deletions benchmarks/utils/src/get-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,61 @@ bench('noop', () => {})
const request = new Request('http://localhost/about/me')

group('getPath', () => {
bench('slice + indexOf', () => {
bench('slice + indexOf : w/o decodeURI', () => {
const url = request.url
const queryIndex = url.indexOf('?', 8)
url.slice(url.indexOf('/', 8), queryIndex === -1 ? undefined : queryIndex)
return url.slice(url.indexOf('/', 8), queryIndex === -1 ? undefined : queryIndex)
})

bench('regexp', () => {
bench('regexp : w/o decodeURI', () => {
const match = request.url.match(/^https?:\/\/[^/]+(\/[^?]*)/)
match ? match[1] : ''
return match ? match[1] : ''
})

bench('slice + indexOf', () => {
const url = request.url
const queryIndex = url.indexOf('?', 8)
const path = url.slice(url.indexOf('/', 8), queryIndex === -1 ? undefined : queryIndex)
return path.includes('%') ? decodeURIComponent(path) : path
})

bench('slice + for-loop + flag', () => {
const url = request.url
let start = url.indexOf('/', 8)
let i = start
let hasPercentEncoding = false
for (; i < url.length; i++) {
const charCode = url.charCodeAt(i)
if (charCode === 37) {
// '%'
hasPercentEncoding = true
} else if (charCode === 63) {
// '?'
break
}
}
return hasPercentEncoding ? decodeURIComponent(url.slice(start, i)) : url.slice(start, i)
})

bench('slice + for-loop + immediate return', () => {
const url = request.url
const start = url.indexOf('/', 8)
let i = start
for (; i < url.length; i++) {
const charCode = url.charCodeAt(i)
if (charCode === 37) {
// '%'
// If the path contains percent encoding, use `indexOf()` to find '?' and return the result immediately.
// Although this is a performance disadvantage, it is acceptable since we prefer cases that do not include percent encoding.
const queryIndex = url.indexOf('?', i)
const path = url.slice(start, queryIndex === -1 ? undefined : queryIndex)
return decodeURI(path.includes('%25') ? path.replace(/%25/g, '%2525') : path)
} else if (charCode === 63) {
// '?'
break
}
}
return url.slice(start, i)
})
})

Expand Down
Binary file modified bun.lockb
Binary file not shown.
19 changes: 17 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hono",
"version": "4.3.9",
"version": "4.3.11",
"description": "Ultrafast web framework for the Edges",
"main": "dist/cjs/index.js",
"type": "module",
Expand Down Expand Up @@ -178,6 +178,11 @@
"import": "./dist/middleware/jwt/index.js",
"require": "./dist/cjs/middleware/jwt/index.js"
},
"./timeout": {
"types": "./dist/types/middleware/timeout/index.d.ts",
"import": "./dist/middleware/timeout/index.js",
"require": "./dist/cjs/middleware/timeout/index.js"
},
"./timing": {
"types": "./dist/types/middleware/timing/index.d.ts",
"import": "./dist/middleware/timing/index.js",
Expand Down Expand Up @@ -337,6 +342,11 @@
"types": "./dist/types/helper/websocket/index.d.ts",
"import": "./dist/helper/websocket/index.js",
"require": "./dist/cjs/helper/websocket/index.js"
},
"./conninfo": {
"types": "./dist/types/helper/conninfo/index.d.ts",
"import": "./dist/helper/conninfo/index.js",
"require": "./dist/cjs/helper/conninfo/index.js"
}
},
"typesVersions": {
Expand Down Expand Up @@ -419,6 +429,9 @@
"jwt": [
"./dist/types/middleware/jwt"
],
"timeout": [
"./dist/types/middleware/timeout"
],
"timing": [
"./dist/types/middleware/timing"
],
Expand Down Expand Up @@ -514,6 +527,9 @@
],
"ws": [
"./dist/types/helper/websocket"
],
"conninfo": [
"./dist/types/helper/conninfo"
]
}
},
Expand Down Expand Up @@ -545,7 +561,6 @@
"nodejs"
],
"devDependencies": {
"@cloudflare/workers-types": "^4.20231121.0",
"@hono/eslint-config": "^0.0.4",
"@hono/node-server": "^1.8.2",
"@types/crypto-js": "^4.1.1",
Expand Down
57 changes: 57 additions & 0 deletions src/adapter/bun/conninfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Context } from '../../context'
import { HonoRequest } from '../../request'
import { getConnInfo } from './conninfo'

const createRandomBunServer = () => {
const address = Math.random().toString()
const port = Math.floor(Math.random() * (65535 + 1))
return {
address,
port,
server: {
requestIP() {
return {
address,
family: 'IPv6',
port,
}
},
},
}
}
describe('getConnInfo', () => {
it('Should info is valid', () => {
const { port, server, address } = createRandomBunServer()
const c = new Context(new HonoRequest(new Request('http://localhost/')), { env: server })
const info = getConnInfo(c)

expect(info.remote.port).toBe(port)
expect(info.remote.address).toBe(address)
expect(info.remote.addressType).toBe('IPv6')
expect(info.remote.transport).toBeUndefined()
})
it('Should getConnInfo works when env is { server: server }', () => {
const { port, server, address } = createRandomBunServer()
const c = new Context(new HonoRequest(new Request('http://localhost/')), { env: { server } })

const info = getConnInfo(c)

expect(info.remote.port).toBe(port)
expect(info.remote.address).toBe(address)
expect(info.remote.addressType).toBe('IPv6')
expect(info.remote.transport).toBeUndefined()
})
it('Should throw error when user did not give server', () => {
const c = new Context(new HonoRequest(new Request('http://localhost/')), { env: {} })

expect(() => getConnInfo(c)).toThrowError(TypeError)
})
it('Should throw error when requestIP is not function', () => {
const c = new Context(new HonoRequest(new Request('http://localhost/')), {
env: {
requestIP: 0,
},
})
expect(() => getConnInfo(c)).toThrowError(TypeError)
})
})
35 changes: 35 additions & 0 deletions src/adapter/bun/conninfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Context } from '../..'
import type { GetConnInfo } from '../../helper/conninfo'

/**
* Get ConnInfo with Bun
* @param c Context
* @returns ConnInfo
*/
export const getConnInfo: GetConnInfo = (c: Context) => {
const server = ('server' in c.env ? c.env.server : c.env) as
| {
requestIP?: (req: Request) => {
address: string
family: string
port: number
}
}
| undefined

if (!server) {
throw new TypeError('env has to include the 2nd argument of fetch.')
}
if (typeof server.requestIP !== 'function') {
throw new TypeError('server.requestIP is not a function.')
}
const info = server.requestIP(c.req.raw)

return {
remote: {
address: info.address,
addressType: info.family === 'IPv6' || info.family === 'IPv4' ? info.family : 'unknown',
port: info.port,
},
}
}
1 change: 1 addition & 0 deletions src/adapter/bun/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { serveStatic } from './serve-static'
export { bunFileSystemModule, toSSG } from './ssg'
export { createBunWebSocket } from './websocket'
export { getConnInfo } from './conninfo'
19 changes: 19 additions & 0 deletions src/adapter/cloudflare-workers/conninfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Context } from '../../context'
import { HonoRequest } from '../../request'
import { getConnInfo } from './conninfo'

describe('getConnInfo', () => {
it('Should getConnInfo works', () => {
const address = Math.random().toString()
const req = new Request('http://localhost/', {
headers: {
'cf-connecting-ip': address,
},
})
const c = new Context(new HonoRequest(req))

const info = getConnInfo(c)

expect(info.remote.address).toBe(address)
})
})
8 changes: 8 additions & 0 deletions src/adapter/cloudflare-workers/conninfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { GetConnInfo } from '../../helper/conninfo'

export const getConnInfo: GetConnInfo = (c) => ({
remote: {
address: c.req.header('cf-connecting-ip'),
addressType: 'unknown',
},
})
4 changes: 2 additions & 2 deletions src/adapter/cloudflare-workers/serve-static.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { KVNamespace } from '@cloudflare/workers-types'
import { serveStatic as baseServeStatic } from '../../middleware/serve-static'
import type { ServeStaticOptions as BaseServeStaticOptions } from '../../middleware/serve-static'
import type { Env, MiddlewareHandler } from '../../types'
import { getContentFromKVAsset } from './utils'

export type ServeStaticOptions<E extends Env = Env> = BaseServeStaticOptions<E> & {
namespace?: KVNamespace
// namespace is KVNamespace
namespace?: unknown
manifest: object | string
}

Expand Down
11 changes: 7 additions & 4 deletions src/adapter/cloudflare-workers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { KVNamespace } from '@cloudflare/workers-types'
declare const __STATIC_CONTENT: KVNamespace
// __STATIC_CONTENT is KVNamespace
declare const __STATIC_CONTENT: unknown
declare const __STATIC_CONTENT_MANIFEST: string

export type KVAssetOptions = {
manifest?: object | string
namespace?: KVNamespace
// namespace is KVNamespace
namespace?: unknown
}

export const getContentFromKVAsset = async (
Expand All @@ -27,7 +28,8 @@ export const getContentFromKVAsset = async (
}
}

let ASSET_NAMESPACE: KVNamespace
// ASSET_NAMESPACE is KVNamespace
let ASSET_NAMESPACE: unknown
if (options && options.namespace) {
ASSET_NAMESPACE = options.namespace
} else {
Expand All @@ -39,6 +41,7 @@ export const getContentFromKVAsset = async (
return null
}

// @ts-expect-error ASSET_NAMESPACE is not typed
const content = await ASSET_NAMESPACE.get(key, { type: 'stream' })
if (!content) {
return null
Expand Down
29 changes: 10 additions & 19 deletions src/adapter/cloudflare-workers/websocket.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import type { WebSocketPair } from '@cloudflare/workers-types'
import type { UpgradeWebSocket, WSContext, WSReadyState } from '../../helper/websocket'

// Based on https://github.com/honojs/hono/issues/1153#issuecomment-1767321332
export const upgradeWebSocket: UpgradeWebSocket = (createEvents) => async (c, next) => {
const CFWebSocketPair = (
globalThis as unknown as {
WebSocketPair: typeof WebSocketPair
}
).WebSocketPair

const events = await createEvents(c)

const upgradeHeader = c.req.header('Upgrade')
if (upgradeHeader !== 'websocket') {
return await next()
}

const webSocketPair = new CFWebSocketPair()
const client = webSocketPair[0]
const server = webSocketPair[1]
// @ts-expect-error WebSocketPair is not typed
const webSocketPair = new WebSocketPair()
const client: WebSocket = webSocketPair[0]
const server: WebSocket = webSocketPair[1]

const wsContext: WSContext = {
binaryType: 'arraybuffer',
Expand All @@ -35,33 +28,31 @@ export const upgradeWebSocket: UpgradeWebSocket = (createEvents) => async (c, ne
send: (source) => server.send(source),
}
if (events.onOpen) {
server.addEventListener(
'open',
(evt: unknown) => events.onOpen && events.onOpen(evt as Event, wsContext)
)
server.addEventListener('open', (evt: Event) => events.onOpen && events.onOpen(evt, wsContext))
}
if (events.onClose) {
server.addEventListener(
'close',
(evt: unknown) => events.onClose && events.onClose(evt as CloseEvent, wsContext)
(evt: CloseEvent) => events.onClose && events.onClose(evt, wsContext)
)
}
if (events.onMessage) {
server.addEventListener(
'message',
(evt: unknown) => events.onMessage && events.onMessage(evt as MessageEvent, wsContext)
(evt: MessageEvent) => events.onMessage && events.onMessage(evt, wsContext)
)
}
if (events.onError) {
server.addEventListener(
'error',
(evt: unknown) => events.onError && events.onError(evt as ErrorEvent as Event, wsContext)
(evt: Event) => events.onError && events.onError(evt, wsContext)
)
}
// @ts-expect-error server.accept is not typed
server.accept()
return new Response(null, {
status: 101,
// @ts-ignore
// @ts-expect-error type not typed
webSocket: client,
})
}
25 changes: 25 additions & 0 deletions src/adapter/deno/conninfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Context } from '../../context'
import { HonoRequest } from '../../request'
import { getConnInfo } from './conninfo'

describe('getConnInfo', () => {
it('Should info is valid', () => {
const transport = 'tcp'
const address = Math.random().toString()
const port = Math.floor(Math.random() * (65535 + 1))
const c = new Context(new HonoRequest(new Request('http://localhost/')), {
env: {
remoteAddr: {
transport,
hostname: address,
port,
},
},
})
const info = getConnInfo(c)

expect(info.remote.port).toBe(port)
expect(info.remote.address).toBe(address)
expect(info.remote.transport).toBe(transport)
})
})
Loading

0 comments on commit 78d9cc5

Please sign in to comment.