Merge pull request #1505 from micro/resover-refactor
Extract Micro Resolver (Namespace)
This commit is contained in:
		| @@ -7,13 +7,15 @@ import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/micro/go-micro/v2/api/handler" | ||||
| 	"github.com/micro/go-micro/v2/api/resolver" | ||||
| 	"github.com/micro/go-micro/v2/api/resolver/vpath" | ||||
| 	"github.com/micro/go-micro/v2/api/router" | ||||
| 	regRouter "github.com/micro/go-micro/v2/api/router/registry" | ||||
| 	"github.com/micro/go-micro/v2/registry" | ||||
| 	"github.com/micro/go-micro/v2/registry/memory" | ||||
| ) | ||||
|  | ||||
| func testHttp(t *testing.T, path, service string) { | ||||
| func testHttp(t *testing.T, path, service, ns string) { | ||||
| 	r := memory.NewRegistry() | ||||
|  | ||||
| 	l, err := net.Listen("tcp", "127.0.0.1:0") | ||||
| @@ -55,6 +57,9 @@ func testHttp(t *testing.T, path, service string) { | ||||
| 	rt := regRouter.NewRouter( | ||||
| 		router.WithHandler("http"), | ||||
| 		router.WithRegistry(r), | ||||
| 		router.WithResolver(vpath.NewResolver( | ||||
| 			resolver.WithNamespace(resolver.StaticNamespace(ns)), | ||||
| 		)), | ||||
| 	) | ||||
|  | ||||
| 	p := NewHandler(handler.WithRouter(rt)) | ||||
| @@ -73,40 +78,50 @@ func testHttp(t *testing.T, path, service string) { | ||||
|  | ||||
| func TestHttpHandler(t *testing.T) { | ||||
| 	testData := []struct { | ||||
| 		path    string | ||||
| 		service string | ||||
| 		path      string | ||||
| 		service   string | ||||
| 		namespace string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"/test/foo", | ||||
| 			"test", | ||||
| 			"go.micro.api.test", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/test/foo/baz", | ||||
| 			"test", | ||||
| 			"go.micro.api.test", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo", | ||||
| 			"v1.foo", | ||||
| 			"go.micro.api.v1.foo", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar", | ||||
| 			"v1.foo", | ||||
| 			"go.micro.api.v1.foo", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v2/baz", | ||||
| 			"v2.baz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v2/baz/bar", | ||||
| 			"v2.baz", | ||||
| 			"go.micro.api.v2.baz", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v2/baz/bar", | ||||
| 			"go.micro.api.v2.baz", | ||||
| 			"go.micro.api", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v2/baz/bar", | ||||
| 			"v2.baz", | ||||
| 			"", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		testHttp(t, d.path, d.service) | ||||
| 		t.Run(d.service, func(t *testing.T) { | ||||
| 			testHttp(t, d.path, d.service, d.namespace) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,9 @@ import ( | ||||
| 	"github.com/micro/go-micro/v2/api/resolver" | ||||
| ) | ||||
|  | ||||
| type Resolver struct{} | ||||
| type Resolver struct { | ||||
| 	opts resolver.Options | ||||
| } | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	return &resolver.Endpoint{ | ||||
| @@ -23,5 +25,5 @@ func (r *Resolver) String() string { | ||||
| } | ||||
|  | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{} | ||||
| 	return &Resolver{opts: resolver.NewOptions(opts...)} | ||||
| } | ||||
|   | ||||
| @@ -1,50 +0,0 @@ | ||||
| // Package micro provides a micro rpc resolver which prefixes a namespace | ||||
| package micro | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/micro/go-micro/v2/api/resolver" | ||||
| ) | ||||
|  | ||||
| // default resolver for legacy purposes | ||||
| // it uses proxy routing to resolve names | ||||
| // /foo becomes namespace.foo | ||||
| // /v1/foo becomes namespace.v1.foo | ||||
| type Resolver struct { | ||||
| 	Options resolver.Options | ||||
| } | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	var name, method string | ||||
|  | ||||
| 	switch r.Options.Handler { | ||||
| 	// internal handlers | ||||
| 	case "meta", "api", "rpc", "micro": | ||||
| 		name, method = apiRoute(req.URL.Path) | ||||
| 	default: | ||||
| 		method = req.Method | ||||
| 		name = proxyRoute(req.URL.Path) | ||||
| 	} | ||||
|  | ||||
| 	// set the namespace if it exists | ||||
| 	if len(r.Options.Namespace) > 0 { | ||||
| 		name = r.Options.Namespace + "." + name | ||||
| 	} | ||||
|  | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   name, | ||||
| 		Method: method, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) String() string { | ||||
| 	return "micro" | ||||
| } | ||||
|  | ||||
| // NewResolver creates a new micro resolver | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{ | ||||
| 		Options: resolver.NewOptions(opts...), | ||||
| 	} | ||||
| } | ||||
| @@ -1,95 +0,0 @@ | ||||
| package micro | ||||
|  | ||||
| import ( | ||||
| 	"path" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	proxyRe   = regexp.MustCompile("^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$") | ||||
| 	versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") | ||||
| ) | ||||
|  | ||||
| // Translates /foo/bar/zool into api service go.micro.api.foo method Bar.Zool | ||||
| // Translates /foo/bar into api service go.micro.api.foo method Foo.Bar | ||||
| func apiRoute(p string) (string, string) { | ||||
| 	p = path.Clean(p) | ||||
| 	p = strings.TrimPrefix(p, "/") | ||||
| 	parts := strings.Split(p, "/") | ||||
|  | ||||
| 	// if we have 1 part assume name Name.Call | ||||
| 	if len(parts) == 1 && len(parts[0]) > 0 { | ||||
| 		return parts[0], methodName(append(parts, "Call")) | ||||
| 	} | ||||
|  | ||||
| 	// If we've got two or less parts | ||||
| 	// Use first part as service | ||||
| 	// Use all parts as method | ||||
| 	if len(parts) <= 2 { | ||||
| 		name := parts[0] | ||||
| 		return name, methodName(parts) | ||||
| 	} | ||||
|  | ||||
| 	// Treat /v[0-9]+ as versioning where we have 3 parts | ||||
| 	// /v1/foo/bar => service: v1.foo method: Foo.bar | ||||
| 	if len(parts) == 3 && versionRe.Match([]byte(parts[0])) { | ||||
| 		name := strings.Join(parts[:len(parts)-1], ".") | ||||
| 		return name, methodName(parts[len(parts)-2:]) | ||||
| 	} | ||||
|  | ||||
| 	// Service is everything minus last two parts | ||||
| 	// Method is the last two parts | ||||
| 	name := strings.Join(parts[:len(parts)-2], ".") | ||||
| 	return name, methodName(parts[len(parts)-2:]) | ||||
| } | ||||
|  | ||||
| func proxyRoute(p string) string { | ||||
| 	parts := strings.Split(p, "/") | ||||
| 	if len(parts) < 2 { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	var service string | ||||
| 	var alias string | ||||
|  | ||||
| 	// /[service]/methods | ||||
| 	if len(parts) > 2 { | ||||
| 		// /v1/[service] | ||||
| 		if versionRe.MatchString(parts[1]) { | ||||
| 			service = parts[1] + "." + parts[2] | ||||
| 			alias = parts[2] | ||||
| 		} else { | ||||
| 			service = parts[1] | ||||
| 			alias = parts[1] | ||||
| 		} | ||||
| 		// /[service] | ||||
| 	} else { | ||||
| 		service = parts[1] | ||||
| 		alias = parts[1] | ||||
| 	} | ||||
|  | ||||
| 	// check service name is valid | ||||
| 	if !proxyRe.MatchString(alias) { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return service | ||||
| } | ||||
|  | ||||
| func methodName(parts []string) string { | ||||
| 	for i, part := range parts { | ||||
| 		parts[i] = toCamel(part) | ||||
| 	} | ||||
|  | ||||
| 	return strings.Join(parts, ".") | ||||
| } | ||||
|  | ||||
| func toCamel(s string) string { | ||||
| 	words := strings.Split(s, "-") | ||||
| 	var out string | ||||
| 	for _, word := range words { | ||||
| 		out += strings.Title(word) | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
| @@ -1,130 +0,0 @@ | ||||
| package micro | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestApiRoute(t *testing.T) { | ||||
| 	testData := []struct { | ||||
| 		path    string | ||||
| 		service string | ||||
| 		method  string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"/foo/bar", | ||||
| 			"foo", | ||||
| 			"Foo.Bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/foo/bar", | ||||
| 			"foo", | ||||
| 			"Foo.Bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz", | ||||
| 			"foo", | ||||
| 			"Bar.Baz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz-xyz", | ||||
| 			"foo", | ||||
| 			"Bar.BazXyz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz/cat", | ||||
| 			"foo.bar", | ||||
| 			"Baz.Cat", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/baz/cat/car", | ||||
| 			"foo.bar.baz", | ||||
| 			"Cat.Car", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/fooBar/bazCat", | ||||
| 			"foo", | ||||
| 			"FooBar.BazCat", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar", | ||||
| 			"v1.foo", | ||||
| 			"Foo.Bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz", | ||||
| 			"v1.foo", | ||||
| 			"Bar.Baz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz/cat", | ||||
| 			"v1.foo.bar", | ||||
| 			"Baz.Cat", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		s, m := apiRoute(d.path) | ||||
| 		if d.service != s { | ||||
| 			t.Fatalf("Expected service: %s for path: %s got: %s %s", d.service, d.path, s, m) | ||||
| 		} | ||||
| 		if d.method != m { | ||||
| 			t.Fatalf("Expected service: %s for path: %s got: %s", d.method, d.path, m) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestProxyRoute(t *testing.T) { | ||||
| 	testData := []struct { | ||||
| 		path    string | ||||
| 		service string | ||||
| 	}{ | ||||
| 		// no namespace | ||||
| 		{ | ||||
| 			"/f", | ||||
| 			"f", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/f", | ||||
| 			"f", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/f-b", | ||||
| 			"f-b", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar", | ||||
| 			"foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo-bar", | ||||
| 			"foo-bar", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo-bar-baz", | ||||
| 			"foo-bar-baz", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/foo/bar/bar", | ||||
| 			"foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar", | ||||
| 			"v1.foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz", | ||||
| 			"v1.foo", | ||||
| 		}, | ||||
| 		{ | ||||
| 			"/v1/foo/bar/baz/cat", | ||||
| 			"v1.foo", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range testData { | ||||
| 		s := proxyRoute(d.path) | ||||
| 		if d.service != s { | ||||
| 			t.Fatalf("Expected service: %s for path: %s got: %s", d.service, d.path, s) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,11 +1,22 @@ | ||||
| package resolver | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/micro/go-micro/v2/auth" | ||||
| ) | ||||
|  | ||||
| // NewOptions returns new initialised options | ||||
| func NewOptions(opts ...Option) Options { | ||||
| 	var options Options | ||||
| 	for _, o := range opts { | ||||
| 		o(&options) | ||||
| 	} | ||||
|  | ||||
| 	if options.Namespace == nil { | ||||
| 		options.Namespace = StaticNamespace(auth.DefaultNamespace) | ||||
| 	} | ||||
|  | ||||
| 	return options | ||||
| } | ||||
|  | ||||
| @@ -16,8 +27,8 @@ func WithHandler(h string) Option { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithNamespace sets the namespace being used | ||||
| func WithNamespace(n string) Option { | ||||
| // WithNamespace sets the function which determines the namespace for a request | ||||
| func WithNamespace(n func(*http.Request) string) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.Namespace = n | ||||
| 	} | ||||
|   | ||||
| @@ -8,15 +8,20 @@ import ( | ||||
| 	"github.com/micro/go-micro/v2/api/resolver" | ||||
| ) | ||||
|  | ||||
| type Resolver struct{} | ||||
| type Resolver struct { | ||||
| 	opts resolver.Options | ||||
| } | ||||
|  | ||||
| func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	if req.URL.Path == "/" { | ||||
| 		return nil, resolver.ErrNotFound | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(req.URL.Path[1:], "/") | ||||
| 	ns := r.opts.Namespace(req) | ||||
|  | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   parts[0], | ||||
| 		Name:   ns + "." + parts[0], | ||||
| 		Host:   req.Host, | ||||
| 		Method: req.Method, | ||||
| 		Path:   req.URL.Path, | ||||
| @@ -28,5 +33,5 @@ func (r *Resolver) String() string { | ||||
| } | ||||
|  | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{} | ||||
| 	return &Resolver{opts: resolver.NewOptions(opts...)} | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,14 @@ type Endpoint struct { | ||||
|  | ||||
| type Options struct { | ||||
| 	Handler   string | ||||
| 	Namespace string | ||||
| 	Namespace func(*http.Request) string | ||||
| } | ||||
|  | ||||
| type Option func(o *Options) | ||||
|  | ||||
| // StaticNamespace returns the same namespace for each request | ||||
| func StaticNamespace(ns string) func(*http.Request) string { | ||||
| 	return func(*http.Request) string { | ||||
| 		return ns | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package vpath | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| @@ -10,7 +11,13 @@ import ( | ||||
| 	"github.com/micro/go-micro/v2/api/resolver" | ||||
| ) | ||||
|  | ||||
| type Resolver struct{} | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{opts: resolver.NewOptions(opts...)} | ||||
| } | ||||
|  | ||||
| type Resolver struct { | ||||
| 	opts resolver.Options | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	re = regexp.MustCompile("^v[0-9]+$") | ||||
| @@ -21,11 +28,12 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 		return nil, errors.New("unknown name") | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(req.URL.Path[1:], "/") | ||||
| 	fmt.Println(req.URL.Path) | ||||
|  | ||||
| 	parts := strings.Split(req.URL.Path[1:], "/") | ||||
| 	if len(parts) == 1 { | ||||
| 		return &resolver.Endpoint{ | ||||
| 			Name:   parts[0], | ||||
| 			Name:   r.withNamespace(req, parts...), | ||||
| 			Host:   req.Host, | ||||
| 			Method: req.Method, | ||||
| 			Path:   req.URL.Path, | ||||
| @@ -35,7 +43,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	// /v1/foo | ||||
| 	if re.MatchString(parts[0]) { | ||||
| 		return &resolver.Endpoint{ | ||||
| 			Name:   parts[1], | ||||
| 			Name:   r.withNamespace(req, parts[0:2]...), | ||||
| 			Host:   req.Host, | ||||
| 			Method: req.Method, | ||||
| 			Path:   req.URL.Path, | ||||
| @@ -43,7 +51,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { | ||||
| 	} | ||||
|  | ||||
| 	return &resolver.Endpoint{ | ||||
| 		Name:   parts[0], | ||||
| 		Name:   r.withNamespace(req, parts[0]), | ||||
| 		Host:   req.Host, | ||||
| 		Method: req.Method, | ||||
| 		Path:   req.URL.Path, | ||||
| @@ -54,6 +62,11 @@ func (r *Resolver) String() string { | ||||
| 	return "path" | ||||
| } | ||||
|  | ||||
| func NewResolver(opts ...resolver.Option) resolver.Resolver { | ||||
| 	return &Resolver{} | ||||
| func (r *Resolver) withNamespace(req *http.Request, parts ...string) string { | ||||
| 	ns := r.opts.Namespace(req) | ||||
| 	if len(ns) == 0 { | ||||
| 		return strings.Join(parts, ".") | ||||
| 	} | ||||
|  | ||||
| 	return strings.Join(append([]string{ns}, parts...), ".") | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package router | ||||
|  | ||||
| import ( | ||||
| 	"github.com/micro/go-micro/v2/api/resolver" | ||||
| 	"github.com/micro/go-micro/v2/api/resolver/micro" | ||||
| 	"github.com/micro/go-micro/v2/api/resolver/vpath" | ||||
| 	"github.com/micro/go-micro/v2/registry" | ||||
| ) | ||||
|  | ||||
| @@ -25,7 +25,7 @@ func NewOptions(opts ...Option) Options { | ||||
| 	} | ||||
|  | ||||
| 	if options.Resolver == nil { | ||||
| 		options.Resolver = micro.NewResolver( | ||||
| 		options.Resolver = vpath.NewResolver( | ||||
| 			resolver.WithHandler(options.Handler), | ||||
| 		) | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user