Browse Source

simplify dependency resolution

Darien Raymond 7 years ago
parent
commit
307aac26b3

+ 1 - 0
app/app.go

@@ -1 +1,2 @@
+// Package app contains feature implementations of V2Ray. The features may be enabled during runtime.
 package app
 package app

+ 2 - 4
app/commander/commander.go

@@ -12,7 +12,6 @@ import (
 	"v2ray.com/core"
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/signal/done"
 	"v2ray.com/core/common/signal/done"
-	"v2ray.com/core/features"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/outbound"
 )
 )
 
 
@@ -31,9 +30,8 @@ func NewCommander(ctx context.Context, config *Config) (*Commander, error) {
 		tag: config.Tag,
 		tag: config.Tag,
 	}
 	}
 
 
-	v := core.MustFromContext(ctx)
-	v.RequireFeatures([]interface{}{outbound.ManagerType()}, func(fs []features.Feature) {
-		c.ohm = fs[0].(outbound.Manager)
+	core.RequireFeatures(ctx, func(om outbound.Manager) {
+		c.ohm = om
 	})
 	})
 
 
 	for _, rawConfig := range config.Service {
 	for _, rawConfig := range config.Service {

+ 10 - 14
app/dispatcher/default.go

@@ -16,7 +16,6 @@ import (
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/session"
 	"v2ray.com/core/common/session"
 	"v2ray.com/core/common/vio"
 	"v2ray.com/core/common/vio"
-	"v2ray.com/core/features"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/policy"
 	"v2ray.com/core/features/policy"
 	"v2ray.com/core/features/routing"
 	"v2ray.com/core/features/routing"
@@ -94,23 +93,20 @@ type DefaultDispatcher struct {
 func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) {
 func NewDefaultDispatcher(ctx context.Context, config *Config) (*DefaultDispatcher, error) {
 	d := &DefaultDispatcher{}
 	d := &DefaultDispatcher{}
 
 
-	v := core.MustFromContext(ctx)
-	v.RequireFeatures([]interface{}{outbound.ManagerType(), routing.RouterType(), policy.ManagerType()}, func(fs []features.Feature) {
-		d.ohm = fs[0].(outbound.Manager)
-		d.router = fs[1].(routing.Router)
-		d.policy = fs[2].(policy.Manager)
-	})
-	v.RequireFeatures([]interface{}{core.ServerType()}, func([]features.Feature) {
-		f := v.GetFeature(stats.ManagerType())
-		if f == nil {
-			return
-		}
-		d.stats = f.(stats.Manager)
-	})
+	core.RequireFeatures(ctx, d.Init)
 
 
 	return d, nil
 	return d, nil
 }
 }
 
 
+// Init initializes DefaultDispatcher.
+// This method is visible for testing purpose.
+func (d *DefaultDispatcher) Init(om outbound.Manager, router routing.Router, pm policy.Manager, sm stats.Manager) {
+	d.ohm = om
+	d.router = router
+	d.policy = pm
+	d.stats = sm
+}
+
 // Type implements common.HasType.
 // Type implements common.HasType.
 func (*DefaultDispatcher) Type() interface{} {
 func (*DefaultDispatcher) Type() interface{} {
 	return routing.DispatcherType()
 	return routing.DispatcherType()

+ 6 - 7
app/dns/server.go

@@ -7,14 +7,13 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"v2ray.com/core/features/routing"
-
 	"v2ray.com/core"
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/strmatcher"
 	"v2ray.com/core/common/strmatcher"
 	"v2ray.com/core/features"
 	"v2ray.com/core/features"
 	"v2ray.com/core/features/dns"
 	"v2ray.com/core/features/dns"
+	"v2ray.com/core/features/routing"
 )
 )
 
 
 type Server struct {
 type Server struct {
@@ -43,8 +42,6 @@ func New(ctx context.Context, config *Config) (*Server, error) {
 	}
 	}
 	server.hosts = hosts
 	server.hosts = hosts
 
 
-	v := core.MustFromContext(ctx)
-
 	addNameServer := func(endpoint *net.Endpoint) int {
 	addNameServer := func(endpoint *net.Endpoint) int {
 		address := endpoint.Address.AsAddress()
 		address := endpoint.Address.AsAddress()
 		if address.Family().IsDomain() && address.Domain() == "localhost" {
 		if address.Family().IsDomain() && address.Domain() == "localhost" {
@@ -57,9 +54,9 @@ func New(ctx context.Context, config *Config) (*Server, error) {
 			if dest.Network == net.Network_UDP {
 			if dest.Network == net.Network_UDP {
 				idx := len(server.servers)
 				idx := len(server.servers)
 				server.servers = append(server.servers, nil)
 				server.servers = append(server.servers, nil)
-				v.RequireFeatures([]interface{}{routing.DispatcherType()}, func(fs []features.Feature) {
-					dispatcher := fs[0].(routing.Dispatcher)
-					server.servers[idx] = NewClassicNameServer(dest, dispatcher, server.clientIP)
+
+				core.RequireFeatures(ctx, func(d routing.Dispatcher) {
+					server.servers[idx] = NewClassicNameServer(dest, d, server.clientIP)
 				})
 				})
 			}
 			}
 		}
 		}
@@ -102,6 +99,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
 	return server, nil
 	return server, nil
 }
 }
 
 
+// Type implements common.HasType.
 func (*Server) Type() interface{} {
 func (*Server) Type() interface{} {
 	return dns.ClientType()
 	return dns.ClientType()
 }
 }
@@ -123,6 +121,7 @@ func (s *Server) queryIPTimeout(server NameServerInterface, domain string) ([]ne
 	return ips, err
 	return ips, err
 }
 }
 
 
+// LookupIP implements dns.Client.
 func (s *Server) LookupIP(domain string) ([]net.IP, error) {
 func (s *Server) LookupIP(domain string) ([]net.IP, error) {
 	if ip := s.hosts.LookupIP(domain); len(ip) > 0 {
 	if ip := s.hosts.LookupIP(domain); len(ip) > 0 {
 		return ip, nil
 		return ip, nil

+ 3 - 4
app/proxyman/command/command.go

@@ -7,7 +7,6 @@ import (
 
 
 	"v2ray.com/core"
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
-	"v2ray.com/core/features"
 	"v2ray.com/core/features/inbound"
 	"v2ray.com/core/features/inbound"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
@@ -132,9 +131,9 @@ func (s *service) Register(server *grpc.Server) {
 	hs := &handlerServer{
 	hs := &handlerServer{
 		s: s.v,
 		s: s.v,
 	}
 	}
-	s.v.RequireFeatures([]interface{}{inbound.ManagerType(), outbound.ManagerType()}, func(fs []features.Feature) {
-		hs.ihm = fs[0].(inbound.Manager)
-		hs.ohm = fs[1].(outbound.Manager)
+	s.v.RequireFeatures(func(im inbound.Manager, om outbound.Manager) {
+		hs.ihm = im
+		hs.ohm = om
 	})
 	})
 	RegisterHandlerServiceServer(server, hs)
 	RegisterHandlerServiceServer(server, hs)
 }
 }

+ 2 - 5
app/proxyman/mux/mux.go

@@ -8,8 +8,6 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"v2ray.com/core/features"
-
 	"v2ray.com/core"
 	"v2ray.com/core"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/app/proxyman"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
@@ -307,9 +305,8 @@ type Server struct {
 // NewServer creates a new mux.Server.
 // NewServer creates a new mux.Server.
 func NewServer(ctx context.Context) *Server {
 func NewServer(ctx context.Context) *Server {
 	s := &Server{}
 	s := &Server{}
-	v := core.MustFromContext(ctx)
-	v.RequireFeatures([]interface{}{routing.DispatcherType()}, func(fs []features.Feature) {
-		s.dispatcher = fs[0].(routing.Dispatcher)
+	core.RequireFeatures(ctx, func(d routing.Dispatcher) {
+		s.dispatcher = d
 	})
 	})
 	return s
 	return s
 }
 }

+ 3 - 4
app/router/router.go

@@ -9,7 +9,6 @@ import (
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/session"
 	"v2ray.com/core/common/session"
-	"v2ray.com/core/features"
 	"v2ray.com/core/features/dns"
 	"v2ray.com/core/features/dns"
 	"v2ray.com/core/features/routing"
 	"v2ray.com/core/features/routing"
 	"v2ray.com/core/proxy"
 	"v2ray.com/core/proxy"
@@ -38,10 +37,10 @@ func NewRouter(ctx context.Context, config *Config) (*Router, error) {
 		r.rules[idx].Condition = cond
 		r.rules[idx].Condition = cond
 	}
 	}
 
 
-	v := core.MustFromContext(ctx)
-	v.RequireFeatures([]interface{}{dns.ClientType()}, func(fs []features.Feature) {
-		r.dns = fs[0].(dns.Client)
+	core.RequireFeatures(ctx, func(d dns.Client) {
+		r.dns = d
 	})
 	})
+
 	return r, nil
 	return r, nil
 }
 }
 
 

+ 9 - 5
app/stats/command/command.go

@@ -75,17 +75,21 @@ func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest
 }
 }
 
 
 type service struct {
 type service struct {
-	v *core.Instance
+	statsManager feature_stats.Manager
 }
 }
 
 
 func (s *service) Register(server *grpc.Server) {
 func (s *service) Register(server *grpc.Server) {
-	f := s.v.GetFeature(feature_stats.ManagerType())
-	RegisterStatsServiceServer(server, NewStatsServer(f.(feature_stats.Manager)))
+	RegisterStatsServiceServer(server, NewStatsServer(s.statsManager))
 }
 }
 
 
 func init() {
 func init() {
 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
 	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
-		s := core.MustFromContext(ctx)
-		return &service{v: s}, nil
+		s := new(service)
+
+		core.RequireFeatures(ctx, func(sm feature_stats.Manager) {
+			s.statsManager = sm
+		})
+
+		return s, nil
 	}))
 	}))
 }
 }

+ 6 - 0
features/stats/errors.generated.go

@@ -0,0 +1,6 @@
+package stats
+
+import "v2ray.com/core/common/errors"
+
+type errPathObjHolder struct {}
+func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) }

+ 26 - 0
features/stats/stats.go

@@ -1,5 +1,7 @@
 package stats
 package stats
 
 
+//go:generate errorgen
+
 import "v2ray.com/core/features"
 import "v2ray.com/core/features"
 
 
 // Counter is the interface for stats counters.
 // Counter is the interface for stats counters.
@@ -36,3 +38,27 @@ func GetOrRegisterCounter(m Manager, name string) (Counter, error) {
 func ManagerType() interface{} {
 func ManagerType() interface{} {
 	return (*Manager)(nil)
 	return (*Manager)(nil)
 }
 }
+
+// NoopManager is an implementation of Manager, which doesn't has actual functionalities.
+type NoopManager struct{}
+
+// Type implements common.HasType.
+func (NoopManager) Type() interface{} {
+	return ManagerType()
+}
+
+// RegisterCounter implements Manager.
+func (NoopManager) RegisterCounter(string) (Counter, error) {
+	return nil, newError("not implemented")
+}
+
+// GetCounter implements Manager.
+func (NoopManager) GetCounter(string) Counter {
+	return nil
+}
+
+// Start implements common.Runnable.
+func (NoopManager) Start() error { return nil }
+
+// Close implements common.Closable.
+func (NoopManager) Close() error { return nil }

+ 3 - 6
proxy/freedom/freedom.go

@@ -6,8 +6,6 @@ import (
 	"context"
 	"context"
 	"time"
 	"time"
 
 
-	"v2ray.com/core/features"
-
 	"v2ray.com/core"
 	"v2ray.com/core"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/buf"
@@ -37,10 +35,9 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 		config: *config,
 		config: *config,
 	}
 	}
 
 
-	v := core.MustFromContext(ctx)
-	v.RequireFeatures([]interface{}{policy.ManagerType(), dns.ClientType()}, func(fs []features.Feature) {
-		f.policyManager = fs[0].(policy.Manager)
-		f.dns = fs[1].(dns.Client)
+	core.RequireFeatures(ctx, func(pm policy.Manager, d dns.Client) {
+		f.policyManager = pm
+		f.dns = d
 	})
 	})
 
 
 	return f, nil
 	return f, nil

+ 49 - 7
v2ray.go

@@ -2,6 +2,7 @@ package core
 
 
 import (
 import (
 	"context"
 	"context"
+	"reflect"
 	"sync"
 	"sync"
 
 
 	"v2ray.com/core/common"
 	"v2ray.com/core/common"
@@ -12,6 +13,7 @@ import (
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/outbound"
 	"v2ray.com/core/features/policy"
 	"v2ray.com/core/features/policy"
 	"v2ray.com/core/features/routing"
 	"v2ray.com/core/features/routing"
+	"v2ray.com/core/features/stats"
 )
 )
 
 
 // Server is an instance of V2Ray. At any time, there must be at most one Server instance running.
 // Server is an instance of V2Ray. At any time, there must be at most one Server instance running.
@@ -25,13 +27,13 @@ func ServerType() interface{} {
 }
 }
 
 
 type resolution struct {
 type resolution struct {
-	deps     []interface{}
-	callback func([]features.Feature)
+	deps     []reflect.Type
+	callback interface{}
 }
 }
 
 
-func getFeature(allFeatures []features.Feature, t interface{}) features.Feature {
+func getFeature(allFeatures []features.Feature, t reflect.Type) features.Feature {
 	for _, f := range allFeatures {
 	for _, f := range allFeatures {
-		if f.Type() == t {
+		if reflect.TypeOf(f.Type()) == t {
 			return f
 			return f
 		}
 		}
 	}
 	}
@@ -48,7 +50,25 @@ func (r *resolution) resolve(allFeatures []features.Feature) bool {
 		fs = append(fs, f)
 		fs = append(fs, f)
 	}
 	}
 
 
-	r.callback(fs)
+	callback := reflect.ValueOf(r.callback)
+	var input []reflect.Value
+	callbackType := callback.Type()
+	for i := 0; i < callbackType.NumIn(); i++ {
+		pt := callbackType.In(i)
+		for _, f := range fs {
+			if reflect.TypeOf(f).AssignableTo(pt) {
+				input = append(input, reflect.ValueOf(f))
+				break
+			}
+		}
+	}
+
+	if len(input) != callbackType.NumIn() {
+		panic("Can't get all input parameters")
+	}
+
+	callback.Call(input)
+
 	return true
 	return true
 }
 }
 
 
@@ -112,6 +132,13 @@ func addOutboundHandlers(server *Instance, configs []*OutboundHandlerConfig) err
 	return nil
 	return nil
 }
 }
 
 
+// RequireFeatures is a helper function to require features from Instance in context.
+// See Instance.RequireFeatures for more information.
+func RequireFeatures(ctx context.Context, callback interface{}) {
+	v := MustFromContext(ctx)
+	v.RequireFeatures(callback)
+}
+
 // New returns a new V2Ray instance based on given configuration.
 // New returns a new V2Ray instance based on given configuration.
 // The instance is not started at this point.
 // The instance is not started at this point.
 // To ensure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional.
 // To ensure V2Ray instance works properly, the config must contain one Dispatcher, one InboundHandlerManager and one OutboundHandlerManager. Other features are optional.
@@ -151,6 +178,10 @@ func New(config *Config) (*Instance, error) {
 		server.AddFeature(routing.DefaultRouter{})
 		server.AddFeature(routing.DefaultRouter{})
 	}
 	}
 
 
+	if server.GetFeature(stats.ManagerType()) == nil {
+		server.AddFeature(stats.NoopManager{})
+	}
+
 	// Add an empty instance at the end, for optional feature requirement.
 	// Add an empty instance at the end, for optional feature requirement.
 	server.AddFeature(&Instance{})
 	server.AddFeature(&Instance{})
 
 
@@ -195,7 +226,18 @@ func (s *Instance) Close() error {
 }
 }
 
 
 // RequireFeatures registers a callback, which will be called when all dependent features are registered.
 // RequireFeatures registers a callback, which will be called when all dependent features are registered.
-func (s *Instance) RequireFeatures(featureTypes []interface{}, callback func([]features.Feature)) {
+// The callback must be a func(). All its parameters must be features.Feature.
+func (s *Instance) RequireFeatures(callback interface{}) {
+	callbackType := reflect.TypeOf(callback)
+	if callbackType.Kind() != reflect.Func {
+		panic("not a function")
+	}
+
+	var featureTypes []reflect.Type
+	for i := 0; i < callbackType.NumIn(); i++ {
+		featureTypes = append(featureTypes, reflect.PtrTo(callbackType.In(i)))
+	}
+
 	r := resolution{
 	r := resolution{
 		deps:     featureTypes,
 		deps:     featureTypes,
 		callback: callback,
 		callback: callback,
@@ -236,7 +278,7 @@ func (s *Instance) AddFeature(feature features.Feature) {
 
 
 // GetFeature returns a feature of the given type, or nil if such feature is not registered.
 // GetFeature returns a feature of the given type, or nil if such feature is not registered.
 func (s *Instance) GetFeature(featureType interface{}) features.Feature {
 func (s *Instance) GetFeature(featureType interface{}) features.Feature {
-	return getFeature(s.features, featureType)
+	return getFeature(s.features, reflect.TypeOf(featureType))
 }
 }
 
 
 // Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown.
 // Start starts the V2Ray instance, including all registered features. When Start returns error, the state of the instance is unknown.

+ 15 - 0
v2ray_test.go

@@ -13,12 +13,27 @@ import (
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/common/uuid"
 	"v2ray.com/core/common/uuid"
+	"v2ray.com/core/features/dns"
 	_ "v2ray.com/core/main/distro/all"
 	_ "v2ray.com/core/main/distro/all"
 	"v2ray.com/core/proxy/dokodemo"
 	"v2ray.com/core/proxy/dokodemo"
 	"v2ray.com/core/proxy/vmess"
 	"v2ray.com/core/proxy/vmess"
 	"v2ray.com/core/proxy/vmess/outbound"
 	"v2ray.com/core/proxy/vmess/outbound"
 )
 )
 
 
+func TestV2RayDependency(t *testing.T) {
+	instance := new(Instance)
+
+	wait := make(chan bool, 1)
+	instance.RequireFeatures(func(d dns.Client) {
+		if d == nil {
+			t.Error("expected dns client fulfilled, but actually nil")
+		}
+		wait <- true
+	})
+	instance.AddFeature(dns.LocalClient{})
+	<-wait
+}
+
 func TestV2RayClose(t *testing.T) {
 func TestV2RayClose(t *testing.T) {
 	port := net.Port(dice.RollUint16())
 	port := net.Port(dice.RollUint16())
 	userId := uuid.New()
 	userId := uuid.New()