diff --git a/app.go b/app.go index c3d54b77b0..7ea5111742 100644 --- a/app.go +++ b/app.go @@ -28,9 +28,8 @@ import ( "sync/atomic" "text/tabwriter" "time" - "encoding/json" - + "github.com/dgrr/http2" "github.com/gofiber/fiber/v2/internal/colorable" "github.com/gofiber/fiber/v2/internal/isatty" "github.com/gofiber/fiber/v2/utils" @@ -369,6 +368,13 @@ type Config struct { // If set to true, will print all routes with their method, path and handler. // Default: false EnablePrintRoutes bool `json:"enable_print_routes"` + + // Enable HTTP2 protocol + // + // WARNING: HTTP/2 support is still in early access. Some features may not be working. + // + // Default: false + EnableHTTP2 bool `json:"enable_http2"` } // Static defines configuration options when defining static assets. @@ -755,8 +761,19 @@ func NewError(code int, message ...string) *Error { return err } +//Configure fasthttp server with http2. +func (app *App) configureHTTP2() { + app.mutex.Lock() + http2.ConfigureServer(app.server, http2.ServerConfig{}) + app.mutex.Unlock() +} + // Listener can be used to pass a custom listener. func (app *App) Listener(ln net.Listener) error { + // Configure HTTP2 protocol + if app.config.EnableHTTP2 { + app.configureHTTP2() + } // Prefork is supported for custom listeners if app.config.Prefork { addr, tlsConfig := lnMetadata(app.config.Network, ln) @@ -808,6 +825,10 @@ func (app *App) Listen(addr string) error { // certFile and keyFile are the paths to TLS certificate and key file: // app.ListenTLS(":8080", "./cert.pem", "./cert.key") func (app *App) ListenTLS(addr, certFile, keyFile string) error { + // Configure HTTP2 protocol + if app.config.EnableHTTP2 { + app.configureHTTP2() + } // Check for valid cert/key path if len(certFile) == 0 || len(keyFile) == 0 { return errors.New("tls: provide a valid cert or key path") @@ -849,6 +870,11 @@ func (app *App) ListenTLS(addr, certFile, keyFile string) error { // certFile, keyFile and clientCertFile are the paths to TLS certificate and key file: // app.ListenMutualTLS(":8080", "./cert.pem", "./cert.key", "./client.pem") func (app *App) ListenMutualTLS(addr, certFile, keyFile, clientCertFile string) error { + // Configure HTTP2 protocol + if app.config.EnableHTTP2 { + app.configureHTTP2() + } + // Check for valid cert/key path if len(certFile) == 0 || len(keyFile) == 0 { return errors.New("tls: provide a valid cert or key path") diff --git a/app_test.go b/app_test.go index d1a40ea684..6669cc907c 100644 --- a/app_test.go +++ b/app_test.go @@ -1747,3 +1747,52 @@ func Test_App_print_Route_with_group(t *testing.T) { utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "PUT")) utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test/fiber/*")) } + +func Test_App_ListenTLS_With_HTTP2(t *testing.T) { + app := New(Config{ + EnableHTTP2: true, + }) + + go func() { + time.Sleep(1000 * time.Millisecond) + utils.AssertEqual(t, nil, app.Shutdown()) + }() + + utils.AssertEqual(t, nil, app.ListenTLS("127.0.0.1:8080", ".github/testdata/ssl.pem", ".github/testdata/ssl.key")) +} + +func Test_App_ListenTLS_Prefork_With_HTTP2(t *testing.T) { + testPreforkMaster = true + + app := New(Config{DisableStartupMessage: true, Prefork: true, EnableHTTP2: true}) + + // invalid key file content + utils.AssertEqual(t, false, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.tmpl") == nil) + + utils.AssertEqual(t, nil, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key")) + +} + +func Test_App_ListenMutualTLS_With_HTTP2(t *testing.T) { + app := New(Config{ + EnableHTTP2: true, + }) + + go func() { + time.Sleep(1000 * time.Millisecond) + utils.AssertEqual(t, nil, app.Shutdown()) + }() + + utils.AssertEqual(t, nil, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem")) +} + +func Test_App_ListenMutualTLS_Prefork_With_HTTP2(t *testing.T) { + testPreforkMaster = true + + app := New(Config{DisableStartupMessage: true, Prefork: true, EnableHTTP2: true}) + + // invalid key file content + utils.AssertEqual(t, false, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.html", "") == nil) + + utils.AssertEqual(t, nil, app.ListenMutualTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem")) +} diff --git a/client.go b/client.go index f0d8db7c42..d84171d66f 100644 --- a/client.go +++ b/client.go @@ -15,9 +15,8 @@ import ( "strings" "sync" "time" - "encoding/json" - + "github.com/dgrr/http2" "github.com/gofiber/fiber/v2/utils" "github.com/valyala/fasthttp" ) @@ -678,6 +677,13 @@ func (a *Agent) TLSConfig(config *tls.Config) *Agent { return a } +// Enable HTTP/2 support for client. +// +// WARNING: HTTP/2 support is still in early access. Some features may not be working. +func (a *Agent) EnableHTTP2() error { + return http2.ConfigureClient(a.HostClient, http2.ClientOpts{}) +} + // MaxRedirectsCount sets max redirect count for GET and HEAD. func (a *Agent) MaxRedirectsCount(count int) *Agent { a.maxRedirectsCount = count diff --git a/client_test.go b/client_test.go index f9de9367de..094cff5208 100644 --- a/client_test.go +++ b/client_test.go @@ -1176,3 +1176,16 @@ func (e *errorMultipartWriter) Close() error { return errors.New type errorWriter struct{} func (errorWriter) Write(_ []byte) (int, error) { return 0, errors.New("Write error") } + +func Test_HostClient_With_HTTP2(t *testing.T) { + a := AcquireAgent() + a.Host("example.com:443") + + utils.AssertEqual(t, nil, a.Parse()) + utils.AssertEqual(t, nil, a.EnableHTTP2()) + + statusCode, _, err := a.Get(nil, "https://example.com") + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, 200, statusCode) + +} diff --git a/ctx.go b/ctx.go index 4b54a8c334..64e60a7428 100644 --- a/ctx.go +++ b/ctx.go @@ -1590,3 +1590,13 @@ func (c *Ctx) IsFromLocal() bool { } return c.isLocalHost(ips[0]) } + +//IsHTTP2 will return true if request's header protocol equals to 'HTTP/2' +func (c *Ctx) IsHTTP2() bool { + return utils.UnsafeString(c.Request().Header.Protocol()) == "HTTP/2" +} + +//IsHTTP11 will return true if request's header protocol equals to 'HTTP/1.1' +func (c *Ctx) IsHTTP11() bool { + return utils.UnsafeString(c.Request().Header.Protocol()) == "HTTP/1.1" +} diff --git a/ctx_test.go b/ctx_test.go index 0c1b316245..35ddcc02f0 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -3859,3 +3859,18 @@ func Test_Ctx_IsFromLocal(t *testing.T) { utils.AssertEqual(t, false, c.IsFromLocal()) } } + +func Test_Ctx_IsHTTP11(t *testing.T) { + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) + utils.AssertEqual(t, true, c.IsHTTP11()) +} + +func Test_Ctx_IsHTTP2(t *testing.T) { + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + c.Request().Header.SetProtocol("HTTP/2") + defer app.ReleaseCtx(c) + utils.AssertEqual(t, true, c.IsHTTP2()) +} diff --git a/go.mod b/go.mod index ba0e0d82c7..eb44621a77 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/gofiber/fiber/v2 go 1.16 require ( - github.com/valyala/fasthttp v1.35.0 + github.com/dgrr/http2 v0.3.5 + github.com/valyala/fasthttp v1.38.0 + github.com/valyala/fastrand v1.1.0 // indirect golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 ) diff --git a/go.sum b/go.sum index 969f84c591..6f39d50686 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,37 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrr/http2 v0.3.5 h1:R54Afxa+yX21j64nbh3+qcj8vhvfuCows0NCxk83c54= +github.com/dgrr/http2 v0.3.5/go.mod h1:ZYb0czp1g5/p7q01JWWKA6qkERz8SScP8KL62ugeqes= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/summerwind/h2spec v2.2.1+incompatible/go.mod h1:eP7IHGVDEe9cbCxRNtmGfII77lBvLgJLNfJjTaKa9sI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.35.0 h1:wwkR8mZn2NbigFsaw2Zj5r+xkmzjbrA/lyTmiSlal/Y= -github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/fasthttp v1.38.0 h1:yTjSSNjuDi2PPvXY2836bIwLmiTS2T4T9p1coQshpco= +github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -22,3 +40,5 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=