From c864b6c17201e68c26b599a024ce16a55a7dad23 Mon Sep 17 00:00:00 2001 From: Antonije Ivanovic Date: Thu, 19 Oct 2023 17:37:03 +0100 Subject: [PATCH 1/5] Move support for h2c from gin router init to http server init. If we initialize h2c using gin router it will use h2c handler to wrap handler with all routes registered on gin router. The problem is that it is possible to construct router factory with RunServer function which will wrap h2c handler with additional handler. Any logic in this handler will not be able to understand http2 cleartext requests. We have this example in krakend-ce repository where CORS support is added through RunServer function. Then we have CORS handler -> h2c handler -> Gin router. Simplest fix is to move h2c support to server initialization and add h2c handler as the outermost layer. Signed-off-by: Antonije Ivanovic --- router/gin/engine.go | 1 - transport/http/server/server.go | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/router/gin/engine.go b/router/gin/engine.go index b75c491a3..98ace972a 100644 --- a/router/gin/engine.go +++ b/router/gin/engine.go @@ -73,7 +73,6 @@ func NewEngine(cfg config.ServiceConfig, opt EngineOptions) *gin.Engine { engine.AppEngine = ginOptions.AppEngine engine.MaxMultipartMemory = ginOptions.MaxMultipartMemory engine.RemoveExtraSlash = ginOptions.RemoveExtraSlash - engine.UseH2C = ginOptions.UseH2C paths = ginOptions.LoggerSkipPaths returnErrorMsg = ginOptions.ReturnErrorMsg diff --git a/transport/http/server/server.go b/transport/http/server/server.go index dbd9cc5dd..f05dc13d3 100644 --- a/transport/http/server/server.go +++ b/transport/http/server/server.go @@ -9,6 +9,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "encoding/json" "errors" "fmt" "net" @@ -20,6 +21,8 @@ import ( "github.com/luraproject/lura/v2/config" "github.com/luraproject/lura/v2/core" "github.com/luraproject/lura/v2/logging" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" ) // ToHTTPError translates an error into a HTTP status code @@ -38,6 +41,8 @@ const ( // HeaderIncompleteResponseValue is the value of the CompleteResponseHeader // if the response is not complete HeaderIncompleteResponseValue = "false" + // ginNamespace used to grab router extra config + ginNamespace = "github_com/luraproject/lura/router/gin" ) var ( @@ -139,7 +144,24 @@ func NewServer(cfg config.ServiceConfig, handler http.Handler) *http.Server { return NewServerWithLogger(cfg, handler, nil) } +type serverOptions struct { + // UseH2C enable h2c support. + UseH2C bool `json:"use_h2c"` +} + func NewServerWithLogger(cfg config.ServiceConfig, handler http.Handler, logger logging.Logger) *http.Server { + // wrap handler with h2c handler if it is enabled + srvCfg := serverOptions{} + if v, ok := cfg.ExtraConfig[ginNamespace]; ok { + if d, err := json.Marshal(v); err == nil { + if err := json.Unmarshal(d, &srvCfg); err == nil { + if srvCfg.UseH2C { + handler = h2c.NewHandler(handler, &http2.Server{}) + } + } + } + } + return &http.Server{ Addr: net.JoinHostPort(cfg.Address, fmt.Sprintf("%d", cfg.Port)), Handler: handler, From c2b430d91765626b7e1319003815ecad5248e8cd Mon Sep 17 00:00:00 2001 From: Antonije Ivanovic Date: Fri, 20 Oct 2023 10:57:18 +0100 Subject: [PATCH 2/5] Add test. Signed-off-by: Antonije Ivanovic --- transport/http/server/server_test.go | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/transport/http/server/server_test.go b/transport/http/server/server_test.go index cebd379f8..f6c39ab89 100644 --- a/transport/http/server/server_test.go +++ b/transport/http/server/server_test.go @@ -11,6 +11,7 @@ import ( "html" "log" "math/rand" + "net" "net/http" "os" "testing" @@ -18,6 +19,7 @@ import ( "github.com/luraproject/lura/v2/config" "github.com/luraproject/lura/v2/logging" + "golang.org/x/net/http2" ) func init() { @@ -201,6 +203,45 @@ func TestRunServer_plain(t *testing.T) { } } +func TestRunServer_h2c(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + port := newPort() + + done := make(chan error) + go func() { + done <- RunServer( + ctx, + config.ServiceConfig{ + Port: port, + ExtraConfig: config.ExtraConfig{ + ginNamespace: serverOptions{UseH2C: true}, + }, + }, + http.HandlerFunc(dummyHandler), + ) + }() + + <-time.After(100 * time.Millisecond) + + client := h2cClient() + resp, err := client.Get(fmt.Sprintf("http://localhost:%d", port)) + if err != nil { + t.Error(err) + return + } + if resp.StatusCode != 200 { + t.Errorf("unexpected status code: %d", resp.StatusCode) + return + } + cancel() + + if err = <-done; err != nil { + t.Error(err) + } +} + func TestRunServer_disabledTLS(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -412,6 +453,18 @@ func mtlsClient(certPath, keyPath string) (*http.Client, error) { return &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConf}}, nil } +// h2cClient initializes client which executes cleartext http2 requests +func h2cClient() *http.Client { + return &http.Client{ + Transport: &http2.Transport{ + DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) { + return net.Dial(network, addr) + }, + AllowHTTP: true, + }, + } +} + // newPort returns random port numbers to avoid port collisions during the tests func newPort() int { return 16666 + rand.Intn(40000) From f6dc72d9e424985fada755866e8885afc6b20ade Mon Sep 17 00:00:00 2001 From: Antonije Ivanovic Date: Tue, 24 Oct 2023 16:17:33 +0100 Subject: [PATCH 3/5] Addressed review comments. Signed-off-by: Antonije Ivanovic --- config/config.go | 3 +++ router/gin/engine.go | 1 + transport/http/server/server.go | 20 ++------------------ 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/config/config.go b/config/config.go index 13facceaf..c9b631b20 100644 --- a/config/config.go +++ b/config/config.go @@ -160,6 +160,9 @@ type ServiceConfig struct { // the router layer TLS *TLS `mapstructure:"tls"` + // UseH2C enables h2c support. + UseH2C bool `json:"use_h2c"` + // run lura in debug mode Debug bool `mapstructure:"debug_endpoint"` Echo bool `mapstructure:"echo_endpoint"` diff --git a/router/gin/engine.go b/router/gin/engine.go index 98ace972a..b75c491a3 100644 --- a/router/gin/engine.go +++ b/router/gin/engine.go @@ -73,6 +73,7 @@ func NewEngine(cfg config.ServiceConfig, opt EngineOptions) *gin.Engine { engine.AppEngine = ginOptions.AppEngine engine.MaxMultipartMemory = ginOptions.MaxMultipartMemory engine.RemoveExtraSlash = ginOptions.RemoveExtraSlash + engine.UseH2C = ginOptions.UseH2C paths = ginOptions.LoggerSkipPaths returnErrorMsg = ginOptions.ReturnErrorMsg diff --git a/transport/http/server/server.go b/transport/http/server/server.go index f05dc13d3..989ec1a4a 100644 --- a/transport/http/server/server.go +++ b/transport/http/server/server.go @@ -9,7 +9,6 @@ import ( "context" "crypto/tls" "crypto/x509" - "encoding/json" "errors" "fmt" "net" @@ -41,8 +40,6 @@ const ( // HeaderIncompleteResponseValue is the value of the CompleteResponseHeader // if the response is not complete HeaderIncompleteResponseValue = "false" - // ginNamespace used to grab router extra config - ginNamespace = "github_com/luraproject/lura/router/gin" ) var ( @@ -144,22 +141,9 @@ func NewServer(cfg config.ServiceConfig, handler http.Handler) *http.Server { return NewServerWithLogger(cfg, handler, nil) } -type serverOptions struct { - // UseH2C enable h2c support. - UseH2C bool `json:"use_h2c"` -} - func NewServerWithLogger(cfg config.ServiceConfig, handler http.Handler, logger logging.Logger) *http.Server { - // wrap handler with h2c handler if it is enabled - srvCfg := serverOptions{} - if v, ok := cfg.ExtraConfig[ginNamespace]; ok { - if d, err := json.Marshal(v); err == nil { - if err := json.Unmarshal(d, &srvCfg); err == nil { - if srvCfg.UseH2C { - handler = h2c.NewHandler(handler, &http2.Server{}) - } - } - } + if cfg.UseH2C { + handler = h2c.NewHandler(handler, &http2.Server{}) } return &http.Server{ From b777b7feb6a96850288cfc05a9b8b2528944c6b7 Mon Sep 17 00:00:00 2001 From: Antonije Ivanovic Date: Wed, 25 Oct 2023 10:46:54 +0100 Subject: [PATCH 4/5] Fix tests. Signed-off-by: Antonije Ivanovic --- transport/http/server/server_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/transport/http/server/server_test.go b/transport/http/server/server_test.go index f6c39ab89..42485c1ac 100644 --- a/transport/http/server/server_test.go +++ b/transport/http/server/server_test.go @@ -214,10 +214,8 @@ func TestRunServer_h2c(t *testing.T) { done <- RunServer( ctx, config.ServiceConfig{ - Port: port, - ExtraConfig: config.ExtraConfig{ - ginNamespace: serverOptions{UseH2C: true}, - }, + Port: port, + UseH2C: true, }, http.HandlerFunc(dummyHandler), ) From c9838bda35095ec8c5f9a59061fc657bb7967f0c Mon Sep 17 00:00:00 2001 From: Antonije Ivanovic Date: Thu, 26 Oct 2023 13:46:07 +0100 Subject: [PATCH 5/5] Update config hash in test Signed-off-by: Antonije Ivanovic --- config/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config_test.go b/config/config_test.go index dd81593d6..a9db9c741 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -211,7 +211,7 @@ func TestConfig_init(t *testing.T) { t.Error(err.Error()) } - if hash != "FK9W9HSD9jkExuf53M9SUs5bCwlwRde5kRX0cn8rGm4=" { + if hash != "NILRcvZq9y+zinGtRRfG+Zm1ORVE3gI2gjpcgDI3A/Q=" { t.Errorf("unexpected hash: %s", hash) } }