Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2afd947d0 | ||
|
|
cfde1b4306 | ||
|
|
0a4e5d50f3 | ||
|
|
ed55ce758e | ||
|
|
22e8dd4548 | ||
|
|
a702746df7 | ||
|
|
a57ce2e77a | ||
|
|
7a08c3142a | ||
|
|
362015c1cf |
@@ -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.
+125
-18
@@ -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,25 @@ 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"
|
||||||
|
// HTTP/1.x 版本字符串固定为 8 字符,直接检查第 8 个字符
|
||||||
|
mut version_start := path_end + 1
|
||||||
|
for version_start < line_end && raw[version_start] == ` ` {
|
||||||
|
version_start++
|
||||||
|
}
|
||||||
|
// "HTTP/1.0" 或 "HTTP/1.1",检查索引 7 的字符(0-based)
|
||||||
|
if version_start + 7 < line_end {
|
||||||
|
is_http11 = raw[version_start + 7] != `0`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于 HTTP/1.0,检查 Connection 头是否包含 keep-alive
|
||||||
|
// 参考 Go net/http: wantsHttp10KeepAlive() 只检查 Connection 头的值
|
||||||
|
if !is_http11 {
|
||||||
|
is_http11 = has_connection_keep_alive(raw, line_end, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,10 +425,82 @@ 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)
|
// 检查 Connection 头是否包含 keep-alive(参考 Go net/http 实现)
|
||||||
|
// 只检查 "Connection:" 头的值,避免误判请求体中的内容
|
||||||
|
@[inline]
|
||||||
|
fn has_connection_keep_alive(raw string, headers_start int, len int) bool {
|
||||||
|
mut i := headers_start
|
||||||
|
|
||||||
|
// 遍历每一行头部
|
||||||
|
for i < len - 12 { // 至少需要 "Connection:" + 一些值
|
||||||
|
// 跳过 \r\n
|
||||||
|
if raw[i] == `\r` && i + 1 < len && raw[i + 1] == `\n` {
|
||||||
|
i += 2
|
||||||
|
// 检查是否到达头部结束(空行)
|
||||||
|
if i < len - 1 && raw[i] == `\r` && raw[i + 1] == `\n` {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是 "Connection:" 头(不区分大小写)
|
||||||
|
if (raw[i] == `C` || raw[i] == `c`) && i + 11 < len {
|
||||||
|
// 检查 "onnection:"
|
||||||
|
if (raw[i+1] == `o` || raw[i+1] == `O`) &&
|
||||||
|
(raw[i+2] == `n` || raw[i+2] == `N`) &&
|
||||||
|
(raw[i+3] == `n` || raw[i+3] == `N`) &&
|
||||||
|
(raw[i+4] == `e` || raw[i+4] == `E`) &&
|
||||||
|
(raw[i+5] == `c` || raw[i+5] == `C`) &&
|
||||||
|
(raw[i+6] == `t` || raw[i+6] == `T`) &&
|
||||||
|
(raw[i+7] == `i` || raw[i+7] == `I`) &&
|
||||||
|
(raw[i+8] == `o` || raw[i+8] == `O`) &&
|
||||||
|
(raw[i+9] == `n` || raw[i+9] == `N`) &&
|
||||||
|
raw[i+10] == `:` {
|
||||||
|
// 找到 Connection 头,检查值是否包含 keep-alive
|
||||||
|
mut value_start := i + 11
|
||||||
|
// 跳过空格
|
||||||
|
for value_start < len && raw[value_start] == ` ` {
|
||||||
|
value_start++
|
||||||
|
}
|
||||||
|
// 查找行尾
|
||||||
|
mut value_end := value_start
|
||||||
|
for value_end < len && raw[value_end] != `\r` && raw[value_end] != `\n` {
|
||||||
|
value_end++
|
||||||
|
}
|
||||||
|
// 在值中查找 "keep-alive"(不区分大小写)
|
||||||
|
return contains_keep_alive_token(raw, value_start, value_end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查字符串片段是否包含 keep-alive token
|
||||||
|
@[inline]
|
||||||
|
fn contains_keep_alive_token(raw string, start int, end int) bool {
|
||||||
|
// 查找 "keep-alive" 或 "Keep-Alive"(支持多值如 "keep-alive, upgrade")
|
||||||
|
mut i := start
|
||||||
|
for i <= end - 10 {
|
||||||
|
if raw[i] == `k` || raw[i] == `K` {
|
||||||
|
if (raw[i+1] == `e` || raw[i+1] == `E`) &&
|
||||||
|
(raw[i+2] == `e` || raw[i+2] == `E`) &&
|
||||||
|
(raw[i+3] == `p` || raw[i+3] == `P`) &&
|
||||||
|
raw[i+4] == `-` &&
|
||||||
|
(raw[i+5] == `a` || raw[i+5] == `A`) &&
|
||||||
|
(raw[i+6] == `l` || raw[i+6] == `L`) &&
|
||||||
|
(raw[i+7] == `i` || raw[i+7] == `I`) &&
|
||||||
|
(raw[i+8] == `v` || raw[i+8] == `V`) &&
|
||||||
|
(raw[i+9] == `e` || raw[i+9] == `E`) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}// 快速解析 query string(单次遍历,避免 split)
|
||||||
@[inline]
|
@[inline]
|
||||||
fn parse_query_string_fast(raw string, start int, end int) map[string]string {
|
fn parse_query_string_fast(raw string, start int, end int) map[string]string {
|
||||||
mut query_map := map[string]string{}
|
mut query_map := map[string]string{}
|
||||||
@@ -576,7 +672,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 +726,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,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.8'
|
||||||
license: 'MIT'
|
license: 'MIT'
|
||||||
dependencies: []
|
dependencies: []
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user