mirror of
https://github.com/infinite-iroha/touka.git
synced 2026-06-13 15:47:38 +08:00
feat: redesign server startup around Run options
Replace the old RunShutdown and RunTLS style entry points with a single Run(opts...) API for v1. Add focused startup semantics tests, keep TLS and graceful shutdown independent, ensure sibling servers are cleaned up on startup failure, and update docs to match the new option-based startup model.
This commit is contained in:
parent
fca9bbd3ef
commit
e4d3eed379
13 changed files with 577 additions and 335 deletions
196
serve_test.go
196
serve_test.go
|
|
@ -7,6 +7,8 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -79,3 +81,197 @@ func TestServeServerHTTPModeIgnoresTLSConfig(t *testing.T) {
|
|||
t.Fatalf("serveServer should stop with ErrServerClosed after shutdown, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunRejectsRedirectWithoutTLS(t *testing.T) {
|
||||
engine := New()
|
||||
err := engine.Run(WithHTTPRedirect(":80"))
|
||||
if err == nil {
|
||||
t.Fatal("expected redirect mode without TLS to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithGracefulShutdownDefaultUsesDefaultTimeout(t *testing.T) {
|
||||
cfg := defaultRunConfig()
|
||||
if err := WithGracefulShutdownDefault().apply(&cfg); err != nil {
|
||||
t.Fatalf("apply graceful default option: %v", err)
|
||||
}
|
||||
if !cfg.graceful {
|
||||
t.Fatal("expected graceful shutdown to be enabled")
|
||||
}
|
||||
if cfg.shutdownTimeout != defaultShutdownTimeout {
|
||||
t.Fatalf("expected default shutdown timeout %v, got %v", defaultShutdownTimeout, cfg.shutdownTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithTLSDoesNotRequireGracefulShutdown(t *testing.T) {
|
||||
cfg := defaultRunConfig()
|
||||
tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12}
|
||||
if err := WithTLS(tlsConfig).apply(&cfg); err != nil {
|
||||
t.Fatalf("apply TLS option: %v", err)
|
||||
}
|
||||
if cfg.mode != runModeHTTPS {
|
||||
t.Fatalf("expected HTTPS mode, got %v", cfg.mode)
|
||||
}
|
||||
if cfg.graceful {
|
||||
t.Fatal("expected TLS option to remain independent from graceful shutdown")
|
||||
}
|
||||
if cfg.tlsConfig != tlsConfig {
|
||||
t.Fatal("expected TLS config to be preserved in run config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRedirectServerRejectsHTTPSAddrWithoutPort(t *testing.T) {
|
||||
engine := New()
|
||||
if _, err := buildRedirectServer(engine, "example.com", ":80"); err == nil {
|
||||
t.Fatal("expected redirect server builder to reject https address without port")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRunConfigRejectsShutdownContextWithoutGraceful(t *testing.T) {
|
||||
cfg := defaultRunConfig()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
if err := WithShutdownContext(ctx).apply(&cfg); err != nil {
|
||||
t.Fatalf("apply shutdown context option: %v", err)
|
||||
}
|
||||
if err := validateRunConfig(cfg); err == nil {
|
||||
t.Fatal("expected shutdown context without graceful shutdown to fail validation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMainServerGracefulSetsBaseContextAndShutdownHook(t *testing.T) {
|
||||
engine := New()
|
||||
server := buildMainServer(engine, runConfig{addr: ":8080", graceful: true, mode: runModeHTTP})
|
||||
if server.BaseContext == nil {
|
||||
t.Fatal("expected graceful main server to set BaseContext")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen for base context check: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
if got := server.BaseContext(listener); got != engine.shutdownCtx {
|
||||
t.Fatal("expected graceful main server to use engine shutdown context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMainServerTLSConfiguratorPrecedence(t *testing.T) {
|
||||
engine := New()
|
||||
serverConfigured := false
|
||||
tlsConfigured := false
|
||||
engine.SetServerConfigurator(func(s *http.Server) {
|
||||
serverConfigured = true
|
||||
s.ReadTimeout = time.Second
|
||||
})
|
||||
engine.SetTLSServerConfigurator(func(s *http.Server) {
|
||||
tlsConfigured = true
|
||||
s.IdleTimeout = time.Second
|
||||
})
|
||||
|
||||
server := buildMainServer(engine, runConfig{addr: ":443", mode: runModeHTTPS, tlsConfig: &tls.Config{}})
|
||||
if !tlsConfigured {
|
||||
t.Fatal("expected TLS configurator to run for HTTPS main server")
|
||||
}
|
||||
if serverConfigured {
|
||||
t.Fatal("expected generic server configurator to be skipped when TLS configurator is set")
|
||||
}
|
||||
if server.IdleTimeout != time.Second {
|
||||
t.Fatal("expected TLS configurator changes to be applied to HTTPS main server")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRedirectServerUsesGenericConfigurator(t *testing.T) {
|
||||
engine := New()
|
||||
configured := false
|
||||
engine.SetServerConfigurator(func(s *http.Server) {
|
||||
configured = true
|
||||
s.ReadTimeout = time.Second
|
||||
})
|
||||
|
||||
server, err := buildRedirectServer(engine, ":443", ":80")
|
||||
if err != nil {
|
||||
t.Fatalf("build redirect server: %v", err)
|
||||
}
|
||||
if !configured {
|
||||
t.Fatal("expected redirect server to use generic server configurator")
|
||||
}
|
||||
if server.ReadTimeout != time.Second {
|
||||
t.Fatal("expected redirect server configurator changes to be applied")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSRunDoesNotMutateDefaultHTTPProtocols(t *testing.T) {
|
||||
engine := New()
|
||||
httpsServer := buildMainServer(engine, runConfig{addr: ":443", mode: runModeHTTPS, tlsConfig: &tls.Config{}})
|
||||
if !httpsServer.Protocols.HTTP2() {
|
||||
t.Fatal("expected HTTPS server to enable HTTP/2 under default protocol settings")
|
||||
}
|
||||
|
||||
httpServer := buildMainServer(engine, defaultRunConfig())
|
||||
if httpServer.Protocols.HTTP2() {
|
||||
t.Fatal("expected later plain HTTP server to keep default HTTP/2 disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRedirectServerRedirectsWithoutGracefulMode(t *testing.T) {
|
||||
engine := New()
|
||||
server, err := buildRedirectServer(engine, ":443", ":80")
|
||||
if err != nil {
|
||||
t.Fatalf("build redirect server: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com/plain/path?q=1", nil)
|
||||
req.Host = "example.com:80"
|
||||
rr := httptest.NewRecorder()
|
||||
server.Handler.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusMovedPermanently {
|
||||
t.Fatalf("expected redirect status %d, got %d", http.StatusMovedPermanently, rr.Code)
|
||||
}
|
||||
if location := rr.Header().Get("Location"); location != "https://example.com/plain/path?q=1" {
|
||||
t.Fatalf("unexpected redirect location: %q", location)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGracefulServeShutsDownSiblingServersOnStartupFailure(t *testing.T) {
|
||||
occupied, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen on occupied addr: %v", err)
|
||||
}
|
||||
occupiedAddr := occupied.Addr().String()
|
||||
defer occupied.Close()
|
||||
|
||||
redirectListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen for redirect addr: %v", err)
|
||||
}
|
||||
redirectAddr := redirectListener.Addr().String()
|
||||
if err := redirectListener.Close(); err != nil {
|
||||
t.Fatalf("close redirect addr probe: %v", err)
|
||||
}
|
||||
|
||||
engine := New()
|
||||
redirectServer, err := buildRedirectServer(engine, ":443", redirectAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("build redirect server: %v", err)
|
||||
}
|
||||
mainServer := &http.Server{Addr: occupiedAddr, Handler: engine}
|
||||
|
||||
err = gracefulServe([]*http.Server{mainServer, redirectServer}, []bool{false, false}, 200*time.Millisecond, nil, context.Background())
|
||||
if err == nil {
|
||||
t.Fatal("expected gracefulServe to fail when one server cannot bind")
|
||||
}
|
||||
if !strings.Contains(err.Error(), occupiedAddr) {
|
||||
t.Fatalf("expected startup failure to mention occupied address %q, got %v", occupiedAddr, err)
|
||||
}
|
||||
|
||||
conn, dialErr := net.DialTimeout("tcp", redirectAddr, 200*time.Millisecond)
|
||||
if dialErr == nil {
|
||||
conn.Close()
|
||||
t.Fatalf("expected sibling redirect server to be shut down after startup failure, but %s is still accepting connections", redirectAddr)
|
||||
}
|
||||
if !strings.Contains(dialErr.Error(), "refused") && !strings.Contains(dialErr.Error(), "reset") {
|
||||
t.Fatalf("unexpected dial result after shutdown, got %v", dialErr)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue