Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/crutchcorn/hono-node-workers-server. Node.js Worker Threads runtime adapter for Hono.
Something went wrong. Try again.
4.3 kB · 152 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153import { createServer as createHttpServer } from 'node:http'import type { AddressInfo } from 'node:net'import { Readable } from 'node:stream'import type { ReadableStream as NodeReadableStream } from 'node:stream/web'import type { IncomingMessage, ServerResponse } from 'node:http'import type { FetchCallback, HttpBindings, ServeOptions, ServerType } from './types.js'
type RequestInitWithDuplex = RequestInit & { duplex?: 'half' }
const hasRequestBody = (incoming: IncomingMessage): boolean => incoming.method !== 'GET' && incoming.method !== 'HEAD'
const headersFromIncoming = (incoming: IncomingMessage): Headers => { const headers = new Headers()
for (let i = 0; i < incoming.rawHeaders.length; i += 2) { headers.append(incoming.rawHeaders[i], incoming.rawHeaders[i + 1]) }
return headers}
const requestUrl = (incoming: IncomingMessage, hostname?: string): string => { const incomingUrl = incoming.url || '/'
if (incomingUrl.startsWith('http://') || incomingUrl.startsWith('https://')) { return incomingUrl }
const host = incoming.headers.host || hostname if (!host) { throw new Error('Missing host header') }
const protocol = incoming.socket && 'encrypted' in incoming.socket ? 'https' : 'http' return new URL(incomingUrl, `${protocol}://${host}`).href}
const toRequest = ( incoming: IncomingMessage, outgoing: ServerResponse, hostname?: string): Request => { const controller = new AbortController()
outgoing.on('close', () => { if (!outgoing.writableFinished) { controller.abort() } })
const init: RequestInitWithDuplex = { method: incoming.method, headers: headersFromIncoming(incoming), signal: controller.signal, }
if (hasRequestBody(incoming)) { init.body = Readable.toWeb(incoming) as ReadableStream<Uint8Array> init.duplex = 'half' }
return new Request(requestUrl(incoming, hostname), init)}
const outgoingHeaders = (headers: Headers): Record<string, string | string[]> => { const output: Record<string, string | string[]> = {} const getSetCookie = (headers as Headers & { getSetCookie?: () => string[] }).getSetCookie const setCookie = getSetCookie?.call(headers)
for (const [key, value] of headers) { if (key !== 'set-cookie') { output[key] = value } }
if (setCookie && setCookie.length > 0) { output['set-cookie'] = setCookie }
return output}
const writeResponse = async (response: Response, outgoing: ServerResponse): Promise<void> => { outgoing.writeHead(response.status, outgoingHeaders(response.headers))
if (!response.body) { outgoing.end() return }
await new Promise<void>((resolve, reject) => { const body = Readable.fromWeb(response.body as unknown as NodeReadableStream<Uint8Array>) body.on('error', reject) outgoing.on('error', reject) outgoing.on('finish', resolve) body.pipe(outgoing) })}
const errorResponse = (status: number): Response => new Response(null, { status, })
export const getRequestListener = ( fetchCallback: FetchCallback, options: { hostname?: string } = {}) => { return async (incoming: IncomingMessage, outgoing: ServerResponse): Promise<void> => { let request: Request
try { request = toRequest(incoming, outgoing, options.hostname) } catch { await writeResponse(errorResponse(400), outgoing) return }
let response: Response try { response = await fetchCallback(request, { incoming, outgoing } satisfies HttpBindings) } catch { response = errorResponse(500) }
try { await writeResponse(response, outgoing) } catch { if (!outgoing.headersSent) { outgoing.writeHead(500) } outgoing.end() } }}
export const createAdaptorServer = (options: ServeOptions): ServerType => { const createServer = options.createServer || createHttpServer return createServer(options.serverOptions || {}, getRequestListener(options.fetch, options))}
export const serve = ( options: ServeOptions, listeningListener?: (info: AddressInfo) => void): ServerType => { const server = createAdaptorServer(options) server.listen(options.port ?? 3000, options.hostname, () => { listeningListener?.(server.address() as AddressInfo) }) return server}