Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed55ce758e | ||
|
|
22e8dd4548 | ||
|
|
a702746df7 | ||
|
|
a57ce2e77a | ||
|
|
7a08c3142a | ||
|
|
362015c1cf | ||
|
|
0969901481 | ||
|
|
6bddfc3731 | ||
|
|
26ac804b1d | ||
|
|
56299b5485 | ||
|
|
d31492fd59 | ||
|
|
e19f799e46 | ||
|
|
f7e753969d | ||
|
|
e9d14a35f6 |
@@ -2,7 +2,6 @@
|
||||
*.exe
|
||||
*.out
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.dll
|
||||
|
||||
|
||||
@@ -50,8 +50,13 @@ cd v-hono
|
||||
### Standard Build (picoev backend)
|
||||
|
||||
```bash
|
||||
v your_app.v -o app.exe
|
||||
./app.exe
|
||||
# macOS / Linux
|
||||
v -prod -o app your_app.v
|
||||
./app
|
||||
|
||||
# Windows
|
||||
v -o app.exe your_app.v
|
||||
.\app.exe
|
||||
```
|
||||
|
||||
### High-Performance Build (uSockets backend)
|
||||
@@ -59,25 +64,33 @@ v your_app.v -o app.exe
|
||||
For maximum performance with uSockets backend:
|
||||
|
||||
```bash
|
||||
v -enable-globals -cc gcc -ldflags "-ldbghelp" your_app.v -o app.exe
|
||||
./app.exe
|
||||
# macOS / Linux
|
||||
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
|
||||
|
||||
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
|
||||
# Install via Scoop
|
||||
# Install via Scoop (recommended)
|
||||
scoop install mingw
|
||||
|
||||
# Or via Chocolatey
|
||||
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
|
||||
@@ -94,7 +107,11 @@ To use uSockets backend on Windows, you need:
|
||||
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
|
||||
|
||||
|
||||
@@ -448,21 +448,49 @@ func checkKeepAlive(client *http.Client) bool {
|
||||
|
||||
func checkThroughput(client *http.Client) float64 {
|
||||
start := time.Now()
|
||||
success := 0
|
||||
requests := 200
|
||||
concurrency := 1000
|
||||
totalRequests := 1000000
|
||||
requestsPerWorker := totalRequests / concurrency
|
||||
|
||||
for i := 0; i < requests; i++ {
|
||||
resp, err := client.Get(baseURL + "/health")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == 200 {
|
||||
success++
|
||||
}
|
||||
// 创建共享的 Transport,启用连接池复用
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: concurrency,
|
||||
MaxIdleConnsPerHost: concurrency,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DisableKeepAlives: false,
|
||||
}
|
||||
|
||||
results := make(chan int, concurrency)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
localSuccess := 0
|
||||
// 每个 goroutine 使用独立 Client,但共享 Transport(连接池)
|
||||
localClient := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: transport,
|
||||
}
|
||||
for j := 0; j < requestsPerWorker; j++ {
|
||||
resp, err := localClient.Get(baseURL + "/health")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == 200 {
|
||||
localSuccess++
|
||||
}
|
||||
}
|
||||
results <- localSuccess
|
||||
}()
|
||||
}
|
||||
|
||||
success := 0
|
||||
for i := 0; i < concurrency; i++ {
|
||||
success += <-results
|
||||
}
|
||||
|
||||
elapsed := time.Since(start).Seconds()
|
||||
fmt.Printf("\n [并发: %d, 总请求: %d, 成功: %d, 耗时: %.2fs]\n ", concurrency, totalRequests, success, elapsed)
|
||||
return float64(success) / elapsed
|
||||
}
|
||||
|
||||
+41
-17
@@ -583,8 +583,8 @@ fn send_picoev_response(mut res picohttpparser.Response, ctx Context, response h
|
||||
|
||||
// handle_picoev_streaming_response - Handle a streaming response using PicoevStreamWriter
|
||||
// This function:
|
||||
// 1. Sends the HTTP headers for streaming
|
||||
// 2. Creates a PicoevStreamWriter
|
||||
// 1. Sends the HTTP headers for streaming directly to socket fd
|
||||
// 2. Creates a PicoevStreamWriter with the socket fd
|
||||
// 3. Executes the streaming callback
|
||||
// 4. Handles errors and cleanup
|
||||
fn handle_picoev_streaming_response(mut res picohttpparser.Response, ctx Context, config PicoevConfig) {
|
||||
@@ -601,14 +601,10 @@ fn handle_picoev_streaming_response(mut res picohttpparser.Response, ctx Context
|
||||
// Determine status code
|
||||
status_code := if ctx.status_code != 0 { ctx.status_code } else { 200 }
|
||||
|
||||
// Send HTTP response line
|
||||
if status_code == 200 {
|
||||
res.http_ok()
|
||||
} else {
|
||||
res.raw('HTTP/1.1 ${status_code} ${get_status_text(status_code)}\r\n')
|
||||
}
|
||||
// Build HTTP headers string for direct socket write
|
||||
mut headers_str := 'HTTP/1.1 ${status_code} ${get_status_text(status_code)}\r\n'
|
||||
|
||||
// Send headers from context
|
||||
// Add headers from context
|
||||
mut has_transfer_encoding := false
|
||||
mut has_connection := false
|
||||
|
||||
@@ -618,7 +614,7 @@ fn handle_picoev_streaming_response(mut res picohttpparser.Response, ctx Context
|
||||
if key_len == 14 && eq_ignore_case(key, 'content-length') {
|
||||
continue
|
||||
}
|
||||
res.header(key, value)
|
||||
headers_str += '${key}: ${value}\r\n'
|
||||
if key_len == 17 && eq_ignore_case(key, 'transfer-encoding') {
|
||||
has_transfer_encoding = true
|
||||
} else if key_len == 10 && eq_ignore_case(key, 'connection') {
|
||||
@@ -628,20 +624,27 @@ fn handle_picoev_streaming_response(mut res picohttpparser.Response, ctx Context
|
||||
|
||||
// Ensure Transfer-Encoding: chunked is set
|
||||
if !has_transfer_encoding {
|
||||
res.header('Transfer-Encoding', 'chunked')
|
||||
headers_str += 'Transfer-Encoding: chunked\r\n'
|
||||
}
|
||||
|
||||
// Ensure Connection header is set for streaming
|
||||
if !has_connection {
|
||||
res.header('Connection', 'keep-alive')
|
||||
res.header('Keep-Alive', 'timeout=${config.timeout_secs}')
|
||||
headers_str += 'Connection: keep-alive\r\n'
|
||||
headers_str += 'Keep-Alive: timeout=${config.timeout_secs}\r\n'
|
||||
}
|
||||
|
||||
// End headers section (empty body to signal headers are complete)
|
||||
// Note: We don't call res.body() or res.end() here because we're streaming
|
||||
res.raw('\r\n')
|
||||
// End headers section
|
||||
headers_str += '\r\n'
|
||||
|
||||
// Create PicoevStreamWriter for streaming
|
||||
// Write headers directly to socket fd for immediate delivery
|
||||
fd := res.fd
|
||||
write_to_socket_fd(fd, headers_str.bytes()) or {
|
||||
eprintln('[Picoev Stream Error] Failed to write headers: ${err.msg()}')
|
||||
cleanup_stream_config(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Create PicoevStreamWriter for streaming (uses fd directly)
|
||||
mut writer := PicoevStreamWriter.new(res)
|
||||
|
||||
// Create StreamContext with the writer
|
||||
@@ -665,6 +668,27 @@ fn handle_picoev_streaming_response(mut res picohttpparser.Response, ctx Context
|
||||
cleanup_stream_config(ctx)
|
||||
}
|
||||
|
||||
// write_to_socket_fd - Helper function to write bytes directly to socket fd
|
||||
// Used for sending HTTP headers before streaming begins
|
||||
fn write_to_socket_fd(fd int, data []u8) ! {
|
||||
if data.len == 0 {
|
||||
return
|
||||
}
|
||||
mut total_written := 0
|
||||
for total_written < data.len {
|
||||
remaining := data.len - total_written
|
||||
ptr := unsafe { &u8(data.data) + total_written }
|
||||
written := C.send(fd, ptr, remaining, 0)
|
||||
if written < 0 {
|
||||
return error('Failed to write to socket')
|
||||
}
|
||||
if written == 0 {
|
||||
return error('Connection closed by peer')
|
||||
}
|
||||
total_written += written
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态码文本
|
||||
fn get_status_text(code int) string {
|
||||
return match code {
|
||||
|
||||
+45
-13
@@ -8,6 +8,9 @@ import time
|
||||
import picohttpparser
|
||||
import usockets
|
||||
|
||||
// C function declaration for direct socket writes
|
||||
fn C.send(sockfd int, buf voidptr, len int, flags int) int
|
||||
|
||||
// ============================================================================
|
||||
// SSE Event Structure
|
||||
// ============================================================================
|
||||
@@ -184,23 +187,26 @@ pub fn (mut ctx StreamContext) trigger_abort() {
|
||||
|
||||
// PicoevStreamWriter - StreamWriter implementation for picoev server
|
||||
// Supports chunked transfer encoding for streaming responses
|
||||
// Uses direct socket fd writes for immediate data delivery (true streaming)
|
||||
@[heap]
|
||||
pub struct PicoevStreamWriter {
|
||||
mut:
|
||||
res &picohttpparser.Response
|
||||
fd int // Socket file descriptor for direct writes
|
||||
connected bool
|
||||
}
|
||||
|
||||
// PicoevStreamWriter.new - Create a new PicoevStreamWriter with the given response
|
||||
// Extracts the fd from picohttpparser.Response for direct socket writes
|
||||
pub fn PicoevStreamWriter.new(res &picohttpparser.Response) &PicoevStreamWriter {
|
||||
return &PicoevStreamWriter{
|
||||
res: unsafe { res }
|
||||
fd: res.fd
|
||||
connected: true
|
||||
}
|
||||
}
|
||||
|
||||
// write - Write raw bytes to the stream using chunked transfer encoding
|
||||
// Format: {hex_size}\r\n{data}\r\n
|
||||
// Writes directly to socket fd for immediate delivery
|
||||
pub fn (mut w PicoevStreamWriter) write(data []u8) ! {
|
||||
if !w.connected {
|
||||
return error('Connection closed')
|
||||
@@ -210,9 +216,10 @@ pub fn (mut w PicoevStreamWriter) write(data []u8) ! {
|
||||
}
|
||||
// Write chunked format: size in hex, CRLF, data, CRLF
|
||||
chunk_header := format_chunk_size(data.len)
|
||||
w.res.raw(chunk_header)
|
||||
w.res.raw(data.bytestr())
|
||||
w.res.raw('\r\n')
|
||||
// Write directly to socket fd
|
||||
w.write_to_fd(chunk_header.bytes())!
|
||||
w.write_to_fd(data)!
|
||||
w.write_to_fd('\r\n'.bytes())!
|
||||
}
|
||||
|
||||
// write_string - Write a string to the stream using chunked transfer encoding
|
||||
@@ -225,15 +232,40 @@ pub fn (mut w PicoevStreamWriter) write_string(data string) ! {
|
||||
}
|
||||
// Write chunked format: size in hex, CRLF, data, CRLF
|
||||
chunk_header := format_chunk_size(data.len)
|
||||
w.res.raw(chunk_header)
|
||||
w.res.raw(data)
|
||||
w.res.raw('\r\n')
|
||||
// Write directly to socket fd
|
||||
w.write_to_fd(chunk_header.bytes())!
|
||||
w.write_to_fd(data.bytes())!
|
||||
w.write_to_fd('\r\n'.bytes())!
|
||||
}
|
||||
|
||||
// flush - Flush the buffer (picoev handles this automatically)
|
||||
// write_to_fd - Write bytes directly to the socket file descriptor
|
||||
// This ensures immediate delivery without buffering
|
||||
@[inline]
|
||||
fn (mut w PicoevStreamWriter) write_to_fd(data []u8) ! {
|
||||
if data.len == 0 {
|
||||
return
|
||||
}
|
||||
mut total_written := 0
|
||||
for total_written < data.len {
|
||||
remaining := data.len - total_written
|
||||
ptr := unsafe { &u8(data.data) + total_written }
|
||||
written := C.send(w.fd, ptr, remaining, 0)
|
||||
if written < 0 {
|
||||
w.connected = false
|
||||
return error('Failed to write to socket')
|
||||
}
|
||||
if written == 0 {
|
||||
w.connected = false
|
||||
return error('Connection closed by peer')
|
||||
}
|
||||
total_written += written
|
||||
}
|
||||
}
|
||||
|
||||
// flush - Flush the buffer (direct fd writes are already unbuffered)
|
||||
pub fn (mut w PicoevStreamWriter) flush() ! {
|
||||
// picoev automatically flushes data
|
||||
// No explicit flush needed
|
||||
// Direct fd writes are unbuffered, no explicit flush needed
|
||||
// But we can use TCP_NODELAY to disable Nagle's algorithm if needed
|
||||
}
|
||||
|
||||
// close - Close the stream by sending the final chunk
|
||||
@@ -241,9 +273,9 @@ pub fn (mut w PicoevStreamWriter) flush() ! {
|
||||
pub fn (mut w PicoevStreamWriter) close() ! {
|
||||
if w.connected {
|
||||
// Write the final chunk (zero-length chunk) to signal end of stream
|
||||
w.res.raw('0\r\n\r\n')
|
||||
w.res.end()
|
||||
w.write_to_fd('0\r\n\r\n'.bytes()) or {}
|
||||
w.connected = false
|
||||
// Note: Don't close the fd here, picoev manages the connection lifecycle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+94
-27
@@ -1,13 +1,36 @@
|
||||
#!/bin/bash
|
||||
# uSockets 编译脚本
|
||||
# 用于重新编译 libusockets_full.a
|
||||
# 用于重新编译 libusockets_full.a (包含 uSockets + libuv)
|
||||
# 支持: Windows (Git Bash/MSYS2), macOS (Intel/Apple Silicon), Linux
|
||||
# 输出到: usockets/lib/{platform}/libusockets_full.a
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
USOCKETS_DIR="/tmp/uSockets"
|
||||
LIBUV_DIR="/tmp/libuv"
|
||||
MERGE_DIR="/tmp/merge_libs"
|
||||
|
||||
echo "=== uSockets 编译脚本 ==="
|
||||
echo "检测到系统: $OSTYPE"
|
||||
|
||||
# 确定输出目录
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
if [[ $(uname -m) == "arm64" ]]; then
|
||||
OUTPUT_DIR="$SCRIPT_DIR/lib/macos-arm64"
|
||||
else
|
||||
OUTPUT_DIR="$SCRIPT_DIR/lib/macos-x64"
|
||||
fi
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "mingw"* ]]; then
|
||||
OUTPUT_DIR="$SCRIPT_DIR/lib/windows"
|
||||
elif [[ "$OSTYPE" == "linux"* ]]; then
|
||||
OUTPUT_DIR="$SCRIPT_DIR/lib/linux"
|
||||
else
|
||||
OUTPUT_DIR="$SCRIPT_DIR/lib/linux"
|
||||
fi
|
||||
|
||||
echo "输出目录: $OUTPUT_DIR"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# 检查是否有 uSockets 源码
|
||||
if [ ! -d "$USOCKETS_DIR" ]; then
|
||||
@@ -15,41 +38,85 @@ if [ ! -d "$USOCKETS_DIR" ]; then
|
||||
git clone --depth 1 https://github.com/uNetworking/uSockets.git "$USOCKETS_DIR"
|
||||
fi
|
||||
|
||||
# 检查是否有 libuv 源码
|
||||
if [ ! -d "$LIBUV_DIR" ]; then
|
||||
echo "克隆 libuv..."
|
||||
git clone --depth 1 --branch v1.48.0 https://github.com/libuv/libuv.git "$LIBUV_DIR"
|
||||
fi
|
||||
|
||||
# 应用 backlog 修改
|
||||
echo "应用 backlog=16384 修改..."
|
||||
if [ -f "$SCRIPT_DIR/src/bsd.c" ]; then
|
||||
cp "$SCRIPT_DIR/src/bsd.c" "$USOCKETS_DIR/src/bsd.c"
|
||||
else
|
||||
# 如果没有本地修改的源码,直接修改
|
||||
sed -i.bak 's/listen(listenFd, 512)/listen(listenFd, 16384)/g' "$USOCKETS_DIR/src/bsd.c"
|
||||
fi
|
||||
|
||||
# 编译
|
||||
echo "编译 uSockets..."
|
||||
cd "$USOCKETS_DIR"
|
||||
make clean
|
||||
|
||||
# 根据系统选择编译参数
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
if [[ $(uname -m) == "arm64" ]]; then
|
||||
# Apple Silicon
|
||||
WITH_LIBUV=1 CFLAGS="-I/opt/homebrew/include" make
|
||||
else
|
||||
# Intel Mac
|
||||
WITH_LIBUV=1 CFLAGS="-I/usr/local/include" make
|
||||
# 编译 libuv
|
||||
compile_libuv() {
|
||||
echo "编译 libuv..."
|
||||
cd "$LIBUV_DIR"
|
||||
rm -rf build 2>/dev/null || true
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
if ! command -v cmake &> /dev/null; then
|
||||
echo "错误: 需要安装 cmake"
|
||||
echo " macOS: brew install cmake"
|
||||
echo " Linux: sudo apt install cmake 或 sudo yum install cmake"
|
||||
echo " Windows: scoop install cmake 或 choco install cmake"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Linux
|
||||
WITH_LIBUV=1 make
|
||||
fi
|
||||
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "mingw"* ]]; then
|
||||
cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DLIBUV_BUILD_SHARED=OFF
|
||||
else
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DLIBUV_BUILD_SHARED=OFF
|
||||
fi
|
||||
|
||||
cmake --build . --config Release
|
||||
}
|
||||
|
||||
# 复制到 v-hono
|
||||
echo "复制库文件..."
|
||||
cp "$USOCKETS_DIR/uSockets.a" "$SCRIPT_DIR/../lib/libusockets_full.a"
|
||||
# 编译 uSockets
|
||||
compile_usockets() {
|
||||
echo "编译 uSockets..."
|
||||
cd "$USOCKETS_DIR"
|
||||
rm -f *.o *.a 2>/dev/null || true
|
||||
|
||||
gcc -DLIBUS_NO_SSL -DLIBUS_USE_LIBUV -std=c11 -Isrc -I"$LIBUV_DIR/include" -O3 -c src/*.c src/eventing/*.c src/crypto/*.c
|
||||
ar rcs uSockets.a *.o
|
||||
rm -f *.o
|
||||
}
|
||||
|
||||
echo "=== 编译完成 ==="
|
||||
echo "库文件: $SCRIPT_DIR/../lib/libusockets_full.a"
|
||||
# 合并库文件
|
||||
merge_libraries() {
|
||||
echo "合并库文件..."
|
||||
rm -rf "$MERGE_DIR"
|
||||
mkdir -p "$MERGE_DIR"
|
||||
|
||||
cd "$MERGE_DIR"
|
||||
ar x "$USOCKETS_DIR/uSockets.a"
|
||||
ar x "$LIBUV_DIR/build/libuv.a"
|
||||
|
||||
ar rcs "$OUTPUT_DIR/libusockets_full.a" *.o
|
||||
|
||||
rm -rf "$MERGE_DIR"
|
||||
}
|
||||
|
||||
# 执行编译
|
||||
compile_libuv
|
||||
compile_usockets
|
||||
merge_libraries
|
||||
|
||||
# 显示结果
|
||||
LIB_SIZE=$(ls -lh "$OUTPUT_DIR/libusockets_full.a" | awk '{print $5}')
|
||||
echo ""
|
||||
echo "重新编译 v-hono 应用:"
|
||||
echo " v -enable-globals -prod -o your_app your_app.v"
|
||||
echo "=== 编译完成 ==="
|
||||
echo "库文件: $OUTPUT_DIR/libusockets_full.a ($LIB_SIZE)"
|
||||
echo ""
|
||||
echo "编译 v-hono 应用:"
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "mingw"* ]]; then
|
||||
echo " v -enable-globals -cc gcc -ldflags \"-ldbghelp\" -o app.exe app.v"
|
||||
else
|
||||
echo " v -enable-globals -prod -o app app.v"
|
||||
fi
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
-23
@@ -2,41 +2,36 @@ module usockets
|
||||
|
||||
// 条件编译:根据操作系统选择正确的路径和库
|
||||
// @VMODROOT 指向包含 v.mod 的模块根目录 (v-hono/)
|
||||
// 预编译库按平台存放在 usockets/lib/{platform}/ 目录
|
||||
|
||||
$if windows {
|
||||
#flag -DLIBUS_USE_LIBUV
|
||||
#flag -DLIBUS_NO_SSL
|
||||
#flag -I@VMODROOT/usockets/include
|
||||
#flag -L@VMODROOT/lib
|
||||
#flag @VMODROOT/lib/libusockets_full.a
|
||||
#flag -lws2_32 -liphlpapi -lpsapi -luserenv -lole32
|
||||
#flag -L@VMODROOT/usockets/lib/windows
|
||||
#flag @VMODROOT/usockets/lib/windows/libusockets_full.a
|
||||
#flag -lws2_32 -liphlpapi -lpsapi -luserenv -lole32 -ldbghelp
|
||||
} $else $if macos {
|
||||
// macOS: 支持 Apple Silicon (M1/M2/M3) 和 Intel Mac
|
||||
#flag -DLIBUS_USE_LIBUV
|
||||
#flag -DLIBUS_NO_SSL
|
||||
|
||||
// libuv 路径 - 根据架构选择 Homebrew 路径
|
||||
$if arm64 {
|
||||
#flag -I/opt/homebrew/include
|
||||
#flag -L/opt/homebrew/lib
|
||||
} $else {
|
||||
#flag -I/usr/local/include
|
||||
#flag -L/usr/local/lib
|
||||
}
|
||||
|
||||
// uSockets 库路径
|
||||
#flag -I@VMODROOT/usockets/include
|
||||
#flag -L@VMODROOT/lib
|
||||
#flag @VMODROOT/lib/libusockets_full.a
|
||||
#flag -luv
|
||||
|
||||
$if arm64 {
|
||||
// Apple Silicon (M1/M2/M3)
|
||||
#flag -L@VMODROOT/usockets/lib/macos-arm64
|
||||
#flag @VMODROOT/usockets/lib/macos-arm64/libusockets_full.a
|
||||
} $else {
|
||||
// Intel Mac
|
||||
#flag -L@VMODROOT/usockets/lib/macos-x64
|
||||
#flag @VMODROOT/usockets/lib/macos-x64/libusockets_full.a
|
||||
}
|
||||
} $else {
|
||||
// Linux 和其他平台
|
||||
#flag -DLIBUS_USE_LIBUV
|
||||
#flag -DLIBUS_NO_SSL
|
||||
#flag -I@VMODROOT/usockets/include
|
||||
#flag -L@VMODROOT/lib
|
||||
#flag @VMODROOT/lib/libusockets_full.a
|
||||
#flag -luv
|
||||
#flag -L@VMODROOT/usockets/lib/linux
|
||||
#flag @VMODROOT/usockets/lib/linux/libusockets_full.a
|
||||
}
|
||||
|
||||
#include "libusockets.h"
|
||||
@@ -105,8 +100,6 @@ pub fn (ctx SocketContext) on_writable(h voidptr) { C.us_socket_context_on_writa
|
||||
pub fn (ctx SocketContext) on_timeout(h voidptr) { C.us_socket_context_on_timeout(0, ctx, h) }
|
||||
pub fn (ctx SocketContext) on_end(h voidptr) { C.us_socket_context_on_end(0, ctx, h) }
|
||||
pub fn (ctx SocketContext) listen(port int) ListenSocket {
|
||||
// 参数: ssl=0, context, host=nil, port, options=LIBUS_LISTEN_DEFAULT(0), socket_ext_size=0
|
||||
// 注意: backlog 是在 uSockets 库内部硬编码的,不通过此函数传递
|
||||
return C.us_socket_context_listen(0, ctx, unsafe { nil }, port, 0, 0)
|
||||
}
|
||||
|
||||
|
||||
+45
-17
@@ -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
|
||||
if response.status_code != 101 {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ fn usockets_on_data(s usockets.Socket, data &char, length int) usockets.Socket {
|
||||
|
||||
// 解析 HTTP 请求
|
||||
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 {
|
||||
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:零中间件快速路径
|
||||
if !g_usockets_app.has_middlewares {
|
||||
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 {
|
||||
middlewares := get_middlewares_for_path_usockets_optimized(g_usockets_app, path)
|
||||
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
|
||||
}
|
||||
@@ -289,11 +289,11 @@ fn usockets_on_data(s usockets.Socket, data &char, length int) usockets.Socket {
|
||||
// 优化1:零中间件快速路径
|
||||
if !g_usockets_app.has_middlewares {
|
||||
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 {
|
||||
middlewares := get_middlewares_for_path_usockets_optimized(g_usockets_app, path)
|
||||
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
|
||||
}
|
||||
@@ -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 {
|
||||
response := handler(mut hono_ctx)
|
||||
send_usockets_response(s, hono_ctx, response)
|
||||
send_usockets_response(s, hono_ctx, response, is_http11)
|
||||
} 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 请求 - 零分配优化版
|
||||
// 使用指针遍历避免创建临时数组
|
||||
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 path := ''
|
||||
mut query_map := map[string]string{}
|
||||
mut body := ''
|
||||
mut is_http11 := true
|
||||
|
||||
len := raw.len
|
||||
if len == 0 {
|
||||
return method, path, query_map, body
|
||||
return method, path, query_map, body, is_http11
|
||||
}
|
||||
|
||||
// 1. 查找第一行结束位置(\r\n)
|
||||
@@ -354,7 +360,7 @@ fn parse_http_request_usockets(raw string) (string, string, map[string]string, s
|
||||
}
|
||||
}
|
||||
if line_end == -1 {
|
||||
return method, path, query_map, body
|
||||
return method, path, query_map, body, is_http11
|
||||
}
|
||||
|
||||
// 2. 解析 method(到第一个空格)
|
||||
@@ -363,7 +369,7 @@ fn parse_http_request_usockets(raw string) (string, string, map[string]string, s
|
||||
method_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]
|
||||
|
||||
@@ -391,7 +397,18 @@ fn parse_http_request_usockets(raw string) (string, string, map[string]string, s
|
||||
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 {
|
||||
if raw[i] == `\r` && raw[i + 1] == `\n` && raw[i + 2] == `\r` && raw[i + 3] == `\n` {
|
||||
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)
|
||||
@@ -576,7 +593,7 @@ fn exec_middlewares_usockets(idx int, middlewares []ContextMiddleware, mut ctx C
|
||||
}
|
||||
|
||||
// 发送 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
|
||||
if is_streaming_response(ctx) {
|
||||
// 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('\r\n')
|
||||
|
||||
// Connection
|
||||
resp.write_string('Connection: keep-alive\r\n')
|
||||
// Connection - HTTP/1.0 用 close,HTTP/1.1 用 keep-alive
|
||||
if is_http11 {
|
||||
resp.write_string('Connection: keep-alive\r\n')
|
||||
} else {
|
||||
resp.write_string('Connection: close\r\n')
|
||||
}
|
||||
|
||||
// 空行 + body
|
||||
resp.write_string('\r\n')
|
||||
resp.write_string(response.body)
|
||||
|
||||
s.write_bytes(resp.str())
|
||||
|
||||
// HTTP/1.0 请求完成后关闭连接
|
||||
// 只调用 shutdown() 发送 FIN,让 on_end 回调处理 close()
|
||||
// 这样可以确保发送缓冲区的数据完全发送
|
||||
if !is_http11 {
|
||||
s.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user