lolly/internal/e2e/http2_e2e_test.go
xfy fa74074bc7 refactor(e2e): 简化 HTTP/2 测试为 HTTPS 连接测试
- 移除 HTTP/2 协议特定测试(流多路复用、头部压缩、服务器推送等)
- 重命名测试函数 TestE2EHTTP2* → TestE2EHTTPS*
- 使用 testutil.CreateTLSClient 简化客户端创建
- 移除 golang.org/x/net/http2 依赖

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 18:02:36 +08:00

303 lines
8.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build e2e
// http2_e2e_test.go - HTTP/2 协议 E2E 测试
//
// 测试 lolly HTTP/2 功能HTTPS 连接、协议协商等。
//
// 作者xfy
package e2e
import (
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"rua.plus/lolly/internal/e2e/testutil"
)
// TestE2EHTTPSConnection 测试 HTTPS 连接。
//
// 验证 HTTPS 连接可以成功建立。
func TestE2EHTTPSConnection(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 生成自签名证书
certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
require.NoError(t, err, "Failed to generate certificate")
defer cleanup()
// 构建带 SSL 的配置
cfg := testutil.NewConfigBuilder().
WithServer(":8443").
WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
WithStatic("/", "/var/www/html")
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(configYAML),
testutil.WithCert(certPath, keyPath),
)
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
// 创建 TLS 客户端
client, err := testutil.CreateTLSClient(certPath)
require.NoError(t, err, "Failed to create TLS client")
// 发送请求
resp, err := client.Get(lolly.HTTPSBaseURL())
require.NoError(t, err, "HTTPS request failed")
defer resp.Body.Close()
t.Logf("HTTPS response status: %d, protocol: %s", resp.StatusCode, resp.Proto)
}
// TestE2EHTTPSConcurrentRequests 测试 HTTPS 并发请求。
//
// 验证多个并发 HTTPS 请求正常工作。
func TestE2EHTTPSConcurrentRequests(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 生成自签名证书
certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
require.NoError(t, err, "Failed to generate certificate")
defer cleanup()
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8443").
WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
WithStatic("/", "/var/www/html")
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(configYAML),
testutil.WithCert(certPath, keyPath),
)
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
// 创建 TLS 客户端
client, err := testutil.CreateTLSClient(certPath)
require.NoError(t, err, "Failed to create TLS client")
// 并发发送多个请求
numRequests := 10
var wg sync.WaitGroup
errors := make(chan error, numRequests)
start := time.Now()
for i := 0; i < numRequests; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
resp, err := client.Get(fmt.Sprintf("%s/test%d", lolly.HTTPSBaseURL(), id))
if err != nil {
errors <- fmt.Errorf("request %d failed: %w", id, err)
return
}
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}(i)
}
wg.Wait()
close(errors)
elapsed := time.Since(start)
t.Logf("Completed %d requests in %v", numRequests, elapsed)
// 检查错误
for err := range errors {
t.Errorf("Request error: %v", err)
}
assert.Less(t, elapsed, 2*time.Second, "Concurrent requests should complete quickly")
}
// TestE2EHTTPSCustomHeaders 测试 HTTPS 自定义头部。
//
// 验证自定义头部正确传递。
func TestE2EHTTPSCustomHeaders(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 生成自签名证书
certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
require.NoError(t, err, "Failed to generate certificate")
defer cleanup()
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8443").
WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
WithStatic("/", "/var/www/html")
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(configYAML),
testutil.WithCert(certPath, keyPath),
)
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
// 创建 TLS 客户端
client, err := testutil.CreateTLSClient(certPath)
require.NoError(t, err, "Failed to create TLS client")
// 发送多个请求,验证头部传递
for i := 0; i < 5; i++ {
req, err := http.NewRequest("GET", lolly.HTTPSBaseURL(), nil)
require.NoError(t, err)
// 添加自定义头部
req.Header.Set("X-Custom-Header", "test-value")
resp, err := client.Do(req)
if err != nil {
t.Logf("Request %d error: %v", i, err)
continue
}
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
t.Logf("Request %d: status %d", i, resp.StatusCode)
}
}
// TestE2EHTTPSLargeRequest 测试 HTTPS 大请求处理。
//
// 验证大请求体的处理。
func TestE2EHTTPSLargeRequest(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 生成自签名证书
certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
require.NoError(t, err, "Failed to generate certificate")
defer cleanup()
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8443").
WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
WithStatic("/", "/var/www/html")
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(configYAML),
testutil.WithCert(certPath, keyPath),
)
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
// 创建 TLS 客户端
client, err := testutil.CreateTLSClient(certPath)
require.NoError(t, err, "Failed to create TLS client")
// 发送大请求1MB
largeBody := strings.NewReader(strings.Repeat("x", 1024*1024))
req, err := http.NewRequest("POST", lolly.HTTPSBaseURL()+"/upload", largeBody)
require.NoError(t, err)
resp, err := client.Do(req)
if err != nil {
t.Logf("Large request error: %v", err)
} else {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
t.Logf("Large request status: %d", resp.StatusCode)
}
}
// TestE2EALPNNegotiation 测试 ALPN 协商。
//
// 验证 TLS ALPN 扩展正常工作。
func TestE2EALPNNegotiation(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.DefaultTestTimeout)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 生成自签名证书
certPath, keyPath, cleanup, err := testutil.GenerateSelfSignedCert(t.TempDir())
require.NoError(t, err, "Failed to generate certificate")
defer cleanup()
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8443").
WithSSL("/etc/lolly/ssl/server.crt", "/etc/lolly/ssl/server.key").
WithStatic("/", "/var/www/html")
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx,
testutil.WithConfigYAML(configYAML),
testutil.WithCert(certPath, keyPath),
)
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
// 创建支持 ALPN 的客户端
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h2", "http/1.1"},
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
}
client := &http.Client{Transport: transport}
// 发送请求
resp, err := client.Get(lolly.HTTPSBaseURL())
require.NoError(t, err, "HTTPS request failed")
defer resp.Body.Close()
t.Logf("HTTPS response status: %d, protocol: %s", resp.StatusCode, resp.Proto)
}