summaryrefslogtreecommitdiff
path: root/plugins/http
diff options
context:
space:
mode:
authorValery Piashchynski <[email protected]>2020-11-18 18:15:09 +0300
committerValery Piashchynski <[email protected]>2020-11-18 18:15:09 +0300
commit8fab090abc369237d5f9be2ee676005b24c2a470 (patch)
tree9f7fc358b66357f0218b8256d8073616995b91db /plugins/http
parent4ccd58fc363264d24f642ab7e0ccfe6538a0b91c (diff)
Handler test
Diffstat (limited to 'plugins/http')
-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.yaml0
-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
21 files changed, 2228 insertions, 2142 deletions
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/plugins/http/tests/configs/.rr-handler-echo.yaml b/plugins/http/tests/configs/.rr-handler-echo.yaml
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ 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