| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- //go:build unit
- package server
- import (
- "bufio"
- "context"
- "errors"
- "io"
- "net"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/config"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/require"
- )
- func ingressTestConfig() *config.Config {
- return &config.Config{
- Server: config.ServerConfig{
- Host: "127.0.0.1",
- ReadHeaderTimeout: 1,
- IdleTimeout: 5,
- MaxHeaderBytes: 8 * 1024,
- MaxRequestBodySize: 1024,
- },
- Gateway: config.GatewayConfig{MaxBodySize: 1024},
- }
- }
- func TestProvideHTTPServerAppliesIngressLimits(t *testing.T) {
- srv := ProvideHTTPServer(ingressTestConfig(), gin.New())
- require.Equal(t, 8*1024, srv.MaxHeaderBytes)
- require.Equal(t, time.Second, srv.ReadHeaderTimeout)
- require.Equal(t, 5*time.Second, srv.IdleTimeout)
- }
- func TestProvideHTTPServerEnablesBoundedH2C(t *testing.T) {
- cfg := ingressTestConfig()
- cfg.Server.H2C = config.H2CConfig{
- Enabled: true,
- MaxConcurrentStreams: 25,
- IdleTimeout: 30,
- MaxReadFrameSize: 64 * 1024,
- MaxUploadBufferPerConnection: 1024 * 1024,
- MaxUploadBufferPerStream: 256 * 1024,
- }
- srv := ProvideHTTPServer(cfg, gin.New())
- require.NotNil(t, srv.Protocols)
- require.True(t, srv.Protocols.UnencryptedHTTP2())
- require.True(t, srv.Protocols.HTTP1())
- }
- func TestConfigureTrustedProxies(t *testing.T) {
- gin.SetMode(gin.TestMode)
- tests := []struct {
- name string
- cfg config.ServerConfig
- want string
- }{
- {
- name: "configured proxy resolves forwarded client",
- cfg: config.ServerConfig{
- TrustedProxies: []string{"9.9.9.9/32"},
- TrustedProxiesConfigured: true,
- },
- want: "1.2.3.4",
- },
- {
- name: "explicit empty list ignores forwarded client",
- cfg: config.ServerConfig{
- TrustedProxiesConfigured: true,
- },
- want: "9.9.9.9",
- },
- {
- name: "invalid proxy list fails closed",
- cfg: config.ServerConfig{
- TrustedProxies: []string{"not-a-cidr"},
- TrustedProxiesConfigured: true,
- },
- want: "9.9.9.9",
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- r := gin.New()
- configureTrustedProxies(r, tc.cfg)
- r.GET("/t", func(c *gin.Context) { c.String(http.StatusOK, c.ClientIP()) })
- w := httptest.NewRecorder()
- req := httptest.NewRequest(http.MethodGet, "/t", nil)
- req.RemoteAddr = "9.9.9.9:12345"
- req.Header.Set("X-Forwarded-For", "1.2.3.4")
- r.ServeHTTP(w, req)
- require.Equal(t, http.StatusOK, w.Code)
- require.Equal(t, tc.want, w.Body.String())
- })
- }
- }
- func TestHTTPServerRejectsOversizedHTTP1Header(t *testing.T) {
- r := gin.New()
- r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
- srv := ProvideHTTPServer(ingressTestConfig(), r)
- addr, stop := serveIngressTestServer(t, srv)
- defer stop()
- conn, err := net.DialTimeout("tcp", addr, time.Second)
- require.NoError(t, err)
- defer func() { _ = conn.Close() }()
- _ = conn.SetDeadline(time.Now().Add(3 * time.Second))
- _, err = io.WriteString(conn, "GET / HTTP/1.1\r\nHost: test\r\nX-Fill: "+strings.Repeat("a", 32*1024)+"\r\n\r\n")
- require.NoError(t, err)
- resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
- require.NoError(t, err)
- defer func() { _ = resp.Body.Close() }()
- require.Equal(t, http.StatusRequestHeaderFieldsTooLarge, resp.StatusCode)
- }
- func TestHTTPServerClosesSlowIncompleteHeader(t *testing.T) {
- r := gin.New()
- r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
- srv := ProvideHTTPServer(ingressTestConfig(), r)
- addr, stop := serveIngressTestServer(t, srv)
- defer stop()
- conn, err := net.DialTimeout("tcp", addr, time.Second)
- require.NoError(t, err)
- defer func() { _ = conn.Close() }()
- _, err = io.WriteString(conn, "GET / HTTP/1.1\r\nHost: test\r\nX-Slow:")
- require.NoError(t, err)
- time.Sleep(1200 * time.Millisecond)
- _ = conn.SetReadDeadline(time.Now().Add(time.Second))
- _, err = bufio.NewReader(conn).ReadByte()
- require.Error(t, err)
- }
- func TestHTTPServerGlobalBodyLimit(t *testing.T) {
- r := gin.New()
- r.POST("/", func(c *gin.Context) {
- _, err := io.ReadAll(c.Request.Body)
- if err != nil {
- var maxErr *http.MaxBytesError
- if errors.As(err, &maxErr) {
- c.Status(http.StatusRequestEntityTooLarge)
- return
- }
- }
- c.Status(http.StatusOK)
- })
- srv := ProvideHTTPServer(ingressTestConfig(), r)
- req, err := http.NewRequest(http.MethodPost, "/", strings.NewReader(strings.Repeat("x", 1025)))
- require.NoError(t, err)
- rec := httptest.NewRecorder()
- srv.Handler.ServeHTTP(rec, req)
- require.Equal(t, http.StatusRequestEntityTooLarge, rec.Code)
- }
- func serveIngressTestServer(t *testing.T, srv *http.Server) (string, func()) {
- t.Helper()
- ln, err := net.Listen("tcp", "127.0.0.1:0")
- require.NoError(t, err)
- go func() { _ = srv.Serve(ln) }()
- return ln.Addr().String(), func() {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second)
- defer cancel()
- _ = srv.Shutdown(ctx)
- }
- }
|