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,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: ''
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user