diff options
author | Valery Piashchynski <[email protected]> | 2020-11-27 16:08:27 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2020-11-27 16:08:27 +0300 |
commit | 6abfe73b5f7b66a7be094486e29ab3f2ab1ce940 (patch) | |
tree | 888b5eb96ab396ef53f77ce3944ed62af9cc878d | |
parent | b5020bfce6b5362400cb9b578fe32c1a6ed5d61a (diff) |
Gzip plugin
-rwxr-xr-x | .github/workflows/ci-build.yml | 3 | ||||
-rwxr-xr-x | .rr.yaml | 40 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rwxr-xr-x | go.mod | 1 | ||||
-rwxr-xr-x | go.sum | 2 | ||||
-rw-r--r-- | plugins/gzip/plugin.go | 26 | ||||
-rw-r--r-- | plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml | 22 | ||||
-rw-r--r-- | plugins/gzip/tests/configs/.rr-http-withGzip.yaml | 22 | ||||
-rw-r--r-- | plugins/gzip/tests/plugin_test.go | 170 | ||||
-rw-r--r-- | plugins/gzip/tests/psr-worker.php (renamed from plugins/http/tests/psr-worker.php) | 2 | ||||
-rw-r--r-- | plugins/http/config.go | 20 | ||||
-rw-r--r-- | plugins/http/plugin.go | 30 | ||||
-rw-r--r-- | plugins/http/tests/configs/.rr-echoErr.yaml | 2 | ||||
-rw-r--r-- | plugins/http/tests/configs/.rr-http.yaml | 2 |
14 files changed, 308 insertions, 37 deletions
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 49d790f9..374f690a 100755 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -76,12 +76,13 @@ jobs: go test -v -race ./plugins/resetter/tests -tags=debug -coverprofile=informer.txt -covermode=atomic go test -v -race ./plugins/http/attributes -tags=debug -coverprofile=attributes.txt -covermode=atomic go test -v -race ./plugins/http/tests -tags=debug -coverprofile=http_tests.txt -covermode=atomic + go test -v -race ./plugins/gzip/tests -tags=debug -coverprofile=gzip.txt -covermode=atomic - name: Run code coverage uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - files: lib.txt, rpc_config.txt, rpc.txt, plugin_config.txt, logger.txt, server.txt, metrics.txt, informer.txt attributes.txt http_tests.txt + files: gzip.txt, lib.txt, rpc_config.txt, rpc.txt, plugin_config.txt, logger.txt, server.txt, metrics.txt, informer.txt attributes.txt http_tests.txt flags: unittests name: codecov-umbrella fail_ci_if_error: false @@ -1,7 +1,39 @@ -app: - commmand: "php app.php" +rpc: + listen: tcp://127.0.0.1:6001 + disabled: false + +server: + command: "php ../../../tests/http/client.php echo pipes" user: "" group: "" env: - DEBUG: true - APP_KEY: "..."
\ No newline at end of file + "RR_HTTP": "true" + relay: "pipes" + relayTimeout: "20s" + +http: + debug: true + address: 127.0.0.1:18903 + maxRequestSize: 1024 + middleware: [ "" ] + uploads: + forbid: [ ".php", ".exe", ".bat" ] + trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] + pool: + numWorkers: 2 + maxJobs: 0 + allocateTimeout: 60s + destroyTimeout: 60s + + ssl: + port: 8892 + redirect: false + cert: fixtures/server.crt + key: fixtures/server.key + # rootCa: root.crt + fcgi: + address: tcp://0.0.0.0:7921 + http2: + enabled: false + h2c: false + maxConcurrentStreams: 128
\ No newline at end of file @@ -10,4 +10,5 @@ test: go test -v -race -cover ./plugins/informer/tests -tags=debug go test -v -race -cover ./plugins/resetter/tests -tags=debug go test -v -race -cover ./plugins/http/attributes -tags=debug - go test -v -race -cover ./plugins/http/tests -tags=debug
\ No newline at end of file + go test -v -race -cover ./plugins/http/tests -tags=debug + go test -v -race -cover ./plugins/gzip/tests -tags=debug
\ No newline at end of file @@ -3,6 +3,7 @@ module github.com/spiral/roadrunner/v2 go 1.15 require ( + github.com/NYTimes/gziphandler v1.1.1 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/fatih/color v1.10.0 github.com/go-ole/go-ole v1.2.4 // indirect @@ -14,6 +14,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= diff --git a/plugins/gzip/plugin.go b/plugins/gzip/plugin.go new file mode 100644 index 00000000..f5a0f4ea --- /dev/null +++ b/plugins/gzip/plugin.go @@ -0,0 +1,26 @@ +package gzip + +import ( + "net/http" + + "github.com/NYTimes/gziphandler" +) + +const PluginName = "gzip" + +type Gzip struct { +} + +func (g *Gzip) Init() error { + return nil +} + +func (g *Gzip) Middleware(next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gziphandler.GzipHandler(next).ServeHTTP(w, r) + } +} + +func (g *Gzip) Name() string { + return PluginName +} diff --git a/plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml b/plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml new file mode 100644 index 00000000..df02c043 --- /dev/null +++ b/plugins/gzip/tests/configs/.rr-http-middlewareNotExist.yaml @@ -0,0 +1,22 @@ +server: + command: "php psr-worker.php" + user: "" + group: "" + env: + "RR_HTTP": "true" + relay: "pipes" + relayTimeout: "20s" + +http: + debug: true + address: 127.0.0.1:18103 + maxRequestSize: 1024 + middleware: [ "gzip", "foo" ] + uploads: + forbid: [ ".php", ".exe", ".bat" ] + trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] + pool: + numWorkers: 2 + maxJobs: 0 + allocateTimeout: 60s + destroyTimeout: 60s
\ No newline at end of file diff --git a/plugins/gzip/tests/configs/.rr-http-withGzip.yaml b/plugins/gzip/tests/configs/.rr-http-withGzip.yaml new file mode 100644 index 00000000..b91a9aad --- /dev/null +++ b/plugins/gzip/tests/configs/.rr-http-withGzip.yaml @@ -0,0 +1,22 @@ +server: + command: "php psr-worker.php" + user: "" + group: "" + env: + "RR_HTTP": "true" + relay: "pipes" + relayTimeout: "20s" + +http: + debug: true + address: 127.0.0.1:18953 + maxRequestSize: 1024 + middleware: [ "gzip" ] + uploads: + forbid: [ ".php", ".exe", ".bat" ] + trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] + pool: + numWorkers: 2 + maxJobs: 0 + allocateTimeout: 60s + destroyTimeout: 60s
\ No newline at end of file diff --git a/plugins/gzip/tests/plugin_test.go b/plugins/gzip/tests/plugin_test.go new file mode 100644 index 00000000..c7f12643 --- /dev/null +++ b/plugins/gzip/tests/plugin_test.go @@ -0,0 +1,170 @@ +package tests + +import ( + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/spiral/endure" + "github.com/spiral/roadrunner/v2/mocks" + "github.com/spiral/roadrunner/v2/plugins/config" + "github.com/spiral/roadrunner/v2/plugins/gzip" + httpPlugin "github.com/spiral/roadrunner/v2/plugins/http" + "github.com/spiral/roadrunner/v2/plugins/logger" + "github.com/spiral/roadrunner/v2/plugins/server" + "github.com/stretchr/testify/assert" +) + +func TestGzipPlugin(t *testing.T) { + cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel), endure.Visualize(endure.StdOut, "")) + assert.NoError(t, err) + + cfg := &config.Viper{ + Path: "configs/.rr-http-withGzip.yaml", + Prefix: "rr", + } + + err = cont.RegisterAll( + cfg, + &logger.ZapLogger{}, + &server.Plugin{}, + &httpPlugin.Plugin{}, + &gzip.Gzip{}, + ) + assert.NoError(t, err) + + err = cont.Init() + if err != nil { + t.Fatal(err) + } + + ch, err := cont.Serve() + assert.NoError(t, err) + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + wg := &sync.WaitGroup{} + wg.Add(1) + + go func() { + tt := time.NewTimer(time.Second * 10) + defer wg.Done() + for { + select { + case e := <-ch: + assert.Fail(t, "error", e.Error.Error()) + err = cont.Stop() + if err != nil { + assert.FailNow(t, "error", err.Error()) + } + case <-sig: + err = cont.Stop() + if err != nil { + assert.FailNow(t, "error", err.Error()) + } + return + case <-tt.C: + // timeout + err = cont.Stop() + if err != nil { + assert.FailNow(t, "error", err.Error()) + } + return + } + } + }() + + t.Run("GzipCheckHeader", headerCheck) + wg.Wait() +} + +func headerCheck(t *testing.T) { + req, err := http.NewRequest("GET", "http://localhost:18953", nil) + assert.NoError(t, err) + client := &http.Client{ + Transport: &http.Transport{ + DisableCompression: false, + }, + } + + r, err := client.Do(req) + assert.NoError(t, err) + assert.True(t, r.Uncompressed) + + err = r.Body.Close() + assert.NoError(t, err) +} + +func TestMiddlewareNotExist(t *testing.T) { + cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel), endure.Visualize(endure.StdOut, "")) + assert.NoError(t, err) + + cfg := &config.Viper{ + Path: "configs/.rr-http-middlewareNotExist.yaml", + Prefix: "rr", + } + + controller := gomock.NewController(t) + mockLogger := mocks.NewMockLogger(controller) + + mockLogger.EXPECT().Warn("requested middleware does not exist", "requested", "foo") + + err = cont.RegisterAll( + cfg, + mockLogger, + &server.Plugin{}, + &httpPlugin.Plugin{}, + &gzip.Gzip{}, + ) + assert.NoError(t, err) + + err = cont.Init() + if err != nil { + t.Fatal(err) + } + + ch, err := cont.Serve() + assert.NoError(t, err) + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + wg := &sync.WaitGroup{} + wg.Add(1) + + go func() { + tt := time.NewTimer(time.Second * 5) + defer wg.Done() + for { + select { + case e := <-ch: + assert.Fail(t, "error", e.Error.Error()) + err = cont.Stop() + if err != nil { + assert.FailNow(t, "error", err.Error()) + } + case <-sig: + err = cont.Stop() + if err != nil { + assert.FailNow(t, "error", err.Error()) + } + return + case <-tt.C: + // timeout + err = cont.Stop() + if err != nil { + assert.FailNow(t, "error", err.Error()) + } + return + } + } + }() + + wg.Wait() +} diff --git a/plugins/http/tests/psr-worker.php b/plugins/gzip/tests/psr-worker.php index 65fc6bde..ed936bde 100644 --- a/plugins/http/tests/psr-worker.php +++ b/plugins/gzip/tests/psr-worker.php @@ -14,7 +14,7 @@ $psr7 = new RoadRunner\PSR7Client($worker); while ($req = $psr7->acceptRequest()) { try { $resp = new \Zend\Diactoros\Response(); - $resp->getBody()->write("hello world"); + $resp->getBody()->write(str_repeat("hello world", 1000)); $psr7->respond($resp); } catch (\Throwable $e) { diff --git a/plugins/http/config.go b/plugins/http/config.go index 3f1fa69e..ae18e4ce 100644 --- a/plugins/http/config.go +++ b/plugins/http/config.go @@ -32,23 +32,6 @@ func (c *Cidrs) IsTrusted(ip string) bool { return false } -type ServerConfig struct { - // Command includes command strings with all the parameters, example: "php worker.php pipes". - Command string - - // User under which process will be started - User string - - // Relay defines connection method and factory to be used to connect to workers: - // "pipes", "tcp://:6001", "unix://pool.sock" - // This config section must not change on re-configuration. - Relay string - - // RelayTimeout defines for how long socket factory will be waiting for worker connection. This config section - // must not change on re-configuration. - RelayTimeout time.Duration -} - // Config configures RoadRunner HTTP server. type Config struct { // Port and port to handle as http server. @@ -78,6 +61,9 @@ type Config struct { // Env is environment variables passed to the http pool Env map[string]string + Middleware []string + + // slice of net.IPNet cidrs Cidrs } diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go index 7ce3a70d..19765afc 100644 --- a/plugins/http/plugin.go +++ b/plugins/http/plugin.go @@ -13,6 +13,7 @@ import ( "sync" "github.com/hashicorp/go-multierror" + "github.com/spiral/endure" "github.com/spiral/errors" "github.com/spiral/roadrunner/v2" "github.com/spiral/roadrunner/v2/interfaces/log" @@ -38,6 +39,8 @@ type Middleware interface { Middleware(f http.Handler) http.HandlerFunc } +type middleware map[string]Middleware + // Service manages pool, http servers. type Plugin struct { sync.Mutex @@ -48,7 +51,7 @@ type Plugin struct { cfg *Config // middlewares to chain - mdwr []Middleware + mdwr middleware // Event listener to stdout listener util.EventListener @@ -87,6 +90,7 @@ func (s *Plugin) Init(cfg config.Configurer, log log.Logger, server factory.Serv s.configurer = cfg s.log = log + s.mdwr = make(map[string]Middleware) if !s.cfg.EnableHTTP() && !s.cfg.EnableTLS() && !s.cfg.EnableFCGI() { return errors.E(op, errors.Disabled) @@ -333,8 +337,8 @@ func (s *Plugin) Collects() []interface{} { } } -func (s *Plugin) AddMiddleware(m Middleware) { - s.mdwr = append(s.mdwr, m) +func (s *Plugin) AddMiddleware(name endure.Named, m Middleware) { + s.mdwr[name.Name()] = m } func (s *Plugin) redirect(w http.ResponseWriter, r *http.Request) bool { @@ -498,19 +502,23 @@ func (s *Plugin) tlsAddr(host string, forcePort bool) string { func (s *Plugin) addMiddlewares() { if s.http != nil { - for i := 0; i < len(s.mdwr); i++ { - s.http.Handler = s.mdwr[i].Middleware(s.http.Handler) - } + applyMiddlewares(s.http, s.mdwr, s.cfg.Middleware, s.log) } if s.https != nil { - for i := 0; i < len(s.mdwr); i++ { - s.https.Handler = s.mdwr[i].Middleware(s.https.Handler) - } + applyMiddlewares(s.https, s.mdwr, s.cfg.Middleware, s.log) } if s.fcgi != nil { - for i := 0; i < len(s.mdwr); i++ { - s.fcgi.Handler = s.mdwr[i].Middleware(s.fcgi.Handler) + applyMiddlewares(s.fcgi, s.mdwr, s.cfg.Middleware, s.log) + } +} + +func applyMiddlewares(server *http.Server, middlewares map[string]Middleware, order []string, log log.Logger) { + for i := 0; i < len(order); i++ { + if mdwr, ok := middlewares[order[i]]; ok { + server.Handler = mdwr.Middleware(server.Handler) + } else { + log.Warn("requested middleware does not exist", "requested", order[i]) } } } diff --git a/plugins/http/tests/configs/.rr-echoErr.yaml b/plugins/http/tests/configs/.rr-echoErr.yaml index 696fc0ae..6ecdbb2a 100644 --- a/plugins/http/tests/configs/.rr-echoErr.yaml +++ b/plugins/http/tests/configs/.rr-echoErr.yaml @@ -15,7 +15,7 @@ http: debug: true address: 127.0.0.1:8080 maxRequestSize: 1024 - middleware: [ "" ] + middleware: [ "pluginMiddleware", "pluginMiddleware2" ] uploads: forbid: [ "" ] trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] diff --git a/plugins/http/tests/configs/.rr-http.yaml b/plugins/http/tests/configs/.rr-http.yaml index c907c5e7..7b91f735 100644 --- a/plugins/http/tests/configs/.rr-http.yaml +++ b/plugins/http/tests/configs/.rr-http.yaml @@ -15,7 +15,7 @@ http: debug: true address: 127.0.0.1:18903 maxRequestSize: 1024 - middleware: [ "" ] + middleware: [ "pluginMiddleware", "pluginMiddleware2" ] uploads: forbid: [ ".php", ".exe", ".bat" ] trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] |