Files
v-hono/example.v
T

857 lines
28 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import net.http
import hono
import os
import json
fn main() {
// ===== 1. 应用初始化 =====
mut app := hono.Hono.new()
// 创建分片上传管理器
mut upload_manager := hono.new_chunk_upload_manager(hono.ChunkUploadConfig{})
// ===== 2. 中间件配置 =====
// 日志中间件
app.use(fn (mut c hono.Context, next fn (mut hono.Context) http.Response) http.Response {
println('[LOG] ${c.req.method} ${c.path}')
return next(mut c)
})
// 认证中间件
app.use(fn (mut c hono.Context, next fn (mut hono.Context) http.Response) http.Response {
println("url path: ${c.path}")
token := c.query['token']
if c.path != "/" && token == '' {
c.status(401)
return c.json('{"error": "Unauthorized", "message": "Token required"}')
}
return next(mut c)
})
// ===== 3. 静态文件服务中间件 =====
// 默认静态文件服务(./public 目录)
app.use(hono.serve_static_default())
// 资源文件服务(./static 目录)
app.use(hono.serve_static_path('/assets', './static'))
// 上传文件服务(./uploads 目录)
options := hono.StaticOptions{
root: './uploads'
path: '/files'
index: 'index.html'
dotfiles: false
etag: true
last_modified: true
max_age: 3600 // 1小时缓存
headers: {
'X-Served-By': 'V-Hono'
'X-Static-Files': 'true'
}
}
app.use(hono.serve_static(options))
// ===== 4. 分块上传功能 =====
// 分片上传接口
app.post('/upload/chunk', fn [mut upload_manager] (mut c hono.Context) http.Response {
return upload_manager.handle_chunk_upload(mut c)
})
// 查询上传状态接口
app.get('/upload/status', fn [mut upload_manager] (mut c hono.Context) http.Response {
return upload_manager.get_upload_status(mut c)
})
// 查询已上传分片接口
app.get('/upload/chunks', fn [mut upload_manager] (mut c hono.Context) http.Response {
file_hash := c.query['file_hash'] or {
c.status(400)
return c.json('{"error": "Missing file_hash parameter"}')
}
upload_status := upload_manager.uploads[file_hash] or {
// 如果内存中没有记录,检查是否有最终文件(已完成上传)
final_path := './uploads/${file_hash.trim_space()}'
if os.exists(final_path) {
// 文件已存在,返回所有分片已上传
mut uploaded_chunks := []int{}
uploaded_chunks << 0 // 假设只有一个分片
return c.json('{"uploaded_chunks": ' + json.encode(uploaded_chunks) + ', "total_chunks": 1, "completed": true}')
}
c.status(404)
return c.json('{"error": "Upload not found"}')
}
// 检查是否所有分片都已上传(使用分片大小记录文件)
chunk_dir := os.join_path(os.join_path('./uploads/chunks', file_hash.trim_space()), upload_status.chunk_size.str())
mut total_chunk_size := u64(0)
mut chunk_count := upload_status.uploaded_chunks.len
if os.exists(chunk_dir) {
// 读取分片大小记录文件
size_record_path := os.join_path(chunk_dir, 'total_size.record')
if os.exists(size_record_path) {
size_data := os.read_file(size_record_path) or { '0' }
total_chunk_size = size_data.u64()
}
}
is_completed := total_chunk_size >= u64(upload_status.file_size)
return c.json('{"uploaded_chunks": ' + json.encode(upload_status.uploaded_chunks) + ', "total_chunks": $chunk_count, "completed": $is_completed}')
})
// 检查分片是否存在接口(秒传功能)
app.get('/upload/chunk_exists', fn [mut upload_manager] (mut c hono.Context) http.Response {
mut chunk_path := ''
file_hash := c.query['file_hash'] or {
c.status(400)
return c.json('{"error": "Missing file_hash parameter"}')
}
chunk_index := c.query['chunk_index'] or {
c.status(400)
return c.json('{"error": "Missing chunk_index parameter"}')
}
chunk_hash := c.query['chunk_hash'] or {
c.status(400)
return c.json('{"error": "Missing chunk_hash parameter"}')
}
file_size_str := c.query['file_size'] or {
c.status(400)
return c.json('{"error": "Missing file_size parameter"}')
}
file_size := file_size_str.int()
// 新增 trunk_size 参数
trunk_size_str := c.query['trunk_size'] or { '' }
mut chunk_size := upload_manager.config.chunk_size
if trunk_size_str != '' {
chunk_size = trunk_size_str.int()
} else if upload_status := upload_manager.uploads[file_hash] {
chunk_size = upload_status.chunk_size
}
chunk_path = os.join_path(os.join_path(os.join_path('./uploads/chunks', file_hash.trim_space()), chunk_size.str()), 'chunk_${chunk_index}.part')
println('[DEBUG] Checking chunk: $chunk_path')
println('[DEBUG] File hash: $file_hash')
println('[DEBUG] Chunk index: $chunk_index')
println('[DEBUG] Expected hash: $chunk_hash')
// 清理无效的上传状态
upload_manager.cleanup_invalid_status()
mut exists := os.exists(chunk_path)
println('[DEBUG] File exists: $exists')
// 计算是否所有分片都已上传
mut all_chunk_uploaded := false
// 方案1:优先检查最终文件是否存在(新格式:filehash.filetype)
uploads_dir := './uploads/files'
if os.exists(uploads_dir) {
// 从上传状态中获取文件扩展名
mut file_ext := ''
if upload_status := upload_manager.uploads[file_hash] {
file_ext = get_file_extension(upload_status.filename)
}
// 如果内存中没有记录,尝试从数据库获取
if file_ext == '' {
if file_info := upload_manager.db.get_file_by_hash(file_hash) {
file_ext = file_info.file_type
}
}
// 构建最终文件名
final_filename := '${file_hash.trim_space()}${file_ext}'
final_path := os.join_path(uploads_dir, final_filename)
if os.exists(final_path) {
println('[DEBUG] Found final file: $final_path')
all_chunk_uploaded = true
}
}
// 方案2:如果最终文件不存在,检查分片文件
if !all_chunk_uploaded {
chunk_dir := os.join_path(os.join_path('./uploads/chunks', file_hash.trim_space()), chunk_size.str())
if os.exists(chunk_dir) {
// 使用分片大小记录文件,避免遍历
mut total_chunk_size := u64(0)
mut chunk_count := 0
// 读取分片大小记录文件
size_record_path := os.join_path(chunk_dir, 'total_size.record')
if os.exists(size_record_path) {
size_data := os.read_file(size_record_path) or { '0' }
total_chunk_size = size_data.u64()
}
// 从内存中的上传状态获取分片数量
if upload_status := upload_manager.uploads[file_hash] {
chunk_count = upload_status.uploaded_chunks.len
} else {
// 如果内存中没有状态,遍历分片文件来计算实际分片数量
mut actual_chunk_count := 0
for i := 0; ; i++ {
chunk_path = os.join_path(chunk_dir, 'chunk_${i}.part')
if os.exists(chunk_path) {
actual_chunk_count++
} else {
break
}
}
chunk_count = actual_chunk_count
}
// 使用传入的文件大小
expected_file_size := u64(file_size)
println('[DEBUG] Total chunk size: $total_chunk_size, Expected file size: $expected_file_size, Chunk count: $chunk_count')
// 计算理论上需要的分片数量
expected_chunks := (expected_file_size + u64(chunk_size) - 1) / u64(chunk_size)
println('[DEBUG] Expected chunks: $expected_chunks')
// 只有当分片数量达到预期且总大小 >= 文件大小时,才认为上传完成
if chunk_count >= int(expected_chunks) && total_chunk_size >= expected_file_size {
all_chunk_uploaded = true
println('[DEBUG] All chunks uploaded: chunk_count=$chunk_count >= expected_chunks=$expected_chunks && total_size=$total_chunk_size >= file_size=$expected_file_size')
} else {
println('[DEBUG] Not all chunks uploaded: chunk_count=$chunk_count < expected_chunks=$expected_chunks || total_size=$total_chunk_size < file_size=$expected_file_size')
}
}
}
println('[DEBUG] All chunk uploaded: $all_chunk_uploaded')
// chunk_exists 接口不应该自动触发合并,只返回状态信息
// 合并操作应该在 /upload/chunk 接口中处理
// 只检查文件是否存在,不验证 hash(因为前后端 hash 算法可能不一致)
// 如果需要严格验证,可以在这里添加 hash 检查逻辑
// 返回时字段改回 all_chunk_uploaded
return c.json('{"exists": $exists, "all_chunk_uploaded": $all_chunk_uploaded}')
})
// 获取所有文件信息
app.get('/api/files', fn [mut upload_manager] (mut c hono.Context) http.Response {
files := upload_manager.db.get_all_files() or {
c.status(500)
return c.json('{"error": "Failed to get files", "message": "$err"}')
}
return c.json(json.encode(files))
})
// 根据文件UUID获取文件信息
app.get('/api/files/:uuid', fn [mut upload_manager] (mut c hono.Context) http.Response {
file_uuid := c.params['uuid'] or {
c.status(400)
return c.json('{"error": "Missing file UUID"}')
}
file_info := upload_manager.db.get_file_by_uuid(file_uuid) or {
c.status(404)
return c.json('{"error": "File not found"}')
}
return c.json(json.encode(file_info))
})
// 根据文件hash获取文件信息
app.get('/api/files/hash/:hash', fn [mut upload_manager] (mut c hono.Context) http.Response {
file_hash := c.params['hash'] or {
c.status(400)
return c.json('{"error": "Missing file hash"}')
}
file_info := upload_manager.db.get_file_by_hash(file_hash) or {
c.status(404)
return c.json('{"error": "File not found"}')
}
return c.json(json.encode(file_info))
})
// 删除文件信息
app.delete('/api/files/:uuid', fn [mut upload_manager] (mut c hono.Context) http.Response {
file_uuid := c.params['uuid'] or {
c.status(400)
return c.json('{"error": "Missing file UUID"}')
}
upload_manager.db.delete_file(file_uuid) or {
c.status(500)
return c.json('{"error": "Failed to delete file", "message": "$err"}')
}
return c.json('{"success": true, "message": "File deleted successfully"}')
})
// 获取上传配置信息
app.get('/api/upload-config', fn [mut upload_manager] (mut c hono.Context) http.Response {
config := {
'chunk_size': upload_manager.config.chunk_size.str()
'max_file_size': upload_manager.config.max_file_size.str()
'max_chunk_size': upload_manager.config.max_chunk_size.str()
'temp_dir': upload_manager.config.temp_dir
'upload_dir': upload_manager.config.upload_dir
'cleanup_delay': upload_manager.config.cleanup_delay.str()
'clear_chunks_on_complete': upload_manager.config.clear_chunks_on_complete.str()
}
return c.json(json.encode(config))
})
// ===== 5. 基础路由 =====
// 根路径 - 欢迎页面
app.get('/', fn (mut c hono.Context) http.Response {
html_content := '<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>V-Hono 完整示例</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
.section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
.endpoint { background: #f5f5f5; padding: 10px; margin: 5px 0; border-radius: 3px; }
.feature { color: #2c5aa0; font-weight: bold; }
.static-section { background: #e8f4fd; border-left: 4px solid #2196F3; }
.api-section { background: #f3e5f5; border-left: 4px solid #9C27B0; }
.file-section { background: #e8f5e8; border-left: 4px solid #4CAF50; }
.route-section { background: #fff3e0; border-left: 4px solid #FF9800; }
.upload-section { background: #fff8e1; border-left: 4px solid #FFC107; }
</style>
</head>
<body>
<h1>🚀 V-Hono 完整示例</h1>
<p>A lightweight web framework for V with static file serving and chunk upload</p>
<div class="section static-section">
<h2>📁 静态文件服务</h2>
<div class="endpoint">
<strong>默认目录:</strong> <code>/</code> → <code>./public</code>
</div>
<div class="endpoint">
<strong>资源目录:</strong> <code>/assets</code> → <code>./static</code>
</div>
<div class="endpoint">
<strong>文件目录:</strong> <code>/files</code> → <code>./uploads</code>
</div>
<p><strong>测试文件:</strong></p>
<ul>
<li><a href="/test.txt">/test.txt</a> (来自 ./public/test.txt)</li>
<li><a href="/assets/style.css">/assets/style.css</a> (来自 ./static/style.css)</li>
<li><a href="/files/sample.json">/files/sample.json</a> (来自 ./uploads/sample.json)</li>
</ul>
</div>
<div class="section api-section">
<h2>🔧 API 端点</h2>
<div class="endpoint">
<strong>健康检查:</strong> <a href="/api/health">/api/health</a>
</div>
<div class="endpoint">
<strong>静态文件状态:</strong> <a href="/api/status">/api/status</a>
</div>
<div class="endpoint">
<strong>静态文件信息:</strong> <a href="/api/static-info">/api/static-info</a>
</div>
<div class="endpoint">
<strong>基础路由:</strong> <a href="/hello">/hello</a>
</div>
</div>
<div class="section file-section">
<h2>📄 文件服务端点</h2>
<div class="endpoint">
<strong>基本文件服务:</strong> <a href="/file/test.txt">/file/test.txt</a>
</div>
<div class="endpoint">
<strong>缓存文件服务:</strong> <a href="/file-cached/test.txt">/file-cached/test.txt</a>
</div>
<div class="endpoint">
<strong>文件下载:</strong> <a href="/download/downloadable.txt">/download/downloadable.txt</a>
</div>
<div class="endpoint">
<strong>图片文件服务:</strong> <a href="/image/style.css">/image/style.css</a>
</div>
<div class="endpoint">
<strong>安全文件服务:</strong> <a href="/safe-file/test.txt">/safe-file/test.txt</a>
</div>
</div>
<div class="section route-section">
<h2>🎯 动态路由示例</h2>
<div class="endpoint">
<strong>单参数:</strong> <a href="/users/123">/users/123</a>
</div>
<div class="endpoint">
<strong>多参数:</strong> <a href="/posts/456/comments/789">/posts/456/comments/789</a>
</div>
<div class="endpoint">
<strong>嵌套参数:</strong> <a href="/api/users/101/posts/202">/api/users/101/posts/202</a>
</div>
</div>
<div class="section upload-section">
<h2>📤 分块上传功能</h2>
<div class="endpoint">
<strong>分片上传:</strong> <a href="/upload/chunk">POST /upload/chunk</a>
</div>
<div class="endpoint">
<strong>上传状态:</strong> <a href="/upload/status">GET /upload/status</a>
</div>
<div class="endpoint">
<strong>分片查询:</strong> <a href="/upload/chunks">GET /upload/chunks</a>
</div>
<div class="endpoint">
<strong>分片检查:</strong> <a href="/upload/chunk_exists">GET /upload/chunk_exists</a>
</div>
<div class="endpoint">
<strong>文件管理:</strong> <a href="/api/files">GET /api/files</a>
</div>
<p><strong>特性:</strong></p>
<ul>
<li class="feature">🚀 大文件分片上传</li>
<li class="feature">🔄 断点续传</li>
<li class="feature">⚡ 秒传功能</li>
<li class="feature">📊 上传进度监控</li>
<li class="feature">🛡️ 文件完整性验证</li>
</ul>
</div>
<div class="section">
<h2>✨ 特性</h2>
<ul>
<li class="feature">🚀 高性能路由系统(Trie + Hybrid)</li>
<li class="feature">🛡️ 路径遍历攻击防护</li>
<li class="feature">📁 自动索引文件支持</li>
<li class="feature">🔒 点文件访问控制</li>
<li class="feature">🏷️ ETag 和 Last-Modified 支持</li>
<li class="feature">💾 可配置缓存策略</li>
<li class="feature">🎯 智能 Content-Type 检测</li>
<li class="feature">🔧 洋葱模型中间件</li>
<li class="feature">🔄 支持所有 HTTP 方法</li>
<li class="feature">📊 路由性能统计</li>
<li class="feature">📤 分块上传支持</li>
</ul>
</div>
<div class="section">
<h2>🧪 测试命令</h2>
<pre>
# 静态文件测试
curl http://localhost:8080/test.txt
curl http://localhost:8080/assets/style.css
curl http://localhost:8080/files/sample.json
# 文件服务测试
curl http://localhost:8080/file/test.txt
curl http://localhost:8080/file-cached/test.txt
curl http://localhost:8080/download/downloadable.txt
curl http://localhost:8080/image/style.css
curl http://localhost:8080/safe-file/test.txt
# API 测试
curl http://localhost:8080/api/health
curl http://localhost:8080/api/status
curl http://localhost:8080/hello
# 动态路由测试
curl http://localhost:8080/users/123
curl http://localhost:8080/posts/456/comments/789
# 分块上传测试
curl -X POST http://localhost:8080/upload/chunk -F "file_hash=test123" -F "chunk_index=0" -F "chunk=@test.txt"
curl http://localhost:8080/upload/status?file_hash=test123
curl http://localhost:8080/api/files
# POST 测试
curl -X POST http://localhost:8080/api/users -d "{\\"name\\":\\"John\\"}"
curl -X POST "http://localhost:8080/api/test/create/456?token=abc123" -d "{\\"name\\":\\"Test\\"}"
</pre>
</div>
</body>
</html>'
return c.html(html_content)
})
// 基础路由
app.get('/hello', fn (mut c hono.Context) http.Response {
return c.text('Hello, World!')
})
// Path 信息展示路由
app.get('/path-info', fn (mut c hono.Context) http.Response {
// 构建JSON响应
mut response := '{'
response += '"path_info": {'
response += '"path": "${c.path}",'
response += '"url": "${c.url}"'
response += '}'
response += '}'
return c.json(response)
})
// ===== 6. API 路由 =====
// 健康检查
app.get('/api/health', fn (mut c hono.Context) http.Response {
c.status(200)
return c.json('{"status": "ok", "message": "Health check passed"}')
})
// 静态文件服务状态
app.get('/api/status', fn (mut c hono.Context) http.Response {
return c.json('{"status": "ok", "static_files": "enabled"}')
})
// 静态文件服务信息
app.get('/api/static-info', fn (mut c hono.Context) http.Response {
return c.json('{
"static_directories": [
{"path": "/", "root": "./public"},
{"path": "/assets", "root": "./static"},
{"path": "/files", "root": "./uploads"}
],
"features": [
"Path traversal protection",
"Auto index files",
"ETag support",
"Last-Modified headers",
"Configurable caching",
"Custom headers",
"Chunk upload support"
]
}')
})
// ===== 7. 动态路由 =====
// 单参数路由
app.get('/users/:id', fn (mut c hono.Context) http.Response {
user_id := c.params['id']
return c.json('{"user_id": "${user_id}", "name": "John Doe"}')
})
// 多参数路由
app.get('/posts/:id/comments/:comment_id', fn (mut c hono.Context) http.Response {
post_id := c.params['id']
comment_id := c.params['comment_id']
return c.json('{"post_id": "${post_id}", "comment_id": "${comment_id}", "content": "Great post!"}')
})
// 嵌套参数路由
app.get('/api/users/:user_id/posts/:post_id', fn (mut c hono.Context) http.Response {
user_id := c.params['user_id']
post_id := c.params['post_id']
return c.json('{"user_id": "${user_id}", "post_id": "${post_id}", "title": "Sample Post"}')
})
// 路径分析路由
app.get('/analyze/*path', fn (mut c hono.Context) http.Response {
requested_path := c.params['path']
// 构建JSON响应
mut response := '{'
response += '"path_analysis": {'
response += '"original_path": "${c.path}",'
response += '"requested_path": "${requested_path}"'
response += '}'
response += '}'
return c.json(response)
})
// ===== 8. HTTP 方法示例 =====
// POST 请求
app.post('/api/users', fn (mut c hono.Context) http.Response {
println('=== POST 请求信息 ===')
println('请求体: ${c.body}')
println('查询参数: ${c.query}')
c.status(201)
return c.json('{"message": "User created", "data": "${c.body}"}')
})
// 带参数的 POST 请求
app.post('/api/users/:id', fn (mut c hono.Context) http.Response {
user_id := c.params['id']
println('=== 带参数的 POST 请求信息 ===')
println('用户ID: ${user_id}')
println('请求体: ${c.body}')
println('查询参数: ${c.query}')
c.status(200)
return c.json('{"message": "User updated", "user_id": "${user_id}", "data": "${c.body}"}')
})
// 复杂的 POST 请求
app.post('/api/test/:action/:id', fn (mut c hono.Context) http.Response {
action := c.params['action']
id := c.params['id']
println('=== 复杂 POST 请求信息 ===')
println('动作: ${action}')
println('ID: ${id}')
println('查询参数: ${c.query}')
println('请求体: ${c.body}')
// 构建响应JSON
mut response := '{'
response += '"message": "复杂测试",'
response += '"params": {'
response += '"action": "${action}",'
response += '"id": "${id}"'
response += '},'
response += '"query": {'
// 添加查询参数
mut query_count := 0
for key, value in c.query {
if query_count > 0 {
response += ','
}
response += '"${key}": "${value}"'
query_count++
}
response += '},'
response += '"body": "${c.body}"'
response += '}'
c.status(200)
return c.json(response)
})
// PUT 请求示例
app.put('/api/users/:id', fn (mut c hono.Context) http.Response {
user_id := c.params['id']
c.status(200)
return c.json('{"message": "User replaced", "user_id": "${user_id}", "data": "${c.body}"}')
})
// DELETE 请求示例
app.delete('/api/users/:id', fn (mut c hono.Context) http.Response {
_ := c.params['id'] // 忽略用户ID
c.status(204)
return c.text('')
})
// PATCH 请求示例
app.patch('/api/users/:id', fn (mut c hono.Context) http.Response {
user_id := c.params['id']
c.status(200)
return c.json('{"message": "User partially updated", "user_id": "${user_id}", "data": "${c.body}"}')
})
// HEAD 请求示例
app.head('/api/users/:id', fn (mut c hono.Context) http.Response {
user_id := c.params['id']
c.headers['X-User-ID'] = user_id
c.status(200)
return c.text('')
})
// OPTIONS 请求示例
app.options('/api/users/:id', fn (mut c hono.Context) http.Response {
c.headers['Allow'] = 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS'
c.headers['Access-Control-Allow-Origin'] = '*'
c.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS'
c.status(200)
return c.text('')
})
// ===== 9. 文件服务功能 =====
// 基本文件服务示例
app.get('/file/:filename', fn (mut c hono.Context) http.Response {
filename := c.params['filename']
file_path := 'public/${filename}'
return c.file(file_path)
})
// 带选项的文件服务示例
app.get('/file-cached/:filename', fn (mut c hono.Context) http.Response {
filename := c.params['filename']
file_path := 'public/${filename}'
options := hono.FileOptions{
max_age: 3600 // 缓存1小时
headers: {
'X-Served-By': 'v-hono'
}
}
return c.file_with_options(file_path, options)
})
// 自定义Content-Type的文件服务
app.get('/download/:filename', fn (mut c hono.Context) http.Response {
filename := c.params['filename']
file_path := 'uploads/${filename}'
options := hono.FileOptions{
content_type: 'application/octet-stream'
headers: {
'Content-Disposition': 'attachment; filename="${filename}"'
}
}
return c.file_with_options(file_path, options)
})
// 图片文件服务示例
app.get('/image/:filename', fn (mut c hono.Context) http.Response {
filename := c.params['filename']
file_path := 'static/${filename}'
options := hono.FileOptions{
max_age: 86400 // 缓存24小时
headers: {
'X-Image-Type': 'static'
}
}
return c.file_with_options(file_path, options)
})
// 禁用缓存的文件服务
app.get('/dynamic/:filename', fn (mut c hono.Context) http.Response {
filename := c.params['filename']
file_path := 'dynamic/${filename}'
options := hono.FileOptions{
no_cache: true
headers: {
'X-Dynamic': 'true'
}
}
return c.file_with_options(file_path, options)
})
// ===== 10. 错误处理示例 =====
// 安全文件服务
app.get('/safe-file/:filename', fn (mut c hono.Context) http.Response {
filename := c.params['filename']
// 安全检查:只允许访问特定目录
if filename.contains('..') || filename.starts_with('/') {
c.status(403)
return c.text('Access denied')
}
file_path := 'public/${filename}'
return c.file(file_path)
})
// 404 错误处理
app.get('/**', fn (mut c hono.Context) http.Response {
c.status(404)
return c.json('{"error": "Not Found", "message": "The requested resource was not found"}')
})
// ===== 11. 服务器启动 =====
println('🚀 V-Hono 完整示例启动中...')
println('📍 服务器地址: http://localhost:8080')
println('')
println('📁 静态文件服务:')
println(' - 默认: ./public (访问路径: /)')
println(' - 资源: ./static (访问路径: /assets)')
println(' - 文件: ./uploads (访问路径: /files)')
println('')
println('🔧 API 端点:')
println(' - GET /api/health')
println(' - GET /api/status')
println(' - GET /api/static-info')
println(' - GET /hello')
println(' - GET /path-info')
println(' - GET /analyze/*path')
println('')
println('📄 文件服务端点:')
println(' - GET /file/:filename - 基本文件服务')
println(' - GET /file-cached/:filename - 缓存文件服务')
println(' - GET /download/:filename - 文件下载')
println(' - GET /image/:filename - 图片文件服务')
println(' - GET /dynamic/:filename - 动态文件服务')
println(' - GET /safe-file/:filename - 安全文件服务')
println('')
println('📤 分块上传端点:')
println(' - POST /upload/chunk - 分片上传')
println(' - GET /upload/status - 上传状态')
println(' - GET /upload/chunks - 分片查询')
println(' - GET /upload/chunk_exists - 分片检查')
println(' - GET /api/files - 文件管理')
println('')
println('🎯 动态路由:')
println(' - GET /users/123')
println(' - GET /posts/456/comments/789')
println(' - GET /api/users/101/posts/202')
println('')
println('📝 HTTP 方法示例:')
println(' - POST /api/users')
println(' - POST /api/users/123')
println(' - POST /api/test/create/456')
println(' - PUT /api/users/123')
println(' - DELETE /api/users/123')
println(' - PATCH /api/users/123')
println(' - HEAD /api/users/123')
println(' - OPTIONS /api/users/123')
println('')
println('🧪 测试命令示例:')
println(' # 静态文件测试')
println(' curl http://localhost:8080/test.txt')
println(' curl http://localhost:8080/assets/style.css')
println(' curl http://localhost:8080/files/sample.json')
println('')
println(' # 文件服务测试')
println(' curl http://localhost:8080/file/test.txt')
println(' curl http://localhost:8080/file-cached/test.txt')
println(' curl http://localhost:8080/download/downloadable.txt')
println(' curl http://localhost:8080/image/style.css')
println(' curl http://localhost:8080/safe-file/test.txt')
println('')
println(' # API 测试')
println(' curl http://localhost:8080/api/health')
println(' curl http://localhost:8080/api/status')
println(' curl http://localhost:8080/hello')
println('')
println(' # 动态路由测试')
println(' curl http://localhost:8080/users/123')
println(' curl http://localhost:8080/posts/456/comments/789')
println('')
println(' # 分块上传测试')
println(' curl -X POST http://localhost:8080/upload/chunk -F "file_hash=test123" -F "chunk_index=0" -F "chunk=@test.txt"')
println(' curl http://localhost:8080/upload/status?file_hash=test123')
println(' curl http://localhost:8080/api/files')
println('')
println(' # POST 测试')
println(' curl -X POST http://localhost:8080/api/users -d "{\\"name\\":\\"John\\"}"')
println(' curl -X POST "http://localhost:8080/api/test/create/456?token=abc123" -d "{\\"name\\":\\"Test\\"}"')
app.listen(':8080')
}
// 获取文件扩展名
fn get_file_extension(filename string) string {
println('[DEBUG] Getting extension for filename: "$filename"')
parts := filename.split('.')
println('[DEBUG] Split parts: $parts')
if parts.len > 1 {
ext := '.${parts.last()}'
println('[DEBUG] Extracted extension: "$ext"')
return ext
}
println('[DEBUG] No extension found, returning empty string')
return ''
}