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:
meiseayoung
2026-01-04 11:11:15 +08:00
parent 44ba82c194
commit 4e2c6a90c3
23 changed files with 7156 additions and 89 deletions
+135
View File
@@ -0,0 +1,135 @@
module hono
import net.http
// CorsOrigin 类型 - 支持多种来源配置方式
// 可以是字符串(单域名或 "*")、字符串数组(多域名)或回调函数
pub type CorsOrigin = string | []string | fn (string, Context) string
// CorsOptions 结构体 - CORS 配置选项
pub struct CorsOptions {
pub:
origin CorsOrigin = '*' // 允许的来源,默认允许所有
allow_methods []string = ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']
allow_headers []string = [] // 允许的请求头
expose_headers []string = [] // 暴露的响应头
max_age int // 预检请求缓存时间(秒),默认 0
credentials bool // 是否允许凭证,默认 false
}
// cors - CORS 中间件工厂函数
// 返回一个 ContextMiddleware,用于处理跨域请求
pub fn cors(options ...CorsOptions) ContextMiddleware {
opts := if options.len > 0 { options[0] } else { CorsOptions{} }
return fn [opts] (mut c Context, next fn (mut Context) http.Response) http.Response {
// 获取请求的 Origin 头
origin := c.req.header.get_custom('Origin') or { '' }
// 如果没有 Origin 头,直接继续处理
if origin.len == 0 {
return next(mut c)
}
// 计算允许的 Origin
allowed_origin := get_allowed_origin(origin, opts.origin, c)
// 如果 Origin 不被允许,直接继续处理(不设置 CORS 头)
if allowed_origin.len == 0 {
return next(mut c)
}
// 设置 Access-Control-Allow-Origin
c.headers['Access-Control-Allow-Origin'] = allowed_origin
// 设置 Access-Control-Allow-Credentials
if opts.credentials {
c.headers['Access-Control-Allow-Credentials'] = 'true'
}
// 设置 Access-Control-Expose-Headers
if opts.expose_headers.len > 0 {
c.headers['Access-Control-Expose-Headers'] = opts.expose_headers.join(', ')
}
// 检查是否为预检请求 (OPTIONS)
if c.req.method == http.Method.options {
return handle_preflight(mut c, opts)
}
// 非预检请求,继续处理
return next(mut c)
}
}
// get_allowed_origin - 根据配置计算允许的 Origin
fn get_allowed_origin(request_origin string, origin_config CorsOrigin, c Context) string {
match origin_config {
string {
// 单个字符串配置
if origin_config == '*' {
// 通配符,允许所有来源
return '*'
}
// 特定域名,检查是否匹配
if origin_config == request_origin {
return request_origin
}
return ''
}
[]string {
// 多域名配置,检查请求来源是否在列表中
for allowed in origin_config {
if allowed == '*' {
return '*'
}
if allowed == request_origin {
return request_origin
}
}
return ''
}
fn (string, Context) string {
// 回调函数配置
return origin_config(request_origin, c)
}
}
}
// handle_preflight - 处理 OPTIONS 预检请求
fn handle_preflight(mut c Context, opts CorsOptions) http.Response {
// 设置 Access-Control-Allow-Methods
if opts.allow_methods.len > 0 {
c.headers['Access-Control-Allow-Methods'] = opts.allow_methods.join(', ')
}
// 设置 Access-Control-Allow-Headers
if opts.allow_headers.len > 0 {
c.headers['Access-Control-Allow-Headers'] = opts.allow_headers.join(', ')
} else {
// 如果没有配置,尝试使用请求中的 Access-Control-Request-Headers
if request_headers := c.req.header.get_custom('Access-Control-Request-Headers') {
c.headers['Access-Control-Allow-Headers'] = request_headers
}
}
// 设置 Access-Control-Max-Age
if opts.max_age > 0 {
c.headers['Access-Control-Max-Age'] = opts.max_age.str()
}
// 返回 204 No Content
c.status(204)
mut headers := http.new_header()
headers.add_custom('Connection', 'keep-alive') or {}
for key, value in c.headers {
headers.add_custom(key, value) or { continue }
}
return http.Response{
status_code: c.status_code
header: headers
body: ''
}
}