summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
Diffstat (limited to 'service')
-rw-r--r--service/env/environment.go16
-rw-r--r--service/env/service.go14
-rw-r--r--service/env/service_test.go17
-rw-r--r--service/http/handler.go43
-rw-r--r--service/http/handler_test.go443
-rw-r--r--service/http/parse.go4
-rw-r--r--service/http/parse_test.go2
-rw-r--r--service/http/request.go4
-rw-r--r--service/http/service.go17
-rw-r--r--service/http/uploads_config.go2
-rw-r--r--service/http/uploads_test.go40
-rw-r--r--service/rpc/config.go2
-rw-r--r--service/static/config.go21
-rw-r--r--service/static/config_test.go8
-rw-r--r--service/static/service.go7
-rw-r--r--service/static/service_test.go37
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)