Add `set_forwarded_host` and `set_forwarded_proto` options to control whether the proxy automatically sets these headers. This fixes issues with upstream servers that validate X-Forwarded-Host against known hosts. Changes: - Add SetForwardedHost/SetForwardedProto fields to ProxyHeaders struct - Modify SetForwardedHeaders and WriteForwardedHeaders function signatures - Update modifyRequestHeaders to read config and pass control parameters - Update WebSocket call chain to support new config - Add unit tests for new functionality - Update default config generation (-g) to include new options Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
398 lines
10 KiB
Go
398 lines
10 KiB
Go
// Package proxy 提供 WebSocket 代理模块的性能基准测试。
|
||
//
|
||
// 该文件测试 WebSocket 代理模块各项操作的性能,包括:
|
||
// - 握手升级请求构建性能
|
||
// - 不同帧大小的数据转发性能
|
||
// - Ping/Pong 心跳往返延迟
|
||
// - 并发连接吞吐量
|
||
//
|
||
// 注意:WebSocket 代理在 TCP 层进行透明转发,不解析 WebSocket 帧协议。
|
||
// 帧编码/解码开销由底层 TCP 数据传输的编解码开销代表。
|
||
//
|
||
// 作者:xfy
|
||
package proxy
|
||
|
||
import (
|
||
"net"
|
||
"net/http"
|
||
"strings"
|
||
"sync"
|
||
"testing"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
)
|
||
|
||
// BenchmarkWebSocketHandshake 基准测试 WebSocket 握手升级请求构建性能。
|
||
//
|
||
// 测试 buildWebSocketUpgradeRequest() 函数的开销,包括:
|
||
// - 路径和查询参数处理
|
||
// - 请求头复制(Upgrade、Connection、Sec-WebSocket-*)
|
||
// - X-Forwarded 代理头注入
|
||
//
|
||
// 模拟真实客户端请求,包含常见的 WebSocket 握手头。
|
||
func BenchmarkWebSocketHandshake(b *testing.B) {
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
|
||
for b.Loop() {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/ws?token=abc123&channel=default")
|
||
ctx.Request.Header.SetHost("client.example.com")
|
||
ctx.Request.Header.Set("Upgrade", "websocket")
|
||
ctx.Request.Header.Set("Connection", "Upgrade")
|
||
ctx.Request.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
|
||
ctx.Request.Header.Set("Sec-WebSocket-Version", "13")
|
||
ctx.Request.Header.Set("Sec-WebSocket-Protocol", "chat, superchat")
|
||
ctx.Request.Header.Set("Sec-WebSocket-Extensions", "permessage-deflate")
|
||
ctx.Request.Header.Set("Origin", "https://example.com")
|
||
|
||
result := buildWebSocketUpgradeRequest(ctx, "backend.example.com:8080", nil)
|
||
|
||
// 验证握手请求包含关键头
|
||
if !strings.Contains(result, "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==") {
|
||
b.Fatal("handshake missing Sec-WebSocket-Key")
|
||
}
|
||
if !strings.Contains(result, "Upgrade: websocket") {
|
||
b.Fatal("handshake missing Upgrade header")
|
||
}
|
||
}
|
||
}
|
||
|
||
// BenchmarkWebSocketFrameSmall 基准测试小帧(<126 bytes)发送接收性能。
|
||
//
|
||
// 测试 copyData() 函数处理小数据块的开销。小帧对应 WebSocket
|
||
// 协议中 length < 126 的场景,帧头仅需 2 bytes(无 extended payload)。
|
||
// 这是最常见的 WebSocket 帧大小(聊天消息、心跳、状态更新等)。
|
||
func BenchmarkWebSocketFrameSmall(b *testing.B) {
|
||
smallData := make([]byte, 100) // 100 bytes < 126
|
||
for i := range smallData {
|
||
smallData[i] = byte(i % 256)
|
||
}
|
||
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
|
||
b.RunParallel(func(pb *testing.PB) {
|
||
for pb.Next() {
|
||
src1, src2 := net.Pipe()
|
||
dst1, dst2 := net.Pipe()
|
||
|
||
bridge := &WebSocketBridge{}
|
||
|
||
// 启动数据复制
|
||
done := make(chan struct{})
|
||
go func() {
|
||
_ = bridge.copyData(dst1, src1, "bench-small")
|
||
close(done)
|
||
}()
|
||
|
||
// 发送数据
|
||
go func() {
|
||
_, _ = src2.Write(smallData)
|
||
_ = src2.Close()
|
||
}()
|
||
|
||
// 读取数据
|
||
buf := make([]byte, len(smallData))
|
||
n, err := dst2.Read(buf)
|
||
if err != nil {
|
||
b.Fatalf("read error: %v", err)
|
||
}
|
||
if n != len(smallData) {
|
||
b.Fatalf("expected %d bytes, got %d", len(smallData), n)
|
||
}
|
||
|
||
// 清理
|
||
_ = dst1.Close()
|
||
_ = dst2.Close()
|
||
<-done
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkWebSocketFrameMedium 基准测试中等帧(126-65535 bytes)发送接收性能。
|
||
//
|
||
// 测试 copyData() 函数处理中等数据块的开销。中等帧对应 WebSocket
|
||
// 协议中 126 <= length < 65536 的场景,帧头需要 4 bytes
|
||
// (2 bytes 基础头 + 2 bytes extended payload length)。
|
||
// 典型场景:JSON payloads、小型文件传输、表单数据。
|
||
func BenchmarkWebSocketFrameMedium(b *testing.B) {
|
||
mediumData := make([]byte, 32*1024) // 32KB,典型中等帧大小
|
||
for i := range mediumData {
|
||
mediumData[i] = byte(i % 256)
|
||
}
|
||
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
|
||
b.RunParallel(func(pb *testing.PB) {
|
||
for pb.Next() {
|
||
src1, src2 := net.Pipe()
|
||
dst1, dst2 := net.Pipe()
|
||
|
||
bridge := &WebSocketBridge{}
|
||
|
||
done := make(chan struct{})
|
||
go func() {
|
||
_ = bridge.copyData(dst1, src1, "bench-medium")
|
||
close(done)
|
||
}()
|
||
|
||
go func() {
|
||
_, _ = src2.Write(mediumData)
|
||
_ = src2.Close()
|
||
}()
|
||
|
||
// 循环读取完整数据(可能分多次 Read)
|
||
received := 0
|
||
buf := make([]byte, len(mediumData))
|
||
for received < len(mediumData) {
|
||
n, err := dst2.Read(buf[received:])
|
||
if err != nil {
|
||
b.Fatalf("read error at offset %d: %v", received, err)
|
||
}
|
||
received += n
|
||
}
|
||
if received != len(mediumData) {
|
||
b.Fatalf("expected %d bytes, got %d", len(mediumData), received)
|
||
}
|
||
|
||
_ = dst1.Close()
|
||
_ = dst2.Close()
|
||
<-done
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkWebSocketFrameLarge 基准测试大帧(>65535 bytes)发送接收性能。
|
||
//
|
||
// 测试 copyData() 函数处理大数据块的开销。大帧对应 WebSocket
|
||
// 协议中 length >= 65536 的场景,帧头需要 10 bytes
|
||
// (2 bytes 基础头 + 8 bytes extended payload length)。
|
||
// 典型场景:文件传输、视频帧、大数据集推送。
|
||
//
|
||
// 使用 32KB 内部缓冲区,大帧需要多次 Read/Write 循环。
|
||
func BenchmarkWebSocketFrameLarge(b *testing.B) {
|
||
largeData := make([]byte, 128*1024) // 128KB > 65535
|
||
for i := range largeData {
|
||
largeData[i] = byte(i % 256)
|
||
}
|
||
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
|
||
b.RunParallel(func(pb *testing.PB) {
|
||
for pb.Next() {
|
||
src1, src2 := net.Pipe()
|
||
dst1, dst2 := net.Pipe()
|
||
|
||
bridge := &WebSocketBridge{}
|
||
|
||
done := make(chan struct{})
|
||
go func() {
|
||
_ = bridge.copyData(dst1, src1, "bench-large")
|
||
close(done)
|
||
}()
|
||
|
||
go func() {
|
||
_, _ = src2.Write(largeData)
|
||
_ = src2.Close()
|
||
}()
|
||
|
||
// 循环读取完整数据
|
||
received := 0
|
||
buf := make([]byte, len(largeData))
|
||
for received < len(largeData) {
|
||
n, err := dst2.Read(buf[received:])
|
||
if err != nil {
|
||
b.Fatalf("read error at offset %d: %v", received, err)
|
||
}
|
||
received += n
|
||
}
|
||
if received != len(largeData) {
|
||
b.Fatalf("expected %d bytes, got %d", len(largeData), received)
|
||
}
|
||
|
||
_ = dst1.Close()
|
||
_ = dst2.Close()
|
||
<-done
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkWebSocketPingPong 基准测试 Ping/Pong 心跳往返性能。
|
||
//
|
||
// WebSocket 代理在 TCP 层透明转发,Ping/Pong 控制帧由底层
|
||
// TCP 连接直接传递。本测试模拟 Ping/Pong 的往返延迟:
|
||
// - 发送小数据包(Ping 帧,通常 0-125 bytes payload)
|
||
// - 等待对端接收并返回(Pong 帧)
|
||
// - 测量完整往返时间
|
||
//
|
||
// 使用 16-byte 模拟 Ping payload,这是最常见的心跳大小。
|
||
func BenchmarkWebSocketPingPong(b *testing.B) {
|
||
pingPayload := []byte("ping-1234567890") // 16 bytes,模拟 Ping 帧
|
||
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
|
||
b.RunParallel(func(pb *testing.PB) {
|
||
for pb.Next() {
|
||
client1, client2 := net.Pipe()
|
||
target1, target2 := net.Pipe()
|
||
|
||
// 启动双向桥接
|
||
bridge := NewWebSocketBridge(client1, target1)
|
||
bridgeDone := make(chan struct{})
|
||
go func() {
|
||
_ = bridge.Bridge()
|
||
close(bridgeDone)
|
||
}()
|
||
|
||
// 客户端发送 Ping
|
||
go func() {
|
||
_, _ = client2.Write(pingPayload)
|
||
}()
|
||
|
||
// 后端读取 Ping 并回传 Pong
|
||
buf := make([]byte, len(pingPayload))
|
||
n, err := target2.Read(buf)
|
||
if err != nil {
|
||
b.Fatalf("target read error: %v", err)
|
||
}
|
||
if n != len(pingPayload) {
|
||
b.Fatalf("expected %d bytes, got %d", len(pingPayload), n)
|
||
}
|
||
|
||
// 后端回传 Pong
|
||
go func() {
|
||
_, _ = target2.Write(buf[:n])
|
||
}()
|
||
|
||
// 客户端读取 Pong
|
||
pongBuf := make([]byte, len(pingPayload))
|
||
n, err = client2.Read(pongBuf)
|
||
if err != nil {
|
||
b.Fatalf("client read error: %v", err)
|
||
}
|
||
if n != len(pingPayload) {
|
||
b.Fatalf("expected %d bytes Pong, got %d", len(pingPayload), n)
|
||
}
|
||
|
||
// 清理
|
||
_ = client2.Close()
|
||
_ = target2.Close()
|
||
<-bridgeDone
|
||
}
|
||
})
|
||
}
|
||
|
||
// BenchmarkWebSocketConcurrent 基准测试并发连接吞吐量。
|
||
//
|
||
// 模拟多个 WebSocket 连接同时进行双向数据转发的场景。
|
||
// 测试 Bridge() 函数在高并发下的性能表现,包括:
|
||
// - 多个 goroutine 同时读写
|
||
// - sync.Mutex 保护关闭状态的开销
|
||
// - 双向数据转发的总吞吐量
|
||
//
|
||
// 每个连接发送 4KB 数据,模拟典型的 WebSocket 消息大小。
|
||
func BenchmarkWebSocketConcurrent(b *testing.B) {
|
||
messageSize := 4 * 1024 // 4KB 典型消息大小
|
||
message := make([]byte, messageSize)
|
||
for i := range message {
|
||
message[i] = byte(i % 256)
|
||
}
|
||
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
|
||
for b.Loop() {
|
||
client1, client2 := net.Pipe()
|
||
target1, target2 := net.Pipe()
|
||
|
||
bridge := NewWebSocketBridge(client1, target1)
|
||
|
||
// 启动桥接
|
||
bridgeDone := make(chan struct{})
|
||
go func() {
|
||
_ = bridge.Bridge()
|
||
close(bridgeDone)
|
||
}()
|
||
|
||
var wg sync.WaitGroup
|
||
wg.Add(2)
|
||
|
||
// 客户端 -> 后端
|
||
go func() {
|
||
defer wg.Done()
|
||
_, _ = client2.Write(message)
|
||
}()
|
||
|
||
// 后端读取客户端数据
|
||
go func() {
|
||
defer wg.Done()
|
||
buf := make([]byte, messageSize)
|
||
received := 0
|
||
for received < messageSize {
|
||
n, err := target2.Read(buf[received:])
|
||
if err != nil {
|
||
break
|
||
}
|
||
received += n
|
||
}
|
||
}()
|
||
|
||
wg.Wait()
|
||
|
||
// 清理
|
||
_ = client2.Close()
|
||
_ = target2.Close()
|
||
<-bridgeDone
|
||
}
|
||
}
|
||
|
||
// BenchmarkWebSocketWriteUpgradeResponse 基准测试升级响应写入性能。
|
||
//
|
||
// 测试 writeUpgradeResponse() 函数将 HTTP 101 响应写回客户端的开销。
|
||
// 包含响应行构建、头格式化和单次 Write 调用。
|
||
func BenchmarkWebSocketWriteUpgradeResponse(b *testing.B) {
|
||
resp := &http.Response{
|
||
ProtoMajor: 1,
|
||
ProtoMinor: 1,
|
||
Status: "101 Switching Protocols",
|
||
StatusCode: 101,
|
||
Header: http.Header{
|
||
"Upgrade": []string{"websocket"},
|
||
"Connection": []string{"Upgrade"},
|
||
"Sec-WebSocket-Accept": []string{"s3pPLMBiTxaQ9kYGzzhZRbK+xOo="},
|
||
},
|
||
}
|
||
|
||
b.ReportAllocs()
|
||
b.ResetTimer()
|
||
|
||
for b.Loop() {
|
||
conn1, conn2 := net.Pipe()
|
||
|
||
done := make(chan error, 1)
|
||
go func() {
|
||
done <- writeUpgradeResponse(conn1, resp)
|
||
_ = conn1.Close()
|
||
}()
|
||
|
||
// 读取响应
|
||
buf := make([]byte, 1024)
|
||
n, err := conn2.Read(buf)
|
||
if err != nil {
|
||
b.Fatalf("read error: %v", err)
|
||
}
|
||
|
||
response := string(buf[:n])
|
||
if !strings.Contains(response, "101 Switching Protocols") {
|
||
b.Fatal("response missing status line")
|
||
}
|
||
|
||
_ = conn2.Close()
|
||
<-done
|
||
}
|
||
}
|