From d3e489aad302288683f64d1cf2386157b3ca5e4e Mon Sep 17 00:00:00 2001 From: yhh <359807859@qq.com> Date: Wed, 31 Dec 2025 09:52:45 +0800 Subject: [PATCH] feat(server): add HTTP file-based routing support - Add file-based HTTP routing with httpDir and httpPrefix config options - Create defineHttp
() helper for type-safe route definitions - Support dynamic routes with [param].ts file naming convention - Add CORS support for cross-origin requests - Allow merging file routes with inline http config - RPC server now supports attaching to existing HTTP server via server option - Add comprehensive documentation for HTTP routing --- .../content/docs/en/modules/network/server.md | 130 +++++++++ .../content/docs/modules/network/server.md | 130 +++++++++ packages/framework/network/CHANGELOG.md | 7 + packages/framework/network/package.json | 2 +- packages/framework/rpc/CHANGELOG.md | 44 +++ packages/framework/rpc/package.json | 2 +- packages/framework/rpc/src/server/index.ts | 39 ++- packages/framework/server/CHANGELOG.md | 49 ++++ packages/framework/server/package.json | 2 +- packages/framework/server/src/core/server.ts | 141 ++++++++-- .../framework/server/src/helpers/define.ts | 36 ++- packages/framework/server/src/http/index.ts | 7 + packages/framework/server/src/http/router.ts | 263 ++++++++++++++++++ packages/framework/server/src/http/types.ts | 161 +++++++++++ packages/framework/server/src/index.ts | 14 +- .../framework/server/src/router/loader.ts | 113 +++++++- packages/framework/server/src/types/index.ts | 114 ++++++++ packages/framework/transaction/CHANGELOG.md | 7 + packages/framework/transaction/package.json | 2 +- 19 files changed, 1226 insertions(+), 37 deletions(-) create mode 100644 packages/framework/server/src/http/index.ts create mode 100644 packages/framework/server/src/http/router.ts create mode 100644 packages/framework/server/src/http/types.ts diff --git a/docs/src/content/docs/en/modules/network/server.md b/docs/src/content/docs/en/modules/network/server.md index c883fb21..b2fdc0f5 100644 --- a/docs/src/content/docs/en/modules/network/server.md +++ b/docs/src/content/docs/en/modules/network/server.md @@ -79,10 +79,140 @@ await server.start() | `tickRate` | `number` | `20` | Global tick rate (Hz) | | `apiDir` | `string` | `'src/api'` | API handlers directory | | `msgDir` | `string` | `'src/msg'` | Message handlers directory | +| `httpDir` | `string` | `'src/http'` | HTTP routes directory | +| `httpPrefix` | `string` | `'/api'` | HTTP routes prefix | +| `cors` | `boolean \| CorsOptions` | - | CORS configuration | | `onStart` | `(port) => void` | - | Start callback | | `onConnect` | `(conn) => void` | - | Connection callback | | `onDisconnect` | `(conn) => void` | - | Disconnect callback | +## HTTP Routing + +Supports HTTP API sharing the same port with WebSocket, ideal for login, registration, and similar scenarios. + +### File-based Routing + +Create route files in the `httpDir` directory, automatically mapped to HTTP endpoints: + +``` +src/http/ +├── login.ts → POST /api/login +├── register.ts → POST /api/register +├── health.ts → GET /api/health (set method: 'GET') +└── users/ + └── [id].ts → POST /api/users/:id (dynamic route) +``` + +### Define Routes + +Use `defineHttp` to define type-safe route handlers: + +```typescript +// src/http/login.ts +import { defineHttp } from '@esengine/server' + +interface LoginBody { + username: string + password: string +} + +export default defineHttp= { */ export interface ServeOptions
{ /** - * @zh 监听端口 - * @en Listen port + * @zh 监听端口(与 server 二选一) + * @en Listen port (mutually exclusive with server) */ - port: number + port?: number + + /** + * @zh 已有的 HTTP 服务器(与 port 二选一) + * @en Existing HTTP server (mutually exclusive with port) + * + * @zh 使用此选项可以在同一端口同时支持 HTTP 和 WebSocket + * @en Use this option to support both HTTP and WebSocket on the same port + */ + server?: HttpServer /** * @zh API 处理器 @@ -280,7 +290,16 @@ export function serve
( async start() { return new Promise((resolve) => { - wss = new WebSocketServer({ port: options.port }) + // 根据配置创建 WebSocketServer + if (options.server) { + // 附加到已有的 HTTP 服务器 + wss = new WebSocketServer({ server: options.server }) + } else if (options.port) { + // 独立创建 + wss = new WebSocketServer({ port: options.port }) + } else { + throw new Error('Either port or server must be provided') + } wss.on('connection', async (ws, req) => { const id = String(++connIdCounter) @@ -318,10 +337,16 @@ export function serve
( await options.onConnect?.(conn) }) - wss.on('listening', () => { - options.onStart?.(options.port) + // 如果使用已有的 HTTP 服务器,WebSocketServer 不会触发 listening 事件 + if (options.server) { + options.onStart?.(0) // 端口由 HTTP 服务器管理 resolve() - }) + } else { + wss.on('listening', () => { + options.onStart?.(options.port!) + resolve() + }) + } }) }, diff --git a/packages/framework/server/CHANGELOG.md b/packages/framework/server/CHANGELOG.md index 28813347..66b75689 100644 --- a/packages/framework/server/CHANGELOG.md +++ b/packages/framework/server/CHANGELOG.md @@ -1,5 +1,54 @@ # @esengine/server +## 4.1.0 + +### Minor Changes + +- feat(server): add HTTP file-based routing support + + New feature that allows organizing HTTP routes in separate files, similar to API and message handlers: + + ```typescript + // src/http/login.ts + import { defineHttp } from '@esengine/server'; + + export default defineHttp<{ username: string; password: string }>({ + method: 'POST', + handler(req, res) { + const { username, password } = req.body; + // ... authentication logic + res.json({ token: '...', userId: '...' }); + } + }); + ``` + + Server configuration: + + ```typescript + const server = await createServer({ + port: 8080, + httpDir: 'src/http', // HTTP routes directory + httpPrefix: '/api', // Route prefix + cors: true + }); + ``` + + File naming convention: + - `login.ts` → POST /api/login + - `users/profile.ts` → POST /api/users/profile + - `users/[id].ts` → POST /api/users/:id (dynamic routes) + - Set `method: 'GET'` in defineHttp for GET requests + + Also includes: + - `defineHttp
()` helper function for type-safe route definitions + - Support for merging file routes with inline `http` config + - RPC server now supports attaching to existing HTTP server via `server` option + +### Patch Changes + +- Updated dependencies []: + - @esengine/rpc@1.1.2 + ## 4.0.0 ### Patch Changes diff --git a/packages/framework/server/package.json b/packages/framework/server/package.json index 507f801d..35660ba4 100644 --- a/packages/framework/server/package.json +++ b/packages/framework/server/package.json @@ -1,6 +1,6 @@ { "name": "@esengine/server", - "version": "4.0.0", + "version": "4.1.0", "description": "Game server framework for ESEngine with file-based routing", "type": "module", "main": "./dist/index.js", diff --git a/packages/framework/server/src/core/server.ts b/packages/framework/server/src/core/server.ts index 41ea8e92..c319cad2 100644 --- a/packages/framework/server/src/core/server.ts +++ b/packages/framework/server/src/core/server.ts @@ -4,6 +4,7 @@ */ import * as path from 'node:path' +import { createServer as createHttpServer, type Server as HttpServer } from 'node:http' import { serve, type RpcServer } from '@esengine/rpc/server' import { rpc } from '@esengine/rpc' import type { @@ -14,18 +15,23 @@ import type { MsgContext, LoadedApiHandler, LoadedMsgHandler, + LoadedHttpHandler, } from '../types/index.js' -import { loadApiHandlers, loadMsgHandlers } from '../router/loader.js' +import type { HttpRoutes, HttpHandler } from '../http/types.js' +import { loadApiHandlers, loadMsgHandlers, loadHttpHandlers } from '../router/loader.js' import { RoomManager, type RoomClass, type Room } from '../room/index.js' +import { createHttpRouter } from '../http/router.js' /** * @zh 默认配置 * @en Default configuration */ -const DEFAULT_CONFIG: Required