feat: add 7 built-in middleware components inspired by Hono.js
New Middleware: - CORS: Cross-Origin Resource Sharing with full configuration support - Cookie Helper: Cookie management with signed cookie support (HMAC-SHA256) - JWT: JSON Web Token authentication (HS256/384/512) - Bearer Auth: Simple Bearer token authentication - Compression: Gzip and deflate response compression - Rate Limiting: Request rate limiting with customizable stores - Validator: Schema-based request validation Features: - Context store for middleware data sharing (get/set/get_client_ip) - Middleware export module with utility functions - Comprehensive property-based tests for all middleware - Example file demonstrating all middleware usage Files Added: - cors.v, cookie.v, jwt.v, bearer_auth.v, compress.v, rate_limit.v, validator.v - middleware.v (unified exports) - examples/middleware_demo.v - tests/test_*.v (9 new test files) Documentation: - Updated README.md with detailed middleware usage guide - Added Schema Builder API documentation
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
// middleware_demo.v - 中间件使用示例
|
||||
// 本示例展示了 v-hono 框架所有内置中间件的基本用法
|
||||
module main
|
||||
|
||||
import hono
|
||||
import net.http
|
||||
import time
|
||||
|
||||
fn main() {
|
||||
mut app := hono.Hono.new()
|
||||
|
||||
// ============================================================================
|
||||
// 1. CORS 中间件示例
|
||||
// ============================================================================
|
||||
// 基本用法:允许所有来源
|
||||
app.use(hono.cors())
|
||||
|
||||
// 高级配置示例(注释掉以避免重复)
|
||||
// app.use(hono.cors(hono.CorsOptions{
|
||||
// origin: 'https://example.com' // 只允许特定域名
|
||||
// credentials: true // 允许携带凭证
|
||||
// max_age: 600 // 预检请求缓存 10 分钟
|
||||
// allow_methods: ['GET', 'POST', 'PUT', 'DELETE']
|
||||
// allow_headers: ['Content-Type', 'Authorization']
|
||||
// }))
|
||||
|
||||
// ============================================================================
|
||||
// 2. 压缩中间件示例
|
||||
// ============================================================================
|
||||
// 使用 gzip 压缩(默认)
|
||||
app.use(hono.gzip())
|
||||
|
||||
// 或者使用 deflate 压缩
|
||||
// app.use(hono.deflate_compress())
|
||||
|
||||
// 自定义压缩配置
|
||||
// app.use(hono.compress(hono.CompressOptions{
|
||||
// encoding: .gzip
|
||||
// threshold: 2048 // 只压缩大于 2KB 的响应
|
||||
// level: 9 // 最高压缩级别
|
||||
// }))
|
||||
|
||||
// ============================================================================
|
||||
// 3. 安全响应头中间件示例
|
||||
// ============================================================================
|
||||
app.use(hono.secure_headers())
|
||||
|
||||
// ============================================================================
|
||||
// 4. 请求 ID 中间件示例
|
||||
// ============================================================================
|
||||
app.use(hono.request_id())
|
||||
|
||||
// ============================================================================
|
||||
// 5. 请求计时中间件示例
|
||||
// ============================================================================
|
||||
app.use(hono.timing())
|
||||
|
||||
// ============================================================================
|
||||
// 6. 限流中间件示例
|
||||
// ============================================================================
|
||||
// 创建内存存储
|
||||
store := hono.MemoryStore.new()
|
||||
|
||||
// 应用限流中间件:每分钟最多 100 个请求
|
||||
app.use(hono.rate_limiter(hono.RateLimitOptions{
|
||||
store: store
|
||||
window_ms: 60000 // 1 分钟
|
||||
limit: 100 // 最多 100 个请求
|
||||
headers: true // 添加限流响应头
|
||||
}))
|
||||
|
||||
// ============================================================================
|
||||
// 路由示例
|
||||
// ============================================================================
|
||||
|
||||
// 基本路由
|
||||
app.get('/', fn (mut c hono.Context) http.Response {
|
||||
return c.json('{"message": "Welcome to v-hono middleware demo!"}')
|
||||
})
|
||||
|
||||
// 获取请求信息
|
||||
app.get('/info', fn (mut c hono.Context) http.Response {
|
||||
// 获取请求 ID
|
||||
request_id := c.get('request_id') or { 'unknown' }
|
||||
// 获取客户端 IP
|
||||
client_ip := c.get_client_ip()
|
||||
|
||||
return c.json('{"request_id": "${request_id}", "client_ip": "${client_ip}"}')
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// 7. Cookie Helper 示例
|
||||
// ============================================================================
|
||||
|
||||
// 设置 Cookie
|
||||
app.get('/cookie/set', fn (mut c hono.Context) http.Response {
|
||||
// 设置普通 Cookie
|
||||
hono.set_cookie(mut c, 'session_id', 'abc123', hono.CookieOptions{
|
||||
http_only: true
|
||||
secure: false // 开发环境设为 false
|
||||
max_age: 3600 // 1 小时
|
||||
path: '/'
|
||||
})
|
||||
|
||||
return c.json('{"message": "Cookie set successfully"}')
|
||||
})
|
||||
|
||||
// 获取 Cookie
|
||||
app.get('/cookie/get', fn (mut c hono.Context) http.Response {
|
||||
if session_id := hono.get_cookie(c, 'session_id') {
|
||||
return c.json('{"session_id": "${session_id}"}')
|
||||
}
|
||||
return c.json('{"error": "Cookie not found"}')
|
||||
})
|
||||
|
||||
// 获取所有 Cookie
|
||||
app.get('/cookie/all', fn (mut c hono.Context) http.Response {
|
||||
cookies := hono.get_all_cookies(c)
|
||||
mut parts := []string{}
|
||||
for name, value in cookies {
|
||||
parts << '"${name}": "${value}"'
|
||||
}
|
||||
return c.json('{${parts.join(", ")}}')
|
||||
})
|
||||
|
||||
// 删除 Cookie
|
||||
app.get('/cookie/delete', fn (mut c hono.Context) http.Response {
|
||||
hono.delete_cookie(mut c, 'session_id')
|
||||
return c.json('{"message": "Cookie deleted"}')
|
||||
})
|
||||
|
||||
// 签名 Cookie 示例
|
||||
app.get('/cookie/signed/set', fn (mut c hono.Context) http.Response {
|
||||
secret := 'my-secret-key-for-signing'
|
||||
hono.set_signed_cookie(mut c, 'user_data', 'user123', secret) or {
|
||||
c.status(500)
|
||||
return c.json('{"error": "Failed to set signed cookie"}')
|
||||
}
|
||||
return c.json('{"message": "Signed cookie set successfully"}')
|
||||
})
|
||||
|
||||
app.get('/cookie/signed/get', fn (mut c hono.Context) http.Response {
|
||||
secret := 'my-secret-key-for-signing'
|
||||
user_data := hono.get_signed_cookie(c, 'user_data', secret) or {
|
||||
c.status(400)
|
||||
return c.json('{"error": "Invalid or missing signed cookie"}')
|
||||
}
|
||||
return c.json('{"user_data": "${user_data}"}')
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// 8. JWT 中间件示例
|
||||
// ============================================================================
|
||||
|
||||
// 生成 JWT Token
|
||||
app.post('/auth/login', fn (mut c hono.Context) http.Response {
|
||||
// 在实际应用中,这里应该验证用户凭证
|
||||
secret := 'my-jwt-secret-key'
|
||||
|
||||
// 创建 JWT payload
|
||||
payload := hono.JwtPayload{
|
||||
sub: 'user123'
|
||||
iss: 'v-hono-demo'
|
||||
exp: time.now().unix() + 3600 // 1 小时后过期
|
||||
iat: time.now().unix()
|
||||
claims: {
|
||||
'role': 'admin'
|
||||
'name': 'John Doe'
|
||||
}
|
||||
}
|
||||
|
||||
// 签名 JWT
|
||||
token := hono.sign_jwt(payload, secret, .hs256) or {
|
||||
c.status(500)
|
||||
return c.json('{"error": "Failed to generate token"}')
|
||||
}
|
||||
|
||||
return c.json('{"token": "${token}"}')
|
||||
})
|
||||
|
||||
// 验证 JWT Token(手动验证示例)
|
||||
app.get('/auth/verify', fn (mut c hono.Context) http.Response {
|
||||
secret := 'my-jwt-secret-key'
|
||||
|
||||
// 从 Authorization 头获取 token
|
||||
auth_header := c.req.header.get_custom('Authorization') or {
|
||||
c.status(401)
|
||||
return c.json('{"error": "Missing Authorization header"}')
|
||||
}
|
||||
|
||||
if !auth_header.starts_with('Bearer ') {
|
||||
c.status(401)
|
||||
return c.json('{"error": "Invalid Authorization format"}')
|
||||
}
|
||||
|
||||
token := auth_header[7..]
|
||||
|
||||
// 验证 token
|
||||
payload := hono.verify_jwt(token, secret, .hs256) or {
|
||||
c.status(401)
|
||||
return c.json('{"error": "Invalid token: ${err}"}')
|
||||
}
|
||||
|
||||
return c.json('{"sub": "${payload.sub}", "iss": "${payload.iss}"}')
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// 9. Bearer Auth 中间件示例(保护特定路由)
|
||||
// ============================================================================
|
||||
|
||||
// 创建受保护的子应用
|
||||
mut protected_app := hono.Hono.new()
|
||||
|
||||
// 应用 Bearer Auth 中间件
|
||||
protected_app.use(hono.bearer(hono.BearerAuthOptions{
|
||||
token: 'my-api-token' // 简单的静态 token
|
||||
realm: 'Protected API'
|
||||
}))
|
||||
|
||||
protected_app.get('/data', fn (mut c hono.Context) http.Response {
|
||||
// 获取已验证的 token
|
||||
token := hono.get_bearer_token(c) or { 'unknown' }
|
||||
return c.json('{"message": "Protected data", "token": "${token}"}')
|
||||
})
|
||||
|
||||
// 挂载受保护的路由
|
||||
app.route('/api', mut protected_app)
|
||||
|
||||
// ============================================================================
|
||||
// 10. 请求验证示例
|
||||
// ============================================================================
|
||||
|
||||
// JSON body 验证
|
||||
app.post('/users',
|
||||
hono.validate_json(hono.v_object({
|
||||
'name': hono.v_string().required().min(2).max(50)
|
||||
'email': hono.v_string().required().pattern(r'^[\w\.-]+@[\w\.-]+\.\w+$')
|
||||
'age': hono.v_int().min(0).max(150)
|
||||
})),
|
||||
fn (mut c hono.Context) http.Response {
|
||||
// 获取验证后的数据
|
||||
data := hono.get_validated_data(c)
|
||||
name := data['name'] or { '' }
|
||||
email := data['email'] or { '' }
|
||||
|
||||
return c.json('{"message": "User created", "name": "${name}", "email": "${email}"}')
|
||||
}
|
||||
)
|
||||
|
||||
// Query 参数验证
|
||||
app.get('/search',
|
||||
hono.validate_query(hono.v_object({
|
||||
'q': hono.v_string().required().min(1)
|
||||
'page': hono.v_int().min(1)
|
||||
'size': hono.v_int().min(1).max(100)
|
||||
})),
|
||||
fn (mut c hono.Context) http.Response {
|
||||
q := hono.get_validated_field(c, 'q') or { '' }
|
||||
page := hono.get_validated_field(c, 'page') or { '1' }
|
||||
|
||||
return c.json('{"query": "${q}", "page": ${page}}')
|
||||
}
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 11. 中间件组合示例
|
||||
// ============================================================================
|
||||
|
||||
// 组合多个中间件
|
||||
combined := hono.combine_middlewares([
|
||||
hono.cors_middleware(),
|
||||
hono.secure_headers(),
|
||||
hono.timing(),
|
||||
])
|
||||
|
||||
// 创建使用组合中间件的子应用
|
||||
mut combined_app := hono.Hono.new()
|
||||
combined_app.use(combined)
|
||||
|
||||
combined_app.get('/test', fn (mut c hono.Context) http.Response {
|
||||
duration := c.get('request_duration_ms') or { '0' }
|
||||
return c.json('{"message": "Combined middleware test", "duration_ms": "${duration}"}')
|
||||
})
|
||||
|
||||
app.route('/combined', mut combined_app)
|
||||
|
||||
// ============================================================================
|
||||
// 启动服务器
|
||||
// ============================================================================
|
||||
|
||||
println('=== v-hono Middleware Demo ===')
|
||||
println('')
|
||||
println('Available endpoints:')
|
||||
println(' GET / - Welcome message')
|
||||
println(' GET /info - Request info (ID, IP)')
|
||||
println('')
|
||||
println('Cookie endpoints:')
|
||||
println(' GET /cookie/set - Set a cookie')
|
||||
println(' GET /cookie/get - Get a cookie')
|
||||
println(' GET /cookie/all - Get all cookies')
|
||||
println(' GET /cookie/delete - Delete a cookie')
|
||||
println(' GET /cookie/signed/set - Set a signed cookie')
|
||||
println(' GET /cookie/signed/get - Get a signed cookie')
|
||||
println('')
|
||||
println('Auth endpoints:')
|
||||
println(' POST /auth/login - Get JWT token')
|
||||
println(' GET /auth/verify - Verify JWT token')
|
||||
println(' GET /api/data - Protected endpoint (Bearer token: my-api-token)')
|
||||
println('')
|
||||
println('Validation endpoints:')
|
||||
println(' POST /users - Create user (JSON body validation)')
|
||||
println(' GET /search?q=xxx - Search (query validation)')
|
||||
println('')
|
||||
println('Combined middleware:')
|
||||
println(' GET /combined/test - Test combined middlewares')
|
||||
println('')
|
||||
println('Starting server on http://localhost:3000')
|
||||
println('')
|
||||
|
||||
app.listen(':3000')
|
||||
}
|
||||
Reference in New Issue
Block a user