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
+320
View File
@@ -0,0 +1,320 @@
import meiseayoung.hono
import net.http
import rand
import time
// Cookie Helper 属性测试
// Property-Based Testing for Cookie functionality
const test_iterations = 100
struct PropertyTestStats {
mut:
total_tests int
passed_tests int
failed_tests int
}
fn (mut stats PropertyTestStats) run_property_test(test_name string, test_func fn () bool) {
stats.total_tests++
print('🔬 ${test_name}... ')
if test_func() {
stats.passed_tests++
println('✅')
} else {
stats.failed_tests++
println('❌')
}
}
fn (stats PropertyTestStats) print_summary() {
println('\n=== Cookie Helper 属性测试总结 ===')
println('总测试数: ${stats.total_tests}')
println('通过: ${stats.passed_tests}')
println('失败: ${stats.failed_tests}')
if stats.failed_tests == 0 {
println('🎉 所有属性测试通过!')
} else {
println('⚠️ 有 ${stats.failed_tests} 个属性测试失败')
}
}
// 创建带 Cookie 头的测试 Context
fn create_context_with_cookies(cookie_header string) hono.Context {
mut headers := http.new_header()
if cookie_header.len > 0 {
headers.add_custom('Cookie', cookie_header) or {}
}
req := http.Request{
method: .get
url: '/test'
header: headers
}
return hono.Context.new(req, map[string]string{}, map[string]string{}, '')
}
// 创建空的测试 Context
fn create_empty_context() hono.Context {
req := http.Request{
method: .get
url: '/test'
}
return hono.Context.new(req, map[string]string{}, map[string]string{}, '')
}
// 生成随机的 Cookie 名称(只包含有效字符)
fn generate_random_cookie_name() string {
valid_chars := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-'
len := rand.int_in_range(1, 20) or { 5 }
mut name := ''
for _ in 0 .. len {
idx := rand.int_in_range(0, valid_chars.len) or { 0 }
name += valid_chars[idx].ascii_str()
}
return name
}
// 生成随机的 Cookie 值(不包含分号和等号)
fn generate_random_cookie_value() string {
// 使用安全的字符集,避免特殊字符
valid_chars := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.'
len := rand.int_in_range(1, 50) or { 10 }
mut value := ''
for _ in 0 .. len {
idx := rand.int_in_range(0, valid_chars.len) or { 0 }
value += valid_chars[idx].ascii_str()
}
return value
}
// 生成随机的密钥
fn generate_random_secret() string {
chars := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'
len := rand.int_in_range(16, 64) or { 32 }
mut secret := ''
for _ in 0 .. len {
idx := rand.int_in_range(0, chars.len) or { 0 }
secret += chars[idx].ascii_str()
}
return secret
}
// ============================================================================
// Property 3: Cookie Round-Trip Consistency
// Feature: builtin-middleware, Property 3: Cookie Round-Trip Consistency
// Validates: Requirements 2.1, 2.3
//
// *For any* cookie name and value, calling set_cookie followed by get_cookie
// on the same name SHALL return the original value.
// ============================================================================
fn test_property_3_cookie_roundtrip() bool {
rand.seed([u32(time.now().unix()), u32(12345)])
for i in 0 .. test_iterations {
name := generate_random_cookie_name()
value := generate_random_cookie_value()
// 设置 Cookie
mut ctx := create_empty_context()
hono.set_cookie(mut ctx, name, value)
// 获取 Set-Cookie 头中的值
set_cookie_header := ctx.headers['Set-Cookie'] or {
println(' Iteration ${i}: Failed to get Set-Cookie header')
return false
}
// 提取 Cookie 值
mut cookie_value := ''
parts := set_cookie_header.split(';')
if parts.len > 0 {
name_value := parts[0].trim_space()
eq_pos := name_value.index('=') or {
println(' Iteration ${i}: Invalid cookie format')
return false
}
cookie_value = name_value[eq_pos + 1..]
}
// 创建带 Cookie 的 Context 来验证
verify_ctx := create_context_with_cookies('${name}=${cookie_value}')
// 获取 Cookie 并验证
retrieved := hono.get_cookie(verify_ctx, name) or {
println(' Iteration ${i}: Failed to get cookie')
return false
}
if retrieved != value {
println(' Iteration ${i}: Value mismatch - expected "${value}", got "${retrieved}"')
return false
}
}
return true
}
// ============================================================================
// Property 4: Signed Cookie Round-Trip
// Feature: builtin-middleware, Property 4: Signed Cookie Round-Trip
// Validates: Requirements 2.7, 2.8
//
// *For any* cookie name, value, and secret, calling set_signed_cookie followed
// by get_signed_cookie with the same secret SHALL return the original value.
// ============================================================================
fn test_property_4_signed_cookie_roundtrip() bool {
rand.seed([u32(time.now().unix()), u32(54321)])
for i in 0 .. test_iterations {
name := generate_random_cookie_name()
value := generate_random_cookie_value()
secret := generate_random_secret()
// 设置签名 Cookie
mut ctx := create_empty_context()
hono.set_signed_cookie(mut ctx, name, value, secret) or {
println(' Iteration ${i}: Failed to set signed cookie: ${err}')
return false
}
// 获取 Set-Cookie 头中的值
set_cookie_header := ctx.headers['Set-Cookie'] or {
println(' Iteration ${i}: Failed to get Set-Cookie header')
return false
}
// 提取 Cookie 值(包含签名)
mut cookie_value := ''
parts := set_cookie_header.split(';')
if parts.len > 0 {
name_value := parts[0].trim_space()
eq_pos := name_value.index('=') or {
println(' Iteration ${i}: Invalid cookie format')
return false
}
cookie_value = name_value[eq_pos + 1..]
}
// 创建带签名 Cookie 的 Context 来验证
verify_ctx := create_context_with_cookies('${name}=${cookie_value}')
// 获取并验证签名 Cookie
retrieved := hono.get_signed_cookie(verify_ctx, name, secret) or {
println(' Iteration ${i}: Failed to get signed cookie: ${err}')
return false
}
if retrieved != value {
println(' Iteration ${i}: Value mismatch - expected "${value}", got "${retrieved}"')
return false
}
}
return true
}
// ============================================================================
// Property 5: Signed Cookie Tamper Detection
// Feature: builtin-middleware, Property 5: Signed Cookie Tamper Detection
// Validates: Requirements 2.9
//
// *For any* signed cookie, modifying the cookie value or signature SHALL cause
// get_signed_cookie to return an error.
// ============================================================================
fn test_property_5_signed_cookie_tamper_detection() bool {
rand.seed([u32(time.now().unix()), u32(98765)])
for i in 0 .. test_iterations {
name := generate_random_cookie_name()
value := generate_random_cookie_value()
secret := generate_random_secret()
// 设置签名 Cookie
mut ctx := create_empty_context()
hono.set_signed_cookie(mut ctx, name, value, secret) or {
println(' Iteration ${i}: Failed to set signed cookie: ${err}')
return false
}
// 获取 Set-Cookie 头中的值
set_cookie_header := ctx.headers['Set-Cookie'] or {
println(' Iteration ${i}: Failed to get Set-Cookie header')
return false
}
// 提取 Cookie 值(包含签名)
mut cookie_value := ''
parts := set_cookie_header.split(';')
if parts.len > 0 {
name_value := parts[0].trim_space()
eq_pos := name_value.index('=') or {
println(' Iteration ${i}: Invalid cookie format')
return false
}
cookie_value = name_value[eq_pos + 1..]
}
// 篡改 Cookie 值(修改第一个字符)
if cookie_value.len > 1 {
// 找到签名分隔符
dot_pos := cookie_value.last_index('.') or {
println(' Iteration ${i}: No signature separator found')
return false
}
// 篡改值部分
original_value_part := cookie_value[..dot_pos]
signature_part := cookie_value[dot_pos..]
// 修改值的第一个字符
mut tampered_value := ''
if original_value_part.len > 0 {
first_char := original_value_part[0]
new_char := if first_char == `a` { 'b' } else { 'a' }
tampered_value = new_char + original_value_part[1..] + signature_part
} else {
tampered_value = 'x' + signature_part
}
// 创建带篡改 Cookie 的 Context
verify_ctx := create_context_with_cookies('${name}=${tampered_value}')
// 验证应该失败
if _ := hono.get_signed_cookie(verify_ctx, name, secret) {
println(' Iteration ${i}: Tampered cookie was accepted (should have been rejected)')
return false
}
}
}
return true
}
fn main() {
println('🚀 开始 Cookie Helper 属性测试...')
println('每个属性测试运行 ${test_iterations} 次迭代\n')
mut stats := PropertyTestStats{}
// 运行属性测试
// Feature: builtin-middleware, Property 3: Cookie Round-Trip Consistency
// Validates: Requirements 2.1, 2.3
stats.run_property_test('Property 3: Cookie Round-Trip Consistency', test_property_3_cookie_roundtrip)
// Feature: builtin-middleware, Property 4: Signed Cookie Round-Trip
// Validates: Requirements 2.7, 2.8
stats.run_property_test('Property 4: Signed Cookie Round-Trip', test_property_4_signed_cookie_roundtrip)
// Feature: builtin-middleware, Property 5: Signed Cookie Tamper Detection
// Validates: Requirements 2.9
stats.run_property_test('Property 5: Signed Cookie Tamper Detection', test_property_5_signed_cookie_tamper_detection)
// 打印测试总结
stats.print_summary()
}