Files
v-hono/example.v
T

512 lines
16 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.
module main
import net.http
import hono
fn main() {
// ===== 1. 应用初始化 =====
mut app := hono.Hono.new()
// ===== 2. 中间件配置 =====
// 日志中间件
app.use(fn (mut c hono.Context, next fn (mut hono.Context) http.Response) http.Response {
println('[LOG] ${c.req.method} ${c.url}')
return next(mut c)
})
// 认证中间件
app.use(fn (mut c hono.Context, next fn (mut hono.Context) http.Response) http.Response {
println("url path: ${c.url}")
token := c.query['token']
if c.url != "/" && 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.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; }
</style>
</head>
<body>
<h1>🚀 V-Hono 完整示例</h1>
<p>A lightweight web framework for V with static file serving</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">
<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>
</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
# 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!')
})
// ===== 5. 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"
]
}')
})
// ===== 6. 动态路由 =====
// 单参数路由
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"}')
})
// ===== 7. 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 {
user_id := c.params['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('')
})
// ===== 8. 文件服务功能 =====
// 基本文件服务示例
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)
})
// ===== 9. 错误处理示例 =====
// 安全文件服务
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"}')
})
// ===== 10. 服务器启动 =====
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('')
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(' - 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(' # 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')
}