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:
+225
@@ -0,0 +1,225 @@
|
||||
module hono
|
||||
|
||||
import net.http
|
||||
import time
|
||||
import sync
|
||||
|
||||
// RateLimitEntry 结构体 - 限流条目
|
||||
pub struct RateLimitEntry {
|
||||
pub mut:
|
||||
count int // 当前请求计数
|
||||
reset_at i64 // 重置时间(Unix 毫秒时间戳)
|
||||
}
|
||||
|
||||
// RateLimitStore 接口 - 限流存储后端
|
||||
pub interface RateLimitStore {
|
||||
mut:
|
||||
// increment - 增加计数并返回当前计数和重置时间
|
||||
// 参数: key - 客户端标识, window_ms - 时间窗口(毫秒)
|
||||
// 返回: (当前计数, 重置时间戳)
|
||||
increment(key string, window_ms i64) (int, i64)
|
||||
// reset - 重置指定 key 的计数
|
||||
reset(key string)
|
||||
}
|
||||
|
||||
// MemoryStore 结构体 - 内存存储实现
|
||||
pub struct MemoryStore {
|
||||
mut:
|
||||
data map[string]RateLimitEntry
|
||||
mtx sync.Mutex // 线程安全锁
|
||||
}
|
||||
|
||||
// MemoryStore.new - 创建新的内存存储
|
||||
pub fn MemoryStore.new() &MemoryStore {
|
||||
return &MemoryStore{
|
||||
data: map[string]RateLimitEntry{}
|
||||
}
|
||||
}
|
||||
|
||||
// increment - 增加计数并返回当前计数和重置时间
|
||||
pub fn (mut s MemoryStore) increment(key string, window_ms i64) (int, i64) {
|
||||
s.mtx.@lock()
|
||||
defer { s.mtx.unlock() }
|
||||
|
||||
now := time.now().unix_milli()
|
||||
|
||||
// 检查是否存在条目
|
||||
if key in s.data {
|
||||
mut entry := s.data[key]
|
||||
|
||||
// 检查是否需要重置(窗口已过期)
|
||||
if now >= entry.reset_at {
|
||||
// 窗口已过期,重置计数
|
||||
entry.count = 1
|
||||
entry.reset_at = now + window_ms
|
||||
} else {
|
||||
// 窗口内,增加计数
|
||||
entry.count++
|
||||
}
|
||||
|
||||
s.data[key] = entry
|
||||
return entry.count, entry.reset_at
|
||||
}
|
||||
|
||||
// 新条目
|
||||
new_entry := RateLimitEntry{
|
||||
count: 1
|
||||
reset_at: now + window_ms
|
||||
}
|
||||
s.data[key] = new_entry
|
||||
|
||||
return new_entry.count, new_entry.reset_at
|
||||
}
|
||||
|
||||
// reset - 重置指定 key 的计数
|
||||
pub fn (mut s MemoryStore) reset(key string) {
|
||||
s.mtx.@lock()
|
||||
defer { s.mtx.unlock() }
|
||||
|
||||
s.data.delete(key)
|
||||
}
|
||||
|
||||
// get_entry - 获取指定 key 的条目(用于测试)
|
||||
pub fn (s MemoryStore) get_entry(key string) ?RateLimitEntry {
|
||||
if key in s.data {
|
||||
return s.data[key]
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// cleanup_expired - 清理过期条目(可选的维护方法)
|
||||
pub fn (mut s MemoryStore) cleanup_expired() {
|
||||
s.mtx.@lock()
|
||||
defer { s.mtx.unlock() }
|
||||
|
||||
now := time.now().unix_milli()
|
||||
mut keys_to_delete := []string{}
|
||||
|
||||
for key, entry in s.data {
|
||||
if now >= entry.reset_at {
|
||||
keys_to_delete << key
|
||||
}
|
||||
}
|
||||
|
||||
for key in keys_to_delete {
|
||||
s.data.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// RateLimitOptions 结构体 - 限流配置选项
|
||||
pub struct RateLimitOptions {
|
||||
pub:
|
||||
window_ms i64 = 60000 // 时间窗口(毫秒),默认 1 分钟
|
||||
limit int = 100 // 窗口内最大请求数,默认 100
|
||||
key_generator ?fn (Context) string // 客户端标识生成器
|
||||
skip ?fn (Context) bool // 跳过限流条件
|
||||
handler ?fn (mut Context, RateLimitInfo) http.Response // 自定义限流响应
|
||||
store &MemoryStore // 存储后端(必需)
|
||||
headers bool = true // 是否添加限流头,默认 true
|
||||
}
|
||||
|
||||
// RateLimitInfo 结构体 - 限流信息(传递给自定义 handler)
|
||||
pub struct RateLimitInfo {
|
||||
pub:
|
||||
limit int // 最大请求数
|
||||
remaining int // 剩余请求数
|
||||
reset_at i64 // 重置时间戳(毫秒)
|
||||
}
|
||||
|
||||
// rate_limit - 限流中间件工厂函数
|
||||
// 返回一个 ContextMiddleware,用于限制请求频率
|
||||
// 注意:必须提供 store 参数
|
||||
pub fn rate_limit(options RateLimitOptions) ContextMiddleware {
|
||||
mut store := options.store
|
||||
|
||||
return fn [options, mut store] (mut c Context, next fn (mut Context) http.Response) http.Response {
|
||||
// 检查是否跳过限流
|
||||
if skip_fn := options.skip {
|
||||
if skip_fn(c) {
|
||||
return next(mut c)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成客户端标识 key
|
||||
key := generate_rate_limit_key(c, options.key_generator)
|
||||
|
||||
// 增加计数
|
||||
count, reset_at := store.increment(key, options.window_ms)
|
||||
|
||||
// 计算剩余请求数
|
||||
remaining := if count > options.limit { 0 } else { options.limit - count }
|
||||
|
||||
// 设置限流响应头
|
||||
if options.headers {
|
||||
set_rate_limit_headers(mut c, options.limit, remaining, reset_at)
|
||||
}
|
||||
|
||||
// 检查是否超过限制
|
||||
if count > options.limit {
|
||||
// 创建限流信息
|
||||
info := RateLimitInfo{
|
||||
limit: options.limit
|
||||
remaining: 0
|
||||
reset_at: reset_at
|
||||
}
|
||||
|
||||
// 使用自定义 handler 或默认响应
|
||||
if custom_handler := options.handler {
|
||||
return custom_handler(mut c, info)
|
||||
}
|
||||
|
||||
return rate_limit_exceeded_response(mut c, reset_at)
|
||||
}
|
||||
|
||||
// 继续处理请求
|
||||
return next(mut c)
|
||||
}
|
||||
}
|
||||
|
||||
// generate_rate_limit_key - 生成客户端标识 key
|
||||
fn generate_rate_limit_key(c Context, key_generator ?fn (Context) string) string {
|
||||
// 使用自定义 key 生成器
|
||||
if gen_fn := key_generator {
|
||||
return gen_fn(c)
|
||||
}
|
||||
|
||||
// 默认使用客户端 IP
|
||||
return c.get_client_ip()
|
||||
}
|
||||
|
||||
// set_rate_limit_headers - 设置限流响应头
|
||||
fn set_rate_limit_headers(mut c Context, limit int, remaining int, reset_at i64) {
|
||||
c.headers['X-RateLimit-Limit'] = limit.str()
|
||||
c.headers['X-RateLimit-Remaining'] = remaining.str()
|
||||
// 将毫秒时间戳转换为秒(HTTP 标准)
|
||||
c.headers['X-RateLimit-Reset'] = (reset_at / 1000).str()
|
||||
}
|
||||
|
||||
// rate_limit_exceeded_response - 返回 429 限流响应
|
||||
fn rate_limit_exceeded_response(mut c Context, reset_at i64) http.Response {
|
||||
c.status(429)
|
||||
|
||||
// 计算 Retry-After(秒)
|
||||
now := time.now().unix_milli()
|
||||
retry_after := if reset_at > now { (reset_at - now) / 1000 } else { i64(0) }
|
||||
if retry_after > 0 {
|
||||
c.headers['Retry-After'] = retry_after.str()
|
||||
}
|
||||
|
||||
return c.json('{"error":"Too Many Requests","message":"Rate limit exceeded. Please try again later."}')
|
||||
}
|
||||
|
||||
// get_rate_limit_info - 从 Context 获取限流信息
|
||||
// 这是一个便捷方法,用于在 handler 中获取当前请求的限流状态
|
||||
pub fn get_rate_limit_info(c Context) ?RateLimitInfo {
|
||||
limit_str := c.headers['X-RateLimit-Limit'] or { return none }
|
||||
remaining_str := c.headers['X-RateLimit-Remaining'] or { return none }
|
||||
reset_str := c.headers['X-RateLimit-Reset'] or { return none }
|
||||
|
||||
return RateLimitInfo{
|
||||
limit: limit_str.int()
|
||||
remaining: remaining_str.int()
|
||||
reset_at: reset_str.i64() * 1000 // 转换回毫秒
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user