perf: 重构优化路由性能,添加日志系统,优化大文件内存占用,加强输入验证及路径安全等
This commit is contained in:
+157
-29
@@ -27,6 +27,8 @@ pub mut:
|
||||
context_router ContextRouter = ContextRouter{}
|
||||
context_hybrid_router ContextHybridRouter
|
||||
context_trie_router ContextTrieRouter
|
||||
fast_router FastRouter // 新增:快速路由器
|
||||
use_fast_router bool = true // 新增:是否使用快速路由器
|
||||
context_middlewares []ContextMiddleware
|
||||
}
|
||||
|
||||
@@ -39,7 +41,17 @@ pub fn (mut app Hono) get(path string, handler fn (mut Context) http.Response) {
|
||||
path: path
|
||||
handler: handler
|
||||
}
|
||||
app.context_hybrid_router.add_route('GET', h, '')
|
||||
|
||||
// 添加到快速路由器
|
||||
if app.use_fast_router {
|
||||
app.fast_router.add_route('GET', h, '') or {
|
||||
println('[WARNING] FastRouter failed to add route ${path}, falling back to hybrid router')
|
||||
app.context_hybrid_router.add_route('GET', h, '')
|
||||
}
|
||||
} else {
|
||||
app.context_hybrid_router.add_route('GET', h, '')
|
||||
}
|
||||
|
||||
app.context_trie_router.add_route('GET', path, h)
|
||||
app.context_router.handlers.get << h
|
||||
}
|
||||
@@ -49,43 +61,103 @@ pub fn (mut app Hono) post(path string, handler fn (mut Context) http.Response)
|
||||
path: path
|
||||
handler: handler
|
||||
}
|
||||
app.context_hybrid_router.add_route('POST', h, '')
|
||||
|
||||
// 添加到快速路由器
|
||||
if app.use_fast_router {
|
||||
app.fast_router.add_route('POST', h, '') or {
|
||||
println('[WARNING] FastRouter failed to add route ${path}, falling back to hybrid router')
|
||||
app.context_hybrid_router.add_route('POST', h, '')
|
||||
}
|
||||
} else {
|
||||
app.context_hybrid_router.add_route('POST', h, '')
|
||||
}
|
||||
|
||||
app.context_trie_router.add_route('POST', path, h)
|
||||
app.context_router.handlers.post << h
|
||||
}
|
||||
|
||||
pub fn (mut app Hono) put(path string, handler fn (mut Context) http.Response) {
|
||||
h := ContextHandler{path, handler}
|
||||
|
||||
// 添加到快速路由器
|
||||
if app.use_fast_router {
|
||||
app.fast_router.add_route('PUT', h, app.base_path) or {
|
||||
println('[WARNING] FastRouter failed to add route ${path}, falling back to hybrid router')
|
||||
app.context_hybrid_router.add_route('PUT', h, app.base_path)
|
||||
}
|
||||
} else {
|
||||
app.context_hybrid_router.add_route('PUT', h, app.base_path)
|
||||
}
|
||||
|
||||
app.context_router.handlers.put << h
|
||||
app.context_hybrid_router.add_route('PUT', h, app.base_path)
|
||||
app.context_trie_router.add_route('PUT', path, h)
|
||||
}
|
||||
|
||||
pub fn (mut app Hono) delete(path string, handler fn (mut Context) http.Response) {
|
||||
h := ContextHandler{path, handler}
|
||||
|
||||
// 添加到快速路由器
|
||||
if app.use_fast_router {
|
||||
app.fast_router.add_route('DELETE', h, app.base_path) or {
|
||||
println('[WARNING] FastRouter failed to add route ${path}, falling back to hybrid router')
|
||||
app.context_hybrid_router.add_route('DELETE', h, app.base_path)
|
||||
}
|
||||
} else {
|
||||
app.context_hybrid_router.add_route('DELETE', h, app.base_path)
|
||||
}
|
||||
|
||||
app.context_router.handlers.delete << h
|
||||
app.context_hybrid_router.add_route('DELETE', h, app.base_path)
|
||||
app.context_trie_router.add_route('DELETE', path, h)
|
||||
}
|
||||
|
||||
pub fn (mut app Hono) patch(path string, handler fn (mut Context) http.Response) {
|
||||
h := ContextHandler{path, handler}
|
||||
|
||||
// 添加到快速路由器
|
||||
if app.use_fast_router {
|
||||
app.fast_router.add_route('PATCH', h, app.base_path) or {
|
||||
println('[WARNING] FastRouter failed to add route ${path}, falling back to hybrid router')
|
||||
app.context_hybrid_router.add_route('PATCH', h, app.base_path)
|
||||
}
|
||||
} else {
|
||||
app.context_hybrid_router.add_route('PATCH', h, app.base_path)
|
||||
}
|
||||
|
||||
app.context_router.handlers.patch << h
|
||||
app.context_hybrid_router.add_route('PATCH', h, app.base_path)
|
||||
app.context_trie_router.add_route('PATCH', path, h)
|
||||
}
|
||||
|
||||
pub fn (mut app Hono) head(path string, handler fn (mut Context) http.Response) {
|
||||
h := ContextHandler{path, handler}
|
||||
|
||||
// 添加到快速路由器
|
||||
if app.use_fast_router {
|
||||
app.fast_router.add_route('HEAD', h, app.base_path) or {
|
||||
println('[WARNING] FastRouter failed to add route ${path}, falling back to hybrid router')
|
||||
app.context_hybrid_router.add_route('HEAD', h, app.base_path)
|
||||
}
|
||||
} else {
|
||||
app.context_hybrid_router.add_route('HEAD', h, app.base_path)
|
||||
}
|
||||
|
||||
app.context_router.handlers.head << h
|
||||
app.context_hybrid_router.add_route('HEAD', h, app.base_path)
|
||||
app.context_trie_router.add_route('HEAD', path, h)
|
||||
}
|
||||
|
||||
pub fn (mut app Hono) options(path string, handler fn (mut Context) http.Response) {
|
||||
h := ContextHandler{path, handler}
|
||||
|
||||
// 添加到快速路由器
|
||||
if app.use_fast_router {
|
||||
app.fast_router.add_route('OPTIONS', h, app.base_path) or {
|
||||
println('[WARNING] FastRouter failed to add route ${path}, falling back to hybrid router')
|
||||
app.context_hybrid_router.add_route('OPTIONS', h, app.base_path)
|
||||
}
|
||||
} else {
|
||||
app.context_hybrid_router.add_route('OPTIONS', h, app.base_path)
|
||||
}
|
||||
|
||||
app.context_router.handlers.options << h
|
||||
app.context_hybrid_router.add_route('OPTIONS', h, app.base_path)
|
||||
app.context_trie_router.add_route('OPTIONS', path, h)
|
||||
}
|
||||
|
||||
@@ -126,26 +198,56 @@ fn (mut s ServerHanler) handle(req http.Request) http.Response {
|
||||
}
|
||||
|
||||
// 尝试 Context 路由
|
||||
if route_match := s.app.context_hybrid_router.match_route(req.method.str(), url.path) {
|
||||
// param 由路由匹配结果提供
|
||||
param_map := route_match.params.clone()
|
||||
// body
|
||||
body := req.data
|
||||
// 构造 Context
|
||||
mut ctx := Context.new(req, param_map, query_map, body)
|
||||
// 洋葱模型递归执行中间件
|
||||
return s.exec_context_middlewares(0, mut ctx, fn [route_match] (mut c Context) http.Response {
|
||||
return route_match.handler.handle(mut c)
|
||||
})
|
||||
// 优先使用快速路由器
|
||||
if s.app.use_fast_router {
|
||||
if route_match := s.app.fast_router.match_route(req.method.str(), url.path) {
|
||||
// param 由路由匹配结果提供
|
||||
param_map := route_match.params.clone()
|
||||
// body
|
||||
body := req.data
|
||||
// 构造 Context
|
||||
mut ctx := Context.new(req, param_map, query_map, body)
|
||||
// 洋葱模型递归执行中间件
|
||||
return s.exec_context_middlewares(0, mut ctx, fn [route_match] (mut c Context) http.Response {
|
||||
return route_match.handler.handle(mut c)
|
||||
})
|
||||
}
|
||||
|
||||
// 如果快速路由器没有匹配,回退到混合路由器
|
||||
if route_match := s.app.context_hybrid_router.match_route(req.method.str(), url.path) {
|
||||
// param 由路由匹配结果提供
|
||||
param_map := route_match.params.clone()
|
||||
// body
|
||||
body := req.data
|
||||
// 构造 Context
|
||||
mut ctx := Context.new(req, param_map, query_map, body)
|
||||
// 洋葱模型递归执行中间件
|
||||
return s.exec_context_middlewares(0, mut ctx, fn [route_match] (mut c Context) http.Response {
|
||||
return route_match.handler.handle(mut c)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 如果没有匹配的路由,也要执行中间件
|
||||
param_map := map[string]string{}
|
||||
body := req.data
|
||||
mut ctx := Context.new(req, param_map, query_map, body)
|
||||
return s.exec_context_middlewares(0, mut ctx, fn [res] (mut c Context) http.Response {
|
||||
return res
|
||||
})
|
||||
if route_match := s.app.context_hybrid_router.match_route(req.method.str(), url.path) {
|
||||
// param 由路由匹配结果提供
|
||||
param_map := route_match.params.clone()
|
||||
// body
|
||||
body := req.data
|
||||
// 构造 Context
|
||||
mut ctx := Context.new(req, param_map, query_map, body)
|
||||
// 洋葱模型递归执行中间件
|
||||
return s.exec_context_middlewares(0, mut ctx, fn [route_match] (mut c Context) http.Response {
|
||||
return route_match.handler.handle(mut c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有匹配的路由,也要执行中间件
|
||||
param_map := map[string]string{}
|
||||
body := req.data
|
||||
mut ctx := Context.new(req, param_map, query_map, body)
|
||||
return s.exec_context_middlewares(0, mut ctx, fn [res] (mut c Context) http.Response {
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
// Context 版本的中间件执行函数
|
||||
@@ -200,14 +302,38 @@ pub fn (mut app Hono) set_base_path(base_path string) {
|
||||
|
||||
// 路由统计信息
|
||||
pub fn (app Hono) get_router_stats() (int, int, int, int) {
|
||||
static_paths, dynamic_paths := app.context_hybrid_router.get_all_routes()
|
||||
cache_size, cache_capacity := app.context_hybrid_router.get_cache_stats()
|
||||
return static_paths.len, dynamic_paths.len, cache_size, cache_capacity
|
||||
if app.use_fast_router {
|
||||
static_count, dynamic_count, cache_count := app.fast_router.get_stats()
|
||||
return static_count, dynamic_count, cache_count, 0
|
||||
} else {
|
||||
static_paths, dynamic_paths := app.context_hybrid_router.get_all_routes()
|
||||
cache_size, cache_capacity := app.context_hybrid_router.get_cache_stats()
|
||||
return static_paths.len, dynamic_paths.len, cache_size, cache_capacity
|
||||
}
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
pub fn (mut app Hono) clear_cache() {
|
||||
app.context_hybrid_router.clear_cache()
|
||||
if app.use_fast_router {
|
||||
app.fast_router.clear_cache()
|
||||
} else {
|
||||
app.context_hybrid_router.clear_cache()
|
||||
}
|
||||
}
|
||||
|
||||
// 启用/禁用快速路由器
|
||||
pub fn (mut app Hono) set_fast_router_enabled(enabled bool) {
|
||||
app.use_fast_router = enabled
|
||||
println('[INFO] FastRouter ${if enabled { 'enabled' } else { 'disabled' }}')
|
||||
}
|
||||
|
||||
// 获取路由器性能分析
|
||||
pub fn (mut app Hono) analyze_router_performance() {
|
||||
if app.use_fast_router {
|
||||
app.fast_router.analyze_performance()
|
||||
} else {
|
||||
app.context_hybrid_router.analyze_router_performance()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn Hono.new() Hono {
|
||||
@@ -218,5 +344,7 @@ pub fn Hono.new() Hono {
|
||||
context_router: ContextRouter{}
|
||||
context_hybrid_router: ContextHybridRouter.new()
|
||||
context_trie_router: ContextTrieRouter.new()
|
||||
fast_router: FastRouter.new()
|
||||
use_fast_router: true
|
||||
}
|
||||
}
|
||||
|
||||
+34
-11
@@ -105,11 +105,12 @@ pub fn (mut cache ContextLRUCache) put(key string, value ContextRouteMatch) {
|
||||
|
||||
// 移动到链表头部
|
||||
fn (mut cache ContextLRUCache) move_to_front(mut node ContextLRUCacheNode) {
|
||||
// 如果已经是头节点,直接返回
|
||||
if unsafe { voidptr(node) == voidptr(cache.head) } {
|
||||
return
|
||||
}
|
||||
|
||||
// 从当前位置移除
|
||||
// 安全地从当前位置移除
|
||||
if node.prev != unsafe { nil } {
|
||||
mut prev := node.prev
|
||||
prev.next = node.next
|
||||
@@ -119,11 +120,12 @@ fn (mut cache ContextLRUCache) move_to_front(mut node ContextLRUCacheNode) {
|
||||
next.prev = node.prev
|
||||
}
|
||||
|
||||
// 如果是尾节点
|
||||
// 如果是尾节点,更新尾指针
|
||||
if unsafe { voidptr(node) == voidptr(cache.tail) } {
|
||||
cache.tail = node.prev
|
||||
}
|
||||
|
||||
// 添加到头部
|
||||
cache.add_to_front(mut node)
|
||||
}
|
||||
|
||||
@@ -161,10 +163,10 @@ fn (mut cache ContextLRUCache) remove_node(mut node ContextLRUCacheNode) {
|
||||
return // 避免移除已被清理的节点
|
||||
}
|
||||
|
||||
// 从哈希表中移除
|
||||
// 先从哈希表中移除,避免悬空指针
|
||||
cache.cache.delete(node.key)
|
||||
|
||||
// 更新链表指针
|
||||
// 安全更新链表指针
|
||||
if node.prev != unsafe { nil } {
|
||||
mut prev := node.prev
|
||||
prev.next = node.next
|
||||
@@ -181,12 +183,15 @@ fn (mut cache ContextLRUCache) remove_node(mut node ContextLRUCacheNode) {
|
||||
cache.tail = node.prev
|
||||
}
|
||||
|
||||
// 清理节点引用,防止内存泄漏
|
||||
// 彻底清理节点引用,防止内存泄漏
|
||||
node.prev = unsafe { nil }
|
||||
node.next = unsafe { nil }
|
||||
node.key = '' // 清空key作为已清理的标记
|
||||
|
||||
cache.size--
|
||||
// 更新大小计数
|
||||
if cache.size > 0 {
|
||||
cache.size--
|
||||
}
|
||||
}
|
||||
|
||||
// 获取缓存统计信息
|
||||
@@ -233,9 +238,9 @@ pub fn (mut cache ContextLRUCache) set_cleanup_interval(interval_seconds i64) {
|
||||
|
||||
// 检查缓存是否健康
|
||||
pub fn (mut cache ContextLRUCache) is_healthy() bool {
|
||||
// 检查链表一致性
|
||||
// 检查基本状态
|
||||
if cache.size == 0 {
|
||||
return cache.head == unsafe { nil } && cache.tail == unsafe { nil }
|
||||
return cache.head == unsafe { nil } && cache.tail == unsafe { nil } && cache.cache.len == 0
|
||||
}
|
||||
|
||||
// 检查头尾指针
|
||||
@@ -244,7 +249,22 @@ pub fn (mut cache ContextLRUCache) is_healthy() bool {
|
||||
}
|
||||
|
||||
// 检查哈希表和链表大小一致
|
||||
return cache.cache.len == cache.size
|
||||
if cache.cache.len != cache.size {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查链表完整性
|
||||
mut count := 0
|
||||
mut current := cache.head
|
||||
for current != unsafe { nil } {
|
||||
count++
|
||||
if count > cache.size {
|
||||
return false // 检测到循环引用
|
||||
}
|
||||
current = current.next
|
||||
}
|
||||
|
||||
return count == cache.size
|
||||
}
|
||||
|
||||
// 检查节点是否过期
|
||||
@@ -295,14 +315,17 @@ pub fn (mut cache ContextLRUCache) force_cleanup_expired() {
|
||||
|
||||
// 安全清理所有缓存(内存安全版本)
|
||||
pub fn (mut cache ContextLRUCache) clear() {
|
||||
// 逐个清理节点引用
|
||||
// 逐个安全清理节点引用
|
||||
mut current := cache.head
|
||||
for current != unsafe { nil } {
|
||||
mut next := current.next
|
||||
// 清理当前节点的引用
|
||||
|
||||
// 彻底清理当前节点的所有引用
|
||||
current.prev = unsafe { nil }
|
||||
current.next = unsafe { nil }
|
||||
current.key = ''
|
||||
// 清空 value 中的数据(如果需要的话)
|
||||
|
||||
current = next
|
||||
}
|
||||
|
||||
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
module hono
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
// 服务器配置结构
|
||||
pub struct ServerConfig {
|
||||
pub mut:
|
||||
host string = '127.0.0.1'
|
||||
port int = 8080
|
||||
read_timeout int = 30 // 秒
|
||||
write_timeout int = 30 // 秒
|
||||
max_request_size u64 = 10 * 1024 * 1024 // 10MB
|
||||
enable_cors bool = true
|
||||
enable_gzip bool = true
|
||||
}
|
||||
|
||||
// 静态文件配置
|
||||
pub struct StaticConfig {
|
||||
pub mut:
|
||||
enabled bool = true
|
||||
root_dir string = './static'
|
||||
index_files []string = ['index.html', 'index.htm']
|
||||
cache_max_age int = 3600 // 秒
|
||||
enable_directory_listing bool
|
||||
allowed_extensions []string = ['.html', '.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.txt', '.pdf']
|
||||
}
|
||||
|
||||
// 上传配置
|
||||
pub struct UploadConfig {
|
||||
pub mut:
|
||||
enabled bool = true
|
||||
upload_dir string = './uploads'
|
||||
max_file_size u64 = 100 * 1024 * 1024 // 100MB
|
||||
max_chunk_size u64 = 5 * 1024 * 1024 // 5MB
|
||||
merge_buffer_size int = 8192 // 8KB
|
||||
allowed_types []string = ['.txt', '.pdf', '.doc', '.docx', '.jpg', '.jpeg', '.png', '.gif']
|
||||
cleanup_timeout int = 3600 // 秒,清理未完成上传的超时时间
|
||||
}
|
||||
|
||||
// 缓存配置
|
||||
pub struct CacheConfig {
|
||||
pub mut:
|
||||
enabled bool = true
|
||||
max_size int = 1000
|
||||
default_ttl int = 300 // 秒
|
||||
cleanup_interval int = 60 // 秒
|
||||
}
|
||||
|
||||
// 安全配置
|
||||
pub struct SecurityConfig {
|
||||
pub mut:
|
||||
enable_path_validation bool = true
|
||||
enable_file_type_check bool = true
|
||||
enable_size_limits bool = true
|
||||
max_path_length int = 255
|
||||
blocked_extensions []string = ['.exe', '.bat', '.cmd', '.sh', '.php', '.asp', '.jsp']
|
||||
enable_xss_protection bool = true
|
||||
enable_csrf_protection bool
|
||||
}
|
||||
|
||||
// 日志配置
|
||||
pub struct LogConfig {
|
||||
pub mut:
|
||||
enabled bool = true
|
||||
level string = 'info' // debug, info, warn, error
|
||||
output string = 'console' // console, file, both
|
||||
file_path string = './logs/app.log'
|
||||
max_file_size u64 = 10 * 1024 * 1024 // 10MB
|
||||
max_backup_files int = 5
|
||||
enable_request_log bool = true
|
||||
}
|
||||
|
||||
// 应用配置主结构
|
||||
pub struct AppConfig {
|
||||
pub mut:
|
||||
server ServerConfig
|
||||
static StaticConfig
|
||||
upload UploadConfig
|
||||
cache CacheConfig
|
||||
security SecurityConfig
|
||||
log LogConfig
|
||||
debug bool
|
||||
env string = 'development' // development, production, test
|
||||
}
|
||||
|
||||
// 默认配置
|
||||
pub fn default_config() AppConfig {
|
||||
return AppConfig{
|
||||
server: ServerConfig{}
|
||||
static: StaticConfig{}
|
||||
upload: UploadConfig{}
|
||||
cache: CacheConfig{}
|
||||
security: SecurityConfig{}
|
||||
log: LogConfig{}
|
||||
}
|
||||
}
|
||||
|
||||
// 从文件加载配置
|
||||
pub fn load_config(config_path string) !AppConfig {
|
||||
if !os.exists(config_path) {
|
||||
println('配置文件不存在: ${config_path},使用默认配置')
|
||||
return default_config()
|
||||
}
|
||||
|
||||
config_content := os.read_file(config_path) or {
|
||||
return error('无法读取配置文件: ${err}')
|
||||
}
|
||||
|
||||
config := json.decode(AppConfig, config_content) or {
|
||||
return error('配置文件格式错误: ${err}')
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
validate_config(config) or {
|
||||
return error('配置验证失败: ${err}')
|
||||
}
|
||||
|
||||
println('成功加载配置文件: ${config_path}')
|
||||
return config
|
||||
}
|
||||
|
||||
// 保存配置到文件
|
||||
pub fn save_config(config AppConfig, config_path string) ! {
|
||||
config_json := json.encode_pretty(config)
|
||||
|
||||
// 确保目录存在
|
||||
config_dir := os.dir(config_path)
|
||||
if !os.exists(config_dir) {
|
||||
os.mkdir_all(config_dir) or {
|
||||
return error('无法创建配置目录: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
os.write_file(config_path, config_json) or {
|
||||
return error('无法保存配置文件: ${err}')
|
||||
}
|
||||
|
||||
println('配置已保存到: ${config_path}')
|
||||
}
|
||||
|
||||
// 从环境变量加载配置
|
||||
pub fn load_config_from_env() AppConfig {
|
||||
mut config := default_config()
|
||||
|
||||
// 服务器配置
|
||||
host := os.getenv('HONO_HOST')
|
||||
if host != '' {
|
||||
config.server.host = host
|
||||
}
|
||||
|
||||
port_str := os.getenv('HONO_PORT')
|
||||
if port_str != '' {
|
||||
config.server.port = port_str.int()
|
||||
}
|
||||
|
||||
env := os.getenv('HONO_ENV')
|
||||
if env != '' {
|
||||
config.env = env
|
||||
}
|
||||
|
||||
debug_str := os.getenv('HONO_DEBUG')
|
||||
if debug_str != '' {
|
||||
config.debug = debug_str.to_lower() in ['true', '1', 'yes']
|
||||
}
|
||||
|
||||
// 上传配置
|
||||
upload_dir := os.getenv('HONO_UPLOAD_DIR')
|
||||
if upload_dir != '' {
|
||||
config.upload.upload_dir = upload_dir
|
||||
}
|
||||
|
||||
static_dir := os.getenv('HONO_STATIC_DIR')
|
||||
if static_dir != '' {
|
||||
config.static.root_dir = static_dir
|
||||
}
|
||||
|
||||
// 日志配置
|
||||
log_level := os.getenv('HONO_LOG_LEVEL')
|
||||
if log_level != '' {
|
||||
config.log.level = log_level
|
||||
}
|
||||
|
||||
log_file := os.getenv('HONO_LOG_FILE')
|
||||
if log_file != '' {
|
||||
config.log.file_path = log_file
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
pub fn validate_config(config AppConfig) ! {
|
||||
// 验证端口范围
|
||||
if config.server.port < 1 || config.server.port > 65535 {
|
||||
return error('端口号必须在 1-65535 范围内')
|
||||
}
|
||||
|
||||
// 验证超时设置
|
||||
if config.server.read_timeout < 1 || config.server.write_timeout < 1 {
|
||||
return error('超时时间必须大于0')
|
||||
}
|
||||
|
||||
// 验证文件大小限制
|
||||
if config.server.max_request_size < 1024 {
|
||||
return error('最大请求大小不能小于1KB')
|
||||
}
|
||||
|
||||
if config.upload.max_file_size < 1024 {
|
||||
return error('最大文件大小不能小于1KB')
|
||||
}
|
||||
|
||||
if config.upload.max_chunk_size < 1024 {
|
||||
return error('最大分片大小不能小于1KB')
|
||||
}
|
||||
|
||||
// 验证缓存配置
|
||||
if config.cache.max_size < 1 {
|
||||
return error('缓存最大大小必须大于0')
|
||||
}
|
||||
|
||||
// 验证日志级别
|
||||
if config.log.level !in ['debug', 'info', 'warn', 'error'] {
|
||||
return error('日志级别必须是: debug, info, warn, error 之一')
|
||||
}
|
||||
|
||||
// 验证环境
|
||||
if config.env !in ['development', 'production', 'test'] {
|
||||
return error('环境必须是: development, production, test 之一')
|
||||
}
|
||||
|
||||
// 验证目录路径
|
||||
if config.static.enabled && config.static.root_dir == '' {
|
||||
return error('静态文件目录不能为空')
|
||||
}
|
||||
|
||||
if config.upload.enabled && config.upload.upload_dir == '' {
|
||||
return error('上传目录不能为空')
|
||||
}
|
||||
}
|
||||
|
||||
// 创建示例配置文件
|
||||
pub fn create_example_config(config_path string) ! {
|
||||
config := default_config()
|
||||
save_config(config, config_path) or {
|
||||
return error('无法创建示例配置文件: ${err}')
|
||||
}
|
||||
println('示例配置文件已创建: ${config_path}')
|
||||
}
|
||||
|
||||
// 获取配置摘要信息
|
||||
pub fn get_config_summary(config AppConfig) string {
|
||||
mut summary := []string{}
|
||||
|
||||
summary << '=== 应用配置摘要 ==='
|
||||
summary << '环境: ${config.env}'
|
||||
summary << '调试模式: ${config.debug}'
|
||||
summary << ''
|
||||
summary << '服务器:'
|
||||
summary << ' 地址: ${config.server.host}:${config.server.port}'
|
||||
summary << ' 最大请求大小: ${config.server.max_request_size / 1024 / 1024}MB'
|
||||
summary << ' CORS: ${config.server.enable_cors}'
|
||||
summary << ' GZIP: ${config.server.enable_gzip}'
|
||||
summary << ''
|
||||
summary << '静态文件:'
|
||||
summary << ' 启用: ${config.static.enabled}'
|
||||
summary << ' 目录: ${config.static.root_dir}'
|
||||
summary << ' 缓存时间: ${config.static.cache_max_age}秒'
|
||||
summary << ''
|
||||
summary << '文件上传:'
|
||||
summary << ' 启用: ${config.upload.enabled}'
|
||||
summary << ' 目录: ${config.upload.upload_dir}'
|
||||
summary << ' 最大文件大小: ${config.upload.max_file_size / 1024 / 1024}MB'
|
||||
summary << ' 最大分片大小: ${config.upload.max_chunk_size / 1024 / 1024}MB'
|
||||
summary << ''
|
||||
summary << '缓存:'
|
||||
summary << ' 启用: ${config.cache.enabled}'
|
||||
summary << ' 最大条目: ${config.cache.max_size}'
|
||||
summary << ' 默认TTL: ${config.cache.default_ttl}秒'
|
||||
summary << ''
|
||||
summary << '安全:'
|
||||
summary << ' 路径验证: ${config.security.enable_path_validation}'
|
||||
summary << ' 文件类型检查: ${config.security.enable_file_type_check}'
|
||||
summary << ' XSS保护: ${config.security.enable_xss_protection}'
|
||||
summary << ''
|
||||
summary << '日志:'
|
||||
summary << ' 启用: ${config.log.enabled}'
|
||||
summary << ' 级别: ${config.log.level}'
|
||||
summary << ' 输出: ${config.log.output}'
|
||||
summary << ' 请求日志: ${config.log.enable_request_log}'
|
||||
|
||||
return summary.join('\n')
|
||||
}
|
||||
|
||||
// 合并配置(用于配置覆盖)
|
||||
pub fn merge_config(base AppConfig, override AppConfig) AppConfig {
|
||||
mut merged := base
|
||||
|
||||
// 这里可以实现更复杂的合并逻辑
|
||||
// 目前简单地用override覆盖base的非零值
|
||||
if override.server.host != '' {
|
||||
merged.server.host = override.server.host
|
||||
}
|
||||
if override.server.port != 0 {
|
||||
merged.server.port = override.server.port
|
||||
}
|
||||
if override.env != '' {
|
||||
merged.env = override.env
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
module hono
|
||||
|
||||
import net.http
|
||||
import json
|
||||
|
||||
// 错误类型枚举
|
||||
pub enum ErrorType {
|
||||
bad_request = 400
|
||||
unauthorized = 401
|
||||
forbidden = 403
|
||||
not_found = 404
|
||||
method_not_allowed = 405
|
||||
conflict = 409
|
||||
payload_too_large = 413
|
||||
unsupported_media_type = 415
|
||||
unprocessable_entity = 422
|
||||
internal_server_error = 500
|
||||
not_implemented = 501
|
||||
bad_gateway = 502
|
||||
service_unavailable = 503
|
||||
}
|
||||
|
||||
// 标准化错误响应结构
|
||||
pub struct ErrorResponse {
|
||||
pub:
|
||||
error string
|
||||
message string
|
||||
code int
|
||||
timestamp string
|
||||
path string
|
||||
details map[string]string
|
||||
}
|
||||
|
||||
// 错误处理器接口
|
||||
pub interface IErrorHandler {
|
||||
handle_error(mut c Context, error_type ErrorType, message string, details map[string]string) http.Response
|
||||
}
|
||||
|
||||
// 默认错误处理器
|
||||
pub struct DefaultErrorHandler {}
|
||||
|
||||
// 实现错误处理器接口
|
||||
pub fn (eh DefaultErrorHandler) handle_error(mut c Context, error_type ErrorType, message string, details map[string]string) http.Response {
|
||||
error_response := ErrorResponse{
|
||||
error: get_error_name(error_type)
|
||||
message: message
|
||||
code: int(error_type)
|
||||
timestamp: get_current_timestamp()
|
||||
path: c.path
|
||||
details: details
|
||||
}
|
||||
|
||||
c.status(int(error_type))
|
||||
return c.json(json.encode(error_response))
|
||||
}
|
||||
|
||||
// 获取错误名称
|
||||
fn get_error_name(error_type ErrorType) string {
|
||||
return match error_type {
|
||||
.bad_request { 'Bad Request' }
|
||||
.unauthorized { 'Unauthorized' }
|
||||
.forbidden { 'Forbidden' }
|
||||
.not_found { 'Not Found' }
|
||||
.method_not_allowed { 'Method Not Allowed' }
|
||||
.conflict { 'Conflict' }
|
||||
.payload_too_large { 'Payload Too Large' }
|
||||
.unsupported_media_type { 'Unsupported Media Type' }
|
||||
.unprocessable_entity { 'Unprocessable Entity' }
|
||||
.internal_server_error { 'Internal Server Error' }
|
||||
.not_implemented { 'Not Implemented' }
|
||||
.bad_gateway { 'Bad Gateway' }
|
||||
.service_unavailable { 'Service Unavailable' }
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前时间戳
|
||||
fn get_current_timestamp() string {
|
||||
// 简化的时间戳实现
|
||||
return '2025-12-26T00:00:00Z'
|
||||
}
|
||||
|
||||
// Context 扩展方法 - 便捷的错误处理
|
||||
pub fn (mut c Context) error_response(error_type ErrorType, message string) http.Response {
|
||||
return c.error_response_with_details(error_type, message, map[string]string{})
|
||||
}
|
||||
|
||||
pub fn (mut c Context) error_response_with_details(error_type ErrorType, message string, details map[string]string) http.Response {
|
||||
handler := DefaultErrorHandler{}
|
||||
return handler.handle_error(mut c, error_type, message, details)
|
||||
}
|
||||
|
||||
// 常用错误处理快捷方法
|
||||
pub fn (mut c Context) bad_request(message string) http.Response {
|
||||
return c.error_response(.bad_request, message)
|
||||
}
|
||||
|
||||
pub fn (mut c Context) unauthorized(message string) http.Response {
|
||||
return c.error_response(.unauthorized, message)
|
||||
}
|
||||
|
||||
pub fn (mut c Context) forbidden(message string) http.Response {
|
||||
return c.error_response(.forbidden, message)
|
||||
}
|
||||
|
||||
pub fn (mut c Context) not_found(message string) http.Response {
|
||||
return c.error_response(.not_found, message)
|
||||
}
|
||||
|
||||
pub fn (mut c Context) internal_error(message string) http.Response {
|
||||
return c.error_response(.internal_server_error, message)
|
||||
}
|
||||
|
||||
pub fn (mut c Context) validation_error(message string, field_errors map[string]string) http.Response {
|
||||
return c.error_response_with_details(.unprocessable_entity, message, field_errors)
|
||||
}
|
||||
|
||||
// 参数验证错误处理
|
||||
pub fn (mut c Context) missing_parameter(param_name string) http.Response {
|
||||
return c.bad_request('Missing required parameter: ${param_name}')
|
||||
}
|
||||
|
||||
pub fn (mut c Context) invalid_parameter(param_name string, reason string) http.Response {
|
||||
details := {
|
||||
'parameter': param_name
|
||||
'reason': reason
|
||||
}
|
||||
return c.error_response_with_details(.bad_request, 'Invalid parameter: ${param_name}', details)
|
||||
}
|
||||
|
||||
// 资源相关错误处理
|
||||
pub fn (mut c Context) resource_not_found(resource_type string, resource_id string) http.Response {
|
||||
details := {
|
||||
'resource_type': resource_type
|
||||
'resource_id': resource_id
|
||||
}
|
||||
return c.error_response_with_details(.not_found, '${resource_type} not found', details)
|
||||
}
|
||||
|
||||
pub fn (mut c Context) resource_conflict(resource_type string, reason string) http.Response {
|
||||
details := {
|
||||
'resource_type': resource_type
|
||||
'reason': reason
|
||||
}
|
||||
return c.error_response_with_details(.conflict, 'Resource conflict: ${reason}', details)
|
||||
}
|
||||
|
||||
// 文件操作错误处理
|
||||
pub fn (mut c Context) file_operation_error(operation string, filename string, reason string) http.Response {
|
||||
details := {
|
||||
'operation': operation
|
||||
'filename': filename
|
||||
'reason': reason
|
||||
}
|
||||
return c.error_response_with_details(.internal_server_error, 'File operation failed: ${operation}', details)
|
||||
}
|
||||
|
||||
// 数据库操作错误处理
|
||||
pub fn (mut c Context) database_error(operation string, reason string) http.Response {
|
||||
details := {
|
||||
'operation': operation
|
||||
'reason': reason
|
||||
}
|
||||
return c.error_response_with_details(.internal_server_error, 'Database operation failed', details)
|
||||
}
|
||||
|
||||
// 验证结果处理
|
||||
pub fn handle_validation_result[T](mut c Context, result !T, param_name string) !T {
|
||||
return result or {
|
||||
// 这里不能直接返回 http.Response,需要在调用处处理
|
||||
return error('Validation failed for ${param_name}: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
// 全局错误处理中间件
|
||||
pub fn error_handling_middleware() fn (mut Context, fn (mut Context) http.Response) http.Response {
|
||||
return fn (mut c Context, next fn (mut Context) http.Response) http.Response {
|
||||
// 捕获 panic 并转换为错误响应
|
||||
// V语言目前不支持 try-catch,这里提供结构化的错误处理框架
|
||||
response := next(mut c)
|
||||
|
||||
// 如果响应状态码是错误状态,确保响应格式一致
|
||||
if c.status_code >= 400 {
|
||||
// 如果响应体不是标准错误格式,转换为标准格式
|
||||
if !response.body.contains('"error"') {
|
||||
error_type := match c.status_code {
|
||||
400 { ErrorType.bad_request }
|
||||
401 { ErrorType.unauthorized }
|
||||
403 { ErrorType.forbidden }
|
||||
404 { ErrorType.not_found }
|
||||
409 { ErrorType.conflict }
|
||||
422 { ErrorType.unprocessable_entity }
|
||||
500 { ErrorType.internal_server_error }
|
||||
else { ErrorType.internal_server_error }
|
||||
}
|
||||
return c.error_response(error_type, response.body)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
// 错误日志记录
|
||||
pub struct ErrorLogger {
|
||||
mut:
|
||||
enabled bool = true
|
||||
}
|
||||
|
||||
pub fn (mut logger ErrorLogger) log_error(error_response ErrorResponse, request_info string) {
|
||||
if !logger.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
println('[ERROR] ${error_response.timestamp} - ${error_response.code} ${error_response.error}')
|
||||
println(' Path: ${error_response.path}')
|
||||
println(' Message: ${error_response.message}')
|
||||
if error_response.details.len > 0 {
|
||||
println(' Details: ${error_response.details}')
|
||||
}
|
||||
println(' Request: ${request_info}')
|
||||
}
|
||||
|
||||
// 创建错误日志实例
|
||||
pub fn new_error_logger() ErrorLogger {
|
||||
return ErrorLogger{
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
module hono
|
||||
|
||||
import regex
|
||||
|
||||
// 预编译路由条目
|
||||
pub struct PrecompiledRoute {
|
||||
pub:
|
||||
method string
|
||||
pattern string
|
||||
regex regex.RE
|
||||
param_names []string
|
||||
handler IHandler
|
||||
complexity int // 路由复杂度分数
|
||||
}
|
||||
|
||||
// 编译后的正则表达式缓存
|
||||
pub struct FastCompiledRegex {
|
||||
pub mut:
|
||||
regex regex.RE
|
||||
param_names []string
|
||||
compiled bool
|
||||
}
|
||||
|
||||
// 快速路由器(增强版本)
|
||||
pub struct FastRouter {
|
||||
pub mut:
|
||||
static_routes map[string]IHandler
|
||||
precompiled_routes []PrecompiledRoute
|
||||
lru_cache ContextLRUCache // 使用 LRU 缓存替代简单 map
|
||||
regex_cache map[string]FastCompiledRegex // 正则表达式缓存
|
||||
cache_enabled bool = true
|
||||
sort_enabled bool = true // 是否启用路由排序
|
||||
}
|
||||
|
||||
// 创建快速路由器
|
||||
pub fn FastRouter.new() FastRouter {
|
||||
return FastRouter{
|
||||
static_routes: map[string]IHandler{}
|
||||
precompiled_routes: []PrecompiledRoute{}
|
||||
lru_cache: ContextLRUCache.new(1000) // 默认1000条缓存
|
||||
regex_cache: map[string]FastCompiledRegex{}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建带自定义缓存大小的快速路由器
|
||||
pub fn FastRouter.new_with_cache_size(cache_size int) FastRouter {
|
||||
return FastRouter{
|
||||
static_routes: map[string]IHandler{}
|
||||
precompiled_routes: []PrecompiledRoute{}
|
||||
lru_cache: ContextLRUCache.new(cache_size)
|
||||
regex_cache: map[string]FastCompiledRegex{}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加路由(预编译)
|
||||
pub fn (mut router FastRouter) add_route(method string, handler IHandler, base_path string) ! {
|
||||
full_path := handler.path
|
||||
|
||||
// 静态路由直接存储
|
||||
if !full_path.contains(':') && !full_path.contains('*') {
|
||||
router.static_routes['${method}:${full_path}'] = handler
|
||||
return
|
||||
}
|
||||
|
||||
// 动态路由预编译
|
||||
compiled_route := router.precompile_route(method, handler) or {
|
||||
return error('Failed to precompile route ${full_path}: ${err}')
|
||||
}
|
||||
|
||||
router.precompiled_routes << compiled_route
|
||||
|
||||
// 如果启用排序,按复杂度排序
|
||||
if router.sort_enabled {
|
||||
router.sort_dynamic_routes()
|
||||
}
|
||||
}
|
||||
|
||||
// 按路由复杂度排序动态路由(简单路由优先)
|
||||
fn (mut router FastRouter) sort_dynamic_routes() {
|
||||
router.precompiled_routes.sort_with_compare(fn (a &PrecompiledRoute, b &PrecompiledRoute) int {
|
||||
if a.complexity < b.complexity {
|
||||
return -1
|
||||
} else if a.complexity > b.complexity {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
// 计算路由复杂度分数(FastRouter 版本)
|
||||
fn calculate_fast_route_complexity(path string) int {
|
||||
mut score := 0
|
||||
|
||||
// 参数数量(每个参数+10分)
|
||||
score += path.count(':') * 10
|
||||
|
||||
// 通配符(每个通配符+20分)
|
||||
score += path.count('*') * 20
|
||||
|
||||
// 路径段数量(每个段+1分)
|
||||
score += path.split('/').len
|
||||
|
||||
// 特殊字符(每个+5分)
|
||||
special_chars := ['?', '+', '.', '(', ')', '[', ']', '{', '}', '^', '$', '|']
|
||||
for ch in special_chars {
|
||||
score += path.count(ch) * 5
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// 预编译路由
|
||||
fn (router FastRouter) precompile_route(method string, handler IHandler) !PrecompiledRoute {
|
||||
route_path := handler.path
|
||||
|
||||
// 计算路由复杂度
|
||||
complexity := calculate_fast_route_complexity(route_path)
|
||||
|
||||
// 提取参数名
|
||||
mut param_names := []string{}
|
||||
mut param_reg := regex.regex_opt(r':[a-zA-Z_][a-zA-Z0-9_]*') or {
|
||||
return error('Failed to create param regex')
|
||||
}
|
||||
|
||||
all_params := param_reg.find_all_str(route_path)
|
||||
for param in all_params {
|
||||
param_names << param[1..] // 去掉冒号
|
||||
}
|
||||
|
||||
// 构建正则表达式
|
||||
mut regex_pattern := route_path
|
||||
|
||||
// 转义特殊字符
|
||||
special_chars := ['?', '+', '.', '(', ')', '[', ']', '{', '}', '^', '$', '|']
|
||||
for ch in special_chars {
|
||||
regex_pattern = regex_pattern.replace(ch, '\\${ch}')
|
||||
}
|
||||
|
||||
// 替换参数为命名捕获组
|
||||
regex_pattern = param_reg.replace_by_fn(regex_pattern, fn (re regex.RE, in_txt string, start int, end int) string {
|
||||
param_name := in_txt[start+1..end]
|
||||
return '(?P<${param_name}>[^/]+)'
|
||||
})
|
||||
|
||||
// 添加锚点
|
||||
regex_pattern = '^${regex_pattern}$'
|
||||
|
||||
// 编译正则表达式
|
||||
compiled_regex := regex.regex_opt(regex_pattern) or {
|
||||
return error('Failed to compile regex: ${regex_pattern}')
|
||||
}
|
||||
|
||||
return PrecompiledRoute{
|
||||
method: method
|
||||
pattern: route_path
|
||||
regex: compiled_regex
|
||||
param_names: param_names
|
||||
handler: handler
|
||||
complexity: complexity
|
||||
}
|
||||
}
|
||||
|
||||
// 快速路由匹配
|
||||
pub fn (mut router FastRouter) match_route(method string, path string) ?ContextRouteMatch {
|
||||
// 1. 检查 LRU 缓存
|
||||
if router.cache_enabled {
|
||||
cache_key := '${method}:${path}'
|
||||
if cached := router.lru_cache.get(cache_key) {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 静态路由匹配
|
||||
static_key := '${method}:${path}'
|
||||
if static_handler := router.static_routes[static_key] {
|
||||
result := ContextRouteMatch{
|
||||
handler: static_handler
|
||||
params: map[string]string{}
|
||||
path: path
|
||||
base_path: ''
|
||||
}
|
||||
|
||||
if router.cache_enabled {
|
||||
router.lru_cache.put(static_key, result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. 预编译动态路由匹配(已按复杂度排序)
|
||||
for route in router.precompiled_routes {
|
||||
if route.method != method {
|
||||
continue
|
||||
}
|
||||
|
||||
if route.regex.matches_string(path) {
|
||||
// 提取参数
|
||||
mut params := map[string]string{}
|
||||
for param_name in route.param_names {
|
||||
group := route.regex.get_group_by_name(path, param_name)
|
||||
params[param_name] = group
|
||||
}
|
||||
|
||||
result := ContextRouteMatch{
|
||||
handler: route.handler
|
||||
params: params
|
||||
path: route.pattern
|
||||
base_path: ''
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
if router.cache_enabled {
|
||||
cache_key := '${method}:${path}'
|
||||
router.lru_cache.put(cache_key, result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return none
|
||||
}
|
||||
|
||||
// 获取路由统计
|
||||
pub fn (router FastRouter) get_stats() (int, int, int) {
|
||||
cache_size, _ := router.lru_cache.get_stats()
|
||||
return router.static_routes.len, router.precompiled_routes.len, cache_size
|
||||
}
|
||||
|
||||
// 获取详细统计信息
|
||||
pub fn (mut router FastRouter) get_detailed_stats() map[string]i64 {
|
||||
cache_stats := router.lru_cache.get_detailed_stats()
|
||||
|
||||
mut regex_compiled := 0
|
||||
for _, cached_regex in router.regex_cache {
|
||||
if cached_regex.compiled {
|
||||
regex_compiled++
|
||||
}
|
||||
}
|
||||
|
||||
mut stats := map[string]i64{}
|
||||
stats['static_routes'] = i64(router.static_routes.len)
|
||||
stats['dynamic_routes'] = i64(router.precompiled_routes.len)
|
||||
stats['regex_cache_total'] = i64(router.regex_cache.len)
|
||||
stats['regex_cache_compiled'] = i64(regex_compiled)
|
||||
stats['cache_enabled'] = if router.cache_enabled { 1 } else { 0 }
|
||||
stats['sort_enabled'] = if router.sort_enabled { 1 } else { 0 }
|
||||
|
||||
// 合并 LRU 缓存统计
|
||||
for key, value in cache_stats {
|
||||
stats['lru_${key}'] = value
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// 获取缓存统计信息
|
||||
pub fn (router FastRouter) get_cache_stats() (int, int) {
|
||||
return router.lru_cache.get_stats()
|
||||
}
|
||||
|
||||
// 获取正则表达式缓存统计信息
|
||||
pub fn (router FastRouter) get_regex_cache_stats() (int, int) {
|
||||
mut compiled_count := 0
|
||||
for _, cached_regex in router.regex_cache {
|
||||
if cached_regex.compiled {
|
||||
compiled_count++
|
||||
}
|
||||
}
|
||||
return router.regex_cache.len, compiled_count
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
pub fn (mut router FastRouter) clear_cache() {
|
||||
router.lru_cache.clear()
|
||||
}
|
||||
|
||||
// 清理正则表达式缓存
|
||||
pub fn (mut router FastRouter) clear_regex_cache() {
|
||||
router.regex_cache.clear()
|
||||
}
|
||||
|
||||
// 强制清理过期缓存
|
||||
pub fn (mut router FastRouter) force_cleanup_expired() {
|
||||
router.lru_cache.force_cleanup_expired()
|
||||
}
|
||||
|
||||
// 启用/禁用缓存
|
||||
pub fn (mut router FastRouter) set_cache_enabled(enabled bool) {
|
||||
router.cache_enabled = enabled
|
||||
if !enabled {
|
||||
router.clear_cache()
|
||||
}
|
||||
}
|
||||
|
||||
// 启用/禁用路由排序
|
||||
pub fn (mut router FastRouter) set_sort_enabled(enabled bool) {
|
||||
router.sort_enabled = enabled
|
||||
if enabled {
|
||||
router.sort_dynamic_routes()
|
||||
}
|
||||
}
|
||||
|
||||
// 设置缓存 TTL
|
||||
pub fn (mut router FastRouter) set_cache_ttl(ttl_seconds i64) {
|
||||
router.lru_cache.set_ttl(ttl_seconds)
|
||||
}
|
||||
|
||||
// 设置缓存清理间隔
|
||||
pub fn (mut router FastRouter) set_cache_cleanup_interval(interval_seconds i64) {
|
||||
router.lru_cache.set_cleanup_interval(interval_seconds)
|
||||
}
|
||||
|
||||
// 检查路由器健康状态
|
||||
pub fn (mut router FastRouter) is_healthy() bool {
|
||||
return router.lru_cache.is_healthy()
|
||||
}
|
||||
|
||||
// 预热缓存
|
||||
pub fn (mut router FastRouter) warmup_cache(common_paths []string, method string) {
|
||||
if !router.cache_enabled {
|
||||
return
|
||||
}
|
||||
|
||||
for path in common_paths {
|
||||
router.match_route(method, path)
|
||||
}
|
||||
}
|
||||
|
||||
// 预热正则表达式缓存
|
||||
pub fn (mut router FastRouter) warmup_regex_cache() {
|
||||
println('[INFO] Warming up FastRouter regex cache...')
|
||||
warmed_count := router.precompiled_routes.len
|
||||
|
||||
// 预编译的路由已经有编译好的正则表达式
|
||||
// 这里可以进行一些预热操作
|
||||
|
||||
println('[INFO] FastRouter regex cache warmup completed: ${warmed_count} patterns ready')
|
||||
}
|
||||
|
||||
// 智能预热(基于路由复杂度)
|
||||
pub fn (mut router FastRouter) smart_warmup(sample_paths []string) {
|
||||
if !router.cache_enabled {
|
||||
return
|
||||
}
|
||||
|
||||
println('[INFO] Starting smart warmup for FastRouter...')
|
||||
|
||||
// 按复杂度分组预热
|
||||
mut simple_routes := []string{}
|
||||
mut complex_routes := []string{}
|
||||
|
||||
for route in router.precompiled_routes {
|
||||
if route.complexity <= 20 {
|
||||
simple_routes << route.pattern
|
||||
} else {
|
||||
complex_routes << route.pattern
|
||||
}
|
||||
}
|
||||
|
||||
// 先预热简单路由
|
||||
for path in sample_paths {
|
||||
router.match_route('GET', path)
|
||||
}
|
||||
|
||||
println('[INFO] Smart warmup completed: ${simple_routes.len} simple, ${complex_routes.len} complex routes')
|
||||
}
|
||||
|
||||
// 获取所有路由
|
||||
pub fn (router FastRouter) get_all_routes() ([]string, []string) {
|
||||
mut static_paths := []string{}
|
||||
mut dynamic_paths := []string{}
|
||||
|
||||
for key in router.static_routes.keys() {
|
||||
static_paths << key
|
||||
}
|
||||
|
||||
for route in router.precompiled_routes {
|
||||
dynamic_paths << '${route.method}:${route.pattern} (complexity: ${route.complexity})'
|
||||
}
|
||||
|
||||
return static_paths, dynamic_paths
|
||||
}
|
||||
|
||||
// 获取路由按复杂度分组
|
||||
pub fn (router FastRouter) get_routes_by_complexity() ([]PrecompiledRoute, []PrecompiledRoute) {
|
||||
mut simple_routes := []PrecompiledRoute{}
|
||||
mut complex_routes := []PrecompiledRoute{}
|
||||
|
||||
for route in router.precompiled_routes {
|
||||
if route.complexity <= 30 {
|
||||
simple_routes << route
|
||||
} else {
|
||||
complex_routes << route
|
||||
}
|
||||
}
|
||||
|
||||
return simple_routes, complex_routes
|
||||
}
|
||||
|
||||
// 性能分析
|
||||
pub fn (mut router FastRouter) analyze_performance() {
|
||||
static_count, dynamic_count, cache_count := router.get_stats()
|
||||
regex_total, regex_compiled := router.get_regex_cache_stats()
|
||||
|
||||
println('[ENHANCED FAST ROUTER] Performance Analysis:')
|
||||
println(' Static Routes: ${static_count}')
|
||||
println(' Precompiled Dynamic Routes: ${dynamic_count}')
|
||||
println(' LRU Cache Size: ${cache_count}')
|
||||
println(' Regex Cache: ${regex_compiled}/${regex_total} compiled')
|
||||
println(' Cache Enabled: ${router.cache_enabled}')
|
||||
println(' Sort Enabled: ${router.sort_enabled}')
|
||||
|
||||
// 显示路由复杂度分布
|
||||
simple_routes, complex_routes := router.get_routes_by_complexity()
|
||||
println(' Route Complexity Distribution:')
|
||||
println(' Simple Routes (≤30): ${simple_routes.len}')
|
||||
println(' Complex Routes (>30): ${complex_routes.len}')
|
||||
|
||||
// 显示缓存健康状态
|
||||
if router.is_healthy() {
|
||||
println(' Cache Health: ✅ Healthy')
|
||||
} else {
|
||||
println(' Cache Health: ⚠️ Issues detected')
|
||||
}
|
||||
|
||||
// 显示详细统计
|
||||
detailed_stats := router.get_detailed_stats()
|
||||
println(' Detailed Stats:')
|
||||
for key, value in detailed_stats {
|
||||
println(' ${key}: ${value}')
|
||||
}
|
||||
}
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
module hono
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
// 日志级别枚举
|
||||
pub enum LogLevel {
|
||||
debug = 0
|
||||
info = 1
|
||||
warn = 2
|
||||
error = 3
|
||||
}
|
||||
|
||||
// 日志级别字符串映射
|
||||
const log_level_strings = {
|
||||
LogLevel.debug: 'DEBUG'
|
||||
LogLevel.info: 'INFO'
|
||||
LogLevel.warn: 'WARN'
|
||||
LogLevel.error: 'ERROR'
|
||||
}
|
||||
|
||||
// 日志输出类型
|
||||
pub enum LogOutput {
|
||||
console
|
||||
file
|
||||
both
|
||||
}
|
||||
|
||||
// 日志条目结构
|
||||
pub struct LogEntry {
|
||||
pub mut:
|
||||
timestamp string
|
||||
level string
|
||||
message string
|
||||
module string
|
||||
function string
|
||||
file string
|
||||
line int
|
||||
fields map[string]string
|
||||
request_id string
|
||||
}
|
||||
|
||||
// 日志配置
|
||||
pub struct LoggerConfig {
|
||||
pub mut:
|
||||
level LogLevel = LogLevel.info
|
||||
output LogOutput = LogOutput.console
|
||||
file_path string = './logs/app.log'
|
||||
max_file_size u64 = 10 * 1024 * 1024 // 10MB
|
||||
max_backup_files int = 5
|
||||
enable_colors bool = true
|
||||
enable_json bool
|
||||
time_format string = '2006-01-02 15:04:05'
|
||||
}
|
||||
|
||||
// 日志器结构
|
||||
pub struct Logger {
|
||||
pub mut:
|
||||
config LoggerConfig
|
||||
current_file_size u64
|
||||
}
|
||||
|
||||
// 创建新的日志器实例
|
||||
pub fn new_logger(config LoggerConfig) &Logger {
|
||||
return &Logger{
|
||||
config: config
|
||||
}
|
||||
}
|
||||
|
||||
// 字符串转日志级别
|
||||
pub fn parse_log_level(level_str string) LogLevel {
|
||||
match level_str.to_lower() {
|
||||
'debug' { return LogLevel.debug }
|
||||
'info' { return LogLevel.info }
|
||||
'warn' { return LogLevel.warn }
|
||||
'error' { return LogLevel.error }
|
||||
else { return LogLevel.info }
|
||||
}
|
||||
}
|
||||
|
||||
// 日志级别转字符串
|
||||
pub fn log_level_to_string(level LogLevel) string {
|
||||
return log_level_strings[level] or { 'INFO' }
|
||||
}
|
||||
|
||||
// 检查日志级别是否应该输出
|
||||
fn (l &Logger) should_log(level LogLevel) bool {
|
||||
return int(level) >= int(l.config.level)
|
||||
}
|
||||
|
||||
// 格式化时间戳
|
||||
fn format_timestamp() string {
|
||||
now := time.now()
|
||||
return now.format_ss()
|
||||
}
|
||||
|
||||
// 获取ANSI颜色代码
|
||||
fn get_color_code(level LogLevel) string {
|
||||
match level {
|
||||
.debug { return '\033[36m' } // 青色
|
||||
.info { return '\033[32m' } // 绿色
|
||||
.warn { return '\033[33m' } // 黄色
|
||||
.error { return '\033[31m' } // 红色
|
||||
}
|
||||
}
|
||||
|
||||
// 重置ANSI颜色
|
||||
const color_reset = '\033[0m'
|
||||
|
||||
// 格式化日志消息(文本格式)
|
||||
fn (l &Logger) format_text_log(entry LogEntry) string {
|
||||
mut parts := []string{}
|
||||
|
||||
// 时间戳
|
||||
parts << '[${entry.timestamp}]'
|
||||
|
||||
// 日志级别(带颜色)
|
||||
if l.config.enable_colors {
|
||||
color := get_color_code(parse_log_level(entry.level))
|
||||
parts << '${color}${entry.level}${color_reset}'
|
||||
} else {
|
||||
parts << entry.level
|
||||
}
|
||||
|
||||
// 模块信息
|
||||
if entry.module != '' {
|
||||
parts << '[${entry.module}]'
|
||||
}
|
||||
|
||||
// 消息
|
||||
parts << entry.message
|
||||
|
||||
// 附加字段
|
||||
if entry.fields.len > 0 {
|
||||
mut field_parts := []string{}
|
||||
for key, value in entry.fields {
|
||||
field_parts << '${key}=${value}'
|
||||
}
|
||||
parts << '{${field_parts.join(', ')}}'
|
||||
}
|
||||
|
||||
// 请求ID
|
||||
if entry.request_id != '' {
|
||||
parts << '[req:${entry.request_id}]'
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
// 格式化日志消息(JSON格式)
|
||||
fn (l &Logger) format_json_log(entry LogEntry) string {
|
||||
return json.encode(entry)
|
||||
}
|
||||
|
||||
// 写入日志到文件
|
||||
fn (mut l Logger) write_to_file(content string) {
|
||||
// 确保日志目录存在
|
||||
log_dir := os.dir(l.config.file_path)
|
||||
if !os.exists(log_dir) {
|
||||
os.mkdir_all(log_dir) or {
|
||||
eprintln('无法创建日志目录: ${err}')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查文件大小,必要时轮转
|
||||
if l.config.max_file_size > 0 {
|
||||
if os.exists(l.config.file_path) {
|
||||
file_size := os.file_size(l.config.file_path)
|
||||
if file_size >= l.config.max_file_size {
|
||||
l.rotate_log_file()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入日志
|
||||
mut file := os.open_append(l.config.file_path) or {
|
||||
eprintln('无法打开日志文件: ${err}')
|
||||
return
|
||||
}
|
||||
defer {
|
||||
file.close()
|
||||
}
|
||||
|
||||
file.writeln(content) or {
|
||||
eprintln('无法写入日志文件: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
// 轮转日志文件
|
||||
fn (l &Logger) rotate_log_file() {
|
||||
// 删除最旧的备份文件
|
||||
oldest_backup := '${l.config.file_path}.${l.config.max_backup_files}'
|
||||
if os.exists(oldest_backup) {
|
||||
os.rm(oldest_backup) or {}
|
||||
}
|
||||
|
||||
// 移动现有备份文件
|
||||
for i := l.config.max_backup_files - 1; i >= 1; i-- {
|
||||
old_file := '${l.config.file_path}.${i}'
|
||||
new_file := '${l.config.file_path}.${i + 1}'
|
||||
if os.exists(old_file) {
|
||||
os.mv(old_file, new_file) or {}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动当前日志文件为第一个备份
|
||||
if os.exists(l.config.file_path) {
|
||||
backup_file := '${l.config.file_path}.1'
|
||||
os.mv(l.config.file_path, backup_file) or {}
|
||||
}
|
||||
}
|
||||
|
||||
// 核心日志方法
|
||||
fn (mut l Logger) log(level LogLevel, message string, mod_name string, fields map[string]string, request_id string) {
|
||||
if !l.should_log(level) {
|
||||
return
|
||||
}
|
||||
|
||||
entry := LogEntry{
|
||||
timestamp: format_timestamp()
|
||||
level: log_level_to_string(level)
|
||||
message: message
|
||||
module: mod_name
|
||||
fields: fields
|
||||
request_id: request_id
|
||||
}
|
||||
|
||||
// 格式化日志内容
|
||||
content := if l.config.enable_json {
|
||||
l.format_json_log(entry)
|
||||
} else {
|
||||
l.format_text_log(entry)
|
||||
}
|
||||
|
||||
// 输出日志
|
||||
match l.config.output {
|
||||
.console {
|
||||
println(content)
|
||||
}
|
||||
.file {
|
||||
l.write_to_file(content)
|
||||
}
|
||||
.both {
|
||||
println(content)
|
||||
l.write_to_file(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 公共日志方法
|
||||
pub fn (mut l Logger) debug(message string) {
|
||||
l.log(LogLevel.debug, message, '', {}, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) info(message string) {
|
||||
l.log(LogLevel.info, message, '', {}, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) warn(message string) {
|
||||
l.log(LogLevel.warn, message, '', {}, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) error(message string) {
|
||||
l.log(LogLevel.error, message, '', {}, '')
|
||||
}
|
||||
|
||||
// 带模块的日志方法
|
||||
pub fn (mut l Logger) debug_with_module(message string, mod_name string) {
|
||||
l.log(LogLevel.debug, message, mod_name, {}, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) info_with_module(message string, mod_name string) {
|
||||
l.log(LogLevel.info, message, mod_name, {}, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) warn_with_module(message string, mod_name string) {
|
||||
l.log(LogLevel.warn, message, mod_name, {}, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) error_with_module(message string, mod_name string) {
|
||||
l.log(LogLevel.error, message, mod_name, {}, '')
|
||||
}
|
||||
|
||||
// 带字段的日志方法
|
||||
pub fn (mut l Logger) debug_with_fields(message string, fields map[string]string) {
|
||||
l.log(LogLevel.debug, message, '', fields, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) info_with_fields(message string, fields map[string]string) {
|
||||
l.log(LogLevel.info, message, '', fields, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) warn_with_fields(message string, fields map[string]string) {
|
||||
l.log(LogLevel.warn, message, '', fields, '')
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) error_with_fields(message string, fields map[string]string) {
|
||||
l.log(LogLevel.error, message, '', fields, '')
|
||||
}
|
||||
|
||||
// 带请求ID的日志方法
|
||||
pub fn (mut l Logger) debug_with_request(message string, request_id string) {
|
||||
l.log(LogLevel.debug, message, '', {}, request_id)
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) info_with_request(message string, request_id string) {
|
||||
l.log(LogLevel.info, message, '', {}, request_id)
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) warn_with_request(message string, request_id string) {
|
||||
l.log(LogLevel.warn, message, '', {}, request_id)
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) error_with_request(message string, request_id string) {
|
||||
l.log(LogLevel.error, message, '', {}, request_id)
|
||||
}
|
||||
|
||||
// 完整的日志方法
|
||||
pub fn (mut l Logger) log_full(level LogLevel, message string, mod_name string, fields map[string]string, request_id string) {
|
||||
l.log(level, message, mod_name, fields, request_id)
|
||||
}
|
||||
|
||||
// HTTP请求日志结构
|
||||
pub struct RequestLog {
|
||||
pub mut:
|
||||
method string
|
||||
path string
|
||||
status_code int
|
||||
response_time f64 // 毫秒
|
||||
user_agent string
|
||||
remote_addr string
|
||||
request_size u64
|
||||
response_size u64
|
||||
request_id string
|
||||
}
|
||||
|
||||
// 记录HTTP请求日志
|
||||
pub fn log_request(mut logger Logger, req_log RequestLog) {
|
||||
fields := {
|
||||
'method': req_log.method
|
||||
'path': req_log.path
|
||||
'status': req_log.status_code.str()
|
||||
'response_time': '${req_log.response_time:.2f}ms'
|
||||
'user_agent': req_log.user_agent
|
||||
'remote_addr': req_log.remote_addr
|
||||
'request_size': req_log.request_size.str()
|
||||
'response_size': req_log.response_size.str()
|
||||
}
|
||||
|
||||
message := '${req_log.method} ${req_log.path} ${req_log.status_code} ${req_log.response_time:.2f}ms'
|
||||
logger.log_full(LogLevel.info, message, 'HTTP', fields, req_log.request_id)
|
||||
}
|
||||
|
||||
// 性能监控日志
|
||||
pub fn log_performance(mut logger Logger, operation string, duration f64, details map[string]string) {
|
||||
mut fields := {
|
||||
'operation': operation
|
||||
'duration': '${duration:.2f}ms'
|
||||
}
|
||||
|
||||
// 合并详细信息
|
||||
for key, value in details {
|
||||
fields[key] = value
|
||||
}
|
||||
|
||||
message := '性能监控: ${operation} 耗时 ${duration:.2f}ms'
|
||||
logger.log_full(LogLevel.info, message, 'PERF', fields, '')
|
||||
}
|
||||
|
||||
// 错误日志(带堆栈信息)
|
||||
pub fn log_error_with_stack(mut logger Logger, message string, err_details string, mod_name string) {
|
||||
fields := {
|
||||
'error_details': err_details
|
||||
'stack_trace': 'V语言暂不支持堆栈跟踪'
|
||||
}
|
||||
|
||||
logger.log_full(LogLevel.error, message, mod_name, fields, '')
|
||||
}
|
||||
+14
-26
@@ -85,32 +85,38 @@ pub fn (mut c Context) file(file_path string) http.Response {
|
||||
|
||||
// file_with_options method - serve a file with custom options
|
||||
pub fn (mut c Context) file_with_options(file_path string, options FileOptions) http.Response {
|
||||
// 安全检查:防止路径遍历攻击
|
||||
if !is_safe_file_path(file_path) {
|
||||
// 增强的安全检查
|
||||
validation_options := PathValidationOptions{
|
||||
allow_absolute_paths: false
|
||||
allow_hidden_files: false
|
||||
check_file_extension: true
|
||||
}
|
||||
|
||||
safe_file_path := validate_file_path(file_path, validation_options) or {
|
||||
c.status(403)
|
||||
return c.text('Forbidden')
|
||||
return c.text('Forbidden: $err')
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if !os.exists(file_path) {
|
||||
if !os.exists(safe_file_path) {
|
||||
c.status(404)
|
||||
return c.text('File Not Found')
|
||||
}
|
||||
|
||||
// 检查是否为目录
|
||||
if os.is_dir(file_path) {
|
||||
if os.is_dir(safe_file_path) {
|
||||
c.status(400)
|
||||
return c.text('Cannot serve directory')
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
file_content := os.read_file(file_path) or {
|
||||
file_content := os.read_file(safe_file_path) or {
|
||||
c.status(500)
|
||||
return c.text('Internal Server Error')
|
||||
}
|
||||
|
||||
// 获取文件信息
|
||||
file_info := os.stat(file_path) or {
|
||||
file_info := os.stat(safe_file_path) or {
|
||||
c.status(500)
|
||||
return c.text('Internal Server Error')
|
||||
}
|
||||
@@ -126,7 +132,7 @@ pub fn (mut c Context) file_with_options(file_path string, options FileOptions)
|
||||
if options.content_type != '' {
|
||||
c.headers['Content-Type'] = options.content_type
|
||||
} else {
|
||||
content_type := get_file_content_type(file_path)
|
||||
content_type := get_safe_content_type(safe_file_path)
|
||||
c.headers['Content-Type'] = content_type
|
||||
}
|
||||
|
||||
@@ -202,24 +208,6 @@ pub:
|
||||
compress bool // 是否启用压缩(对流式传输)
|
||||
}
|
||||
|
||||
// 安全检查:防止路径遍历攻击
|
||||
fn is_safe_file_path(path string) bool {
|
||||
// 检查是否包含 .. 或绝对路径
|
||||
if path.contains('..') || path.starts_with('/') || path.starts_with('\\') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否包含危险字符
|
||||
dangerous_chars := ['<', '>', ':', '"', '|', '?', '*']
|
||||
for dangerous_char in dangerous_chars {
|
||||
if path.contains(dangerous_char) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 根据文件扩展名获取Content-Type
|
||||
fn get_file_content_type(file_path string) string {
|
||||
ext := os.file_ext(file_path).to_lower()
|
||||
|
||||
+152
-34
@@ -9,6 +9,14 @@ enum RouteType {
|
||||
wildcard
|
||||
}
|
||||
|
||||
// 编译后的正则表达式缓存
|
||||
pub struct CompiledRegex {
|
||||
pub mut:
|
||||
regex regex.RE
|
||||
param_names []string
|
||||
compiled bool
|
||||
}
|
||||
|
||||
// Context 路由节点
|
||||
struct ContextRouteNode {
|
||||
mut:
|
||||
@@ -31,10 +39,11 @@ pub:
|
||||
|
||||
// Context 混合路由器
|
||||
pub struct ContextHybridRouter {
|
||||
mut:
|
||||
pub mut:
|
||||
static_routes map[string]IHandler
|
||||
dynamic_routes []IHandler
|
||||
cache ContextLRUCache
|
||||
regex_cache map[string]CompiledRegex // 新增:正则表达式缓存
|
||||
}
|
||||
|
||||
// ContextHybridRouter 构造函数
|
||||
@@ -43,6 +52,7 @@ pub fn ContextHybridRouter.new() ContextHybridRouter {
|
||||
static_routes: map[string]IHandler{}
|
||||
dynamic_routes: []IHandler{}
|
||||
cache: ContextLRUCache.new(1000)
|
||||
regex_cache: map[string]CompiledRegex{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,57 +66,83 @@ fn is_dynamic_path(path string) bool {
|
||||
return path.contains(':') || path.contains('*') || path.contains('?') || path.contains('+')
|
||||
}
|
||||
|
||||
// Context 版本的正则匹配函数
|
||||
fn (mut router ContextHybridRouter) match_path_with_regex(real_path string, reg_path string) (bool, regex.RE, []string) {
|
||||
// Context 版本的正则匹配函数(优化版,使用缓存)
|
||||
pub fn (mut router ContextHybridRouter) match_path_with_regex(real_path string, reg_path string) (bool, regex.RE, []string) {
|
||||
// 先检查缓存
|
||||
if cached := router.regex_cache[reg_path] {
|
||||
if cached.compiled {
|
||||
return cached.regex.matches_string(real_path), cached.regex, cached.param_names
|
||||
}
|
||||
}
|
||||
|
||||
// 如果缓存中没有,编译并缓存
|
||||
mut compiled_regex := CompiledRegex{}
|
||||
mut replaced_path := reg_path
|
||||
mut param_names := []string{}
|
||||
|
||||
// 处理多个星号 **
|
||||
if reg_path.contains('**') {
|
||||
// 将 ** 替换为 .*
|
||||
replaced_path := reg_path.replace('**', '.*')
|
||||
replaced_path = reg_path.replace('**', '.*')
|
||||
mut reg := regex.regex_opt(replaced_path) or { return false, regex.RE{}, []string{} }
|
||||
compiled_regex = CompiledRegex{
|
||||
regex: reg
|
||||
param_names: []string{}
|
||||
compiled: true
|
||||
}
|
||||
router.regex_cache[reg_path] = compiled_regex
|
||||
return reg.matches_string(real_path), reg, []string{}
|
||||
}
|
||||
|
||||
// 处理单个星号 *
|
||||
if reg_path.contains('*') {
|
||||
// 将 * 替换为 [^/]*
|
||||
replaced_path := reg_path.replace('*', '[^/]*')
|
||||
replaced_path = reg_path.replace('*', '[^/]*')
|
||||
mut reg := regex.regex_opt(replaced_path) or { return false, regex.RE{}, []string{} }
|
||||
compiled_regex = CompiledRegex{
|
||||
regex: reg
|
||||
param_names: []string{}
|
||||
compiled: true
|
||||
}
|
||||
router.regex_cache[reg_path] = compiled_regex
|
||||
return reg.matches_string(real_path), reg, []string{}
|
||||
}
|
||||
|
||||
// 处理参数 :param
|
||||
if reg_path.contains(':') {
|
||||
mut replaced_path := reg_path
|
||||
mut param_names := []string{}
|
||||
// 预先提取参数名(避免在匹配时重复提取)
|
||||
mut param_reg := regex.regex_opt(r':[a-zA-Z_][a-zA-Z0-9_]*') or { return false, regex.RE{}, []string{} }
|
||||
all_params := param_reg.find_all_str(reg_path)
|
||||
for param in all_params {
|
||||
param_names << param[1..] // 去掉冒号
|
||||
}
|
||||
|
||||
// 转义特殊字符
|
||||
replaced_path = replaced_path.replace('?', r'\?')
|
||||
replaced_path = replaced_path.replace('+', r'\+')
|
||||
replaced_path = replaced_path.replace('.', r'\.')
|
||||
replaced_path = replaced_path.replace('(', r'\(')
|
||||
replaced_path = replaced_path.replace(')', r'\)')
|
||||
replaced_path = replaced_path.replace('[', r'\[')
|
||||
replaced_path = replaced_path.replace(']', r'\]')
|
||||
replaced_path = replaced_path.replace('{', r'\{')
|
||||
replaced_path = replaced_path.replace('}', r'\}')
|
||||
replaced_path = replaced_path.replace('^', r'\^')
|
||||
replaced_path = replaced_path.replace('$', r'\$')
|
||||
replaced_path = replaced_path.replace('|', r'\|')
|
||||
// 批量转义特殊字符(减少多次replace调用)
|
||||
special_chars := ['?', '+', '.', '(', ')', '[', ']', '{', '}', '^', '$', '|']
|
||||
for ch in special_chars {
|
||||
replaced_path = replaced_path.replace(ch, '\\${ch}')
|
||||
}
|
||||
|
||||
// 提取参数名并替换为命名捕获组
|
||||
mut pamam_reg := regex.regex_opt(r':[a-zA-Z_][a-zA-Z0-9_]*') or { return false, regex.RE{}, []string{} }
|
||||
replaced_path = pamam_reg.replace_by_fn(replaced_path, fn [mut param_names] (re regex.RE, in_txt string, start int, end int) string {
|
||||
// 替换参数为命名捕获组
|
||||
replaced_path = param_reg.replace_by_fn(replaced_path, fn (re regex.RE, in_txt string, start int, end int) string {
|
||||
param_name := in_txt[start+1..end]
|
||||
param_names << param_name
|
||||
return '(?P<' + param_name + '>[^/]+)'
|
||||
return '(?P<${param_name}>[^/]+)'
|
||||
})
|
||||
|
||||
// 添加结束锚点
|
||||
replaced_path = '^' + replaced_path + '$'
|
||||
// 添加锚点
|
||||
replaced_path = '^${replaced_path}$'
|
||||
|
||||
// 编译正则表达式
|
||||
mut reg := regex.regex_opt(replaced_path) or { return false, regex.RE{}, []string{} }
|
||||
|
||||
// 缓存编译结果
|
||||
compiled_regex = CompiledRegex{
|
||||
regex: reg
|
||||
param_names: param_names
|
||||
compiled: true
|
||||
}
|
||||
router.regex_cache[reg_path] = compiled_regex
|
||||
|
||||
return reg.matches_string(real_path), reg, param_names
|
||||
}
|
||||
|
||||
@@ -120,11 +156,51 @@ pub fn (mut router ContextHybridRouter) add_route(method string, handler IHandle
|
||||
router.static_routes['${method}:${full_path}'] = handler
|
||||
} else {
|
||||
router.dynamic_routes << handler
|
||||
// 按路由复杂度排序,简单路由优先匹配
|
||||
router.sort_dynamic_routes()
|
||||
}
|
||||
}
|
||||
|
||||
// 按路由复杂度排序动态路由(简单路由优先)
|
||||
fn (mut router ContextHybridRouter) sort_dynamic_routes() {
|
||||
router.dynamic_routes.sort_with_compare(fn (a &IHandler, b &IHandler) int {
|
||||
// 计算路由复杂度分数(分数越低越简单,优先匹配)
|
||||
score_a := calculate_route_complexity(a.path)
|
||||
score_b := calculate_route_complexity(b.path)
|
||||
|
||||
if score_a < score_b {
|
||||
return -1
|
||||
} else if score_a > score_b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
// 计算路由复杂度分数
|
||||
fn calculate_route_complexity(path string) int {
|
||||
mut score := 0
|
||||
|
||||
// 参数数量(每个参数+10分)
|
||||
score += path.count(':') * 10
|
||||
|
||||
// 通配符(每个通配符+20分)
|
||||
score += path.count('*') * 20
|
||||
|
||||
// 路径段数量(每个段+1分)
|
||||
score += path.split('/').len
|
||||
|
||||
// 特殊字符(每个+5分)
|
||||
special_chars := ['?', '+', '.', '(', ')', '[', ']', '{', '}', '^', '$', '|']
|
||||
for ch in special_chars {
|
||||
score += path.count(ch) * 5
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// Context 版本的静态路径匹配
|
||||
fn (router ContextHybridRouter) match_static_route(method string, path string) ?IHandler {
|
||||
pub fn (router ContextHybridRouter) match_static_route(method string, path string) ?IHandler {
|
||||
key := '${method}:${path}'
|
||||
if key in router.static_routes {
|
||||
return router.static_routes[key]
|
||||
@@ -141,15 +217,12 @@ fn (mut router ContextHybridRouter) match_dynamic_route(method string, path stri
|
||||
}
|
||||
|
||||
for handler in router.dynamic_routes {
|
||||
match_result, replaced_path_reg, _ := router.match_path_with_regex(path, handler.path)
|
||||
match_result, replaced_path_reg, param_names := router.match_path_with_regex(path, handler.path)
|
||||
if match_result {
|
||||
mut param_map := map[string]string{}
|
||||
|
||||
// 从原始路由路径中提取参数名
|
||||
mut pamam_reg := regex.regex_opt(r':\w+') or { panic(err) }
|
||||
all_params := pamam_reg.find_all_str(handler.path)
|
||||
for param in all_params {
|
||||
param_name := param[1..]
|
||||
// 直接使用已编译的正则表达式和缓存的参数名
|
||||
for param_name in param_names {
|
||||
group := replaced_path_reg.get_group_by_name(path, param_name)
|
||||
param_map[param_name] = group
|
||||
}
|
||||
@@ -209,4 +282,49 @@ pub fn (mut router ContextHybridRouter) clear_cache() {
|
||||
router.cache.clear()
|
||||
}
|
||||
|
||||
// 获取正则表达式缓存统计信息
|
||||
pub fn (router ContextHybridRouter) get_regex_cache_stats() (int, int) {
|
||||
mut compiled_count := 0
|
||||
for _, cached_regex in router.regex_cache {
|
||||
if cached_regex.compiled {
|
||||
compiled_count++
|
||||
}
|
||||
}
|
||||
return router.regex_cache.len, compiled_count
|
||||
}
|
||||
|
||||
// 清理正则表达式缓存
|
||||
pub fn (mut router ContextHybridRouter) clear_regex_cache() {
|
||||
router.regex_cache.clear()
|
||||
}
|
||||
|
||||
// 预热正则表达式缓存
|
||||
pub fn (mut router ContextHybridRouter) warmup_regex_cache() {
|
||||
println('[INFO] Warming up regex cache...')
|
||||
mut warmed_count := 0
|
||||
|
||||
for handler in router.dynamic_routes {
|
||||
if handler.path.contains(':') || handler.path.contains('*') {
|
||||
// 预编译动态路由的正则表达式
|
||||
router.match_path_with_regex('/dummy/path', handler.path)
|
||||
warmed_count++
|
||||
}
|
||||
}
|
||||
|
||||
total_cached, compiled_cached := router.get_regex_cache_stats()
|
||||
println('[INFO] Regex cache warmup completed: ${warmed_count} patterns warmed, ${compiled_cached}/${total_cached} compiled')
|
||||
}
|
||||
|
||||
// 路由性能分析
|
||||
pub fn (router ContextHybridRouter) analyze_router_performance() {
|
||||
cache_size, cache_hits := router.get_cache_stats()
|
||||
regex_total, regex_compiled := router.get_regex_cache_stats()
|
||||
|
||||
println('[PERFORMANCE] Router Analysis:')
|
||||
println(' Static Routes: ${router.static_routes.len}')
|
||||
println(' Dynamic Routes: ${router.dynamic_routes.len}')
|
||||
println(' Route Cache: ${cache_hits}/${cache_size} hits')
|
||||
println(' Regex Cache: ${regex_compiled}/${regex_total} compiled')
|
||||
}
|
||||
|
||||
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
module hono
|
||||
|
||||
import os
|
||||
|
||||
// 文件类型白名单
|
||||
const allowed_file_extensions = [
|
||||
// 文档类型
|
||||
'.txt', '.md', '.pdf', '.doc', '.docx',
|
||||
// 图片类型
|
||||
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.ico', '.webp',
|
||||
// 音视频类型
|
||||
'.mp3', '.mp4', '.wav', '.avi', '.mov', '.webm',
|
||||
// 压缩文件
|
||||
'.zip', '.rar', '.7z', '.tar', '.gz',
|
||||
// Web 文件
|
||||
'.html', '.htm', '.css', '.js', '.json', '.xml',
|
||||
// 字体文件
|
||||
'.ttf', '.otf', '.woff', '.woff2', '.eot'
|
||||
]
|
||||
|
||||
// 危险字符列表
|
||||
const dangerous_chars = ['<', '>', '"', '|', '?', '*', '\x00', '\r', '\n']
|
||||
|
||||
// 危险路径模式
|
||||
const dangerous_patterns = [
|
||||
'..', // 路径遍历
|
||||
'~', // 用户目录
|
||||
'$', // 环境变量
|
||||
'%', // Windows 环境变量
|
||||
'\\\\', // UNC 路径
|
||||
'/..', // Unix 路径遍历
|
||||
'\\..', // Windows 路径遍历
|
||||
'../', // 相对路径遍历
|
||||
'..\\', // Windows 相对路径遍历
|
||||
]
|
||||
|
||||
// 安全路径验证选项
|
||||
pub struct PathValidationOptions {
|
||||
pub:
|
||||
allow_absolute_paths bool // 是否允许绝对路径
|
||||
allow_hidden_files bool // 是否允许隐藏文件(以.开头)
|
||||
check_file_extension bool = true // 是否检查文件扩展名白名单
|
||||
max_path_length int = 260 // 最大路径长度(Windows限制)
|
||||
allowed_base_paths []string // 允许的基础路径列表
|
||||
custom_allowed_extensions []string // 自定义允许的扩展名
|
||||
}
|
||||
|
||||
// 默认路径验证选项
|
||||
pub fn default_path_validation_options() PathValidationOptions {
|
||||
return PathValidationOptions{}
|
||||
}
|
||||
|
||||
// 增强的路径安全检查
|
||||
pub fn validate_file_path(path string, options PathValidationOptions) !string {
|
||||
// 1. 基本检查
|
||||
if path.len == 0 {
|
||||
return error('Empty path not allowed')
|
||||
}
|
||||
|
||||
if path.len > options.max_path_length {
|
||||
return error('Path too long: ${path.len} > ${options.max_path_length}')
|
||||
}
|
||||
|
||||
// 2. 规范化路径
|
||||
clean_path := normalize_path(path)
|
||||
|
||||
// 3. 检查危险字符
|
||||
for dangerous_char in dangerous_chars {
|
||||
if clean_path.contains(dangerous_char) {
|
||||
return error('Dangerous character detected: ${dangerous_char}')
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 检查危险模式
|
||||
for pattern in dangerous_patterns {
|
||||
if clean_path.contains(pattern) {
|
||||
return error('Dangerous path pattern detected: ${pattern}')
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 检查绝对路径
|
||||
if !options.allow_absolute_paths {
|
||||
if is_absolute_path(clean_path) {
|
||||
return error('Absolute paths not allowed')
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 检查隐藏文件
|
||||
if !options.allow_hidden_files {
|
||||
if is_hidden_file(clean_path) {
|
||||
return error('Hidden files not allowed')
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 检查文件扩展名
|
||||
if options.check_file_extension {
|
||||
if !is_allowed_file_type(clean_path, options.custom_allowed_extensions) {
|
||||
return error('File type not allowed')
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 检查基础路径限制
|
||||
if options.allowed_base_paths.len > 0 {
|
||||
if !is_within_allowed_paths(clean_path, options.allowed_base_paths) {
|
||||
return error('Path outside allowed directories')
|
||||
}
|
||||
}
|
||||
|
||||
return clean_path
|
||||
}
|
||||
|
||||
// 路径规范化
|
||||
fn normalize_path(path string) string {
|
||||
// 移除多余的空白字符
|
||||
mut clean_path := path.trim_space()
|
||||
|
||||
// 统一路径分隔符为 /
|
||||
clean_path = clean_path.replace('\\', '/')
|
||||
|
||||
// 移除重复的路径分隔符
|
||||
for clean_path.contains('//') {
|
||||
clean_path = clean_path.replace('//', '/')
|
||||
}
|
||||
|
||||
// 移除末尾的路径分隔符
|
||||
if clean_path.ends_with('/') && clean_path.len > 1 {
|
||||
clean_path = clean_path[..clean_path.len - 1]
|
||||
}
|
||||
|
||||
return clean_path
|
||||
}
|
||||
|
||||
// 检查是否为绝对路径
|
||||
fn is_absolute_path(path string) bool {
|
||||
// Unix/Linux 绝对路径
|
||||
if path.starts_with('/') {
|
||||
return true
|
||||
}
|
||||
|
||||
// Windows 绝对路径 (C:, D: 等)
|
||||
if path.len >= 2 && path[1] == `:` {
|
||||
return true
|
||||
}
|
||||
|
||||
// UNC 路径 (\\server\share)
|
||||
if path.starts_with('//') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否为隐藏文件
|
||||
fn is_hidden_file(path string) bool {
|
||||
// 获取文件名部分
|
||||
parts := path.split('/')
|
||||
if parts.len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
filename := parts.last()
|
||||
return filename.starts_with('.') && filename != '.' && filename != '..'
|
||||
}
|
||||
|
||||
// 检查文件类型是否被允许
|
||||
fn is_allowed_file_type(path string, custom_extensions []string) bool {
|
||||
ext := os.file_ext(path).to_lower()
|
||||
|
||||
// 如果没有扩展名,拒绝
|
||||
if ext == '' {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查自定义扩展名列表
|
||||
if custom_extensions.len > 0 {
|
||||
return ext in custom_extensions
|
||||
}
|
||||
|
||||
// 检查默认白名单
|
||||
return ext in allowed_file_extensions
|
||||
}
|
||||
|
||||
// 检查路径是否在允许的基础路径内
|
||||
fn is_within_allowed_paths(path string, allowed_paths []string) bool {
|
||||
for allowed_path in allowed_paths {
|
||||
normalized_allowed := normalize_path(allowed_path)
|
||||
if path.starts_with(normalized_allowed) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 便捷函数:基本路径安全检查(向后兼容)
|
||||
pub fn is_safe_path(path string) bool {
|
||||
result := validate_file_path(path, default_path_validation_options()) or { return false }
|
||||
return result.len > 0
|
||||
}
|
||||
|
||||
// 便捷函数:文件路径安全检查(向后兼容)
|
||||
pub fn is_safe_file_path(path string) bool {
|
||||
return is_safe_path(path)
|
||||
}
|
||||
|
||||
// 输入验证:文件哈希
|
||||
pub fn validate_file_hash(hash string) !string {
|
||||
if hash.len == 0 {
|
||||
return error('File hash cannot be empty')
|
||||
}
|
||||
|
||||
// MD5 哈希长度检查
|
||||
if hash.len != 32 {
|
||||
return error('Invalid hash length: expected 32, got ${hash.len}')
|
||||
}
|
||||
|
||||
// 检查是否只包含十六进制字符
|
||||
for ch in hash {
|
||||
if !ch.is_hex_digit() {
|
||||
return error('Invalid hash character: ${ch}')
|
||||
}
|
||||
}
|
||||
|
||||
return hash.to_lower()
|
||||
}
|
||||
|
||||
// 输入验证:分片索引
|
||||
pub fn validate_chunk_index(index_str string, max_chunks int) !int {
|
||||
if index_str.len == 0 {
|
||||
return error('Chunk index cannot be empty')
|
||||
}
|
||||
|
||||
// 检查是否只包含数字
|
||||
for ch in index_str {
|
||||
if !ch.is_digit() {
|
||||
return error('Chunk index must be a number')
|
||||
}
|
||||
}
|
||||
|
||||
index := index_str.int()
|
||||
|
||||
if index < 0 {
|
||||
return error('Chunk index cannot be negative')
|
||||
}
|
||||
|
||||
if max_chunks > 0 && index >= max_chunks {
|
||||
return error('Chunk index ${index} exceeds maximum ${max_chunks}')
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
// 输入验证:文件大小
|
||||
pub fn validate_file_size(size_str string, max_size int) !int {
|
||||
if size_str.len == 0 {
|
||||
return error('File size cannot be empty')
|
||||
}
|
||||
|
||||
// 检查是否只包含数字
|
||||
for ch in size_str {
|
||||
if !ch.is_digit() {
|
||||
return error('File size must be a number')
|
||||
}
|
||||
}
|
||||
|
||||
size := size_str.int()
|
||||
|
||||
if size <= 0 {
|
||||
return error('File size must be positive')
|
||||
}
|
||||
|
||||
if size > max_size {
|
||||
return error('File size ${size} exceeds maximum ${max_size}')
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
// 输入验证:文件名
|
||||
pub fn validate_filename(filename string) !string {
|
||||
if filename.len == 0 {
|
||||
return error('Filename cannot be empty')
|
||||
}
|
||||
|
||||
if filename.len > 255 {
|
||||
return error('Filename too long: ${filename.len} > 255')
|
||||
}
|
||||
|
||||
// 检查危险字符
|
||||
for dangerous_char in dangerous_chars {
|
||||
if filename.contains(dangerous_char) {
|
||||
return error('Dangerous character in filename: ${dangerous_char}')
|
||||
}
|
||||
}
|
||||
|
||||
// 检查保留名称 (Windows)
|
||||
reserved_names := ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4',
|
||||
'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2',
|
||||
'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']
|
||||
|
||||
name_without_ext := filename.split('.')[0].to_upper()
|
||||
if name_without_ext in reserved_names {
|
||||
return error('Reserved filename not allowed: ${filename}')
|
||||
}
|
||||
|
||||
return filename
|
||||
}
|
||||
|
||||
// 安全的内容类型检测
|
||||
pub fn get_safe_content_type(file_path string) string {
|
||||
ext := os.file_ext(file_path).to_lower()
|
||||
|
||||
// 只返回已知安全的内容类型
|
||||
match ext {
|
||||
'.html', '.htm' { return 'text/html; charset=utf-8' }
|
||||
'.css' { return 'text/css; charset=utf-8' }
|
||||
'.js' { return 'application/javascript; charset=utf-8' }
|
||||
'.json' { return 'application/json; charset=utf-8' }
|
||||
'.xml' { return 'application/xml; charset=utf-8' }
|
||||
'.txt' { return 'text/plain; charset=utf-8' }
|
||||
'.md' { return 'text/markdown; charset=utf-8' }
|
||||
'.pdf' { return 'application/pdf' }
|
||||
'.png' { return 'image/png' }
|
||||
'.jpg', '.jpeg' { return 'image/jpeg' }
|
||||
'.gif' { return 'image/gif' }
|
||||
'.svg' { return 'image/svg+xml' }
|
||||
'.ico' { return 'image/x-icon' }
|
||||
'.woff' { return 'font/woff' }
|
||||
'.woff2' { return 'font/woff2' }
|
||||
'.ttf' { return 'font/ttf' }
|
||||
'.eot' { return 'application/vnd.ms-fontobject' }
|
||||
'.otf' { return 'font/otf' }
|
||||
'.mp4' { return 'video/mp4' }
|
||||
'.webm' { return 'video/webm' }
|
||||
'.mp3' { return 'audio/mpeg' }
|
||||
'.wav' { return 'audio/wav' }
|
||||
'.zip' { return 'application/zip' }
|
||||
'.tar' { return 'application/x-tar' }
|
||||
'.gz' { return 'application/gzip' }
|
||||
else {
|
||||
// 对于未知类型,返回安全的默认类型
|
||||
return 'application/octet-stream'
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-22
@@ -55,8 +55,15 @@ pub fn serve_static(options StaticOptions) fn (mut Context, fn (mut Context) htt
|
||||
println(' Root: ${options.root}')
|
||||
|
||||
// 安全检查:防止路径遍历攻击
|
||||
if !is_safe_path(file_path) {
|
||||
println(' [DEBUG] Path traversal attack detected')
|
||||
validation_options := PathValidationOptions{
|
||||
allow_absolute_paths: false
|
||||
allow_hidden_files: options.dotfiles
|
||||
check_file_extension: true
|
||||
allowed_base_paths: [options.root]
|
||||
}
|
||||
|
||||
safe_file_path := validate_file_path(file_path, validation_options) or {
|
||||
println(' [DEBUG] Path validation failed: $err')
|
||||
c.status(403)
|
||||
return c.text('Forbidden')
|
||||
}
|
||||
@@ -69,7 +76,7 @@ pub fn serve_static(options StaticOptions) fn (mut Context, fn (mut Context) htt
|
||||
}
|
||||
|
||||
// 构建完整的文件路径
|
||||
full_path := os.join_path(options.root, file_path)
|
||||
full_path := os.join_path(options.root, safe_file_path)
|
||||
println(' Full path: ${full_path}')
|
||||
|
||||
// 检查文件是否存在
|
||||
@@ -115,7 +122,7 @@ fn serve_file(mut c Context, file_path string, options StaticOptions) http.Respo
|
||||
c.status(200)
|
||||
|
||||
// 设置Content-Type
|
||||
content_type := get_content_type(file_path)
|
||||
content_type := get_safe_content_type(file_path)
|
||||
c.headers['Content-Type'] = content_type
|
||||
|
||||
// 设置Content-Length
|
||||
@@ -173,24 +180,6 @@ fn serve_file(mut c Context, file_path string, options StaticOptions) http.Respo
|
||||
}
|
||||
}
|
||||
|
||||
// 安全检查:防止路径遍历攻击
|
||||
fn is_safe_path(path string) bool {
|
||||
// 检查是否包含 .. 或绝对路径
|
||||
if path.contains('..') || path.starts_with('/') || path.starts_with('\\') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否包含危险字符
|
||||
dangerous_chars := ['<', '>', ':', '"', '|', '?', '*']
|
||||
for dangerous_char in dangerous_chars {
|
||||
if path.contains(dangerous_char) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 根据文件扩展名获取Content-Type
|
||||
fn get_content_type(file_path string) string {
|
||||
ext := os.file_ext(file_path).to_lower()
|
||||
|
||||
+3
-3
@@ -145,16 +145,16 @@ fn (tr ContextTrieRouter) collect_routes(node &ContextTrieNode, current_path str
|
||||
|
||||
// 收集静态子节点
|
||||
for segment, child in node.children {
|
||||
tr.collect_routes(child, current_path + '/' + segment, mut routes)
|
||||
tr.collect_routes(child, '${current_path}/${segment}', mut routes)
|
||||
}
|
||||
|
||||
// 收集参数子节点
|
||||
if node.param_child != unsafe { nil } {
|
||||
tr.collect_routes(node.param_child, current_path + '/:' + node.param_child.param_name, mut routes)
|
||||
tr.collect_routes(node.param_child, '${current_path}/:${node.param_child.param_name}', mut routes)
|
||||
}
|
||||
|
||||
// 收集通配符子节点
|
||||
if node.wildcard_child != unsafe { nil } {
|
||||
tr.collect_routes(node.wildcard_child, current_path + '/*', mut routes)
|
||||
tr.collect_routes(node.wildcard_child, '${current_path}/*', mut routes)
|
||||
}
|
||||
}
|
||||
+77
-67
@@ -17,6 +17,7 @@ pub:
|
||||
cleanup_delay int = 3600 // 1小时后清理临时文件
|
||||
clear_chunks_on_complete bool // 上传完成后是否清空分片,默认不清空
|
||||
db_path string = './uploads/files.db' // 数据库文件路径
|
||||
merge_buffer_size int = 8192 // 文件合并时的缓冲区大小(8KB)
|
||||
}
|
||||
|
||||
// 分片信息
|
||||
@@ -74,65 +75,73 @@ pub fn new_chunk_upload_manager(config ChunkUploadConfig) ChunkUploadManager {
|
||||
pub fn (mut manager ChunkUploadManager) handle_chunk_upload(mut ctx Context) http.Response {
|
||||
// 解析 multipart 表单数据
|
||||
form_data := hono.parse_multipart_form(ctx.req) or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Invalid form data", "message": "$err"}')
|
||||
return ctx.bad_request('Invalid form data: ${err}')
|
||||
}
|
||||
|
||||
// 获取必要参数
|
||||
file_hash := form_data.get('file_hash') or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Missing file_hash"}')
|
||||
// 获取并验证必要参数
|
||||
file_hash_raw := form_data.get('file_hash') or {
|
||||
return ctx.missing_parameter('file_hash')
|
||||
}
|
||||
|
||||
// 验证文件哈希
|
||||
file_hash := validate_file_hash(file_hash_raw) or {
|
||||
return ctx.invalid_parameter('file_hash', err.msg())
|
||||
}
|
||||
|
||||
chunk_index_str := form_data.get('chunk_index') or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Missing chunk_index"}')
|
||||
return ctx.missing_parameter('chunk_index')
|
||||
}
|
||||
|
||||
filename := form_data.get('filename') or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Missing filename"}')
|
||||
filename_raw := form_data.get('filename') or {
|
||||
return ctx.missing_parameter('filename')
|
||||
}
|
||||
|
||||
// 验证文件名
|
||||
filename := validate_filename(filename_raw) or {
|
||||
return ctx.invalid_parameter('filename', err.msg())
|
||||
}
|
||||
|
||||
file_size_str := form_data.get('file_size') or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Missing file_size"}')
|
||||
return ctx.missing_parameter('file_size')
|
||||
}
|
||||
|
||||
// 解析数值
|
||||
chunk_index := chunk_index_str.int()
|
||||
file_size := file_size_str.int()
|
||||
|
||||
// 验证文件大小
|
||||
if file_size > manager.config.max_file_size {
|
||||
ctx.status(413)
|
||||
return ctx.json('{"error": "File too large", "max_size": "${manager.config.max_file_size}"}')
|
||||
file_size := validate_file_size(file_size_str, manager.config.max_file_size) or {
|
||||
return ctx.invalid_parameter('file_size', err.msg())
|
||||
}
|
||||
|
||||
// 获取前端传递的分片大小参数
|
||||
chunk_size_str := form_data.get('chunk_size') or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Missing chunk_size"}')
|
||||
return ctx.missing_parameter('chunk_size')
|
||||
}
|
||||
chunk_size := chunk_size_str.int()
|
||||
|
||||
// 验证分片大小(使用配置中的最大分片大小)
|
||||
if chunk_size > manager.config.max_chunk_size {
|
||||
ctx.status(413)
|
||||
return ctx.json('{"error": "Chunk size too large", "max_chunk_size": "${manager.config.max_chunk_size}"}')
|
||||
// 验证分片大小
|
||||
chunk_size := validate_file_size(chunk_size_str, manager.config.max_chunk_size) or {
|
||||
return ctx.invalid_parameter('chunk_size', err.msg())
|
||||
}
|
||||
|
||||
// 验证分片索引
|
||||
chunk_index := validate_chunk_index(chunk_index_str, 0) or { // 0 表示不限制最大值
|
||||
return ctx.invalid_parameter('chunk_index', err.msg())
|
||||
}
|
||||
|
||||
// 获取文件数据
|
||||
file_data := form_data.get_file('chunk') or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Missing chunk file"}')
|
||||
return ctx.missing_parameter('chunk')
|
||||
}
|
||||
|
||||
// 验证分片数据大小
|
||||
if file_data.len > chunk_size {
|
||||
return ctx.validation_error('Chunk data size exceeds declared chunk_size', {
|
||||
'actual_size': file_data.len.str()
|
||||
'declared_size': chunk_size.str()
|
||||
})
|
||||
}
|
||||
|
||||
// 创建按文件hash和分片大小分组的目录
|
||||
chunk_dir := os.join_path(manager.config.temp_dir, file_hash.trim_space(), chunk_size.str())
|
||||
chunk_dir := os.join_path(manager.config.temp_dir, file_hash, chunk_size.str())
|
||||
os.mkdir_all(chunk_dir) or {
|
||||
ctx.status(500)
|
||||
return ctx.json('{"error": "Failed to create chunk directory"}')
|
||||
return ctx.file_operation_error('create_directory', chunk_dir, err.msg())
|
||||
}
|
||||
|
||||
// 保存分片文件到hash/chunksize子目录
|
||||
@@ -143,8 +152,7 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_upload(mut ctx Context) htt
|
||||
|
||||
os.write_file(chunk_path, file_data) or {
|
||||
println('[DEBUG] Failed to save chunk: $err')
|
||||
ctx.status(500)
|
||||
return ctx.json('{"error": "Failed to save chunk"}')
|
||||
return ctx.file_operation_error('save_chunk', chunk_path, err.msg())
|
||||
}
|
||||
|
||||
// 更新上传状态
|
||||
@@ -155,7 +163,7 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_upload(mut ctx Context) htt
|
||||
// 判断是否所有分片都已上传,自动合并
|
||||
// 使用记录文件来避免遍历分片文件计算总大小
|
||||
mut all_chunk_uploaded := false
|
||||
merge_chunk_dir := os.join_path(manager.config.temp_dir, file_hash.trim_space(), chunk_size.str())
|
||||
merge_chunk_dir := os.join_path(manager.config.temp_dir, file_hash, chunk_size.str())
|
||||
|
||||
if os.exists(merge_chunk_dir) {
|
||||
// 更新已上传分片的总大小记录
|
||||
@@ -181,7 +189,7 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_upload(mut ctx Context) htt
|
||||
|
||||
// 获取文件扩展名
|
||||
file_ext := get_file_extension(filename)
|
||||
final_filename := '${file_hash.trim_space()}${file_ext}'
|
||||
final_filename := '${file_hash}${file_ext}'
|
||||
final_path := os.join_path(manager.config.upload_dir, final_filename)
|
||||
|
||||
// 检查最终文件是否已经存在,避免重复合并
|
||||
@@ -194,8 +202,7 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_upload(mut ctx Context) htt
|
||||
|
||||
manager.merge_chunks(file_hash, actual_total_chunks, final_path, chunk_size) or {
|
||||
println('[DEBUG] Merge failed: $err')
|
||||
ctx.status(500)
|
||||
return ctx.json('{"error": "Failed to merge chunks", "message": "$err"}')
|
||||
return ctx.file_operation_error('merge_chunks', final_path, err.msg())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,8 +231,7 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_upload(mut ctx Context) htt
|
||||
pub fn (mut manager ChunkUploadManager) handle_chunk_merge(mut ctx Context) http.Response {
|
||||
// 解析请求体
|
||||
merge_request := hono.parse_merge_request(ctx.body) or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Invalid request body", "message": "$err"}')
|
||||
return ctx.bad_request('Invalid request body: ${err}')
|
||||
}
|
||||
|
||||
file_hash := merge_request.file_hash
|
||||
@@ -234,14 +240,15 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_merge(mut ctx Context) http
|
||||
|
||||
// 检查上传状态
|
||||
upload_status := manager.uploads[file_hash] or {
|
||||
ctx.status(404)
|
||||
return ctx.json('{"error": "Upload not found"}')
|
||||
return ctx.resource_not_found('upload', file_hash)
|
||||
}
|
||||
|
||||
// 验证所有分片是否上传完成
|
||||
if upload_status.uploaded_chunks.len != total_chunks {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Not all chunks uploaded", "uploaded": ${upload_status.uploaded_chunks.len}, "total": $total_chunks}')
|
||||
return ctx.validation_error('Not all chunks uploaded', {
|
||||
'uploaded': upload_status.uploaded_chunks.len.str()
|
||||
'total': total_chunks.str()
|
||||
})
|
||||
}
|
||||
|
||||
// 合并文件
|
||||
@@ -252,8 +259,7 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_merge(mut ctx Context) http
|
||||
// 从上传状态中获取分片大小
|
||||
chunk_size := upload_status.chunk_size
|
||||
manager.merge_chunks(file_hash, total_chunks, final_path, chunk_size) or {
|
||||
ctx.status(500)
|
||||
return ctx.json('{"error": "Failed to merge chunks", "message": "$err"}')
|
||||
return ctx.file_operation_error('merge_chunks', final_path, err.msg())
|
||||
}
|
||||
|
||||
// 在数据库中记录文件信息
|
||||
@@ -278,13 +284,11 @@ pub fn (mut manager ChunkUploadManager) handle_chunk_merge(mut ctx Context) http
|
||||
// 获取上传状态
|
||||
pub fn (manager ChunkUploadManager) get_upload_status(mut ctx Context) http.Response {
|
||||
file_hash := ctx.query['file_hash'] or {
|
||||
ctx.status(400)
|
||||
return ctx.json('{"error": "Missing file_hash parameter"}')
|
||||
return ctx.missing_parameter('file_hash')
|
||||
}
|
||||
|
||||
upload_status := manager.uploads[file_hash] or {
|
||||
ctx.status(404)
|
||||
return ctx.json('{"error": "Upload not found"}')
|
||||
return ctx.resource_not_found('upload', file_hash)
|
||||
}
|
||||
|
||||
return ctx.json(json.encode(upload_status))
|
||||
@@ -316,7 +320,7 @@ pub fn (mut manager ChunkUploadManager) update_upload_status(file_hash string, f
|
||||
manager.uploads[file_hash].updated_at = now
|
||||
}
|
||||
|
||||
// 合并分片
|
||||
// 合并分片 - 流式版本,减少内存占用
|
||||
fn (mut manager ChunkUploadManager) merge_chunks(file_hash string, total_chunks int, final_path string, chunk_size int) ! {
|
||||
println('[DEBUG] Merge chunks called with:')
|
||||
println('[DEBUG] file_hash: $file_hash')
|
||||
@@ -336,11 +340,6 @@ fn (mut manager ChunkUploadManager) merge_chunks(file_hash string, total_chunks
|
||||
}
|
||||
|
||||
println('[DEBUG] Creating final file: $final_path')
|
||||
println('[DEBUG] Final path length: ${final_path.len}')
|
||||
println('[DEBUG] Final path bytes: ${final_path.bytes()}')
|
||||
println('[DEBUG] Final path exists: ${os.exists(final_path)}')
|
||||
println('[DEBUG] Final path is file: ${os.is_file(final_path)}')
|
||||
println('[DEBUG] Final path is dir: ${os.is_dir(final_path)}')
|
||||
|
||||
// 如果文件已存在,直接返回成功
|
||||
if os.exists(final_path) {
|
||||
@@ -348,12 +347,8 @@ fn (mut manager ChunkUploadManager) merge_chunks(file_hash string, total_chunks
|
||||
return
|
||||
}
|
||||
|
||||
// Try to create the file with explicit path cleaning
|
||||
// 清理路径并创建最终文件
|
||||
clean_path := final_path.trim_space()
|
||||
println('[DEBUG] Clean path: "$clean_path"')
|
||||
println('[DEBUG] Clean path length: ${clean_path.len}')
|
||||
|
||||
// Try to get absolute path
|
||||
abs_path := os.abs_path(clean_path)
|
||||
println('[DEBUG] Absolute path: "$abs_path"')
|
||||
|
||||
@@ -363,23 +358,38 @@ fn (mut manager ChunkUploadManager) merge_chunks(file_hash string, total_chunks
|
||||
}
|
||||
defer { final_file.close() }
|
||||
|
||||
// 流式合并分片,使用可配置的缓冲区大小
|
||||
buffer_size := manager.config.merge_buffer_size
|
||||
mut buffer := []u8{len: buffer_size}
|
||||
|
||||
for i in 0 .. total_chunks {
|
||||
chunk_path := os.join_path(manager.config.temp_dir, file_hash.trim_space(), chunk_size.str(), 'chunk_${i}.part')
|
||||
println('[DEBUG] Reading chunk: $chunk_path')
|
||||
println('[DEBUG] Processing chunk: $chunk_path')
|
||||
|
||||
if !os.exists(chunk_path) {
|
||||
return error('Chunk file not found: $chunk_path')
|
||||
}
|
||||
|
||||
chunk_data := os.read_file(chunk_path) or {
|
||||
return error('Failed to read chunk $i: $err')
|
||||
// 流式读取和写入分片文件
|
||||
mut chunk_file := os.open(chunk_path) or {
|
||||
return error('Failed to open chunk $i: $err')
|
||||
}
|
||||
|
||||
final_file.write(chunk_data.bytes()) or {
|
||||
return error('Failed to write chunk $i: $err')
|
||||
mut bytes_copied := 0
|
||||
for {
|
||||
bytes_read := chunk_file.read(mut buffer) or { break }
|
||||
if bytes_read == 0 { break }
|
||||
|
||||
final_file.write(buffer[..bytes_read]) or {
|
||||
chunk_file.close()
|
||||
return error('Failed to write chunk $i data: $err')
|
||||
}
|
||||
|
||||
bytes_copied += bytes_read
|
||||
}
|
||||
|
||||
println('[DEBUG] Chunk $i merged successfully, size: ${chunk_data.len} bytes')
|
||||
chunk_file.close()
|
||||
println('[DEBUG] Chunk $i merged successfully, size: ${bytes_copied} bytes')
|
||||
}
|
||||
|
||||
println('[DEBUG] All chunks merged successfully to: $final_path')
|
||||
|
||||
Reference in New Issue
Block a user