summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfy-J <[email protected]>2019-01-05 16:50:00 +0300
committerGitHub <[email protected]>2019-01-05 16:50:00 +0300
commitc7dbbb682e272c9eace7cf9d7d8fbdeba8ad2636 (patch)
tree81fda7c04fe8d84ba72f4143db48891ba1544520
parent4258f54f5b1ef25a692644d2bd58256092b7c23f (diff)
parentd1532db3043a1038f287fe31d1f2537d37d4d3a9 (diff)
Merge pull request #82 from spiral/qol
Quality of Life update
-rw-r--r--.travis.yml25
-rw-r--r--CHANGELOG.md27
-rw-r--r--Makefile1
-rwxr-xr-xbuild.sh2
-rw-r--r--cmd/rr/cmd/root.go87
-rw-r--r--cmd/rr/cmd/serve.go11
-rw-r--r--cmd/rr/http/debug.go54
-rw-r--r--cmd/util/config.go114
-rw-r--r--composer.json3
-rw-r--r--go.mod7
-rw-r--r--osutil/isolate.go13
-rw-r--r--osutil/isolate_win.go13
-rw-r--r--pool.go2
-rw-r--r--server.go2
-rw-r--r--server_config.go3
-rw-r--r--service/env/environment.go16
-rw-r--r--service/env/service.go14
-rw-r--r--service/env/service_test.go17
-rw-r--r--service/http/handler.go43
-rw-r--r--service/http/handler_test.go333
-rw-r--r--service/http/service.go17
-rw-r--r--service/http/uploads_test.go40
-rw-r--r--service/rpc/config.go2
-rw-r--r--src/Diactoros/ServerRequestFactory.php26
-rw-r--r--src/Diactoros/StreamFactory.php45
-rw-r--r--src/Diactoros/UploadedFileFactory.php35
-rw-r--r--src/Exception/RoadRunnerException.php2
-rw-r--r--src/PSR7Client.php11
-rw-r--r--src/Worker.php4
-rw-r--r--tests/http/echoDelay.php11
-rw-r--r--tests/http/payload.php5
-rw-r--r--tests/http/pid.php2
-rw-r--r--util/state_test.go36
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
diff --git a/Makefile b/Makefile
index bcac2b29..345fd4b7 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/build.sh b/build.sh
index fd2263e0..5772f9f8 100755
--- a/build.sh
+++ b/build.sh
@@ -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": {
diff --git a/go.mod b/go.mod
index 4033f11b..2b04eb3a 100644
--- a/go.mod
+++ b/go.mod
@@ -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}
+}
diff --git a/pool.go b/pool.go
index 30c30dfa..7dfea26c 100644
--- a/pool.go
+++ b/pool.go
@@ -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.
diff --git a/server.go b/server.go
index 48741d20..25ce7945 100644
--- a/server.go
+++ b/server.go
@@ -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)
+}