summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.github/workflows/ci-build.yml3
-rw-r--r--Makefile3
-rw-r--r--plugins/http/attributes/attributes.go21
-rw-r--r--plugins/http/attributes/attributes_test.go4
-rw-r--r--plugins/http/config.go41
-rw-r--r--plugins/http/handler.go95
-rw-r--r--plugins/http/handler_test.go1962
-rw-r--r--plugins/http/parse.go2
-rw-r--r--plugins/http/plugin.go35
-rw-r--r--plugins/http/request.go5
-rw-r--r--plugins/http/response.go3
-rw-r--r--plugins/http/rpc.go34
-rw-r--r--plugins/http/test/http_test.go72
-rw-r--r--plugins/http/tests/configs/.rr-handler-echo.yaml (renamed from src/Logger/.empty)0
-rw-r--r--plugins/http/tests/configs/.rr-http.yaml (renamed from plugins/http/test/.rr-http.yaml)10
-rw-r--r--plugins/http/tests/fixtures/server.crt (renamed from plugins/http/fixtures/server.crt)0
-rw-r--r--plugins/http/tests/fixtures/server.key (renamed from plugins/http/fixtures/server.key)0
-rw-r--r--plugins/http/tests/handler_test.go1839
-rw-r--r--plugins/http/tests/http_test.go178
-rw-r--r--plugins/http/tests/plugin1.go26
-rw-r--r--plugins/http/tests/psr-worker.php (renamed from plugins/http/test/psr-worker.php)0
-rw-r--r--plugins/http/tests/yaml_configs.go39
-rw-r--r--plugins/http/uploads.go4
-rw-r--r--plugins/logger/plugin.go6
-rw-r--r--plugins/logger/zap_adapter.go (renamed from interfaces/log/zap_adapter.go)5
-rwxr-xr-xprotocol.go27
-rw-r--r--src/Exception/RoadRunnerException.php2
-rw-r--r--src/Http/HttpClient.php75
-rw-r--r--src/Http/PSR7Client.php217
-rw-r--r--src/Metrics/Metrics.php80
-rw-r--r--src/Metrics/MetricsInterface.php64
-rw-r--r--src/WorkerInterface.php0
-rw-r--r--[-rwxr-xr-x]tests/broken.php0
-rw-r--r--[-rwxr-xr-x]tests/client.php0
-rw-r--r--[-rwxr-xr-x]tests/delay.php0
-rw-r--r--[-rwxr-xr-x]tests/echo.php0
-rw-r--r--[-rwxr-xr-x]tests/error.php0
-rw-r--r--[-rwxr-xr-x]tests/failboot.php0
-rw-r--r--[-rwxr-xr-x]tests/gzip-large-file.txt0
-rw-r--r--[-rwxr-xr-x]tests/head.php0
-rw-r--r--[-rwxr-xr-x]tests/http/client.php0
-rw-r--r--[-rwxr-xr-x]tests/http/cookie.php0
-rw-r--r--[-rwxr-xr-x]tests/http/data.php0
-rw-r--r--[-rwxr-xr-x]tests/http/echo.php0
-rw-r--r--[-rwxr-xr-x]tests/http/echoDelay.php0
-rw-r--r--[-rwxr-xr-x]tests/http/echoerr.php0
-rw-r--r--[-rwxr-xr-x]tests/http/env.php0
-rw-r--r--[-rwxr-xr-x]tests/http/error.php0
-rw-r--r--[-rwxr-xr-x]tests/http/error2.php0
-rw-r--r--[-rwxr-xr-x]tests/http/header.php0
-rw-r--r--[-rwxr-xr-x]tests/http/headers.php0
-rw-r--r--[-rwxr-xr-x]tests/http/ip.php0
-rw-r--r--[-rwxr-xr-x]tests/http/memleak.php0
-rw-r--r--[-rwxr-xr-x]tests/http/payload.php0
-rw-r--r--[-rwxr-xr-x]tests/http/pid.php0
-rw-r--r--[-rwxr-xr-x]tests/http/push.php0
-rw-r--r--[-rwxr-xr-x]tests/http/request-uri.php0
-rw-r--r--[-rwxr-xr-x]tests/http/server.php0
-rw-r--r--[-rwxr-xr-x]tests/http/slow-client.php0
-rw-r--r--[-rwxr-xr-x]tests/http/stuck.php0
-rw-r--r--[-rwxr-xr-x]tests/http/upload.php0
-rw-r--r--[-rwxr-xr-x]tests/http/user-agent.php0
-rw-r--r--[-rwxr-xr-x]tests/pid.php0
-rw-r--r--[-rwxr-xr-x]tests/sample.txt0
-rw-r--r--[-rwxr-xr-x]tests/slow-client.php0
-rw-r--r--[-rwxr-xr-x]tests/slow-destroy.php0
-rw-r--r--[-rwxr-xr-x]tests/slow-pid.php0
-rw-r--r--[-rwxr-xr-x]tests/stop.php0
68 files changed, 2255 insertions, 2597 deletions
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 8b462f81..4844d107 100755
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -74,12 +74,13 @@ jobs:
go test -v -race ./plugins/metrics/tests -tags=debug -coverprofile=metrics.txt -covermode=atomic
go test -v -race ./plugins/informer/tests -tags=debug -coverprofile=informer.txt -covermode=atomic
go test -v -race ./plugins/resetter/tests -tags=debug -coverprofile=informer.txt -covermode=atomic
+ go test -v -race ./plugins/http/attributes -tags=debug -coverprofile=attributes.txt -covermode=atomic
- name: Run code coverage
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
- files: lib.txt, rpc_config.txt, rpc.txt, plugin_config.txt, logger.txt, server.txt, metrics.txt, informer.txt
+ files: lib.txt, rpc_config.txt, rpc.txt, plugin_config.txt, logger.txt, server.txt, metrics.txt, informer.txt attributes.txt
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
diff --git a/Makefile b/Makefile
index b45c21f1..9273d408 100644
--- a/Makefile
+++ b/Makefile
@@ -7,4 +7,5 @@ test:
go test -v -race -cover ./plugins/logger/tests -tags=debug
go test -v -race -cover ./plugins/metrics/tests -tags=debug
go test -v -race -cover ./plugins/informer/tests -tags=debug
- go test -v -race -cover ./plugins/resetter/tests -tags=debug \ No newline at end of file
+ go test -v -race -cover ./plugins/resetter/tests -tags=debug
+ go test -v -race -cover ./plugins/http/attributes -tags=debug \ No newline at end of file
diff --git a/plugins/http/attributes/attributes.go b/plugins/http/attributes/attributes.go
index 77d6ea69..4c453766 100644
--- a/plugins/http/attributes/attributes.go
+++ b/plugins/http/attributes/attributes.go
@@ -6,9 +6,18 @@ import (
"net/http"
)
-type attrKey int
+// contextKey is a value for use with context.WithValue. It's used as
+// a pointer so it fits in an interface{} without allocation.
+type contextKey struct {
+ name string
+}
+
+func (k *contextKey) String() string { return k.name }
-const contextKey attrKey = iota
+var (
+ // PsrContextKey is a context key. It can be used in the http attributes
+ PsrContextKey = &contextKey{"psr_attributes"}
+)
type attrs map[string]interface{}
@@ -30,12 +39,12 @@ func (v attrs) del(key string) {
// Init returns request with new context and attribute bag.
func Init(r *http.Request) *http.Request {
- return r.WithContext(context.WithValue(r.Context(), contextKey, attrs{}))
+ return r.WithContext(context.WithValue(r.Context(), PsrContextKey, attrs{}))
}
// All returns all context attributes.
func All(r *http.Request) map[string]interface{} {
- v := r.Context().Value(contextKey)
+ v := r.Context().Value(PsrContextKey)
if v == nil {
return attrs{}
}
@@ -46,7 +55,7 @@ func All(r *http.Request) map[string]interface{} {
// Get gets the value from request context. It replaces any existing
// values.
func Get(r *http.Request, key string) interface{} {
- v := r.Context().Value(contextKey)
+ v := r.Context().Value(PsrContextKey)
if v == nil {
return nil
}
@@ -57,7 +66,7 @@ func Get(r *http.Request, key string) interface{} {
// Set sets the key to value. It replaces any existing
// values. Context specific.
func Set(r *http.Request, key string, value interface{}) error {
- v := r.Context().Value(contextKey)
+ v := r.Context().Value(PsrContextKey)
if v == nil {
return errors.New("unable to find `psr:attributes` context key")
}
diff --git a/plugins/http/attributes/attributes_test.go b/plugins/http/attributes/attributes_test.go
index a4c85eea..5622deb4 100644
--- a/plugins/http/attributes/attributes_test.go
+++ b/plugins/http/attributes/attributes_test.go
@@ -72,8 +72,6 @@ func TestSetAttribute(t *testing.T) {
func TestSetAttributeNone(t *testing.T) {
r := &http.Request{}
err := Set(r, "key", "value")
- if err != nil {
- t.Errorf("error during the Set: error %v", err)
- }
+ assert.Error(t, err)
assert.Equal(t, Get(r, "key"), nil)
}
diff --git a/plugins/http/config.go b/plugins/http/config.go
index b3f4ca13..b827aced 100644
--- a/plugins/http/config.go
+++ b/plugins/http/config.go
@@ -11,6 +11,27 @@ import (
"github.com/spiral/roadrunner/v2"
)
+type Cidrs []*net.IPNet
+
+func (c *Cidrs) IsTrusted(ip string) bool {
+ if len(*c) == 0 {
+ return false
+ }
+
+ i := net.ParseIP(ip)
+ if i == nil {
+ return false
+ }
+
+ for _, cird := range *c {
+ if cird.Contains(i) {
+ return true
+ }
+ }
+
+ return false
+}
+
type ServerConfig struct {
// Command includes command strings with all the parameters, example: "php worker.php pipes".
Command string
@@ -30,7 +51,6 @@ type ServerConfig struct {
// Pool defines worker pool configuration, number of workers, timeouts and etc. This config section might change
// while server is running.
-
env map[string]string
}
@@ -49,11 +69,11 @@ type Config struct {
HTTP2 *HTTP2Config
// MaxRequestSize specified max size for payload body in megabytes, set 0 to unlimited.
- MaxRequestSize int64
+ MaxRequestSize uint64
// TrustedSubnets declare IP subnets which are allowed to set ip using X-Real-Ip and X-Forwarded-For
TrustedSubnets []string
- cidrs []*net.IPNet
+ cidrs Cidrs
// Uploads configures uploads configuration.
Uploads *UploadsConfig
@@ -185,24 +205,27 @@ func (c *Config) Hydrate(cfg Config) error {
}
}
- if err := c.parseCIDRs(); err != nil {
+ cidrs, err := ParseCIDRs(c.TrustedSubnets)
+ if err != nil {
return err
}
+ c.cidrs = cidrs
return c.Valid()
}
-func (c *Config) parseCIDRs() error {
- for _, cidr := range c.TrustedSubnets {
+func ParseCIDRs(subnets []string) (Cidrs, error) {
+ c := make(Cidrs, 0, len(subnets))
+ for _, cidr := range subnets {
_, cr, err := net.ParseCIDR(cidr)
if err != nil {
- return err
+ return nil, err
}
- c.cidrs = append(c.cidrs, cr)
+ c = append(c, cr)
}
- return nil
+ return c, nil
}
// IsTrusted if api can be trusted to use X-Real-Ip, X-Forwarded-For
diff --git a/plugins/http/handler.go b/plugins/http/handler.go
index 5b612d7e..efca6001 100644
--- a/plugins/http/handler.go
+++ b/plugins/http/handler.go
@@ -1,7 +1,6 @@
package http
import (
- "fmt"
"net"
"net/http"
"strconv"
@@ -9,9 +8,11 @@ import (
"sync"
"time"
- "github.com/pkg/errors"
+ "github.com/hashicorp/go-multierror"
+ "github.com/spiral/errors"
"github.com/spiral/roadrunner/v2"
"github.com/spiral/roadrunner/v2/interfaces/log"
+ "github.com/spiral/roadrunner/v2/util"
)
const (
@@ -22,6 +23,11 @@ const (
EventError
)
+type Handler interface {
+ AddListener(l util.EventListener)
+ ServeHTTP(w http.ResponseWriter, r *http.Request)
+}
+
// ErrorEvent represents singular http error event.
type ErrorEvent struct {
// Request contains client request, must not be stored.
@@ -60,16 +66,30 @@ func (e *ResponseEvent) Elapsed() time.Duration {
// Handler serves http connections to underlying PHP application using PSR-7 protocol. Context will include request headers,
// parsed files and query, payload will include parsed form dataTree (if any).
-type Handler struct {
- cfg *Config
- log log.Logger
- rr roadrunner.Pool
- mul sync.Mutex
- lsn func(event int, ctx interface{})
+type handler struct {
+ maxRequestSize uint64
+ uploads UploadsConfig
+ trusted Cidrs
+ log log.Logger
+ pool roadrunner.Pool
+ mul sync.Mutex
+ lsn util.EventListener
+}
+
+func NewHandler(maxReqSize uint64, uploads UploadsConfig, trusted Cidrs, pool roadrunner.Pool) (Handler, error) {
+ if pool == nil {
+ return nil, errors.E(errors.Str("pool should be initialized"))
+ }
+ return &handler{
+ maxRequestSize: maxReqSize * roadrunner.MB,
+ uploads: uploads,
+ pool: pool,
+ trusted: trusted,
+ }, nil
}
// Listen attaches handler event controller.
-func (h *Handler) Listen(l func(event int, ctx interface{})) {
+func (h *handler) AddListener(l util.EventListener) {
h.mul.Lock()
defer h.mul.Unlock()
@@ -77,23 +97,19 @@ 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) {
+func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ const op = errors.Op("ServeHTTP")
start := time.Now()
// validating request size
- if h.cfg.MaxRequestSize != 0 {
- if length := r.Header.Get("content-length"); length != "" {
- if size, err := strconv.ParseInt(length, 10, 64); err != nil {
- h.handleError(w, r, err, start)
- return
- } else if size > h.cfg.MaxRequestSize*1024*1024 {
- h.handleError(w, r, errors.New("request body max size is exceeded"), start)
- return
- }
+ if h.maxRequestSize != 0 {
+ err := h.maxSize(w, r, start, op)
+ if err != nil {
+ return
}
}
- req, err := NewRequest(r, h.cfg.Uploads)
+ req, err := NewRequest(r, h.uploads)
if err != nil {
h.handleError(w, r, err, start)
return
@@ -111,7 +127,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
- rsp, err := h.rr.Exec(p)
+ rsp, err := h.pool.Exec(p)
if err != nil {
h.handleError(w, r, err, start)
return
@@ -130,44 +146,59 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
+func (h *handler) maxSize(w http.ResponseWriter, r *http.Request, start time.Time, op errors.Op) error {
+ if length := r.Header.Get("content-length"); length != "" {
+ if size, err := strconv.ParseInt(length, 10, 64); err != nil {
+ h.handleError(w, r, err, start)
+ return err
+ } else if size > int64(h.maxRequestSize) {
+ h.handleError(w, r, errors.E(op, errors.Str("request body max size is exceeded")), start)
+ return err
+ }
+ }
+ return nil
+}
+
// handleError sends error.
-func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error, start time.Time) {
+func (h *handler) handleError(w http.ResponseWriter, r *http.Request, err error, start time.Time) {
// if pipe is broken, there is no sense to write the header
// in this case we just report about error
if err == errEPIPE {
- h.throw(EventError, &ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)})
+ h.throw(ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)})
return
}
+ err = multierror.Append(err)
// ResponseWriter is ok, write the error code
w.WriteHeader(500)
_, err2 := w.Write([]byte(err.Error()))
// error during the writing to the ResponseWriter
if err2 != nil {
+ err = multierror.Append(err2, err)
// concat original error with ResponseWriter error
- h.throw(EventError, &ErrorEvent{Request: r, Error: errors.New(fmt.Sprintf("error: %v, during handle this error, ResponseWriter error occurred: %v", err, err2)), start: start, elapsed: time.Since(start)})
+ h.throw(ErrorEvent{Request: r, Error: errors.E(err), start: start, elapsed: time.Since(start)})
return
}
- h.throw(EventError, &ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)})
+ h.throw(ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)})
}
// handleResponse triggers response event.
-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)})
+func (h *handler) handleResponse(req *Request, resp *Response, start time.Time) {
+ h.throw(ResponseEvent{Request: req, Response: resp, start: start, elapsed: time.Since(start)})
}
// throw invokes event handler if any.
-func (h *Handler) throw(event int, ctx interface{}) {
+func (h *handler) throw(ctx interface{}) {
h.mul.Lock()
defer h.mul.Unlock()
if h.lsn != nil {
- h.lsn(event, ctx)
+ h.lsn(ctx)
}
}
// get real ip passing multiple proxy
-func (h *Handler) resolveIP(r *Request) {
- if !h.cfg.IsTrusted(r.RemoteAddr) {
+func (h *handler) resolveIP(r *Request) {
+ if h.trusted.IsTrusted(r.RemoteAddr) == false {
return
}
@@ -187,7 +218,7 @@ func (h *Handler) resolveIP(r *Request) {
}
// The logic here is the following:
- // In general case, we only expect X-Real-Ip header. If it exist, we get the IP addres from header and set request Remote address
+ // In general case, we only expect X-Real-Ip header. If it exist, we get the IP address from header and set request Remote address
// But, if there is no X-Real-Ip header, we also trying to check CloudFlare headers
// True-Client-IP is a general CF header in which copied information from X-Real-Ip in CF.
// CF-Connecting-IP is an Enterprise feature and we check it last in order.
diff --git a/plugins/http/handler_test.go b/plugins/http/handler_test.go
deleted file mode 100644
index d15cf96f..00000000
--- a/plugins/http/handler_test.go
+++ /dev/null
@@ -1,1962 +0,0 @@
-package http
-
-//
-//import (
-// "bytes"
-// "context"
-// "github.com/spiral/roadrunner"
-// "github.com/stretchr/testify/assert"
-// "io/ioutil"
-// "mime/multipart"
-// "net/http"
-// "net/http/httptest"
-// "net/url"
-// "os"
-// "runtime"
-// "strings"
-// "testing"
-// "time"
-//)
-//
-//// get request and return body
-//func get(url string) (string, *http.Response, error) {
-// r, err := http.Get(url)
-// if err != nil {
-// return "", nil, err
-// }
-// b, err := ioutil.ReadAll(r.Body)
-// if err != nil {
-// return "", nil, err
-// }
-//
-// err = r.Body.Close()
-// if err != nil {
-// return "", nil, err
-// }
-// return string(b), r, err
-//}
-//
-//// get request and return body
-//func getHeader(url string, h map[string]string) (string, *http.Response, error) {
-// req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil))
-// if err != nil {
-// return "", nil, err
-// }
-//
-// for k, v := range h {
-// req.Header.Set(k, v)
-// }
-//
-// r, err := http.DefaultClient.Do(req)
-// if err != nil {
-// return "", nil, err
-// }
-//
-// b, err := ioutil.ReadAll(r.Body)
-// if err != nil {
-// return "", nil, err
-// }
-//
-// err = r.Body.Close()
-// if err != nil {
-// return "", nil, err
-// }
-// return string(b), r, err
-//}
-//
-//func TestHandler_Echo(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: 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.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// body, r, err := get("http://localhost:8177/?hello=world")
-// assert.NoError(t, err)
-// assert.Equal(t, 201, r.StatusCode)
-// assert.Equal(t, "WORLD", body)
-//}
-//
-//func Test_HandlerErrors(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php echo pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// wr := httptest.NewRecorder()
-// rq := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("data")))
-//
-// h.ServeHTTP(wr, rq)
-// assert.Equal(t, 500, wr.Code)
-//}
-//
-//func Test_Handler_JSON_error(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php echo pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// wr := httptest.NewRecorder()
-// rq := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("{sd")))
-// rq.Header.Add("Content-Type", "application/json")
-// rq.Header.Add("Content-Size", "3")
-//
-// h.ServeHTTP(wr, rq)
-// assert.Equal(t, 500, wr.Code)
-//}
-//
-//func TestHandler_Headers(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php header pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8078", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 100)
-//
-// req, err := http.NewRequest("GET", "http://localhost:8078?hello=world", nil)
-// assert.NoError(t, err)
-//
-// req.Header.Add("input", "sample")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "world", r.Header.Get("Header"))
-// assert.Equal(t, "SAMPLE", string(b))
-//}
-//
-//func TestHandler_Empty_User_Agent(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php user-agent pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8088", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// req, err := http.NewRequest("GET", "http://localhost:8088?hello=world", nil)
-// assert.NoError(t, err)
-//
-// req.Header.Add("user-agent", "")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "", string(b))
-//}
-//
-//func TestHandler_User_Agent(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php user-agent pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8088", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// req, err := http.NewRequest("GET", "http://localhost:8088?hello=world", nil)
-// assert.NoError(t, err)
-//
-// req.Header.Add("User-Agent", "go-agent")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "go-agent", string(b))
-//}
-//
-//func TestHandler_Cookies(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php cookie pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8079", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// req, err := http.NewRequest("GET", "http://localhost:8079", nil)
-// assert.NoError(t, err)
-//
-// req.AddCookie(&http.Cookie{Name: "input", Value: "input-value"})
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "INPUT-VALUE", string(b))
-//
-// for _, c := range r.Cookies() {
-// assert.Equal(t, "output", c.Name)
-// assert.Equal(t, "cookie-output", c.Value)
-// }
-//}
-//
-//func TestHandler_JsonPayload_POST(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php payload pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8090", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// req, err := http.NewRequest(
-// "POST",
-// "http://localhost"+hs.Addr,
-// bytes.NewBufferString(`{"key":"value"}`),
-// )
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/json")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, `{"value":"key"}`, string(b))
-//}
-//
-//func TestHandler_JsonPayload_PUT(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php payload pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8081", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`))
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/json")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, `{"value":"key"}`, string(b))
-//}
-//
-//func TestHandler_JsonPayload_PATCH(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php payload pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8082", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`))
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/json")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, `{"value":"key"}`, string(b))
-//}
-//
-//func TestHandler_FormData_POST(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8083", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 500)
-//
-// form := url.Values{}
-//
-// form.Add("key", "value")
-// form.Add("name[]", "name1")
-// form.Add("name[]", "name2")
-// form.Add("name[]", "name3")
-// form.Add("arr[x][y][z]", "y")
-// form.Add("arr[x][y][e]", "f")
-// form.Add("arr[c]p", "l")
-// form.Add("arr[c]z", "")
-//
-// req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-//}
-//
-//func TestHandler_FormData_POST_Overwrite(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8083", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// form := url.Values{}
-//
-// form.Add("key", "value1")
-// form.Add("key", "value2")
-//
-// req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"key":"value2","arr":{"x":{"y":null}}}`, string(b))
-//}
-//
-//func TestHandler_FormData_POST_Form_UrlEncoded_Charset(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8083", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// form := url.Values{}
-//
-// form.Add("key", "value")
-// form.Add("name[]", "name1")
-// form.Add("name[]", "name2")
-// form.Add("name[]", "name3")
-// form.Add("arr[x][y][z]", "y")
-// form.Add("arr[x][y][e]", "f")
-// form.Add("arr[c]p", "l")
-// form.Add("arr[c]z", "")
-//
-// req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-//}
-//
-//func TestHandler_FormData_PUT(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8084", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 500)
-//
-// form := url.Values{}
-//
-// form.Add("key", "value")
-// form.Add("name[]", "name1")
-// form.Add("name[]", "name2")
-// form.Add("name[]", "name3")
-// form.Add("arr[x][y][z]", "y")
-// form.Add("arr[x][y][e]", "f")
-// form.Add("arr[c]p", "l")
-// form.Add("arr[c]z", "")
-//
-// req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-//}
-//
-//func TestHandler_FormData_PATCH(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8085", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// form := url.Values{}
-//
-// form.Add("key", "value")
-// form.Add("name[]", "name1")
-// form.Add("name[]", "name2")
-// form.Add("name[]", "name3")
-// form.Add("arr[x][y][z]", "y")
-// form.Add("arr[x][y][e]", "f")
-// form.Add("arr[c]p", "l")
-// form.Add("arr[c]z", "")
-//
-// req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
-// assert.NoError(t, err)
-//
-// req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-//}
-//
-//func TestHandler_Multipart_POST(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8019", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// var mb bytes.Buffer
-// w := multipart.NewWriter(&mb)
-// err := w.WriteField("key", "value")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("key", "value")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name1")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name2")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name3")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[x][y][z]", "y")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[x][y][e]", "f")
-//
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[c]p", "l")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[c]z", "")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.Close()
-// if err != nil {
-// t.Errorf("error closing the writer: error %v", err)
-// }
-//
-// req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb)
-// assert.NoError(t, err)
-//
-// req.Header.Set("Content-Type", w.FormDataContentType())
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-//}
-//
-//func TestHandler_Multipart_PUT(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8020", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 500)
-//
-// var mb bytes.Buffer
-// w := multipart.NewWriter(&mb)
-// err := w.WriteField("key", "value")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("key", "value")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name1")
-//
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name2")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name3")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[x][y][z]", "y")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[x][y][e]", "f")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[c]p", "l")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[c]z", "")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.Close()
-// if err != nil {
-// t.Errorf("error closing the writer: error %v", err)
-// }
-//
-// req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, &mb)
-// assert.NoError(t, err)
-//
-// req.Header.Set("Content-Type", w.FormDataContentType())
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-//}
-//
-//func TestHandler_Multipart_PATCH(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php data pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8021", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-//
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 500)
-//
-// var mb bytes.Buffer
-// w := multipart.NewWriter(&mb)
-// err := w.WriteField("key", "value")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("key", "value")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name1")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name2")
-//
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("name[]", "name3")
-//
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[x][y][z]", "y")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[x][y][e]", "f")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[c]p", "l")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.WriteField("arr[c]z", "")
-// if err != nil {
-// t.Errorf("error writing the field: error %v", err)
-// }
-//
-// err = w.Close()
-// if err != nil {
-// t.Errorf("error closing the writer: error %v", err)
-// }
-//
-// req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, &mb)
-// assert.NoError(t, err)
-//
-// req.Header.Set("Content-Type", w.FormDataContentType())
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// b, err := ioutil.ReadAll(r.Body)
-// assert.NoError(t, err)
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-//
-// assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-//}
-//
-//func TestHandler_Error(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: 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.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// _, r, err := get("http://localhost:8177/?hello=world")
-// assert.NoError(t, err)
-// assert.Equal(t, 500, r.StatusCode)
-//}
-//
-//func TestHandler_Error2(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php error2 pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// _, r, err := get("http://localhost:8177/?hello=world")
-// assert.NoError(t, err)
-// assert.Equal(t, 500, r.StatusCode)
-//}
-//
-//func TestHandler_Error3(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php pid pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// b2 := &bytes.Buffer{}
-// for i := 0; i < 1024*1024; i++ {
-// b2.Write([]byte(" "))
-// }
-//
-// req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, b2)
-// assert.NoError(t, err)
-//
-// r, err := http.DefaultClient.Do(req)
-// assert.NoError(t, err)
-// defer func() {
-// err := r.Body.Close()
-// if err != nil {
-// t.Errorf("error during the closing Body: error %v", err)
-//
-// }
-// }()
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 500, r.StatusCode)
-//}
-//
-//func TestHandler_ResponseDuration(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: 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.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// 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{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: 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.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// 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{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: 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.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// 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 TestHandler_IP(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// TrustedSubnets: []string{
-// "10.0.0.0/8",
-// "127.0.0.0/8",
-// "172.16.0.0/12",
-// "192.168.0.0/16",
-// "::1/128",
-// "fc00::/7",
-// "fe80::/10",
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php ip pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// err := h.cfg.parseCIDRs()
-// if err != nil {
-// t.Errorf("error parsing CIDRs: error %v", err)
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// body, r, err := get("http://127.0.0.1:8177/")
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "127.0.0.1", body)
-//}
-//
-//func TestHandler_XRealIP(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// TrustedSubnets: []string{
-// "10.0.0.0/8",
-// "127.0.0.0/8",
-// "172.16.0.0/12",
-// "192.168.0.0/16",
-// "::1/128",
-// "fc00::/7",
-// "fe80::/10",
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php ip pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// err := h.cfg.parseCIDRs()
-// if err != nil {
-// t.Errorf("error parsing CIDRs: error %v", err)
-// }
-//
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
-// "X-Real-Ip": "200.0.0.1",
-// })
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "200.0.0.1", body)
-//}
-//
-//func TestHandler_XForwardedFor(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// TrustedSubnets: []string{
-// "10.0.0.0/8",
-// "127.0.0.0/8",
-// "172.16.0.0/12",
-// "192.168.0.0/16",
-// "100.0.0.0/16",
-// "200.0.0.0/16",
-// "::1/128",
-// "fc00::/7",
-// "fe80::/10",
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php ip pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// err := h.cfg.parseCIDRs()
-// if err != nil {
-// t.Errorf("error parsing CIDRs: error %v", err)
-// }
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
-// "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1",
-// })
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "101.0.0.1", body)
-//
-// body, r, err = getHeader("http://127.0.0.1:8177/", map[string]string{
-// "X-Forwarded-For": "100.0.0.1, 200.0.0.1, 101.0.0.1, invalid",
-// })
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "101.0.0.1", body)
-//}
-//
-//func TestHandler_XForwardedFor_NotTrustedRemoteIp(t *testing.T) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// TrustedSubnets: []string{
-// "10.0.0.0/8",
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php ip pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: 1,
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// err := h.cfg.parseCIDRs()
-// if err != nil {
-// t.Errorf("error parsing CIDRs: error %v", err)
-// }
-// assert.NoError(t, h.pool.Start())
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// t.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// t.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
-// "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1",
-// })
-//
-// assert.NoError(t, err)
-// assert.Equal(t, 200, r.StatusCode)
-// assert.Equal(t, "127.0.0.1", body)
-//}
-//
-//func BenchmarkHandler_Listen_Echo(b *testing.B) {
-// h := &Handler{
-// cfg: &Config{
-// MaxRequestSize: 1024,
-// Uploads: &UploadsConfig{
-// Dir: os.TempDir(),
-// Forbid: []string{},
-// },
-// },
-// pool: roadrunner.NewServer(&roadrunner.ServerConfig{
-// Command: "php ../../tests/http/client.php echo pipes",
-// Relay: "pipes",
-// Pool: &roadrunner.Config{
-// NumWorkers: int64(runtime.NumCPU()),
-// AllocateTimeout: 10000000,
-// DestroyTimeout: 10000000,
-// },
-// }),
-// }
-//
-// err := h.pool.Start()
-// if err != nil {
-// b.Errorf("error starting the worker pool: error %v", err)
-// }
-// defer h.pool.Stop()
-//
-// hs := &http.Server{Addr: ":8177", Handler: h}
-// defer func() {
-// err := hs.Shutdown(context.Background())
-// if err != nil {
-// b.Errorf("error during the shutdown: error %v", err)
-// }
-// }()
-//
-// go func() {
-// err := hs.ListenAndServe()
-// if err != nil && err != http.ErrServerClosed {
-// b.Errorf("error listening the interface: error %v", err)
-// }
-// }()
-// time.Sleep(time.Millisecond * 10)
-//
-// bb := "WORLD"
-// for n := 0; n < b.N; n++ {
-// r, err := http.Get("http://localhost:8177/?hello=world")
-// if err != nil {
-// b.Fail()
-// }
-// // Response might be nil here
-// if r != nil {
-// br, err := ioutil.ReadAll(r.Body)
-// if err != nil {
-// b.Errorf("error reading Body: error %v", err)
-// }
-// if string(br) != bb {
-// b.Fail()
-// }
-// err = r.Body.Close()
-// if err != nil {
-// b.Errorf("error closing the Body: error %v", err)
-// }
-// } else {
-// b.Errorf("got nil response")
-// }
-// }
-//}
diff --git a/plugins/http/parse.go b/plugins/http/parse.go
index 9b58d328..c1038725 100644
--- a/plugins/http/parse.go
+++ b/plugins/http/parse.go
@@ -60,7 +60,7 @@ func (d dataTree) mount(i []string, v []string) {
}
// parse incoming dataTree request into JSON (including contentMultipart form dataTree)
-func parseUploads(r *http.Request, cfg *UploadsConfig) *Uploads {
+func parseUploads(r *http.Request, cfg UploadsConfig) *Uploads {
u := &Uploads{
cfg: cfg,
tree: make(fileTree),
diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go
index e5ccabc4..fc08a01f 100644
--- a/plugins/http/plugin.go
+++ b/plugins/http/plugin.go
@@ -47,13 +47,13 @@ type Plugin struct {
configurer config.Configurer
log log.Logger
- mdwr []middleware
- listeners []util.EventListener
+ mdwr []middleware
+ listener util.EventListener
pool roadrunner.Pool
server factory.Server
- //controller roadrunner.Controller
- handler *Handler
+
+ handler Handler
http *http.Server
https *http.Server
@@ -68,7 +68,7 @@ func (s *Plugin) AddMiddleware(m middleware) {
// AddListener attaches server event controller.
func (s *Plugin) AddListener(listener util.EventListener) {
// save listeners for Reset
- s.listeners = append(s.listeners, listener)
+ s.listener = listener
s.pool.AddListener(listener)
}
@@ -82,7 +82,6 @@ func (s *Plugin) Init(cfg config.Configurer, log log.Logger, server factory.Serv
}
s.configurer = cfg
- s.listeners = make([]util.EventListener, 0, 1)
s.log = log
// Set needed env vars
@@ -141,8 +140,21 @@ func (s *Plugin) Serve() chan error {
// s.pool.Attach(s.controller)
//}
- s.handler = &Handler{cfg: s.cfg, rr: s.pool}
- //s.handler.Listen(s.throw)
+ var err error
+ s.handler, err = NewHandler(
+ s.cfg.MaxRequestSize,
+ *s.cfg.Uploads,
+ s.cfg.cidrs,
+ s.pool,
+ )
+ if err != nil {
+ errCh <- errors.E(op, err)
+ return errCh
+ }
+
+ if s.listener != nil {
+ s.handler.AddListener(s.listener)
+ }
if s.cfg.EnableHTTP() {
if s.cfg.EnableH2C() {
@@ -155,7 +167,7 @@ func (s *Plugin) Serve() chan error {
if s.cfg.EnableTLS() {
s.https = s.initSSL()
if s.cfg.SSL.RootCA != "" {
- err := s.appendRootCa()
+ err = s.appendRootCa()
if err != nil {
errCh <- errors.E(op, err)
return errCh
@@ -464,8 +476,7 @@ func (s *Plugin) Reset() error {
}
// restore original listeners
- for i := 0; i < len(s.listeners); i++ {
- s.pool.AddListener(s.listeners[i])
- }
+ s.pool.AddListener(s.listener)
+
return nil
}
diff --git a/plugins/http/request.go b/plugins/http/request.go
index 7e9839b2..69478d2b 100644
--- a/plugins/http/request.go
+++ b/plugins/http/request.go
@@ -11,6 +11,7 @@ import (
json "github.com/json-iterator/go"
"github.com/spiral/roadrunner/v2"
"github.com/spiral/roadrunner/v2/interfaces/log"
+ "github.com/spiral/roadrunner/v2/plugins/http/attributes"
)
const (
@@ -67,7 +68,7 @@ func fetchIP(pair string) string {
}
// NewRequest creates new PSR7 compatible request using net/http request.
-func NewRequest(r *http.Request, cfg *UploadsConfig) (*Request, error) {
+func NewRequest(r *http.Request, cfg UploadsConfig) (*Request, error) {
req := &Request{
RemoteAddr: fetchIP(r.RemoteAddr),
Protocol: r.Proto,
@@ -76,7 +77,7 @@ func NewRequest(r *http.Request, cfg *UploadsConfig) (*Request, error) {
Header: r.Header,
Cookies: make(map[string]string),
RawQuery: r.URL.RawQuery,
- //Attributes: attributes.All(r),
+ Attributes: attributes.All(r),
}
for _, c := range r.Cookies() {
diff --git a/plugins/http/response.go b/plugins/http/response.go
index 9ebc0632..0964c7e5 100644
--- a/plugins/http/response.go
+++ b/plugins/http/response.go
@@ -24,8 +24,7 @@ type Response struct {
// NewResponse creates new response based on given pool payload.
func NewResponse(p roadrunner.Payload) (*Response, error) {
r := &Response{body: p.Body}
- j := json.ConfigCompatibleWithStandardLibrary
- if err := j.Unmarshal(p.Context, r); err != nil {
+ if err := json.Unmarshal(p.Context, r); err != nil {
return nil, err
}
diff --git a/plugins/http/rpc.go b/plugins/http/rpc.go
deleted file mode 100644
index 56d8f1a1..00000000
--- a/plugins/http/rpc.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package http
-
-//import (
-// "github.com/pkg/errors"
-// "github.com/spiral/roadrunner/util"
-//)
-
-//type rpcServer struct{ svc *Plugin }
-//
-//// WorkerList contains list of workers.
-//type WorkerList struct {
-// // Workers is list of workers.
-// Workers []*util.State `json:"workers"`
-//}
-//
-//// Reset resets underlying RR worker pool and restarts all of it's workers.
-//func (rpc *rpcServer) Reset(reset bool, r *string) error {
-// if rpc.svc == nil || rpc.svc.handler == nil {
-// return errors.New("http server is not running")
-// }
-//
-// *r = "OK"
-// return rpc.svc.Server().Reset()
-//}
-//
-//// Workers returns list of active workers and their stats.
-//func (rpc *rpcServer) Workers(list bool, r *WorkerList) (err error) {
-// if rpc.svc == nil || rpc.svc.handler == nil {
-// return errors.New("http server is not running")
-// }
-//
-// r.Workers, err = util.ServerState(rpc.svc.Server())
-// return err
-//}
diff --git a/plugins/http/test/http_test.go b/plugins/http/test/http_test.go
deleted file mode 100644
index 07925d33..00000000
--- a/plugins/http/test/http_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package test
-
-import (
- "os"
- "os/signal"
- "syscall"
- "testing"
- "time"
-
- "github.com/spiral/endure"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/http"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- //rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc"
- "github.com/spiral/roadrunner/v2/plugins/server"
- "github.com/stretchr/testify/assert"
-)
-
-func TestHTTPInit(t *testing.T) {
- cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel), endure.Visualize(endure.StdOut, ""))
- assert.NoError(t, err)
-
- cfg := &config.Viper{
- Path: ".rr-http.yaml",
- Prefix: "rr",
- }
-
- err = cont.RegisterAll(
- cfg,
- //&rpcPlugin.Plugin{},
- &logger.ZapLogger{},
- &server.Plugin{},
- &http.Plugin{},
- )
- assert.NoError(t, err)
-
- err = cont.Init()
- if err != nil {
- t.Fatal(err)
- }
-
- ch, err := cont.Serve()
- assert.NoError(t, err)
-
- sig := make(chan os.Signal, 1)
- signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
-
- tt := time.NewTimer(time.Minute * 3)
- for {
- select {
- case e := <-ch:
- assert.Fail(t, "error", e.Error.Error())
- err = cont.Stop()
- if err != nil {
- assert.FailNow(t, "error", err.Error())
- }
- case <-sig:
- err = cont.Stop()
- if err != nil {
- assert.FailNow(t, "error", err.Error())
- }
- return
- case <-tt.C:
- // timeout
- err = cont.Stop()
- if err != nil {
- assert.FailNow(t, "error", err.Error())
- }
- return
- }
- }
-}
diff --git a/src/Logger/.empty b/plugins/http/tests/configs/.rr-handler-echo.yaml
index e69de29b..e69de29b 100644
--- a/src/Logger/.empty
+++ b/plugins/http/tests/configs/.rr-handler-echo.yaml
diff --git a/plugins/http/test/.rr-http.yaml b/plugins/http/tests/configs/.rr-http.yaml
index 6fbfd378..8c6f86d6 100644
--- a/plugins/http/test/.rr-http.yaml
+++ b/plugins/http/tests/configs/.rr-http.yaml
@@ -21,11 +21,11 @@ http:
allocateTimeout: 60s
destroyTimeout: 60s
- # ssl:
- # port: 443
- # redirect: true
- # cert: server.crt
- # key: server.key
+ ssl:
+ port: 8888
+ redirect: true
+ cert: fixtures/server.crt
+ key: fixtures/server.key
# rootCa: root.crt
fcgi:
address: tcp://0.0.0.0:6920
diff --git a/plugins/http/fixtures/server.crt b/plugins/http/tests/fixtures/server.crt
index 24d67fd7..24d67fd7 100644
--- a/plugins/http/fixtures/server.crt
+++ b/plugins/http/tests/fixtures/server.crt
diff --git a/plugins/http/fixtures/server.key b/plugins/http/tests/fixtures/server.key
index 7501dd46..7501dd46 100644
--- a/plugins/http/fixtures/server.key
+++ b/plugins/http/tests/fixtures/server.key
diff --git a/plugins/http/tests/handler_test.go b/plugins/http/tests/handler_test.go
new file mode 100644
index 00000000..81a8449e
--- /dev/null
+++ b/plugins/http/tests/handler_test.go
@@ -0,0 +1,1839 @@
+package tests
+
+import (
+ "bytes"
+ "context"
+ "io/ioutil"
+ "mime/multipart"
+ "net/url"
+ "os/exec"
+ "runtime"
+ "strings"
+
+ "github.com/spiral/roadrunner/v2"
+ httpPlugin "github.com/spiral/roadrunner/v2/plugins/http"
+ "github.com/stretchr/testify/assert"
+
+ "net/http"
+ "os"
+ "testing"
+ "time"
+)
+
+func TestHandler_Echo(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "echo", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err = hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err = hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ body, r, err := get("http://localhost:8177/?hello=world")
+ assert.NoError(t, err)
+ assert.Equal(t, 201, r.StatusCode)
+ assert.Equal(t, "WORLD", body)
+}
+
+func Test_HandlerErrors(t *testing.T) {
+ _, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, nil)
+ assert.Error(t, err)
+}
+
+func TestHandler_Headers(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "header", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8078", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 100)
+
+ req, err := http.NewRequest("GET", "http://localhost:8078?hello=world", nil)
+ assert.NoError(t, err)
+
+ req.Header.Add("input", "sample")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "world", r.Header.Get("Header"))
+ assert.Equal(t, "SAMPLE", string(b))
+}
+
+func TestHandler_Empty_User_Agent(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "user-agent", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8088", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ req, err := http.NewRequest("GET", "http://localhost:8088?hello=world", nil)
+ assert.NoError(t, err)
+
+ req.Header.Add("user-agent", "")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "", string(b))
+}
+
+func TestHandler_User_Agent(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "user-agent", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8088", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ req, err := http.NewRequest("GET", "http://localhost:8088?hello=world", nil)
+ assert.NoError(t, err)
+
+ req.Header.Add("User-Agent", "go-agent")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "go-agent", string(b))
+}
+
+func TestHandler_Cookies(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "cookie", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8079", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ req, err := http.NewRequest("GET", "http://localhost:8079", nil)
+ assert.NoError(t, err)
+
+ req.AddCookie(&http.Cookie{Name: "input", Value: "input-value"})
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "INPUT-VALUE", string(b))
+
+ for _, c := range r.Cookies() {
+ assert.Equal(t, "output", c.Name)
+ assert.Equal(t, "cookie-output", c.Value)
+ }
+}
+
+func TestHandler_JsonPayload_POST(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "payload", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8090", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ req, err := http.NewRequest(
+ "POST",
+ "http://localhost"+hs.Addr,
+ bytes.NewBufferString(`{"key":"value"}`),
+ )
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/json")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, `{"value":"key"}`, string(b))
+}
+
+func TestHandler_JsonPayload_PUT(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "payload", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8081", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`))
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/json")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, `{"value":"key"}`, string(b))
+}
+
+func TestHandler_JsonPayload_PATCH(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "payload", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8082", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`))
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/json")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, `{"value":"key"}`, string(b))
+}
+
+func TestHandler_FormData_POST(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8083", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 500)
+
+ form := url.Values{}
+
+ form.Add("key", "value")
+ form.Add("name[]", "name1")
+ form.Add("name[]", "name2")
+ form.Add("name[]", "name3")
+ form.Add("arr[x][y][z]", "y")
+ form.Add("arr[x][y][e]", "f")
+ form.Add("arr[c]p", "l")
+ form.Add("arr[c]z", "")
+
+ req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ // Sorted
+ assert.Equal(t, "{\"arr\":{\"c\":{\"z\":\"\",\"p\":\"l\"},\"x\":{\"y\":{\"e\":\"f\",\"z\":\"y\"}}},\"key\":\"value\",\"name\":[\"name1\",\"name2\",\"name3\"]}", string(b))
+}
+
+func TestHandler_FormData_POST_Overwrite(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8083", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ form := url.Values{}
+
+ form.Add("key", "value1")
+ form.Add("key", "value2")
+
+ req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ assert.Equal(t, `{"key":"value2","arr":{"x":{"y":null}}}`, string(b))
+}
+
+func TestHandler_FormData_POST_Form_UrlEncoded_Charset(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8083", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ form := url.Values{}
+
+ form.Add("key", "value")
+ form.Add("name[]", "name1")
+ form.Add("name[]", "name2")
+ form.Add("name[]", "name3")
+ form.Add("arr[x][y][z]", "y")
+ form.Add("arr[x][y][e]", "f")
+ form.Add("arr[c]p", "l")
+ form.Add("arr[c]z", "")
+
+ req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
+}
+
+func TestHandler_FormData_PUT(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8084", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 500)
+
+ form := url.Values{}
+
+ form.Add("key", "value")
+ form.Add("name[]", "name1")
+ form.Add("name[]", "name2")
+ form.Add("name[]", "name3")
+ form.Add("arr[x][y][z]", "y")
+ form.Add("arr[x][y][e]", "f")
+ form.Add("arr[c]p", "l")
+ form.Add("arr[c]z", "")
+
+ req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
+}
+
+func TestHandler_FormData_PATCH(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8085", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ form := url.Values{}
+
+ form.Add("key", "value")
+ form.Add("name[]", "name1")
+ form.Add("name[]", "name2")
+ form.Add("name[]", "name3")
+ form.Add("arr[x][y][z]", "y")
+ form.Add("arr[x][y][e]", "f")
+ form.Add("arr[c]p", "l")
+ form.Add("arr[c]z", "")
+
+ req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
+ assert.NoError(t, err)
+
+ req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ assert.Equal(t, "{\"arr\":{\"c\":{\"p\":\"l\",\"z\":\"\"},\"x\":{\"y\":{\"e\":\"f\",\"z\":\"y\"}}},\"key\":\"value\",\"name\":[\"name1\",\"name2\",\"name3\"]}", string(b))
+}
+
+func TestHandler_Multipart_POST(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8019", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ var mb bytes.Buffer
+ w := multipart.NewWriter(&mb)
+ err = w.WriteField("key", "value")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("key", "value")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name1")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name2")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name3")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[x][y][z]", "y")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[x][y][e]", "f")
+
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[c]p", "l")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[c]z", "")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.Close()
+ if err != nil {
+ t.Errorf("error closing the writer: error %v", err)
+ }
+
+ req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb)
+ assert.NoError(t, err)
+
+ req.Header.Set("Content-Type", w.FormDataContentType())
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ assert.Equal(t, "{\"arr\":{\"c\":{\"z\":\"\",\"p\":\"l\"},\"x\":{\"y\":{\"e\":\"f\",\"z\":\"y\"}}},\"key\":\"value\",\"name\":[\"name1\",\"name2\",\"name3\"]}", string(b))
+}
+
+func TestHandler_Multipart_PUT(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8020", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 500)
+
+ var mb bytes.Buffer
+ w := multipart.NewWriter(&mb)
+ err = w.WriteField("key", "value")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("key", "value")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name1")
+
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name2")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name3")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[x][y][z]", "y")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[x][y][e]", "f")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[c]p", "l")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[c]z", "")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.Close()
+ if err != nil {
+ t.Errorf("error closing the writer: error %v", err)
+ }
+
+ req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, &mb)
+ assert.NoError(t, err)
+
+ req.Header.Set("Content-Type", w.FormDataContentType())
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
+}
+
+func TestHandler_Multipart_PATCH(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "data", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8021", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 500)
+
+ var mb bytes.Buffer
+ w := multipart.NewWriter(&mb)
+ err = w.WriteField("key", "value")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("key", "value")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name1")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name2")
+
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("name[]", "name3")
+
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[x][y][z]", "y")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[x][y][e]", "f")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[c]p", "l")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.WriteField("arr[c]z", "")
+ if err != nil {
+ t.Errorf("error writing the field: error %v", err)
+ }
+
+ err = w.Close()
+ if err != nil {
+ t.Errorf("error closing the writer: error %v", err)
+ }
+
+ req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, &mb)
+ assert.NoError(t, err)
+
+ req.Header.Set("Content-Type", w.FormDataContentType())
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err := r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ b, err := ioutil.ReadAll(r.Body)
+ assert.NoError(t, err)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+
+ assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
+}
+
+func TestHandler_Error(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "error", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ _, r, err := get("http://localhost:8177/?hello=world")
+ assert.NoError(t, err)
+ assert.Equal(t, 500, r.StatusCode)
+}
+
+func TestHandler_Error2(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "error2", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ _, r, err := get("http://localhost:8177/?hello=world")
+ assert.NoError(t, err)
+ assert.Equal(t, 500, r.StatusCode)
+}
+
+func TestHandler_Error3(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "pid", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err = hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ b2 := &bytes.Buffer{}
+ for i := 0; i < 1024*1024; i++ {
+ b2.Write([]byte(" "))
+ }
+
+ req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, b2)
+ assert.NoError(t, err)
+
+ r, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ defer func() {
+ err = r.Body.Close()
+ if err != nil {
+ t.Errorf("error during the closing Body: error %v", err)
+
+ }
+ }()
+
+ assert.NoError(t, err)
+ assert.Equal(t, 500, r.StatusCode)
+}
+
+func TestHandler_ResponseDuration(t *testing.T) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "echo", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ gotresp := make(chan interface{})
+ h.AddListener(func(event interface{}) {
+ switch t := event.(type) {
+ case httpPlugin.ResponseEvent:
+ if t.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) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "echoDelay", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err = hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err = hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ gotresp := make(chan interface{})
+ h.AddListener(func(event interface{}) {
+ switch tp := event.(type) {
+ case httpPlugin.ResponseEvent:
+ if tp.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) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "error", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err = hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ goterr := make(chan interface{})
+ h.AddListener(func(event interface{}) {
+ switch tp := event.(type) {
+ case httpPlugin.ErrorEvent:
+ if tp.Elapsed() > 0 {
+ close(goterr)
+ }
+ }
+ })
+
+ _, r, err := get("http://localhost:8177/?hello=world")
+ assert.NoError(t, err)
+
+ <-goterr
+
+ assert.Equal(t, 500, r.StatusCode)
+}
+
+func TestHandler_IP(t *testing.T) {
+ trusted := []string{
+ "10.0.0.0/8",
+ "127.0.0.0/8",
+ "172.16.0.0/12",
+ "192.168.0.0/16",
+ "::1/128",
+ "fc00::/7",
+ "fe80::/10",
+ }
+
+ cidrs, err := httpPlugin.ParseCIDRs(trusted)
+ assert.NoError(t, err)
+ assert.NotNil(t, cidrs)
+
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "ip", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, cidrs, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ body, r, err := get("http://127.0.0.1:8177/")
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "127.0.0.1", body)
+}
+
+func TestHandler_XRealIP(t *testing.T) {
+ trusted := []string{
+ "10.0.0.0/8",
+ "127.0.0.0/8",
+ "172.16.0.0/12",
+ "192.168.0.0/16",
+ "::1/128",
+ "fc00::/7",
+ "fe80::/10",
+ }
+
+ cidrs, err := httpPlugin.ParseCIDRs(trusted)
+ assert.NoError(t, err)
+ assert.NotNil(t, cidrs)
+
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "ip", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, cidrs, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: "127.0.0.1:8179", Handler: h}
+ defer func() {
+ err = hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err = hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ body, r, err := getHeader("http://127.0.0.1:8179/", map[string]string{
+ "X-Real-Ip": "200.0.0.1",
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "200.0.0.1", body)
+}
+
+func TestHandler_XForwardedFor(t *testing.T) {
+ trusted := []string{
+ "10.0.0.0/8",
+ "127.0.0.0/8",
+ "172.16.0.0/12",
+ "192.168.0.0/16",
+ "100.0.0.0/16",
+ "200.0.0.0/16",
+ "::1/128",
+ "fc00::/7",
+ "fe80::/10",
+ }
+
+ cidrs, err := httpPlugin.ParseCIDRs(trusted)
+ assert.NoError(t, err)
+ assert.NotNil(t, cidrs)
+
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "ip", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, cidrs, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
+ "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1",
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "101.0.0.1", body)
+
+ body, r, err = getHeader("http://127.0.0.1:8177/", map[string]string{
+ "X-Forwarded-For": "100.0.0.1, 200.0.0.1, 101.0.0.1, invalid",
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "101.0.0.1", body)
+}
+
+func TestHandler_XForwardedFor_NotTrustedRemoteIp(t *testing.T) {
+ trusted := []string{
+ "10.0.0.0/8",
+ }
+
+ cidrs, err := httpPlugin.ParseCIDRs(trusted)
+ assert.NoError(t, err)
+ assert.NotNil(t, cidrs)
+
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "ip", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, cidrs, pool)
+ assert.NoError(t, err)
+
+ hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ t.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err := hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ t.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
+ "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1",
+ })
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, r.StatusCode)
+ assert.Equal(t, "127.0.0.1", body)
+}
+
+func BenchmarkHandler_Listen_Echo(b *testing.B) {
+ pool, err := roadrunner.NewPool(context.Background(),
+ func() *exec.Cmd { return exec.Command("php", "../../../tests/http/client.php", "echo", "pipes") },
+ roadrunner.NewPipeFactory(),
+ roadrunner.PoolConfig{
+ NumWorkers: int64(runtime.NumCPU()),
+ AllocateTimeout: time.Second * 1000,
+ DestroyTimeout: time.Second * 1000,
+ })
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer func() {
+ pool.Destroy(context.Background())
+ }()
+
+ h, err := httpPlugin.NewHandler(1024, httpPlugin.UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{},
+ }, nil, pool)
+ assert.NoError(b, err)
+
+ hs := &http.Server{Addr: ":8177", Handler: h}
+ defer func() {
+ err := hs.Shutdown(context.Background())
+ if err != nil {
+ b.Errorf("error during the shutdown: error %v", err)
+ }
+ }()
+
+ go func() {
+ err = hs.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ b.Errorf("error listening the interface: error %v", err)
+ }
+ }()
+ time.Sleep(time.Millisecond * 10)
+
+ b.ResetTimer()
+ b.ReportAllocs()
+ bb := "WORLD"
+ for n := 0; n < b.N; n++ {
+ r, err := http.Get("http://localhost:8177/?hello=world")
+ if err != nil {
+ b.Fail()
+ }
+ // Response might be nil here
+ if r != nil {
+ br, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ b.Errorf("error reading Body: error %v", err)
+ }
+ if string(br) != bb {
+ b.Fail()
+ }
+ err = r.Body.Close()
+ if err != nil {
+ b.Errorf("error closing the Body: error %v", err)
+ }
+ } else {
+ b.Errorf("got nil response")
+ }
+ }
+}
diff --git a/plugins/http/tests/http_test.go b/plugins/http/tests/http_test.go
new file mode 100644
index 00000000..2e380a5e
--- /dev/null
+++ b/plugins/http/tests/http_test.go
@@ -0,0 +1,178 @@
+package tests
+
+import (
+ "bytes"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "os/signal"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/spiral/endure"
+ "github.com/spiral/roadrunner/v2/plugins/config"
+ httpPlugin "github.com/spiral/roadrunner/v2/plugins/http"
+ "github.com/spiral/roadrunner/v2/plugins/logger"
+
+ rpcPlugin "github.com/spiral/roadrunner/v2/plugins/rpc"
+ "github.com/spiral/roadrunner/v2/plugins/server"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestHTTPInit(t *testing.T) {
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel), endure.Visualize(endure.StdOut, ""))
+ assert.NoError(t, err)
+
+ cfg := &config.Viper{
+ Path: ".rr-http.yaml",
+ Prefix: "rr",
+ }
+
+ err = cont.RegisterAll(
+ cfg,
+ &rpcPlugin.Plugin{},
+ &logger.ZapLogger{},
+ &server.Plugin{},
+ &httpPlugin.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ assert.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ tt := time.NewTimer(time.Second * 5)
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ case <-sig:
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ case <-tt.C:
+ // timeout
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ }
+ }
+}
+
+func TestHTTPHandler(t *testing.T) {
+ cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel), endure.Visualize(endure.StdOut, ""))
+ assert.NoError(t, err)
+
+ cfg := &config.Viper{
+ Path: "configs/.rr-handler-echo.yaml",
+ Prefix: "rr",
+ }
+
+ err = cont.RegisterAll(
+ cfg,
+ &rpcPlugin.Plugin{},
+ &logger.ZapLogger{},
+ &server.Plugin{},
+ &httpPlugin.Plugin{},
+ )
+ assert.NoError(t, err)
+
+ err = cont.Init()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ch, err := cont.Serve()
+ assert.NoError(t, err)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+
+ go func() {
+ tt := time.NewTimer(time.Minute * 5)
+ for {
+ select {
+ case e := <-ch:
+ assert.Fail(t, "error", e.Error.Error())
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ case <-sig:
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ case <-tt.C:
+ // timeout
+ err = cont.Stop()
+ if err != nil {
+ assert.FailNow(t, "error", err.Error())
+ }
+ return
+ }
+ }
+ }()
+}
+
+func get(url string) (string, *http.Response, error) {
+ r, err := http.Get(url)
+ if err != nil {
+ return "", nil, err
+ }
+ b, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return "", nil, err
+ }
+
+ err = r.Body.Close()
+ if err != nil {
+ return "", nil, err
+ }
+ return string(b), r, err
+}
+
+// get request and return body
+func getHeader(url string, h map[string]string) (string, *http.Response, error) {
+ req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil))
+ if err != nil {
+ return "", nil, err
+ }
+
+ for k, v := range h {
+ req.Header.Set(k, v)
+ }
+
+ r, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return "", nil, err
+ }
+
+ b, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ return "", nil, err
+ }
+
+ err = r.Body.Close()
+ if err != nil {
+ return "", nil, err
+ }
+ return string(b), r, err
+}
diff --git a/plugins/http/tests/plugin1.go b/plugins/http/tests/plugin1.go
new file mode 100644
index 00000000..3613ec35
--- /dev/null
+++ b/plugins/http/tests/plugin1.go
@@ -0,0 +1,26 @@
+package tests
+
+import "github.com/spiral/roadrunner/v2/plugins/config"
+
+type Plugin1 struct {
+ config config.Configurer
+}
+
+func (p1 *Plugin1) Init(cfg config.Configurer) error {
+ p1.config = cfg
+ return nil
+}
+
+func (p1 *Plugin1) Serve() chan error {
+ errCh := make(chan error, 1)
+ return errCh
+}
+
+func (p1 *Plugin1) Stop() error {
+ return nil
+}
+
+func (p1 *Plugin1) Name() string {
+ return "http_test.plugin1"
+}
+
diff --git a/plugins/http/test/psr-worker.php b/plugins/http/tests/psr-worker.php
index 65fc6bde..65fc6bde 100644
--- a/plugins/http/test/psr-worker.php
+++ b/plugins/http/tests/psr-worker.php
diff --git a/plugins/http/tests/yaml_configs.go b/plugins/http/tests/yaml_configs.go
new file mode 100644
index 00000000..9d40edac
--- /dev/null
+++ b/plugins/http/tests/yaml_configs.go
@@ -0,0 +1,39 @@
+package tests
+
+var t1 string = `
+server:
+ command: "php psr-worker.php"
+ user: ""
+ group: ""
+ env:
+ "RR_HTTP": "true"
+ relay: "pipes"
+ relayTimeout: "20s"
+
+http:
+ debug: true
+ address: 0.0.0.0:8080
+ maxRequestSize: 200
+ middleware: [ "" ]
+ uploads:
+ forbid: [ ".php", ".exe", ".bat" ]
+ trustedSubnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ]
+ pool:
+ numWorkers: 4
+ maxJobs: 0
+ allocateTimeout: 60s
+ destroyTimeout: 60s
+
+ ssl:
+ port: 8888
+ redirect: true
+ cert: fixtures/server.crt
+ key: fixtures/server.key
+ # rootCa: root.crt
+ fcgi:
+ address: tcp://0.0.0.0:6920
+ http2:
+ enabled: false
+ h2c: false
+ maxConcurrentStreams: 128
+`
diff --git a/plugins/http/uploads.go b/plugins/http/uploads.go
index 2a1524ef..c936262a 100644
--- a/plugins/http/uploads.go
+++ b/plugins/http/uploads.go
@@ -31,7 +31,7 @@ const (
// Uploads tree manages uploaded files tree and temporary files.
type Uploads struct {
// associated temp directory and forbidden extensions.
- cfg *UploadsConfig
+ cfg UploadsConfig
// pre processed data tree for Uploads.
tree fileTree
@@ -114,7 +114,7 @@ func NewUpload(f *multipart.FileHeader) *FileUpload {
// STACK
// DEFER FILE CLOSE (2)
// DEFER TMP CLOSE (1)
-func (f *FileUpload) Open(cfg *UploadsConfig) (err error) {
+func (f *FileUpload) Open(cfg UploadsConfig) (err error) {
if cfg.Forbids(f.Name) {
f.Error = UploadErrorExtension
return nil
diff --git a/plugins/logger/plugin.go b/plugins/logger/plugin.go
index 0a8485d9..2937056c 100644
--- a/plugins/logger/plugin.go
+++ b/plugins/logger/plugin.go
@@ -35,7 +35,7 @@ func (z *ZapLogger) Init(cfg config.Configurer) error {
// DefaultLogger returns default logger.
func (z *ZapLogger) DefaultLogger() (log.Logger, error) {
- return log.NewZapAdapter(z.base), nil
+ return NewZapAdapter(z.base), nil
}
// NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params.
@@ -45,10 +45,10 @@ func (z *ZapLogger) NamedLogger(name string) (log.Logger, error) {
if err != nil {
return nil, err
}
- return log.NewZapAdapter(l), nil
+ return NewZapAdapter(l), nil
}
- return log.NewZapAdapter(z.base.Named(name)), nil
+ return NewZapAdapter(z.base.Named(name)), nil
}
// NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params.
diff --git a/interfaces/log/zap_adapter.go b/plugins/logger/zap_adapter.go
index 65f8d04b..41e6d8f9 100644
--- a/interfaces/log/zap_adapter.go
+++ b/plugins/logger/zap_adapter.go
@@ -1,8 +1,9 @@
-package log
+package logger
import (
"fmt"
+ "github.com/spiral/roadrunner/v2/interfaces/log"
"go.uber.org/zap"
)
@@ -51,6 +52,6 @@ func (log *ZapAdapter) Error(msg string, keyvals ...interface{}) {
log.zl.Error(msg, log.fields(keyvals)...)
}
-func (log *ZapAdapter) With(keyvals ...interface{}) Logger {
+func (log *ZapAdapter) With(keyvals ...interface{}) log.Logger {
return NewZapAdapter(log.zl.With(log.fields(keyvals)...))
}
diff --git a/protocol.go b/protocol.go
index bdf78296..5e8be37a 100755
--- a/protocol.go
+++ b/protocol.go
@@ -1,15 +1,13 @@
package roadrunner
import (
- "fmt"
"os"
json "github.com/json-iterator/go"
+ "github.com/spiral/errors"
"github.com/spiral/goridge/v2"
)
-var j = json.ConfigCompatibleWithStandardLibrary
-
type stopCommand struct {
Stop bool `json:"stop"`
}
@@ -19,35 +17,42 @@ type pidCommand struct {
}
func sendControl(rl goridge.Relay, v interface{}) error {
+ const op = errors.Op("send control")
if data, ok := v.([]byte); ok {
- return rl.Send(data, goridge.PayloadControl|goridge.PayloadRaw)
+ err := rl.Send(data, goridge.PayloadControl|goridge.PayloadRaw)
+ if err != nil {
+ return errors.E(op, err)
+ }
+ return nil
}
- data, err := j.Marshal(v)
+ data, err := json.Marshal(v)
if err != nil {
- return fmt.Errorf("invalid payload: %s", err)
+ return errors.E(op, errors.Errorf("invalid payload: %s", err))
}
return rl.Send(data, goridge.PayloadControl)
}
func fetchPID(rl goridge.Relay) (int64, error) {
+ const op = errors.Op("fetchPID")
err := sendControl(rl, pidCommand{Pid: os.Getpid()})
if err != nil {
- return 0, err
+ return 0, errors.E(op, err)
}
body, p, err := rl.Receive()
if err != nil {
- return 0, err
+ return 0, errors.E(op, err)
}
if !p.HasFlag(goridge.PayloadControl) {
- return 0, fmt.Errorf("unexpected response, header is missing")
+ return 0, errors.E(op, errors.Str("unexpected response, header is missing"))
}
link := &pidCommand{}
- if err := json.Unmarshal(body, link); err != nil {
- return 0, err
+ err = json.Unmarshal(body, link)
+ if err != nil {
+ return 0, errors.E(op, err)
}
return int64(link.Pid), nil
diff --git a/src/Exception/RoadRunnerException.php b/src/Exception/RoadRunnerException.php
index cd657502..f83c3dd4 100644
--- a/src/Exception/RoadRunnerException.php
+++ b/src/Exception/RoadRunnerException.php
@@ -9,6 +9,6 @@ declare(strict_types=1);
namespace Spiral\RoadRunner\Exception;
-class RoadRunnerException extends \RuntimeException
+class RoadRunnerException extends \Spiral\RoadRunner\Exceptions\RoadRunnerException
{
}
diff --git a/src/Http/HttpClient.php b/src/Http/HttpClient.php
deleted file mode 100644
index 4ca152c8..00000000
--- a/src/Http/HttpClient.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-/**
- * High-performance PHP process supervisor and load balancer written in Go
- *
- * @author Alex Bond
- */
-declare(strict_types=1);
-
-namespace Spiral\RoadRunner;
-
-final class HttpClient
-{
- /** @var Worker */
- private $worker;
-
- /**
- * @param Worker $worker
- */
- public function __construct(Worker $worker)
- {
- $this->worker = $worker;
- }
-
- /**
- * @return Worker
- */
- public function getWorker(): Worker
- {
- return $this->worker;
- }
-
- /**
- * @return mixed[]|null Request information as ['ctx'=>[], 'body'=>string]
- * or null if termination request or invalid context.
- */
- public function acceptRequest(): ?array
- {
- $body = $this->getWorker()->receive($ctx);
- if (empty($body) && empty($ctx)) {
- // termination request
- return null;
- }
-
- $ctx = json_decode($ctx, true);
- if ($ctx === null) {
- // invalid context
- return null;
- }
-
- return ['ctx' => $ctx, 'body' => $body];
- }
-
- /**
- * Send response to the application server.
- *
- * @param int $status Http status code
- * @param string $body Body of response
- * @param string[][] $headers An associative array of the message's headers. Each
- * key MUST be a header name, and each value MUST be an array of strings
- * for that header.
- */
- public function respond(int $status, string $body, array $headers = []): void
- {
- if (empty($headers)) {
- // this is required to represent empty header set as map and not as array
- $headers = new \stdClass();
- }
-
- $this->getWorker()->send(
- $body,
- (string) json_encode(['status' => $status, 'headers' => $headers])
- );
- }
-}
diff --git a/src/Http/PSR7Client.php b/src/Http/PSR7Client.php
deleted file mode 100644
index 777dd891..00000000
--- a/src/Http/PSR7Client.php
+++ /dev/null
@@ -1,217 +0,0 @@
-<?php
-
-/**
- * High-performance PHP process supervisor and load balancer written in Go
- *
- * @author Wolfy-J
- */
-declare(strict_types=1);
-
-namespace Spiral\RoadRunner;
-
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestFactoryInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Message\StreamFactoryInterface;
-use Psr\Http\Message\UploadedFileFactoryInterface;
-use Psr\Http\Message\UploadedFileInterface;
-
-/**
- * Manages PSR-7 request and response.
- */
-class PSR7Client
-{
- /** @var HttpClient */
- private $httpClient;
-
- /** @var ServerRequestFactoryInterface */
- private $requestFactory;
-
- /** @var StreamFactoryInterface */
- private $streamFactory;
-
- /** @var UploadedFileFactoryInterface */
- private $uploadsFactory;
-
- /** @var mixed[] */
- private $originalServer = [];
-
- /** @var string[] Valid values for HTTP protocol version */
- private static $allowedVersions = ['1.0', '1.1', '2',];
-
- /**
- * @param Worker $worker
- * @param ServerRequestFactoryInterface|null $requestFactory
- * @param StreamFactoryInterface|null $streamFactory
- * @param UploadedFileFactoryInterface|null $uploadsFactory
- */
- public function __construct(
- Worker $worker,
- ServerRequestFactoryInterface $requestFactory = null,
- StreamFactoryInterface $streamFactory = null,
- UploadedFileFactoryInterface $uploadsFactory = null
- ) {
- $this->httpClient = new HttpClient($worker);
- $this->requestFactory = $requestFactory ?? new Diactoros\ServerRequestFactory();
- $this->streamFactory = $streamFactory ?? new Diactoros\StreamFactory();
- $this->uploadsFactory = $uploadsFactory ?? new Diactoros\UploadedFileFactory();
- $this->originalServer = $_SERVER;
- }
-
- /**
- * @return Worker
- */
- public function getWorker(): Worker
- {
- return $this->httpClient->getWorker();
- }
-
- /**
- * @return ServerRequestInterface|null
- */
- public function acceptRequest(): ?ServerRequestInterface
- {
- $rawRequest = $this->httpClient->acceptRequest();
- if ($rawRequest === null) {
- return null;
- }
-
- $_SERVER = $this->configureServer($rawRequest['ctx']);
-
- $request = $this->requestFactory->createServerRequest(
- $rawRequest['ctx']['method'],
- $rawRequest['ctx']['uri'],
- $_SERVER
- );
-
- parse_str($rawRequest['ctx']['rawQuery'], $query);
-
- $request = $request
- ->withProtocolVersion(static::fetchProtocolVersion($rawRequest['ctx']['protocol']))
- ->withCookieParams($rawRequest['ctx']['cookies'])
- ->withQueryParams($query)
- ->withUploadedFiles($this->wrapUploads($rawRequest['ctx']['uploads']));
-
- foreach ($rawRequest['ctx']['attributes'] as $name => $value) {
- $request = $request->withAttribute($name, $value);
- }
-
- foreach ($rawRequest['ctx']['headers'] as $name => $value) {
- $request = $request->withHeader($name, $value);
- }
-
- if ($rawRequest['ctx']['parsed']) {
- return $request->withParsedBody(json_decode($rawRequest['body'], true));
- }
-
- if ($rawRequest['body'] !== null) {
- return $request->withBody($this->streamFactory->createStream($rawRequest['body']));
- }
-
- return $request;
- }
-
- /**
- * Send response to the application server.
- *
- * @param ResponseInterface $response
- */
- public function respond(ResponseInterface $response): void
- {
- $this->httpClient->respond(
- $response->getStatusCode(),
- $response->getBody()->__toString(),
- $response->getHeaders()
- );
- }
-
- /**
- * Returns altered copy of _SERVER variable. Sets ip-address,
- * request-time and other values.
- *
- * @param mixed[] $ctx
- * @return mixed[]
- */
- protected function configureServer(array $ctx): array
- {
- $server = $this->originalServer;
-
- $server['REQUEST_URI'] = $ctx['uri'];
- $server['REQUEST_TIME'] = time();
- $server['REQUEST_TIME_FLOAT'] = microtime(true);
- $server['REMOTE_ADDR'] = $ctx['attributes']['ipAddress'] ?? $ctx['remoteAddr'] ?? '127.0.0.1';
- $server['REQUEST_METHOD'] = $ctx['method'];
-
- $server['HTTP_USER_AGENT'] = '';
- foreach ($ctx['headers'] as $key => $value) {
- $key = strtoupper(str_replace('-', '_', $key));
- if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) {
- $server[$key] = implode(', ', $value);
- } else {
- $server['HTTP_' . $key] = implode(', ', $value);
- }
- }
-
- return $server;
- }
-
- /**
- * Wraps all uploaded files with UploadedFile.
- *
- * @param array[] $files
- *
- * @return UploadedFileInterface[]|mixed[]
- */
- private function wrapUploads($files): array
- {
- if (empty($files)) {
- return [];
- }
-
- $result = [];
- foreach ($files as $index => $f) {
- if (!isset($f['name'])) {
- $result[$index] = $this->wrapUploads($f);
- continue;
- }
-
- if (UPLOAD_ERR_OK === $f['error']) {
- $stream = $this->streamFactory->createStreamFromFile($f['tmpName']);
- } else {
- $stream = $this->streamFactory->createStream();
- }
-
- $result[$index] = $this->uploadsFactory->createUploadedFile(
- $stream,
- $f['size'],
- $f['error'],
- $f['name'],
- $f['mime']
- );
- }
-
- return $result;
- }
-
- /**
- * Normalize HTTP protocol version to valid values
- *
- * @param string $version
- * @return string
- */
- private static function fetchProtocolVersion(string $version): string
- {
- $v = substr($version, 5);
-
- if ($v === '2.0') {
- return '2';
- }
-
- // Fallback for values outside of valid protocol versions
- if (!in_array($v, static::$allowedVersions, true)) {
- return '1.1';
- }
-
- return $v;
- }
-}
diff --git a/src/Metrics/Metrics.php b/src/Metrics/Metrics.php
deleted file mode 100644
index d6b6e1da..00000000
--- a/src/Metrics/Metrics.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-/**
- * Spiral Framework.
- *
- * @license MIT
- * @author Anton Titov (Wolfy-J)
- */
-declare(strict_types=1);
-
-namespace Spiral\RoadRunner;
-
-use Spiral\Goridge\Exceptions\RPCException;
-use Spiral\Goridge\RPC;
-use Spiral\RoadRunner\Exception\MetricException;
-
-/**
- * Application metrics.
- */
-final class Metrics implements MetricsInterface
-{
- /** @var RPC */
- private $rpc;
-
- /**
- * @param RPC $rpc
- */
- public function __construct(RPC $rpc)
- {
- $this->rpc = $rpc;
- }
-
- /**
- * @inheritDoc
- */
- public function add(string $name, float $value, array $labels = []): void
- {
- try {
- $this->rpc->call('metrics.Add', compact('name', 'value', 'labels'));
- } catch (RPCException $e) {
- throw new MetricException($e->getMessage(), $e->getCode(), $e);
- }
- }
-
- /**
- * @inheritDoc
- */
- public function sub(string $name, float $value, array $labels = []): void
- {
- try {
- $this->rpc->call('metrics.Sub', compact('name', 'value', 'labels'));
- } catch (RPCException $e) {
- throw new MetricException($e->getMessage(), $e->getCode(), $e);
- }
- }
-
- /**
- * @inheritDoc
- */
- public function observe(string $name, float $value, array $labels = []): void
- {
- try {
- $this->rpc->call('metrics.Observe', compact('name', 'value', 'labels'));
- } catch (RPCException $e) {
- throw new MetricException($e->getMessage(), $e->getCode(), $e);
- }
- }
-
- /**
- * @inheritDoc
- */
- public function set(string $name, float $value, array $labels = []): void
- {
- try {
- $this->rpc->call('metrics.Set', compact('name', 'value', 'labels'));
- } catch (RPCException $e) {
- throw new MetricException($e->getMessage(), $e->getCode(), $e);
- }
- }
-}
diff --git a/src/Metrics/MetricsInterface.php b/src/Metrics/MetricsInterface.php
deleted file mode 100644
index ec2009b0..00000000
--- a/src/Metrics/MetricsInterface.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-/**
- * Spiral Framework.
- *
- * @license MIT
- * @author Anton Titov (Wolfy-J)
- */
-declare(strict_types=1);
-
-namespace Spiral\RoadRunner;
-
-use Spiral\RoadRunner\Exception\MetricException;
-
-interface MetricsInterface
-{
- /**
- * Add collector value. Fallback to appropriate method of related collector.
- *
- * @param string $collector
- * @param float $value
- * @param mixed[] $labels
- *
- * @throws MetricException
- * @return void
- */
- public function add(string $collector, float $value, array $labels = []);
-
- /**
- * Subtract the collector value, only for gauge collector.
- *
- * @param string $collector
- * @param float $value
- * @param mixed[] $labels
- *
- * @throws MetricException
- * @return void
- */
- public function sub(string $collector, float $value, array $labels = []);
-
- /**
- * Observe collector value, only for histogram and summary collectors.
- *
- * @param string $collector
- * @param float $value
- * @param mixed[] $labels
- *
- * @throws MetricException
- * @return void
- */
- public function observe(string $collector, float $value, array $labels = []);
-
- /**
- * Set collector value, only for gauge collector.
- *
- * @param string $collector
- * @param float $value
- * @param mixed[] $labels
- *
- * @throws MetricException
- * @return void
- */
- public function set(string $collector, float $value, array $labels = []);
-}
diff --git a/src/WorkerInterface.php b/src/WorkerInterface.php
deleted file mode 100644
index e69de29b..00000000
--- a/src/WorkerInterface.php
+++ /dev/null
diff --git a/tests/broken.php b/tests/broken.php
index 42b4e7c2..42b4e7c2 100755..100644
--- a/tests/broken.php
+++ b/tests/broken.php
diff --git a/tests/client.php b/tests/client.php
index 835b1c6c..835b1c6c 100755..100644
--- a/tests/client.php
+++ b/tests/client.php
diff --git a/tests/delay.php b/tests/delay.php
index bf9ecc12..bf9ecc12 100755..100644
--- a/tests/delay.php
+++ b/tests/delay.php
diff --git a/tests/echo.php b/tests/echo.php
index 1570e3df..1570e3df 100755..100644
--- a/tests/echo.php
+++ b/tests/echo.php
diff --git a/tests/error.php b/tests/error.php
index 8e1c8d0d..8e1c8d0d 100755..100644
--- a/tests/error.php
+++ b/tests/error.php
diff --git a/tests/failboot.php b/tests/failboot.php
index d59462cd..d59462cd 100755..100644
--- a/tests/failboot.php
+++ b/tests/failboot.php
diff --git a/tests/gzip-large-file.txt b/tests/gzip-large-file.txt
index 4c3eef8f..4c3eef8f 100755..100644
--- a/tests/gzip-large-file.txt
+++ b/tests/gzip-large-file.txt
diff --git a/tests/head.php b/tests/head.php
index 88ebd3f2..88ebd3f2 100755..100644
--- a/tests/head.php
+++ b/tests/head.php
diff --git a/tests/http/client.php b/tests/http/client.php
index 9f21b273..9f21b273 100755..100644
--- a/tests/http/client.php
+++ b/tests/http/client.php
diff --git a/tests/http/cookie.php b/tests/http/cookie.php
index 97673ef5..97673ef5 100755..100644
--- a/tests/http/cookie.php
+++ b/tests/http/cookie.php
diff --git a/tests/http/data.php b/tests/http/data.php
index 6570936a..6570936a 100755..100644
--- a/tests/http/data.php
+++ b/tests/http/data.php
diff --git a/tests/http/echo.php b/tests/http/echo.php
index 08e29a26..08e29a26 100755..100644
--- a/tests/http/echo.php
+++ b/tests/http/echo.php
diff --git a/tests/http/echoDelay.php b/tests/http/echoDelay.php
index 78e85477..78e85477 100755..100644
--- a/tests/http/echoDelay.php
+++ b/tests/http/echoDelay.php
diff --git a/tests/http/echoerr.php b/tests/http/echoerr.php
index 7e1d05e7..7e1d05e7 100755..100644
--- a/tests/http/echoerr.php
+++ b/tests/http/echoerr.php
diff --git a/tests/http/env.php b/tests/http/env.php
index 3755bdea..3755bdea 100755..100644
--- a/tests/http/env.php
+++ b/tests/http/env.php
diff --git a/tests/http/error.php b/tests/http/error.php
index 527e4068..527e4068 100755..100644
--- a/tests/http/error.php
+++ b/tests/http/error.php
diff --git a/tests/http/error2.php b/tests/http/error2.php
index 12a672ac..12a672ac 100755..100644
--- a/tests/http/error2.php
+++ b/tests/http/error2.php
diff --git a/tests/http/header.php b/tests/http/header.php
index 393d9623..393d9623 100755..100644
--- a/tests/http/header.php
+++ b/tests/http/header.php
diff --git a/tests/http/headers.php b/tests/http/headers.php
index b6f3967a..b6f3967a 100755..100644
--- a/tests/http/headers.php
+++ b/tests/http/headers.php
diff --git a/tests/http/ip.php b/tests/http/ip.php
index 49eb9285..49eb9285 100755..100644
--- a/tests/http/ip.php
+++ b/tests/http/ip.php
diff --git a/tests/http/memleak.php b/tests/http/memleak.php
index 197a7fb1..197a7fb1 100755..100644
--- a/tests/http/memleak.php
+++ b/tests/http/memleak.php
diff --git a/tests/http/payload.php b/tests/http/payload.php
index b7a0311f..b7a0311f 100755..100644
--- a/tests/http/payload.php
+++ b/tests/http/payload.php
diff --git a/tests/http/pid.php b/tests/http/pid.php
index f22d8e23..f22d8e23 100755..100644
--- a/tests/http/pid.php
+++ b/tests/http/pid.php
diff --git a/tests/http/push.php b/tests/http/push.php
index d88fc076..d88fc076 100755..100644
--- a/tests/http/push.php
+++ b/tests/http/push.php
diff --git a/tests/http/request-uri.php b/tests/http/request-uri.php
index d4c87551..d4c87551 100755..100644
--- a/tests/http/request-uri.php
+++ b/tests/http/request-uri.php
diff --git a/tests/http/server.php b/tests/http/server.php
index 393d9623..393d9623 100755..100644
--- a/tests/http/server.php
+++ b/tests/http/server.php
diff --git a/tests/http/slow-client.php b/tests/http/slow-client.php
index 4d3963d7..4d3963d7 100755..100644
--- a/tests/http/slow-client.php
+++ b/tests/http/slow-client.php
diff --git a/tests/http/stuck.php b/tests/http/stuck.php
index 2dea0572..2dea0572 100755..100644
--- a/tests/http/stuck.php
+++ b/tests/http/stuck.php
diff --git a/tests/http/upload.php b/tests/http/upload.php
index bb4af766..bb4af766 100755..100644
--- a/tests/http/upload.php
+++ b/tests/http/upload.php
diff --git a/tests/http/user-agent.php b/tests/http/user-agent.php
index 03d7a2c8..03d7a2c8 100755..100644
--- a/tests/http/user-agent.php
+++ b/tests/http/user-agent.php
diff --git a/tests/pid.php b/tests/pid.php
index bf10a025..bf10a025 100755..100644
--- a/tests/pid.php
+++ b/tests/pid.php
diff --git a/tests/sample.txt b/tests/sample.txt
index eed7e79a..eed7e79a 100755..100644
--- a/tests/sample.txt
+++ b/tests/sample.txt
diff --git a/tests/slow-client.php b/tests/slow-client.php
index ece0a439..ece0a439 100755..100644
--- a/tests/slow-client.php
+++ b/tests/slow-client.php
diff --git a/tests/slow-destroy.php b/tests/slow-destroy.php
index e2a01af2..e2a01af2 100755..100644
--- a/tests/slow-destroy.php
+++ b/tests/slow-destroy.php
diff --git a/tests/slow-pid.php b/tests/slow-pid.php
index 747e7e86..747e7e86 100755..100644
--- a/tests/slow-pid.php
+++ b/tests/slow-pid.php
diff --git a/tests/stop.php b/tests/stop.php
index 0100ad0f..0100ad0f 100755..100644
--- a/tests/stop.php
+++ b/tests/stop.php