diff --git a/gin.go b/gin.go index 57f8c2a3d9..4cf9be139b 100644 --- a/gin.go +++ b/gin.go @@ -582,6 +582,23 @@ func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) { return } +// RunTLSAndQUIC attaches the router to a http.Server and starts listening and serving +// both TLS/TCP and QUIC connections in parallel requests. +// It is a shortcut for http3.ListenAndServe(addr, certFile, keyFile, router) +// Note: this method will block the calling goroutine indefinitely unless an error happens. +func (engine *Engine) RunTLSAndQUIC(addr, certFile, keyFile string) (err error) { + debugPrint("Listening and serving both TLS/TCP and QUIC on %s\n", addr) + defer func() { debugPrintError(err) }() + + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + } + + err = http3.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler()) + return +} + // RunListener attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified net.Listener func (engine *Engine) RunListener(listener net.Listener) (err error) { diff --git a/gin_integration_test.go b/gin_integration_test.go index 2125df925b..659c847773 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -20,25 +20,13 @@ import ( "testing" "time" + "github.com/quic-go/quic-go" + "github.com/quic-go/quic-go/http3" "github.com/stretchr/testify/assert" ) -// params[0]=url example:http://127.0.0.1:8080/index (cannot be empty) -// params[1]=response status (custom compare status) default:"200 OK" -// params[2]=response body (custom compare content) default:"it worked" -func testRequest(t *testing.T, params ...string) { - - if len(params) == 0 { - t.Fatal("url cannot be empty") - } - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } - client := &http.Client{Transport: tr} - +// This function handles the client side testing of different http versions +func requestHTTPClient(t *testing.T, client *http.Client, params ...string) { resp, err := client.Get(params[0]) assert.NoError(t, err) defer resp.Body.Close() @@ -62,6 +50,51 @@ func testRequest(t *testing.T, params ...string) { } } +// params[0]=url example:http://127.0.0.1:8080/index (cannot be empty) +// params[1]=response status (custom compare status) default:"200 OK" +// params[2]=response body (custom compare content) default:"it worked" +// params[3]=HTTP Version to use (default: "HTTP/2.0") +func testRequest(t *testing.T, params ...string) { + + if len(params) == 0 { + t.Fatal("url cannot be empty") + } + + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + } + + quicConfig := &quic.Config{} + + var httpVersion = "HTTP/2.0" + if len(params) > 3 && params[3] != "" { + httpVersion = params[3] + } + + if httpVersion == "HTTP/2.0" { + + tr := &http.Transport{ + TLSClientConfig: tlsConfig, + } + client := &http.Client{Transport: tr} + + requestHTTPClient(t, client, params...) + } + + if httpVersion == "HTTP/3.0" { + quicRoundTripper := &http3.RoundTripper{ + TLSClientConfig: tlsConfig, + QUICConfig: quicConfig, + } + + quicClient := &http.Client{ + Transport: quicRoundTripper, + } + + requestHTTPClient(t, quicClient, params...) + } +} + func TestRunEmpty(t *testing.T) { os.Setenv("PORT", "") router := New() @@ -274,6 +307,23 @@ func TestBadUnixSocket(t *testing.T) { assert.Error(t, router.RunUnix("#/tmp/unix_unit_test")) } +func TestRunTLSAndQUIC(t *testing.T) { + router := New() + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + + assert.NoError(t, router.RunTLSAndQUIC(":8442", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + }() + + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + assert.Error(t, router.RunTLSAndQUIC(":8442", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + testRequest(t, "https://localhost:8442/example") + testRequest(t, "https://localhost:8442/example", "200 OK", "it worked", "HTTP/3.0") +} + func TestRunQUIC(t *testing.T) { router := New() go func() { @@ -287,7 +337,7 @@ func TestRunQUIC(t *testing.T) { time.Sleep(5 * time.Millisecond) assert.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) - testRequest(t, "https://localhost:8443/example") + testRequest(t, "https://localhost:8443/example", "200 OK", "it worked", "HTTP/3.0") } func TestFileDescriptor(t *testing.T) { diff --git a/go.mod b/go.mod index 4937d2b7cf..1bc9a2ca56 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 - github.com/quic-go/quic-go v0.43.1 + github.com/quic-go/quic-go v0.44.0 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.25.0 @@ -40,9 +40,9 @@ require ( go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect - golang.org/x/mod v0.11.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.17.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 44af4cc163..2cd1a44034 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ= github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= +github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -96,8 +98,12 @@ golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -110,6 +116,8 @@ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=