api: extract routers to external repos

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2020-11-04 23:48:24 +03:00
parent 8a2b122015
commit 7c311aea19
4 changed files with 0 additions and 1149 deletions

View File

@ -1,498 +0,0 @@
// Package registry provides a dynamic api service router
package registry
import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"time"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/router"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/registry"
util "github.com/unistack-org/micro/v3/util/router"
)
// endpoint struct, that holds compiled pcre
type endpoint struct {
hostregs []*regexp.Regexp
pathregs []util.Pattern
pcreregs []*regexp.Regexp
}
// router is the default router
type registryRouter struct {
exit chan bool
opts router.Options
sync.RWMutex
eps map[string]*api.Service
// compiled regexp for host and path
ceps map[string]*endpoint
}
func (r *registryRouter) isClosed() bool {
select {
case <-r.exit:
return true
default:
return false
}
}
// refresh list of api services
func (r *registryRouter) refresh() {
var attempts int
for {
services, err := r.opts.Registry.ListServices(r.opts.Context)
if err != nil {
attempts++
if logger.V(logger.ErrorLevel) {
logger.Errorf("unable to list services: %v", err)
}
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
attempts = 0
// for each service, get service and store endpoints
for _, s := range services {
service, err := r.opts.Registry.GetService(r.opts.Context, s.Name)
if err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("unable to get service: %v", err)
}
continue
}
r.store(service)
}
// refresh list in 10 minutes... cruft
// use registry watching
select {
case <-time.After(time.Minute * 10):
case <-r.exit:
return
}
}
}
// process watch event
func (r *registryRouter) process(res *registry.Result) {
// skip these things
if res == nil || res.Service == nil {
return
}
// get entry from cache
service, err := r.opts.Registry.GetService(r.opts.Context, res.Service.Name)
if err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("unable to get %v service: %v", res.Service.Name, err)
}
return
}
// update our local endpoints
r.store(service)
}
// store local endpoint cache
func (r *registryRouter) store(services []*registry.Service) {
// endpoints
eps := map[string]*api.Service{}
// services
names := map[string]bool{}
// create a new endpoint mapping
for _, service := range services {
// set names we need later
names[service.Name] = true
// map per endpoint
for _, sep := range service.Endpoints {
// create a key service:endpoint_name
key := fmt.Sprintf("%s.%s", service.Name, sep.Name)
// decode endpoint
end := api.Decode(sep.Metadata)
// no endpoint or no name
if end == nil || len(end.Name) == 0 {
continue
}
// if we got nothing skip
if err := api.Validate(end); err != nil {
if logger.V(logger.TraceLevel) {
logger.Tracef("endpoint validation failed: %v", err)
}
continue
}
// try get endpoint
ep, ok := eps[key]
if !ok {
ep = &api.Service{Name: service.Name}
}
// overwrite the endpoint
ep.Endpoint = end
// append services
ep.Services = append(ep.Services, service)
// store it
eps[key] = ep
}
}
r.Lock()
defer r.Unlock()
// delete any existing eps for services we know
for key, service := range r.eps {
// skip what we don't care about
if !names[service.Name] {
continue
}
// ok we know this thing
// delete delete delete
delete(r.eps, key)
}
// now set the eps we have
for name, ep := range eps {
r.eps[name] = ep
cep := &endpoint{}
for _, h := range ep.Endpoint.Host {
if h == "" || h == "*" {
continue
}
hostreg, err := regexp.CompilePOSIX(h)
if err != nil {
if logger.V(logger.TraceLevel) {
logger.Tracef("endpoint have invalid host regexp: %v", err)
}
continue
}
cep.hostregs = append(cep.hostregs, hostreg)
}
for _, p := range ep.Endpoint.Path {
var pcreok bool
if p[0] == '^' && p[len(p)-1] == '$' {
pcrereg, err := regexp.CompilePOSIX(p)
if err == nil {
cep.pcreregs = append(cep.pcreregs, pcrereg)
pcreok = true
}
}
rule, err := util.Parse(p)
if err != nil && !pcreok {
if logger.V(logger.TraceLevel) {
logger.Tracef("endpoint have invalid path pattern: %v", err)
}
continue
} else if err != nil && pcreok {
continue
}
tpl := rule.Compile()
pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "")
if err != nil {
if logger.V(logger.TraceLevel) {
logger.Tracef("endpoint have invalid path pattern: %v", err)
}
continue
}
cep.pathregs = append(cep.pathregs, pathreg)
}
r.ceps[name] = cep
}
}
// watch for endpoint changes
func (r *registryRouter) watch() {
var attempts int
for {
if r.isClosed() {
return
}
// watch for changes
w, err := r.opts.Registry.Watch(r.opts.Context)
if err != nil {
attempts++
if logger.V(logger.ErrorLevel) {
logger.Errorf("error watching endpoints: %v", err)
}
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
ch := make(chan bool)
go func() {
select {
case <-ch:
w.Stop()
case <-r.exit:
w.Stop()
}
}()
// reset if we get here
attempts = 0
for {
// process next event
res, err := w.Next()
if err != nil {
if logger.V(logger.ErrorLevel) {
logger.Errorf("error getting next endpoint: %v", err)
}
close(ch)
break
}
r.process(res)
}
}
}
func (r *registryRouter) Options() router.Options {
return r.opts
}
func (r *registryRouter) Close() error {
select {
case <-r.exit:
return nil
default:
close(r.exit)
}
return nil
}
func (r *registryRouter) Register(ep *api.Endpoint) error {
return nil
}
func (r *registryRouter) Deregister(ep *api.Endpoint) error {
return nil
}
func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
r.RLock()
defer r.RUnlock()
var idx int
if len(req.URL.Path) > 0 && req.URL.Path != "/" {
idx = 1
}
path := strings.Split(req.URL.Path[idx:], "/")
// use the first match
// TODO: weighted matching
for n, e := range r.eps {
cep, ok := r.ceps[n]
if !ok {
continue
}
ep := e.Endpoint
var mMatch, hMatch, pMatch bool
// 1. try method
for _, m := range ep.Method {
if m == req.Method {
mMatch = true
break
}
}
if !mMatch {
continue
}
if logger.V(logger.TraceLevel) {
logger.Tracef("api method match %s", req.Method)
}
// 2. try host
if len(ep.Host) == 0 {
hMatch = true
} else {
for idx, h := range ep.Host {
if h == "" || h == "*" {
hMatch = true
break
} else {
if cep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true
break
}
}
}
}
if !hMatch {
continue
}
if logger.V(logger.TraceLevel) {
logger.Tracef("api host match %s", req.URL.Host)
}
// 3. try path via google.api path matching
for _, pathreg := range cep.pathregs {
matches, err := pathreg.Match(path, "")
if err != nil {
if logger.V(logger.TraceLevel) {
logger.Tracef("api gpath not match %s != %v", path, pathreg)
}
continue
}
if logger.V(logger.TraceLevel) {
logger.Tracef("api gpath match %s = %v", path, pathreg)
}
pMatch = true
ctx := req.Context()
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(metadata.Metadata)
}
for k, v := range matches {
md[fmt.Sprintf("x-api-field-%s", k)] = v
}
md["x-api-body"] = ep.Body
*req = *req.Clone(metadata.NewContext(ctx, md))
break
}
if !pMatch {
// 4. try path via pcre path matching
for _, pathreg := range cep.pcreregs {
if !pathreg.MatchString(req.URL.Path) {
if logger.V(logger.TraceLevel) {
logger.Tracef("api pcre path not match %s != %v", path, pathreg)
}
continue
}
if logger.V(logger.TraceLevel) {
logger.Tracef("api pcre path match %s != %v", path, pathreg)
}
pMatch = true
break
}
}
if !pMatch {
continue
}
// TODO: Percentage traffic
// we got here, so its a match
return e, nil
}
// no match
return nil, errors.New("not found")
}
func (r *registryRouter) Route(req *http.Request) (*api.Service, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
// try get an endpoint
ep, err := r.Endpoint(req)
if err == nil {
return ep, nil
}
// error not nil
// ignore that shit
// TODO: don't ignore that shit
// get the service name
rp, err := r.opts.Resolver.Resolve(req)
if err != nil {
return nil, err
}
// service name
name := rp.Name
// get service
services, err := r.opts.Registry.GetService(r.opts.Context, name, registry.GetDomain(rp.Domain))
if err != nil {
return nil, err
}
// only use endpoint matching when the meta handler is set aka api.Default
switch r.opts.Handler {
// rpc handlers
case "meta", "api", "rpc":
handler := r.opts.Handler
// set default handler to api
if r.opts.Handler == "meta" {
handler = "rpc"
}
// construct api service
return &api.Service{
Name: name,
Endpoint: &api.Endpoint{
Name: rp.Method,
Handler: handler,
},
Services: services,
}, nil
// http handler
case "http", "proxy", "web":
// construct api service
return &api.Service{
Name: name,
Endpoint: &api.Endpoint{
Name: req.URL.String(),
Handler: r.opts.Handler,
Host: []string{req.Host},
Method: []string{req.Method},
Path: []string{req.URL.Path},
},
Services: services,
}, nil
}
return nil, errors.New("unknown handler")
}
func newRouter(opts ...router.Option) (*registryRouter, error) {
options := router.NewOptions(opts...)
if options.Registry == nil {
return nil, fmt.Errorf("registry is not set")
}
r := &registryRouter{
exit: make(chan bool),
opts: options,
eps: make(map[string]*api.Service),
ceps: make(map[string]*endpoint),
}
go r.watch()
go r.refresh()
return r, nil
}
// NewRouter returns the default router
func NewRouter(opts ...router.Option) (router.Router, error) {
return newRouter(opts...)
}

View File

@ -1,38 +0,0 @@
package registry
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/unistack-org/micro/v3/registry"
)
func TestStoreRegex(t *testing.T) {
t.Skip()
router, err := newRouter()
if err != nil {
t.Fatal(err)
}
router.store([]*registry.Service{
{
Name: "Foobar",
Version: "latest",
Endpoints: []*registry.Endpoint{
{
Name: "foo",
Metadata: map[string]string{
"endpoint": "FooEndpoint",
"description": "Some description",
"method": "POST",
"path": "^/foo/$",
"handler": "rpc",
},
},
},
Metadata: map[string]string{},
},
},
)
assert.Len(t, router.ceps["Foobar.foo"].pcreregs, 1)
}

View File

@ -1,257 +0,0 @@
// +build ignore
package router_test
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"testing"
"time"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/handler"
"github.com/unistack-org/micro/v3/api/handler/rpc"
"github.com/unistack-org/micro/v3/api/router"
rregistry "github.com/unistack-org/micro/v3/api/router/registry"
rstatic "github.com/unistack-org/micro/v3/api/router/static"
"github.com/unistack-org/micro/v3/broker"
bmemory "github.com/unistack-org/micro/v3/broker/memory"
"github.com/unistack-org/micro/v3/client"
gcli "github.com/unistack-org/micro/v3/client/grpc"
rmemory "github.com/unistack-org/micro/v3/registry/memory"
rt "github.com/unistack-org/micro/v3/router"
regRouter "github.com/unistack-org/micro/v3/router/registry"
"github.com/unistack-org/micro/v3/server"
gsrv "github.com/unistack-org/micro/v3/server/grpc"
pb "github.com/unistack-org/micro/v3/server/grpc/proto"
)
// server is used to implement helloworld.GreeterServer.
type testServer struct {
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) Call(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) CallPcre(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) CallPcreInvalid(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
func initial(t *testing.T) (server.Server, client.Client) {
r := rmemory.NewRegistry()
b := bmemory.NewBroker(broker.Registry(r))
// create a new client
s := gsrv.NewServer(
server.Name("foo"),
server.Broker(b),
server.Registry(r),
)
rtr := regRouter.NewRouter(
rt.Registry(r),
)
// create a new server
c := gcli.NewClient(
client.Router(rtr),
client.Broker(b),
)
h := &testServer{}
pb.RegisterTestHandler(s, h)
if err := s.Start(); err != nil {
t.Fatalf("failed to start: %v", err)
}
return s, c
}
func check(t *testing.T, addr string, path string, expected string) {
req, err := http.NewRequest("POST", fmt.Sprintf(path, addr), nil)
if err != nil {
t.Fatalf("Failed to created http.Request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
rsp, err := (&http.Client{}).Do(req)
if err != nil {
t.Fatalf("Failed to created http.Request: %v", err)
}
defer rsp.Body.Close()
buf, err := ioutil.ReadAll(rsp.Body)
if err != nil {
t.Fatal(err)
}
jsonMsg := expected
if string(buf) != jsonMsg {
t.Fatalf("invalid message received, parsing error %s != %s", buf, jsonMsg)
}
}
func TestRouterRegistryPcre(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rregistry.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`)
}
func TestRouterStaticPcre(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
err := router.Register(&api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"^/api/v0/test/call/?$"},
Handler: "rpc",
})
if err != nil {
t.Fatal(err)
}
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call", `{"msg":"Hello "}`)
}
func TestRouterStaticGpath(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
err := router.Register(&api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"/api/v0/test/call/{uuid}"},
Handler: "rpc",
})
if err != nil {
t.Fatal(err)
}
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`)
}
func TestRouterStaticPcreInvalid(t *testing.T) {
var ep *api.Endpoint
var err error
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
ep = &api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"^/api/v0/test/call/?"},
Handler: "rpc",
}
err = router.Register(ep)
if err == nil {
t.Fatalf("invalid endpoint %v", ep)
}
ep = &api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"/api/v0/test/call/?$"},
Handler: "rpc",
}
err = router.Register(ep)
if err == nil {
t.Fatalf("invalid endpoint %v", ep)
}
_ = c
}

View File

@ -1,356 +0,0 @@
package static
import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/router"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/registry"
rutil "github.com/unistack-org/micro/v3/util/registry"
util "github.com/unistack-org/micro/v3/util/router"
)
type endpoint struct {
apiep *api.Endpoint
hostregs []*regexp.Regexp
pathregs []util.Pattern
pcreregs []*regexp.Regexp
}
// router is the default router
type staticRouter struct {
exit chan bool
opts router.Options
sync.RWMutex
eps map[string]*endpoint
}
func (r *staticRouter) isClosed() bool {
select {
case <-r.exit:
return true
default:
return false
}
}
/*
// watch for endpoint changes
func (r *staticRouter) watch() {
var attempts int
for {
if r.isClosed() {
return
}
// watch for changes
w, err := r.opts.Registry.Watch()
if err != nil {
attempts++
log.Println("Error watching endpoints", err)
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
ch := make(chan bool)
go func() {
select {
case <-ch:
w.Stop()
case <-r.exit:
w.Stop()
}
}()
// reset if we get here
attempts = 0
for {
// process next event
res, err := w.Next()
if err != nil {
log.Println("Error getting next endpoint", err)
close(ch)
break
}
r.process(res)
}
}
}
*/
func (r *staticRouter) Register(ep *api.Endpoint) error {
if err := api.Validate(ep); err != nil {
return err
}
var pathregs []util.Pattern
var hostregs []*regexp.Regexp
var pcreregs []*regexp.Regexp
for _, h := range ep.Host {
if h == "" || h == "*" {
continue
}
hostreg, err := regexp.CompilePOSIX(h)
if err != nil {
return err
}
hostregs = append(hostregs, hostreg)
}
for _, p := range ep.Path {
var pcreok bool
// pcre only when we have start and end markers
if p[0] == '^' && p[len(p)-1] == '$' {
pcrereg, err := regexp.CompilePOSIX(p)
if err == nil {
pcreregs = append(pcreregs, pcrereg)
pcreok = true
}
}
rule, err := util.Parse(p)
if err != nil && !pcreok {
return err
} else if err != nil && pcreok {
continue
}
tpl := rule.Compile()
pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "")
if err != nil {
return err
}
pathregs = append(pathregs, pathreg)
}
r.Lock()
r.eps[ep.Name] = &endpoint{
apiep: ep,
pcreregs: pcreregs,
pathregs: pathregs,
hostregs: hostregs,
}
r.Unlock()
return nil
}
func (r *staticRouter) Deregister(ep *api.Endpoint) error {
if err := api.Validate(ep); err != nil {
return err
}
r.Lock()
delete(r.eps, ep.Name)
r.Unlock()
return nil
}
func (r *staticRouter) Options() router.Options {
return r.opts
}
func (r *staticRouter) Close() error {
select {
case <-r.exit:
return nil
default:
close(r.exit)
}
return nil
}
func (r *staticRouter) Endpoint(req *http.Request) (*api.Service, error) {
ep, err := r.endpoint(req)
if err != nil {
return nil, err
}
epf := strings.Split(ep.apiep.Name, ".")
services, err := r.opts.Registry.GetService(r.opts.Context, epf[0])
if err != nil {
return nil, err
}
// hack for stream endpoint
if ep.apiep.Stream {
svcs := rutil.Copy(services)
for _, svc := range svcs {
if len(svc.Endpoints) == 0 {
e := &registry.Endpoint{}
e.Name = strings.Join(epf[1:], ".")
e.Metadata = make(map[string]string)
e.Metadata["stream"] = "true"
svc.Endpoints = append(svc.Endpoints, e)
}
for _, e := range svc.Endpoints {
e.Name = strings.Join(epf[1:], ".")
e.Metadata = make(map[string]string)
e.Metadata["stream"] = "true"
}
}
services = svcs
}
svc := &api.Service{
Name: epf[0],
Endpoint: &api.Endpoint{
Name: strings.Join(epf[1:], "."),
Handler: "rpc",
Host: ep.apiep.Host,
Method: ep.apiep.Method,
Path: ep.apiep.Path,
Body: ep.apiep.Body,
Stream: ep.apiep.Stream,
},
Services: services,
}
return svc, nil
}
func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
r.RLock()
defer r.RUnlock()
var idx int
if len(req.URL.Path) > 0 && req.URL.Path != "/" {
idx = 1
}
path := strings.Split(req.URL.Path[idx:], "/")
// use the first match
// TODO: weighted matching
for _, ep := range r.eps {
var mMatch, hMatch, pMatch bool
// 1. try method
for _, m := range ep.apiep.Method {
if m == req.Method {
mMatch = true
break
}
}
if !mMatch {
continue
}
if logger.V(logger.TraceLevel) {
logger.Tracef("api method match %s", req.Method)
}
// 2. try host
if len(ep.apiep.Host) == 0 {
hMatch = true
} else {
for idx, h := range ep.apiep.Host {
if h == "" || h == "*" {
hMatch = true
break
} else {
if ep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true
break
}
}
}
}
if !hMatch {
continue
}
if logger.V(logger.TraceLevel) {
logger.Tracef("api host match %s", req.URL.Host)
}
// 3. try google.api path
for _, pathreg := range ep.pathregs {
matches, err := pathreg.Match(path, "")
if err != nil {
if logger.V(logger.TraceLevel) {
logger.Tracef("api gpath not match %s != %v", path, pathreg)
}
continue
}
if logger.V(logger.TraceLevel) {
logger.Tracef("api gpath match %s = %v", path, pathreg)
}
pMatch = true
ctx := req.Context()
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(metadata.Metadata)
}
for k, v := range matches {
md[fmt.Sprintf("x-api-field-%s", k)] = v
}
md["x-api-body"] = ep.apiep.Body
*req = *req.Clone(metadata.NewContext(ctx, md))
break
}
if !pMatch {
// 4. try path via pcre path matching
for _, pathreg := range ep.pcreregs {
if !pathreg.MatchString(req.URL.Path) {
if logger.V(logger.TraceLevel) {
logger.Tracef("api pcre path not match %s != %v", req.URL.Path, pathreg)
}
continue
}
pMatch = true
break
}
}
if !pMatch {
continue
}
// TODO: Percentage traffic
// we got here, so its a match
return ep, nil
}
// no match
return nil, fmt.Errorf("endpoint not found for %v", req.URL)
}
func (r *staticRouter) Route(req *http.Request) (*api.Service, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
// try get an endpoint
ep, err := r.Endpoint(req)
if err != nil {
return nil, err
}
return ep, nil
}
func NewRouter(opts ...router.Option) *staticRouter {
options := router.NewOptions(opts...)
r := &staticRouter{
exit: make(chan bool),
opts: options,
eps: make(map[string]*endpoint),
}
//go r.watch()
//go r.refresh()
return r
}