http_ingress_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. //go:build unit
  2. package server
  3. import (
  4. "bufio"
  5. "context"
  6. "errors"
  7. "io"
  8. "net"
  9. "net/http"
  10. "net/http/httptest"
  11. "strings"
  12. "testing"
  13. "time"
  14. "github.com/Wei-Shaw/sub2api/internal/config"
  15. "github.com/gin-gonic/gin"
  16. "github.com/stretchr/testify/require"
  17. )
  18. func ingressTestConfig() *config.Config {
  19. return &config.Config{
  20. Server: config.ServerConfig{
  21. Host: "127.0.0.1",
  22. ReadHeaderTimeout: 1,
  23. IdleTimeout: 5,
  24. MaxHeaderBytes: 8 * 1024,
  25. MaxRequestBodySize: 1024,
  26. },
  27. Gateway: config.GatewayConfig{MaxBodySize: 1024},
  28. }
  29. }
  30. func TestProvideHTTPServerAppliesIngressLimits(t *testing.T) {
  31. srv := ProvideHTTPServer(ingressTestConfig(), gin.New())
  32. require.Equal(t, 8*1024, srv.MaxHeaderBytes)
  33. require.Equal(t, time.Second, srv.ReadHeaderTimeout)
  34. require.Equal(t, 5*time.Second, srv.IdleTimeout)
  35. }
  36. func TestProvideHTTPServerEnablesBoundedH2C(t *testing.T) {
  37. cfg := ingressTestConfig()
  38. cfg.Server.H2C = config.H2CConfig{
  39. Enabled: true,
  40. MaxConcurrentStreams: 25,
  41. IdleTimeout: 30,
  42. MaxReadFrameSize: 64 * 1024,
  43. MaxUploadBufferPerConnection: 1024 * 1024,
  44. MaxUploadBufferPerStream: 256 * 1024,
  45. }
  46. srv := ProvideHTTPServer(cfg, gin.New())
  47. require.NotNil(t, srv.Protocols)
  48. require.True(t, srv.Protocols.UnencryptedHTTP2())
  49. require.True(t, srv.Protocols.HTTP1())
  50. }
  51. func TestConfigureTrustedProxies(t *testing.T) {
  52. gin.SetMode(gin.TestMode)
  53. tests := []struct {
  54. name string
  55. cfg config.ServerConfig
  56. want string
  57. }{
  58. {
  59. name: "configured proxy resolves forwarded client",
  60. cfg: config.ServerConfig{
  61. TrustedProxies: []string{"9.9.9.9/32"},
  62. TrustedProxiesConfigured: true,
  63. },
  64. want: "1.2.3.4",
  65. },
  66. {
  67. name: "explicit empty list ignores forwarded client",
  68. cfg: config.ServerConfig{
  69. TrustedProxiesConfigured: true,
  70. },
  71. want: "9.9.9.9",
  72. },
  73. {
  74. name: "invalid proxy list fails closed",
  75. cfg: config.ServerConfig{
  76. TrustedProxies: []string{"not-a-cidr"},
  77. TrustedProxiesConfigured: true,
  78. },
  79. want: "9.9.9.9",
  80. },
  81. }
  82. for _, tc := range tests {
  83. t.Run(tc.name, func(t *testing.T) {
  84. r := gin.New()
  85. configureTrustedProxies(r, tc.cfg)
  86. r.GET("/t", func(c *gin.Context) { c.String(http.StatusOK, c.ClientIP()) })
  87. w := httptest.NewRecorder()
  88. req := httptest.NewRequest(http.MethodGet, "/t", nil)
  89. req.RemoteAddr = "9.9.9.9:12345"
  90. req.Header.Set("X-Forwarded-For", "1.2.3.4")
  91. r.ServeHTTP(w, req)
  92. require.Equal(t, http.StatusOK, w.Code)
  93. require.Equal(t, tc.want, w.Body.String())
  94. })
  95. }
  96. }
  97. func TestHTTPServerRejectsOversizedHTTP1Header(t *testing.T) {
  98. r := gin.New()
  99. r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
  100. srv := ProvideHTTPServer(ingressTestConfig(), r)
  101. addr, stop := serveIngressTestServer(t, srv)
  102. defer stop()
  103. conn, err := net.DialTimeout("tcp", addr, time.Second)
  104. require.NoError(t, err)
  105. defer func() { _ = conn.Close() }()
  106. _ = conn.SetDeadline(time.Now().Add(3 * time.Second))
  107. _, err = io.WriteString(conn, "GET / HTTP/1.1\r\nHost: test\r\nX-Fill: "+strings.Repeat("a", 32*1024)+"\r\n\r\n")
  108. require.NoError(t, err)
  109. resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
  110. require.NoError(t, err)
  111. defer func() { _ = resp.Body.Close() }()
  112. require.Equal(t, http.StatusRequestHeaderFieldsTooLarge, resp.StatusCode)
  113. }
  114. func TestHTTPServerClosesSlowIncompleteHeader(t *testing.T) {
  115. r := gin.New()
  116. r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) })
  117. srv := ProvideHTTPServer(ingressTestConfig(), r)
  118. addr, stop := serveIngressTestServer(t, srv)
  119. defer stop()
  120. conn, err := net.DialTimeout("tcp", addr, time.Second)
  121. require.NoError(t, err)
  122. defer func() { _ = conn.Close() }()
  123. _, err = io.WriteString(conn, "GET / HTTP/1.1\r\nHost: test\r\nX-Slow:")
  124. require.NoError(t, err)
  125. time.Sleep(1200 * time.Millisecond)
  126. _ = conn.SetReadDeadline(time.Now().Add(time.Second))
  127. _, err = bufio.NewReader(conn).ReadByte()
  128. require.Error(t, err)
  129. }
  130. func TestHTTPServerGlobalBodyLimit(t *testing.T) {
  131. r := gin.New()
  132. r.POST("/", func(c *gin.Context) {
  133. _, err := io.ReadAll(c.Request.Body)
  134. if err != nil {
  135. var maxErr *http.MaxBytesError
  136. if errors.As(err, &maxErr) {
  137. c.Status(http.StatusRequestEntityTooLarge)
  138. return
  139. }
  140. }
  141. c.Status(http.StatusOK)
  142. })
  143. srv := ProvideHTTPServer(ingressTestConfig(), r)
  144. req, err := http.NewRequest(http.MethodPost, "/", strings.NewReader(strings.Repeat("x", 1025)))
  145. require.NoError(t, err)
  146. rec := httptest.NewRecorder()
  147. srv.Handler.ServeHTTP(rec, req)
  148. require.Equal(t, http.StatusRequestEntityTooLarge, rec.Code)
  149. }
  150. func serveIngressTestServer(t *testing.T, srv *http.Server) (string, func()) {
  151. t.Helper()
  152. ln, err := net.Listen("tcp", "127.0.0.1:0")
  153. require.NoError(t, err)
  154. go func() { _ = srv.Serve(ln) }()
  155. return ln.Addr().String(), func() {
  156. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  157. defer cancel()
  158. _ = srv.Shutdown(ctx)
  159. }
  160. }