docs(network): add HTTP routing documentation (#415)
Add comprehensive HTTP routing documentation for the server module: - Create new http.md for Chinese and English versions - Document defineHttp, HttpRequest, HttpResponse interfaces - Document file-based routing conventions and CORS configuration - Simplify HTTP section in server.md with link to detailed docs
This commit is contained in:
@@ -267,6 +267,7 @@ export default defineConfig({
|
|||||||
{ label: '概述', slug: 'modules/network', translations: { en: 'Overview' } },
|
{ label: '概述', slug: 'modules/network', translations: { en: 'Overview' } },
|
||||||
{ label: '客户端', slug: 'modules/network/client', translations: { en: 'Client' } },
|
{ label: '客户端', slug: 'modules/network/client', translations: { en: 'Client' } },
|
||||||
{ label: '服务器', slug: 'modules/network/server', translations: { en: 'Server' } },
|
{ label: '服务器', slug: 'modules/network/server', translations: { en: 'Server' } },
|
||||||
|
{ label: 'HTTP 路由', slug: 'modules/network/http', translations: { en: 'HTTP Routing' } },
|
||||||
{ label: '认证系统', slug: 'modules/network/auth', translations: { en: 'Authentication' } },
|
{ label: '认证系统', slug: 'modules/network/auth', translations: { en: 'Authentication' } },
|
||||||
{ label: '速率限制', slug: 'modules/network/rate-limit', translations: { en: 'Rate Limiting' } },
|
{ label: '速率限制', slug: 'modules/network/rate-limit', translations: { en: 'Rate Limiting' } },
|
||||||
{ label: '状态同步', slug: 'modules/network/sync', translations: { en: 'State Sync' } },
|
{ label: '状态同步', slug: 'modules/network/sync', translations: { en: 'State Sync' } },
|
||||||
|
|||||||
490
docs/src/content/docs/en/modules/network/http.md
Normal file
490
docs/src/content/docs/en/modules/network/http.md
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
---
|
||||||
|
title: "HTTP Routing"
|
||||||
|
description: "HTTP REST API routing with WebSocket port sharing support"
|
||||||
|
---
|
||||||
|
|
||||||
|
`@esengine/server` includes a lightweight HTTP routing feature that can share the same port with WebSocket services, making it easy to implement REST APIs.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Inline Route Definition
|
||||||
|
|
||||||
|
The simplest way is to define HTTP routes directly when creating the server:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createServer } from '@esengine/server'
|
||||||
|
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
http: {
|
||||||
|
'/api/health': (req, res) => {
|
||||||
|
res.json({ status: 'ok', time: Date.now() })
|
||||||
|
},
|
||||||
|
'/api/users': {
|
||||||
|
GET: (req, res) => {
|
||||||
|
res.json({ users: [] })
|
||||||
|
},
|
||||||
|
POST: async (req, res) => {
|
||||||
|
const body = req.body as { name: string }
|
||||||
|
res.status(201).json({ id: '1', name: body.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cors: true // Enable CORS
|
||||||
|
})
|
||||||
|
|
||||||
|
await server.start()
|
||||||
|
```
|
||||||
|
|
||||||
|
### File-based Routing
|
||||||
|
|
||||||
|
For larger projects, file-based routing is recommended. Create a `src/http` directory where each file corresponds to a route:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/login.ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
interface LoginBody {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineHttp<LoginBody>({
|
||||||
|
method: 'POST',
|
||||||
|
handler(req, res) {
|
||||||
|
const { username, password } = req.body as LoginBody
|
||||||
|
|
||||||
|
// Validate user...
|
||||||
|
if (username === 'admin' && password === '123456') {
|
||||||
|
res.json({ token: 'jwt-token-here', userId: 'user-1' })
|
||||||
|
} else {
|
||||||
|
res.error(401, 'Invalid username or password')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// server.ts
|
||||||
|
import { createServer } from '@esengine/server'
|
||||||
|
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
httpDir: './src/http', // HTTP routes directory
|
||||||
|
httpPrefix: '/api', // Route prefix
|
||||||
|
cors: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await server.start()
|
||||||
|
// Route: POST /api/login
|
||||||
|
```
|
||||||
|
|
||||||
|
## defineHttp Definition
|
||||||
|
|
||||||
|
`defineHttp` is used to define type-safe HTTP handlers:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
interface CreateUserBody {
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineHttp<CreateUserBody>({
|
||||||
|
// HTTP method (default POST)
|
||||||
|
method: 'POST',
|
||||||
|
|
||||||
|
// Handler function
|
||||||
|
handler(req, res) {
|
||||||
|
const body = req.body as CreateUserBody
|
||||||
|
// Handle request...
|
||||||
|
res.status(201).json({ id: 'new-user-id' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supported HTTP Methods
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS'
|
||||||
|
```
|
||||||
|
|
||||||
|
## HttpRequest Object
|
||||||
|
|
||||||
|
The HTTP request object contains the following properties:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HttpRequest {
|
||||||
|
/** Raw Node.js IncomingMessage */
|
||||||
|
raw: IncomingMessage
|
||||||
|
|
||||||
|
/** HTTP method */
|
||||||
|
method: string
|
||||||
|
|
||||||
|
/** Request path */
|
||||||
|
path: string
|
||||||
|
|
||||||
|
/** Query parameters */
|
||||||
|
query: Record<string, string>
|
||||||
|
|
||||||
|
/** Request headers */
|
||||||
|
headers: Record<string, string | string[] | undefined>
|
||||||
|
|
||||||
|
/** Parsed request body */
|
||||||
|
body: unknown
|
||||||
|
|
||||||
|
/** Client IP */
|
||||||
|
ip: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Examples
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// Get query parameters
|
||||||
|
const page = parseInt(req.query.page ?? '1')
|
||||||
|
const limit = parseInt(req.query.limit ?? '10')
|
||||||
|
|
||||||
|
// Get request headers
|
||||||
|
const authHeader = req.headers.authorization
|
||||||
|
|
||||||
|
// Get client IP
|
||||||
|
console.log('Request from:', req.ip)
|
||||||
|
|
||||||
|
res.json({ page, limit })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Body Parsing
|
||||||
|
|
||||||
|
The request body is automatically parsed based on `Content-Type`:
|
||||||
|
|
||||||
|
- `application/json` - Parsed as JSON object
|
||||||
|
- `application/x-www-form-urlencoded` - Parsed as key-value object
|
||||||
|
- Others - Kept as raw string
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp<{ name: string; age: number }>({
|
||||||
|
method: 'POST',
|
||||||
|
handler(req, res) {
|
||||||
|
// body is already parsed
|
||||||
|
const { name, age } = req.body as { name: string; age: number }
|
||||||
|
res.json({ received: { name, age } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## HttpResponse Object
|
||||||
|
|
||||||
|
The HTTP response object provides a chainable API:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HttpResponse {
|
||||||
|
/** Raw Node.js ServerResponse */
|
||||||
|
raw: ServerResponse
|
||||||
|
|
||||||
|
/** Set status code */
|
||||||
|
status(code: number): HttpResponse
|
||||||
|
|
||||||
|
/** Set response header */
|
||||||
|
header(name: string, value: string): HttpResponse
|
||||||
|
|
||||||
|
/** Send JSON response */
|
||||||
|
json(data: unknown): void
|
||||||
|
|
||||||
|
/** Send text response */
|
||||||
|
text(data: string): void
|
||||||
|
|
||||||
|
/** Send error response */
|
||||||
|
error(code: number, message: string): void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Examples
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'POST',
|
||||||
|
handler(req, res) {
|
||||||
|
// Set status code and custom headers
|
||||||
|
res
|
||||||
|
.status(201)
|
||||||
|
.header('X-Custom-Header', 'value')
|
||||||
|
.json({ created: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// Send error response
|
||||||
|
res.error(404, 'Resource not found')
|
||||||
|
// Equivalent to: res.status(404).json({ error: 'Resource not found' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// Send plain text
|
||||||
|
res.text('Hello, World!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Routing Conventions
|
||||||
|
|
||||||
|
### Name Conversion
|
||||||
|
|
||||||
|
File names are automatically converted to route paths:
|
||||||
|
|
||||||
|
| File Path | Route Path (prefix=/api) |
|
||||||
|
|-----------|-------------------------|
|
||||||
|
| `login.ts` | `/api/login` |
|
||||||
|
| `users/profile.ts` | `/api/users/profile` |
|
||||||
|
| `users/[id].ts` | `/api/users/:id` |
|
||||||
|
| `game/room/[roomId].ts` | `/api/game/room/:roomId` |
|
||||||
|
|
||||||
|
### Dynamic Route Parameters
|
||||||
|
|
||||||
|
Use `[param]` syntax to define dynamic parameters:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/users/[id].ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// Get parameter from path
|
||||||
|
// Note: current version requires manual path parsing
|
||||||
|
const id = req.path.split('/').pop()
|
||||||
|
res.json({ userId: id })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skip Rules
|
||||||
|
|
||||||
|
The following files are automatically skipped:
|
||||||
|
|
||||||
|
- Files starting with `_` (e.g., `_helper.ts`)
|
||||||
|
- `index.ts` / `index.js` files
|
||||||
|
- Non `.ts` / `.js` / `.mts` / `.mjs` files
|
||||||
|
|
||||||
|
### Directory Structure Example
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
└── http/
|
||||||
|
├── _utils.ts # Skipped (underscore prefix)
|
||||||
|
├── index.ts # Skipped (index file)
|
||||||
|
├── health.ts # GET /api/health
|
||||||
|
├── login.ts # POST /api/login
|
||||||
|
├── register.ts # POST /api/register
|
||||||
|
└── users/
|
||||||
|
├── index.ts # Skipped
|
||||||
|
├── list.ts # GET /api/users/list
|
||||||
|
└── [id].ts # GET /api/users/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
## CORS Configuration
|
||||||
|
|
||||||
|
### Quick Enable
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
cors: true // Use default configuration
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Configuration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
cors: {
|
||||||
|
// Allowed origins
|
||||||
|
origin: ['http://localhost:5173', 'https://myapp.com'],
|
||||||
|
// Or use wildcard
|
||||||
|
// origin: '*',
|
||||||
|
// origin: true, // Reflect request origin
|
||||||
|
|
||||||
|
// Allowed HTTP methods
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||||
|
|
||||||
|
// Allowed request headers
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
||||||
|
|
||||||
|
// Allow credentials (cookies)
|
||||||
|
credentials: true,
|
||||||
|
|
||||||
|
// Preflight cache max age (seconds)
|
||||||
|
maxAge: 86400
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### CorsOptions Type
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface CorsOptions {
|
||||||
|
/** Allowed origins: string, string array, true (reflect) or '*' */
|
||||||
|
origin?: string | string[] | boolean
|
||||||
|
|
||||||
|
/** Allowed HTTP methods */
|
||||||
|
methods?: string[]
|
||||||
|
|
||||||
|
/** Allowed request headers */
|
||||||
|
allowedHeaders?: string[]
|
||||||
|
|
||||||
|
/** Allow credentials */
|
||||||
|
credentials?: boolean
|
||||||
|
|
||||||
|
/** Preflight cache max age (seconds) */
|
||||||
|
maxAge?: number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Route Merging
|
||||||
|
|
||||||
|
File routes and inline routes can be used together, with inline routes having higher priority:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
httpDir: './src/http',
|
||||||
|
httpPrefix: '/api',
|
||||||
|
|
||||||
|
// Inline routes merge with file routes
|
||||||
|
http: {
|
||||||
|
'/health': (req, res) => res.json({ status: 'ok' }),
|
||||||
|
'/api/special': (req, res) => res.json({ special: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sharing Port with WebSocket
|
||||||
|
|
||||||
|
HTTP routes automatically share the same port with WebSocket services:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
// WebSocket related config
|
||||||
|
apiDir: './src/api',
|
||||||
|
msgDir: './src/msg',
|
||||||
|
|
||||||
|
// HTTP related config
|
||||||
|
httpDir: './src/http',
|
||||||
|
httpPrefix: '/api',
|
||||||
|
cors: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await server.start()
|
||||||
|
|
||||||
|
// Same port 3000:
|
||||||
|
// - WebSocket: ws://localhost:3000
|
||||||
|
// - HTTP API: http://localhost:3000/api/*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Examples
|
||||||
|
|
||||||
|
### Game Server Login API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/auth/login.ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
import { createJwtAuthProvider } from '@esengine/server/auth'
|
||||||
|
|
||||||
|
interface LoginRequest {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoginResponse {
|
||||||
|
token: string
|
||||||
|
userId: string
|
||||||
|
expiresAt: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwtProvider = createJwtAuthProvider({
|
||||||
|
secret: process.env.JWT_SECRET!,
|
||||||
|
expiresIn: 3600
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineHttp<LoginRequest>({
|
||||||
|
method: 'POST',
|
||||||
|
async handler(req, res) {
|
||||||
|
const { username, password } = req.body as LoginRequest
|
||||||
|
|
||||||
|
// Validate user
|
||||||
|
const user = await db.users.findByUsername(username)
|
||||||
|
if (!user || !await verifyPassword(password, user.passwordHash)) {
|
||||||
|
res.error(401, 'Invalid username or password')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate JWT
|
||||||
|
const token = jwtProvider.sign({
|
||||||
|
sub: user.id,
|
||||||
|
name: user.username,
|
||||||
|
roles: user.roles
|
||||||
|
})
|
||||||
|
|
||||||
|
const response: LoginResponse = {
|
||||||
|
token,
|
||||||
|
userId: user.id,
|
||||||
|
expiresAt: Date.now() + 3600 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Game Data Query API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/game/leaderboard.ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
async handler(req, res) {
|
||||||
|
const limit = parseInt(req.query.limit ?? '10')
|
||||||
|
const offset = parseInt(req.query.offset ?? '0')
|
||||||
|
|
||||||
|
const players = await db.players.findMany({
|
||||||
|
sort: { score: 'desc' },
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
data: players,
|
||||||
|
pagination: { limit, offset }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use defineHttp** - Get better type hints and code organization
|
||||||
|
2. **Unified Error Handling** - Use `res.error()` to return consistent error format
|
||||||
|
3. **Enable CORS** - Required for frontend-backend separation
|
||||||
|
4. **Directory Organization** - Organize HTTP route files by functional modules
|
||||||
|
5. **Validate Input** - Always validate `req.body` and `req.query` content
|
||||||
|
6. **Status Code Standards** - Follow HTTP status code conventions (200, 201, 400, 401, 404, 500, etc.)
|
||||||
@@ -90,128 +90,21 @@ await server.start()
|
|||||||
|
|
||||||
Supports HTTP API sharing the same port with WebSocket, ideal for login, registration, and similar scenarios.
|
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
|
```typescript
|
||||||
// src/http/login.ts
|
const server = await createServer({
|
||||||
import { defineHttp } from '@esengine/server'
|
port: 3000,
|
||||||
|
httpDir: './src/http', // HTTP routes directory
|
||||||
|
httpPrefix: '/api', // Route prefix
|
||||||
|
cors: true,
|
||||||
|
|
||||||
interface LoginBody {
|
// Or inline definition
|
||||||
username: string
|
http: {
|
||||||
password: string
|
'/health': (req, res) => res.json({ status: 'ok' })
|
||||||
}
|
|
||||||
|
|
||||||
export default defineHttp<LoginBody>({
|
|
||||||
method: 'POST', // Default POST, options: GET/PUT/DELETE/PATCH
|
|
||||||
handler(req, res) {
|
|
||||||
const { username, password } = req.body
|
|
||||||
|
|
||||||
// Validate credentials...
|
|
||||||
if (!isValid(username, password)) {
|
|
||||||
res.error(401, 'Invalid credentials')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate token...
|
|
||||||
res.json({ token: '...', userId: '...' })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Request Object (HttpRequest)
|
> For detailed documentation, see [HTTP Routing](/en/modules/network/http)
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface HttpRequest {
|
|
||||||
raw: IncomingMessage // Node.js raw request
|
|
||||||
method: string // Request method
|
|
||||||
path: string // Request path
|
|
||||||
query: Record<string, string> // Query parameters
|
|
||||||
headers: Record<string, string | string[] | undefined>
|
|
||||||
body: unknown // Parsed JSON body
|
|
||||||
ip: string // Client IP
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Response Object (HttpResponse)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface HttpResponse {
|
|
||||||
raw: ServerResponse // Node.js raw response
|
|
||||||
status(code: number): HttpResponse // Set status code (chainable)
|
|
||||||
header(name: string, value: string): HttpResponse // Set header (chainable)
|
|
||||||
json(data: unknown): void // Send JSON
|
|
||||||
text(data: string): void // Send text
|
|
||||||
error(code: number, message: string): void // Send error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Usage Example
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Complete login server example
|
|
||||||
import { createServer, defineHttp } from '@esengine/server'
|
|
||||||
import { createJwtAuthProvider, withAuth } from '@esengine/server/auth'
|
|
||||||
|
|
||||||
const jwtProvider = createJwtAuthProvider({
|
|
||||||
secret: process.env.JWT_SECRET!,
|
|
||||||
expiresIn: 3600 * 24,
|
|
||||||
})
|
|
||||||
|
|
||||||
const server = await createServer({
|
|
||||||
port: 8080,
|
|
||||||
httpDir: 'src/http',
|
|
||||||
httpPrefix: '/api',
|
|
||||||
cors: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wrap with auth (WebSocket connections validate token)
|
|
||||||
const authServer = withAuth(server, {
|
|
||||||
provider: jwtProvider,
|
|
||||||
extractCredentials: (req) => {
|
|
||||||
const url = new URL(req.url, 'http://localhost')
|
|
||||||
return url.searchParams.get('token')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await authServer.start()
|
|
||||||
// HTTP: http://localhost:8080/api/*
|
|
||||||
// WebSocket: ws://localhost:8080?token=xxx
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inline Routes
|
|
||||||
|
|
||||||
Routes can also be defined directly in configuration (merged with file routes, inline takes priority):
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const server = await createServer({
|
|
||||||
port: 8080,
|
|
||||||
http: {
|
|
||||||
'/health': {
|
|
||||||
GET: (req, res) => res.json({ status: 'ok' }),
|
|
||||||
},
|
|
||||||
'/webhook': async (req, res) => {
|
|
||||||
// Accepts all methods
|
|
||||||
await handleWebhook(req.body)
|
|
||||||
res.json({ received: true })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Room System
|
## Room System
|
||||||
|
|
||||||
|
|||||||
490
docs/src/content/docs/modules/network/http.md
Normal file
490
docs/src/content/docs/modules/network/http.md
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
---
|
||||||
|
title: "HTTP 路由"
|
||||||
|
description: "HTTP REST API 路由功能,支持与 WebSocket 共用端口"
|
||||||
|
---
|
||||||
|
|
||||||
|
`@esengine/server` 内置了轻量级的 HTTP 路由功能,可以与 WebSocket 服务共用同一端口,方便实现 REST API。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 内联路由定义
|
||||||
|
|
||||||
|
最简单的方式是在创建服务器时直接定义 HTTP 路由:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createServer } from '@esengine/server'
|
||||||
|
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
http: {
|
||||||
|
'/api/health': (req, res) => {
|
||||||
|
res.json({ status: 'ok', time: Date.now() })
|
||||||
|
},
|
||||||
|
'/api/users': {
|
||||||
|
GET: (req, res) => {
|
||||||
|
res.json({ users: [] })
|
||||||
|
},
|
||||||
|
POST: async (req, res) => {
|
||||||
|
const body = req.body as { name: string }
|
||||||
|
res.status(201).json({ id: '1', name: body.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cors: true // 启用 CORS
|
||||||
|
})
|
||||||
|
|
||||||
|
await server.start()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件路由
|
||||||
|
|
||||||
|
对于较大的项目,推荐使用文件路由。创建 `src/http` 目录,每个文件对应一个路由:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/login.ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
interface LoginBody {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineHttp<LoginBody>({
|
||||||
|
method: 'POST',
|
||||||
|
handler(req, res) {
|
||||||
|
const { username, password } = req.body as LoginBody
|
||||||
|
|
||||||
|
// 验证用户...
|
||||||
|
if (username === 'admin' && password === '123456') {
|
||||||
|
res.json({ token: 'jwt-token-here', userId: 'user-1' })
|
||||||
|
} else {
|
||||||
|
res.error(401, '用户名或密码错误')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// server.ts
|
||||||
|
import { createServer } from '@esengine/server'
|
||||||
|
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
httpDir: './src/http', // HTTP 路由目录
|
||||||
|
httpPrefix: '/api', // 路由前缀
|
||||||
|
cors: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await server.start()
|
||||||
|
// 路由: POST /api/login
|
||||||
|
```
|
||||||
|
|
||||||
|
## defineHttp 定义
|
||||||
|
|
||||||
|
`defineHttp` 用于定义类型安全的 HTTP 处理器:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
interface CreateUserBody {
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineHttp<CreateUserBody>({
|
||||||
|
// HTTP 方法(默认 POST)
|
||||||
|
method: 'POST',
|
||||||
|
|
||||||
|
// 处理函数
|
||||||
|
handler(req, res) {
|
||||||
|
const body = req.body as CreateUserBody
|
||||||
|
// 处理请求...
|
||||||
|
res.status(201).json({ id: 'new-user-id' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 支持的 HTTP 方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS'
|
||||||
|
```
|
||||||
|
|
||||||
|
## HttpRequest 对象
|
||||||
|
|
||||||
|
HTTP 请求对象包含以下属性:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HttpRequest {
|
||||||
|
/** 原始 Node.js IncomingMessage */
|
||||||
|
raw: IncomingMessage
|
||||||
|
|
||||||
|
/** HTTP 方法 */
|
||||||
|
method: string
|
||||||
|
|
||||||
|
/** 请求路径 */
|
||||||
|
path: string
|
||||||
|
|
||||||
|
/** 查询参数 */
|
||||||
|
query: Record<string, string>
|
||||||
|
|
||||||
|
/** 请求头 */
|
||||||
|
headers: Record<string, string | string[] | undefined>
|
||||||
|
|
||||||
|
/** 解析后的请求体 */
|
||||||
|
body: unknown
|
||||||
|
|
||||||
|
/** 客户端 IP */
|
||||||
|
ip: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// 获取查询参数
|
||||||
|
const page = parseInt(req.query.page ?? '1')
|
||||||
|
const limit = parseInt(req.query.limit ?? '10')
|
||||||
|
|
||||||
|
// 获取请求头
|
||||||
|
const authHeader = req.headers.authorization
|
||||||
|
|
||||||
|
// 获取客户端 IP
|
||||||
|
console.log('Request from:', req.ip)
|
||||||
|
|
||||||
|
res.json({ page, limit })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 请求体解析
|
||||||
|
|
||||||
|
请求体会根据 `Content-Type` 自动解析:
|
||||||
|
|
||||||
|
- `application/json` - 解析为 JSON 对象
|
||||||
|
- `application/x-www-form-urlencoded` - 解析为键值对对象
|
||||||
|
- 其他 - 保持原始字符串
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp<{ name: string; age: number }>({
|
||||||
|
method: 'POST',
|
||||||
|
handler(req, res) {
|
||||||
|
// body 已自动解析
|
||||||
|
const { name, age } = req.body as { name: string; age: number }
|
||||||
|
res.json({ received: { name, age } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## HttpResponse 对象
|
||||||
|
|
||||||
|
HTTP 响应对象提供链式 API:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HttpResponse {
|
||||||
|
/** 原始 Node.js ServerResponse */
|
||||||
|
raw: ServerResponse
|
||||||
|
|
||||||
|
/** 设置状态码 */
|
||||||
|
status(code: number): HttpResponse
|
||||||
|
|
||||||
|
/** 设置响应头 */
|
||||||
|
header(name: string, value: string): HttpResponse
|
||||||
|
|
||||||
|
/** 发送 JSON 响应 */
|
||||||
|
json(data: unknown): void
|
||||||
|
|
||||||
|
/** 发送文本响应 */
|
||||||
|
text(data: string): void
|
||||||
|
|
||||||
|
/** 发送错误响应 */
|
||||||
|
error(code: number, message: string): void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用示例
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'POST',
|
||||||
|
handler(req, res) {
|
||||||
|
// 设置状态码和自定义头
|
||||||
|
res
|
||||||
|
.status(201)
|
||||||
|
.header('X-Custom-Header', 'value')
|
||||||
|
.json({ created: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// 发送错误响应
|
||||||
|
res.error(404, '资源不存在')
|
||||||
|
// 等价于: res.status(404).json({ error: '资源不存在' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// 发送纯文本
|
||||||
|
res.text('Hello, World!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件路由规范
|
||||||
|
|
||||||
|
### 命名转换
|
||||||
|
|
||||||
|
文件名会自动转换为路由路径:
|
||||||
|
|
||||||
|
| 文件路径 | 路由路径(prefix=/api) |
|
||||||
|
|---------|----------------------|
|
||||||
|
| `login.ts` | `/api/login` |
|
||||||
|
| `users/profile.ts` | `/api/users/profile` |
|
||||||
|
| `users/[id].ts` | `/api/users/:id` |
|
||||||
|
| `game/room/[roomId].ts` | `/api/game/room/:roomId` |
|
||||||
|
|
||||||
|
### 动态路由参数
|
||||||
|
|
||||||
|
使用 `[param]` 语法定义动态参数:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/users/[id].ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
handler(req, res) {
|
||||||
|
// 从路径获取参数
|
||||||
|
// 注意:当前版本需要手动解析 path
|
||||||
|
const id = req.path.split('/').pop()
|
||||||
|
res.json({ userId: id })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 跳过规则
|
||||||
|
|
||||||
|
以下文件会被自动跳过:
|
||||||
|
|
||||||
|
- 以 `_` 开头的文件(如 `_helper.ts`)
|
||||||
|
- `index.ts` / `index.js` 文件
|
||||||
|
- 非 `.ts` / `.js` / `.mts` / `.mjs` 文件
|
||||||
|
|
||||||
|
### 目录结构示例
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
└── http/
|
||||||
|
├── _utils.ts # 跳过(下划线开头)
|
||||||
|
├── index.ts # 跳过(index 文件)
|
||||||
|
├── health.ts # GET /api/health
|
||||||
|
├── login.ts # POST /api/login
|
||||||
|
├── register.ts # POST /api/register
|
||||||
|
└── users/
|
||||||
|
├── index.ts # 跳过
|
||||||
|
├── list.ts # GET /api/users/list
|
||||||
|
└── [id].ts # GET /api/users/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
## CORS 配置
|
||||||
|
|
||||||
|
### 快速启用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
cors: true // 使用默认配置
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义配置
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
cors: {
|
||||||
|
// 允许的来源
|
||||||
|
origin: ['http://localhost:5173', 'https://myapp.com'],
|
||||||
|
// 或使用通配符
|
||||||
|
// origin: '*',
|
||||||
|
// origin: true, // 反射请求来源
|
||||||
|
|
||||||
|
// 允许的 HTTP 方法
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||||
|
|
||||||
|
// 允许的请求头
|
||||||
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
||||||
|
|
||||||
|
// 是否允许携带凭证(cookies)
|
||||||
|
credentials: true,
|
||||||
|
|
||||||
|
// 预检请求缓存时间(秒)
|
||||||
|
maxAge: 86400
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### CorsOptions 类型
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface CorsOptions {
|
||||||
|
/** 允许的来源:字符串、字符串数组、true(反射)或 '*' */
|
||||||
|
origin?: string | string[] | boolean
|
||||||
|
|
||||||
|
/** 允许的 HTTP 方法 */
|
||||||
|
methods?: string[]
|
||||||
|
|
||||||
|
/** 允许的请求头 */
|
||||||
|
allowedHeaders?: string[]
|
||||||
|
|
||||||
|
/** 是否允许携带凭证 */
|
||||||
|
credentials?: boolean
|
||||||
|
|
||||||
|
/** 预检请求缓存时间(秒) */
|
||||||
|
maxAge?: number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 路由合并
|
||||||
|
|
||||||
|
文件路由和内联路由可以同时使用,内联路由优先级更高:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
httpDir: './src/http',
|
||||||
|
httpPrefix: '/api',
|
||||||
|
|
||||||
|
// 内联路由会与文件路由合并
|
||||||
|
http: {
|
||||||
|
'/health': (req, res) => res.json({ status: 'ok' }),
|
||||||
|
'/api/special': (req, res) => res.json({ special: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与 WebSocket 共用端口
|
||||||
|
|
||||||
|
HTTP 路由与 WebSocket 服务自动共用同一端口:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
|
port: 3000,
|
||||||
|
// WebSocket 相关配置
|
||||||
|
apiDir: './src/api',
|
||||||
|
msgDir: './src/msg',
|
||||||
|
|
||||||
|
// HTTP 相关配置
|
||||||
|
httpDir: './src/http',
|
||||||
|
httpPrefix: '/api',
|
||||||
|
cors: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await server.start()
|
||||||
|
|
||||||
|
// 同一端口 3000:
|
||||||
|
// - WebSocket: ws://localhost:3000
|
||||||
|
// - HTTP API: http://localhost:3000/api/*
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
### 游戏服务器登录 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/auth/login.ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
import { createJwtAuthProvider } from '@esengine/server/auth'
|
||||||
|
|
||||||
|
interface LoginRequest {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoginResponse {
|
||||||
|
token: string
|
||||||
|
userId: string
|
||||||
|
expiresAt: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwtProvider = createJwtAuthProvider({
|
||||||
|
secret: process.env.JWT_SECRET!,
|
||||||
|
expiresIn: 3600
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineHttp<LoginRequest>({
|
||||||
|
method: 'POST',
|
||||||
|
async handler(req, res) {
|
||||||
|
const { username, password } = req.body as LoginRequest
|
||||||
|
|
||||||
|
// 验证用户
|
||||||
|
const user = await db.users.findByUsername(username)
|
||||||
|
if (!user || !await verifyPassword(password, user.passwordHash)) {
|
||||||
|
res.error(401, '用户名或密码错误')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 JWT
|
||||||
|
const token = jwtProvider.sign({
|
||||||
|
sub: user.id,
|
||||||
|
name: user.username,
|
||||||
|
roles: user.roles
|
||||||
|
})
|
||||||
|
|
||||||
|
const response: LoginResponse = {
|
||||||
|
token,
|
||||||
|
userId: user.id,
|
||||||
|
expiresAt: Date.now() + 3600 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 游戏数据查询 API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/http/game/leaderboard.ts
|
||||||
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
|
export default defineHttp({
|
||||||
|
method: 'GET',
|
||||||
|
async handler(req, res) {
|
||||||
|
const limit = parseInt(req.query.limit ?? '10')
|
||||||
|
const offset = parseInt(req.query.offset ?? '0')
|
||||||
|
|
||||||
|
const players = await db.players.findMany({
|
||||||
|
sort: { score: 'desc' },
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
data: players,
|
||||||
|
pagination: { limit, offset }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **使用 defineHttp** - 获得更好的类型提示和代码组织
|
||||||
|
2. **统一错误处理** - 使用 `res.error()` 返回一致的错误格式
|
||||||
|
3. **启用 CORS** - 前后端分离时必须配置
|
||||||
|
4. **目录组织** - 按功能模块组织 HTTP 路由文件
|
||||||
|
5. **验证输入** - 始终验证 `req.body` 和 `req.query` 的内容
|
||||||
|
6. **状态码规范** - 遵循 HTTP 状态码规范(200、201、400、401、404、500 等)
|
||||||
@@ -90,128 +90,35 @@ await server.start()
|
|||||||
|
|
||||||
支持 HTTP API 与 WebSocket 共用端口,适用于登录、注册等场景。
|
支持 HTTP API 与 WebSocket 共用端口,适用于登录、注册等场景。
|
||||||
|
|
||||||
### 文件路由
|
```typescript
|
||||||
|
const server = await createServer({
|
||||||
在 `httpDir` 目录下创建路由文件,自动映射为 HTTP 端点:
|
port: 3000,
|
||||||
|
httpDir: './src/http', // HTTP 路由目录
|
||||||
|
httpPrefix: '/api', // 路由前缀
|
||||||
|
cors: true,
|
||||||
|
|
||||||
|
// 或内联定义
|
||||||
|
http: {
|
||||||
|
'/health': (req, res) => res.json({ status: 'ok' })
|
||||||
|
}
|
||||||
|
})
|
||||||
```
|
```
|
||||||
src/http/
|
|
||||||
├── login.ts → POST /api/login
|
|
||||||
├── register.ts → POST /api/register
|
|
||||||
├── health.ts → GET /api/health (需设置 method: 'GET')
|
|
||||||
└── users/
|
|
||||||
└── [id].ts → POST /api/users/:id (动态路由)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 定义路由
|
|
||||||
|
|
||||||
使用 `defineHttp` 定义类型安全的路由处理器:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/http/login.ts
|
// src/http/login.ts
|
||||||
import { defineHttp } from '@esengine/server'
|
import { defineHttp } from '@esengine/server'
|
||||||
|
|
||||||
interface LoginBody {
|
export default defineHttp<{ username: string; password: string }>({
|
||||||
username: string
|
method: 'POST',
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineHttp<LoginBody>({
|
|
||||||
method: 'POST', // 默认 POST,可选 GET/PUT/DELETE/PATCH
|
|
||||||
handler(req, res) {
|
handler(req, res) {
|
||||||
const { username, password } = req.body
|
const { username, password } = req.body
|
||||||
|
// 验证并返回 token...
|
||||||
// 验证凭证...
|
res.json({ token: '...' })
|
||||||
if (!isValid(username, password)) {
|
|
||||||
res.error(401, 'Invalid credentials')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 token...
|
|
||||||
res.json({ token: '...', userId: '...' })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### 请求对象 (HttpRequest)
|
> 详细文档请参考 [HTTP 路由](/modules/network/http)
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface HttpRequest {
|
|
||||||
raw: IncomingMessage // Node.js 原始请求
|
|
||||||
method: string // 请求方法
|
|
||||||
path: string // 请求路径
|
|
||||||
query: Record<string, string> // 查询参数
|
|
||||||
headers: Record<string, string | string[] | undefined>
|
|
||||||
body: unknown // 解析后的 JSON 请求体
|
|
||||||
ip: string // 客户端 IP
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 响应对象 (HttpResponse)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface HttpResponse {
|
|
||||||
raw: ServerResponse // Node.js 原始响应
|
|
||||||
status(code: number): HttpResponse // 设置状态码(链式)
|
|
||||||
header(name: string, value: string): HttpResponse // 设置头(链式)
|
|
||||||
json(data: unknown): void // 发送 JSON
|
|
||||||
text(data: string): void // 发送文本
|
|
||||||
error(code: number, message: string): void // 发送错误
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 使用示例
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 完整的登录服务器示例
|
|
||||||
import { createServer, defineHttp } from '@esengine/server'
|
|
||||||
import { createJwtAuthProvider, withAuth } from '@esengine/server/auth'
|
|
||||||
|
|
||||||
const jwtProvider = createJwtAuthProvider({
|
|
||||||
secret: process.env.JWT_SECRET!,
|
|
||||||
expiresIn: 3600 * 24,
|
|
||||||
})
|
|
||||||
|
|
||||||
const server = await createServer({
|
|
||||||
port: 8080,
|
|
||||||
httpDir: 'src/http',
|
|
||||||
httpPrefix: '/api',
|
|
||||||
cors: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 包装认证(WebSocket 连接验证 token)
|
|
||||||
const authServer = withAuth(server, {
|
|
||||||
provider: jwtProvider,
|
|
||||||
extractCredentials: (req) => {
|
|
||||||
const url = new URL(req.url, 'http://localhost')
|
|
||||||
return url.searchParams.get('token')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await authServer.start()
|
|
||||||
// HTTP: http://localhost:8080/api/*
|
|
||||||
// WebSocket: ws://localhost:8080?token=xxx
|
|
||||||
```
|
|
||||||
|
|
||||||
### 内联路由
|
|
||||||
|
|
||||||
也可以直接在配置中定义路由(与文件路由合并,内联优先):
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const server = await createServer({
|
|
||||||
port: 8080,
|
|
||||||
http: {
|
|
||||||
'/health': {
|
|
||||||
GET: (req, res) => res.json({ status: 'ok' }),
|
|
||||||
},
|
|
||||||
'/webhook': async (req, res) => {
|
|
||||||
// 接受所有方法
|
|
||||||
await handleWebhook(req.body)
|
|
||||||
res.json({ received: true })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Room 系统
|
## Room 系统
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user