diff options
Diffstat (limited to 'service/http')
-rw-r--r-- | service/http/attributes.go | 69 | ||||
-rw-r--r-- | service/http/attributes/attributes.go | 74 | ||||
-rw-r--r-- | service/http/attributes/attributes_test.go | 67 | ||||
-rw-r--r-- | service/http/attributes_test.go | 67 | ||||
-rw-r--r-- | service/http/config.go | 43 | ||||
-rw-r--r-- | service/http/config_test.go | 21 | ||||
-rw-r--r-- | service/http/handler.go | 29 | ||||
-rw-r--r-- | service/http/request.go | 14 | ||||
-rw-r--r-- | service/http/service.go | 31 | ||||
-rw-r--r-- | service/http/service_test.go | 14 |
10 files changed, 244 insertions, 185 deletions
diff --git a/service/http/attributes.go b/service/http/attributes.go deleted file mode 100644 index acea38a1..00000000 --- a/service/http/attributes.go +++ /dev/null @@ -1,69 +0,0 @@ -package http - -import ( - "context" - "net/http" - "errors" -) - -const contextKey = "psr:attributes" - -type attrs map[string]interface{} - -// InitAttributes returns request with new context and attribute bag. -func InitAttributes(r *http.Request) *http.Request { - return r.WithContext(context.WithValue(r.Context(), contextKey, attrs{})) -} - -// AllAttributes returns all context attributes. -func AllAttributes(r *http.Request) map[string]interface{} { - v := r.Context().Value(contextKey) - if v == nil { - return attrs{} - } - - return v.(attrs) -} - -// Get gets the value from request context. It replaces any existing -// values. -func GetAttribute(r *http.Request, key string) interface{} { - v := r.Context().Value(contextKey) - if v == nil { - return nil - } - - return v.(attrs).Get(key) -} - -// Set sets the key to value. It replaces any existing -// values. Context specific. -func SetAttribute(r *http.Request, key string, value interface{}) error { - v := r.Context().Value(contextKey) - if v == nil { - return errors.New("unable to find psr:attributes context value") - } - - v.(attrs).Set(key, value) - return nil -} - -// Get gets the value associated with the given key. -func (v attrs) Get(key string) interface{} { - if v == nil { - return "" - } - - return v[key] -} - -// Set sets the key to value. It replaces any existing -// values. -func (v attrs) Set(key string, value interface{}) { - v[key] = value -} - -// Del deletes the value associated with key. -func (v attrs) Del(key string) { - delete(v, key) -} diff --git a/service/http/attributes/attributes.go b/service/http/attributes/attributes.go new file mode 100644 index 00000000..94d0e9c1 --- /dev/null +++ b/service/http/attributes/attributes.go @@ -0,0 +1,74 @@ +package attributes + +import ( + "context" + "errors" + "net/http" +) + +const contextKey = "psr:attributes" + +type attrs map[string]interface{} + +func (v attrs) get(key string) interface{} { + if v == nil { + return "" + } + + return v[key] +} + +func (v attrs) set(key string, value interface{}) { + v[key] = value +} + +func (v attrs) del(key string) { + delete(v, key) +} + +// 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{})) +} + +// All returns all context attributes. +func All(r *http.Request) map[string]interface{} { + v := r.Context().Value(contextKey) + if v == nil { + return attrs{} + } + + return v.(attrs) +} + +// 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) + if v == nil { + return nil + } + + return v.(attrs).get(key) +} + +// 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) + if v == nil { + return errors.New("unable to find `psr:attributes` context key") + } + + v.(attrs).set(key, value) + return nil +} + +// Delete deletes values associated with attribute key. +func (v attrs) Delete(key string) { + if v == nil { + return + } + + v.del(key) +} diff --git a/service/http/attributes/attributes_test.go b/service/http/attributes/attributes_test.go new file mode 100644 index 00000000..a71d6542 --- /dev/null +++ b/service/http/attributes/attributes_test.go @@ -0,0 +1,67 @@ +package attributes + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +func TestAllAttributes(t *testing.T) { + r := &http.Request{} + r = Init(r) + + Set(r, "key", "value") + + assert.Equal(t, All(r), map[string]interface{}{ + "key": "value", + }) +} + +func TestAllAttributesNone(t *testing.T) { + r := &http.Request{} + r = Init(r) + + assert.Equal(t, All(r), map[string]interface{}{}) +} + +func TestAllAttributesNone2(t *testing.T) { + r := &http.Request{} + + assert.Equal(t, All(r), map[string]interface{}{}) +} + +func TestGetAttribute(t *testing.T) { + r := &http.Request{} + r = Init(r) + + Set(r, "key", "value") + assert.Equal(t, Get(r, "key"), "value") +} + +func TestGetAttributeNone(t *testing.T) { + r := &http.Request{} + r = Init(r) + + assert.Equal(t, Get(r, "key"), nil) +} + +func TestGetAttributeNone2(t *testing.T) { + r := &http.Request{} + + assert.Equal(t, Get(r, "key"), nil) +} + +func TestSetAttribute(t *testing.T) { + r := &http.Request{} + r = Init(r) + + Set(r, "key", "value") + assert.Equal(t, Get(r, "key"), "value") +} + +func TestSetAttributeNone(t *testing.T) { + r := &http.Request{} + + Set(r, "key", "value") + assert.Equal(t, Get(r, "key"), nil) +} diff --git a/service/http/attributes_test.go b/service/http/attributes_test.go deleted file mode 100644 index aeb7fe74..00000000 --- a/service/http/attributes_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package http - -import ( - "testing" - "net/http" - "github.com/stretchr/testify/assert" -) - -func TestAllAttributes(t *testing.T) { - r := &http.Request{} - r = InitAttributes(r) - - SetAttribute(r, "key", "value") - - assert.Equal(t, AllAttributes(r), map[string]interface{}{ - "key": "value", - }) -} - -func TestAllAttributesNone(t *testing.T) { - r := &http.Request{} - r = InitAttributes(r) - - assert.Equal(t, AllAttributes(r), map[string]interface{}{}) -} - -func TestAllAttributesNone2(t *testing.T) { - r := &http.Request{} - - assert.Equal(t, AllAttributes(r), map[string]interface{}{}) -} - -func TestGetAttribute(t *testing.T) { - r := &http.Request{} - r = InitAttributes(r) - - SetAttribute(r, "key", "value") - assert.Equal(t, GetAttribute(r, "key"), "value") -} - -func TestGetAttributeNone(t *testing.T) { - r := &http.Request{} - r = InitAttributes(r) - - assert.Equal(t, GetAttribute(r, "key"), nil) -} - -func TestGetAttributeNone2(t *testing.T) { - r := &http.Request{} - - assert.Equal(t, GetAttribute(r, "key"), nil) -} - -func TestSetAttribute(t *testing.T) { - r := &http.Request{} - r = InitAttributes(r) - - SetAttribute(r, "key", "value") - assert.Equal(t, GetAttribute(r, "key"), "value") -} - -func TestSetAttributeNone(t *testing.T) { - r := &http.Request{} - - SetAttribute(r, "key", "value") - assert.Equal(t, GetAttribute(r, "key"), nil) -}
\ No newline at end of file diff --git a/service/http/config.go b/service/http/config.go index 19a2e71d..20a247fb 100644 --- a/service/http/config.go +++ b/service/http/config.go @@ -3,7 +3,9 @@ package http import ( "errors" "github.com/spiral/roadrunner" + "github.com/spiral/roadrunner/service" "strings" + "time" ) // Config configures RoadRunner HTTP server. @@ -24,25 +26,54 @@ type Config struct { Workers *roadrunner.ServerConfig } +// Hydrate must populate Config values using given Config source. Must return error if Config is not valid. +func (c *Config) Hydrate(cfg service.Config) error { + if err := cfg.Unmarshal(c); err != nil { + return err + } + + if err := c.Valid(); err != nil { + return err + } + + if c.Workers.Relay == "" { + c.Workers.Relay = "pipes" + } + + if c.Workers.RelayTimeout < time.Microsecond { + c.Workers.RelayTimeout = time.Second * time.Duration(c.Workers.RelayTimeout.Nanoseconds()) + } + + if c.Workers.Pool.AllocateTimeout < time.Microsecond { + c.Workers.Pool.AllocateTimeout = time.Second * time.Duration(c.Workers.Pool.AllocateTimeout.Nanoseconds()) + } + + if c.Workers.Pool.DestroyTimeout < time.Microsecond { + c.Workers.Pool.DestroyTimeout = time.Second * time.Duration(c.Workers.Pool.DestroyTimeout.Nanoseconds()) + } + + return nil +} + // Valid validates the configuration. -func (cfg *Config) Valid() error { - if cfg.Uploads == nil { +func (c *Config) Valid() error { + if c.Uploads == nil { return errors.New("mailformed uploads config") } - if cfg.Workers == nil { + if c.Workers == nil { return errors.New("mailformed workers config") } - if cfg.Workers.Pool == nil { + if c.Workers.Pool == nil { return errors.New("mailformed workers config (pool config is missing)") } - if err := cfg.Workers.Pool.Valid(); err != nil { + if err := c.Workers.Pool.Valid(); err != nil { return err } - if !strings.Contains(cfg.Address, ":") { + if !strings.Contains(c.Address, ":") { return errors.New("mailformed server address") } diff --git a/service/http/config_test.go b/service/http/config_test.go index cb804f4a..2e3fe731 100644 --- a/service/http/config_test.go +++ b/service/http/config_test.go @@ -1,13 +1,34 @@ package http import ( + "encoding/json" "github.com/spiral/roadrunner" + "github.com/spiral/roadrunner/service" "github.com/stretchr/testify/assert" "os" "testing" "time" ) +type mockCfg struct{ cfg string } + +func (cfg *mockCfg) Get(name string) service.Config { return nil } +func (cfg *mockCfg) Unmarshal(out interface{}) error { return json.Unmarshal([]byte(cfg.cfg), out) } + +func Test_Config_Hydrate_Error1(t *testing.T) { + cfg := &mockCfg{`{"enable": true}`} + c := &Config{} + + assert.Error(t, c.Hydrate(cfg)) +} + +func Test_Config_Hydrate_Error2(t *testing.T) { + cfg := &mockCfg{`{"dir": "/dir/"`} + c := &Config{} + + assert.Error(t, c.Hydrate(cfg)) +} + func Test_Config_Valid(t *testing.T) { cfg := &Config{ Enable: true, diff --git a/service/http/handler.go b/service/http/handler.go index 6f2617b1..9e67e5b4 100644 --- a/service/http/handler.go +++ b/service/http/handler.go @@ -9,26 +9,29 @@ import ( ) const ( - // EventResponse thrown after the request been processed. See Event as payload. + // EventResponse thrown after the request been processed. See ErrorEvent as payload. EventResponse = iota + 500 // EventError thrown on any non job error provided by road runner server. EventError ) -// Event represents singular http response event. -type Event struct { - // Method of the request. - Method string +// ErrorEvent represents singular http error event. +type ErrorEvent struct { + // Request contains client request, must not be stored. + Request *http.Request - // URI requested by the client. - URI string + // Error - associated error, if any. + Error error +} - // Status is response status. - Status int +// ResponseEvent represents singular http response event. +type ResponseEvent struct { + // Request contains client request, must not be stored. + Request *Request - // Associated error, if any. - Error error + // Response contains service response. + Response *Response } // Handler serves http connections to underlying PHP application using PSR-7 protocol. Context will include request headers, @@ -99,7 +102,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // handleError sends error. func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) { - h.throw(EventError, &Event{Method: r.Method, URI: uri(r), Status: 500, Error: err}) + h.throw(EventError, &ErrorEvent{Request: r, Error: err}) w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -107,7 +110,7 @@ func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) // handleResponse triggers response event. func (h *Handler) handleResponse(req *Request, resp *Response) { - h.throw(EventResponse, &Event{Method: req.Method, URI: req.URI, Status: resp.Status}) + h.throw(EventResponse, &ResponseEvent{Request: req, Response: resp}) } // throw invokes event srv if any. diff --git a/service/http/request.go b/service/http/request.go index 21566416..6d5cc126 100644 --- a/service/http/request.go +++ b/service/http/request.go @@ -4,7 +4,9 @@ import ( "encoding/json" "fmt" "github.com/spiral/roadrunner" + "github.com/spiral/roadrunner/service/http/attributes" "io/ioutil" + "net" "net/http" "net/url" "strings" @@ -20,6 +22,9 @@ const ( // Request maps net/http requests to PSR7 compatible structure and managed state of temporary uploaded files. type Request struct { + // RemoteAddr contains ip address of client, make sure to check X-Real-Ip and X-Forwarded-For for real client address. + RemoteAddr string `json:"remoteAddr"` + // Protocol includes HTTP protocol version. Protocol string `json:"protocol"` @@ -60,7 +65,14 @@ func NewRequest(r *http.Request, cfg *UploadsConfig) (req *Request, err error) { Headers: r.Header, Cookies: make(map[string]string), RawQuery: r.URL.RawQuery, - Attributes: AllAttributes(r), + Attributes: attributes.All(r), + } + + // otherwise, return remote address as is + if strings.ContainsRune(r.RemoteAddr, ':') { + req.RemoteAddr, _, _ = net.SplitHostPort(r.RemoteAddr) + } else { + req.RemoteAddr = r.RemoteAddr } for _, c := range r.Cookies() { diff --git a/service/http/service.go b/service/http/service.go index 710cd60c..f7fdf2ab 100644 --- a/service/http/service.go +++ b/service/http/service.go @@ -3,7 +3,7 @@ package http import ( "context" "github.com/spiral/roadrunner" - "github.com/spiral/roadrunner/service" + "github.com/spiral/roadrunner/service/http/attributes" "github.com/spiral/roadrunner/service/rpc" "net/http" "sync" @@ -41,28 +41,14 @@ func (s *Service) AddListener(l func(event int, ctx interface{})) { // Init must return configure svc and return true if svc hasStatus enabled. Must return error in case of // misconfiguration. Services must not be used without proper configuration pushed first. -func (s *Service) Init(cfg service.Config, c service.Container) (bool, error) { - config := &Config{} - - if err := cfg.Unmarshal(config); err != nil { - return false, err - } - - if !config.Enable { +func (s *Service) Init(cfg *Config, r *rpc.Service) (bool, error) { + if !cfg.Enable { return false, nil } - if err := config.Valid(); err != nil { - return false, err - } - - s.cfg = config - - // registering http RPC interface - if r, ok := c.Get(rpc.ID); ok >= service.StatusConfigured { - if h, ok := r.(*rpc.Service); ok { - h.Register(ID, &rpcServer{s}) - } + s.cfg = cfg + if r != nil { + r.Register(ID, &rpcServer{s}) } return true, nil @@ -113,16 +99,17 @@ func (s *Service) Stop() { // middleware handles connection using set of mdws and rr PSR-7 server. func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { - r = InitAttributes(r) + r = attributes.Init(r) + // chaining middlewares f := s.srv.ServeHTTP for _, m := range s.mdws { f = m(f) } - f(w, r) } +// listener handles service, server and pool events. func (s *Service) listener(event int, ctx interface{}) { for _, l := range s.lsns { l(event, ctx) diff --git a/service/http/service_test.go b/service/http/service_test.go index 50836b4b..b442ae51 100644 --- a/service/http/service_test.go +++ b/service/http/service_test.go @@ -42,7 +42,7 @@ func Test_Service_NoConfig(t *testing.T) { c := service.NewContainer(logger) c.Register(ID, &Service{}) - assert.NoError(t, c.Init(&testCfg{httpCfg: `{}`})) + assert.Error(t, c.Init(&testCfg{httpCfg: `{}`})) s, st := c.Get(ID) assert.NotNil(t, s) @@ -108,7 +108,7 @@ func Test_Service_Configure_Enable(t *testing.T) { s, st := c.Get(ID) assert.NotNil(t, s) - assert.Equal(t, service.StatusConfigured, st) + assert.Equal(t, service.StatusOK, st) } func Test_Service_Echo(t *testing.T) { @@ -139,10 +139,10 @@ func Test_Service_Echo(t *testing.T) { s, st := c.Get(ID) assert.NotNil(t, s) - assert.Equal(t, service.StatusConfigured, st) + assert.Equal(t, service.StatusOK, st) // should do nothing - s.Stop() + s.(*Service).Stop() go func() { c.Serve() }() time.Sleep(time.Millisecond * 100) @@ -191,7 +191,7 @@ func Test_Service_ErrorEcho(t *testing.T) { s, st := c.Get(ID) assert.NotNil(t, s) - assert.Equal(t, service.StatusConfigured, st) + assert.Equal(t, service.StatusOK, st) goterr := make(chan interface{}) s.(*Service).AddListener(func(event int, ctx interface{}) { @@ -251,7 +251,7 @@ func Test_Service_Middleware(t *testing.T) { s, st := c.Get(ID) assert.NotNil(t, s) - assert.Equal(t, service.StatusConfigured, st) + assert.Equal(t, service.StatusOK, st) s.(*Service).AddMiddleware(func(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -325,7 +325,7 @@ func Test_Service_Listener(t *testing.T) { s, st := c.Get(ID) assert.NotNil(t, s) - assert.Equal(t, service.StatusConfigured, st) + assert.Equal(t, service.StatusOK, st) stop := make(chan interface{}) s.(*Service).AddListener(func(event int, ctx interface{}) { |