diff options
author | Wolfy-J <[email protected]> | 2019-01-05 16:50:00 +0300 |
---|---|---|
committer | GitHub <[email protected]> | 2019-01-05 16:50:00 +0300 |
commit | c7dbbb682e272c9eace7cf9d7d8fbdeba8ad2636 (patch) | |
tree | 81fda7c04fe8d84ba72f4143db48891ba1544520 | |
parent | 4258f54f5b1ef25a692644d2bd58256092b7c23f (diff) | |
parent | d1532db3043a1038f287fe31d1f2537d37d4d3a9 (diff) |
Merge pull request #82 from spiral/qol
Quality of Life update
33 files changed, 776 insertions, 247 deletions
diff --git a/.travis.yml b/.travis.yml index 6dd312df..53c3b379 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,21 +5,17 @@ sudo: required go: - "1.11.x" -before_install: - - sudo add-apt-repository -y ppa:ondrej/php - - sudo apt-get update - - sudo apt-get install -y php7.0-cli - - sudo cp `which php7.0` `which php` - - php -v - - composer self-update - install: - export GO111MODULE=on - go mod download - - composer install --no-interaction --prefer-source --ignore-platform-reqs + - php -v + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - php composer.phar install --no-interaction --prefer-source script: - go test -race -v -coverprofile=lib.txt -covermode=atomic + - go test ./util -race -v -coverprofile=util.txt -covermode=atomic - go test ./service -race -v -coverprofile=service.txt -covermode=atomic - go test ./service/env -race -v -coverprofile=env.txt -covermode=atomic - go test ./service/rpc -race -v -coverprofile=rpc.txt -covermode=atomic @@ -28,6 +24,7 @@ script: after_success: - bash <(curl -s https://codecov.io/bash) -f lib.txt + - bash <(curl -s https://codecov.io/bash) -f util.txt - bash <(curl -s https://codecov.io/bash) -f service.txt - bash <(curl -s https://codecov.io/bash) -f env.txt - bash <(curl -s https://codecov.io/bash) -f rpc.txt @@ -43,8 +40,6 @@ jobs: - sudo apt-get update - sudo apt-get install -y php7.0-cli - sudo cp `which php7.0` `which php` - - php -v - - composer self-update - stage: Test env: "PHP=7.1" before_install: @@ -52,8 +47,6 @@ jobs: - sudo apt-get update - sudo apt-get install -y php7.1-cli - sudo cp `which php7.1` `which php` - - php -v - - composer self-update - stage: Test env: "PHP=7.2" before_install: @@ -61,14 +54,10 @@ jobs: - sudo apt-get update - sudo apt-get install -y php7.2-cli - sudo cp `which php7.2` `which php` - - php -v - - composer self-update - stage: Test env: "PHP=7.3" before_install: - sudo add-apt-repository -y ppa:ondrej/php - sudo apt-get update - sudo apt-get install -y php7.3-cli - - sudo cp `which php7.3` `which php` - - php -v - - composer self-update
\ No newline at end of file + - sudo cp `which php7.3` `which php`
\ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d60bbbc5..0bb205a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,33 @@ CHANGELOG ========= +v1.3.0 (05.01.2019) +------------------- +- added support for zend/diactros 1.0 and 2.0 +- added `strict_types=1` +- added elapsed time into debug log +- ability to redefine config via flags (example: `rr serve -v -d -o http.workers.pool.numWorkers=1`) +- fixed bug causing child processes die before parent rr (annoying error on windows "worker exit status ....") +- improved stop sequence and graceful exit +- `env.Environment` has been spitted into `env.Setter` and `env.Getter` +- added `env.Copy` method +- config management has been moved out from root command into `utils` +- spf13/viper dependency has been bumped up to 1.3.1 +- more tests +- new travis configuration + +v1.2.8 (26.12.2018) +------------------- +- bugfix #76 error_log redirect has been disabled after `http:reset` command + +v1.2.7 (20.12.2018) +------------------- +- #67 bugfix, invalid protocol version while using HTTP/2 with new http-interop by @bognerf +- #66 added HTTP_USER_AGENT value and tests for it +- typo fix in static service by @Alex-Bond +- added PHP 7.3 to travis +- less ambiguous error when invalid data found in a pipe(`invalid prefix (checksum)` => `invalid data found in the buffer (possible echo)`) + v1.2.6 (18.10.2018) ------------------- - bugfix: ignored `stopping` value during http server shutdown @@ -10,6 +10,7 @@ uninstall: rm -f /usr/local/bin/rr test: go test -v -race -cover + go test -v -race -cover ./util go test -v -race -cover ./service go test -v -race -cover ./service/env go test -v -race -cover ./service/rpc @@ -2,7 +2,7 @@ cd $(dirname "${BASH_SOURCE[0]}") OD="$(pwd)" # Pushes application version into the build information. -RR_VERSION=1.2.7 +RR_VERSION=1.3.0 # Hardcode some values to the core package LDFLAGS="$LDFLAGS -X github.com/spiral/roadrunner/cmd/rr/cmd.Version=${RR_VERSION}" diff --git a/cmd/rr/cmd/root.go b/cmd/rr/cmd/root.go index 15a004e0..ceeeb840 100644 --- a/cmd/rr/cmd/root.go +++ b/cmd/rr/cmd/root.go @@ -23,16 +23,15 @@ package cmd import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/spiral/roadrunner/cmd/util" "github.com/spiral/roadrunner/service" "os" - "path/filepath" ) // Service bus for all the commands. var ( - cfgFile string + cfgFile string + override []string // Verbose enables verbosity mode (container specific). Verbose bool @@ -59,26 +58,6 @@ var ( } ) -// ViperWrapper provides interface bridge between v configs and service.Config. -type ViperWrapper struct { - v *viper.Viper -} - -// Get nested config section (sub-map), returns nil if section not found. -func (w *ViperWrapper) Get(key string) service.Config { - sub := w.v.Sub(key) - if sub == nil { - return nil - } - - return &ViperWrapper{sub} -} - -// Unmarshal unmarshal config data into given struct. -func (w *ViperWrapper) Unmarshal(out interface{}) error { - return w.v.Unmarshal(out) -} - // Execute adds all child commands to the CLI command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the CLI. func Execute() { @@ -92,60 +71,28 @@ func init() { CLI.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Verbose output") CLI.PersistentFlags().BoolVarP(&Debug, "debug", "d", false, "debug mode") CLI.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is .rr.yaml)") + CLI.PersistentFlags().StringArrayVarP( + &override, + "override", + "o", + nil, + "override config value (dot.notation=value)", + ) cobra.OnInitialize(func() { if Verbose { Logger.SetLevel(logrus.DebugLevel) } - if cfg := initConfig(cfgFile, []string{"."}, ".rr"); cfg != nil { - if err := Container.Init(cfg); err != nil { - util.Printf("<red+hb>Error:</reset> <red>%s</reset>\n", err) - os.Exit(1) - } + cfg, err := util.LoadConfig(cfgFile, []string{"."}, ".rr", override) + if err != nil { + Logger.Warnf("config: %s", err) + return } - }) -} - -func initConfig(cfgFile string, path []string, name string) service.Config { - cfg := viper.New() - - if cfgFile != "" { - if absPath, err := filepath.Abs(cfgFile); err == nil { - cfgFile = absPath - // force working absPath related to config file - if err := os.Chdir(filepath.Dir(absPath)); err != nil { - Logger.Error(err) - } + if err := Container.Init(cfg); err != nil { + util.Printf("<red+hb>Error:</reset> <red>%s</reset>\n", err) + os.Exit(1) } - - // Use cfg file from the flag. - cfg.SetConfigFile(cfgFile) - - if dir, err := filepath.Abs(cfgFile); err == nil { - // force working absPath related to config file - if err := os.Chdir(filepath.Dir(dir)); err != nil { - Logger.Error(err) - } - } - } else { - // automatic location - for _, p := range path { - cfg.AddConfigPath(p) - } - - cfg.SetConfigName(name) - } - - // read in environment variables that match - cfg.AutomaticEnv() - - // If a cfg file is found, read it in. - if err := cfg.ReadInConfig(); err != nil { - Logger.Warnf("config: %s", err) - return nil - } - - return &ViperWrapper{cfg} + }) } diff --git a/cmd/rr/cmd/serve.go b/cmd/rr/cmd/serve.go index 8028395a..31dd6039 100644 --- a/cmd/rr/cmd/serve.go +++ b/cmd/rr/cmd/serve.go @@ -36,14 +36,21 @@ func init() { Run: serveHandler, }) - signal.Notify(stopSignal, os.Interrupt, os.Kill, syscall.SIGTERM) + signal.Notify(stopSignal, os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGINT) } func serveHandler(cmd *cobra.Command, args []string) { + stopped := make(chan interface{}) + go func() { <-stopSignal Container.Stop() + close(stopped) }() - Container.Serve() + if err := Container.Serve(); err != nil { + return + } + + <-stopped } diff --git a/cmd/rr/http/debug.go b/cmd/rr/http/debug.go index 53980303..ae383e8d 100644 --- a/cmd/rr/http/debug.go +++ b/cmd/rr/http/debug.go @@ -8,7 +8,10 @@ import ( rr "github.com/spiral/roadrunner/cmd/rr/cmd" "github.com/spiral/roadrunner/cmd/util" rrhttp "github.com/spiral/roadrunner/service/http" + "net" "net/http" + "strings" + "time" ) func init() { @@ -37,25 +40,31 @@ func (s *debugger) listener(event int, ctx interface{}) { case rrhttp.EventResponse: e := ctx.(*rrhttp.ResponseEvent) s.logger.Info(util.Sprintf( - "<cyan+h>%s</reset> %s <white+hb>%s</reset> %s", + "<cyan+h>%s</reset> %s %s <white+hb>%s</reset> %s", e.Request.RemoteAddr, + elapsed(e.Elapsed()), statusColor(e.Response.Status), e.Request.Method, e.Request.URI, )) + case rrhttp.EventError: e := ctx.(*rrhttp.ErrorEvent) if _, ok := e.Error.(roadrunner.JobError); ok { s.logger.Info(util.Sprintf( - "%s <white+hb>%s</reset> %s", + "<cyan+h>%s</reset> %s %s <white+hb>%s</reset> %s", + addr(e.Request.RemoteAddr), + elapsed(e.Elapsed()), statusColor(500), e.Request.Method, uri(e.Request), )) } else { s.logger.Info(util.Sprintf( - "%s <white+hb>%s</reset> %s <red>%s</reset>", + "<cyan+h>%s</reset> %s %s <white+hb>%s</reset> %s <red>%s</reset>", + addr(e.Request.RemoteAddr), + elapsed(e.Elapsed()), statusColor(500), e.Request.Method, uri(e.Request), @@ -88,3 +97,42 @@ func uri(r *http.Request) string { return fmt.Sprintf("http://%s%s", r.Host, r.URL.String()) } + +// fits duration into 5 characters +func elapsed(d time.Duration) string { + var v string + switch { + case d > 100*time.Second: + v = fmt.Sprintf("%.1fs", d.Seconds()) + case d > 10*time.Second: + v = fmt.Sprintf("%.2fs", d.Seconds()) + case d > time.Second: + v = fmt.Sprintf("%.3fs", d.Seconds()) + case d > 100*time.Millisecond: + v = fmt.Sprintf("%.0fms", d.Seconds()*1000) + case d > 10*time.Millisecond: + v = fmt.Sprintf("%.1fms", d.Seconds()*1000) + default: + v = fmt.Sprintf("%.2fms", d.Seconds()*1000) + } + + if d > time.Second { + return util.Sprintf("<red>{%v}</reset>", v) + } + + if d > time.Millisecond*500 { + return util.Sprintf("<yellow>{%v}</reset>", v) + } + + return util.Sprintf("<gray+hb>{%v}</reset>", v) +} + +func addr(addr string) string { + // otherwise, return remote address as is + if !strings.ContainsRune(addr, ':') { + return addr + } + + addr, _, _ = net.SplitHostPort(addr) + return addr +} diff --git a/cmd/util/config.go b/cmd/util/config.go new file mode 100644 index 00000000..a829f44c --- /dev/null +++ b/cmd/util/config.go @@ -0,0 +1,114 @@ +package util + +import ( + "fmt" + "github.com/spf13/viper" + "github.com/spiral/roadrunner/service" + "os" + "path/filepath" + "strings" +) + +// configWrapper provides interface bridge between v configs and service.Config. +type configWrapper struct { + v *viper.Viper +} + +// Get nested config section (sub-map), returns nil if section not found. +func (w *configWrapper) Get(key string) service.Config { + sub := w.v.Sub(key) + if sub == nil { + return nil + } + + return &configWrapper{sub} +} + +// Unmarshal unmarshal config data into given struct. +func (w *configWrapper) Unmarshal(out interface{}) error { + return w.v.Unmarshal(out) +} + +// LoadConfig config and merge it's values with set of flags. +func LoadConfig(cfgFile string, path []string, name string, flags []string) (*configWrapper, error) { + cfg := viper.New() + + if cfgFile != "" { + if absPath, err := filepath.Abs(cfgFile); err == nil { + cfgFile = absPath + + // force working absPath related to config file + if err := os.Chdir(filepath.Dir(absPath)); err != nil { + return nil, err + } + } + + // Use cfg file from the flag. + cfg.SetConfigFile(cfgFile) + + if dir, err := filepath.Abs(cfgFile); err == nil { + // force working absPath related to config file + if err := os.Chdir(filepath.Dir(dir)); err != nil { + return nil, err + } + } + } else { + // automatic location + for _, p := range path { + cfg.AddConfigPath(p) + } + + cfg.SetConfigName(name) + } + + // read in environment variables that match + cfg.AutomaticEnv() + + // If a cfg file is found, read it in. + if err := cfg.ReadInConfig(); err != nil { + return nil, err + } + + if len(flags) != 0 { + for _, f := range flags { + k, v, err := parseFlag(f) + if err != nil { + return nil, err + } + + cfg.Set(k, v) + } + + merged := viper.New() + + // we have to copy all the merged values into new config in order normalize it (viper bug?) + if err := merged.MergeConfigMap(cfg.AllSettings()); err != nil { + return nil, err + } + + return &configWrapper{merged}, nil + } + + return &configWrapper{cfg}, nil +} + +func parseFlag(flag string) (string, string, error) { + if !strings.Contains(flag, "=") { + return "", "", fmt.Errorf("invalid flag `%s`", flag) + } + + parts := strings.SplitN(strings.TrimLeft(flag, " \"'`"), "=", 2) + + return strings.Trim(parts[0], " \n\t"), parseValue(strings.Trim(parts[1], " \n\t")), nil +} + +func parseValue(value string) string { + escape := []rune(value)[0] + + if escape == '"' || escape == '\'' || escape == '`' { + value = strings.Trim(value, string(escape)) + value = strings.Replace(value, fmt.Sprintf("\\%s", string(escape)), string(escape), -1) + } + + return value +} diff --git a/composer.json b/composer.json index 591a1ae8..a811d756 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,11 @@ ], "require": { "php": "^7.0", + "ext-json": "*", "spiral/goridge": "^2.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0", - "http-interop/http-factory-diactoros": "^1.0" + "zendframework/zend-diactoros": "^1.3|^2.0" }, "autoload": { "psr-4": { @@ -13,18 +13,15 @@ require ( github.com/mattn/go-isatty v0.0.4 // indirect github.com/mattn/go-runewidth v0.0.3 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b - github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc github.com/pkg/errors v0.8.0 github.com/shirou/gopsutil v2.17.12+incompatible github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect github.com/sirupsen/logrus v1.1.1 github.com/spf13/cobra v0.0.3 - github.com/spf13/pflag v1.0.3 // indirect - github.com/spf13/viper v1.2.1 + github.com/spf13/viper v1.3.1 github.com/spiral/goridge v2.1.3+incompatible - golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e // indirect + github.com/stretchr/testify v1.2.2 golang.org/x/net v0.0.0-20181017193950-04a2e542c03f - golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/osutil/isolate.go b/osutil/isolate.go new file mode 100644 index 00000000..d4b64fb6 --- /dev/null +++ b/osutil/isolate.go @@ -0,0 +1,13 @@ +// +build !windows + +package osutil + +import ( + "os/exec" + "syscall" +) + +// IsolateProcess change gpid for the process to avoid bypassing signals to php processes. +func IsolateProcess(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true, Pgid: 0} +} diff --git a/osutil/isolate_win.go b/osutil/isolate_win.go new file mode 100644 index 00000000..ca7fca20 --- /dev/null +++ b/osutil/isolate_win.go @@ -0,0 +1,13 @@ +// +build windows + +package osutil + +import ( + "os/exec" + "syscall" +) + +// IsolateProcess change gpid for the process to avoid bypassing signals to php processes. +func IsolateProcess(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP} +} @@ -22,7 +22,7 @@ const ( // Pool managed set of inner worker processes. type Pool interface { - // AddListener all caused events to attached watcher. + // Listen all caused events to attached watcher. Listen(l func(event int, ctx interface{})) // Exec one task with given payload and context, returns result or error. @@ -176,7 +176,7 @@ func (s *Server) Pool() Pool { return s.pool } -// AddListener pool events. +// Listen pool events. func (s *Server) poolListener(event int, ctx interface{}) { if event == EventPoolError { // pool failure, rebuilding diff --git a/server_config.go b/server_config.go index 454b7992..35965962 100644 --- a/server_config.go +++ b/server_config.go @@ -3,6 +3,7 @@ package roadrunner import ( "errors" "fmt" + "github.com/spiral/roadrunner/osutil" "net" "os" "os/exec" @@ -75,6 +76,8 @@ func (cfg *ServerConfig) makeCommand() func() *exec.Cmd { var cmd = strings.Split(cfg.Command, " ") return func() *exec.Cmd { cmd := exec.Command(cmd[0], cmd[1:]...) + osutil.IsolateProcess(cmd) + cmd.Env = append(os.Environ(), fmt.Sprintf("RR_RELAY=%s", cfg.Relay)) cmd.Env = append(cmd.Env, cfg.env...) return cmd diff --git a/service/env/environment.go b/service/env/environment.go index 52a5bcf4..ab8febf7 100644 --- a/service/env/environment.go +++ b/service/env/environment.go @@ -3,9 +3,21 @@ package env // Environment aggregates list of environment variables. This interface can be used in custom implementation to drive // values from external sources. type Environment interface { - // GetEnv must return list of env variables. - GetEnv() (map[string]string, error) + Setter + Getter + + // Copy all environment values. + Copy(setter Setter) error +} +// Setter provides ability to set environment value. +type Setter interface { // SetEnv sets or creates environment value. SetEnv(key, value string) } + +// Getter provides ability to set environment value. +type Getter interface { + // GetEnv must return list of env variables. + GetEnv() (map[string]string, error) +} diff --git a/service/env/service.go b/service/env/service.go index 4d1327d4..83175b36 100644 --- a/service/env/service.go +++ b/service/env/service.go @@ -39,3 +39,17 @@ func (s *Service) GetEnv() (map[string]string, error) { func (s *Service) SetEnv(key, value string) { s.values[key] = value } + +// Copy all environment values. +func (s *Service) Copy(setter Setter) error { + values, err := s.GetEnv() + if err != nil { + return err + } + + for k, v := range values { + setter.SetEnv(k, v) + } + + return nil +} diff --git a/service/env/service_test.go b/service/env/service_test.go index 61fecd28..c20bb76c 100644 --- a/service/env/service_test.go +++ b/service/env/service_test.go @@ -49,3 +49,20 @@ func Test_Set(t *testing.T) { assert.Equal(t, "value-new", values["key"]) assert.Equal(t, "new", values["other"]) } + +func Test_Copy(t *testing.T) { + s1 := NewService(map[string]string{"RR": "version"}) + s2 := NewService(map[string]string{}) + + s1.SetEnv("key", "value-new") + s1.SetEnv("other", "new") + + assert.NoError(t, s1.Copy(s2)) + + values, err := s2.GetEnv() + assert.NoError(t, err) + assert.Len(t, values, 3) + assert.Equal(t, "version", values["RR"]) + assert.Equal(t, "value-new", values["key"]) + assert.Equal(t, "new", values["other"]) +} diff --git a/service/http/handler.go b/service/http/handler.go index d7521959..8cebc42a 100644 --- a/service/http/handler.go +++ b/service/http/handler.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" "sync" + "time" ) const ( @@ -23,6 +24,15 @@ type ErrorEvent struct { // Error - associated error, if any. Error error + + // event timings + start time.Time + elapsed time.Duration +} + +// Elapsed returns duration of the invocation. +func (e *ErrorEvent) Elapsed() time.Duration { + return e.elapsed } // ResponseEvent represents singular http response event. @@ -32,6 +42,15 @@ type ResponseEvent struct { // Response contains service response. Response *Response + + // event timings + start time.Time + elapsed time.Duration +} + +// Elapsed returns duration of the invocation. +func (e *ResponseEvent) Elapsed() time.Duration { + return e.elapsed } // Handler serves http connections to underlying PHP application using PSR-7 protocol. Context will include request headers, @@ -53,14 +72,16 @@ func (h *Handler) Listen(l func(event int, ctx interface{})) { // mdwr serve using PSR-7 requests passed to underlying application. Attempts to serve static files first if enabled. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + start := time.Now() + // validating request size if h.cfg.MaxRequest != 0 { if length := r.Header.Get("content-length"); length != "" { if size, err := strconv.ParseInt(length, 10, 64); err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } else if size > h.cfg.MaxRequest*1024*1024 { - h.handleError(w, r, errors.New("request body max size is exceeded")) + h.handleError(w, r, errors.New("request body max size is exceeded"), start) return } } @@ -68,7 +89,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { req, err := NewRequest(r, h.cfg.Uploads) if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } @@ -77,37 +98,37 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { p, err := req.Payload() if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } rsp, err := h.rr.Exec(p) if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } resp, err := NewResponse(rsp) if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } - h.handleResponse(req, resp) + h.handleResponse(req, resp, start) resp.Write(w) } // handleError sends error. -func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) { - h.throw(EventError, &ErrorEvent{Request: r, Error: err}) +func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error, start time.Time) { + h.throw(EventError, &ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)}) w.WriteHeader(500) w.Write([]byte(err.Error())) } // handleResponse triggers response event. -func (h *Handler) handleResponse(req *Request, resp *Response) { - h.throw(EventResponse, &ResponseEvent{Request: req, Response: resp}) +func (h *Handler) handleResponse(req *Request, resp *Response, start time.Time) { + h.throw(EventResponse, &ResponseEvent{Request: req, Response: resp, start: start, elapsed: time.Since(start)}) } // throw invokes event handler if any. diff --git a/service/http/handler_test.go b/service/http/handler_test.go index 1750bf43..770158e5 100644 --- a/service/http/handler_test.go +++ b/service/http/handler_test.go @@ -29,8 +29,8 @@ func get(url string) (string, *http.Response, error) { return string(b), r, err } -func TestServer_Echo(t *testing.T) { - st := &Handler{ +func TestHandler_Echo(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -49,10 +49,10 @@ func TestServer_Echo(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -65,7 +65,7 @@ func TestServer_Echo(t *testing.T) { } func Test_HandlerErrors(t *testing.T) { - st := &Handler{ + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -87,12 +87,12 @@ func Test_HandlerErrors(t *testing.T) { wr := httptest.NewRecorder() rq := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("data"))) - st.ServeHTTP(wr, rq) + h.ServeHTTP(wr, rq) assert.Equal(t, 500, wr.Code) } func Test_Handler_JSON_error(t *testing.T) { - st := &Handler{ + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -116,12 +116,12 @@ func Test_Handler_JSON_error(t *testing.T) { rq.Header.Add("Content-Type", "application/json") rq.Header.Add("Content-Size", "3") - st.ServeHTTP(wr, rq) + h.ServeHTTP(wr, rq) assert.Equal(t, 500, wr.Code) } -func TestServer_Headers(t *testing.T) { - st := &Handler{ +func TestHandler_Headers(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -140,10 +140,10 @@ func TestServer_Headers(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8078", Handler: st} + hs := &http.Server{Addr: ":8078", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -167,8 +167,8 @@ func TestServer_Headers(t *testing.T) { assert.Equal(t, "SAMPLE", string(b)) } -func TestServer_Empty_User_Agent(t *testing.T) { - st := &Handler{ +func TestHandler_Empty_User_Agent(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -187,10 +187,10 @@ func TestServer_Empty_User_Agent(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8088", Handler: st} + hs := &http.Server{Addr: ":8088", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -213,9 +213,8 @@ func TestServer_Empty_User_Agent(t *testing.T) { assert.Equal(t, "", string(b)) } - -func TestServer_User_Agent(t *testing.T) { - st := &Handler{ +func TestHandler_User_Agent(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -234,10 +233,10 @@ func TestServer_User_Agent(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8088", Handler: st} + hs := &http.Server{Addr: ":8088", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -260,8 +259,8 @@ func TestServer_User_Agent(t *testing.T) { assert.Equal(t, "go-agent", string(b)) } -func TestServer_Cookies(t *testing.T) { - st := &Handler{ +func TestHandler_Cookies(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -280,10 +279,10 @@ func TestServer_Cookies(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8079", Handler: st} + hs := &http.Server{Addr: ":8079", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -311,8 +310,8 @@ func TestServer_Cookies(t *testing.T) { } } -func TestServer_JsonPayload_POST(t *testing.T) { - st := &Handler{ +func TestHandler_JsonPayload_POST(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -331,10 +330,10 @@ func TestServer_JsonPayload_POST(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8090", Handler: st} + hs := &http.Server{Addr: ":8090", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -361,8 +360,8 @@ func TestServer_JsonPayload_POST(t *testing.T) { assert.Equal(t, `{"value":"key"}`, string(b)) } -func TestServer_JsonPayload_PUT(t *testing.T) { - st := &Handler{ +func TestHandler_JsonPayload_PUT(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -381,10 +380,10 @@ func TestServer_JsonPayload_PUT(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8081", Handler: st} + hs := &http.Server{Addr: ":8081", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -407,8 +406,8 @@ func TestServer_JsonPayload_PUT(t *testing.T) { assert.Equal(t, `{"value":"key"}`, string(b)) } -func TestServer_JsonPayload_PATCH(t *testing.T) { - st := &Handler{ +func TestHandler_JsonPayload_PATCH(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -427,10 +426,10 @@ func TestServer_JsonPayload_PATCH(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8082", Handler: st} + hs := &http.Server{Addr: ":8082", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -453,8 +452,8 @@ func TestServer_JsonPayload_PATCH(t *testing.T) { assert.Equal(t, `{"value":"key"}`, string(b)) } -func TestServer_FormData_POST(t *testing.T) { - st := &Handler{ +func TestHandler_FormData_POST(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -473,10 +472,10 @@ func TestServer_FormData_POST(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8083", Handler: st} + hs := &http.Server{Addr: ":8083", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -511,8 +510,8 @@ func TestServer_FormData_POST(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_FormData_PUT(t *testing.T) { - st := &Handler{ +func TestHandler_FormData_PUT(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -531,10 +530,10 @@ func TestServer_FormData_PUT(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8084", Handler: st} + hs := &http.Server{Addr: ":8084", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -569,8 +568,8 @@ func TestServer_FormData_PUT(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_FormData_PATCH(t *testing.T) { - st := &Handler{ +func TestHandler_FormData_PATCH(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -589,10 +588,10 @@ func TestServer_FormData_PATCH(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8085", Handler: st} + hs := &http.Server{Addr: ":8085", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -627,8 +626,8 @@ func TestServer_FormData_PATCH(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Multipart_POST(t *testing.T) { - st := &Handler{ +func TestHandler_Multipart_POST(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -647,10 +646,10 @@ func TestServer_Multipart_POST(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8019", Handler: st} + hs := &http.Server{Addr: ":8019", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -689,8 +688,8 @@ func TestServer_Multipart_POST(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Multipart_PUT(t *testing.T) { - st := &Handler{ +func TestHandler_Multipart_PUT(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -709,10 +708,10 @@ func TestServer_Multipart_PUT(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8020", Handler: st} + hs := &http.Server{Addr: ":8020", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -751,8 +750,8 @@ func TestServer_Multipart_PUT(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Multipart_PATCH(t *testing.T) { - st := &Handler{ +func TestHandler_Multipart_PATCH(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -771,10 +770,10 @@ func TestServer_Multipart_PATCH(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -813,8 +812,8 @@ func TestServer_Multipart_PATCH(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Error(t *testing.T) { - st := &Handler{ +func TestHandler_Error(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -833,10 +832,10 @@ func TestServer_Error(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -847,8 +846,8 @@ func TestServer_Error(t *testing.T) { assert.Equal(t, 500, r.StatusCode) } -func TestServer_Error2(t *testing.T) { - st := &Handler{ +func TestHandler_Error2(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -867,10 +866,10 @@ func TestServer_Error2(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -881,8 +880,8 @@ func TestServer_Error2(t *testing.T) { assert.Equal(t, 500, r.StatusCode) } -func TestServer_Error3(t *testing.T) { - st := &Handler{ +func TestHandler_Error3(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1, Uploads: &UploadsConfig{ @@ -901,10 +900,10 @@ func TestServer_Error3(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -926,8 +925,154 @@ func TestServer_Error3(t *testing.T) { assert.Equal(t, 500, r.StatusCode) } +func TestHandler_ResponseDuration(t *testing.T) { + h := &Handler{ + cfg: &Config{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: roadrunner.NewServer(&roadrunner.ServerConfig{ + Command: "php ../../tests/http/client.php echo pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: 1, + AllocateTimeout: 10000000, + DestroyTimeout: 10000000, + }, + }), + } + + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8177", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + time.Sleep(time.Millisecond * 10) + + gotresp := make(chan interface{}) + h.Listen(func(event int, ctx interface{}) { + if event == EventResponse { + c := ctx.(*ResponseEvent) + + if c.Elapsed() > 0 { + close(gotresp) + } + } + }) + + body, r, err := get("http://localhost:8177/?hello=world") + assert.NoError(t, err) + + <-gotresp + + assert.Equal(t, 201, r.StatusCode) + assert.Equal(t, "WORLD", body) +} + +func TestHandler_ResponseDurationDelayed(t *testing.T) { + h := &Handler{ + cfg: &Config{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: roadrunner.NewServer(&roadrunner.ServerConfig{ + Command: "php ../../tests/http/client.php echoDelay pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: 1, + AllocateTimeout: 10000000, + DestroyTimeout: 10000000, + }, + }), + } + + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8177", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + time.Sleep(time.Millisecond * 10) + + gotresp := make(chan interface{}) + h.Listen(func(event int, ctx interface{}) { + if event == EventResponse { + c := ctx.(*ResponseEvent) + + if c.Elapsed() > time.Second { + close(gotresp) + } + } + }) + + body, r, err := get("http://localhost:8177/?hello=world") + assert.NoError(t, err) + + <-gotresp + + assert.Equal(t, 201, r.StatusCode) + assert.Equal(t, "WORLD", body) +} + +func TestHandler_ErrorDuration(t *testing.T) { + h := &Handler{ + cfg: &Config{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: roadrunner.NewServer(&roadrunner.ServerConfig{ + Command: "php ../../tests/http/client.php error pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: 1, + AllocateTimeout: 10000000, + DestroyTimeout: 10000000, + }, + }), + } + + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8177", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + time.Sleep(time.Millisecond * 10) + + goterr := make(chan interface{}) + h.Listen(func(event int, ctx interface{}) { + if event == EventError { + c := ctx.(*ErrorEvent) + + if c.Elapsed() > 0 { + close(goterr) + } + } + }) + + _, r, err := get("http://localhost:8177/?hello=world") + assert.NoError(t, err) + + <-goterr + + assert.Equal(t, 500, r.StatusCode) +} + func BenchmarkHandler_Listen_Echo(b *testing.B) { - st := &Handler{ + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -946,10 +1091,10 @@ func BenchmarkHandler_Listen_Echo(b *testing.B) { }), } - st.rr.Start() - defer st.rr.Stop() + h.rr.Start() + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() diff --git a/service/http/service.go b/service/http/service.go index e8e8eb51..ad59f887 100644 --- a/service/http/service.go +++ b/service/http/service.go @@ -54,7 +54,9 @@ func (s *Service) Init(cfg *Config, r *rpc.Service, e env.Environment) (bool, er s.cfg = cfg s.env = e if r != nil { - r.Register(ID, &rpcServer{s}) + if err := r.Register(ID, &rpcServer{s}); err != nil { + return false, err + } } return true, nil @@ -65,18 +67,13 @@ func (s *Service) Serve() error { s.mu.Lock() if s.env != nil { - values, err := s.env.GetEnv() - if err != nil { - return err - } - - for k, v := range values { - s.cfg.Workers.SetEnv(k, v) + if err := s.env.Copy(s.cfg.Workers); err != nil { + return nil } - - s.cfg.Workers.SetEnv("RR_HTTP", "true") } + s.cfg.Workers.SetEnv("RR_HTTP", "true") + s.rr = roadrunner.NewServer(s.cfg.Workers) s.rr.Listen(s.throw) diff --git a/service/http/uploads_test.go b/service/http/uploads_test.go index 96e95733..d452f834 100644 --- a/service/http/uploads_test.go +++ b/service/http/uploads_test.go @@ -17,8 +17,8 @@ import ( "time" ) -func TestServer_Upload_File(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_File(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -37,10 +37,10 @@ func TestServer_Upload_File(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -78,8 +78,8 @@ func TestServer_Upload_File(t *testing.T) { assert.Equal(t, `{"upload":`+fs+`}`, string(b)) } -func TestServer_Upload_NestedFile(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_NestedFile(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -98,10 +98,10 @@ func TestServer_Upload_NestedFile(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -139,8 +139,8 @@ func TestServer_Upload_NestedFile(t *testing.T) { assert.Equal(t, `{"upload":{"x":{"y":{"z":[`+fs+`]}}}}`, string(b)) } -func TestServer_Upload_File_NoTmpDir(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_File_NoTmpDir(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -159,10 +159,10 @@ func TestServer_Upload_File_NoTmpDir(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -200,8 +200,8 @@ func TestServer_Upload_File_NoTmpDir(t *testing.T) { assert.Equal(t, `{"upload":`+fs+`}`, string(b)) } -func TestServer_Upload_File_Forbids(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_File_Forbids(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -220,10 +220,10 @@ func TestServer_Upload_File_Forbids(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() diff --git a/service/rpc/config.go b/service/rpc/config.go index 653da6ea..fc8cfdbb 100644 --- a/service/rpc/config.go +++ b/service/rpc/config.go @@ -13,7 +13,7 @@ type Config struct { // Indicates if RPC connection is enabled. Enable bool - // AddListener string + // Listen string Listen string } diff --git a/src/Diactoros/ServerRequestFactory.php b/src/Diactoros/ServerRequestFactory.php new file mode 100644 index 00000000..4d427121 --- /dev/null +++ b/src/Diactoros/ServerRequestFactory.php @@ -0,0 +1,26 @@ +<?php +declare(strict_types=1); + +/** + * High-performance PHP process supervisor and load balancer written in Go + * + * @author Wolfy-J + */ + +namespace Spiral\RoadRunner\Diactoros; + +use Psr\Http\Message\ServerRequestFactoryInterface; +use Psr\Http\Message\ServerRequestInterface; +use Zend\Diactoros\ServerRequest; + +final class ServerRequestFactory implements ServerRequestFactoryInterface +{ + /** + * @inheritdoc + */ + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + $uploadedFiles = []; + return new ServerRequest($serverParams, $uploadedFiles, $uri, $method); + } +}
\ No newline at end of file diff --git a/src/Diactoros/StreamFactory.php b/src/Diactoros/StreamFactory.php new file mode 100644 index 00000000..6004ef11 --- /dev/null +++ b/src/Diactoros/StreamFactory.php @@ -0,0 +1,45 @@ +<?php +declare(strict_types=1); + +/** + * High-performance PHP process supervisor and load balancer written in Go + * + * @author Wolfy-J + */ + +namespace Spiral\RoadRunner\Diactoros; + +use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; +use Zend\Diactoros\Stream; + +final class StreamFactory implements StreamFactoryInterface +{ + /** + * @inheritdoc + */ + public function createStream(string $content = ''): StreamInterface + { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $content); + rewind($resource); + return $this->createStreamFromResource($resource); + } + + /** + * @inheritdoc + */ + public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface + { + $resource = fopen($file, $mode); + return $this->createStreamFromResource($resource); + } + + /** + * @inheritdoc + */ + public function createStreamFromResource($resource): StreamInterface + { + return new Stream($resource); + } +}
\ No newline at end of file diff --git a/src/Diactoros/UploadedFileFactory.php b/src/Diactoros/UploadedFileFactory.php new file mode 100644 index 00000000..1543a826 --- /dev/null +++ b/src/Diactoros/UploadedFileFactory.php @@ -0,0 +1,35 @@ +<?php +declare(strict_types=1); + +/** + * High-performance PHP process supervisor and load balancer written in Go + * + * @author Wolfy-J + */ + +namespace Spiral\RoadRunner\Diactoros; + +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UploadedFileFactoryInterface; +use Psr\Http\Message\UploadedFileInterface; +use Zend\Diactoros\UploadedFile; + +final class UploadedFileFactory implements UploadedFileFactoryInterface +{ + /** + * @inheritdoc + */ + public function createUploadedFile( + StreamInterface $stream, + int $size = null, + int $error = \UPLOAD_ERR_OK, + string $clientFilename = null, + string $clientMediaType = null + ): UploadedFileInterface { + if ($size === null) { + $size = $stream->getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } +}
\ No newline at end of file diff --git a/src/Exception/RoadRunnerException.php b/src/Exception/RoadRunnerException.php index ee99bb2b..7c5c5929 100644 --- a/src/Exception/RoadRunnerException.php +++ b/src/Exception/RoadRunnerException.php @@ -1,4 +1,6 @@ <?php +declare(strict_types=1); + /** * High-performance PHP process supervisor and load balancer written in Go * diff --git a/src/PSR7Client.php b/src/PSR7Client.php index 0b148884..1136ce10 100644 --- a/src/PSR7Client.php +++ b/src/PSR7Client.php @@ -1,4 +1,6 @@ <?php +declare(strict_types=1); + /** * High-performance PHP process supervisor and load balancer written in Go * @@ -7,7 +9,6 @@ namespace Spiral\RoadRunner; -use Http\Factory\Diactoros; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; @@ -124,10 +125,10 @@ class PSR7Client $headers = new \stdClass(); } - $this->worker->send($response->getBody(), json_encode([ - 'status' => $response->getStatusCode(), - 'headers' => $headers - ])); + $this->worker->send( + $response->getBody()->__toString(), + json_encode(['status' => $response->getStatusCode(), 'headers' => $headers]) + ); } /** diff --git a/src/Worker.php b/src/Worker.php index 7f92a714..da80e461 100644 --- a/src/Worker.php +++ b/src/Worker.php @@ -1,4 +1,6 @@ <?php +declare(strict_types=1); + /** * High-performance PHP process supervisor and load balancer written in Go * @@ -132,7 +134,7 @@ class Worker * * @throws RoadRunnerException */ - private function handleControl(string $body = null, &$header = null, int $flags): bool + private function handleControl(string $body = null, &$header = null, int $flags = 0): bool { $header = $body; if (is_null($body) || $flags & Relay::PAYLOAD_RAW) { diff --git a/tests/http/echoDelay.php b/tests/http/echoDelay.php new file mode 100644 index 00000000..2ee2b049 --- /dev/null +++ b/tests/http/echoDelay.php @@ -0,0 +1,11 @@ +<?php + +use \Psr\Http\Message\ServerRequestInterface; +use \Psr\Http\Message\ResponseInterface; + +function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface +{ + sleep(1); + $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); + return $resp->withStatus(201); +}
\ No newline at end of file diff --git a/tests/http/payload.php b/tests/http/payload.php index a16984c5..52c0f819 100644 --- a/tests/http/payload.php +++ b/tests/http/payload.php @@ -5,6 +5,11 @@ use Psr\Http\Message\ServerRequestInterface; function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface { + if ( $req->getHeaderLine("Content-Type") != 'application/json' ) { + $resp->getBody()->write("invalid content-type"); + return $resp; + } + // we expect json body $p = json_decode($req->getBody(), true); $resp->getBody()->write(json_encode(array_flip($p))); diff --git a/tests/http/pid.php b/tests/http/pid.php index 1cc322bf..0c9a4e4d 100644 --- a/tests/http/pid.php +++ b/tests/http/pid.php @@ -5,7 +5,7 @@ use Psr\Http\Message\ServerRequestInterface; function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface { - $resp->getBody()->write(getmypid()); + $resp->getBody()->write((string)getmypid()); return $resp; }
\ No newline at end of file diff --git a/util/state_test.go b/util/state_test.go new file mode 100644 index 00000000..2afe682e --- /dev/null +++ b/util/state_test.go @@ -0,0 +1,36 @@ +package util + +import ( + "github.com/spiral/roadrunner" + "github.com/stretchr/testify/assert" + "runtime" + "testing" + "time" +) + +func TestServerState(t *testing.T) { + rr := roadrunner.NewServer( + &roadrunner.ServerConfig{ + Command: "php ../tests/client.php echo tcp", + Relay: "tcp://:9007", + RelayTimeout: 10 * time.Second, + Pool: &roadrunner.Config{ + NumWorkers: int64(runtime.NumCPU()), + AllocateTimeout: time.Second, + DestroyTimeout: time.Second, + }, + }) + defer rr.Stop() + + assert.NoError(t, rr.Start()) + + state, err := ServerState(rr) + assert.NoError(t, err) + + assert.Len(t, state, runtime.NumCPU()) +} + +func TestServerState_Err(t *testing.T) { + _, err := ServerState(nil) + assert.Error(t, err) +} |