From 2281714970b9e3633f38f791cd81310b6a6841de Mon Sep 17 00:00:00 2001 From: Thanatat Tamtan Date: Sun, 18 Apr 2021 17:33:09 +0700 Subject: [PATCH] add h2c support --- app.go | 32 ++++++++++++----- app_test.go | 80 ++++++++++++++++++++++++++++++++++++++++--- config.go | 5 +++ config_test.go | 1 + go.mod | 1 + go.sum | 8 +++++ testdata/config1.yaml | 1 + 7 files changed, 114 insertions(+), 14 deletions(-) diff --git a/app.go b/app.go index 657b22f..7f3a99f 100644 --- a/app.go +++ b/app.go @@ -13,14 +13,18 @@ import ( "time" reuseport "github.com/kavu/go_reuseport" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" ) // App is the hime app type App struct { - srv http.Server - handler http.Handler - routes Routes - globals sync.Map + srv http.Server + handler http.Handler + routes Routes + globals sync.Map + onceServeHTTP sync.Once + serveHandler http.Handler template map[string]*tmpl templateFuncs []template.FuncMap @@ -30,6 +34,7 @@ type App struct { reusePort bool ETag bool + H2C bool } type ctxKeyApp struct{} @@ -39,6 +44,7 @@ func New() *App { app := &App{} app.srv.Handler = app app.tcpKeepAlive = 3 * time.Minute + app.H2C = true return app } @@ -64,6 +70,7 @@ func (app *App) Clone() *App { tcpKeepAlive: app.tcpKeepAlive, reusePort: app.reusePort, ETag: app.ETag, + H2C: app.H2C, } x.srv.Handler = x @@ -94,15 +101,22 @@ func (app *App) Handler(h http.Handler) *App { } func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { + app.onceServeHTTP.Do(func() { + app.serveHandler = app.handler + if app.serveHandler == nil { + app.serveHandler = http.DefaultServeMux + } + + if app.H2C { + app.serveHandler = h2c.NewHandler(app.serveHandler, &http2.Server{}) + } + }) + ctx := r.Context() ctx = context.WithValue(ctx, ctxKeyApp{}, app) r = r.WithContext(ctx) - if app.handler == nil { - http.DefaultServeMux.ServeHTTP(w, r) - return - } - app.handler.ServeHTTP(w, r) + app.serveHandler.ServeHTTP(w, r) } // Server returns server inside app diff --git a/app_test.go b/app_test.go index 42e5e67..9c66e21 100644 --- a/app_test.go +++ b/app_test.go @@ -4,11 +4,13 @@ import ( "context" "crypto/tls" "html/template" + "net" "net/http" "testing" "time" "github.com/stretchr/testify/assert" + "golang.org/x/net/http2" ) func TestApp(t *testing.T) { @@ -129,10 +131,12 @@ func TestApp(t *testing.T) { app.Address(":8081") go app.ListenAndServe() + defer app.Shutdown(context.Background()) + time.Sleep(time.Second) - http.Get("http://localhost:8081") - app.Shutdown(context.Background()) + _, err := http.Get("http://localhost:8081") + assert.NoError(t, err) assert.True(t, called) }) @@ -150,12 +154,15 @@ func TestApp(t *testing.T) { app.Address(":8082") go app.ListenAndServe() + defer app.Shutdown(context.Background()) + time.Sleep(time.Second) - (&http.Client{Transport: &http.Transport{ + client := http.Client{Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }}).Get("https://localhost:8082") - app.Shutdown(context.Background()) + }} + _, err := client.Get("https://localhost:8082") + assert.NoError(t, err) assert.True(t, called) }) @@ -213,4 +220,67 @@ func TestApp(t *testing.T) { time.Sleep(time.Second) assert.True(t, called) }) + + t.Run("H2C", func(t *testing.T) { + t.Parallel() + + app := New() + app.H2C = true + called := false + app.Handler(Handler(func(ctx *Context) error { + called = true + assert.Equal(t, "HTTP/2.0", ctx.Request.Proto) + return ctx.String("Hello") + })) + app.Address(":8085") + + go app.ListenAndServe() + defer app.Shutdown(context.Background()) + + time.Sleep(time.Second) + + client := http.Client{ + Transport: &http2.Transport{ + AllowHTTP: true, + DisableCompression: true, + DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { + return net.Dial(network, addr) + }, + }, + } + _, err := client.Get("http://localhost:8085") + assert.NoError(t, err) + assert.True(t, called) + }) + + t.Run("Disable H2C", func(t *testing.T) { + t.Parallel() + + app := New() + app.H2C = false + app.Handler(Handler(func(ctx *Context) error { + return ctx.String("Hello") + })) + app.Address(":8086") + + go app.ListenAndServe() + defer app.Shutdown(context.Background()) + + time.Sleep(time.Second) + + client := http.Client{ + Transport: &http2.Transport{ + AllowHTTP: true, + DisableCompression: true, + DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { + return net.Dial(network, addr) + }, + }, + } + _, err := client.Get("http://localhost:8086") + assert.Error(t, err) + + _, err = http.Get("http://localhost:8086") + assert.NoError(t, err) + }) } diff --git a/config.go b/config.go index 7f9964e..71ee944 100644 --- a/config.go +++ b/config.go @@ -21,6 +21,7 @@ type AppConfig struct { ReusePort *bool `yaml:"reusePort" json:"reusePort"` TCPKeepAlive string `yaml:"tcpKeepAlive" json:"tcpKeepAlive"` ETag *bool `yaml:"eTag" json:"eTag"` + H2C *bool `yaml:"h2c" json:"h2c"` GracefulShutdown *GracefulShutdown `yaml:"gracefulShutdown" json:"gracefulShutdown"` TLS *TLS `yaml:"tls" json:"tls"` HTTPSRedirect *HTTPSRedirect `yaml:"httpsRedirect" json:"httpsRedirect"` @@ -67,6 +68,7 @@ func parseDuration(s string, t *time.Duration) { // writeTimeout: 5s // idleTimeout: 30s // eTag: true +// h2c: true // gracefulShutdown: // timeout: 1m // wait: 5s @@ -97,6 +99,9 @@ func (app *App) Config(config AppConfig) *App { if server.ETag != nil { app.ETag = *server.ETag } + if server.H2C != nil { + app.H2C = *server.H2C + } if t := server.TLS; t != nil { app.srv.TLSConfig = server.TLS.config() diff --git a/config_test.go b/config_test.go index a7556bf..c64c378 100644 --- a/config_test.go +++ b/config_test.go @@ -38,6 +38,7 @@ func TestConfig(t *testing.T) { assert.Equal(t, app.tcpKeepAlive, time.Minute) assert.True(t, app.reusePort) assert.True(t, app.ETag) + assert.True(t, app.H2C) assert.Len(t, app.srv.TLSConfig.Certificates, 1) // graceful diff --git a/go.mod b/go.mod index 380f0be..e27fe6b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/tdewolff/minify/v2 v2.9.16 github.com/tdewolff/parse/v2 v2.5.15 // indirect + golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b diff --git a/go.sum b/go.sum index bb87699..6324a38 100644 --- a/go.sum +++ b/go.sum @@ -27,10 +27,18 @@ github.com/tdewolff/parse/v2 v2.5.15 h1:hYZKJZ0KfHMGhN3+hER4R9gQM/umJThkeeyJNtsO github.com/tdewolff/parse/v2 v2.5.15/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs= +golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/testdata/config1.yaml b/testdata/config1.yaml index a4c1d4a..18f59db 100644 --- a/testdata/config1.yaml +++ b/testdata/config1.yaml @@ -24,6 +24,7 @@ server: idleTimeout: 30s reusePort: true eTag: true + h2c: true tcpKeepAlive: 1m gracefulShutdown: timeout: 1m