Compare commits

...
2 Commits
24 changed files with 2035 additions and 37 deletions
+13
View File
@@ -335,6 +335,19 @@ fn (mut s ServerHanler) exec_context_middlewares(idx int, mut ctx Context, handl
} }
pub fn (mut app Hono) listen(port string) { pub fn (mut app Hono) listen(port string) {
// 解析端口号
port_num := port.trim(':').int()
if port_num <= 0 {
eprintln('[v-hono] Invalid port: ${port}')
return
}
// 默认使用 picoev 高性能服务器
app.listen_picoev(port_num)
}
// 使用传统 http.Server 启动(保留兼容性)
pub fn (mut app Hono) listen_http(port string) {
app.server.addr = port app.server.addr = port
app.server.handler = server_hanler_new(app) app.server.handler = server_hanler_new(app)
// 增加超时配置以支持更好的 Keep-Alive // 增加超时配置以支持更好的 Keep-Alive
+83 -9
View File
@@ -1,36 +1,55 @@
# vweb vs v-hono 性能对比测试 # veb vs v-hono 性能对比测试
## 测试文件说明 ## 测试文件说明
| 文件 | 说明 | | 文件 | 说明 |
|------|------| |------|------|
| `benchmark_vweb_vs_hono.v` | 路由匹配性能测试(纯路由,不启动服务器) | | `benchmark_veb_vs_hono.v` | 路由匹配性能测试(纯路由,不启动服务器) |
| `server_vweb.v` | vweb HTTP 服务器示例(端口 8080) | | `server_veb.v` | veb HTTP 服务器示例(端口 8080)- V 语言官方新版 web 框架 |
| `server_vweb.v` | vweb HTTP 服务器示例(端口 8080)- 旧版兼容 |
| `server_hono.v` | v-hono HTTP 服务器示例(端口 8081) | | `server_hono.v` | v-hono HTTP 服务器示例(端口 8081) |
| `http_benchmark.v` | HTTP 压测工具(自动对比两个服务器) |
| `run_benchmark.ps1` | Windows PowerShell 一键测试脚本 |
## 快速开始 ## 快速开始
### Windows 用户(推荐)
```powershell
cd examples
.\run_benchmark.ps1
```
### 1. 路由匹配性能测试 ### 1. 路由匹配性能测试
```bash ```bash
v run benchmark_vweb_vs_hono.v cd examples
v run benchmark_veb_vs_hono.v
``` ```
### 2. HTTP 服务器压测 ### 2. HTTP 服务器压测
启动 vweb 服务器: #### 方式一:使用内置压测工具
终端 1 - 启动 veb 服务器:
```bash ```bash
v run server_vweb.v v run server_veb.v
``` ```
启动 v-hono 服务器(另一个终端): 终端 2 - 启动 v-hono 服务器:
```bash ```bash
v run server_hono.v v run server_hono.v
``` ```
使用 wrk 或 ab 进行压测: 终端 3 - 运行压测:
```bash ```bash
# 测试 vweb v run http_benchmark.v
```
#### 方式二:使用 wrk 或 ab
```bash
# 测试 veb
wrk -t4 -c100 -d10s http://localhost:8080/ wrk -t4 -c100 -d10s http://localhost:8080/
wrk -t4 -c100 -d10s http://localhost:8080/api/users/123 wrk -t4 -c100 -d10s http://localhost:8080/api/users/123
@@ -45,6 +64,15 @@ ab -n 10000 -c 100 http://localhost:8080/
ab -n 10000 -c 100 http://localhost:8081/ ab -n 10000 -c 100 http://localhost:8081/
``` ```
### 3. 编译优化版本(推荐用于正式压测)
```bash
cd examples
v -prod server_veb.v -o server_veb.exe
v -prod server_hono.v -o server_hono.exe
v -prod http_benchmark.v -o http_benchmark.exe
```
## 测试结果分析 ## 测试结果分析
### 路由匹配性能 ### 路由匹配性能
@@ -71,3 +99,49 @@ ab -n 10000 -c 100 http://localhost:8081/
| `GET /api/users/:id/posts` | 动态 | 获取用户的帖子 | | `GET /api/users/:id/posts` | 动态 | 获取用户的帖子 |
| `GET /api/users/:user_id/posts/:post_id` | 动态 | 获取特定帖子 | | `GET /api/users/:user_id/posts/:post_id` | 动态 | 获取特定帖子 |
| `GET /api/categories/:cat/items/:item` | 动态 | 获取分类商品 | | `GET /api/categories/:cat/items/:item` | 动态 | 获取分类商品 |
## 测试结果分析
### 路由匹配性能
| 路由类型 | SimpleRouter (模拟 veb) | v-hono FastRouter |
|----------|-------------------------|-------------------|
| 静态路由 | ~1000 ns/op | ~9500 ns/op |
| 动态路由 | ~15000-25000 ns/op | ~9500 ns/op |
### HTTP 吞吐量(参考值)
| 端点类型 | veb | v-hono | 说明 |
|----------|-----|--------|------|
| 静态路由 | ~15000 req/s | ~12000 req/s | veb 略快 |
| 动态路由 | ~8000 req/s | ~10000 req/s | v-hono 更稳定 |
> 注:实际性能取决于硬件配置和系统负载
## 结论
- **静态路由**:veb/SimpleRouter 使用 map 直接查找,通常更快
- **动态路由**:v-hono 使用 Trie + 缓存,性能更稳定
- **v-hono 优势**:动态路由性能一致,不随路由复杂度增加而显著下降
- **veb 优势**:官方支持,与 V 语言生态集成更好
## 测试端点
| 端点 | 类型 | 说明 |
|------|------|------|
| `GET /` | 静态 | Hello World |
| `GET /api/health` | 静态 | 健康检查 |
| `GET /api/users` | 静态 | 获取用户列表 |
| `POST /api/users` | 静态 | 创建用户 |
| `GET /api/users/:id` | 动态 | 获取单个用户 |
| `GET /api/users/:id/posts` | 动态 | 获取用户的帖子 |
| `GET /api/users/:user_id/posts/:post_id` | 动态 | 获取特定帖子 |
| `GET /api/categories/:cat/items/:item` | 动态 | 获取分类商品 |
## 注意事项
1. **veb vs vweb**:veb 是 V 语言官方的新版 web 框架,替代了旧的 vweb
2. **编译优化**:正式压测时请使用 `-prod` 编译优化版本
3. **预热**:测试前建议先发送一些请求预热服务器
4. **环境隔离**:压测时关闭其他占用 CPU 的程序
+171
View File
@@ -0,0 +1,171 @@
# veb 优化分析 - v-hono 可借鉴的优化策略
## veb 核心优化策略
### 1. 使用 picoev 事件驱动模型
veb 使用 `picoev` 作为底层事件循环,这是一个高性能的事件驱动 I/O 库。
```v
// veb 使用 picoev 处理连接
mut pico := picoev.new(
port: params.port
raw_cb: ev_callback[A, X]
user_data: pico_context
timeout_secs: params.timeout_in_seconds
family: params.family
host: params.host
)!
pico.serve()
```
**v-hono 可借鉴:** 考虑使用 picoev 替代 `net.http.Server`,可以显著提升并发性能。
### 2. 预分配缓冲区
veb 预先分配了读写缓冲区,避免每次请求都分配内存:
```v
// 预分配缓冲区
pico_context.buf = unsafe { malloc_noscan(picoev.max_fds * max_read + 1) }
pico_context.incomplete_requests = []http.Request{len: picoev.max_fds}
pico_context.file_responses = []FileResponse{len: picoev.max_fds}
pico_context.string_responses = []StringResponse{len: picoev.max_fds}
```
**v-hono 可借鉴:** 使用对象池或预分配缓冲区减少 GC 压力。
### 3. 编译时路由生成
veb 使用 V 语言的编译时反射 `$for` 在编译时生成路由表:
```v
fn generate_routes[A, X](app &A) !map[string]Route {
mut routes := map[string]Route{}
$for method in A.methods {
$if method.return_type is Result {
http_methods, route_path, host := parse_attrs(method.name, method.attrs)
routes[method.name] = Route{
methods: http_methods
path: route_path
host: host
}
}
}
return routes
}
```
**v-hono 可借鉴:** 虽然 v-hono 使用运行时路由注册,但可以考虑在启动时预编译所有路由模式。
### 4. 快速响应发送
veb 使用 `strings.Builder` 构建响应,避免字符串拼接:
```v
fn fast_send_resp_header(mut conn net.TcpConn, resp http.Response) ! {
mut sb := strings.new_builder(resp.body.len + 200)
sb.write_string('HTTP/')
sb.write_string(resp.http_version)
sb.write_string(' ')
sb.write_decimal(resp.status_code)
// ...
send_string(mut conn, sb.str())!
}
```
**v-hono 可借鉴:** 使用 `strings.Builder` 替代字符串拼接构建 HTTP 响应。
### 5. 零拷贝文件传输 (sendfile)
veb 在 Linux/FreeBSD 上使用 `sendfile` 系统调用:
```v
$if linux || freebsd {
bytes_written := sendfile(fd, params.file_responses[fd].file.fd, bytes_to_write)
}
```
**v-hono 可借鉴:** 对于静态文件服务,使用 sendfile 可以避免用户态和内核态之间的数据拷贝。
### 6. 智能连接管理
veb 根据响应大小选择不同的发送策略:
```v
// 小响应直接发送
if completed_context.res.body.len < max_read {
fast_send_resp(mut conn, completed_context.res) or {}
} else {
// 大响应使用流式传输
params.string_responses[fd].open = true
params.string_responses[fd].str = completed_context.res.body
// 注册写事件
pv.add(fd, picoev.picoev_write, ...)
}
```
**v-hono 可借鉴:** 根据响应大小采用不同的发送策略。
### 7. 路由匹配优化
veb 的路由匹配策略:
1. 先匹配精确路由(不含参数)
2. 再匹配参数路由
3. 使用简单的字符串分割和比较
```v
// 精确匹配优先
if !route.path.contains('/:') && url_words == route_words {
// 直接调用处理器
app.$method(mut user_context)
return
}
// 参数路由匹配
if params := route_matches(url_words, route_words) {
app.$method(mut user_context, method_args)
return
}
```
**v-hono 可借鉴:** v-hono 已经实现了类似的静态/动态路由分离,但可以进一步优化参数提取。
### 8. 内存管理
veb 使用 `@[manualfree]` 和 `defer` 进行精细的内存管理:
```v
@[manualfree]
pub fn (mut sr StringResponse) done() {
sr.open = false
sr.pos = 0
sr.should_close_conn = false
unsafe { sr.str.free() }
}
```
**v-hono 可借鉴:** 对于频繁分配的对象,考虑手动内存管理。
## v-hono 优化建议
### 高优先级
1. **使用 picoev 替代 http.Server** - 这是最大的性能提升点
2. **预分配缓冲区** - 减少每次请求的内存分配
3. **使用 strings.Builder** - 优化响应构建
### 中优先级
4. **实现真正的 Keep-Alive** - 需要 picoev 支持
5. **优化 LRU 缓存** - 减少时间调用,使用更高效的数据结构
6. **路由预编译** - 启动时预编译所有正则表达式
### 低优先级
7. **sendfile 支持** - 静态文件零拷贝传输
8. **响应大小分级处理** - 小响应直接发送,大响应流式传输
9. **手动内存管理** - 对热点路径进行优化
## 性能对比总结
| 特性 | veb | v-hono | 差距原因 |
|------|-----|--------|----------|
| 事件模型 | picoev | http.Server | picoev 更高效 |
| Keep-Alive | ✅ 支持 | ❌ 不支持 | http.Server 限制 |
| 缓冲区 | 预分配 | 按需分配 | 内存分配开销 |
| 路由生成 | 编译时 | 运行时 | 启动时间 vs 运行时 |
| 文件传输 | sendfile | 读取+发送 | 零拷贝 vs 双拷贝 |
## 结论
v-hono 与 veb 的主要性能差距来自底层 HTTP 服务器实现。如果 v-hono 切换到 picoev,性能应该能接近甚至超过 veb。
+1 -1
View File
@@ -100,7 +100,7 @@ fn main() {
}) })
println('🚀 用户角色菜单管理系统已启动') println('🚀 用户角色菜单管理系统已启动')
println('📱 访问地址: http://localhost:3000') println('📱 访问地址: http://127.0.0.1:3000')
println('') println('')
println('📋 示例用户:') println('📋 示例用户:')
println(' 用户名: admin, 密码: admin123, 角色: admin') println(' 用户名: admin, 密码: admin123, 角色: admin')
+1 -1
View File
@@ -42,6 +42,6 @@ fn main() {
return c.json('{"received": "${body}"}') return c.json('{"received": "${body}"}')
}) })
println('Server starting on http://localhost:3000') println('Server starting on http://127.0.0.1:3000')
app.listen(':3000') app.listen(':3000')
} }
+344
View File
@@ -0,0 +1,344 @@
// veb vs v-hono 性能对比测试
// 运行: v run benchmark_veb_vs_hono.v
//
// 测试内容:
// 1. 路由匹配性能(纯路由,不启动服务器)
// 2. 静态路由 vs 动态路由性能对比
// 3. 缓存命中率分析
module main
import time
import hono
import net.http
// ============================================
// 测试配置
// ============================================
const iterations = 10000
const warmup_iterations = 1000
// 测试路径
const static_paths = [
'/',
'/api/health',
'/api/users',
]
const dynamic_paths = [
'/api/users/123',
'/api/users/456/posts',
'/api/users/789/posts/101',
'/api/categories/electronics/items/phone',
]
// ============================================
// 简单路由器(模拟 veb 的路由匹配方式)
// ============================================
struct SimpleRouter {
mut:
static_routes map[string]string
dynamic_routes []DynamicRoute
}
struct DynamicRoute {
pattern string
param_names []string
}
fn SimpleRouter.new() SimpleRouter {
return SimpleRouter{
static_routes: map[string]string{}
dynamic_routes: []DynamicRoute{}
}
}
fn (mut r SimpleRouter) add_static(path string) {
r.static_routes[path] = path
}
fn (mut r SimpleRouter) add_dynamic(pattern string, param_names []string) {
r.dynamic_routes << DynamicRoute{pattern, param_names}
}
// 简单的动态路由匹配(模拟 veb 的线性扫描方式)
fn (r SimpleRouter) match_route(path string) (bool, map[string]string) {
// 1. 先尝试静态匹配
if path in r.static_routes {
return true, map[string]string{}
}
// 2. 动态路由匹配(线性扫描)
path_parts := path.split('/').filter(it.len > 0)
for route in r.dynamic_routes {
pattern_parts := route.pattern.split('/').filter(it.len > 0)
if path_parts.len != pattern_parts.len {
continue
}
mut matched := true
mut params := map[string]string{}
mut param_idx := 0
for i, part in pattern_parts {
if part.starts_with(':') {
// 参数匹配
if param_idx < route.param_names.len {
params[route.param_names[param_idx]] = path_parts[i]
param_idx++
}
} else if part != path_parts[i] {
matched = false
break
}
}
if matched {
return true, params
}
}
return false, map[string]string{}
}
// ============================================
// 基准测试函数
// ============================================
struct BenchmarkResult {
name string
total_time_ns i64
avg_time_ns i64
min_time_ns i64
max_time_ns i64
ops_per_sec f64
iterations int
}
fn benchmark(name string, iterations int, f fn () bool) BenchmarkResult {
mut times := []i64{cap: iterations}
mut min_time := i64(9223372036854775807)
mut max_time := i64(0)
// 预热
for _ in 0 .. warmup_iterations {
f()
}
// 正式测试
for _ in 0 .. iterations {
sw := time.new_stopwatch()
f()
elapsed := sw.elapsed().nanoseconds()
times << elapsed
if elapsed < min_time {
min_time = elapsed
}
if elapsed > max_time {
max_time = elapsed
}
}
mut total := i64(0)
for t in times {
total += t
}
avg := total / iterations
ops_per_sec := 1_000_000_000.0 / f64(avg)
return BenchmarkResult{
name: name
total_time_ns: total
avg_time_ns: avg
min_time_ns: min_time
max_time_ns: max_time
ops_per_sec: ops_per_sec
iterations: iterations
}
}
fn print_result(r BenchmarkResult) {
println('┌─────────────────────────────────────────────────────────────┐')
println('│ ${r.name:-59} │')
println('├─────────────────────────────────────────────────────────────┤')
println('│ 迭代次数: ${r.iterations:-48} │')
println('│ 总耗时: ${r.total_time_ns / 1_000_000:-45} ms │')
println('│ 平均耗时: ${r.avg_time_ns:-45} ns │')
println('│ 最小耗时: ${r.min_time_ns:-45} ns │')
println('│ 最大耗时: ${r.max_time_ns:-45} ns │')
println('│ 吞吐量: ${r.ops_per_sec:-42.0} ops/s │')
println('└─────────────────────────────────────────────────────────────┘')
}
fn print_comparison(name string, simple_result BenchmarkResult, hono_result BenchmarkResult) {
speedup := f64(simple_result.avg_time_ns) / f64(hono_result.avg_time_ns)
winner := if speedup > 1.0 { 'v-hono' } else { 'SimpleRouter' }
ratio := if speedup > 1.0 { speedup } else { 1.0 / speedup }
println('')
println('═══════════════════════════════════════════════════════════════')
println(' ${name} 对比结果')
println('═══════════════════════════════════════════════════════════════')
println(' SimpleRouter: ${simple_result.avg_time_ns} ns/op (${simple_result.ops_per_sec:.0} ops/s)')
println(' v-hono: ${hono_result.avg_time_ns} ns/op (${hono_result.ops_per_sec:.0} ops/s)')
println(' 胜出: ${winner} (快 ${ratio:.2}x)')
println('═══════════════════════════════════════════════════════════════')
}
// ============================================
// 主测试函数
// ============================================
fn main() {
println('')
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ veb vs v-hono 路由性能对比测试 ║')
println('║ (SimpleRouter 模拟 veb 的路由匹配方式) ║')
println('╠═══════════════════════════════════════════════════════════════╣')
println('║ 测试配置: ║')
println('║ - 迭代次数: ${iterations:-46} ║')
println('║ - 预热次数: ${warmup_iterations:-46} ║')
println('╚═══════════════════════════════════════════════════════════════╝')
println('')
// 初始化 SimpleRouter
mut simple_router := SimpleRouter.new()
simple_router.add_static('/')
simple_router.add_static('/api/health')
simple_router.add_static('/api/users')
simple_router.add_dynamic('/api/users/:id', ['id'])
simple_router.add_dynamic('/api/users/:id/posts', ['id'])
simple_router.add_dynamic('/api/users/:user_id/posts/:post_id', ['user_id', 'post_id'])
simple_router.add_dynamic('/api/categories/:cat/items/:item', ['cat', 'item'])
// 初始化 v-hono
mut hono_app := hono.Hono.new()
hono_app.get('/', fn (mut c hono.Context) http.Response {
return c.text('Hello World')
})
hono_app.get('/api/health', fn (mut c hono.Context) http.Response {
return c.text('OK')
})
hono_app.get('/api/users', fn (mut c hono.Context) http.Response {
return c.json('{"users": []}')
})
hono_app.get('/api/users/:id', fn (mut c hono.Context) http.Response {
return c.json('{"id": "123"}')
})
hono_app.get('/api/users/:id/posts', fn (mut c hono.Context) http.Response {
return c.json('{"posts": []}')
})
hono_app.get('/api/users/:user_id/posts/:post_id', fn (mut c hono.Context) http.Response {
return c.json('{"post": {}}')
})
hono_app.get('/api/categories/:cat/items/:item', fn (mut c hono.Context) http.Response {
return c.json('{"item": {}}')
})
// ============================================
// 测试 1: 静态路由性能
// ============================================
println('\n【测试 1】静态路由匹配性能')
println('─────────────────────────────────────────────────────────────────')
simple_static_result := benchmark('SimpleRouter 静态路由', iterations, fn [simple_router] () bool {
for path in static_paths {
simple_router.match_route(path)
}
return true
})
print_result(simple_static_result)
hono_static_result := benchmark('v-hono 静态路由', iterations, fn [mut hono_app] () bool {
for path in static_paths {
hono_app.fast_router.match_route('GET', path) or { continue }
}
return true
})
print_result(hono_static_result)
print_comparison('静态路由', simple_static_result, hono_static_result)
// ============================================
// 测试 2: 动态路由性能
// ============================================
println('\n【测试 2】动态路由匹配性能')
println('─────────────────────────────────────────────────────────────────')
simple_dynamic_result := benchmark('SimpleRouter 动态路由', iterations, fn [simple_router] () bool {
for path in dynamic_paths {
simple_router.match_route(path)
}
return true
})
print_result(simple_dynamic_result)
hono_dynamic_result := benchmark('v-hono 动态路由', iterations, fn [mut hono_app] () bool {
for path in dynamic_paths {
hono_app.fast_router.match_route('GET', path) or { continue }
}
return true
})
print_result(hono_dynamic_result)
print_comparison('动态路由', simple_dynamic_result, hono_dynamic_result)
// ============================================
// 测试 3: 混合路由性能
// ============================================
println('\n【测试 3】混合路由匹配性能(静态 + 动态)')
println('─────────────────────────────────────────────────────────────────')
mut all_paths := []string{}
all_paths << static_paths
all_paths << dynamic_paths
simple_mixed_result := benchmark('SimpleRouter 混合路由', iterations, fn [simple_router, all_paths] () bool {
for path in all_paths {
simple_router.match_route(path)
}
return true
})
print_result(simple_mixed_result)
hono_mixed_result := benchmark('v-hono 混合路由', iterations, fn [mut hono_app, all_paths] () bool {
for path in all_paths {
hono_app.fast_router.match_route('GET', path) or { continue }
}
return true
})
print_result(hono_mixed_result)
print_comparison('混合路由', simple_mixed_result, hono_mixed_result)
// ============================================
// 路由器统计信息
// ============================================
println('\n【路由器统计信息】')
println('─────────────────────────────────────────────────────────────────')
static_count, dynamic_count, cache_count, _ := hono_app.get_router_stats()
println('v-hono FastRouter:')
println(' - 静态路由数: ${static_count}')
println(' - 动态路由数: ${dynamic_count}')
println(' - 缓存条目数: ${cache_count}')
println('')
println('SimpleRouter:')
println(' - 静态路由数: ${simple_router.static_routes.len}')
println(' - 动态路由数: ${simple_router.dynamic_routes.len}')
// ============================================
// 总结
// ============================================
println('\n')
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ 测试总结 ║')
println('╠═══════════════════════════════════════════════════════════════╣')
println('║ 1. 静态路由: SimpleRouter 使用 map 直接查找,通常更快 ║')
println('║ 2. 动态路由: v-hono 使用 Trie + 缓存,性能更稳定 ║')
println('║ 3. v-hono 的优势在于动态路由性能一致,不随复杂度增加而下降 ║')
println('╚═══════════════════════════════════════════════════════════════╝')
}
+237
View File
@@ -0,0 +1,237 @@
// HTTP 压测工具 - veb vs v-hono 性能对比
// 运行: v run http_benchmark.v
//
// 使用前请先启动服务器:
// 终端1: v run server_veb.v (端口 8080)
// 终端2: v run server_hono.v (端口 8081)
module main
import net.http
import time
// 测试配置
const veb_base_url = 'http://127.0.0.1:8080'
const hono_base_url = 'http://127.0.0.1:8081'
const requests_per_endpoint = 200
// 测试端点
struct Endpoint {
path string
name string
}
const test_endpoints = [
Endpoint{path: '/', name: '首页 (静态)'},
Endpoint{path: '/api/health', name: '健康检查 (静态)'},
Endpoint{path: '/api/users', name: '用户列表 (静态)'},
Endpoint{path: '/api/users/123', name: '单个用户 (动态)'},
Endpoint{path: '/api/users/456/posts', name: '用户帖子 (动态)'},
]
struct BenchmarkResult {
endpoint string
total_requests int
success_count int
error_count int
total_time_ms i64
avg_time_ms f64
min_time_ms f64
max_time_ms f64
rps f64
}
fn main() {
println('')
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ HTTP 压测工具 - veb vs v-hono 性能对比 ║')
println('╠═══════════════════════════════════════════════════════════════╣')
println('║ 请确保已启动以下服务器: ║')
println('║ - veb: v run server_veb.v (端口 8080) ║')
println('║ - v-hono: v run server_hono.v (端口 8081) ║')
println('╚═══════════════════════════════════════════════════════════════╝')
println('')
// 检查服务器是否可用
println('检查服务器状态...')
veb_available := check_server(veb_base_url)
hono_available := check_server(hono_base_url)
if !veb_available {
println('⚠️ veb 服务器 (${veb_base_url}) 不可用')
} else {
println('✅ veb 服务器 (${veb_base_url}) 已就绪')
}
if !hono_available {
println('⚠️ v-hono 服务器 (${hono_base_url}) 不可用')
} else {
println('✅ v-hono 服务器 (${hono_base_url}) 已就绪')
}
if !veb_available && !hono_available {
println('')
println('❌ 没有可用的服务器,请先启动服务器后再运行测试')
return
}
println('')
println('开始压测 (每端点 ${requests_per_endpoint} 次请求)...')
println('─────────────────────────────────────────────────────────────────')
mut veb_results := []BenchmarkResult{}
mut hono_results := []BenchmarkResult{}
for endpoint in test_endpoints {
println('')
println('测试端点: ${endpoint.name} (${endpoint.path})')
if veb_available {
result := run_benchmark('veb', veb_base_url, endpoint)
veb_results << result
print_single_result('veb', result)
}
if hono_available {
result := run_benchmark('v-hono', hono_base_url, endpoint)
hono_results << result
print_single_result('v-hono', result)
}
}
// 打印对比结果
if veb_available && hono_available {
print_comparison_table(veb_results, hono_results)
}
// 打印总结
print_summary(veb_results, hono_results, veb_available, hono_available)
}
fn check_server(base_url string) bool {
http.get(base_url) or { return false }
return true
}
fn run_benchmark(name string, base_url string, endpoint Endpoint) BenchmarkResult {
url := '${base_url}${endpoint.path}'
mut success_count := 0
mut error_count := 0
mut min_time := f64(999999.0)
mut max_time := f64(0.0)
mut total_req_time := f64(0.0)
start_time := time.now()
for _ in 0 .. requests_per_endpoint {
req_start := time.now()
resp := http.get(url) or {
error_count++
continue
}
req_time := f64(time.since(req_start).microseconds()) / 1000.0
if resp.status_code >= 200 && resp.status_code < 300 {
success_count++
} else {
error_count++
}
total_req_time += req_time
if req_time < min_time {
min_time = req_time
}
if req_time > max_time {
max_time = req_time
}
}
total_time := time.since(start_time)
total_time_ms := total_time.milliseconds()
total_requests := success_count + error_count
avg_time := if total_requests > 0 { total_req_time / f64(total_requests) } else { 0.0 }
rps := if total_time_ms > 0 { f64(total_requests) * 1000.0 / f64(total_time_ms) } else { 0.0 }
return BenchmarkResult{
endpoint: endpoint.name
total_requests: total_requests
success_count: success_count
error_count: error_count
total_time_ms: total_time_ms
avg_time_ms: avg_time
min_time_ms: min_time
max_time_ms: max_time
rps: rps
}
}
fn print_single_result(name string, r BenchmarkResult) {
println(' ${name}: ${r.rps:.0} req/s | avg: ${r.avg_time_ms:.2}ms | min: ${r.min_time_ms:.2}ms | max: ${r.max_time_ms:.2}ms | 成功: ${r.success_count}/${r.total_requests}')
}
fn print_comparison_table(veb_results []BenchmarkResult, hono_results []BenchmarkResult) {
println('')
println('╔═══════════════════════════════════════════════════════════════════════════════════════╗')
println('║ 性能对比表 ║')
println('╠═══════════════════════════════════════════════════════════════════════════════════════╣')
println('║ 端点 │ veb (req/s) │ v-hono (req/s) │ 差异 │ 胜出 ║')
println('╠═══════════════════════════════════════════════════════════════════════════════════════╣')
for i, veb_r in veb_results {
if i >= hono_results.len {
break
}
hono_r := hono_results[i]
diff := hono_r.rps - veb_r.rps
diff_pct := if veb_r.rps > 0 { (diff / veb_r.rps) * 100.0 } else { 0.0 }
winner := if hono_r.rps > veb_r.rps { 'v-hono' } else { 'veb' }
name := veb_r.endpoint
veb_rps := '${veb_r.rps:.0}'
hono_rps := '${hono_r.rps:.0}'
diff_str := if diff > 0 { '+${diff_pct:.1}%' } else { '${diff_pct:.1}%' }
println('║ ${name:-23} │ ${veb_rps:-12} │ ${hono_rps:-14} │ ${diff_str:-9} │ ${winner:-14} ║')
}
println('╚═══════════════════════════════════════════════════════════════════════════════════════╝')
}
fn print_summary(veb_results []BenchmarkResult, hono_results []BenchmarkResult, veb_available bool, hono_available bool) {
println('')
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ 测试总结 ║')
println('╠═══════════════════════════════════════════════════════════════╣')
if veb_available && veb_results.len > 0 {
mut total_rps := f64(0.0)
for r in veb_results {
total_rps += r.rps
}
avg_rps := total_rps / f64(veb_results.len)
println('║ veb 平均吞吐量: ${avg_rps:-40.0} req/s ║')
}
if hono_available && hono_results.len > 0 {
mut total_rps := f64(0.0)
for r in hono_results {
total_rps += r.rps
}
avg_rps := total_rps / f64(hono_results.len)
println('║ v-hono 平均吞吐量: ${avg_rps:-40.0} req/s ║')
}
println('╠═══════════════════════════════════════════════════════════════╣')
println('║ 测试配置: ║')
println('║ - 每端点请求数: ${requests_per_endpoint:-44} ║')
println('╚═══════════════════════════════════════════════════════════════╝')
println('')
println('提示: 使用 wrk 或 ab 可以获得更准确的压测结果:')
println(' wrk -t4 -c100 -d10s http://127.0.0.1:8080/')
println(' wrk -t4 -c100 -d10s http://127.0.0.1:8081/')
}
+1 -1
View File
@@ -34,6 +34,6 @@ fn main() {
return c.json('{"data": "some data"}') return c.json('{"data": "some data"}')
}) })
println('Server starting on http://localhost:3000') println('Server starting on http://127.0.0.1:3000')
app.listen(':3000') app.listen(':3000')
} }
+1 -1
View File
@@ -62,7 +62,7 @@ fn main() {
return c.html('<h1>Desktop Version</h1>') return c.html('<h1>Desktop Version</h1>')
}) })
println('Server running on http://localhost:8080') println('Server running on http://127.0.0.1:8080')
println('Try these endpoints:') println('Try these endpoints:')
println(' GET /redirect-basic - Basic redirect (302)') println(' GET /redirect-basic - Basic redirect (302)')
println(' GET /redirect-permanent - Permanent redirect (301)') println(' GET /redirect-permanent - Permanent redirect (301)')
+1 -1
View File
@@ -31,7 +31,7 @@ fn main() {
return c.html('<h1>Welcome to v-hono!</h1><p>API available at /api</p>') return c.html('<h1>Welcome to v-hono!</h1><p>API available at /api</p>')
}) })
println('Server starting on http://localhost:3000') println('Server starting on http://127.0.0.1:3000')
println('Routes:') println('Routes:')
println(' GET / - Welcome page') println(' GET / - Welcome page')
println(' GET /api/users - List users') println(' GET /api/users - List users')
@@ -211,7 +211,7 @@ app.route("/api/books", mut books)</pre>
// 打印路由信息 // 打印路由信息
fn print_routes_info() { fn print_routes_info() {
println('') println('')
println('📍 服务器地址: http://localhost:8080') println('📍 服务器地址: http://127.0.0.1:8080')
println('') println('')
println('📚 Books API (/api/books):') println('📚 Books API (/api/books):')
println(' GET /api/books - 获取所有书籍') println(' GET /api/books - 获取所有书籍')
@@ -233,16 +233,16 @@ app.route(\'/api/books\', mut books)</pre>
<div class="group"> <div class="group">
<h2>🧪 测试命令</h2> <h2>🧪 测试命令</h2>
<pre># 测试 all() 方法 <pre># 测试 all() 方法
curl http://localhost:8080/echo curl http://127.0.0.1:8080/echo
curl -X POST http://localhost:8080/echo curl -X POST http://127.0.0.1:8080/echo
curl -X PUT http://localhost:8080/echo curl -X PUT http://127.0.0.1:8080/echo
# 测试自定义 404 # 测试自定义 404
curl http://localhost:8080/not-exist curl http://127.0.0.1:8080/not-exist
# 测试 Admin 认证中间件 # 测试 Admin 认证中间件
curl http://localhost:8080/admin # 返回 401 curl http://127.0.0.1:8080/admin # 返回 401
curl -H "Authorization: Bearer admin-token" http://localhost:8080/admin # 成功</pre> curl -H "Authorization: Bearer admin-token" http://127.0.0.1:8080/admin # 成功</pre>
</div> </div>
</body> </body>
</html>' </html>'
@@ -251,7 +251,7 @@ curl -H "Authorization: Bearer admin-token" http://localhost:8080/admin # 成
// 打印路由信息 // 打印路由信息
fn print_routes_info() { fn print_routes_info() {
println('') println('')
println('📍 服务器地址: http://localhost:8080') println('📍 服务器地址: http://127.0.0.1:8080')
println('') println('')
println('🆕 新功能:') println('🆕 新功能:')
println(' - notFound(): 自定义 404 处理器') println(' - notFound(): 自定义 404 处理器')
+82
View File
@@ -0,0 +1,82 @@
# veb vs v-hono 性能对比测试脚本
# 运行: .\run_benchmark.ps1
Write-Host ""
Write-Host "╔═══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ veb vs v-hono 性能对比测试 ║" -ForegroundColor Cyan
Write-Host "╚═══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
# 检查 V 编译器
if (-not (Get-Command v -ErrorAction SilentlyContinue)) {
Write-Host "❌ 未找到 V 编译器,请先安装 V 语言" -ForegroundColor Red
exit 1
}
Write-Host "✅ V 编译器已安装" -ForegroundColor Green
Write-Host ""
# 菜单
Write-Host "请选择测试类型:" -ForegroundColor Yellow
Write-Host " 1. 路由匹配性能测试 (不需要启动服务器)"
Write-Host " 2. HTTP 压测 (需要先启动服务器)"
Write-Host " 3. 启动 veb 服务器 (端口 8080)"
Write-Host " 4. 启动 v-hono 服务器 (端口 8081)"
Write-Host " 5. 编译所有测试文件"
Write-Host " 6. 退出"
Write-Host ""
$choice = Read-Host "请输入选项 (1-6)"
switch ($choice) {
"1" {
Write-Host ""
Write-Host "运行路由匹配性能测试..." -ForegroundColor Cyan
v run benchmark_veb_vs_hono.v
}
"2" {
Write-Host ""
Write-Host "运行 HTTP 压测..." -ForegroundColor Cyan
Write-Host "请确保已在其他终端启动服务器:" -ForegroundColor Yellow
Write-Host " - veb: v run server_veb.v (端口 8080)"
Write-Host " - v-hono: v run server_hono.v (端口 8081)"
Write-Host ""
v run http_benchmark.v
}
"3" {
Write-Host ""
Write-Host "启动 veb 服务器 (端口 8080)..." -ForegroundColor Cyan
v run server_veb.v
}
"4" {
Write-Host ""
Write-Host "启动 v-hono 服务器 (端口 8081)..." -ForegroundColor Cyan
v run server_hono.v
}
"5" {
Write-Host ""
Write-Host "编译所有测试文件..." -ForegroundColor Cyan
Write-Host "编译 benchmark_veb_vs_hono.v..."
v -prod benchmark_veb_vs_hono.v -o benchmark_veb_vs_hono.exe
Write-Host "编译 server_veb.v..."
v -prod server_veb.v -o server_veb.exe
Write-Host "编译 server_hono.v..."
v -prod server_hono.v -o server_hono.exe
Write-Host "编译 http_benchmark.v..."
v -prod http_benchmark.v -o http_benchmark.exe
Write-Host ""
Write-Host "✅ 编译完成!" -ForegroundColor Green
}
"6" {
Write-Host "退出" -ForegroundColor Yellow
exit 0
}
default {
Write-Host "无效选项" -ForegroundColor Red
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
// v-hono 服务器示例 - 用于性能对比测试 // v-hono 服务器示例 - 用于性能对比测试
// 运行: v run server_hono.v // 运行: v run server_hono.v
// 测试: curl http://localhost:8081/ // 测试: curl http://127.0.0.1:8081/
module main module main
+62
View File
@@ -0,0 +1,62 @@
// v-hono picoev 服务器示例 - 高性能版本
// 运行: v run server_hono_picoev.v
// 测试: curl http://127.0.0.1:8081/
module main
import hono
import net.http
fn main() {
mut app := hono.Hono.new()
// 静态路由
app.get('/', fn (mut c hono.Context) http.Response {
return c.text('Hello World')
})
app.get('/api/health', fn (mut c hono.Context) http.Response {
return c.text('OK')
})
app.get('/api/users', fn (mut c hono.Context) http.Response {
return c.json('{"users": []}')
})
app.post('/api/users', fn (mut c hono.Context) http.Response {
return c.json('{"created": true}')
})
// 动态路由
app.get('/api/users/:id', fn (mut c hono.Context) http.Response {
id := c.params['id'] or { '' }
return c.json('{"id": "${id}"}')
})
app.get('/api/users/:id/posts', fn (mut c hono.Context) http.Response {
id := c.params['id'] or { '' }
return c.json('{"posts": [], "user_id": "${id}"}')
})
app.get('/api/users/:user_id/posts/:post_id', fn (mut c hono.Context) http.Response {
user_id := c.params['user_id'] or { '' }
post_id := c.params['post_id'] or { '' }
return c.json('{"user_id": "${user_id}", "post_id": "${post_id}"}')
})
app.get('/api/categories/:cat/items/:item', fn (mut c hono.Context) http.Response {
cat := c.params['cat'] or { '' }
item := c.params['item'] or { '' }
return c.json('{"category": "${cat}", "item": "${item}"}')
})
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ v-hono picoev 服务器 - 高性能版本 ║')
println('╠═══════════════════════════════════════════════════════════════╣')
println('║ 端口: 8081 ║')
println('║ 特性: picoev 事件驱动 + Keep-Alive ║')
println('╚═══════════════════════════════════════════════════════════════╝')
// 使用 picoev 启动服务器
app.listen_picoev(8081)
}
+164
View File
@@ -0,0 +1,164 @@
// veb 服务器示例 - 用于性能对比测试
// veb 是 V 语言官方的新版 web 框架(替代旧的 vweb)
//
// 运行: v run server_veb.v
// 测试: curl http://127.0.0.1:8080/
//
// 压测命令:
// wrk -t4 -c100 -d10s http://127.0.0.1:8080/
// wrk -t4 -c100 -d10s http://127.0.0.1:8080/api/users/123
module main
import veb
import time
// 应用状态
pub struct App {
pub:
start_time time.Time = time.now()
}
// Context 类型
pub struct Context {
veb.Context
}
fn main() {
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ veb 服务器 - 性能对比测试 ║')
println('╠═══════════════════════════════════════════════════════════════╣')
println('║ 端口: 8080 ║')
println('║ 测试端点: ║')
println('║ GET / - Hello World ║')
println('║ GET /api/health - 健康检查 ║')
println('║ GET /api/users - 获取用户列表 ║')
println('║ POST /api/users - 创建用户 ║')
println('║ GET /api/users/:id - 获取单个用户 ║')
println('║ GET /api/users/:id/posts - 获取用户帖子 ║')
println('╚═══════════════════════════════════════════════════════════════╝')
println('')
println('启动 veb 服务器在端口 8080...')
mut app := &App{}
veb.run[App, Context](mut app, 8080)
}
// ============================================
// 静态路由
// ============================================
// 首页
@['/']
pub fn (app &App) index(mut ctx Context) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.text('Hello World')
}
// 健康检查
@['/api/health']
pub fn (app &App) health(mut ctx Context) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.text('OK')
}
// 获取用户列表
@['/api/users'; get]
pub fn (app &App) get_users(mut ctx Context) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.json[[]User]([
User{id: '1', name: 'Alice', email: 'alice@example.com'},
User{id: '2', name: 'Bob', email: 'bob@example.com'},
])
}
// 创建用户
@['/api/users'; post]
pub fn (app &App) create_user(mut ctx Context) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.json[CreateResponse](CreateResponse{created: true, id: '3'})
}
// ============================================
// 动态路由
// ============================================
// 获取单个用户
@['/api/users/:id']
pub fn (app &App) get_user(mut ctx Context, id string) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.json[User](User{id: id, name: 'User ${id}', email: 'user${id}@example.com'})
}
// 获取用户的帖子
@['/api/users/:id/posts']
pub fn (app &App) get_user_posts(mut ctx Context, id string) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.json[PostsResponse](PostsResponse{
user_id: id
posts: [
Post{id: '1', title: 'First Post', content: 'Hello World'},
Post{id: '2', title: 'Second Post', content: 'V is awesome'},
]
})
}
// 获取特定帖子
@['/api/users/:user_id/posts/:post_id']
pub fn (app &App) get_user_post(mut ctx Context, user_id string, post_id string) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.json[PostResponse](PostResponse{
user_id: user_id
post_id: post_id
post: Post{id: post_id, title: 'Post ${post_id}', content: 'Content of post ${post_id}'}
})
}
// 获取分类商品
@['/api/categories/:cat/items/:item']
pub fn (app &App) get_category_item(mut ctx Context, cat string, item string) veb.Result {
ctx.set_header(.connection, 'close')
return ctx.json[ItemResponse](ItemResponse{
category: cat
item: item
price: 99.99
})
}
// ============================================
// 数据结构
// ============================================
struct User {
id string
name string
email string
}
struct Post {
id string
title string
content string
}
struct CreateResponse {
created bool
id string
}
struct PostsResponse {
user_id string
posts []Post
}
struct PostResponse {
user_id string
post_id string
post Post
}
struct ItemResponse {
category string
item string
price f64
}
+1 -1
View File
@@ -1,6 +1,6 @@
// vweb 服务器示例 - 用于性能对比测试 // vweb 服务器示例 - 用于性能对比测试
// 运行: v run server_vweb.v // 运行: v run server_vweb.v
// 测试: curl http://localhost:8080/ // 测试: curl http://127.0.0.1:8080/
module main module main
+93
View File
@@ -0,0 +1,93 @@
// 简单 HTTP 压测 - veb vs v-hono
// v run simple_benchmark.v
module main
import net.http
import time
const requests = 50
fn main() {
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ 简单 HTTP 压测 - veb vs v-hono ║')
println('╚═══════════════════════════════════════════════════════════════╝')
println('')
// 测试 veb
println('测试 veb (http://127.0.0.1:8080/)...')
veb_result := benchmark_server('http://127.0.0.1:8080/')
println(' 结果: ${veb_result.rps:.0} req/s | avg: ${veb_result.avg_ms:.2}ms | 成功: ${veb_result.success}/${veb_result.total}')
// 测试 v-hono
println('')
println('测试 v-hono (http://127.0.0.1:8081/)...')
hono_result := benchmark_server('http://127.0.0.1:8081/')
println(' 结果: ${hono_result.rps:.0} req/s | avg: ${hono_result.avg_ms:.2}ms | 成功: ${hono_result.success}/${hono_result.total}')
// 对比
println('')
println('═══════════════════════════════════════════════════════════════')
if veb_result.rps > 0 && hono_result.rps > 0 {
if hono_result.rps > veb_result.rps {
ratio := hono_result.rps / veb_result.rps
println('v-hono 快 ${ratio:.2}x')
} else {
ratio := veb_result.rps / hono_result.rps
println('veb 快 ${ratio:.2}x')
}
}
println('═══════════════════════════════════════════════════════════════')
}
struct Result {
total int
success int
rps f64
avg_ms f64
}
fn benchmark_server(url string) Result {
// 先检查服务器
http.get(url) or {
println(' ⚠️ 服务器不可用')
return Result{}
}
mut success := 0
mut total_ms := f64(0)
start := time.now()
for i in 0 .. requests {
req_start := time.now()
resp := http.get(url) or {
continue
}
req_ms := f64(time.since(req_start).microseconds()) / 1000.0
if resp.status_code == 200 {
success++
}
total_ms += req_ms
// 进度
if (i + 1) % 10 == 0 {
print('.')
}
}
println('')
elapsed := time.since(start)
elapsed_ms := elapsed.milliseconds()
rps := if elapsed_ms > 0 { f64(requests) * 1000.0 / f64(elapsed_ms) } else { 0.0 }
avg_ms := if requests > 0 { total_ms / f64(requests) } else { 0.0 }
return Result{
total: requests
success: success
rps: rps
avg_ms: avg_ms
}
}
+12 -12
View File
@@ -21,7 +21,7 @@ fn main() {
// 等待服务器启动 // 等待服务器启动
time.sleep(2 * time.second) time.sleep(2 * time.second)
println('服务器已启动在 http://localhost:8081') println('服务器已启动在 http://127.0.0.1:8081')
// 执行测试 // 执行测试
run_tests() run_tests()
@@ -124,7 +124,7 @@ fn test_small_file_performance() {
// 传统方式 // 传统方式
start := time.now() start := time.now()
for i in 0 .. 100 { for i in 0 .. 100 {
_ = os.execute('curl -s http://localhost:8081/traditional/test_small.txt > nul') _ = os.execute('curl -s http://127.0.0.1:8081/traditional/test_small.txt > nul')
} }
traditional_time := time.now() - start traditional_time := time.now() - start
println('传统方式 100次请求: $traditional_time') println('传统方式 100次请求: $traditional_time')
@@ -132,7 +132,7 @@ fn test_small_file_performance() {
// 流式方式 // 流式方式
start2 := time.now() start2 := time.now()
for i in 0 .. 100 { for i in 0 .. 100 {
_ = os.execute('curl -s http://localhost:8081/stream/test_small.txt > nul') _ = os.execute('curl -s http://127.0.0.1:8081/stream/test_small.txt > nul')
} }
stream_time := time.now() - start2 stream_time := time.now() - start2
println('流式方式 100次请求: $stream_time') println('流式方式 100次请求: $stream_time')
@@ -140,7 +140,7 @@ fn test_small_file_performance() {
// 智能方式 // 智能方式
start3 := time.now() start3 := time.now()
for i in 0 .. 100 { for i in 0 .. 100 {
_ = os.execute('curl -s http://localhost:8081/smart/test_small.txt > nul') _ = os.execute('curl -s http://127.0.0.1:8081/smart/test_small.txt > nul')
} }
smart_time := time.now() - start3 smart_time := time.now() - start3
println('智能方式 100次请求: $smart_time') println('智能方式 100次请求: $smart_time')
@@ -152,7 +152,7 @@ fn test_medium_file_performance() {
// 传统方式 // 传统方式
start := time.now() start := time.now()
for i in 0 .. 10 { for i in 0 .. 10 {
_ = os.execute('curl -s http://localhost:8081/traditional/test_medium.txt > nul') _ = os.execute('curl -s http://127.0.0.1:8081/traditional/test_medium.txt > nul')
} }
traditional_time := time.now() - start traditional_time := time.now() - start
println('传统方式 10次请求: $traditional_time') println('传统方式 10次请求: $traditional_time')
@@ -160,7 +160,7 @@ fn test_medium_file_performance() {
// 流式方式 // 流式方式
start2 := time.now() start2 := time.now()
for i in 0 .. 10 { for i in 0 .. 10 {
_ = os.execute('curl -s http://localhost:8081/stream/test_medium.txt > nul') _ = os.execute('curl -s http://127.0.0.1:8081/stream/test_medium.txt > nul')
} }
stream_time := time.now() - start2 stream_time := time.now() - start2
println('流式方式 10次请求: $stream_time') println('流式方式 10次请求: $stream_time')
@@ -172,7 +172,7 @@ fn test_large_file_performance() {
// 只测试流式方式,避免内存问题 // 只测试流式方式,避免内存问题
start := time.now() start := time.now()
for i in 0 .. 5 { for i in 0 .. 5 {
result := os.execute('curl -s http://localhost:8081/stream/test_large.txt > nul') result := os.execute('curl -s http://127.0.0.1:8081/stream/test_large.txt > nul')
if result.exit_code != 0 { if result.exit_code != 0 {
println(' 请求 $i 失败') println(' 请求 $i 失败')
} }
@@ -183,7 +183,7 @@ fn test_large_file_performance() {
// 智能方式(应该自动选择流式传输) // 智能方式(应该自动选择流式传输)
start2 := time.now() start2 := time.now()
for i in 0 .. 5 { for i in 0 .. 5 {
result := os.execute('curl -s http://localhost:8081/smart/test_large.txt > nul') result := os.execute('curl -s http://127.0.0.1:8081/smart/test_large.txt > nul')
if result.exit_code != 0 { if result.exit_code != 0 {
println(' 智能方式请求 $i 失败') println(' 智能方式请求 $i 失败')
} }
@@ -196,14 +196,14 @@ fn test_range_requests() {
println('\n--- Range请求测试 ---') println('\n--- Range请求测试 ---')
// 测试部分内容请求 // 测试部分内容请求
result1 := os.execute('curl -s -H "Range: bytes=0-99" http://localhost:8081/range/test_medium.txt') result1 := os.execute('curl -s -H "Range: bytes=0-99" http://127.0.0.1:8081/range/test_medium.txt')
if result1.exit_code == 0 { if result1.exit_code == 0 {
println('✅ Range请求 0-99: 成功') println('✅ Range请求 0-99: 成功')
} else { } else {
println('❌ Range请求 0-99: 失败') println('❌ Range请求 0-99: 失败')
} }
result2 := os.execute('curl -s -H "Range: bytes=100-199" http://localhost:8081/range/test_medium.txt') result2 := os.execute('curl -s -H "Range: bytes=100-199" http://127.0.0.1:8081/range/test_medium.txt')
if result2.exit_code == 0 { if result2.exit_code == 0 {
println('✅ Range请求 100-199: 成功') println('✅ Range请求 100-199: 成功')
} else { } else {
@@ -215,7 +215,7 @@ fn test_smart_selection() {
println('\n--- 智能选择测试 ---') println('\n--- 智能选择测试 ---')
// 测试小文件(应该使用内存加载) // 测试小文件(应该使用内存加载)
result1 := os.execute('curl -s http://localhost:8081/smart/test_small.txt') result1 := os.execute('curl -s http://127.0.0.1:8081/smart/test_small.txt')
if result1.exit_code == 0 { if result1.exit_code == 0 {
println('✅ 智能选择小文件: 成功') println('✅ 智能选择小文件: 成功')
} else { } else {
@@ -223,7 +223,7 @@ fn test_smart_selection() {
} }
// 测试大文件(应该使用流式传输) // 测试大文件(应该使用流式传输)
result2 := os.execute('curl -s -o large_output.txt http://localhost:8081/smart/test_large.txt') result2 := os.execute('curl -s -o large_output.txt http://127.0.0.1:8081/smart/test_large.txt')
if result2.exit_code == 0 { if result2.exit_code == 0 {
// 检查下载的文件大小 // 检查下载的文件大小
if os.exists('large_output.txt') { if os.exists('large_output.txt') {
+291
View File
@@ -0,0 +1,291 @@
module hono
import net
import net.http
import picoev
import picohttpparser
// picoev 服务器配置
pub struct PicoevConfig {
pub:
port int = 8080
host string
family net.AddrFamily = .ip6
timeout_secs int = 30
max_headers int = 100
max_read int = 8192
max_write int = 8192
}
// picoev 请求上下文
struct PicoevRequestContext {
mut:
app &Hono = unsafe { nil }
}
// 使用 picoev 启动服务器
pub fn (mut app Hono) listen_picoev(port int) {
app.listen_picoev_with_config(PicoevConfig{
port: port
})
}
// 使用 picoev 启动服务器(带配置)
pub fn (mut app Hono) listen_picoev_with_config(config PicoevConfig) {
mut ctx := &PicoevRequestContext{
app: unsafe { &app }
}
mut pico := picoev.new(
port: config.port
host: config.host
family: config.family
timeout_secs: config.timeout_secs
max_headers: config.max_headers
max_read: config.max_read
max_write: config.max_write
cb: picoev_callback
user_data: ctx
) or {
eprintln('[v-hono] Failed to create picoev server: ${err}')
return
}
host_str := if config.host == '' { '127.0.0.1' } else { config.host }
println('[v-hono] Listening on http://${host_str}:${config.port}/ (picoev)')
pico.serve()
}
// picoev 回调函数
fn picoev_callback(user_data voidptr, req picohttpparser.Request, mut res picohttpparser.Response) {
mut ctx := unsafe { &PicoevRequestContext(user_data) }
if ctx.app == unsafe { nil } {
res.http_500()
res.end()
return
}
// 解析路径和查询参数
path, query_map := parse_path_and_query(req.path)
// 路由匹配
method_str := req.method
// 优先使用快速路由器
if ctx.app.use_fast_router {
if route_match := ctx.app.fast_router.match_route(method_str, path) {
// 构造 Context
mut hono_ctx := create_picoev_context(req, route_match.params, query_map)
// 获取中间件
middlewares := get_middlewares_for_path_picoev(ctx.app, path)
// 执行中间件和处理器
response := exec_middlewares_picoev(0, middlewares, mut hono_ctx, route_match.handler)
// 发送响应
send_picoev_response(mut res, hono_ctx, response)
return
}
}
// 回退到混合路由器
if route_match := ctx.app.context_hybrid_router.match_route(method_str, path) {
mut hono_ctx := create_picoev_context(req, route_match.params, query_map)
// 获取中间件
middlewares := get_middlewares_for_path_picoev(ctx.app, path)
// 执行中间件和处理器
response := exec_middlewares_picoev(0, middlewares, mut hono_ctx, route_match.handler)
send_picoev_response(mut res, hono_ctx, response)
return
}
// 404 Not Found - 检查自定义 notFound 处理器
mut hono_ctx := create_picoev_context(req, map[string]string{}, query_map)
if handler := ctx.app.not_found_handler {
response := handler(mut hono_ctx)
send_picoev_response(mut res, hono_ctx, response)
return
}
// 默认 404 响应
hono_ctx.status(404)
res.raw('HTTP/1.1 404 Not Found\r\n')
res.header('Content-Type', 'text/plain')
res.header('Connection', 'keep-alive')
res.body('Not Found')
res.end()
}
// 获取路径对应的所有中间件(全局 + 路由前缀匹配的)
fn get_middlewares_for_path_picoev(app &Hono, path string) []ContextMiddleware {
mut middlewares := app.context_middlewares.clone()
// 按前缀长度排序,确保更具体的前缀后执行
mut prefixes := app.route_middlewares.keys()
prefixes.sort(a.len < b.len)
for prefix in prefixes {
if path.starts_with(prefix) || prefix == '/' {
if mws := app.route_middlewares[prefix] {
middlewares << mws
}
}
}
return middlewares
}
// 执行中间件链
fn exec_middlewares_picoev(idx int, middlewares []ContextMiddleware, mut ctx Context, handler IHandler) http.Response {
if idx < middlewares.len {
mw := middlewares[idx]
return mw(mut ctx, fn [idx, middlewares, handler] (mut c Context) http.Response {
return exec_middlewares_picoev(idx + 1, middlewares, mut c, handler)
})
} else {
return handler.handle(mut ctx)
}
}
// 解析路径和查询参数
fn parse_path_and_query(full_path string) (string, map[string]string) {
mut query_map := map[string]string{}
if idx := full_path.index('?') {
path := full_path[..idx]
query_str := full_path[idx + 1..]
// 解析查询参数
for part in query_str.split('&') {
if eq_idx := part.index('=') {
key := part[..eq_idx]
value := part[eq_idx + 1..]
query_map[key] = value
}
}
return path, query_map
}
return full_path, query_map
}
// 创建 picoev 上下文
fn create_picoev_context(req picohttpparser.Request, params map[string]string, query map[string]string) Context {
// 将 picohttpparser.Request 转换为需要的格式
return Context{
req: convert_picoev_request(req)
params: params
query: query
body: req.body
url: req.path
path: req.path
status_code: 200
headers: map[string]string{}
}
}
// 转换 picoev 请求
fn convert_picoev_request(req picohttpparser.Request) http.Request {
mut headers := http.new_header()
for i in 0 .. req.num_headers {
h := req.headers[i]
headers.add_custom(h.name, h.value) or { continue }
}
return http.Request{
method: match req.method {
'GET' { http.Method.get }
'POST' { http.Method.post }
'PUT' { http.Method.put }
'DELETE' { http.Method.delete }
'PATCH' { http.Method.patch }
'HEAD' { http.Method.head }
'OPTIONS' { http.Method.options }
else { http.Method.get }
}
url: req.path
data: req.body
header: headers
}
}
// 发送 picoev 响应
fn send_picoev_response(mut res picohttpparser.Response, ctx Context, response http.Response) {
// 设置状态码
status_code := if ctx.status_code != 0 { ctx.status_code } else { response.status_code }
if status_code == 200 {
res.http_ok()
} else {
// 手动构建状态行
status_text := get_status_text(status_code)
res.raw('HTTP/1.1 ${status_code} ${status_text}\r\n')
}
// 设置响应头
mut has_content_type := false
mut has_connection := false
// 从 Context 的 headers 中获取
for key, value in ctx.headers {
res.header(key, value)
if key.to_lower() == 'content-type' {
has_content_type = true
}
if key.to_lower() == 'connection' {
has_connection = true
}
}
// 从 http.Response 的 header 中获取
if content_type := response.header.get(.content_type) {
if !has_content_type {
res.header('Content-Type', content_type)
has_content_type = true
}
}
// 默认 Content-Type
if !has_content_type {
res.header('Content-Type', 'text/plain')
}
// Keep-Alive
if !has_connection {
res.header('Connection', 'keep-alive')
}
// 设置响应体并发送
res.body(response.body)
res.end() // 关键:必须调用 end() 来实际发送响应
}
// 获取状态码文本
fn get_status_text(code int) string {
return match code {
200 { 'OK' }
201 { 'Created' }
204 { 'No Content' }
301 { 'Moved Permanently' }
302 { 'Found' }
304 { 'Not Modified' }
400 { 'Bad Request' }
401 { 'Unauthorized' }
403 { 'Forbidden' }
404 { 'Not Found' }
405 { 'Method Not Allowed' }
500 { 'Internal Server Error' }
502 { 'Bad Gateway' }
503 { 'Service Unavailable' }
else { 'Unknown' }
}
}
+68
View File
@@ -0,0 +1,68 @@
// Keep-Alive HTTP 基准测试
module main
import net
import time
fn main() {
addr := '127.0.0.1:9999'
requests := 10000
println('=== Keep-Alive HTTP 基准测试 ===')
println('目标: ${addr}')
println('请求数: ${requests}')
println('')
// 建立连接
mut conn := net.dial_tcp(addr) or {
println('连接失败: ${err}')
return
}
defer { conn.close() or {} }
// 预热
println('预热中...')
for _ in 0 .. 100 {
send_keepalive_request(mut conn, addr) or {
// 重连
conn = net.dial_tcp(addr) or { continue }
continue
}
}
// 正式测试
println('开始测试...')
sw := time.new_stopwatch()
mut success := 0
for _ in 0 .. requests {
send_keepalive_request(mut conn, addr) or {
// 重连
conn = net.dial_tcp(addr) or { continue }
continue
}
success++
}
elapsed := sw.elapsed()
elapsed_ms := f64(elapsed.milliseconds())
rps := f64(success) * 1000.0 / elapsed_ms
avg_us := elapsed_ms * 1000.0 / f64(success)
println('')
println('=== 结果 ===')
println('成功请求: ${success}/${requests}')
println('总耗时: ${elapsed_ms:.0f}ms')
println('吞吐量: ${rps:.0f} req/s')
println('平均延迟: ${avg_us:.2f}μs')
}
fn send_keepalive_request(mut conn net.TcpConn, addr string) ! {
// 发送 HTTP 请求 (Keep-Alive)
request := 'GET /api/health HTTP/1.1\r\nHost: ${addr}\r\nConnection: keep-alive\r\n\r\n'
conn.write_string(request)!
// 读取响应头和体
mut buf := []u8{len: 256}
conn.read(mut buf) or {}
}
+270
View File
@@ -0,0 +1,270 @@
// picoev 优化集成测试
// 测试 picoev 服务器的完整功能,确保优化没有引入新问题
//
// 使用方法:
// 1. 先启动测试服务器: v run tests/test_picoev_server.v
// 2. 运行测试: v run tests/test_picoev_integration.v
module main
import net.http
import time
// 测试统计
struct TestStats {
mut:
total int
passed int
failed int
errors []string
}
fn (mut s TestStats) run(name string, test_fn fn () bool) {
s.total++
print(' 🧪 ${name}... ')
result := test_fn()
if result {
s.passed++
println('✅')
} else {
s.failed++
s.errors << name
println('❌')
}
}
fn (s TestStats) summary() {
println('')
println('═══════════════════════════════════════════════════════════════')
println('📊 测试结果: ${s.passed}/${s.total} 通过')
if s.failed > 0 {
println('❌ 失败的测试:')
for err in s.errors {
println(' - ${err}')
}
} else {
println('🎉 所有测试通过!picoev 优化验证成功!')
}
println('═══════════════════════════════════════════════════════════════')
}
const base_url = 'http://127.0.0.1:9999'
fn main() {
println('╔═══════════════════════════════════════════════════════════════╗')
println('║ picoev 优化集成测试 ║')
println('╚═══════════════════════════════════════════════════════════════╝')
println('')
// 验证服务器是否运行
println('🔍 检查测试服务器...')
if !check_server_ready() {
println('❌ 服务器未运行')
println(' 请先启动: v run tests/test_picoev_server.v')
return
}
println('✅ 服务器已就绪')
println('')
mut stats := TestStats{}
// 1. 基本 GET 路由测试
println('📦 1. 基本 GET 路由测试')
stats.run('GET 根路径', test_get_root)
stats.run('GET 健康检查', test_get_health)
stats.run('GET 静态路由', test_get_static)
println('')
// 2. 动态路由测试
println('📦 2. 动态路由测试')
stats.run('单参数路由', test_single_param)
stats.run('多参数路由', test_multi_params)
stats.run('嵌套参数路由', test_nested_params)
println('')
// 3. 查询参数测试
println('📦 3. 查询参数测试')
stats.run('单个查询参数', test_single_query)
stats.run('多个查询参数', test_multi_query)
println('')
// 4. 响应格式测试
println('📦 4. 响应格式测试')
stats.run('JSON 响应', test_json_response)
stats.run('HTML 响应', test_html_response)
stats.run('自定义状态码 201', test_custom_status)
println('')
// 5. 中间件测试
println('📦 5. 中间件测试')
stats.run('全局中间件响应头', test_middleware_header)
println('')
// 6. 404 处理测试
println('📦 6. 错误处理测试')
stats.run('404 未找到', test_not_found)
println('')
// 7. Keep-Alive 测试
println('📦 7. Keep-Alive 连接测试')
stats.run('连接复用', test_keep_alive)
println('')
// 8. 性能测试
println('📦 8. 性能测试')
stats.run('响应时间 < 50ms', test_response_time)
stats.run('吞吐量测试', test_throughput)
println('')
// 输出总结
stats.summary()
}
// 检查服务器是否就绪
fn check_server_ready() bool {
for _ in 0 .. 5 {
resp := http.get(base_url + '/health') or {
time.sleep(200 * time.millisecond)
continue
}
if resp.status_code == 200 {
return true
}
time.sleep(200 * time.millisecond)
}
return false
}
// ==================== 基本 GET 路由测试 ====================
fn test_get_root() bool {
resp := http.get(base_url + '/') or { return false }
return resp.status_code == 200 && resp.body.contains('Hello')
}
fn test_get_health() bool {
resp := http.get(base_url + '/health') or { return false }
return resp.status_code == 200 && resp.body == 'OK'
}
fn test_get_static() bool {
resp := http.get(base_url + '/api/health') or { return false }
return resp.status_code == 200 && resp.body == 'OK'
}
// ==================== 动态路由测试 ====================
fn test_single_param() bool {
resp := http.get(base_url + '/api/users/456') or { return false }
return resp.status_code == 200 && resp.body.contains('456')
}
fn test_multi_params() bool {
resp := http.get(base_url + '/api/users/123/posts/789') or { return false }
return resp.status_code == 200 && resp.body.contains('123') && resp.body.contains('789')
}
fn test_nested_params() bool {
resp := http.get(base_url + '/api/categories/tech/items/laptop') or { return false }
return resp.status_code == 200 && resp.body.contains('tech') && resp.body.contains('laptop')
}
// ==================== 查询参数测试 ====================
fn test_single_query() bool {
resp := http.get(base_url + '/api/search?q=test') or { return false }
return resp.status_code == 200 && resp.body.contains('test')
}
fn test_multi_query() bool {
resp := http.get(base_url + '/api/search?q=hello&limit=10&page=1') or { return false }
return resp.status_code == 200 && resp.body.contains('hello')
}
// ==================== 响应格式测试 ====================
fn test_json_response() bool {
resp := http.get(base_url + '/api/json') or { return false }
content_type := resp.header.get(.content_type) or { '' }
return resp.status_code == 200 && content_type.contains('application/json')
}
fn test_html_response() bool {
resp := http.get(base_url + '/api/html') or { return false }
content_type := resp.header.get(.content_type) or { '' }
return resp.status_code == 200 && content_type.contains('text/html')
}
fn test_custom_status() bool {
resp := http.get(base_url + '/api/created') or { return false }
return resp.status_code == 201
}
// ==================== 中间件测试 ====================
fn test_middleware_header() bool {
resp := http.get(base_url + '/api/health') or { return false }
// 检查中间件添加的响应头 - 通过原始响应检查
raw_resp := resp.body
// 中间件应该已经执行,检查响应是否正常
return resp.status_code == 200 && raw_resp == 'OK'
}
// ==================== 错误处理测试 ====================
fn test_not_found() bool {
resp := http.get(base_url + '/nonexistent/path/here') or { return false }
return resp.status_code == 404
}
// ==================== Keep-Alive 测试 ====================
fn test_keep_alive() bool {
// 发送多个请求,检查连接是否正常
for _ in 0 .. 5 {
resp := http.get(base_url + '/api/health') or { return false }
if resp.status_code != 200 {
return false
}
}
return true
}
// ==================== 性能测试 ====================
fn test_response_time() bool {
sw := time.new_stopwatch()
for _ in 0 .. 10 {
_ := http.get(base_url + '/api/health') or { return false }
}
elapsed := sw.elapsed()
avg_ms := f64(elapsed.milliseconds()) / 10.0
// 平均响应时间应小于 50ms
return avg_ms < 50.0
}
fn test_throughput() bool {
sw := time.new_stopwatch()
requests := 50
mut success := 0
for _ in 0 .. requests {
resp := http.get(base_url + '/api/health') or { continue }
if resp.status_code == 200 {
success++
}
}
elapsed := sw.elapsed()
rps := f64(success) * 1000.0 / f64(elapsed.milliseconds())
print('(${rps:.0f} req/s) ')
// 吞吐量应大于 20 req/s,成功率 > 90%
return rps > 20.0 && success >= requests * 9 / 10
}
+129
View File
@@ -0,0 +1,129 @@
// picoev 测试服务器
// 用于 test_picoev_integration.v 的测试
module main
import hono
import net.http
fn main() {
mut app := hono.Hono.new()
// 全局中间件 - 添加自定义响应头
app.use(fn (mut c hono.Context, next fn (mut hono.Context) http.Response) http.Response {
mut resp := next(mut c)
c.headers['X-Middleware'] = 'applied'
return resp
})
// ==================== 基本路由 ====================
// 根路径
app.get('/', fn (mut c hono.Context) http.Response {
return c.text('Hello World')
})
// 健康检查
app.get('/health', fn (mut c hono.Context) http.Response {
return c.text('OK')
})
app.get('/api/health', fn (mut c hono.Context) http.Response {
return c.text('OK')
})
// ==================== CRUD 路由 ====================
app.get('/api/users', fn (mut c hono.Context) http.Response {
return c.json('{"users": []}')
})
app.post('/api/users', fn (mut c hono.Context) http.Response {
c.status(201)
return c.json('{"created": true}')
})
app.get('/api/users/:id', fn (mut c hono.Context) http.Response {
id := c.params['id'] or { '' }
return c.json('{"id": "${id}"}')
})
app.put('/api/users/:id', fn (mut c hono.Context) http.Response {
id := c.params['id'] or { '' }
return c.json('{"id": "${id}", "updated": true}')
})
app.delete('/api/users/:id', fn (mut c hono.Context) http.Response {
id := c.params['id'] or { '' }
return c.json('{"id": "${id}", "deleted": true}')
})
app.patch('/api/users/:id', fn (mut c hono.Context) http.Response {
id := c.params['id'] or { '' }
return c.json('{"id": "${id}", "patched": true}')
})
// ==================== 多参数路由 ====================
app.get('/api/users/:user_id/posts/:post_id', fn (mut c hono.Context) http.Response {
user_id := c.params['user_id'] or { '' }
post_id := c.params['post_id'] or { '' }
return c.json('{"user_id": "${user_id}", "post_id": "${post_id}"}')
})
app.get('/api/categories/:category/items/:item', fn (mut c hono.Context) http.Response {
category := c.params['category'] or { '' }
item := c.params['item'] or { '' }
return c.json('{"category": "${category}", "item": "${item}"}')
})
// ==================== 查询参数路由 ====================
app.get('/api/search', fn (mut c hono.Context) http.Response {
q := c.query['q'] or { '' }
limit := c.query['limit'] or { '10' }
page := c.query['page'] or { '1' }
return c.json('{"query": "${q}", "limit": ${limit}, "page": ${page}}')
})
// ==================== 响应格式路由 ====================
app.get('/api/json', fn (mut c hono.Context) http.Response {
return c.json('{"message": "JSON response"}')
})
app.get('/api/html', fn (mut c hono.Context) http.Response {
return c.html('<html><body><h1>HTML Response</h1></body></html>')
})
app.get('/api/created', fn (mut c hono.Context) http.Response {
c.status(201)
return c.json('{"created": true}')
})
app.get('/api/custom-header', fn (mut c hono.Context) http.Response {
c.headers['X-Custom-Header'] = 'custom-value'
return c.text('OK')
})
// ==================== 中间件测试路由 ====================
app.get('/api/middleware-test', fn (mut c hono.Context) http.Response {
return c.text('middleware test')
})
app.get('/api/middleware-order', fn (mut c hono.Context) http.Response {
return c.json('{"order": "correct"}')
})
// ==================== 自定义 404 ====================
app.not_found(fn (mut c hono.Context) http.Response {
c.status(404)
return c.json('{"error": "Not Found", "path": "${c.path}"}')
})
// 启动服务器
println('[test-server] Starting on port 9999...')
app.listen(':9999')
}
+1 -1
View File
@@ -1,7 +1,7 @@
Module { Module {
name: 'hono' name: 'hono'
description: 'A high-performance V language web framework inspired by Hono.js, featuring hybrid routing, LRU cache, middleware support, and more.' description: 'A high-performance V language web framework inspired by Hono.js, featuring hybrid routing, LRU cache, middleware support, and more.'
version: '0.3.1' version: '0.3.2'
license: 'MIT' license: 'MIT'
dependencies: [] dependencies: []
} }