diff options
author | Wolfy-J <[email protected]> | 2018-07-08 13:06:05 -0700 |
---|---|---|
committer | Wolfy-J <[email protected]> | 2018-07-08 13:06:05 -0700 |
commit | 29c9bf94350e86ec96f5ce5eeb476dfcd57302cd (patch) | |
tree | 9f59af6446958d144b7de91b5005a3727dc90661 | |
parent | 3c3a7801100f29c99a5e446646c818bf16ccd5f0 (diff) |
dependency injection and lighter service Init methods.
-rw-r--r-- | cmd/rr/http/reset.go | 2 | ||||
-rw-r--r-- | cmd/rr/http/workers.go | 2 | ||||
-rw-r--r-- | service/container.go | 79 | ||||
-rw-r--r-- | service/container_test.go | 4 | ||||
-rw-r--r-- | service/entry.go (renamed from service/service.go) | 29 | ||||
-rw-r--r-- | service/http/config.go | 22 | ||||
-rw-r--r-- | service/http/service.go | 25 | ||||
-rw-r--r-- | service/http/service_test.go | 14 | ||||
-rw-r--r-- | service/injector.go | 112 | ||||
-rw-r--r-- | service/injector_test.go | 24 | ||||
-rw-r--r-- | service/rpc/config.go | 38 | ||||
-rw-r--r-- | service/rpc/config_test.go | 38 | ||||
-rw-r--r-- | service/rpc/service.go | 30 | ||||
-rw-r--r-- | service/rpc/service_test.go | 25 | ||||
-rw-r--r-- | service/static/config.go | 34 | ||||
-rw-r--r-- | service/static/service.go | 30 |
16 files changed, 326 insertions, 182 deletions
diff --git a/cmd/rr/http/reset.go b/cmd/rr/http/reset.go index 431b7e88..3bc089ec 100644 --- a/cmd/rr/http/reset.go +++ b/cmd/rr/http/reset.go @@ -39,7 +39,7 @@ func init() { func reloadHandler(cmd *cobra.Command, args []string) error { svc, st := rr.Container.Get(rpc.ID) - if st < service.StatusConfigured { + if st < service.StatusOK { return errors.New("RPC service is not configured") } diff --git a/cmd/rr/http/workers.go b/cmd/rr/http/workers.go index e697816f..b03c273f 100644 --- a/cmd/rr/http/workers.go +++ b/cmd/rr/http/workers.go @@ -74,7 +74,7 @@ func workersHandler(cmd *cobra.Command, args []string) (err error) { }() svc, st := rr.Container.Get(rrpc.ID) - if st < service.StatusConfigured { + if st < service.StatusOK { return errors.New("RPC service is not configured") } diff --git a/service/container.go b/service/container.go index 12c5a4a1..a003b7e3 100644 --- a/service/container.go +++ b/service/container.go @@ -7,20 +7,10 @@ import ( "sync" ) -// Config provides ability to slice configuration sections and unmarshal configuration data into -// given structure. -type Config interface { - // get nested config section (sub-map), returns nil if section not found. - Get(service string) Config - - // Unmarshal unmarshal config data into given struct. - Unmarshal(out interface{}) error -} - // Container controls all internal RR services and provides plugin based system. type Container interface { // Register add new service to the container under given name. - Register(name string, service Service) + Register(name string, service interface{}) // Reconfigure configures all underlying services with given configuration. Init(cfg Config) error @@ -30,7 +20,7 @@ type Container interface { // get returns svc instance by it's name or nil if svc not found. Method returns current service status // as second value. - Get(service string) (svc Service, status int) + Get(service string) (svc interface{}, status int) // Serve all configured services. Non blocking. Serve() error @@ -39,6 +29,36 @@ type Container interface { Stop() } +// Service can serve. Service can provide Init method which must return (bool, error) signature and might accept +// other services and/or configs as dependency. Container can be requested as well. Config can be requested in a form +// of service.Config or pointer to service specific config struct (automatically unmarshalled), config argument must +// implement service.HydrateConfig. +type Service interface { + // Serve serves. + Serve() error + + // Stop stops the service. + Stop() +} + +// Config provides ability to slice configuration sections and unmarshal configuration data into +// given structure. +type Config interface { + // get nested config section (sub-map), returns nil if section not found. + Get(service string) Config + + // Unmarshal unmarshal config data into given struct. + Unmarshal(out interface{}) error +} + +// HydrateConfig provides ability to automatically hydrate config with values using +// service.Config as the source. +type HydrateConfig interface { + // Hydrate must populate config values using given config source. + // Must return error if config is not valid. + Hydrate(cfg Config) error +} + type container struct { log logrus.FieldLogger mu sync.Mutex @@ -54,7 +74,7 @@ func NewContainer(log logrus.FieldLogger) Container { } // Register add new service to the container under given name. -func (c *container) Register(name string, service Service) { +func (c *container) Register(name string, service interface{}) { c.mu.Lock() defer c.mu.Unlock() @@ -82,7 +102,7 @@ func (c *container) Has(target string) bool { } // get returns svc instance by it's name or nil if svc not found. -func (c *container) Get(target string) (svc Service, status int) { +func (c *container) Get(target string) (svc interface{}, status int) { c.mu.Lock() defer c.mu.Unlock() @@ -98,27 +118,32 @@ func (c *container) Get(target string) (svc Service, status int) { // Init configures all underlying services with given configuration. func (c *container) Init(cfg Config) error { for _, e := range c.services { - if e.getStatus() >= StatusConfigured { + if e.getStatus() >= StatusOK { return fmt.Errorf("service [%s] has already been configured", e.name) } - segment := cfg.Get(e.name) - if segment == nil { - c.log.Debugf("[%s]: no config has been provided", e.name) - continue - } + // inject service dependencies (todo: move to container) + if ok, err := initService(e.svc, cfg.Get(e.name), c); err != nil { + if err == noConfig { + c.log.Warningf("[%s]: no config has been provided", e.name) + + // unable to meet dependency requirements, skippingF + continue + } - ok, err := e.svc.Init(segment, c) - if err != nil { return errors.Wrap(err, fmt.Sprintf("[%s]", e.name)) } else if ok { - e.setStatus(StatusConfigured) + e.setStatus(StatusOK) + c.log.Debugf("[%s]: initiated", e.name) + } else { + c.log.Debugf("[%s]: disabled", e.name) } } return nil } +//todo: refactor ???? // Serve all configured services. Non blocking. func (c *container) Serve() error { var ( @@ -127,7 +152,7 @@ func (c *container) Serve() error { ) for _, e := range c.services { - if e.hasStatus(StatusConfigured) { + if e.hasStatus(StatusOK) && e.canServe() { numServing++ } else { continue @@ -138,7 +163,7 @@ func (c *container) Serve() error { e.setStatus(StatusServing) defer e.setStatus(StatusStopped) - if err := e.svc.Serve(); err != nil { + if err := e.svc.(Service).Serve(); err != nil { c.log.Errorf("[%s]: %s", e.name, err) done <- errors.Wrap(err, fmt.Sprintf("[%s]", e.name)) } else { @@ -167,10 +192,12 @@ func (c *container) Serve() error { // Stop sends stop command to all running services. func (c *container) Stop() { + c.log.Debugf("received stop command") for _, e := range c.services { if e.hasStatus(StatusServing) { - e.svc.Stop() + e.svc.(Service).Stop() e.setStatus(StatusStopped) + c.log.Debugf("[%s]: stopped", e.name) } } diff --git a/service/container_test.go b/service/container_test.go index 3092be78..bf95cbd4 100644 --- a/service/container_test.go +++ b/service/container_test.go @@ -144,7 +144,7 @@ func TestContainer_Configure(t *testing.T) { s, st := c.Get("test") assert.IsType(t, &testService{}, s) - assert.Equal(t, StatusConfigured, st) + assert.Equal(t, StatusOK, st) } func TestContainer_ConfigureNull(t *testing.T) { @@ -176,7 +176,7 @@ func TestContainer_ConfigureDisabled(t *testing.T) { assert.Equal(t, 1, len(hook.Entries)) assert.NoError(t, c.Init(&testCfg{`{"test":"something"}`})) - assert.Equal(t, 1, len(hook.Entries)) + assert.Equal(t, 2, len(hook.Entries)) s, st := c.Get("test") assert.IsType(t, &testService{}, s) diff --git a/service/service.go b/service/entry.go index 6cd12b51..f2cbac28 100644 --- a/service/service.go +++ b/service/entry.go @@ -1,19 +1,8 @@ package service -import "sync" - -// Service provides high level functionality for road runner modules. -type Service interface { - // Init must return configure service and return true if service hasStatus enabled. Must return error in case of - // misconfiguration. Services must not be used without proper configuration pushed first. - Init(cfg Config, c Container) (enabled bool, err error) - - // Serve serves. - Serve() error - - // Stop stops the service. - Stop() -} +import ( + "sync" +) const ( // StatusUndefined when service bus can not find the service. @@ -22,8 +11,8 @@ const ( // StatusRegistered hasStatus setStatus when service has been registered in container. StatusRegistered - // StatusConfigured hasStatus setStatus when service has been properly configured. - StatusConfigured + // StatusOK hasStatus setStatus when service has been properly configured. + StatusOK // StatusServing hasStatus setStatus when service hasStatus currently done. StatusServing @@ -35,7 +24,7 @@ const ( // entry creates association between service instance and given name. type entry struct { name string - svc Service + svc interface{} mu sync.Mutex status int } @@ -59,3 +48,9 @@ func (e *entry) setStatus(status int) { func (e *entry) hasStatus(status int) bool { return e.getStatus() == status } + +// canServe returns true is service can serve. +func (e *entry) canServe() bool { + _, ok := e.svc.(Service) + return ok +} diff --git a/service/http/config.go b/service/http/config.go index 19a2e71d..e46b56cf 100644 --- a/service/http/config.go +++ b/service/http/config.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/spiral/roadrunner" "strings" + "github.com/spiral/roadrunner/service" ) // Config configures RoadRunner HTTP server. @@ -24,25 +25,34 @@ 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 + } + + return c.Valid() +} + // 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/service.go b/service/http/service.go index 7405bf37..30289e3c 100644 --- a/service/http/service.go +++ b/service/http/service.go @@ -3,7 +3,6 @@ package http import ( "context" "github.com/spiral/roadrunner" - "github.com/spiral/roadrunner/service" "github.com/spiral/roadrunner/service/rpc" "net/http" "sync" @@ -42,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 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{}) { diff --git a/service/injector.go b/service/injector.go new file mode 100644 index 00000000..e7dfaa0b --- /dev/null +++ b/service/injector.go @@ -0,0 +1,112 @@ +package service + +import ( + "reflect" + "fmt" +) + +const initMethod = "Init" + +var noConfig = fmt.Errorf("no config has been provided") + +// calls Init method with automatically resolved arguments. +func initService(s interface{}, cfg Config, c *container) (bool, error) { + r := reflect.TypeOf(s) + + m, ok := r.MethodByName(initMethod) + if !ok { + // no Init method is presented, assuming service does not need + // initialization. + return false, nil + } + + if err := verifySignature(m); err != nil { + return false, err + } + + // hydrating + values, err := injectValues(m, s, cfg, c) + if err != nil { + return false, err + } + + // initiating service + out := m.Func.Call(values) + + if out[1].IsNil() { + return out[0].Bool(), nil + } + + return out[0].Bool(), out[1].Interface().(error) +} + +// injectValues returns slice of call arguments for service Init method. +func injectValues(m reflect.Method, s interface{}, cfg Config, c *container) (values []reflect.Value, err error) { + for i := 0; i < m.Type.NumIn(); i ++ { + v := m.Type.In(i) + + switch { + case v.ConvertibleTo(reflect.ValueOf(s).Type()): // service itself + values = append(values, reflect.ValueOf(s)) + + case v.Implements(reflect.TypeOf((*HydrateConfig)(nil)).Elem()): // automatically configured config + if cfg == nil { + // todo: generic value + return nil, noConfig + } + + sc := reflect.New(v.Elem()) + if err := sc.Interface().(HydrateConfig).Hydrate(cfg); err != nil { + return nil, err + } + + values = append(values, sc) + + case v.Implements(reflect.TypeOf((*Config)(nil)).Elem()): // config section + if cfg == nil { + // todo: generic value + return nil, noConfig + } + values = append(values, reflect.ValueOf(cfg)) + + case v.Implements(reflect.TypeOf((*Container)(nil)).Elem()): // container + values = append(values, reflect.ValueOf(c)) + + default: + found := false + + // looking for the service candidate + for _, e := range c.services { + if v.ConvertibleTo(reflect.ValueOf(e.svc).Type()) { + found = true + values = append(values, reflect.ValueOf(e.svc)) + break + } + } + + if !found { + // placeholder (make sure to check inside the method) + values = append(values, reflect.New(v).Elem()) + } + } + } + + return +} + +// verifySignature checks if Init method has valid signature +func verifySignature(m reflect.Method) error { + if m.Type.NumOut() != 2 { + return fmt.Errorf("method Init must have exact 2 return values") + } + + if m.Type.Out(0).Kind() != reflect.Bool { + return fmt.Errorf("first return value of Init method must be bool type") + } + + if !m.Type.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { + return fmt.Errorf("second return value of Init method value must be error type") + } + + return nil +} diff --git a/service/injector_test.go b/service/injector_test.go new file mode 100644 index 00000000..eaf5fa72 --- /dev/null +++ b/service/injector_test.go @@ -0,0 +1,24 @@ +package service + +import ( + "testing" + "github.com/stretchr/testify/assert" + "v/github.com/sirupsen/[email protected]/hooks/test" + "github.com/sirupsen/logrus" +) + +func TestContainer_Init(t *testing.T) { + logger, hook := test.NewNullLogger() + logger.SetLevel(logrus.DebugLevel) + + svc := &testService{ok: true} + + c := NewContainer(logger) + c.Register("test", svc) + c.Register("test2", struct{}{}) + + assert.Equal(t, 2, len(hook.Entries)) + + assert.NoError(t, c.Serve()) + c.Stop() +}
\ No newline at end of file diff --git a/service/rpc/config.go b/service/rpc/config.go index e3168945..0485fdf6 100644 --- a/service/rpc/config.go +++ b/service/rpc/config.go @@ -5,9 +5,11 @@ import ( "net" "strings" "syscall" + "github.com/spiral/roadrunner/service" ) -type config struct { +// Config defines RPC service config. +type Config struct { // Indicates if RPC connection is enabled. Enable bool @@ -15,9 +17,31 @@ type config struct { Listen string } -// listener creates new rpc socket listener. -func (cfg *config) listener() (net.Listener, error) { - dsn := strings.Split(cfg.Listen, "://") +// 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 + } + + return c.Valid() +} + +// Valid returns nil if config is valid. +func (c *Config) Valid() error { + if dsn := strings.Split(c.Listen, "://"); len(dsn) != 2 { + return errors.New("invalid socket DSN (tcp://:6001, unix://rpc.sock)") + } + + if dsn := strings.Split(c.Listen, "://"); len(dsn) != 2 { + return errors.New("invalid socket DSN (tcp://:6001, unix://rpc.sock)") + } + + return nil +} + +// Listener creates new rpc socket Listener. +func (c *Config) Listener() (net.Listener, error) { + dsn := strings.Split(c.Listen, "://") if len(dsn) != 2 { return nil, errors.New("invalid socket DSN (tcp://:6001, unix://rpc.sock)") } @@ -29,9 +53,9 @@ func (cfg *config) listener() (net.Listener, error) { return net.Listen(dsn[0], dsn[1]) } -// dialer creates rpc socket dialer. -func (cfg *config) dialer() (net.Conn, error) { - dsn := strings.Split(cfg.Listen, "://") +// Dialer creates rpc socket Dialer. +func (c *Config) Dialer() (net.Conn, error) { + dsn := strings.Split(c.Listen, "://") if len(dsn) != 2 { return nil, errors.New("invalid socket DSN (tcp://:6001, unix://rpc.sock)") } diff --git a/service/rpc/config_test.go b/service/rpc/config_test.go index a953e30e..87a89a2b 100644 --- a/service/rpc/config_test.go +++ b/service/rpc/config_test.go @@ -6,10 +6,12 @@ import ( "testing" ) +// todo: test hydrate + func TestConfig_Listener(t *testing.T) { - cfg := &config{Listen: "tcp://:18001"} + cfg := &Config{Listen: "tcp://:18001"} - ln, err := cfg.listener() + ln, err := cfg.Listener() assert.NoError(t, err) assert.NotNil(t, ln) defer ln.Close() @@ -23,9 +25,9 @@ func TestConfig_ListenerUnix(t *testing.T) { t.Skip("not supported on " + runtime.GOOS) } - cfg := &config{Listen: "unix://rpc.sock"} + cfg := &Config{Listen: "unix://rpc.sock"} - ln, err := cfg.listener() + ln, err := cfg.Listener() assert.NoError(t, err) assert.NotNil(t, ln) defer ln.Close() @@ -39,28 +41,28 @@ func Test_Config_Error(t *testing.T) { t.Skip("not supported on " + runtime.GOOS) } - cfg := &config{Listen: "uni:unix.sock"} - ln, err := cfg.listener() + cfg := &Config{Listen: "uni:unix.sock"} + ln, err := cfg.Listener() assert.Nil(t, ln) assert.Error(t, err) assert.Equal(t, "invalid socket DSN (tcp://:6001, unix://rpc.sock)", err.Error()) } func Test_Config_ErrorMethod(t *testing.T) { - cfg := &config{Listen: "xinu://unix.sock"} + cfg := &Config{Listen: "xinu://unix.sock"} - ln, err := cfg.listener() + ln, err := cfg.Listener() assert.Nil(t, ln) assert.Error(t, err) } func TestConfig_Dialer(t *testing.T) { - cfg := &config{Listen: "tcp://:18001"} + cfg := &Config{Listen: "tcp://:18001"} - ln, err := cfg.listener() + ln, err := cfg.Listener() defer ln.Close() - conn, err := cfg.dialer() + conn, err := cfg.Dialer() assert.NoError(t, err) assert.NotNil(t, conn) defer conn.Close() @@ -74,12 +76,12 @@ func TestConfig_DialerUnix(t *testing.T) { t.Skip("not supported on " + runtime.GOOS) } - cfg := &config{Listen: "unix://rpc.sock"} + cfg := &Config{Listen: "unix://rpc.sock"} - ln, err := cfg.listener() + ln, err := cfg.Listener() defer ln.Close() - conn, err := cfg.dialer() + conn, err := cfg.Dialer() assert.NoError(t, err) assert.NotNil(t, conn) defer conn.Close() @@ -93,17 +95,17 @@ func Test_Config_DialerError(t *testing.T) { t.Skip("not supported on " + runtime.GOOS) } - cfg := &config{Listen: "uni:unix.sock"} - ln, err := cfg.dialer() + cfg := &Config{Listen: "uni:unix.sock"} + ln, err := cfg.Dialer() assert.Nil(t, ln) assert.Error(t, err) assert.Equal(t, "invalid socket DSN (tcp://:6001, unix://rpc.sock)", err.Error()) } func Test_Config_DialerErrorMethod(t *testing.T) { - cfg := &config{Listen: "xinu://unix.sock"} + cfg := &Config{Listen: "xinu://unix.sock"} - ln, err := cfg.dialer() + ln, err := cfg.Dialer() assert.Nil(t, ln) assert.Error(t, err) } diff --git a/service/rpc/service.go b/service/rpc/service.go index 82f26407..621348e8 100644 --- a/service/rpc/service.go +++ b/service/rpc/service.go @@ -3,7 +3,6 @@ package rpc import ( "errors" "github.com/spiral/goridge" - "github.com/spiral/roadrunner/service" "net/rpc" "sync" ) @@ -13,27 +12,20 @@ const ID = "rpc" // Service is RPC service. type Service struct { - cfg *config - stop chan interface{} - rpc *rpc.Server - + cfg *Config + stop chan interface{} + rpc *rpc.Server mu sync.Mutex serving bool } -// Init must return configure service and return true if service 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, reg service.Container) (enabled bool, err error) { - config := &config{} - if err := cfg.Unmarshal(config); err != nil { - return false, err - } - - if !config.Enable { +// Init rpc service. Must return true if service is enabled. +func (s *Service) Init(cfg *Config) (enabled bool, err error) { + if !cfg.Enable { return false, nil } - s.cfg = config + s.cfg = cfg s.rpc = rpc.NewServer() return true, nil @@ -50,7 +42,7 @@ func (s *Service) Serve() error { s.stop = make(chan interface{}) s.mu.Unlock() - ln, err := s.cfg.listener() + ln, err := s.cfg.Listener() if err != nil { return err } @@ -99,12 +91,12 @@ func (s *Service) Stop() { // - one return value, of type error // It returns an error if the receiver is not an exported type or has // no suitable methods. It also logs the error using package log. -func (s *Service) Register(name string, rcvr interface{}) error { +func (s *Service) Register(name string, svc interface{}) error { if s.rpc == nil { return errors.New("RPC service is not configured") } - return s.rpc.RegisterName(name, rcvr) + return s.rpc.RegisterName(name, svc) } // Client creates new RPC client. @@ -113,7 +105,7 @@ func (s *Service) Client() (*rpc.Client, error) { return nil, errors.New("RPC service is not configured") } - conn, err := s.cfg.dialer() + conn, err := s.cfg.Dialer() if err != nil { return nil, err } diff --git a/service/rpc/service_test.go b/service/rpc/service_test.go index d4734bb5..fc88d38d 100644 --- a/service/rpc/service_test.go +++ b/service/rpc/service_test.go @@ -1,8 +1,6 @@ package rpc import ( - "encoding/json" - "github.com/spiral/roadrunner/service" "github.com/stretchr/testify/assert" "testing" "time" @@ -12,22 +10,9 @@ type testService struct{} func (ts *testService) Echo(msg string, r *string) error { *r = msg; return nil } -type testCfg struct{ cfg string } - -func (cfg *testCfg) Get(name string) service.Config { return nil } -func (cfg *testCfg) Unmarshal(out interface{}) error { return json.Unmarshal([]byte(cfg.cfg), out) } - -func Test_ConfigError(t *testing.T) { - s := &Service{} - ok, err := s.Init(&testCfg{`{"enable":false`}, nil) - - assert.Error(t, err) - assert.False(t, ok) -} - func Test_Disabled(t *testing.T) { s := &Service{} - ok, err := s.Init(&testCfg{`{"enable":false}`}, nil) + ok, err := s.Init(&Config{Enable: false}) assert.NoError(t, err) assert.False(t, ok) @@ -45,7 +30,7 @@ func Test_RegisterNotConfigured(t *testing.T) { func Test_Enabled(t *testing.T) { s := &Service{} - ok, err := s.Init(&testCfg{`{"enable":true, "listen":"tcp://localhost:9008"}`}, nil) + ok, err := s.Init(&Config{Enable: true, Listen: "tcp://localhost:9008"}) assert.NoError(t, err) assert.True(t, ok) @@ -53,7 +38,7 @@ func Test_Enabled(t *testing.T) { func Test_StopNonServing(t *testing.T) { s := &Service{} - ok, err := s.Init(&testCfg{`{"enable":true, "listen":"tcp://localhost:9008"}`}, nil) + ok, err := s.Init(&Config{Enable: true, Listen: "tcp://localhost:9008"}) assert.NoError(t, err) assert.True(t, ok) @@ -62,7 +47,7 @@ func Test_StopNonServing(t *testing.T) { func Test_Serve_Errors(t *testing.T) { s := &Service{} - ok, err := s.Init(&testCfg{`{"enable":true, "listen":"mailformed"}`}, nil) + ok, err := s.Init(&Config{Enable: true, Listen: "mailformed"}) assert.NoError(t, err) assert.True(t, ok) @@ -75,7 +60,7 @@ func Test_Serve_Errors(t *testing.T) { func Test_Serve_Client(t *testing.T) { s := &Service{} - ok, err := s.Init(&testCfg{`{"enable":true, "listen":"tcp://localhost:9018"}`}, nil) + ok, err := s.Init(&Config{Enable: true, Listen: "tcp://localhost:9018"}) assert.NoError(t, err) assert.True(t, ok) diff --git a/service/static/config.go b/service/static/config.go index 1020b8cd..6a2d1206 100644 --- a/service/static/config.go +++ b/service/static/config.go @@ -5,6 +5,7 @@ import ( "os" "path" "strings" + "github.com/spiral/roadrunner/service" ) // Config describes file location and controls access to them. @@ -20,22 +21,18 @@ type Config struct { Forbid []string } -// Forbids must return true if file extension is not allowed for the upload. -func (cfg *Config) Forbids(filename string) bool { - ext := strings.ToLower(path.Ext(filename)) - - for _, v := range cfg.Forbid { - if ext == v { - return true - } +// 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 } - return false + return c.Valid() } -// Valid validates existence of directory. -func (cfg *Config) Valid() error { - st, err := os.Stat(cfg.Dir) +// Valid returns nil if config is valid. +func (c *Config) Valid() error { + st, err := os.Stat(c.Dir) if err != nil { if os.IsNotExist(err) { return errors.New("root directory does not exists") @@ -50,3 +47,16 @@ func (cfg *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 { + ext := strings.ToLower(path.Ext(filename)) + + for _, v := range c.Forbid { + if ext == v { + return true + } + } + + return false +} diff --git a/service/static/service.go b/service/static/service.go index add242e4..968cb594 100644 --- a/service/static/service.go +++ b/service/static/service.go @@ -1,7 +1,6 @@ package static import ( - "github.com/spiral/roadrunner/service" rrttp "github.com/spiral/roadrunner/service/http" "net/http" "path" @@ -22,39 +21,18 @@ type Service struct { // Init must return configure service and return true if service 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) (enabled bool, err error) { - config := &Config{} - if err := cfg.Unmarshal(config); err != nil { - return false, err - } - - if !config.Enable { +func (s *Service) Init(cfg *Config, r *rrttp.Service) (enabled bool, err error) { + if !cfg.Enable || r == nil { return false, nil } - if err := config.Valid(); err != nil { - return false, err - } - - s.cfg = config + s.cfg = cfg s.root = http.Dir(s.cfg.Dir) - - // registering as middleware - if h, ok := c.Get(rrttp.ID); ok >= service.StatusConfigured { - if h, ok := h.(*rrttp.Service); ok { - h.AddMiddleware(s.middleware) - } - } + r.AddMiddleware(s.middleware) return true, nil } -// Serve serves the service. -func (s *Service) Serve() error { return nil } - -// Stop stops the service. -func (s *Service) Stop() {} - // middleware must return true if request/response pair is handled within the middleware. func (s *Service) middleware(f http.HandlerFunc) http.HandlerFunc { // Define the http.HandlerFunc |