Merge pull request #1505 from micro/resover-refactor
Extract Micro Resolver (Namespace)
This commit is contained in:
commit
bf65dc71c7
@ -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))
|
||||
@ -75,38 +80,48 @@ func TestHttpHandler(t *testing.T) {
|
||||
testData := []struct {
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user