@dangao/bun-server 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/readme.md +163 -2
- package/src/auth/controller.ts +148 -0
- package/src/auth/decorators.ts +81 -0
- package/src/auth/index.ts +12 -0
- package/src/auth/jwt.ts +169 -0
- package/src/auth/oauth2.ts +244 -0
- package/src/auth/types.ts +248 -0
- package/src/cache/cache-module.ts +67 -0
- package/src/cache/decorators.ts +202 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/service.ts +151 -0
- package/src/cache/types.ts +420 -0
- package/src/config/config-module.ts +76 -0
- package/src/config/index.ts +8 -0
- package/src/config/service.ts +93 -0
- package/src/config/types.ts +27 -0
- package/src/controller/controller.ts +251 -0
- package/src/controller/decorators.ts +84 -0
- package/src/controller/index.ts +7 -0
- package/src/controller/metadata.ts +27 -0
- package/src/controller/param-binder.ts +157 -0
- package/src/core/application.ts +233 -0
- package/src/core/context.ts +228 -0
- package/src/core/index.ts +4 -0
- package/src/core/server.ts +128 -0
- package/src/core/types.ts +2 -0
- package/src/database/connection-manager.ts +239 -0
- package/src/database/connection-pool.ts +322 -0
- package/src/database/database-extension.ts +62 -0
- package/src/database/database-module.ts +115 -0
- package/src/database/health-indicator.ts +51 -0
- package/src/database/index.ts +47 -0
- package/src/database/orm/decorators.ts +155 -0
- package/src/database/orm/drizzle-repository.ts +39 -0
- package/src/database/orm/index.ts +23 -0
- package/src/database/orm/repository-decorator.ts +39 -0
- package/src/database/orm/repository.ts +103 -0
- package/src/database/orm/service.ts +49 -0
- package/src/database/orm/transaction-decorator.ts +45 -0
- package/src/database/orm/transaction-interceptor.ts +243 -0
- package/src/database/orm/transaction-manager.ts +276 -0
- package/src/database/orm/transaction-types.ts +140 -0
- package/src/database/orm/types.ts +99 -0
- package/src/database/service.ts +221 -0
- package/src/database/types.ts +171 -0
- package/src/di/container.ts +398 -0
- package/src/di/decorators.ts +228 -0
- package/src/di/index.ts +4 -0
- package/src/di/module-registry.ts +188 -0
- package/src/di/module.ts +65 -0
- package/src/di/types.ts +67 -0
- package/src/error/error-codes.ts +222 -0
- package/src/error/filter.ts +43 -0
- package/src/error/handler.ts +66 -0
- package/src/error/http-exception.ts +115 -0
- package/src/error/i18n.ts +217 -0
- package/src/error/index.ts +16 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/logger-extension.ts +31 -0
- package/src/extensions/logger-module.ts +69 -0
- package/src/extensions/types.ts +14 -0
- package/src/files/index.ts +5 -0
- package/src/files/static-middleware.ts +53 -0
- package/src/files/storage.ts +67 -0
- package/src/files/types.ts +33 -0
- package/src/files/upload-middleware.ts +45 -0
- package/src/health/controller.ts +76 -0
- package/src/health/health-module.ts +51 -0
- package/src/health/index.ts +12 -0
- package/src/health/types.ts +28 -0
- package/src/index.ts +270 -0
- package/src/metrics/collector.ts +209 -0
- package/src/metrics/controller.ts +40 -0
- package/src/metrics/index.ts +15 -0
- package/src/metrics/metrics-module.ts +58 -0
- package/src/metrics/middleware.ts +46 -0
- package/src/metrics/prometheus.ts +79 -0
- package/src/metrics/types.ts +103 -0
- package/src/middleware/builtin/cors.ts +60 -0
- package/src/middleware/builtin/error-handler.ts +90 -0
- package/src/middleware/builtin/file-upload.ts +42 -0
- package/src/middleware/builtin/index.ts +14 -0
- package/src/middleware/builtin/logger.ts +91 -0
- package/src/middleware/builtin/rate-limit.ts +252 -0
- package/src/middleware/builtin/static-file.ts +88 -0
- package/src/middleware/decorators.ts +91 -0
- package/src/middleware/index.ts +11 -0
- package/src/middleware/middleware.ts +13 -0
- package/src/middleware/pipeline.ts +93 -0
- package/src/queue/decorators.ts +110 -0
- package/src/queue/index.ts +26 -0
- package/src/queue/queue-module.ts +64 -0
- package/src/queue/service.ts +302 -0
- package/src/queue/types.ts +341 -0
- package/src/request/body-parser.ts +133 -0
- package/src/request/file-handler.ts +46 -0
- package/src/request/index.ts +5 -0
- package/src/request/request.ts +107 -0
- package/src/request/response.ts +150 -0
- package/src/router/decorators.ts +122 -0
- package/src/router/index.ts +6 -0
- package/src/router/registry.ts +98 -0
- package/src/router/route.ts +140 -0
- package/src/router/router.ts +241 -0
- package/src/router/types.ts +27 -0
- package/src/security/access-decision-manager.ts +34 -0
- package/src/security/authentication-manager.ts +47 -0
- package/src/security/context.ts +92 -0
- package/src/security/filter.ts +162 -0
- package/src/security/index.ts +8 -0
- package/src/security/providers/index.ts +3 -0
- package/src/security/providers/jwt-provider.ts +60 -0
- package/src/security/providers/oauth2-provider.ts +70 -0
- package/src/security/security-module.ts +145 -0
- package/src/security/types.ts +165 -0
- package/src/session/decorators.ts +45 -0
- package/src/session/index.ts +19 -0
- package/src/session/middleware.ts +143 -0
- package/src/session/service.ts +218 -0
- package/src/session/session-module.ts +69 -0
- package/src/session/types.ts +373 -0
- package/src/swagger/decorators.ts +133 -0
- package/src/swagger/generator.ts +234 -0
- package/src/swagger/index.ts +7 -0
- package/src/swagger/swagger-extension.ts +41 -0
- package/src/swagger/swagger-module.ts +83 -0
- package/src/swagger/types.ts +188 -0
- package/src/swagger/ui.ts +98 -0
- package/src/testing/harness.ts +96 -0
- package/src/validation/decorators.ts +95 -0
- package/src/validation/errors.ts +28 -0
- package/src/validation/index.ts +14 -0
- package/src/validation/types.ts +35 -0
- package/src/validation/validator.ts +63 -0
- package/src/websocket/decorators.ts +51 -0
- package/src/websocket/index.ts +12 -0
- package/src/websocket/registry.ts +133 -0
- package/tests/cache/cache-module.test.ts +212 -0
- package/tests/config/config-module.test.ts +151 -0
- package/tests/controller/controller.test.ts +189 -0
- package/tests/core/application.test.ts +57 -0
- package/tests/core/context-body.test.ts +44 -0
- package/tests/core/context.test.ts +86 -0
- package/tests/core/edge-cases.test.ts +432 -0
- package/tests/database/database-module.test.ts +385 -0
- package/tests/database/orm.test.ts +164 -0
- package/tests/database/postgres-mysql-integration.test.ts +395 -0
- package/tests/database/transaction.test.ts +238 -0
- package/tests/di/container.test.ts +264 -0
- package/tests/di/module.test.ts +128 -0
- package/tests/error/error-codes.test.ts +121 -0
- package/tests/error/error-handler.test.ts +68 -0
- package/tests/error/error-handling.test.ts +254 -0
- package/tests/error/http-exception.test.ts +37 -0
- package/tests/error/i18n-integration.test.ts +175 -0
- package/tests/extensions/logger-extension.test.ts +40 -0
- package/tests/files/static-middleware.test.ts +67 -0
- package/tests/files/upload-middleware.test.ts +43 -0
- package/tests/health/health-module.test.ts +116 -0
- package/tests/integration/application-router.test.ts +85 -0
- package/tests/integration/body-parsing.test.ts +88 -0
- package/tests/integration/cache-e2e.test.ts +114 -0
- package/tests/integration/oauth2-e2e.test.ts +615 -0
- package/tests/integration/session-e2e.test.ts +207 -0
- package/tests/metrics/metrics-module.test.ts +178 -0
- package/tests/middleware/builtin.test.ts +206 -0
- package/tests/middleware/file-upload.test.ts +41 -0
- package/tests/middleware/middleware.test.ts +120 -0
- package/tests/middleware/pipeline.test.ts +72 -0
- package/tests/middleware/rate-limit.test.ts +314 -0
- package/tests/middleware/static-file.test.ts +62 -0
- package/tests/perf/harness.test.ts +48 -0
- package/tests/perf/optimization.test.ts +183 -0
- package/tests/perf/regression.test.ts +120 -0
- package/tests/queue/queue-module.test.ts +217 -0
- package/tests/request/body-parser.test.ts +96 -0
- package/tests/request/response.test.ts +99 -0
- package/tests/router/decorators.test.ts +48 -0
- package/tests/router/registry.test.ts +51 -0
- package/tests/router/route.test.ts +71 -0
- package/tests/router/router-normalization.test.ts +106 -0
- package/tests/router/router.test.ts +133 -0
- package/tests/security/access-decision-manager.test.ts +84 -0
- package/tests/security/authentication-manager.test.ts +81 -0
- package/tests/security/context.test.ts +302 -0
- package/tests/security/filter.test.ts +225 -0
- package/tests/security/jwt-provider.test.ts +106 -0
- package/tests/security/oauth2-provider.test.ts +269 -0
- package/tests/security/security-module.test.ts +143 -0
- package/tests/session/session-module.test.ts +307 -0
- package/tests/stress/di-stress.test.ts +30 -0
- package/tests/swagger/decorators.test.ts +153 -0
- package/tests/swagger/generator.test.ts +202 -0
- package/tests/swagger/swagger-extension.test.ts +72 -0
- package/tests/swagger/swagger-module.test.ts +79 -0
- package/tests/utils/test-port.ts +10 -0
- package/tests/validation/controller-validation.test.ts +64 -0
- package/tests/validation/validation.test.ts +42 -0
- package/tests/websocket/gateway.test.ts +68 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { BunServer, type ServerOptions } from './server';
|
|
2
|
+
import { Context } from './context';
|
|
3
|
+
import { RouteRegistry } from '../router/registry';
|
|
4
|
+
import { ControllerRegistry } from '../controller/controller';
|
|
5
|
+
import { MiddlewarePipeline } from '../middleware/pipeline';
|
|
6
|
+
import type { Middleware } from '../middleware';
|
|
7
|
+
import { createErrorHandlingMiddleware } from '../middleware';
|
|
8
|
+
import { WebSocketGatewayRegistry } from '../websocket/registry';
|
|
9
|
+
import type { ApplicationExtension } from '../extensions/types';
|
|
10
|
+
import { LoggerExtension } from '../extensions/logger-extension';
|
|
11
|
+
import { ModuleRegistry } from '../di/module-registry';
|
|
12
|
+
import type { ModuleClass } from '../di/module';
|
|
13
|
+
import type { Constructor } from './types'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 应用配置选项
|
|
17
|
+
*/
|
|
18
|
+
export interface ApplicationOptions {
|
|
19
|
+
/**
|
|
20
|
+
* 端口号
|
|
21
|
+
*/
|
|
22
|
+
port?: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 主机名
|
|
26
|
+
*/
|
|
27
|
+
hostname?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 应用主类
|
|
32
|
+
* 负责初始化和启动应用
|
|
33
|
+
*/
|
|
34
|
+
export class Application {
|
|
35
|
+
private server?: BunServer;
|
|
36
|
+
private readonly options: ApplicationOptions;
|
|
37
|
+
private readonly middlewarePipeline: MiddlewarePipeline;
|
|
38
|
+
private readonly websocketRegistry: WebSocketGatewayRegistry;
|
|
39
|
+
private readonly extensions: ApplicationExtension[] = [];
|
|
40
|
+
|
|
41
|
+
public constructor(options: ApplicationOptions = {}) {
|
|
42
|
+
this.options = options;
|
|
43
|
+
this.middlewarePipeline = new MiddlewarePipeline([createErrorHandlingMiddleware()]);
|
|
44
|
+
this.websocketRegistry = WebSocketGatewayRegistry.getInstance();
|
|
45
|
+
|
|
46
|
+
// 每个应用实例使用前先清空全局注册表,避免不同应用之间互相污染
|
|
47
|
+
RouteRegistry.getInstance().clear();
|
|
48
|
+
ControllerRegistry.getInstance().clear();
|
|
49
|
+
ModuleRegistry.getInstance().clear();
|
|
50
|
+
|
|
51
|
+
// 默认注册 Logger(如果通过模块注册,会被覆盖)
|
|
52
|
+
this.registerExtension(new LoggerExtension());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 注册全局中间件
|
|
57
|
+
* @param middleware - 中间件函数
|
|
58
|
+
*/
|
|
59
|
+
public use(middleware: Middleware): void {
|
|
60
|
+
this.middlewarePipeline.use(middleware);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 启动应用
|
|
65
|
+
*/
|
|
66
|
+
public async listen(port?: number, hostname?: string): Promise<void> {
|
|
67
|
+
if (this.server?.isRunning()) {
|
|
68
|
+
throw new Error('Application is already running');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 初始化所有扩展(包括数据库连接等)
|
|
72
|
+
await this.initializeExtensions();
|
|
73
|
+
|
|
74
|
+
const serverOptions: ServerOptions = {
|
|
75
|
+
port: port ?? this.options.port ?? 3000,
|
|
76
|
+
hostname: hostname ?? this.options.hostname,
|
|
77
|
+
fetch: this.handleRequest.bind(this),
|
|
78
|
+
websocketRegistry: this.websocketRegistry,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
this.server = new BunServer(serverOptions);
|
|
82
|
+
this.server.start();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 初始化所有扩展
|
|
87
|
+
*/
|
|
88
|
+
private async initializeExtensions(): Promise<void> {
|
|
89
|
+
const container = this.getContainer();
|
|
90
|
+
|
|
91
|
+
// 初始化应用级别的扩展
|
|
92
|
+
for (const extension of this.extensions) {
|
|
93
|
+
// 如果扩展有 initialize 方法,调用它
|
|
94
|
+
if (
|
|
95
|
+
extension &&
|
|
96
|
+
typeof extension === 'object' &&
|
|
97
|
+
'initialize' in extension &&
|
|
98
|
+
typeof extension.initialize === 'function'
|
|
99
|
+
) {
|
|
100
|
+
await extension.initialize(container);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 初始化模块中的扩展(通过已注册的扩展列表)
|
|
105
|
+
// 模块扩展已经在 registerModule 时添加到 this.extensions
|
|
106
|
+
// 所以上面的循环已经处理了
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 停止应用
|
|
111
|
+
*/
|
|
112
|
+
public async stop(): Promise<void> {
|
|
113
|
+
// 关闭所有扩展(包括数据库连接等)
|
|
114
|
+
await this.closeExtensions();
|
|
115
|
+
this.server?.stop();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 关闭所有扩展
|
|
120
|
+
*/
|
|
121
|
+
private async closeExtensions(): Promise<void> {
|
|
122
|
+
const container = this.getContainer();
|
|
123
|
+
|
|
124
|
+
// 关闭所有扩展(包括模块扩展,因为它们已经在 registerModule 时添加到 this.extensions)
|
|
125
|
+
// 按相反顺序关闭,确保依赖关系正确
|
|
126
|
+
for (let i = this.extensions.length - 1; i >= 0; i--) {
|
|
127
|
+
const extension = this.extensions[i];
|
|
128
|
+
if (
|
|
129
|
+
extension &&
|
|
130
|
+
typeof extension === 'object' &&
|
|
131
|
+
'close' in extension &&
|
|
132
|
+
typeof extension.close === 'function'
|
|
133
|
+
) {
|
|
134
|
+
await extension.close(container);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 处理请求
|
|
141
|
+
* @param context - 请求上下文
|
|
142
|
+
* @returns 响应对象
|
|
143
|
+
*/
|
|
144
|
+
private async handleRequest(context: Context): Promise<Response> {
|
|
145
|
+
// 对于 POST、PUT、PATCH 请求,提前解析 body 并缓存
|
|
146
|
+
// 这样可以确保 Request.body 流只读取一次
|
|
147
|
+
if (['POST', 'PUT', 'PATCH'].includes(context.method)) {
|
|
148
|
+
await context.getBody();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 先通过路由解析出处理器信息,便于安全中间件等基于路由元数据做决策
|
|
152
|
+
const registry = RouteRegistry.getInstance();
|
|
153
|
+
const router = registry.getRouter();
|
|
154
|
+
|
|
155
|
+
// 预解析路由,仅设置上下文信息,不执行处理器
|
|
156
|
+
await router.preHandle(context);
|
|
157
|
+
|
|
158
|
+
// 再进入中间件管道,由中间件(如安全过滤器)根据 routeHandler 和 Auth 元数据做校验,
|
|
159
|
+
// 最后再由路由真正执行控制器方法
|
|
160
|
+
return await this.middlewarePipeline.run(context, async () => {
|
|
161
|
+
const response = await router.handle(context);
|
|
162
|
+
if (response) {
|
|
163
|
+
return response;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
context.setStatus(404);
|
|
167
|
+
return context.createResponse({ error: 'Not Found' });
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 注册控制器
|
|
173
|
+
* @param controllerClass - 控制器类
|
|
174
|
+
*/
|
|
175
|
+
public registerController(controllerClass: Constructor<unknown>): void {
|
|
176
|
+
const registry = ControllerRegistry.getInstance();
|
|
177
|
+
registry.register(controllerClass);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 注册模块
|
|
182
|
+
* @param moduleClass - 模块类
|
|
183
|
+
*/
|
|
184
|
+
public registerModule(moduleClass: ModuleClass): void {
|
|
185
|
+
const registry = ModuleRegistry.getInstance();
|
|
186
|
+
registry.register(moduleClass, this.getContainer());
|
|
187
|
+
|
|
188
|
+
// 注册模块的扩展和中间件
|
|
189
|
+
const extensions = registry.getModuleExtensions(moduleClass);
|
|
190
|
+
for (const extension of extensions) {
|
|
191
|
+
this.registerExtension(extension);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const middlewares = registry.getModuleMiddlewares(moduleClass);
|
|
195
|
+
for (const middleware of middlewares) {
|
|
196
|
+
this.use(middleware);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 注册 WebSocket 网关
|
|
202
|
+
* @param gatewayClass - WebSocket 网关类
|
|
203
|
+
*/
|
|
204
|
+
public registerWebSocketGateway(gatewayClass: Constructor<unknown>): void {
|
|
205
|
+
this.websocketRegistry.register(gatewayClass);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 注册扩展
|
|
210
|
+
* @param extension - 应用扩展
|
|
211
|
+
*/
|
|
212
|
+
public registerExtension(extension: ApplicationExtension): void {
|
|
213
|
+
this.extensions.push(extension);
|
|
214
|
+
extension.register(this.getContainer());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 获取服务器实例
|
|
219
|
+
* @returns Bun Server 实例
|
|
220
|
+
*/
|
|
221
|
+
public getServer(): BunServer | undefined {
|
|
222
|
+
return this.server;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 获取 DI 容器(用于注册服务)
|
|
227
|
+
* @returns DI 容器
|
|
228
|
+
*/
|
|
229
|
+
public getContainer() {
|
|
230
|
+
return ControllerRegistry.getInstance().getContainer();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { BodyParser } from '../request/body-parser';
|
|
2
|
+
import type { UploadedFileInfo } from '../files';
|
|
3
|
+
import type { BodyInit } from 'bun'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 请求上下文
|
|
7
|
+
* 封装 Request 和 Response,提供便捷的访问方法
|
|
8
|
+
*/
|
|
9
|
+
export class Context {
|
|
10
|
+
/**
|
|
11
|
+
* 原始请求对象
|
|
12
|
+
*/
|
|
13
|
+
public readonly request: Request;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 响应对象(可选,由框架创建)
|
|
17
|
+
*/
|
|
18
|
+
public response?: Response;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 请求 URL
|
|
22
|
+
*/
|
|
23
|
+
public readonly url: URL;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 请求方法
|
|
27
|
+
*/
|
|
28
|
+
public readonly method: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 请求路径
|
|
32
|
+
*/
|
|
33
|
+
public readonly path: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 查询参数
|
|
37
|
+
*/
|
|
38
|
+
public readonly query: URLSearchParams;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 路径参数(由路由匹配后填充)
|
|
42
|
+
*/
|
|
43
|
+
public params: Record<string, string> = {};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 请求头
|
|
47
|
+
*/
|
|
48
|
+
public readonly headers: Headers;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 响应头
|
|
52
|
+
*/
|
|
53
|
+
public responseHeaders: Headers;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 状态码
|
|
57
|
+
*/
|
|
58
|
+
public statusCode: number = 200;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 上传文件信息
|
|
62
|
+
*/
|
|
63
|
+
public files: UploadedFileInfo[] = [];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 请求体(解析后的)
|
|
67
|
+
*/
|
|
68
|
+
private _body?: unknown;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 是否已解析请求体
|
|
72
|
+
*/
|
|
73
|
+
private _bodyParsed: boolean = false;
|
|
74
|
+
|
|
75
|
+
public constructor(request: Request) {
|
|
76
|
+
this.request = request;
|
|
77
|
+
this.url = new URL(request.url);
|
|
78
|
+
this.method = request.method;
|
|
79
|
+
this.path = this.url.pathname;
|
|
80
|
+
this.query = this.url.searchParams;
|
|
81
|
+
this.headers = request.headers;
|
|
82
|
+
this.responseHeaders = new Headers();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 获取请求体(自动解析)
|
|
87
|
+
* @returns 解析后的请求体
|
|
88
|
+
*/
|
|
89
|
+
public async getBody(): Promise<unknown> {
|
|
90
|
+
if (!this._bodyParsed) {
|
|
91
|
+
this._body = await BodyParser.parse(this.request);
|
|
92
|
+
this._bodyParsed = true;
|
|
93
|
+
}
|
|
94
|
+
return this._body;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 获取请求体(已解析的,如果未解析则返回 undefined)
|
|
99
|
+
* @returns 请求体或 undefined
|
|
100
|
+
*/
|
|
101
|
+
public get body(): unknown {
|
|
102
|
+
return this._body;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 设置请求体
|
|
107
|
+
* @param body - 请求体
|
|
108
|
+
*/
|
|
109
|
+
public set body(body: unknown) {
|
|
110
|
+
this._body = body;
|
|
111
|
+
this._bodyParsed = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 获取查询参数
|
|
116
|
+
* @param key - 参数名
|
|
117
|
+
* @returns 参数值
|
|
118
|
+
*/
|
|
119
|
+
public getQuery(key: string): string | null {
|
|
120
|
+
return this.query.get(key);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 获取所有查询参数
|
|
125
|
+
* @returns 查询参数对象
|
|
126
|
+
*/
|
|
127
|
+
public getQueryAll(): Record<string, string> {
|
|
128
|
+
const result: Record<string, string> = {};
|
|
129
|
+
this.query.forEach((value, key) => {
|
|
130
|
+
result[key] = value;
|
|
131
|
+
});
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 获取路径参数
|
|
137
|
+
* @param key - 参数名
|
|
138
|
+
* @returns 参数值
|
|
139
|
+
*/
|
|
140
|
+
public getParam(key: string): string | undefined {
|
|
141
|
+
return this.params[key];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 获取请求头
|
|
146
|
+
* @param key - 头名称
|
|
147
|
+
* @returns 头值
|
|
148
|
+
*/
|
|
149
|
+
public getHeader(key: string): string | null {
|
|
150
|
+
return this.headers.get(key);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 获取客户端 IP 地址
|
|
155
|
+
* 优先从 X-Forwarded-For 头获取(代理场景),否则从连接信息获取
|
|
156
|
+
* @returns 客户端 IP 地址
|
|
157
|
+
*/
|
|
158
|
+
public getClientIp(): string {
|
|
159
|
+
// 优先从 X-Forwarded-For 头获取(处理代理场景)
|
|
160
|
+
const forwardedFor = this.getHeader('X-Forwarded-For');
|
|
161
|
+
if (forwardedFor) {
|
|
162
|
+
// X-Forwarded-For 可能包含多个 IP,取第一个
|
|
163
|
+
return forwardedFor.split(',')[0].trim();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 尝试从 X-Real-IP 头获取
|
|
167
|
+
const realIp = this.getHeader('X-Real-IP');
|
|
168
|
+
if (realIp) {
|
|
169
|
+
return realIp.trim();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 如果是在 Bun.serve 中,可以从 request 获取
|
|
173
|
+
// 但标准 Request 对象没有直接 IP 信息,返回默认值
|
|
174
|
+
return 'unknown';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 设置响应头
|
|
179
|
+
* @param key - 头名称
|
|
180
|
+
* @param value - 头值
|
|
181
|
+
*/
|
|
182
|
+
public setHeader(key: string, value: string): void {
|
|
183
|
+
this.responseHeaders.set(key, value);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 设置状态码
|
|
188
|
+
* @param code - HTTP 状态码
|
|
189
|
+
*/
|
|
190
|
+
public setStatus(code: number): void {
|
|
191
|
+
this.statusCode = code;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 创建响应
|
|
196
|
+
* @param body - 响应体
|
|
197
|
+
* @param init - 响应初始化选项
|
|
198
|
+
* @returns Response 对象
|
|
199
|
+
*/
|
|
200
|
+
public createResponse(body?: unknown, init?: ResponseInit): Response {
|
|
201
|
+
// 如果已经是 Response 对象,直接返回
|
|
202
|
+
if (body instanceof Response) {
|
|
203
|
+
return body;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const headers = new Headers(this.responseHeaders);
|
|
207
|
+
|
|
208
|
+
// 如果 body 是对象,自动序列化为 JSON
|
|
209
|
+
if (body !== undefined && body !== null) {
|
|
210
|
+
if (typeof body === 'object' && !(body instanceof ArrayBuffer) && !(body instanceof Blob)) {
|
|
211
|
+
headers.set('Content-Type', 'application/json');
|
|
212
|
+
const jsonString = JSON.stringify(body);
|
|
213
|
+
return new Response(jsonString, {
|
|
214
|
+
status: this.statusCode,
|
|
215
|
+
headers,
|
|
216
|
+
...init,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return new Response(body as BodyInit, {
|
|
222
|
+
status: this.statusCode,
|
|
223
|
+
headers,
|
|
224
|
+
...init,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Server } from "bun";
|
|
2
|
+
import { Context } from "./context";
|
|
3
|
+
import { LoggerManager } from "@dangao/logsmith";
|
|
4
|
+
import type { WebSocketGatewayRegistry } from "../websocket/registry";
|
|
5
|
+
import type { WebSocketConnectionData } from "../websocket/registry";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 服务器配置选项
|
|
9
|
+
*/
|
|
10
|
+
export interface ServerOptions {
|
|
11
|
+
/**
|
|
12
|
+
* 端口号
|
|
13
|
+
*/
|
|
14
|
+
port?: number;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 主机名
|
|
18
|
+
*/
|
|
19
|
+
hostname?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 请求处理函数
|
|
23
|
+
*/
|
|
24
|
+
fetch: (context: Context) => Response | Promise<Response>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* WebSocket 网关注册表
|
|
28
|
+
*/
|
|
29
|
+
websocketRegistry?: WebSocketGatewayRegistry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 服务器封装类
|
|
34
|
+
* 基于 Bun.serve() 构建
|
|
35
|
+
*/
|
|
36
|
+
export class BunServer {
|
|
37
|
+
private server?: Server<WebSocketConnectionData>;
|
|
38
|
+
private readonly options: ServerOptions;
|
|
39
|
+
|
|
40
|
+
public constructor(options: ServerOptions) {
|
|
41
|
+
this.options = options;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 启动服务器
|
|
46
|
+
*/
|
|
47
|
+
public start(): void {
|
|
48
|
+
if (this.server) {
|
|
49
|
+
throw new Error("Server is already running");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const logger = LoggerManager.getLogger();
|
|
53
|
+
|
|
54
|
+
this.server = Bun.serve({
|
|
55
|
+
port: this.options.port ?? 3000,
|
|
56
|
+
hostname: this.options.hostname,
|
|
57
|
+
fetch: (
|
|
58
|
+
request: Request,
|
|
59
|
+
server: Server<WebSocketConnectionData>,
|
|
60
|
+
): Response | Promise<Response> | undefined => {
|
|
61
|
+
const upgradeHeader = request.headers.get("upgrade");
|
|
62
|
+
if (
|
|
63
|
+
this.options.websocketRegistry &&
|
|
64
|
+
upgradeHeader &&
|
|
65
|
+
upgradeHeader.toLowerCase() === "websocket"
|
|
66
|
+
) {
|
|
67
|
+
const url = new URL(request.url);
|
|
68
|
+
if (!this.options.websocketRegistry.hasGateway(url.pathname)) {
|
|
69
|
+
return new Response("WebSocket gateway not found", { status: 404 });
|
|
70
|
+
}
|
|
71
|
+
const upgraded = server.upgrade(request, {
|
|
72
|
+
data: { path: url.pathname },
|
|
73
|
+
});
|
|
74
|
+
if (upgraded) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const context = new Context(request);
|
|
81
|
+
return this.options.fetch(context);
|
|
82
|
+
},
|
|
83
|
+
websocket: {
|
|
84
|
+
open: (ws) => {
|
|
85
|
+
this.options.websocketRegistry?.handleOpen(ws);
|
|
86
|
+
},
|
|
87
|
+
message: (ws, message) => {
|
|
88
|
+
this.options.websocketRegistry?.handleMessage(ws, message);
|
|
89
|
+
},
|
|
90
|
+
close: (ws, code, reason) => {
|
|
91
|
+
this.options.websocketRegistry?.handleClose(ws, code, reason);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const hostname = this.options.hostname ?? "localhost";
|
|
97
|
+
const port = this.options.port ?? 3000;
|
|
98
|
+
logger.info(`Server started at http://${hostname}:${port}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 停止服务器
|
|
103
|
+
*/
|
|
104
|
+
public stop(): void {
|
|
105
|
+
if (this.server) {
|
|
106
|
+
const logger = LoggerManager.getLogger();
|
|
107
|
+
this.server.stop();
|
|
108
|
+
this.server = undefined;
|
|
109
|
+
logger.info("Server stopped");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 获取服务器实例
|
|
115
|
+
* @returns Bun Server 实例
|
|
116
|
+
*/
|
|
117
|
+
public getServer(): Server<WebSocketConnectionData> | undefined {
|
|
118
|
+
return this.server;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 检查服务器是否运行中
|
|
123
|
+
* @returns 是否运行中
|
|
124
|
+
*/
|
|
125
|
+
public isRunning(): boolean {
|
|
126
|
+
return this.server !== undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|