diff --git a/api/handler/http/http_test.go b/api/handler/http/http_test.go index d80fdf18..f0ccd1ff 100644 --- a/api/handler/http/http_test.go +++ b/api/handler/http/http_test.go @@ -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) + }) } } diff --git a/api/resolver/host/host.go b/api/resolver/host/host.go index 157c138f..204bdfa8 100644 --- a/api/resolver/host/host.go +++ b/api/resolver/host/host.go @@ -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...)} } diff --git a/api/resolver/micro/micro.go b/api/resolver/micro/micro.go deleted file mode 100644 index bacbcbac..00000000 --- a/api/resolver/micro/micro.go +++ /dev/null @@ -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...), - } -} diff --git a/api/resolver/micro/route.go b/api/resolver/micro/route.go deleted file mode 100644 index b2f94b01..00000000 --- a/api/resolver/micro/route.go +++ /dev/null @@ -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 -} diff --git a/api/resolver/micro/route_test.go b/api/resolver/micro/route_test.go deleted file mode 100644 index b2c61bce..00000000 --- a/api/resolver/micro/route_test.go +++ /dev/null @@ -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) - } - } -} diff --git a/api/resolver/options.go b/api/resolver/options.go index ce4b2e49..eb9ce875 100644 --- a/api/resolver/options.go +++ b/api/resolver/options.go @@ -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 } diff --git a/api/resolver/path/path.go b/api/resolver/path/path.go index c4d41fc6..84a92656 100644 --- a/api/resolver/path/path.go +++ b/api/resolver/path/path.go @@ -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...)} } diff --git a/api/resolver/resolver.go b/api/resolver/resolver.go index 12854b19..81e8194d 100644 --- a/api/resolver/resolver.go +++ b/api/resolver/resolver.go @@ -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 + } +} diff --git a/api/resolver/vpath/vpath.go b/api/resolver/vpath/vpath.go index 45f6faaf..cf407ded 100644 --- a/api/resolver/vpath/vpath.go +++ b/api/resolver/vpath/vpath.go @@ -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...), ".") } diff --git a/api/router/options.go b/api/router/options.go index 9f0c9325..69b8a504 100644 --- a/api/router/options.go +++ b/api/router/options.go @@ -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), ) }