diff --git a/api/router/registry/registry.go b/api/router/registry/registry.go deleted file mode 100644 index e6844464..00000000 --- a/api/router/registry/registry.go +++ /dev/null @@ -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 := ®istryRouter{ - 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...) -} diff --git a/api/router/registry/registry_test.go b/api/router/registry/registry_test.go deleted file mode 100644 index 21f8d761..00000000 --- a/api/router/registry/registry_test.go +++ /dev/null @@ -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) -} diff --git a/api/router/router_test.go b/api/router/router_test.go deleted file mode 100644 index 408330b7..00000000 --- a/api/router/router_test.go +++ /dev/null @@ -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 -} diff --git a/api/router/static/static.go b/api/router/static/static.go deleted file mode 100644 index 8969ab59..00000000 --- a/api/router/static/static.go +++ /dev/null @@ -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 := ®istry.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 -}