diff --git a/Dockerfile b/Dockerfile index 812d2d1..981901d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: deps -FROM golang:1.24.1-alpine AS deps +FROM golang:1.24.2-alpine AS deps HEALTHCHECK NONE # package version does not matter diff --git a/go.mod b/go.mod index 0e30655..70cbf04 100644 --- a/go.mod +++ b/go.mod @@ -1,36 +1,66 @@ module github.com/yusing/go-proxy -go 1.24.1 +go 1.24.2 + +// misc require ( - github.com/PuerkitoBio/goquery v1.10.2 // parsing HTML for extract fav icon - github.com/coder/websocket v1.8.13 // websocket for API and agent - github.com/coreos/go-oidc/v3 v3.13.0 // oidc authentication - github.com/docker/docker v28.0.4+incompatible // docker daemon github.com/fsnotify/fsnotify v1.8.0 // file watcher github.com/go-acme/lego/v4 v4.22.2 // acme client - github.com/go-playground/validator/v10 v10.25.0 // validator + github.com/go-playground/validator/v10 v10.26.0 // validator github.com/gobwas/glob v0.2.3 // glob matcher for route rules - github.com/golang-jwt/jwt/v5 v5.2.2 // jwt for default auth github.com/gotify/server/v2 v2.6.1 // reference the Message struct for json response github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics - github.com/prometheus/client_golang v1.21.1 // metrics github.com/puzpuzpuz/xsync/v3 v3.5.1 // lock free map for concurrent operations - github.com/rs/zerolog v1.34.0 // logging - github.com/shirou/gopsutil/v4 v4.25.2 // system info metrics - github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon - golang.org/x/crypto v0.36.0 // encrypting password with bcrypt - golang.org/x/net v0.38.0 // HTTP header utilities - golang.org/x/oauth2 v0.28.0 // oauth2 authentication golang.org/x/text v0.23.0 // string utilities golang.org/x/time v0.11.0 // time utilities gopkg.in/yaml.v3 v3.0.1 // yaml parsing for different config files ) +// http + require ( - github.com/docker/cli v28.0.4+incompatible - github.com/docker/go-connections v0.5.0 - github.com/stretchr/testify v1.10.0 + github.com/coder/websocket v1.8.13 // websocket for API and agent + github.com/quic-go/quic-go v0.50.1 // http3 server + golang.org/x/net v0.38.0 // HTTP header utilities +) + +// authentication + +require ( + github.com/coreos/go-oidc/v3 v3.13.0 // oidc authentication + github.com/golang-jwt/jwt/v5 v5.2.2 // jwt for default auth + golang.org/x/crypto v0.36.0 // encrypting password with bcrypt + golang.org/x/oauth2 v0.28.0 // oauth2 authentication +) + +// favicon extraction +require ( + github.com/PuerkitoBio/goquery v1.10.2 // parsing HTML for extract fav icon + github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon +) + +// docker + +require ( + github.com/docker/cli v28.0.4+incompatible // docker cli + github.com/docker/docker v28.0.4+incompatible // docker daemon + github.com/docker/go-connections v0.5.0 // docker connection utilities +) + +// logging + +require ( + github.com/rs/zerolog v1.34.0 // logging + github.com/samber/slog-zerolog/v2 v2.7.3 // zerlog to slog adapter for quic-go +) + +// metrics + +require ( + github.com/prometheus/client_golang v1.21.1 // metrics + github.com/shirou/gopsutil/v4 v4.25.3 // system info metrics + github.com/stretchr/testify v1.10.0 // testing utilities ) require ( @@ -53,9 +83,11 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect @@ -67,6 +99,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect + github.com/onsi/ginkgo/v2 v2.23.3 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/ovh/go-ovh v1.7.0 // indirect @@ -76,6 +109,9 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/procfs v0.16.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/samber/slog-common v0.18.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect @@ -86,6 +122,8 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.uber.org/mock v0.5.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index c61925c..02d297d 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= -github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= @@ -78,6 +80,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gotify/server/v2 v2.6.1 h1:Kf7v5fzBxzELzZa/jonWfwJMkqYqh1LBzBpCmt5QIAI= @@ -123,6 +127,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= +github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= +github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -146,13 +154,23 @@ github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2b github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q= +github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk= -github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/samber/slog-common v0.18.1 h1:c0EipD/nVY9HG5shgm/XAs67mgpWDMF+MmtptdJNCkQ= +github.com/samber/slog-common v0.18.1/go.mod h1:QNZiNGKakvrfbJ2YglQXLCZauzkI9xZBjOhWFKS3IKk= +github.com/samber/slog-zerolog/v2 v2.7.3 h1:/MkPDl/tJhijN2GvB1MWwBn2FU8RiL3rQ8gpXkQm2EY= +github.com/samber/slog-zerolog/v2 v2.7.3/go.mod h1:oWU7WHof4Xp8VguiNO02r1a4VzkgoOyOZhY5CuRke60= +github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE= +github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -190,6 +208,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -200,6 +220,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/internal/common/env.go b/internal/common/env.go index c73028b..767d411 100644 --- a/internal/common/env.go +++ b/internal/common/env.go @@ -19,6 +19,8 @@ var ( IsDebug = GetEnvBool("DEBUG", IsTest) IsTrace = GetEnvBool("TRACE", false) && IsDebug + HTTP3Enabled = GetEnvBool("HTTP3_ENABLED", true) + ProxyHTTPAddr, ProxyHTTPHost, ProxyHTTPPort, diff --git a/internal/net/gphttp/server/server.go b/internal/net/gphttp/server/server.go index 61f63c2..92408af 100644 --- a/internal/net/gphttp/server/server.go +++ b/internal/net/gphttp/server/server.go @@ -3,11 +3,11 @@ package server import ( "context" "crypto/tls" - "log" "net" "net/http" "time" + "github.com/quic-go/quic-go/http3" "github.com/rs/zerolog" "github.com/yusing/go-proxy/internal/autocert" "github.com/yusing/go-proxy/internal/common" @@ -33,6 +33,11 @@ type Options struct { Handler http.Handler } +type httpServer interface { + *http.Server | *http3.Server + Shutdown(ctx context.Context) error +} + func StartServer(parent task.Parent, opt Options) (s *Server) { s = NewServer(opt) s.Start(parent) @@ -82,67 +87,74 @@ func NewServer(opt Options) (s *Server) { func (s *Server) Start(parent task.Parent) { s.startTime = time.Now() subtask := parent.Subtask("server."+s.Name, false) + + if s.https != nil && common.HTTP3Enabled { + s.https.TLSConfig.NextProtos = []string{http3.NextProtoH3, "h2", "http/1.1"} + h3 := &http3.Server{ + Addr: s.https.Addr, + Handler: s.https.Handler, + TLSConfig: http3.ConfigureTLSConfig(s.https.TLSConfig), + } + Start(subtask, h3, &s.l) + s.http.Handler = advertiseHTTP3(s.http.Handler, h3) + s.https.Handler = advertiseHTTP3(s.https.Handler, h3) + } + Start(subtask, s.http, &s.l) Start(subtask, s.https, &s.l) } -func Start(parent task.Parent, srv *http.Server, logger *zerolog.Logger) { +func Start[Server httpServer](parent task.Parent, srv Server, logger *zerolog.Logger) { if srv == nil { return } - srv.BaseContext = func(l net.Listener) context.Context { - return parent.Context() - } - if common.IsDebug { - srv.ErrorLog = log.New(logger, "", 0) - } - - var proto string - if srv.TLSConfig == nil { - proto = "http" - } else { - proto = "https" - } + setDebugLogger(srv, logger) + proto := proto(srv) task := parent.Subtask(proto, false) var lc net.ListenConfig + var serveFunc func() error - // Serve already closes the listener on return - l, err := lc.Listen(task.Context(), "tcp", srv.Addr) - if err != nil { - HandleError(logger, err, "failed to listen on port") - return - } - - task.OnCancel("stop", func() { - Stop(srv, logger) - }) - - logger.Info().Str("addr", srv.Addr).Msg("server started") - - go func() { - if srv.TLSConfig == nil { - err = srv.Serve(l) - } else { - err = srv.Serve(tls.NewListener(l, srv.TLSConfig)) + switch srv := any(srv).(type) { + case *http.Server: + srv.BaseContext = func(l net.Listener) context.Context { + return parent.Context() } + l, err := lc.Listen(task.Context(), "tcp", srv.Addr) + if err != nil { + HandleError(logger, err, "failed to listen on port") + return + } + if srv.TLSConfig != nil { + l = tls.NewListener(l, srv.TLSConfig) + } + serveFunc = getServeFunc(l, srv.Serve) + case *http3.Server: + l, err := lc.ListenPacket(task.Context(), "udp", srv.Addr) + if err != nil { + HandleError(logger, err, "failed to listen on port") + return + } + serveFunc = getServeFunc(l, srv.Serve) + } + task.OnCancel("stop", func() { + stop(srv, logger) + }) + logStarted(srv, logger) + go func() { + err := serveFunc() HandleError(logger, err, "failed to serve "+proto+" server") }() } -func Stop(srv *http.Server, logger *zerolog.Logger) { +func stop[Server httpServer](srv Server, logger *zerolog.Logger) { if srv == nil { return } - var proto string - if srv.TLSConfig == nil { - proto = "http" - } else { - proto = "https" - } + proto := proto(srv) ctx, cancel := context.WithTimeout(task.RootContext(), 3*time.Second) defer cancel() @@ -150,7 +162,7 @@ func Stop(srv *http.Server, logger *zerolog.Logger) { if err := srv.Shutdown(ctx); err != nil { HandleError(logger, err, "failed to shutdown "+proto+" server") } else { - logger.Info().Str("addr", srv.Addr).Msgf("server stopped") + logger.Info().Str("proto", proto).Str("addr", addr(srv)).Msg("server stopped") } } diff --git a/internal/net/gphttp/server/utils.go b/internal/net/gphttp/server/utils.go new file mode 100644 index 0000000..968f584 --- /dev/null +++ b/internal/net/gphttp/server/utils.go @@ -0,0 +1,75 @@ +package server + +import ( + "log" + "log/slog" + "net/http" + + "github.com/quic-go/quic-go/http3" + "github.com/rs/zerolog" + slogzerolog "github.com/samber/slog-zerolog/v2" + "github.com/yusing/go-proxy/internal/common" + "github.com/yusing/go-proxy/internal/net/gphttp" +) + +func advertiseHTTP3(handler http.Handler, h3 *http3.Server) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.ProtoMajor < 3 { + err := h3.SetQUICHeaders(w.Header()) + if err != nil { + gphttp.ServerError(w, r, err) + return + } + } + handler.ServeHTTP(w, r) + }) +} + +func proto[Server httpServer](srv Server) string { + var proto string + switch src := any(srv).(type) { + case *http.Server: + if src.TLSConfig == nil { + proto = "http" + } else { + proto = "https" + } + case *http3.Server: + proto = "h3" + } + return proto +} + +func addr[Server httpServer](srv Server) string { + var addr string + switch src := any(srv).(type) { + case *http.Server: + addr = src.Addr + case *http3.Server: + addr = src.Addr + } + return addr +} + +func getServeFunc[listener any](l listener, serve func(listener) error) func() error { + return func() error { + return serve(l) + } +} + +func setDebugLogger[Server httpServer](srv Server, logger *zerolog.Logger) { + if !common.IsDebug { + return + } + switch srv := any(srv).(type) { + case *http.Server: + srv.ErrorLog = log.New(logger, "", 0) + case *http3.Server: + logOpts := slogzerolog.Option{Level: slog.LevelDebug, Logger: logger} + srv.Logger = slog.New(logOpts.NewZerologHandler()) + } +} + +func logStarted[Server httpServer](srv Server, logger *zerolog.Logger) { + logger.Info().Str("proto", proto(srv)).Str("addr", addr(srv)).Msg("server started") +}