diff options
Diffstat (limited to 'service')
-rw-r--r-- | service/env/environment.go | 16 | ||||
-rw-r--r-- | service/env/service.go | 14 | ||||
-rw-r--r-- | service/env/service_test.go | 17 | ||||
-rw-r--r-- | service/http/handler.go | 43 | ||||
-rw-r--r-- | service/http/handler_test.go | 443 | ||||
-rw-r--r-- | service/http/parse.go | 4 | ||||
-rw-r--r-- | service/http/parse_test.go | 2 | ||||
-rw-r--r-- | service/http/request.go | 4 | ||||
-rw-r--r-- | service/http/service.go | 17 | ||||
-rw-r--r-- | service/http/uploads_config.go | 2 | ||||
-rw-r--r-- | service/http/uploads_test.go | 40 | ||||
-rw-r--r-- | service/rpc/config.go | 2 | ||||
-rw-r--r-- | service/static/config.go | 21 | ||||
-rw-r--r-- | service/static/config_test.go | 8 | ||||
-rw-r--r-- | service/static/service.go | 7 | ||||
-rw-r--r-- | service/static/service_test.go | 37 |
16 files changed, 525 insertions, 152 deletions
diff --git a/service/env/environment.go b/service/env/environment.go index 52a5bcf4..ab8febf7 100644 --- a/service/env/environment.go +++ b/service/env/environment.go @@ -3,9 +3,21 @@ package env // Environment aggregates list of environment variables. This interface can be used in custom implementation to drive // values from external sources. type Environment interface { - // GetEnv must return list of env variables. - GetEnv() (map[string]string, error) + Setter + Getter + + // Copy all environment values. + Copy(setter Setter) error +} +// Setter provides ability to set environment value. +type Setter interface { // SetEnv sets or creates environment value. SetEnv(key, value string) } + +// Getter provides ability to set environment value. +type Getter interface { + // GetEnv must return list of env variables. + GetEnv() (map[string]string, error) +} diff --git a/service/env/service.go b/service/env/service.go index 4d1327d4..83175b36 100644 --- a/service/env/service.go +++ b/service/env/service.go @@ -39,3 +39,17 @@ func (s *Service) GetEnv() (map[string]string, error) { func (s *Service) SetEnv(key, value string) { s.values[key] = value } + +// Copy all environment values. +func (s *Service) Copy(setter Setter) error { + values, err := s.GetEnv() + if err != nil { + return err + } + + for k, v := range values { + setter.SetEnv(k, v) + } + + return nil +} diff --git a/service/env/service_test.go b/service/env/service_test.go index 61fecd28..c20bb76c 100644 --- a/service/env/service_test.go +++ b/service/env/service_test.go @@ -49,3 +49,20 @@ func Test_Set(t *testing.T) { assert.Equal(t, "value-new", values["key"]) assert.Equal(t, "new", values["other"]) } + +func Test_Copy(t *testing.T) { + s1 := NewService(map[string]string{"RR": "version"}) + s2 := NewService(map[string]string{}) + + s1.SetEnv("key", "value-new") + s1.SetEnv("other", "new") + + assert.NoError(t, s1.Copy(s2)) + + values, err := s2.GetEnv() + assert.NoError(t, err) + assert.Len(t, values, 3) + assert.Equal(t, "version", values["RR"]) + assert.Equal(t, "value-new", values["key"]) + assert.Equal(t, "new", values["other"]) +} diff --git a/service/http/handler.go b/service/http/handler.go index d7521959..8cebc42a 100644 --- a/service/http/handler.go +++ b/service/http/handler.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" "sync" + "time" ) const ( @@ -23,6 +24,15 @@ type ErrorEvent struct { // Error - associated error, if any. Error error + + // event timings + start time.Time + elapsed time.Duration +} + +// Elapsed returns duration of the invocation. +func (e *ErrorEvent) Elapsed() time.Duration { + return e.elapsed } // ResponseEvent represents singular http response event. @@ -32,6 +42,15 @@ type ResponseEvent struct { // Response contains service response. Response *Response + + // event timings + start time.Time + elapsed time.Duration +} + +// Elapsed returns duration of the invocation. +func (e *ResponseEvent) Elapsed() time.Duration { + return e.elapsed } // Handler serves http connections to underlying PHP application using PSR-7 protocol. Context will include request headers, @@ -53,14 +72,16 @@ func (h *Handler) Listen(l func(event int, ctx interface{})) { // mdwr serve using PSR-7 requests passed to underlying application. Attempts to serve static files first if enabled. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + start := time.Now() + // validating request size if h.cfg.MaxRequest != 0 { if length := r.Header.Get("content-length"); length != "" { if size, err := strconv.ParseInt(length, 10, 64); err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } else if size > h.cfg.MaxRequest*1024*1024 { - h.handleError(w, r, errors.New("request body max size is exceeded")) + h.handleError(w, r, errors.New("request body max size is exceeded"), start) return } } @@ -68,7 +89,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { req, err := NewRequest(r, h.cfg.Uploads) if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } @@ -77,37 +98,37 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { p, err := req.Payload() if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } rsp, err := h.rr.Exec(p) if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } resp, err := NewResponse(rsp) if err != nil { - h.handleError(w, r, err) + h.handleError(w, r, err, start) return } - h.handleResponse(req, resp) + h.handleResponse(req, resp, start) resp.Write(w) } // handleError sends error. -func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) { - h.throw(EventError, &ErrorEvent{Request: r, Error: err}) +func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error, start time.Time) { + h.throw(EventError, &ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)}) w.WriteHeader(500) w.Write([]byte(err.Error())) } // handleResponse triggers response event. -func (h *Handler) handleResponse(req *Request, resp *Response) { - h.throw(EventResponse, &ResponseEvent{Request: req, Response: resp}) +func (h *Handler) handleResponse(req *Request, resp *Response, start time.Time) { + h.throw(EventResponse, &ResponseEvent{Request: req, Response: resp, start: start, elapsed: time.Since(start)}) } // throw invokes event handler if any. diff --git a/service/http/handler_test.go b/service/http/handler_test.go index 1750bf43..d876ef8e 100644 --- a/service/http/handler_test.go +++ b/service/http/handler_test.go @@ -29,8 +29,8 @@ func get(url string) (string, *http.Response, error) { return string(b), r, err } -func TestServer_Echo(t *testing.T) { - st := &Handler{ +func TestHandler_Echo(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -49,10 +49,10 @@ func TestServer_Echo(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -65,7 +65,7 @@ func TestServer_Echo(t *testing.T) { } func Test_HandlerErrors(t *testing.T) { - st := &Handler{ + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -87,12 +87,12 @@ func Test_HandlerErrors(t *testing.T) { wr := httptest.NewRecorder() rq := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("data"))) - st.ServeHTTP(wr, rq) + h.ServeHTTP(wr, rq) assert.Equal(t, 500, wr.Code) } func Test_Handler_JSON_error(t *testing.T) { - st := &Handler{ + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -116,12 +116,12 @@ func Test_Handler_JSON_error(t *testing.T) { rq.Header.Add("Content-Type", "application/json") rq.Header.Add("Content-Size", "3") - st.ServeHTTP(wr, rq) + h.ServeHTTP(wr, rq) assert.Equal(t, 500, wr.Code) } -func TestServer_Headers(t *testing.T) { - st := &Handler{ +func TestHandler_Headers(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -140,10 +140,10 @@ func TestServer_Headers(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8078", Handler: st} + hs := &http.Server{Addr: ":8078", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -167,8 +167,8 @@ func TestServer_Headers(t *testing.T) { assert.Equal(t, "SAMPLE", string(b)) } -func TestServer_Empty_User_Agent(t *testing.T) { - st := &Handler{ +func TestHandler_Empty_User_Agent(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -187,10 +187,10 @@ func TestServer_Empty_User_Agent(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8088", Handler: st} + hs := &http.Server{Addr: ":8088", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -213,9 +213,8 @@ func TestServer_Empty_User_Agent(t *testing.T) { assert.Equal(t, "", string(b)) } - -func TestServer_User_Agent(t *testing.T) { - st := &Handler{ +func TestHandler_User_Agent(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -234,10 +233,10 @@ func TestServer_User_Agent(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8088", Handler: st} + hs := &http.Server{Addr: ":8088", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -260,8 +259,8 @@ func TestServer_User_Agent(t *testing.T) { assert.Equal(t, "go-agent", string(b)) } -func TestServer_Cookies(t *testing.T) { - st := &Handler{ +func TestHandler_Cookies(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -280,10 +279,10 @@ func TestServer_Cookies(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8079", Handler: st} + hs := &http.Server{Addr: ":8079", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -311,8 +310,8 @@ func TestServer_Cookies(t *testing.T) { } } -func TestServer_JsonPayload_POST(t *testing.T) { - st := &Handler{ +func TestHandler_JsonPayload_POST(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -331,10 +330,10 @@ func TestServer_JsonPayload_POST(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8090", Handler: st} + hs := &http.Server{Addr: ":8090", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -361,8 +360,8 @@ func TestServer_JsonPayload_POST(t *testing.T) { assert.Equal(t, `{"value":"key"}`, string(b)) } -func TestServer_JsonPayload_PUT(t *testing.T) { - st := &Handler{ +func TestHandler_JsonPayload_PUT(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -381,10 +380,10 @@ func TestServer_JsonPayload_PUT(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8081", Handler: st} + hs := &http.Server{Addr: ":8081", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -407,8 +406,8 @@ func TestServer_JsonPayload_PUT(t *testing.T) { assert.Equal(t, `{"value":"key"}`, string(b)) } -func TestServer_JsonPayload_PATCH(t *testing.T) { - st := &Handler{ +func TestHandler_JsonPayload_PATCH(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -427,10 +426,10 @@ func TestServer_JsonPayload_PATCH(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8082", Handler: st} + hs := &http.Server{Addr: ":8082", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -453,8 +452,8 @@ func TestServer_JsonPayload_PATCH(t *testing.T) { assert.Equal(t, `{"value":"key"}`, string(b)) } -func TestServer_FormData_POST(t *testing.T) { - st := &Handler{ +func TestHandler_FormData_POST(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -473,10 +472,10 @@ func TestServer_FormData_POST(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8083", Handler: st} + hs := &http.Server{Addr: ":8083", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -511,8 +510,118 @@ func TestServer_FormData_POST(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_FormData_PUT(t *testing.T) { - st := &Handler{ +func TestHandler_FormData_POST_Overwrite(t *testing.T) { + h := &Handler{ + cfg: &Config{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: roadrunner.NewServer(&roadrunner.ServerConfig{ + Command: "php ../../tests/http/client.php data pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: 1, + AllocateTimeout: 10000000, + DestroyTimeout: 10000000, + }, + }), + } + + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8083", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + 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 r.Body.Close() + + 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{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: 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.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8083", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + 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 r.Body.Close() + + 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{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -531,10 +640,10 @@ func TestServer_FormData_PUT(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8084", Handler: st} + hs := &http.Server{Addr: ":8084", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -569,8 +678,8 @@ func TestServer_FormData_PUT(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_FormData_PATCH(t *testing.T) { - st := &Handler{ +func TestHandler_FormData_PATCH(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -589,10 +698,10 @@ func TestServer_FormData_PATCH(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8085", Handler: st} + hs := &http.Server{Addr: ":8085", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -627,8 +736,8 @@ func TestServer_FormData_PATCH(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Multipart_POST(t *testing.T) { - st := &Handler{ +func TestHandler_Multipart_POST(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -647,10 +756,10 @@ func TestServer_Multipart_POST(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8019", Handler: st} + hs := &http.Server{Addr: ":8019", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -689,8 +798,8 @@ func TestServer_Multipart_POST(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Multipart_PUT(t *testing.T) { - st := &Handler{ +func TestHandler_Multipart_PUT(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -709,10 +818,10 @@ func TestServer_Multipart_PUT(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8020", Handler: st} + hs := &http.Server{Addr: ":8020", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -751,8 +860,8 @@ func TestServer_Multipart_PUT(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Multipart_PATCH(t *testing.T) { - st := &Handler{ +func TestHandler_Multipart_PATCH(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -771,10 +880,10 @@ func TestServer_Multipart_PATCH(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -813,8 +922,8 @@ func TestServer_Multipart_PATCH(t *testing.T) { assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b)) } -func TestServer_Error(t *testing.T) { - st := &Handler{ +func TestHandler_Error(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -833,10 +942,10 @@ func TestServer_Error(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -847,8 +956,8 @@ func TestServer_Error(t *testing.T) { assert.Equal(t, 500, r.StatusCode) } -func TestServer_Error2(t *testing.T) { - st := &Handler{ +func TestHandler_Error2(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -867,10 +976,10 @@ func TestServer_Error2(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -881,8 +990,8 @@ func TestServer_Error2(t *testing.T) { assert.Equal(t, 500, r.StatusCode) } -func TestServer_Error3(t *testing.T) { - st := &Handler{ +func TestHandler_Error3(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1, Uploads: &UploadsConfig{ @@ -901,10 +1010,10 @@ func TestServer_Error3(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -926,8 +1035,154 @@ func TestServer_Error3(t *testing.T) { assert.Equal(t, 500, r.StatusCode) } +func TestHandler_ResponseDuration(t *testing.T) { + h := &Handler{ + cfg: &Config{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: roadrunner.NewServer(&roadrunner.ServerConfig{ + Command: "php ../../tests/http/client.php echo pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: 1, + AllocateTimeout: 10000000, + DestroyTimeout: 10000000, + }, + }), + } + + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8177", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + time.Sleep(time.Millisecond * 10) + + gotresp := make(chan interface{}) + h.Listen(func(event int, ctx interface{}) { + if event == EventResponse { + c := ctx.(*ResponseEvent) + + if c.Elapsed() > 0 { + close(gotresp) + } + } + }) + + body, r, err := get("http://localhost:8177/?hello=world") + assert.NoError(t, err) + + <-gotresp + + assert.Equal(t, 201, r.StatusCode) + assert.Equal(t, "WORLD", body) +} + +func TestHandler_ResponseDurationDelayed(t *testing.T) { + h := &Handler{ + cfg: &Config{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: roadrunner.NewServer(&roadrunner.ServerConfig{ + Command: "php ../../tests/http/client.php echoDelay pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: 1, + AllocateTimeout: 10000000, + DestroyTimeout: 10000000, + }, + }), + } + + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8177", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + time.Sleep(time.Millisecond * 10) + + gotresp := make(chan interface{}) + h.Listen(func(event int, ctx interface{}) { + if event == EventResponse { + c := ctx.(*ResponseEvent) + + if c.Elapsed() > time.Second { + close(gotresp) + } + } + }) + + body, r, err := get("http://localhost:8177/?hello=world") + assert.NoError(t, err) + + <-gotresp + + assert.Equal(t, 201, r.StatusCode) + assert.Equal(t, "WORLD", body) +} + +func TestHandler_ErrorDuration(t *testing.T) { + h := &Handler{ + cfg: &Config{ + MaxRequest: 1024, + Uploads: &UploadsConfig{ + Dir: os.TempDir(), + Forbid: []string{}, + }, + }, + rr: roadrunner.NewServer(&roadrunner.ServerConfig{ + Command: "php ../../tests/http/client.php error pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: 1, + AllocateTimeout: 10000000, + DestroyTimeout: 10000000, + }, + }), + } + + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() + + hs := &http.Server{Addr: ":8177", Handler: h} + defer hs.Shutdown(context.Background()) + + go func() { hs.ListenAndServe() }() + time.Sleep(time.Millisecond * 10) + + goterr := make(chan interface{}) + h.Listen(func(event int, ctx interface{}) { + if event == EventError { + c := ctx.(*ErrorEvent) + + if c.Elapsed() > 0 { + close(goterr) + } + } + }) + + _, r, err := get("http://localhost:8177/?hello=world") + assert.NoError(t, err) + + <-goterr + + assert.Equal(t, 500, r.StatusCode) +} + func BenchmarkHandler_Listen_Echo(b *testing.B) { - st := &Handler{ + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -946,10 +1201,10 @@ func BenchmarkHandler_Listen_Echo(b *testing.B) { }), } - st.rr.Start() - defer st.rr.Stop() + h.rr.Start() + defer h.rr.Stop() - hs := &http.Server{Addr: ":8177", Handler: st} + hs := &http.Server{Addr: ":8177", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() diff --git a/service/http/parse.go b/service/http/parse.go index 1f90930a..9b58d328 100644 --- a/service/http/parse.go +++ b/service/http/parse.go @@ -39,8 +39,8 @@ func (d dataTree) push(k string, v []string) { // mount mounts data tree recursively. func (d dataTree) mount(i []string, v []string) { if len(i) == 1 { - // single value context - d[i[0]] = v[0] + // single value context (last element) + d[i[0]] = v[len(v)-1] return } diff --git a/service/http/parse_test.go b/service/http/parse_test.go index 34c0dc0d..f95a3f9d 100644 --- a/service/http/parse_test.go +++ b/service/http/parse_test.go @@ -50,5 +50,3 @@ func same(in, out []string) bool { return true } - -// bench diff --git a/service/http/request.go b/service/http/request.go index eb5c05bd..b1ca514a 100644 --- a/service/http/request.go +++ b/service/http/request.go @@ -148,12 +148,12 @@ func (r *Request) Payload() (p *roadrunner.Payload, err error) { // contentType returns the payload content type. func (r *Request) contentType() int { - if r.Method != "POST" && r.Method != "PUT" && r.Method != "PATCH" && r.Method != "DELETE" { + if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" { return contentNone } ct := r.Headers.Get("content-type") - if ct == "application/x-www-form-urlencoded" { + if strings.Contains(ct, "application/x-www-form-urlencoded") { return contentFormData } diff --git a/service/http/service.go b/service/http/service.go index e8e8eb51..ad59f887 100644 --- a/service/http/service.go +++ b/service/http/service.go @@ -54,7 +54,9 @@ func (s *Service) Init(cfg *Config, r *rpc.Service, e env.Environment) (bool, er s.cfg = cfg s.env = e if r != nil { - r.Register(ID, &rpcServer{s}) + if err := r.Register(ID, &rpcServer{s}); err != nil { + return false, err + } } return true, nil @@ -65,18 +67,13 @@ func (s *Service) Serve() error { s.mu.Lock() if s.env != nil { - values, err := s.env.GetEnv() - if err != nil { - return err - } - - for k, v := range values { - s.cfg.Workers.SetEnv(k, v) + if err := s.env.Copy(s.cfg.Workers); err != nil { + return nil } - - s.cfg.Workers.SetEnv("RR_HTTP", "true") } + s.cfg.Workers.SetEnv("RR_HTTP", "true") + s.rr = roadrunner.NewServer(s.cfg.Workers) s.rr.Listen(s.throw) diff --git a/service/http/uploads_config.go b/service/http/uploads_config.go index 3f655064..9f62d779 100644 --- a/service/http/uploads_config.go +++ b/service/http/uploads_config.go @@ -31,7 +31,7 @@ func (cfg *UploadsConfig) TmpDir() string { return os.TempDir() } -// Forbids must return true if file extension is not allowed for the upload. +// AlwaysForbid must return true if file extension is not allowed for the upload. func (cfg *UploadsConfig) Forbids(filename string) bool { ext := strings.ToLower(path.Ext(filename)) diff --git a/service/http/uploads_test.go b/service/http/uploads_test.go index 96e95733..d452f834 100644 --- a/service/http/uploads_test.go +++ b/service/http/uploads_test.go @@ -17,8 +17,8 @@ import ( "time" ) -func TestServer_Upload_File(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_File(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -37,10 +37,10 @@ func TestServer_Upload_File(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -78,8 +78,8 @@ func TestServer_Upload_File(t *testing.T) { assert.Equal(t, `{"upload":`+fs+`}`, string(b)) } -func TestServer_Upload_NestedFile(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_NestedFile(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -98,10 +98,10 @@ func TestServer_Upload_NestedFile(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -139,8 +139,8 @@ func TestServer_Upload_NestedFile(t *testing.T) { assert.Equal(t, `{"upload":{"x":{"y":{"z":[`+fs+`]}}}}`, string(b)) } -func TestServer_Upload_File_NoTmpDir(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_File_NoTmpDir(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -159,10 +159,10 @@ func TestServer_Upload_File_NoTmpDir(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() @@ -200,8 +200,8 @@ func TestServer_Upload_File_NoTmpDir(t *testing.T) { assert.Equal(t, `{"upload":`+fs+`}`, string(b)) } -func TestServer_Upload_File_Forbids(t *testing.T) { - st := &Handler{ +func TestHandler_Upload_File_Forbids(t *testing.T) { + h := &Handler{ cfg: &Config{ MaxRequest: 1024, Uploads: &UploadsConfig{ @@ -220,10 +220,10 @@ func TestServer_Upload_File_Forbids(t *testing.T) { }), } - assert.NoError(t, st.rr.Start()) - defer st.rr.Stop() + assert.NoError(t, h.rr.Start()) + defer h.rr.Stop() - hs := &http.Server{Addr: ":8021", Handler: st} + hs := &http.Server{Addr: ":8021", Handler: h} defer hs.Shutdown(context.Background()) go func() { hs.ListenAndServe() }() diff --git a/service/rpc/config.go b/service/rpc/config.go index 653da6ea..fc8cfdbb 100644 --- a/service/rpc/config.go +++ b/service/rpc/config.go @@ -13,7 +13,7 @@ type Config struct { // Indicates if RPC connection is enabled. Enable bool - // AddListener string + // Listen string Listen string } diff --git a/service/static/config.go b/service/static/config.go index 5df7b013..eda459a7 100644 --- a/service/static/config.go +++ b/service/static/config.go @@ -16,6 +16,10 @@ type Config struct { // Forbid specifies list of file extensions which are forbidden for access. // Example: .php, .exe, .bat, .htaccess and etc. Forbid []string + + // Always specifies list of extensions which must always be served by static + // service, even if file not found. + Always []string } // Hydrate must populate Config values using given Config source. Must return error if Config is not valid. @@ -45,8 +49,8 @@ func (c *Config) Valid() error { return nil } -// Forbids must return true if file extension is not allowed for the upload. -func (c *Config) Forbids(filename string) bool { +// AlwaysForbid must return true if file extension is not allowed for the upload. +func (c *Config) AlwaysForbid(filename string) bool { ext := strings.ToLower(path.Ext(filename)) for _, v := range c.Forbid { @@ -57,3 +61,16 @@ func (c *Config) Forbids(filename string) bool { return false } + +// AlwaysServe must indicate that file is expected to be served by static service. +func (c *Config) AlwaysServe(filename string) bool { + ext := strings.ToLower(path.Ext(filename)) + + for _, v := range c.Always { + if ext == v { + return true + } + } + + return false +} diff --git a/service/static/config_test.go b/service/static/config_test.go index e3fa8d16..0221e116 100644 --- a/service/static/config_test.go +++ b/service/static/config_test.go @@ -29,10 +29,10 @@ func Test_Config_Hydrate_Error(t *testing.T) { func TestConfig_Forbids(t *testing.T) { cfg := Config{Forbid: []string{".php"}} - assert.True(t, cfg.Forbids("index.php")) - assert.True(t, cfg.Forbids("index.PHP")) - assert.True(t, cfg.Forbids("phpadmin/index.bak.php")) - assert.False(t, cfg.Forbids("index.html")) + assert.True(t, cfg.AlwaysForbid("index.php")) + assert.True(t, cfg.AlwaysForbid("index.PHP")) + assert.True(t, cfg.AlwaysForbid("phpadmin/index.bak.php")) + assert.False(t, cfg.AlwaysForbid("index.html")) } func TestConfig_Valid(t *testing.T) { diff --git a/service/static/service.go b/service/static/service.go index 2cb419fe..b824e787 100644 --- a/service/static/service.go +++ b/service/static/service.go @@ -45,12 +45,17 @@ func (s *Service) middleware(f http.HandlerFunc) http.HandlerFunc { func (s *Service) handleStatic(w http.ResponseWriter, r *http.Request) bool { fPath := path.Clean(r.URL.Path) - if s.cfg.Forbids(fPath) { + if s.cfg.AlwaysForbid(fPath) { return false } f, err := s.root.Open(fPath) if err != nil { + if s.cfg.AlwaysServe(fPath) { + w.WriteHeader(404) + return true + } + return false } defer f.Close() diff --git a/service/static/service_test.go b/service/static/service_test.go index fbc26a58..af616418 100644 --- a/service/static/service_test.go +++ b/service/static/service_test.go @@ -234,6 +234,43 @@ func Test_Files_Forbid(t *testing.T) { assert.Equal(t, "WORLD", b) } +func Test_Files_Always(t *testing.T) { + logger, _ := test.NewNullLogger() + logger.SetLevel(logrus.DebugLevel) + + c := service.NewContainer(logger) + c.Register(rrhttp.ID, &rrhttp.Service{}) + c.Register(ID, &Service{}) + + assert.NoError(t, c.Init(&testCfg{ + static: `{"enable":true, "dir":"../../tests", "forbid":[".php"], "always":[".ico"]}`, + httpCfg: `{ + "enable": true, + "address": ":6029", + "maxRequest": 1024, + "uploads": { + "dir": ` + tmpDir() + `, + "forbid": [] + }, + "workers":{ + "command": "php ../../tests/http/client.php echo pipes", + "relay": "pipes", + "pool": { + "numWorkers": 1, + "allocateTimeout": 10000000, + "destroyTimeout": 10000000 + } + } + }`})) + + go func() { c.Serve() }() + time.Sleep(time.Millisecond * 100) + defer c.Stop() + + _, r, _ := get("http://localhost:6029/favicon.ico") + assert.Equal(t, 404, r.StatusCode) +} + func Test_Files_NotFound(t *testing.T) { logger, _ := test.NewNullLogger() logger.SetLevel(logrus.DebugLevel) |