Compare commits

...
6 Commits
Author SHA1 Message Date
meiseayoung ed55ce758e fix: HTTP/1.0 connection not closing causes ab benchmark to hang
- Add HTTP version detection in parse_http_request_usockets
- Return Connection: close header for HTTP/1.0 requests
- Call shutdown() for graceful connection close after HTTP/1.0 response
- Fix missing is_http11 parameter in WebSocket upgrade handler

Root cause: ab uses HTTP/1.0 by default, but server always returned
Connection: keep-alive, causing ab to wait for server to close connection.
2026-01-06 15:58:24 +08:00
meiseayoung 22e8dd4548 chore: update v.mod version 2026-01-06 14:11:40 +08:00
meiseayoung a702746df7 build(usockets): add pre-compiled library for MacOS-arm64 platform 2026-01-06 14:10:02 +08:00
meiseayoung a57ce2e77a chore: update v.mod version 2026-01-06 12:01:45 +08:00
meiseayoung 7a08c3142a build(usockets): add pre-compiled library for MacOS-x86 platform 2026-01-06 12:01:20 +08:00
meiseayoung 362015c1cf docs: update build instructions for uSockets backend
- Clarify that Windows requires gcc (tcc does not support .a static library format)
- Add separate build commands for macOS/Linux and Windows
- Document pre-compiled library locations by platform
- Add w64devkit as recommended MinGW download option
2026-01-05 12:25:06 +08:00
5 changed files with 72 additions and 27 deletions
+26 -9
View File
@@ -50,8 +50,13 @@ cd v-hono
### Standard Build (picoev backend) ### Standard Build (picoev backend)
```bash ```bash
v your_app.v -o app.exe # macOS / Linux
./app.exe v -prod -o app your_app.v
./app
# Windows
v -o app.exe your_app.v
.\app.exe
``` ```
### High-Performance Build (uSockets backend) ### High-Performance Build (uSockets backend)
@@ -59,25 +64,33 @@ v your_app.v -o app.exe
For maximum performance with uSockets backend: For maximum performance with uSockets backend:
```bash ```bash
v -enable-globals -cc gcc -ldflags "-ldbghelp" your_app.v -o app.exe # macOS / Linux
./app.exe v -enable-globals -prod -o app your_app.v
./app
# Windows (must use gcc, tcc does not support .a static library format)
v -enable-globals -cc gcc -ldflags "-ldbghelp" -o app.exe your_app.v
.\app.exe
``` ```
**Note**: The `-enable-globals` flag is required for uSockets backend. The `-ldflags "-ldbghelp"` fixes Windows linking issues. **Important Notes:**
- The `-enable-globals` flag is required for uSockets backend
- On Windows, you **must** use `-cc gcc` because V's default compiler (tcc) does not support MinGW `.a` static library format
- The `-ldflags "-ldbghelp"` is required on Windows for libuv linking
#### Windows Prerequisites #### Windows Prerequisites
To use uSockets backend on Windows, you need: To use uSockets backend on Windows, you need:
1. **GCC (MinGW-w64)** - C compiler 1. **GCC (MinGW-w64)** - C compiler (required, tcc is not supported)
```powershell ```powershell
# Install via Scoop # Install via Scoop (recommended)
scoop install mingw scoop install mingw
# Or via Chocolatey # Or via Chocolatey
choco install mingw choco install mingw
# Or download from: https://www.mingw-w64.org/downloads/ # Or download w64devkit from: https://github.com/skeeto/w64devkit/releases
``` ```
2. **V Compiler** - Version 0.4.x or later 2. **V Compiler** - Version 0.4.x or later
@@ -94,7 +107,11 @@ To use uSockets backend on Windows, you need:
v version # Should show V 0.4.x v version # Should show V 0.4.x
``` ```
The uSockets library and libuv are pre-compiled and included in the `usockets/lib/` directory. The uSockets library and libuv are pre-compiled and included in the `usockets/lib/{platform}/` directory:
- `usockets/lib/windows/` - Windows x64
- `usockets/lib/linux/` - Linux x64
- `usockets/lib/macos-arm64/` - macOS Apple Silicon
- `usockets/lib/macos-x64/` - macOS Intel
### Using uSockets Backend ### Using uSockets Backend
Binary file not shown.
Binary file not shown.
+45 -17
View File
@@ -183,7 +183,7 @@ fn handle_usockets_ws_upgrade(s usockets.Socket, raw_data string, route_match Co
// Check if this is a WebSocket upgrade response // Check if this is a WebSocket upgrade response
if response.status_code != 101 { if response.status_code != 101 {
// Not a WebSocket upgrade, send the response as-is // Not a WebSocket upgrade, send the response as-is
send_usockets_response(s, mutable_ctx, response) send_usockets_response(s, mutable_ctx, response, true)
return false return false
} }
@@ -227,7 +227,7 @@ fn usockets_on_data(s usockets.Socket, data &char, length int) usockets.Socket {
// 解析 HTTP 请求 // 解析 HTTP 请求
raw_data := unsafe { tos(&u8(data), length) } raw_data := unsafe { tos(&u8(data), length) }
method, path, query_map, body := parse_http_request_usockets(raw_data) method, path, query_map, body, is_http11 := parse_http_request_usockets(raw_data)
if method.len == 0 { if method.len == 0 {
s.write_bytes('HTTP/1.1 400 Bad Request\r\nContent-Length: 11\r\n\r\nBad Request') s.write_bytes('HTTP/1.1 400 Bad Request\r\nContent-Length: 11\r\n\r\nBad Request')
@@ -259,11 +259,11 @@ fn usockets_on_data(s usockets.Socket, data &char, length int) usockets.Socket {
// 优化1:零中间件快速路径 // 优化1:零中间件快速路径
if !g_usockets_app.has_middlewares { if !g_usockets_app.has_middlewares {
response := route_match.handler.handle(mut hono_ctx) response := route_match.handler.handle(mut hono_ctx)
send_usockets_response(s, hono_ctx, response) send_usockets_response(s, hono_ctx, response, is_http11)
} else { } else {
middlewares := get_middlewares_for_path_usockets_optimized(g_usockets_app, path) middlewares := get_middlewares_for_path_usockets_optimized(g_usockets_app, path)
response := exec_middlewares_usockets(0, middlewares, mut hono_ctx, route_match.handler) response := exec_middlewares_usockets(0, middlewares, mut hono_ctx, route_match.handler)
send_usockets_response(s, hono_ctx, response) send_usockets_response(s, hono_ctx, response, is_http11)
} }
response_sent = true response_sent = true
} }
@@ -289,11 +289,11 @@ fn usockets_on_data(s usockets.Socket, data &char, length int) usockets.Socket {
// 优化1:零中间件快速路径 // 优化1:零中间件快速路径
if !g_usockets_app.has_middlewares { if !g_usockets_app.has_middlewares {
response := route_match.handler.handle(mut hono_ctx) response := route_match.handler.handle(mut hono_ctx)
send_usockets_response(s, hono_ctx, response) send_usockets_response(s, hono_ctx, response, is_http11)
} else { } else {
middlewares := get_middlewares_for_path_usockets_optimized(g_usockets_app, path) middlewares := get_middlewares_for_path_usockets_optimized(g_usockets_app, path)
response := exec_middlewares_usockets(0, middlewares, mut hono_ctx, route_match.handler) response := exec_middlewares_usockets(0, middlewares, mut hono_ctx, route_match.handler)
send_usockets_response(s, hono_ctx, response) send_usockets_response(s, hono_ctx, response, is_http11)
} }
response_sent = true response_sent = true
} }
@@ -305,9 +305,13 @@ fn usockets_on_data(s usockets.Socket, data &char, length int) usockets.Socket {
if handler := g_usockets_app.not_found_handler { if handler := g_usockets_app.not_found_handler {
response := handler(mut hono_ctx) response := handler(mut hono_ctx)
send_usockets_response(s, hono_ctx, response) send_usockets_response(s, hono_ctx, response, is_http11)
} else { } else {
s.write_bytes('HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 9\r\nConnection: keep-alive\r\n\r\nNot Found') conn_header := if is_http11 { 'keep-alive' } else { 'close' }
s.write_bytes('HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 9\r\nConnection: ${conn_header}\r\n\r\nNot Found')
if !is_http11 {
s.shutdown()
}
} }
} }
@@ -334,15 +338,17 @@ fn usockets_on_end(s usockets.Socket) usockets.Socket {
// 解析 HTTP 请求 - 零分配优化版 // 解析 HTTP 请求 - 零分配优化版
// 使用指针遍历避免创建临时数组 // 使用指针遍历避免创建临时数组
fn parse_http_request_usockets(raw string) (string, string, map[string]string, string) { // 返回: (method, path, query_map, body, is_http11)
fn parse_http_request_usockets(raw string) (string, string, map[string]string, string, bool) {
mut method := '' mut method := ''
mut path := '' mut path := ''
mut query_map := map[string]string{} mut query_map := map[string]string{}
mut body := '' mut body := ''
mut is_http11 := true
len := raw.len len := raw.len
if len == 0 { if len == 0 {
return method, path, query_map, body return method, path, query_map, body, is_http11
} }
// 1. 查找第一行结束位置(\r\n) // 1. 查找第一行结束位置(\r\n)
@@ -354,7 +360,7 @@ fn parse_http_request_usockets(raw string) (string, string, map[string]string, s
} }
} }
if line_end == -1 { if line_end == -1 {
return method, path, query_map, body return method, path, query_map, body, is_http11
} }
// 2. 解析 method(到第一个空格) // 2. 解析 method(到第一个空格)
@@ -363,7 +369,7 @@ fn parse_http_request_usockets(raw string) (string, string, map[string]string, s
method_end++ method_end++
} }
if method_end == 0 || method_end >= line_end { if method_end == 0 || method_end >= line_end {
return method, path, query_map, body return method, path, query_map, body, is_http11
} }
method = raw[..method_end] method = raw[..method_end]
@@ -391,7 +397,18 @@ fn parse_http_request_usockets(raw string) (string, string, map[string]string, s
path = raw[path_start..path_end] path = raw[path_start..path_end]
} }
// 4. 查找 body(\r\n\r\n 之后) // 4. 检测 HTTP 版本 - "HTTP/1.0" vs "HTTP/1.1"
// 请求行格式: "GET /path HTTP/1.1\r\n"
mut version_start := path_end + 1
for version_start < line_end && raw[version_start] == ` ` {
version_start++
}
if version_start < line_end {
version_str := raw[version_start..line_end]
is_http11 = !version_str.contains('1.0')
}
// 5. 查找 body(\r\n\r\n 之后)
for i in line_end .. len - 3 { for i in line_end .. len - 3 {
if raw[i] == `\r` && raw[i + 1] == `\n` && raw[i + 2] == `\r` && raw[i + 3] == `\n` { if raw[i] == `\r` && raw[i + 1] == `\n` && raw[i + 2] == `\r` && raw[i + 3] == `\n` {
if i + 4 < len { if i + 4 < len {
@@ -401,7 +418,7 @@ fn parse_http_request_usockets(raw string) (string, string, map[string]string, s
} }
} }
return method, path, query_map, body return method, path, query_map, body, is_http11
} }
// 快速解析 query string(单次遍历,避免 split) // 快速解析 query string(单次遍历,避免 split)
@@ -576,7 +593,7 @@ fn exec_middlewares_usockets(idx int, middlewares []ContextMiddleware, mut ctx C
} }
// 发送 uSockets 响应 - 优化版 // 发送 uSockets 响应 - 优化版
fn send_usockets_response(s usockets.Socket, ctx Context, response http.Response) { fn send_usockets_response(s usockets.Socket, ctx Context, response http.Response, is_http11 bool) {
// Check if this is a streaming response // Check if this is a streaming response
if is_streaming_response(ctx) { if is_streaming_response(ctx) {
// Handle streaming response // Handle streaming response
@@ -630,14 +647,25 @@ fn send_usockets_response(s usockets.Socket, ctx Context, response http.Response
resp.write_string(response.body.len.str()) resp.write_string(response.body.len.str())
resp.write_string('\r\n') resp.write_string('\r\n')
// Connection // Connection - HTTP/1.0 用 close,HTTP/1.1 用 keep-alive
resp.write_string('Connection: keep-alive\r\n') if is_http11 {
resp.write_string('Connection: keep-alive\r\n')
} else {
resp.write_string('Connection: close\r\n')
}
// 空行 + body // 空行 + body
resp.write_string('\r\n') resp.write_string('\r\n')
resp.write_string(response.body) resp.write_string(response.body)
s.write_bytes(resp.str()) s.write_bytes(resp.str())
// HTTP/1.0 请求完成后关闭连接
// 只调用 shutdown() 发送 FIN,让 on_end 回调处理 close()
// 这样可以确保发送缓冲区的数据完全发送
if !is_http11 {
s.shutdown()
}
} }
// ============================================================================ // ============================================================================
+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.7.4' version: '0.7.7'
license: 'MIT' license: 'MIT'
dependencies: [] dependencies: []
} }