Many fixes
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { Hono } from "hono";
|
||||
import { RequestHeaders } from "../../routes/types";
|
||||
import { ApplicationError } from "../../errors";
|
||||
import { HTTP_STATUS_CODE_BAD_REQUEST } from "../../constants";
|
||||
|
||||
/** RFC 9110 field-name token grammar. */
|
||||
const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
||||
|
||||
/** Hono variables populated by transport-boundary middleware. */
|
||||
export type AppEnv = {
|
||||
Variables: {
|
||||
|
||||
/** Decoded Extended JSON request body, when present. */
|
||||
parsedBody?: unknown;
|
||||
|
||||
/** Raw JSON text preserved for signature verification. */
|
||||
rawJsonBody?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export abstract class BaseTransport {
|
||||
/** Attach wire endpoints and middleware to the shared Hono application. */
|
||||
abstract register(app: Hono<AppEnv>): void
|
||||
|
||||
/** Close transport-owned long-lived connections during server shutdown. */
|
||||
abstract stop(): Promise<void>
|
||||
|
||||
/**
|
||||
* Validate and normalize request headers at a transport boundary.
|
||||
*
|
||||
* Lowercase names give HTTP and WebSocket routes identical lookup semantics.
|
||||
* Case-insensitive duplicates are rejected instead of selecting an ambiguous
|
||||
* authentication value. Header values may not contain line breaks.
|
||||
*/
|
||||
public static normalizeRequestHeaders(headers: Readonly<Record<string, string>>): RequestHeaders {
|
||||
const normalizedEntries: Array<[string, string]> = [];
|
||||
const names = new Set<string>();
|
||||
|
||||
for (const [ name, value ] of Object.entries(headers)) {
|
||||
if (!HEADER_NAME_PATTERN.test(name)) {
|
||||
throw new ApplicationError(HTTP_STATUS_CODE_BAD_REQUEST, 'Invalid request header name');
|
||||
}
|
||||
|
||||
if (value.includes('\r') || value.includes('\n')) {
|
||||
throw new ApplicationError(HTTP_STATUS_CODE_BAD_REQUEST, 'Invalid request header value');
|
||||
}
|
||||
|
||||
const normalizedName = name.toLowerCase();
|
||||
if (names.has(normalizedName)) {
|
||||
throw new ApplicationError(HTTP_STATUS_CODE_BAD_REQUEST, 'Duplicate request header name');
|
||||
}
|
||||
|
||||
names.add(normalizedName);
|
||||
normalizedEntries.push([ normalizedName, value ]);
|
||||
}
|
||||
|
||||
return Object.freeze(Object.fromEntries(normalizedEntries));
|
||||
};
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import { toExtendedJson, fromExtendedJson } from '@xo-cash/utils';
|
||||
import type { Logger } from '../../utils/logger.ts';
|
||||
import type { ApplicationRequest, ApplicationRouter } from '../router.ts';
|
||||
import type { StreamResponse } from '../stream/base-stream.ts';
|
||||
import type { AppEnv, TransportRouter } from './transport-router.ts';
|
||||
|
||||
import { ApplicationError, normalizePublicError } from '../../errors/index.ts';
|
||||
import { HTTP_STATUS_CODE_BAD_REQUEST, HTTP_STATUS_CODE_NO_CONTENT } from '../../constants.ts';
|
||||
import { HonoSSEStream } from '../stream/hono-sse-stream.ts';
|
||||
import { HttpRequestStream } from '../stream/http-request-stream.ts';
|
||||
import { type AppEnv, BaseTransport } from './base-transport.ts';
|
||||
|
||||
/** Hono context key where decoded Extended JSON bodies are stored. */
|
||||
const PARSED_BODY_KEY = 'parsedBody';
|
||||
@@ -21,7 +21,7 @@ const PARSED_BODY_KEY = 'parsedBody';
|
||||
* Normal HTTP and SSE both enter the same application router with different
|
||||
* connection-stream capabilities.
|
||||
*/
|
||||
export class HttpTransportRouter implements TransportRouter {
|
||||
export class HttpTransportRouter extends BaseTransport {
|
||||
private readonly debug: Logger;
|
||||
|
||||
/** SSE connections retained until their final subscription or peer closes. */
|
||||
@@ -35,6 +35,8 @@ export class HttpTransportRouter implements TransportRouter {
|
||||
private readonly router: ApplicationRouter,
|
||||
debug: Logger,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.debug = debug.extend('http-transport');
|
||||
}
|
||||
|
||||
@@ -111,6 +113,7 @@ export class HttpTransportRouter implements TransportRouter {
|
||||
return {
|
||||
path: context.req.path,
|
||||
...(body === undefined ? {} : { body }),
|
||||
headers: context.req.header(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ import { z } from 'zod';
|
||||
|
||||
import type { Logger } from '../../utils/logger.ts';
|
||||
import type { ApplicationRequest, ApplicationRouter } from '../router.ts';
|
||||
import type { AppEnv, UpgradeTransportRouter } from './transport-router.ts';
|
||||
|
||||
import { ApplicationError, normalizePublicError } from '../../errors/index.ts';
|
||||
import { HTTP_STATUS_CODE_BAD_REQUEST } from '../../constants.ts';
|
||||
import { WSStream } from '../stream/ws-stream.ts';
|
||||
import { type AppEnv, BaseTransport } from './base-transport.ts';
|
||||
|
||||
/** Default WebSocket upgrade path for application messages. */
|
||||
const WS_ROUTE = '/ws';
|
||||
@@ -33,7 +33,7 @@ const wsRequestSchema = z
|
||||
* One WSStream is shared by every message on a socket, allowing subscribe and
|
||||
* unsubscribe requests to operate on the same broadcaster registration.
|
||||
*/
|
||||
export class WsTransportRouter implements UpgradeTransportRouter {
|
||||
export class WsTransportRouter extends BaseTransport {
|
||||
/**
|
||||
* Native Node WebSocket server used by Hono's Node adapter.
|
||||
*
|
||||
@@ -42,9 +42,6 @@ export class WsTransportRouter implements UpgradeTransportRouter {
|
||||
*/
|
||||
private readonly wsServer: WebSocketServer;
|
||||
|
||||
/** Upgrade server wired into the Node HTTP listener by ServerHost. */
|
||||
readonly websocketServer: WebSocketServerLike;
|
||||
|
||||
private readonly debug: Logger;
|
||||
|
||||
/**
|
||||
@@ -59,6 +56,8 @@ export class WsTransportRouter implements UpgradeTransportRouter {
|
||||
private readonly maxRequestBodyBytes: number,
|
||||
private readonly url: string = WS_ROUTE,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.debug = debug.extend('ws-transport');
|
||||
|
||||
// Hono's Node WebSocket helper delegates frame handling to `ws`; unlike
|
||||
@@ -68,7 +67,6 @@ export class WsTransportRouter implements UpgradeTransportRouter {
|
||||
noServer: true,
|
||||
maxPayload: this.maxRequestBodyBytes,
|
||||
});
|
||||
this.websocketServer = this.wsServer as unknown as WebSocketServerLike;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user