feat: integrate Hono with NestJS and add logging middleware
This commit is contained in:
152
build.ts
Normal file
152
build.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
import { rm } from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
||||||
|
console.log(`
|
||||||
|
🏗️ Bun Build Script
|
||||||
|
|
||||||
|
Usage: bun run build.ts [options]
|
||||||
|
|
||||||
|
Common Options:
|
||||||
|
--outdir <path> Output directory (default: "dist")
|
||||||
|
--minify Enable minification (or --minify.whitespace, --minify.syntax, etc)
|
||||||
|
--sourcemap <type> Sourcemap type: none|linked|inline|external
|
||||||
|
--target <target> Build target: browser|bun|node
|
||||||
|
--format <format> Output format: esm|cjs|iife
|
||||||
|
--splitting Enable code splitting
|
||||||
|
--packages <type> Package handling: bundle|external
|
||||||
|
--public-path <path> Public path for assets
|
||||||
|
--env <mode> Environment handling: inline|disable|prefix*
|
||||||
|
--conditions <list> Package.json export conditions (comma separated)
|
||||||
|
--external <list> External packages (comma separated)
|
||||||
|
--banner <text> Add banner text to output
|
||||||
|
--footer <text> Add footer text to output
|
||||||
|
--define <obj> Define global constants (e.g. --define.VERSION=1.0.0)
|
||||||
|
--help, -h Show this help message
|
||||||
|
|
||||||
|
Example:
|
||||||
|
bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom
|
||||||
|
`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toCamelCase = (str: string): string => str.replace(/-([a-z])/g, g => g[1].toUpperCase());
|
||||||
|
|
||||||
|
const parseValue = (value: string): any => {
|
||||||
|
if (value === "true") return true;
|
||||||
|
if (value === "false") return false;
|
||||||
|
|
||||||
|
if (/^\d+$/.test(value)) return parseInt(value, 10);
|
||||||
|
if (/^\d*\.\d+$/.test(value)) return parseFloat(value);
|
||||||
|
|
||||||
|
if (value.includes(",")) return value.split(",").map(v => v.trim());
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseArgs(): Partial<Bun.BuildConfig> & { [key: string]: any } {
|
||||||
|
const config: Partial<Bun.BuildConfig> & { [key: string]: any } = {};
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
if (arg === undefined) continue;
|
||||||
|
if (!arg.startsWith("--")) continue;
|
||||||
|
|
||||||
|
if (arg.startsWith("--no-")) {
|
||||||
|
const key = toCamelCase(arg.slice(5));
|
||||||
|
config[key] = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!arg.includes("=") && (i === args.length - 1 || args[i + 1]?.startsWith("--"))) {
|
||||||
|
const key = toCamelCase(arg.slice(2));
|
||||||
|
config[key] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key: string;
|
||||||
|
let value: string;
|
||||||
|
|
||||||
|
if (arg.includes("=")) {
|
||||||
|
[key, value] = arg.slice(2).split("=", 2) as [string, string];
|
||||||
|
} else {
|
||||||
|
key = arg.slice(2);
|
||||||
|
value = args[++i] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
key = toCamelCase(key);
|
||||||
|
|
||||||
|
if (key.includes(".")) {
|
||||||
|
const [parentKey, childKey] = key.split(".");
|
||||||
|
config[parentKey] = config[parentKey] || {};
|
||||||
|
config[parentKey][childKey] = parseValue(value);
|
||||||
|
} else {
|
||||||
|
config[key] = parseValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatFileSize = (bytes: number): string => {
|
||||||
|
const units = ["B", "KB", "MB", "GB"];
|
||||||
|
let size = bytes;
|
||||||
|
let unitIndex = 0;
|
||||||
|
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("\n🚀 Starting build process...\n");
|
||||||
|
|
||||||
|
const cliConfig = parseArgs();
|
||||||
|
const outdir = cliConfig.outdir || path.join(process.cwd(), "dist");
|
||||||
|
|
||||||
|
// if (existsSync(outdir)) {
|
||||||
|
// console.log(`🗑️ Cleaning previous build at ${outdir}`);
|
||||||
|
// await rm(outdir, { recursive: true, force: true });
|
||||||
|
// }
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
// const entrypoints = [...new Bun.Glob("**.html").scanSync("src")]
|
||||||
|
// .map(a => path.resolve("src", a))
|
||||||
|
// .filter(dir => !dir.includes("node_modules"));
|
||||||
|
const entrypoints = [path.resolve("dist/server/index.js")];
|
||||||
|
console.log(`📄 Found ${entrypoints.length} HTML ${entrypoints.length === 1 ? "file" : "files"} to process\n`);
|
||||||
|
|
||||||
|
const result = await Bun.build({
|
||||||
|
entrypoints,
|
||||||
|
outdir,
|
||||||
|
plugins: [],
|
||||||
|
minify: false,
|
||||||
|
// target: "browser",
|
||||||
|
target: "bun",
|
||||||
|
// sourcemap: "linked",
|
||||||
|
sourcemap: false,
|
||||||
|
define: {
|
||||||
|
"process.env.NODE_ENV": JSON.stringify("production"),
|
||||||
|
},
|
||||||
|
external: ["@nestjs/microservices",'@nestjs/platform-express', "@nestjs/websockets", "class-transformer", "class-validator"],
|
||||||
|
...cliConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
const end = performance.now();
|
||||||
|
|
||||||
|
const outputTable = result.outputs.map(output => ({
|
||||||
|
File: path.relative(process.cwd(), output.path),
|
||||||
|
Type: output.kind,
|
||||||
|
Size: formatFileSize(output.size),
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.table(outputTable);
|
||||||
|
const buildTime = (end - start).toFixed(2);
|
||||||
|
|
||||||
|
console.log(`\n✅ Build completed in ${buildTime}ms\n`);
|
||||||
3
bun.lock
3
bun.lock
@@ -33,6 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hattip/adapter-node": "^0.0.49",
|
"@hattip/adapter-node": "^0.0.49",
|
||||||
"@hono-di/vite": "^0.0.15",
|
"@hono-di/vite": "^0.0.15",
|
||||||
|
"@hono/node-server": "^1.19.8",
|
||||||
"@primevue/auto-import-resolver": "^4.5.4",
|
"@primevue/auto-import-resolver": "^4.5.4",
|
||||||
"@types/bun": "^1.3.5",
|
"@types/bun": "^1.3.5",
|
||||||
"@types/node": "^25.0.5",
|
"@types/node": "^25.0.5",
|
||||||
@@ -267,6 +268,8 @@
|
|||||||
|
|
||||||
"@hono-di/vite": ["@hono-di/vite@0.0.15", "", { "dependencies": { "@hono-di/client": "0.0.15", "@hono-di/generate": "0.0.15", "sirv": "^2.0.4" }, "peerDependencies": { "vite": "^7.0.0" } }, "sha512-VzqcqkReVHWxMiGHdnze1I9efNZTSMBmm2qRsLU7XDtJGBBPu3WzlNvlf84NjkExeYb55dMhDc3Cln7bKhv/LQ=="],
|
"@hono-di/vite": ["@hono-di/vite@0.0.15", "", { "dependencies": { "@hono-di/client": "0.0.15", "@hono-di/generate": "0.0.15", "sirv": "^2.0.4" }, "peerDependencies": { "vite": "^7.0.0" } }, "sha512-VzqcqkReVHWxMiGHdnze1I9efNZTSMBmm2qRsLU7XDtJGBBPu3WzlNvlf84NjkExeYb55dMhDc3Cln7bKhv/LQ=="],
|
||||||
|
|
||||||
|
"@hono/node-server": ["@hono/node-server@1.19.8", "", { "peerDependencies": { "hono": "^4" } }, "sha512-0/g2lIOPzX8f3vzW1ggQgvG5mjtFBDBHFAzI5SFAi2DzSqS9luJwqg9T6O/gKYLi+inS7eNxBeIFkkghIPvrMA=="],
|
||||||
|
|
||||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||||
|
|
||||||
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
|
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bunx --bun vite",
|
"dev": "bunx --bun vite",
|
||||||
"build": "bunx --bun vite build && bun build dist/server/index.js --target bun --minify --outdir dist",
|
"build": "bunx --bun vite build && bun run build.ts",
|
||||||
"preview": "bunx --bun vite preview"
|
"preview": "bunx --bun vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hattip/adapter-node": "^0.0.49",
|
"@hattip/adapter-node": "^0.0.49",
|
||||||
"@hono-di/vite": "^0.0.15",
|
"@hono-di/vite": "^0.0.15",
|
||||||
|
"@hono/node-server": "^1.19.8",
|
||||||
"@primevue/auto-import-resolver": "^4.5.4",
|
"@primevue/auto-import-resolver": "^4.5.4",
|
||||||
"@types/bun": "^1.3.5",
|
"@types/bun": "^1.3.5",
|
||||||
"@types/node": "^25.0.5",
|
"@types/node": "^25.0.5",
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export default function transformerClassnamesMinifier(options: CompileClassOptio
|
|||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
async transform(s, _id, { uno }) {
|
async transform(s, _id, { uno }) {
|
||||||
if(s.original.includes('p-button') || s.original.includes('p-component') || s.original.includes('p-button-secondary')) {
|
if(s.original.includes('p-button') || s.original.includes('p-component') || s.original.includes('p-button-secondary')) {
|
||||||
console.log("transforming:", _id);
|
|
||||||
}
|
}
|
||||||
const matches = [...s.original.matchAll(regexp)]
|
const matches = [...s.original.matchAll(regexp)]
|
||||||
if (!matches.length)
|
if (!matches.length)
|
||||||
|
|||||||
32
src/main.ts
32
src/main.ts
@@ -33,7 +33,7 @@ import { NestHonoApplication } from './server/HonoAdapter/interfaces';
|
|||||||
// app.get("/.well-known/*", (c) => {
|
// app.get("/.well-known/*", (c) => {
|
||||||
// return c.json({ ok: true });
|
// return c.json({ ok: true });
|
||||||
// });
|
// });
|
||||||
const app = await NestFactory.create<NestHonoApplication>(
|
const appHonoNest = await NestFactory.create<NestHonoApplication>(
|
||||||
AppModule,
|
AppModule,
|
||||||
new HonoAdapter(),
|
new HonoAdapter(),
|
||||||
{
|
{
|
||||||
@@ -51,10 +51,26 @@ const app = await NestFactory.create<NestHonoApplication>(
|
|||||||
// next();
|
// next();
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
const honoInstance = app.getHonoInstance();
|
const app = appHonoNest.getHonoInstance();
|
||||||
honoInstance.use(contextStorage());
|
app.use(async (c, next) => {
|
||||||
honoInstance.get("*", ssrRender);
|
c.set("fetch", app.request.bind(app));
|
||||||
// app.use('*', contextStorage());
|
const ua = c.req.header("User-Agent")
|
||||||
await app.init();
|
if (!ua) {
|
||||||
const httpAdapter = app.get(HttpAdapterHost);
|
return c.json({ error: "User-Agent header is missing" }, 400);
|
||||||
export default app
|
};
|
||||||
|
c.set("isMobile", isMobile({ ua }));
|
||||||
|
await next();
|
||||||
|
}, contextStorage());
|
||||||
|
|
||||||
|
appHonoNest.useStaticAssets("/*", { root: "./dist/client" });
|
||||||
|
await appHonoNest.init();
|
||||||
|
app.get("/.well-known/*", (c) => {
|
||||||
|
return c.json({ ok: true });
|
||||||
|
});
|
||||||
|
app.use(ssrRender);
|
||||||
|
const httpAdapter = appHonoNest.get(HttpAdapterHost);
|
||||||
|
// await app.listen(3000)
|
||||||
|
// console.log("HTTP Adapter:", app.fetch.toString());
|
||||||
|
export default {
|
||||||
|
fetch: app.fetch.bind(app),
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
// @ts-ignore
|
|
||||||
// @ts-nocheck
|
|
||||||
import { HttpBindings, createAdaptorServer } from '@hono/node-server';
|
|
||||||
import {
|
|
||||||
ServeStaticOptions,
|
|
||||||
serveStatic,
|
|
||||||
} from '@hono/node-server/serve-static';
|
|
||||||
import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response';
|
|
||||||
import { RequestMethod } from '@nestjs/common';
|
import { RequestMethod } from '@nestjs/common';
|
||||||
import { HttpStatus, Logger } from '@nestjs/common';
|
import { HttpStatus, Logger } from '@nestjs/common';
|
||||||
import {
|
import { ErrorHandler, NestApplicationOptions, RequestHandler } from '@nestjs/common/interfaces';
|
||||||
ErrorHandler,
|
|
||||||
NestApplicationOptions,
|
|
||||||
RequestHandler,
|
|
||||||
} from '@nestjs/common/interfaces';
|
|
||||||
import { isObject } from '@nestjs/common/utils/shared.utils';
|
import { isObject } from '@nestjs/common/utils/shared.utils';
|
||||||
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
|
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
|
||||||
import { Context, Next, Hono } from 'hono';
|
import { Context, Next, Hono } from 'hono';
|
||||||
@@ -20,15 +8,14 @@ import { bodyLimit } from 'hono/body-limit';
|
|||||||
import { cors } from 'hono/cors';
|
import { cors } from 'hono/cors';
|
||||||
// import { Data } from 'hono/dist/types/context';
|
// import { Data } from 'hono/dist/types/context';
|
||||||
import { RedirectStatusCode, StatusCode } from 'hono/utils/http-status';
|
import { RedirectStatusCode, StatusCode } from 'hono/utils/http-status';
|
||||||
import * as http from 'http';
|
|
||||||
import http2 from 'http2';
|
|
||||||
import * as https from 'https';
|
|
||||||
|
|
||||||
import { HonoRequest, TypeBodyParser } from './interfaces';
|
import { HonoRequest, TypeBodyParser } from './interfaces';
|
||||||
import { Data } from 'node_modules/hono/dist/types/context';
|
import { Data } from 'node_modules/hono/dist/types/context';
|
||||||
|
|
||||||
|
// Define HttpBindings as an empty interface if you don't need specific bindings
|
||||||
|
interface HttpBindings {}
|
||||||
|
|
||||||
type HonoHandler = RequestHandler<HonoRequest, Context>;
|
type HonoHandler = RequestHandler<HonoRequest, Context>;
|
||||||
type ServerType = http.Server | http2.Http2Server | http2.Http2SecureServer;
|
|
||||||
type Ctx = Context | (() => Promise<Context>);
|
type Ctx = Context | (() => Promise<Context>);
|
||||||
type Method =
|
type Method =
|
||||||
| 'all'
|
| 'all'
|
||||||
@@ -44,11 +31,11 @@ type Method =
|
|||||||
* Adapter for using Hono with NestJS.
|
* Adapter for using Hono with NestJS.
|
||||||
*/
|
*/
|
||||||
export class HonoAdapter extends AbstractHttpAdapter<
|
export class HonoAdapter extends AbstractHttpAdapter<
|
||||||
ServerType,
|
any,
|
||||||
HonoRequest,
|
HonoRequest,
|
||||||
Context
|
Context
|
||||||
> {
|
> {
|
||||||
private _isParserRegistered: boolean;
|
private _isParserRegistered: boolean = false;
|
||||||
|
|
||||||
protected readonly instance: Hono<{ Bindings: HttpBindings }>;
|
protected readonly instance: Hono<{ Bindings: HttpBindings }>;
|
||||||
|
|
||||||
@@ -74,7 +61,9 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
|
|
||||||
private createRouteHandler(routeHandler: HonoHandler) {
|
private createRouteHandler(routeHandler: HonoHandler) {
|
||||||
return async (ctx: Context, next: Next) => {
|
return async (ctx: Context, next: Next) => {
|
||||||
ctx.req['params'] = ctx.req.param();
|
// ctx.req['params'] = ctx.req.param();
|
||||||
|
// ctx.req['params'] = ctx.req.param();
|
||||||
|
Object.assign(ctx.req, { params: ctx.req.param() });
|
||||||
|
|
||||||
await routeHandler(ctx.req, ctx, next);
|
await routeHandler(ctx.req, ctx, next);
|
||||||
|
|
||||||
@@ -110,7 +99,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
responseContentType = 'application/json';
|
responseContentType = 'application/json';
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.setHeader(ctx, 'Content-Type', responseContentType);
|
await this.setHeader(ctx, 'Content-Type', String(responseContentType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseContentType === 'application/json' && isObject(body)) {
|
if (responseContentType === 'application/json' && isObject(body)) {
|
||||||
@@ -210,7 +199,10 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async end() {
|
public async end() {
|
||||||
return RESPONSE_ALREADY_SENT;
|
// return RESPONSE_ALREADY_SENT;
|
||||||
|
new Response(null, {
|
||||||
|
headers: { ['x-hono-already-sent']: "true" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@@ -239,10 +231,15 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public useStaticAssets(path: string, options: ServeStaticOptions) {
|
public async useStaticAssets(path: string, options: any) {
|
||||||
Logger.log('Registering static assets middleware');
|
Logger.log('Registering static assets middleware');
|
||||||
|
if ((process as any).versions?.bun && import.meta.env.PROD) {
|
||||||
|
const { serveStatic } = await import("hono/bun");
|
||||||
|
// app.use(serveStatic({ root: "./dist/client" }))
|
||||||
|
// this.instance.use(serveStatic({ root: "./dist/client" }))
|
||||||
this.instance.use(path, serveStatic(options));
|
this.instance.use(path, serveStatic(options));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public setViewEngine() {
|
public setViewEngine() {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
@@ -303,7 +300,9 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
Logger.log(
|
Logger.log(
|
||||||
`Registering body parser middleware for type: ${type} | bodyLimit: ${bodyLimit}`,
|
`Registering body parser middleware for type: ${type} | bodyLimit: ${bodyLimit}`,
|
||||||
);
|
);
|
||||||
|
if(bodyLimit !== undefined) {
|
||||||
this.instance.use(this.bodyLimit(bodyLimit));
|
this.instance.use(this.bodyLimit(bodyLimit));
|
||||||
|
}
|
||||||
|
|
||||||
// To avoid the Nest application init to override our custom
|
// To avoid the Nest application init to override our custom
|
||||||
// body parser, we mark the parsers as registered.
|
// body parser, we mark the parsers as registered.
|
||||||
@@ -325,7 +324,8 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
ctx.req.header('x-cluster-client-ip') ??
|
ctx.req.header('x-cluster-client-ip') ??
|
||||||
ctx.req.header('x-forwarded') ??
|
ctx.req.header('x-forwarded') ??
|
||||||
ctx.req.header('forwarded-for') ??
|
ctx.req.header('forwarded-for') ??
|
||||||
ctx.req.header('via')
|
ctx.req.header('via')??
|
||||||
|
"unknown"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,31 +358,28 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public initHttpServer(options: NestApplicationOptions) {
|
public initHttpServer(options: NestApplicationOptions) {
|
||||||
|
Logger.log('Initializing Hono HTTP server adapter');
|
||||||
this.instance.use(async (ctx, next) => {
|
this.instance.use(async (ctx, next) => {
|
||||||
ctx.req['ip'] = this.extractClientIp(ctx);
|
// ctx.req['ip'] = this.extractClientIp(ctx);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
ctx.req['query'] = ctx.req.query() as any;
|
// ctx.req['query'] = ctx.req.query() as any;
|
||||||
ctx.req['headers'] = Object.fromEntries(ctx.req.raw.headers);
|
// ctx.req['headers'] = Object.fromEntries(ctx.req.raw.headers);
|
||||||
|
Object.assign(ctx.req, {
|
||||||
|
ip: this.extractClientIp(ctx),
|
||||||
|
query: ctx.req.query() as Record<string, string>,
|
||||||
|
headers: Object.fromEntries(ctx.req.raw.headers as any),
|
||||||
|
})
|
||||||
const contentType = ctx.req.header('content-type');
|
const contentType = ctx.req.header('content-type');
|
||||||
await this.parseRequestBody(ctx, contentType, options.rawBody);
|
if (contentType) {
|
||||||
|
await this.parseRequestBody(ctx, contentType, options.rawBody!);
|
||||||
|
}
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
const isHttpsEnabled = options?.httpsOptions;
|
return this.httpServer;
|
||||||
const createServer = isHttpsEnabled
|
|
||||||
? https.createServer
|
|
||||||
: http.createServer;
|
|
||||||
this.httpServer = createAdaptorServer({
|
|
||||||
fetch: this.instance.fetch,
|
|
||||||
createServer,
|
|
||||||
overrideGlobalObjects: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getType(): string {
|
public getType(): string { return 'hono'; }
|
||||||
return 'hono';
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerParserMiddleware(_prefix?: string, rawBody?: boolean) {
|
public registerParserMiddleware(_prefix?: string, rawBody?: boolean) {
|
||||||
if (this._isParserRegistered) {
|
if (this._isParserRegistered) {
|
||||||
@@ -400,7 +397,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
public async createMiddlewareFactory(requestMethod: RequestMethod) {
|
public async createMiddlewareFactory(requestMethod: RequestMethod) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
return (path: string, callback: Function) => {
|
return (path: string, callback: Function) => {
|
||||||
const routeMethodsMap = {
|
const routeMethodsMap: Record<string, Function> = {
|
||||||
[RequestMethod.ALL]: this.instance.all,
|
[RequestMethod.ALL]: this.instance.all,
|
||||||
[RequestMethod.DELETE]: this.instance.delete,
|
[RequestMethod.DELETE]: this.instance.delete,
|
||||||
[RequestMethod.GET]: this.instance.get,
|
[RequestMethod.GET]: this.instance.get,
|
||||||
@@ -424,12 +421,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
public listen(port: string | number, ...args: any[]): ServerType {
|
public listen(...args: any[]) {}
|
||||||
return this.httpServer.listen(port, ...args);
|
|
||||||
}
|
|
||||||
public fetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
|
|
||||||
return this.instance.fetch(input, init);
|
|
||||||
}
|
|
||||||
public getHonoInstance(): Hono<{ Bindings: HttpBindings }> {
|
public getHonoInstance(): Hono<{ Bindings: HttpBindings }> {
|
||||||
return this.instance;
|
return this.instance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export interface NestHonoApplication<
|
|||||||
callback?: (err: Error, address: string) => void,
|
callback?: (err: Error, address: string) => void,
|
||||||
): Promise<TServer>;
|
): Promise<TServer>;
|
||||||
fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
|
fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
|
||||||
getHonoInstance(): Hono<{ Bindings: any }>;
|
getHonoInstance(): Hono;
|
||||||
}
|
}
|
||||||
export type HonoRequest = Request & {
|
export type HonoRequest = Request & {
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
|
|||||||
466
src/server/HonoAdapter/test.ts
Normal file
466
src/server/HonoAdapter/test.ts
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
// import { HttpBindings, createAdaptorServer } from '@hono/node-server';
|
||||||
|
// import { ServeStaticOptions, serveStatic } from '@hono/node-server/serve-static';
|
||||||
|
// import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response';
|
||||||
|
import { RequestMethod } from '@nestjs/common';
|
||||||
|
import { HttpStatus, Logger } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ErrorHandler,
|
||||||
|
NestApplicationOptions,
|
||||||
|
RequestHandler,
|
||||||
|
} from '@nestjs/common/interfaces';
|
||||||
|
import { isObject } from '@nestjs/common/utils/shared.utils';
|
||||||
|
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
|
||||||
|
import { Context, Next, Hono } from 'hono';
|
||||||
|
import { bodyLimit } from 'hono/body-limit';
|
||||||
|
import { cors } from 'hono/cors';
|
||||||
|
// import { Data } from 'hono/dist/types/context';
|
||||||
|
import { RedirectStatusCode, StatusCode } from 'hono/utils/http-status';
|
||||||
|
import http2 from 'http2';
|
||||||
|
import { createServer as createServerHTTP, Server } from 'http';
|
||||||
|
import { createServer as createServerHTTPS } from 'https';
|
||||||
|
|
||||||
|
import { HonoRequest, TypeBodyParser } from './interfaces';
|
||||||
|
import { Data } from 'node_modules/hono/dist/types/context';
|
||||||
|
|
||||||
|
// Define HttpBindings as an empty interface if you don't need specific bindings
|
||||||
|
interface HttpBindings {}
|
||||||
|
|
||||||
|
type HonoHandler = RequestHandler<HonoRequest, Context>;
|
||||||
|
type ServerType = Server | http2.Http2Server | http2.Http2SecureServer;
|
||||||
|
type Ctx = Context | (() => Promise<Context>);
|
||||||
|
type Method =
|
||||||
|
| 'all'
|
||||||
|
| 'get'
|
||||||
|
| 'post'
|
||||||
|
| 'put'
|
||||||
|
| 'delete'
|
||||||
|
| 'use'
|
||||||
|
| 'patch'
|
||||||
|
| 'options';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for using Hono with NestJS.
|
||||||
|
*/
|
||||||
|
export class HonoAdapter extends AbstractHttpAdapter<
|
||||||
|
ServerType,
|
||||||
|
HonoRequest,
|
||||||
|
Context
|
||||||
|
> {
|
||||||
|
private _isParserRegistered: boolean = false;
|
||||||
|
|
||||||
|
protected readonly instance: Hono<{ Bindings: HttpBindings }>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const honoInstance = new Hono<{ Bindings: HttpBindings }>();
|
||||||
|
super(honoInstance);
|
||||||
|
this.instance = honoInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isParserRegistered(): boolean {
|
||||||
|
return !!this._isParserRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRouteAndHandler(
|
||||||
|
pathOrHandler: string | HonoHandler,
|
||||||
|
handler?: HonoHandler,
|
||||||
|
): [string, HonoHandler] {
|
||||||
|
const path = typeof pathOrHandler === 'function' ? '' : pathOrHandler;
|
||||||
|
handler = typeof pathOrHandler === 'function' ? pathOrHandler : handler;
|
||||||
|
|
||||||
|
return [path, handler!];
|
||||||
|
}
|
||||||
|
|
||||||
|
private createRouteHandler(routeHandler: HonoHandler) {
|
||||||
|
return async (ctx: Context, next: Next) => {
|
||||||
|
// ctx.req['params'] = ctx.req.param();
|
||||||
|
// ctx.req['params'] = ctx.req.param();
|
||||||
|
Object.assign(ctx.req, { params: ctx.req.param() });
|
||||||
|
|
||||||
|
await routeHandler(ctx.req, ctx, next);
|
||||||
|
|
||||||
|
return this.getBody(ctx);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async normalizeContext(ctx: Ctx): Promise<Context> {
|
||||||
|
if (typeof ctx === 'function') {
|
||||||
|
return await ctx();
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getBody(ctx: Ctx, body?: Data) {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
|
||||||
|
if (body === undefined && ctx.res && ctx.res.body !== null) {
|
||||||
|
return ctx.res;
|
||||||
|
}
|
||||||
|
|
||||||
|
let responseContentType = await this.getHeader(ctx, 'Content-Type');
|
||||||
|
|
||||||
|
if (!responseContentType || responseContentType.startsWith('text/plain')) {
|
||||||
|
if (
|
||||||
|
body instanceof Buffer ||
|
||||||
|
body instanceof Uint8Array ||
|
||||||
|
body instanceof ArrayBuffer ||
|
||||||
|
body instanceof ReadableStream
|
||||||
|
) {
|
||||||
|
responseContentType = 'application/octet-stream';
|
||||||
|
} else if (isObject(body)) {
|
||||||
|
responseContentType = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.setHeader(ctx, 'Content-Type', String(responseContentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseContentType === 'application/json' && isObject(body)) {
|
||||||
|
return ctx.json(body);
|
||||||
|
} else if (body === undefined) {
|
||||||
|
return ctx.newResponse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.body(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerRoute(
|
||||||
|
method: Method,
|
||||||
|
pathOrHandler: string | HonoHandler,
|
||||||
|
handler?: HonoHandler,
|
||||||
|
) {
|
||||||
|
const [routePath, routeHandler] = this.getRouteAndHandler(
|
||||||
|
pathOrHandler,
|
||||||
|
handler,
|
||||||
|
);
|
||||||
|
const routeHandler2 = this.createRouteHandler(routeHandler);
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case 'all':
|
||||||
|
this.instance.all(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
case 'get':
|
||||||
|
this.instance.get(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
case 'post':
|
||||||
|
this.instance.post(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
case 'put':
|
||||||
|
this.instance.put(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
this.instance.delete(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
case 'use':
|
||||||
|
this.instance.use(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
case 'patch':
|
||||||
|
this.instance.patch(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
case 'options':
|
||||||
|
this.instance.options(routePath, routeHandler2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public all(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('all', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('get', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public post(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('post', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public put(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('put', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('delete', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public use(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('use', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public patch(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('patch', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public options(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||||
|
this.registerRoute('options', pathOrHandler, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public async reply(ctx: Ctx, body: any, statusCode?: StatusCode) {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
|
||||||
|
if (statusCode) {
|
||||||
|
ctx.status(statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.res = await this.getBody(ctx, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async status(ctx: Ctx, statusCode: StatusCode) {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
return ctx.status(statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async end() {
|
||||||
|
// return RESPONSE_ALREADY_SENT;
|
||||||
|
new Response(null, {
|
||||||
|
headers: { ['x-hono-already-sent']: "true" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async redirect(ctx: Ctx, statusCode: RedirectStatusCode, url: string) {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
ctx.res = ctx.redirect(url, statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setErrorHandler(handler: ErrorHandler) {
|
||||||
|
this.instance.onError(async (err: Error, ctx: Context) => {
|
||||||
|
await handler(err, ctx.req, ctx);
|
||||||
|
|
||||||
|
return this.getBody(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public setNotFoundHandler(handler: RequestHandler) {
|
||||||
|
this.instance.notFound(async (ctx: Context) => {
|
||||||
|
await handler(ctx.req, ctx);
|
||||||
|
await this.status(ctx, HttpStatus.NOT_FOUND);
|
||||||
|
|
||||||
|
return this.getBody(ctx, 'Not Found');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async useStaticAssets(path: string, options: any) {
|
||||||
|
Logger.log("Đã bị comment trong adapter")
|
||||||
|
Logger.log('Registering static assets middleware');
|
||||||
|
if ((process as any).versions?.bun) {
|
||||||
|
const { serveStatic } = await import("hono/bun");
|
||||||
|
// app.use(serveStatic({ root: "./dist/client" }))
|
||||||
|
// this.instance.use(serveStatic({ root: "./dist/client" }))
|
||||||
|
this.instance.use(path, serveStatic(options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setViewEngine() {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isHeadersSent(ctx: Ctx): Promise<boolean> {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
return ctx.finalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getHeader(ctx: Ctx, name: string) {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
return ctx.res.headers.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setHeader(ctx: Ctx, name: string, value: string) {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
ctx.res.headers.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async appendHeader(ctx: Ctx, name: string, value: string) {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
ctx.res.headers.append(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRequestHostname(ctx: Ctx): Promise<string> {
|
||||||
|
ctx = await this.normalizeContext(ctx);
|
||||||
|
return ctx.req.header().host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRequestMethod(request: HonoRequest): string {
|
||||||
|
return request.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRequestUrl(request: HonoRequest): string {
|
||||||
|
return request.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enableCors(options: {
|
||||||
|
origin:
|
||||||
|
| string
|
||||||
|
| string[]
|
||||||
|
| ((origin: string, c: Context) => string | undefined | null);
|
||||||
|
allowMethods?: string[];
|
||||||
|
allowHeaders?: string[];
|
||||||
|
maxAge?: number;
|
||||||
|
credentials?: boolean;
|
||||||
|
exposeHeaders?: string[];
|
||||||
|
}) {
|
||||||
|
this.instance.use(cors(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public useBodyParser(
|
||||||
|
type: TypeBodyParser,
|
||||||
|
rawBody?: boolean,
|
||||||
|
bodyLimit?: number,
|
||||||
|
) {
|
||||||
|
Logger.log(
|
||||||
|
`Registering body parser middleware for type: ${type} | bodyLimit: ${bodyLimit}`,
|
||||||
|
);
|
||||||
|
if(bodyLimit !== undefined) {
|
||||||
|
this.instance.use(this.bodyLimit(bodyLimit));
|
||||||
|
}
|
||||||
|
|
||||||
|
// To avoid the Nest application init to override our custom
|
||||||
|
// body parser, we mark the parsers as registered.
|
||||||
|
this._isParserRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): Promise<void> {
|
||||||
|
return new Promise((resolve) => this.httpServer.close(() => resolve()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractClientIp(ctx: Context): string {
|
||||||
|
return (
|
||||||
|
ctx.req.header('cf-connecting-ip') ??
|
||||||
|
ctx.req.header('x-forwarded-for') ??
|
||||||
|
ctx.req.header('x-real-ip') ??
|
||||||
|
ctx.req.header('forwarded') ??
|
||||||
|
ctx.req.header('true-client-ip') ??
|
||||||
|
ctx.req.header('x-client-ip') ??
|
||||||
|
ctx.req.header('x-cluster-client-ip') ??
|
||||||
|
ctx.req.header('x-forwarded') ??
|
||||||
|
ctx.req.header('forwarded-for') ??
|
||||||
|
ctx.req.header('via')??
|
||||||
|
"unknown"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async parseRequestBody(
|
||||||
|
ctx: Context,
|
||||||
|
contentType: string,
|
||||||
|
rawBody: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
if (
|
||||||
|
contentType?.startsWith('multipart/form-data') ||
|
||||||
|
contentType?.startsWith('application/x-www-form-urlencoded')
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(ctx.req as any).body = await ctx.req
|
||||||
|
.parseBody({
|
||||||
|
all: true,
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
} else if (
|
||||||
|
contentType?.startsWith('application/json') ||
|
||||||
|
contentType?.startsWith('text/plain')
|
||||||
|
) {
|
||||||
|
if (rawBody) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(ctx.req as any).rawBody = Buffer.from(await ctx.req.text());
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(ctx.req as any).body = await ctx.req.json().catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public initHttpServer(options: NestApplicationOptions) {
|
||||||
|
Logger.log('Initializing Hono HTTP server adapter');
|
||||||
|
this.instance.use(async (ctx, next) => {
|
||||||
|
// ctx.req['ip'] = this.extractClientIp(ctx);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
// ctx.req['query'] = ctx.req.query() as any;
|
||||||
|
// ctx.req['headers'] = Object.fromEntries(ctx.req.raw.headers);
|
||||||
|
Object.assign(ctx.req, {
|
||||||
|
ip: this.extractClientIp(ctx),
|
||||||
|
query: ctx.req.query() as Record<string, string>,
|
||||||
|
headers: Object.fromEntries(ctx.req.raw.headers as any),
|
||||||
|
})
|
||||||
|
const contentType = ctx.req.header('content-type');
|
||||||
|
if (contentType) {
|
||||||
|
await this.parseRequestBody(ctx, contentType, options.rawBody!);
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
const isHttpsEnabled = options?.httpsOptions;
|
||||||
|
const createServer = isHttpsEnabled
|
||||||
|
? createServerHTTPS
|
||||||
|
: createServerHTTP;
|
||||||
|
// this.httpServer = createAdaptorServer({
|
||||||
|
// fetch: this.instance.fetch,
|
||||||
|
// createServer,
|
||||||
|
// overrideGlobalObjects: false,
|
||||||
|
// });
|
||||||
|
// this.httpServer = isHttpsEnabled ?
|
||||||
|
return this.httpServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getType(): string {
|
||||||
|
return 'hono';
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerParserMiddleware(_prefix?: string, rawBody?: boolean) {
|
||||||
|
if (this._isParserRegistered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.log('Registering parser middleware');
|
||||||
|
this.useBodyParser('application/x-www-form-urlencoded', rawBody);
|
||||||
|
this.useBodyParser('application/json', rawBody);
|
||||||
|
this.useBodyParser('text/plain', rawBody);
|
||||||
|
|
||||||
|
this._isParserRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createMiddlewareFactory(requestMethod: RequestMethod) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
return (path: string, callback: Function) => {
|
||||||
|
const routeMethodsMap: Record<string, Function> = {
|
||||||
|
[RequestMethod.ALL]: this.instance.all,
|
||||||
|
[RequestMethod.DELETE]: this.instance.delete,
|
||||||
|
[RequestMethod.GET]: this.instance.get,
|
||||||
|
[RequestMethod.OPTIONS]: this.instance.options,
|
||||||
|
[RequestMethod.PATCH]: this.instance.patch,
|
||||||
|
[RequestMethod.POST]: this.instance.post,
|
||||||
|
[RequestMethod.PUT]: this.instance.put,
|
||||||
|
};
|
||||||
|
const routeMethod = (
|
||||||
|
routeMethodsMap[requestMethod] || this.instance.get
|
||||||
|
).bind(this.instance);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
routeMethod(path, async (ctx: Context, next: Function) => {
|
||||||
|
await callback(ctx.req, ctx, next);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyVersionFilter(): () => () => unknown {
|
||||||
|
throw new Error('Versioning not yet supported in Hono');
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public listen(port: string | number, ...args: any[]): ServerType {
|
||||||
|
return this.httpServer.listen(port, ...args);
|
||||||
|
}
|
||||||
|
public fetch(input: RequestInfo, init?: RequestInit): Response | Promise<Response> {
|
||||||
|
return this.instance.fetch(input as Request, init);
|
||||||
|
}
|
||||||
|
public getHonoInstance(): Hono<{ Bindings: HttpBindings }> {
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
public bodyLimit(maxSize: number) {
|
||||||
|
return bodyLimit({
|
||||||
|
maxSize,
|
||||||
|
onError: (ctx) => {
|
||||||
|
const errorMessage = `Body size exceeded: ${maxSize} bytes. Size: ${ctx.req.header('Content-Length')} bytes. Method: ${ctx.req.method}. Path: ${ctx.req.path}`;
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
import { LoggerMiddleware } from './middleware';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -13,4 +14,9 @@ import { AppService } from './app.service';
|
|||||||
AppService, // hono-di:providers
|
AppService, // hono-di:providers
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule implements NestModule {
|
||||||
|
configure(consumer: MiddlewareConsumer) {
|
||||||
|
consumer
|
||||||
|
.apply(LoggerMiddleware).forRoutes('*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
34
src/server/middleware.ts
Normal file
34
src/server/middleware.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Source - https://stackoverflow.com/a
|
||||||
|
// Posted by Stark Jeon
|
||||||
|
// Retrieved 2026-01-10, License - CC BY-SA 4.0
|
||||||
|
|
||||||
|
import { Injectable, NestMiddleware, Logger } from "@nestjs/common";
|
||||||
|
import { Context } from "hono";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoggerMiddleware implements NestMiddleware {
|
||||||
|
private logger = new Logger("HTTP");
|
||||||
|
|
||||||
|
async use(request: Request, response: Response, next: any) {
|
||||||
|
const start = Date.now()
|
||||||
|
// console.log("Request:", request.method, request.url, arguments[2].toString());
|
||||||
|
// const { ip, method, originalUrl } = request;
|
||||||
|
// const userAgent = request.get("user-agent") || "";
|
||||||
|
const ctx = arguments[2] as Context;
|
||||||
|
// ctx
|
||||||
|
// response.on("finish", () => {
|
||||||
|
// const { statusCode } = response;
|
||||||
|
// const contentLength = response.get("content-length");
|
||||||
|
|
||||||
|
// this.logger.log(
|
||||||
|
// `${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip}`,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
await next().finally(() => {
|
||||||
|
const ms = Date.now() - start
|
||||||
|
this.logger.log(
|
||||||
|
`${request.method} ${request.url} - ${ms}ms`,
|
||||||
|
) ;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user