api/resolver: add resolve options (#1756)

* api/resolver: Resolve options

* router/registry: fix init bug

* router/registry: fix wildcard query bug

* web: fix registation domain bug

* registry/etcd: pass domain in service metadata

* api/resolver/subdomain: expose domain func

* Update api/resolver/subdomain/subdomain.go

Co-authored-by: Dominic Wong <domwongemail@googlemail.com>

Co-authored-by: Dominic Wong <domwongemail@googlemail.com>
This commit is contained in:
ben-toogood 2020-06-29 16:37:45 +01:00 committed by GitHub
parent 132c1e35fe
commit df3e5364ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 71 additions and 36 deletions

View File

@ -13,7 +13,10 @@ type Resolver struct {
opts resolver.Options opts resolver.Options
} }
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
// /foo.Bar/Service // /foo.Bar/Service
if req.URL.Path == "/" { if req.URL.Path == "/" {
return nil, errors.New("unknown name") return nil, errors.New("unknown name")
@ -28,7 +31,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
Host: req.Host, Host: req.Host,
Method: req.Method, Method: req.Method,
Path: req.URL.Path, Path: req.URL.Path,
Domain: r.opts.Domain, Domain: options.Domain,
}, nil }, nil
} }

View File

@ -11,13 +11,16 @@ type Resolver struct {
opts resolver.Options opts resolver.Options
} }
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
return &resolver.Endpoint{ return &resolver.Endpoint{
Name: req.Host, Name: req.Host,
Host: req.Host, Host: req.Host,
Method: req.Method, Method: req.Method,
Path: req.URL.Path, Path: req.URL.Path,
Domain: r.opts.Domain, Domain: options.Domain,
}, nil }, nil
} }

View File

@ -6,7 +6,6 @@ import (
type Options struct { type Options struct {
Handler string Handler string
Domain string
ServicePrefix string ServicePrefix string
} }
@ -19,13 +18,6 @@ func WithHandler(h string) Option {
} }
} }
// WithDomain sets the namespace option
func WithDomain(n string) Option {
return func(o *Options) {
o.Domain = n
}
}
// WithServicePrefix sets the ServicePrefix option // WithServicePrefix sets the ServicePrefix option
func WithServicePrefix(p string) Option { func WithServicePrefix(p string) Option {
return func(o *Options) { return func(o *Options) {
@ -39,6 +31,30 @@ func NewOptions(opts ...Option) Options {
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
return options
}
// ResolveOptions are used when resolving a request
type ResolveOptions struct {
Domain string
}
// ResolveOption sets an option
type ResolveOption func(*ResolveOptions)
// Domain sets the resolve Domain option
func Domain(n string) ResolveOption {
return func(o *ResolveOptions) {
o.Domain = n
}
}
// NewResolveOptions returns new initialised resolve options
func NewResolveOptions(opts ...ResolveOption) ResolveOptions {
var options ResolveOptions
for _, o := range opts {
o(&options)
}
if len(options.Domain) == 0 { if len(options.Domain) == 0 {
options.Domain = registry.DefaultDomain options.Domain = registry.DefaultDomain
} }

View File

@ -12,7 +12,10 @@ type Resolver struct {
opts resolver.Options opts resolver.Options
} }
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
if req.URL.Path == "/" { if req.URL.Path == "/" {
return nil, resolver.ErrNotFound return nil, resolver.ErrNotFound
} }
@ -24,7 +27,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
Host: req.Host, Host: req.Host,
Method: req.Method, Method: req.Method,
Path: req.URL.Path, Path: req.URL.Path,
Domain: r.opts.Domain, Domain: options.Domain,
}, nil }, nil
} }

View File

@ -13,7 +13,7 @@ var (
// Resolver resolves requests to endpoints // Resolver resolves requests to endpoints
type Resolver interface { type Resolver interface {
Resolve(r *http.Request) (*Endpoint, error) Resolve(r *http.Request, opts ...ResolveOption) (*Endpoint, error)
String() string String() string
} }

View File

@ -22,21 +22,15 @@ type Resolver struct {
resolver.Resolver resolver.Resolver
} }
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// resolve the endpoint using the provided resolver if dom := r.Domain(req); len(dom) > 0 {
endpoint, err := r.Resolver.Resolve(req) opts = append(opts, resolver.Domain(dom))
if err != nil {
return nil, err
} }
// override the domain return r.Resolver.Resolve(req, opts...)
endpoint.Domain = r.resolveDomain(req)
// return the result
return endpoint, nil
} }
func (r *Resolver) resolveDomain(req *http.Request) string { func (r *Resolver) Domain(req *http.Request) string {
// determine the host, e.g. foobar.m3o.app // determine the host, e.g. foobar.m3o.app
host := req.URL.Hostname() host := req.URL.Hostname()
if len(host) == 0 { if len(host) == 0 {
@ -49,24 +43,24 @@ func (r *Resolver) resolveDomain(req *http.Request) string {
// check for an ip address // check for an ip address
if net.ParseIP(host) != nil { if net.ParseIP(host) != nil {
return r.opts.Domain return ""
} }
// check for dev enviroment // check for dev enviroment
if host == "localhost" || host == "127.0.0.1" { if host == "localhost" || host == "127.0.0.1" {
return r.opts.Domain return ""
} }
// extract the top level domain plus one (e.g. 'myapp.com') // extract the top level domain plus one (e.g. 'myapp.com')
domain, err := publicsuffix.EffectiveTLDPlusOne(host) domain, err := publicsuffix.EffectiveTLDPlusOne(host)
if err != nil { if err != nil {
logger.Debugf("Unable to extract domain from %v", host) logger.Debugf("Unable to extract domain from %v", host)
return r.opts.Domain return ""
} }
// there was no subdomain // there was no subdomain
if host == domain { if host == domain {
return r.opts.Domain return ""
} }
// remove the domain from the host, leaving the subdomain, e.g. "staging.foo.myapp.com" => "staging.foo" // remove the domain from the host, leaving the subdomain, e.g. "staging.foo.myapp.com" => "staging.foo"

View File

@ -22,11 +22,13 @@ var (
re = regexp.MustCompile("^v[0-9]+$") re = regexp.MustCompile("^v[0-9]+$")
) )
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) { func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
if req.URL.Path == "/" { if req.URL.Path == "/" {
return nil, errors.New("unknown name") return nil, errors.New("unknown name")
} }
options := resolver.NewResolveOptions(opts...)
parts := strings.Split(req.URL.Path[1:], "/") parts := strings.Split(req.URL.Path[1:], "/")
if len(parts) == 1 { if len(parts) == 1 {
return &resolver.Endpoint{ return &resolver.Endpoint{
@ -34,7 +36,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
Host: req.Host, Host: req.Host,
Method: req.Method, Method: req.Method,
Path: req.URL.Path, Path: req.URL.Path,
Domain: r.opts.Domain, Domain: options.Domain,
}, nil }, nil
} }
@ -45,7 +47,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
Host: req.Host, Host: req.Host,
Method: req.Method, Method: req.Method,
Path: req.URL.Path, Path: req.URL.Path,
Domain: r.opts.Domain, Domain: options.Domain,
}, nil }, nil
} }
@ -54,7 +56,7 @@ func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
Host: req.Host, Host: req.Host,
Method: req.Method, Method: req.Method,
Path: req.URL.Path, Path: req.URL.Path,
Domain: r.opts.Domain, Domain: options.Domain,
}, nil }, nil
} }

View File

@ -167,6 +167,13 @@ func (e *etcdRegistry) registerNode(s *registry.Service, node *registry.Node, op
options.Domain = defaultDomain options.Domain = defaultDomain
} }
// set the domain in metadata so it can be retrieved by wildcard queries
if s.Metadata == nil {
s.Metadata = map[string]string{"domain": options.Domain}
} else {
s.Metadata["domain"] = options.Domain
}
e.Lock() e.Lock()
// ensure the leases and registers are setup for this domain // ensure the leases and registers are setup for this domain
if _, ok := e.leases[options.Domain]; !ok { if _, ok := e.leases[options.Domain]; !ok {

View File

@ -674,6 +674,7 @@ func (r *router) Close() error {
// remove event chan // remove event chan
r.eventChan = nil r.eventChan = nil
r.running = false
return nil return nil
} }

View File

@ -150,7 +150,7 @@ func (t *table) List() ([]Route, error) {
func isMatch(route Route, address, gateway, network, router string, strategy Strategy) bool { func isMatch(route Route, address, gateway, network, router string, strategy Strategy) bool {
// matches the values provided // matches the values provided
match := func(a, b string) bool { match := func(a, b string) bool {
if a == "*" || a == b { if a == "*" || b == "*" || a == b {
return true return true
} }
return false return false

View File

@ -141,10 +141,16 @@ func (s *service) register() error {
var regErr error var regErr error
// register options
rOpts := []registry.RegisterOption{
registry.RegisterTTL(s.opts.RegisterTTL),
registry.RegisterDomain(s.opts.Service.Server().Options().Namespace),
}
// try three times if necessary // try three times if necessary
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
// attempt to register // attempt to register
if err := r.Register(s.srv, registry.RegisterTTL(s.opts.RegisterTTL)); err != nil { if err := r.Register(s.srv, rOpts...); err != nil {
// set the error // set the error
regErr = err regErr = err
// backoff then retry // backoff then retry