summaryrefslogtreecommitdiff
path: root/service/http
diff options
context:
space:
mode:
Diffstat (limited to 'service/http')
-rw-r--r--service/http/attributes.go69
-rw-r--r--service/http/attributes/attributes.go74
-rw-r--r--service/http/attributes/attributes_test.go67
-rw-r--r--service/http/attributes_test.go67
-rw-r--r--service/http/config.go43
-rw-r--r--service/http/config_test.go21
-rw-r--r--service/http/handler.go29
-rw-r--r--service/http/request.go14
-rw-r--r--service/http/service.go31
-rw-r--r--service/http/service_test.go14
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{}) {