From f00fd7a49ee6f97759b880a6ca8b1e19bd3d666b Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Sun, 19 Apr 2020 00:31:34 +0300 Subject: [PATCH] api/router: support pcre and google.api pattern matching (#1549) * api/router: support pcre and google.api pattern matching Signed-off-by: Vasiliy Tolstov --- api/router/registry/registry.go | 42 +++++++++++++++++++------- api/router/router_test.go | 53 +++++++++++++++++++++++++++++---- api/router/static/static.go | 48 ++++++++++++++++++++++------- 3 files changed, 115 insertions(+), 28 deletions(-) diff --git a/api/router/registry/registry.go b/api/router/registry/registry.go index d5cd2569..b5ab4d87 100644 --- a/api/router/registry/registry.go +++ b/api/router/registry/registry.go @@ -23,6 +23,7 @@ import ( type endpoint struct { hostregs []*regexp.Regexp pathregs []util.Pattern + pcreregs []*regexp.Regexp } // router is the default router @@ -185,13 +186,23 @@ func (r *registryRouter) store(services []*registry.Service) { } for _, p := range ep.Endpoint.Path { + var pcreok bool + pcrereg, err := regexp.CompilePOSIX(p) + if err == nil { + cep.pcreregs = append(cep.pcreregs, pcrereg) + pcreok = true + } + rule, err := util.Parse(p) - if err != nil { + if err != nil && !pcreok { if logger.V(logger.TraceLevel, logger.DefaultLogger) { 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 { @@ -303,11 +314,10 @@ func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) { ep := e.Endpoint var mMatch, hMatch, pMatch bool // 1. try method - methodLoop: for _, m := range ep.Method { if m == req.Method { mMatch = true - break methodLoop + break } } if !mMatch { @@ -321,15 +331,14 @@ func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) { if len(ep.Host) == 0 { hMatch = true } else { - hostLoop: for idx, h := range ep.Host { if h == "" || h == "*" { hMatch = true - break hostLoop + break } else { if cep.hostregs[idx].MatchString(req.URL.Host) { hMatch = true - break hostLoop + break } } } @@ -341,14 +350,12 @@ func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) { logger.Debugf("api host match %s", req.URL.Host) } - // 3. try path - // 3. try path - pathLoop: + // 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.DebugLevel, logger.DefaultLogger) { - logger.Debugf("api path not match %s != %v", path, pathreg) + logger.Debugf("api gpath not match %s != %v", path, pathreg) } continue } @@ -363,8 +370,21 @@ func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) { } md["x-api-body"] = ep.Body *req = *req.Clone(metadata.NewContext(ctx, md)) - break pathLoop + break } + + // 4. try path via pcre path matching + for _, pathreg := range cep.pcreregs { + if !pathreg.MatchString(req.URL.Path) { + if logger.V(logger.DebugLevel, logger.DefaultLogger) { + logger.Debugf("api pcre path not match %s != %v", path, pathreg) + } + continue + } + pMatch = true + break + } + if !pMatch { continue } diff --git a/api/router/router_test.go b/api/router/router_test.go index ec1efc7e..9211a524 100644 --- a/api/router/router_test.go +++ b/api/router/router_test.go @@ -58,8 +58,8 @@ func initial(t *testing.T) (server.Server, client.Client) { return s, c } -func check(addr string, t *testing.T) { - req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/api/v0/test/call/TEST", addr), nil) +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) } @@ -75,7 +75,7 @@ func check(addr string, t *testing.T) { t.Fatal(err) } - jsonMsg := `{"msg":"Hello TEST"}` + jsonMsg := expected if string(buf) != jsonMsg { t.Fatalf("invalid message received, parsing error %s != %s", buf, jsonMsg) } @@ -108,10 +108,51 @@ func TestRouterRegistry(t *testing.T) { defer hsrv.Close() time.Sleep(1 * time.Second) - check(hsrv.Addr, t) + check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`) } -func TestRouterStatic(t *testing.T) { +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 TestRouterStaticG(t *testing.T) { s, c := initial(t) defer s.Stop() @@ -149,5 +190,5 @@ func TestRouterStatic(t *testing.T) { defer hsrv.Close() time.Sleep(1 * time.Second) - check(hsrv.Addr, t) + check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`) } diff --git a/api/router/static/static.go b/api/router/static/static.go index 927969cb..367a53de 100644 --- a/api/router/static/static.go +++ b/api/router/static/static.go @@ -21,6 +21,7 @@ type endpoint struct { apiep *api.Endpoint hostregs []*regexp.Regexp pathregs []util.Pattern + pcreregs []*regexp.Regexp } // router is the default router @@ -94,6 +95,7 @@ func (r *staticRouter) Register(ep *api.Endpoint) error { var pathregs []util.Pattern var hostregs []*regexp.Regexp + var pcreregs []*regexp.Regexp for _, h := range ep.Host { if h == "" || h == "*" { @@ -107,9 +109,18 @@ func (r *staticRouter) Register(ep *api.Endpoint) error { } for _, p := range ep.Path { + var pcreok bool + pcrereg, err := regexp.CompilePOSIX(p) + if err == nil { + pcreregs = append(pcreregs, pcrereg) + pcreok = true + } + rule, err := util.Parse(p) - if err != nil { + 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, "") @@ -120,7 +131,12 @@ func (r *staticRouter) Register(ep *api.Endpoint) error { } r.Lock() - r.eps[ep.Name] = &endpoint{apiep: ep, pathregs: pathregs, hostregs: hostregs} + r.eps[ep.Name] = &endpoint{ + apiep: ep, + pcreregs: pcreregs, + pathregs: pathregs, + hostregs: hostregs, + } r.Unlock() return nil } @@ -219,11 +235,10 @@ func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) { var mMatch, hMatch, pMatch bool // 1. try method - methodLoop: for _, m := range ep.apiep.Method { if m == req.Method { mMatch = true - break methodLoop + break } } if !mMatch { @@ -237,15 +252,14 @@ func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) { if len(ep.apiep.Host) == 0 { hMatch = true } else { - hostLoop: for idx, h := range ep.apiep.Host { if h == "" || h == "*" { hMatch = true - break hostLoop + break } else { if ep.hostregs[idx].MatchString(req.URL.Host) { hMatch = true - break hostLoop + break } } } @@ -257,13 +271,12 @@ func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) { logger.Debugf("api host match %s", req.URL.Host) } - // 3. try path - pathLoop: + // 3. try google.api path for _, pathreg := range ep.pathregs { matches, err := pathreg.Match(path, "") if err != nil { if logger.V(logger.DebugLevel, logger.DefaultLogger) { - logger.Debugf("api path not match %s != %v", path, pathreg) + logger.Debugf("api gpath not match %s != %v", path, pathreg) } continue } @@ -278,8 +291,21 @@ func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) { } md["x-api-body"] = ep.apiep.Body *req = *req.Clone(metadata.NewContext(ctx, md)) - break pathLoop + break } + + // 4. try path via pcre path matching + for _, pathreg := range ep.pcreregs { + if !pathreg.MatchString(req.URL.Path) { + if logger.V(logger.DebugLevel, logger.DefaultLogger) { + logger.Debugf("api pcre path not match %s != %v", req.URL.Path, pathreg) + } + continue + } + pMatch = true + break + } + if !pMatch { continue }