perf: 重构优化路由性能,添加日志系统,优化大文件内存占用,加强输入验证及路径安全等

This commit is contained in:
meiseayoung
2025-12-26 13:46:25 +08:00
parent f722370cf3
commit 3355e274e2
67 changed files with 18527 additions and 963 deletions
+157 -29
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+228
View File
@@ -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
}
}
+433
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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')