2020-06-26 14:28:18 +01:00
|
|
|
// Package subdomain is a resolver which uses the subdomain to determine the domain to route to. It
|
|
|
|
// offloads the endpoint resolution to a child resolver which is provided in New.
|
|
|
|
package subdomain
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2020-08-19 17:47:17 +03:00
|
|
|
"github.com/unistack-org/micro/v3/api/resolver"
|
|
|
|
"github.com/unistack-org/micro/v3/logger"
|
2020-06-26 14:28:18 +01:00
|
|
|
"golang.org/x/net/publicsuffix"
|
|
|
|
)
|
|
|
|
|
2021-02-14 16:16:01 +03:00
|
|
|
// NewResolver creates new subdomain api resolver
|
2020-06-26 14:28:18 +01:00
|
|
|
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
|
|
|
|
options := resolver.NewOptions(opts...)
|
2021-04-27 08:32:47 +03:00
|
|
|
return &subdomainResolver{opts: options, Resolver: parent}
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
2021-02-14 16:16:01 +03:00
|
|
|
type subdomainResolver struct {
|
2020-06-26 14:28:18 +01:00
|
|
|
resolver.Resolver
|
2021-04-26 23:13:23 +03:00
|
|
|
opts resolver.Options
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
2021-02-14 16:16:01 +03:00
|
|
|
// Resolve resolve endpoint based on subdomain
|
|
|
|
func (r *subdomainResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
|
2020-06-29 16:37:45 +01:00
|
|
|
if dom := r.Domain(req); len(dom) > 0 {
|
|
|
|
opts = append(opts, resolver.Domain(dom))
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
2020-06-29 16:37:45 +01:00
|
|
|
return r.Resolver.Resolve(req, opts...)
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
2021-02-14 16:16:01 +03:00
|
|
|
// Domain returns domain
|
|
|
|
func (r *subdomainResolver) Domain(req *http.Request) string {
|
2020-06-26 14:28:18 +01:00
|
|
|
// determine the host, e.g. foobar.m3o.app
|
|
|
|
host := req.URL.Hostname()
|
|
|
|
if len(host) == 0 {
|
|
|
|
if h, _, err := net.SplitHostPort(req.Host); err == nil {
|
|
|
|
host = h // host does contain a port
|
|
|
|
} else if strings.Contains(err.Error(), "missing port in address") {
|
|
|
|
host = req.Host // host does not contain a port
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for an ip address
|
|
|
|
if net.ParseIP(host) != nil {
|
2020-06-29 16:37:45 +01:00
|
|
|
return ""
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
2020-08-25 14:33:36 +03:00
|
|
|
// check for dev environment
|
2020-06-26 14:28:18 +01:00
|
|
|
if host == "localhost" || host == "127.0.0.1" {
|
2020-06-29 16:37:45 +01:00
|
|
|
return ""
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// extract the top level domain plus one (e.g. 'myapp.com')
|
|
|
|
domain, err := publicsuffix.EffectiveTLDPlusOne(host)
|
|
|
|
if err != nil {
|
2020-09-05 02:43:16 +03:00
|
|
|
if logger.V(logger.DebugLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
logger.Debug(r.opts.Context, "Unable to extract domain from %v", host)
|
2020-09-05 02:43:16 +03:00
|
|
|
}
|
2020-06-29 16:37:45 +01:00
|
|
|
return ""
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// there was no subdomain
|
|
|
|
if host == domain {
|
2020-06-29 16:37:45 +01:00
|
|
|
return ""
|
2020-06-26 14:28:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// remove the domain from the host, leaving the subdomain, e.g. "staging.foo.myapp.com" => "staging.foo"
|
|
|
|
subdomain := strings.TrimSuffix(host, "."+domain)
|
|
|
|
|
2020-08-07 20:54:55 +01:00
|
|
|
// ignore the API subdomain
|
|
|
|
if subdomain == "api" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2020-06-26 14:28:18 +01:00
|
|
|
// return the reversed subdomain as the namespace, e.g. "staging.foo" => "foo-staging"
|
|
|
|
comps := strings.Split(subdomain, ".")
|
|
|
|
for i := len(comps)/2 - 1; i >= 0; i-- {
|
|
|
|
opp := len(comps) - 1 - i
|
|
|
|
comps[i], comps[opp] = comps[opp], comps[i]
|
|
|
|
}
|
|
|
|
return strings.Join(comps, "-")
|
|
|
|
}
|
|
|
|
|
2021-02-14 16:16:01 +03:00
|
|
|
func (r *subdomainResolver) String() string {
|
2020-06-26 14:28:18 +01:00
|
|
|
return "subdomain"
|
|
|
|
}
|