diff options
author | Valery Piashchynski <[email protected]> | 2020-11-18 18:15:09 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2020-11-18 18:15:09 +0300 |
commit | 8fab090abc369237d5f9be2ee676005b24c2a470 (patch) | |
tree | 9f7fc358b66357f0218b8256d8073616995b91db /plugins | |
parent | 4ccd58fc363264d24f642ab7e0ccfe6538a0b91c (diff) |
Handler test
Diffstat (limited to 'plugins')
23 files changed, 2288 insertions, 2145 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 diff --git a/plugins/logger/plugin.go b/plugins/logger/plugin.go index 0a8485d9..2937056c 100644 --- a/plugins/logger/plugin.go +++ b/plugins/logger/plugin.go @@ -35,7 +35,7 @@ func (z *ZapLogger) Init(cfg config.Configurer) error { // DefaultLogger returns default logger. func (z *ZapLogger) DefaultLogger() (log.Logger, error) { - return log.NewZapAdapter(z.base), nil + return NewZapAdapter(z.base), nil } // NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params. @@ -45,10 +45,10 @@ func (z *ZapLogger) NamedLogger(name string) (log.Logger, error) { if err != nil { return nil, err } - return log.NewZapAdapter(l), nil + return NewZapAdapter(l), nil } - return log.NewZapAdapter(z.base.Named(name)), nil + return NewZapAdapter(z.base.Named(name)), nil } // NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params. diff --git a/plugins/logger/zap_adapter.go b/plugins/logger/zap_adapter.go new file mode 100644 index 00000000..41e6d8f9 --- /dev/null +++ b/plugins/logger/zap_adapter.go @@ -0,0 +1,57 @@ +package logger + +import ( + "fmt" + + "github.com/spiral/roadrunner/v2/interfaces/log" + "go.uber.org/zap" +) + +type ZapAdapter struct { + zl *zap.Logger +} + +// Create NewZapAdapter which uses general log interface +func NewZapAdapter(zapLogger *zap.Logger) *ZapAdapter { + return &ZapAdapter{ + zl: zapLogger.WithOptions(zap.AddCallerSkip(1)), + } +} + +func (log *ZapAdapter) fields(keyvals []interface{}) []zap.Field { + // we should have even number of keys and values + if len(keyvals)%2 != 0 { + return []zap.Field{zap.Error(fmt.Errorf("odd number of keyvals pairs: %v", keyvals))} + } + + var fields []zap.Field + for i := 0; i < len(keyvals); i += 2 { + key, ok := keyvals[i].(string) + if !ok { + key = fmt.Sprintf("%v", keyvals[i]) + } + fields = append(fields, zap.Any(key, keyvals[i+1])) + } + + return fields +} + +func (log *ZapAdapter) Debug(msg string, keyvals ...interface{}) { + log.zl.Debug(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) Info(msg string, keyvals ...interface{}) { + log.zl.Info(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) Warn(msg string, keyvals ...interface{}) { + log.zl.Warn(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) Error(msg string, keyvals ...interface{}) { + log.zl.Error(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) With(keyvals ...interface{}) log.Logger { + return NewZapAdapter(log.zl.With(log.fields(keyvals)...)) +} |