Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
//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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user