api/router: support pcre and google.api pattern matching (#1549)

* api/router: support pcre and google.api pattern matching

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2020-04-19 00:31:34 +03:00 committed by GitHub
parent ecbc42755c
commit f00fd7a49e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 28 deletions

View File

@ -23,6 +23,7 @@ import (
type endpoint struct { type endpoint struct {
hostregs []*regexp.Regexp hostregs []*regexp.Regexp
pathregs []util.Pattern pathregs []util.Pattern
pcreregs []*regexp.Regexp
} }
// router is the default router // router is the default router
@ -185,13 +186,23 @@ func (r *registryRouter) store(services []*registry.Service) {
} }
for _, p := range ep.Endpoint.Path { 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) rule, err := util.Parse(p)
if err != nil { if err != nil && !pcreok {
if logger.V(logger.TraceLevel, logger.DefaultLogger) { if logger.V(logger.TraceLevel, logger.DefaultLogger) {
logger.Tracef("endpoint have invalid path pattern: %v", err) logger.Tracef("endpoint have invalid path pattern: %v", err)
} }
continue continue
} else if err != nil && pcreok {
continue
} }
tpl := rule.Compile() tpl := rule.Compile()
pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "") pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "")
if err != nil { if err != nil {
@ -303,11 +314,10 @@ func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) {
ep := e.Endpoint ep := e.Endpoint
var mMatch, hMatch, pMatch bool var mMatch, hMatch, pMatch bool
// 1. try method // 1. try method
methodLoop:
for _, m := range ep.Method { for _, m := range ep.Method {
if m == req.Method { if m == req.Method {
mMatch = true mMatch = true
break methodLoop break
} }
} }
if !mMatch { if !mMatch {
@ -321,15 +331,14 @@ func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) {
if len(ep.Host) == 0 { if len(ep.Host) == 0 {
hMatch = true hMatch = true
} else { } else {
hostLoop:
for idx, h := range ep.Host { for idx, h := range ep.Host {
if h == "" || h == "*" { if h == "" || h == "*" {
hMatch = true hMatch = true
break hostLoop break
} else { } else {
if cep.hostregs[idx].MatchString(req.URL.Host) { if cep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true 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) logger.Debugf("api host match %s", req.URL.Host)
} }
// 3. try path // 3. try path via google.api path matching
// 3. try path
pathLoop:
for _, pathreg := range cep.pathregs { for _, pathreg := range cep.pathregs {
matches, err := pathreg.Match(path, "") matches, err := pathreg.Match(path, "")
if err != nil { if err != nil {
if logger.V(logger.DebugLevel, logger.DefaultLogger) { 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 continue
} }
@ -363,8 +370,21 @@ func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) {
} }
md["x-api-body"] = ep.Body md["x-api-body"] = ep.Body
*req = *req.Clone(metadata.NewContext(ctx, md)) *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 { if !pMatch {
continue continue
} }

View File

@ -58,8 +58,8 @@ func initial(t *testing.T) (server.Server, client.Client) {
return s, c return s, c
} }
func check(addr string, t *testing.T) { func check(t *testing.T, addr string, path string, expected string) {
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/api/v0/test/call/TEST", addr), nil) req, err := http.NewRequest("POST", fmt.Sprintf(path, addr), nil)
if err != nil { if err != nil {
t.Fatalf("Failed to created http.Request: %v", err) t.Fatalf("Failed to created http.Request: %v", err)
} }
@ -75,7 +75,7 @@ func check(addr string, t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
jsonMsg := `{"msg":"Hello TEST"}` jsonMsg := expected
if string(buf) != jsonMsg { if string(buf) != jsonMsg {
t.Fatalf("invalid message received, parsing error %s != %s", 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() defer hsrv.Close()
time.Sleep(1 * time.Second) 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) s, c := initial(t)
defer s.Stop() defer s.Stop()
@ -149,5 +190,5 @@ func TestRouterStatic(t *testing.T) {
defer hsrv.Close() defer hsrv.Close()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
check(hsrv.Addr, t) check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`)
} }

View File

@ -21,6 +21,7 @@ type endpoint struct {
apiep *api.Endpoint apiep *api.Endpoint
hostregs []*regexp.Regexp hostregs []*regexp.Regexp
pathregs []util.Pattern pathregs []util.Pattern
pcreregs []*regexp.Regexp
} }
// router is the default router // router is the default router
@ -94,6 +95,7 @@ func (r *staticRouter) Register(ep *api.Endpoint) error {
var pathregs []util.Pattern var pathregs []util.Pattern
var hostregs []*regexp.Regexp var hostregs []*regexp.Regexp
var pcreregs []*regexp.Regexp
for _, h := range ep.Host { for _, h := range ep.Host {
if h == "" || h == "*" { if h == "" || h == "*" {
@ -107,9 +109,18 @@ func (r *staticRouter) Register(ep *api.Endpoint) error {
} }
for _, p := range ep.Path { 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) rule, err := util.Parse(p)
if err != nil { if err != nil && !pcreok {
return err return err
} else if err != nil && pcreok {
continue
} }
tpl := rule.Compile() tpl := rule.Compile()
pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "") 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.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() r.Unlock()
return nil return nil
} }
@ -219,11 +235,10 @@ func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) {
var mMatch, hMatch, pMatch bool var mMatch, hMatch, pMatch bool
// 1. try method // 1. try method
methodLoop:
for _, m := range ep.apiep.Method { for _, m := range ep.apiep.Method {
if m == req.Method { if m == req.Method {
mMatch = true mMatch = true
break methodLoop break
} }
} }
if !mMatch { if !mMatch {
@ -237,15 +252,14 @@ func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) {
if len(ep.apiep.Host) == 0 { if len(ep.apiep.Host) == 0 {
hMatch = true hMatch = true
} else { } else {
hostLoop:
for idx, h := range ep.apiep.Host { for idx, h := range ep.apiep.Host {
if h == "" || h == "*" { if h == "" || h == "*" {
hMatch = true hMatch = true
break hostLoop break
} else { } else {
if ep.hostregs[idx].MatchString(req.URL.Host) { if ep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true 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) logger.Debugf("api host match %s", req.URL.Host)
} }
// 3. try path // 3. try google.api path
pathLoop:
for _, pathreg := range ep.pathregs { for _, pathreg := range ep.pathregs {
matches, err := pathreg.Match(path, "") matches, err := pathreg.Match(path, "")
if err != nil { if err != nil {
if logger.V(logger.DebugLevel, logger.DefaultLogger) { 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 continue
} }
@ -278,8 +291,21 @@ func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) {
} }
md["x-api-body"] = ep.apiep.Body md["x-api-body"] = ep.apiep.Body
*req = *req.Clone(metadata.NewContext(ctx, md)) *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 { if !pMatch {
continue continue
} }