// Package http2 提供 HTTP/2 服务器基准测试。 // // 该文件测试 HTTP/2 服务器的性能,包括: // - 服务器创建开销 // - HTTP/2 帧编码性能 // - HPACK 头部压缩性能 // - 流创建和管理开销 // - 并发流处理吞吐量 // - 完整请求往返延迟 // // 作者:xfy package http2 import ( "bytes" "net/http" "net/http/httptest" "testing" "github.com/valyala/fasthttp" "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" "rua.plus/lolly/internal/config" ) // BenchmarkHTTP2ServerStart 测试 HTTP/2 服务器启动开销 // // 该基准测试测量从配置创建到 http2.Server 实例化的完整开销, // 包括默认值填充、对象池初始化和结构体分配。 func BenchmarkHTTP2ServerStart(b *testing.B) { cfg := &config.HTTP2Config{ Enabled: true, MaxConcurrentStreams: 100, MaxHeaderListSize: 1048576, PushEnabled: false, } handler := func(_ *fasthttp.RequestCtx) {} b.ResetTimer() b.ReportAllocs() for b.Loop() { _, err := NewServer(cfg, handler, nil) if err != nil { b.Fatal(err) } } } // BenchmarkHTTP2FrameEncoding 测试 HTTP/2 帧编码性能 // // 该基准测试测量使用 golang.org/x/net/http2 的 Framer // 编码不同类型 HTTP/2 帧的开销,包括 DATA、HEADERS 和 // SETTINGS 帧。 func BenchmarkHTTP2FrameEncoding(b *testing.B) { // 创建 Framer 实例(复用写入缓冲区) var buf bytes.Buffer framer := http2.NewFramer(&buf, nil) settings := []http2.Setting{ {ID: http2.SettingMaxConcurrentStreams, Val: 100}, {ID: http2.SettingInitialWindowSize, Val: 65535}, {ID: http2.SettingMaxFrameSize, Val: 16384}, } b.ResetTimer() b.ReportAllocs() b.Run("SettingsFrame", func(b *testing.B) { for b.Loop() { buf.Reset() if err := framer.WriteSettings(settings...); err != nil { b.Fatal(err) } } }) b.Run("DataFrame", func(b *testing.B) { // 预创建帧载荷 payload := make([]byte, 1024) for i := range payload { payload[i] = byte('a' + (i % 26)) } b.ResetTimer() for b.Loop() { buf.Reset() if err := framer.WriteData(1, false, payload); err != nil { b.Fatal(err) } } }) b.Run("DataFrame_Small", func(b *testing.B) { payload := []byte(`{"id":1,"name":"test"}`) b.ResetTimer() for b.Loop() { buf.Reset() if err := framer.WriteData(1, false, payload); err != nil { b.Fatal(err) } } }) b.Run("DataFrame_Large", func(b *testing.B) { payload := bytes.Repeat([]byte("X"), 16384) b.ResetTimer() for b.Loop() { buf.Reset() if err := framer.WriteData(1, false, payload); err != nil { b.Fatal(err) } } }) b.Run("PingFrame", func(b *testing.B) { data := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} b.ResetTimer() for b.Loop() { buf.Reset() if err := framer.WritePing(false, data); err != nil { b.Fatal(err) } } }) b.Run("RSTStreamFrame", func(b *testing.B) { b.ResetTimer() for b.Loop() { buf.Reset() if err := framer.WriteRSTStream(1, http2.ErrCodeCancel); err != nil { b.Fatal(err) } } }) b.Run("WindowUpdateFrame", func(b *testing.B) { b.ResetTimer() for b.Loop() { buf.Reset() if err := framer.WriteWindowUpdate(1, 65535); err != nil { b.Fatal(err) } } }) b.Run("GoAwayFrame", func(b *testing.B) { b.ResetTimer() for b.Loop() { buf.Reset() if err := framer.WriteGoAway(1, http2.ErrCodeNo, []byte("graceful shutdown")); err != nil { b.Fatal(err) } } }) } // BenchmarkHTTP2HeadersEncoding 测试 HPACK 头部压缩性能 // // 该基准测试测量使用 HPACK 编码器压缩 HTTP/2 请求头部的开销, // 模拟不同头部数量和大小场景下的压缩性能。 func BenchmarkHTTP2HeadersEncoding(b *testing.B) { // 模拟真实 HTTP/2 请求头部 commonHeaders := []hpack.HeaderField{ {Name: ":method", Value: "GET"}, {Name: ":scheme", Value: "https"}, {Name: ":path", Value: "/api/v1/users"}, {Name: ":authority", Value: "api.example.com"}, {Name: "user-agent", Value: "Mozilla/5.0 (X11; Linux x86_64) Chrome/120.0"}, {Name: "accept", Value: "application/json, text/plain, */*"}, {Name: "accept-encoding", Value: "gzip, deflate, br"}, {Name: "accept-language", Value: "en-US,en;q=0.9"}, } authHeaders := append([]hpack.HeaderField(nil), commonHeaders...) authHeaders = append(authHeaders, hpack.HeaderField{Name: "authorization", Value: "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0"}, hpack.HeaderField{Name: "x-request-id", Value: "req-abc123-def456"}, hpack.HeaderField{Name: "x-trace-id", Value: "trace-789xyz"}, hpack.HeaderField{Name: "cookie", Value: "session=abc123; _ga=GA1.2.123456; _gid=GA1.2.789012"}, ) bodyHeaders := []hpack.HeaderField{ {Name: ":method", Value: "POST"}, {Name: ":scheme", Value: "https"}, {Name: ":path", Value: "/api/v1/upload"}, {Name: ":authority", Value: "api.example.com"}, {Name: "content-type", Value: "multipart/form-data; boundary=----WebKitFormBoundary"}, {Name: "content-length", Value: "1048576"}, } b.ResetTimer() b.ReportAllocs() b.Run("CommonHeaders", func(b *testing.B) { var buf bytes.Buffer encoder := hpack.NewEncoder(&buf) for b.Loop() { buf.Reset() for _, hf := range commonHeaders { if err := encoder.WriteField(hf); err != nil { b.Fatal(err) } } } }) b.Run("CommonHeaders_Parallel", func(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { var buf bytes.Buffer encoder := hpack.NewEncoder(&buf) for _, hf := range commonHeaders { if err := encoder.WriteField(hf); err != nil { b.Fatal(err) } } } }) }) b.Run("AuthHeaders", func(b *testing.B) { var buf bytes.Buffer encoder := hpack.NewEncoder(&buf) for b.Loop() { buf.Reset() for _, hf := range authHeaders { if err := encoder.WriteField(hf); err != nil { b.Fatal(err) } } } }) b.Run("BodyHeaders", func(b *testing.B) { var buf bytes.Buffer encoder := hpack.NewEncoder(&buf) for b.Loop() { buf.Reset() for _, hf := range bodyHeaders { if err := encoder.WriteField(hf); err != nil { b.Fatal(err) } } } }) b.Run("RepeatedHeaders", func(b *testing.B) { // 重复发送相同头部,测试 HPACK 静态表和动态表的效果 var buf bytes.Buffer encoder := hpack.NewEncoder(&buf) for b.Loop() { buf.Reset() for _, hf := range commonHeaders { if err := encoder.WriteField(hf); err != nil { b.Fatal(err) } } } }) } // BenchmarkHTTP2StreamCreate 测试流创建开销 // // 该基准测试测量 HTTP/2 流创建和请求适配的核心开销, // 包括 RequestCtx 从池中获取、请求转换和响应返回。 // 使用适配器直接模拟流处理流程,避免网络 I/O 不确定性。 func BenchmarkHTTP2StreamCreate(b *testing.B) { handler := func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusOK) ctx.WriteString("OK") } adapter := NewFastHTTPHandlerAdapter(handler) b.ResetTimer() b.ReportAllocs() for b.Loop() { req := httptest.NewRequest(http.MethodGet, "/stream/test", nil) req.Header.Set(":authority", "localhost") rec := httptest.NewRecorder() adapter.ServeHTTP(rec, req) } } // BenchmarkHTTP2ConcurrentStreams 测试并发流处理吞吐量 // // 该基准测试使用 b.RunParallel 模拟多个客户端并发发送请求的场景, // 测量服务器在多路复用下的吞吐量,通过复用适配器实例 // 模拟 HTTP/2 多流并发处理的实际开销。 func BenchmarkHTTP2ConcurrentStreams(b *testing.B) { handler := func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusOK) ctx.Response.Header.Set("Content-Type", "application/json") ctx.WriteString(`{"status":"ok"}`) } adapter := NewFastHTTPHandlerAdapter(handler) b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { req := httptest.NewRequest(http.MethodGet, "/api/concurrent", nil) req.Header.Set("Accept", "application/json") req.Header.Set("X-Stream-ID", "stream") rec := httptest.NewRecorder() adapter.ServeHTTP(rec, req) } }) } // BenchmarkHTTP2RequestRoundTrip 测试完整请求往返性能 // // 该基准测试模拟真实的 HTTP/2 请求处理流程: // 1. 客户端创建连接并发送 HTTP/2 前导 // 2. 服务器接收并处理请求(通过适配器转换为 fasthttp) // 3. 服务器返回响应 // 4. 客户端接收响应 // // 注意:由于 Serve 方法会阻塞在 Accept 循环上, // 本测试使用适配器层直接模拟完整流程,避免实际网络 I/O。 func BenchmarkHTTP2RequestRoundTrip(b *testing.B) { // 使用适配器直接测试完整的请求转换流程 handler := func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusOK) ctx.Response.Header.Set("Content-Type", "application/json") ctx.Response.Header.Set("X-Request-ID", "test-id") ctx.WriteString(`{"message":"hello","timestamp":1234567890}`) } adapter := NewFastHTTPHandlerAdapter(handler) // 模拟经过 HPACK 编码后的 HTTP/2 请求 req := httptest.NewRequest(http.MethodGet, "/api/v1/test", nil) req.Header.Set("Accept", "application/json") req.Header.Set("User-Agent", "BenchmarkClient/1.0") req.Header.Set("X-Request-ID", "bench-req-001") rec := httptest.NewRecorder() b.ResetTimer() b.ReportAllocs() for b.Loop() { rec.Body.Reset() adapter.ServeHTTP(rec, req) } } // BenchmarkHTTP2RequestRoundTrip_WithBody 测试带请求体的完整往返 // // 模拟 POST 请求,包含 JSON 请求体。 func BenchmarkHTTP2RequestRoundTrip_WithBody(b *testing.B) { handler := func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusOK) ctx.Response.Header.Set("Content-Type", "application/json") ctx.Write(ctx.PostBody()) } adapter := NewFastHTTPHandlerAdapter(handler) body := []byte(`{"action":"update","data":{"id":12345,"name":"benchmark test","values":[1,2,3,4,5]}}`) b.ResetTimer() b.ReportAllocs() for b.Loop() { req := httptest.NewRequest(http.MethodPost, "/api/v1/update", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.ContentLength = int64(len(body)) rec := httptest.NewRecorder() adapter.ServeHTTP(rec, req) } } // BenchmarkHTTP2RequestRoundTrip_WithBody_Parallel 测试并发带请求体的完整往返 // // 使用 b.RunParallel 模拟高并发 API 请求场景。 func BenchmarkHTTP2RequestRoundTrip_WithBody_Parallel(b *testing.B) { handler := func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusOK) ctx.Response.Header.Set("Content-Type", "application/json") ctx.Write(ctx.PostBody()) } adapter := NewFastHTTPHandlerAdapter(handler) body := []byte(`{"action":"batch","items":[{"id":1},{"id":2},{"id":3}]}`) b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { req := httptest.NewRequest(http.MethodPost, "/api/v1/batch", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.ContentLength = int64(len(body)) rec := httptest.NewRecorder() adapter.ServeHTTP(rec, req) } }) } // BenchmarkHTTP2SettingsValidation 测试设置验证性能 // // 测量 ValidateSettings 函数的开销,用于了解 // 配置验证阶段的性能成本。 // BenchmarkHTTP2AdapterWithHPACKHeaders 测试带 HPACK 编码头部的适配器性能 // // 模拟 HTTP/2 场景下经过 HPACK 压缩和解压缩后的头部转换开销。 func BenchmarkHTTP2AdapterWithHPACKHeaders(b *testing.B) { handler := func(ctx *fasthttp.RequestCtx) { ctx.SetStatusCode(fasthttp.StatusOK) ctx.WriteString("OK") } adapter := NewFastHTTPHandlerAdapter(handler) // 预编码头部(模拟 HPACK 解码后) methods := []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete} paths := []string{"/api/v1/users", "/api/v1/orders", "/api/v1/products", "/api/v1/settings"} b.ResetTimer() b.ReportAllocs() var idx int for b.Loop() { method := methods[idx%len(methods)] path := paths[idx%len(paths)] idx++ req := httptest.NewRequest(method, path, nil) req.Header.Set(":authority", "api.example.com") req.Header.Set("x-http2-stream-id", "1") rec := httptest.NewRecorder() adapter.ServeHTTP(rec, req) } }