From 84076db78ef9f3a92ebf822485d46100236a1907 Mon Sep 17 00:00:00 2001 From: Fabrice CLAEYS Date: Mon, 6 Jun 2016 09:21:00 +0200 Subject: [PATCH 1/3] allow multiple rules --- rules.go | 74 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/rules.go b/rules.go index e6b299d86..94f0b17f4 100644 --- a/rules.go +++ b/rules.go @@ -109,39 +109,53 @@ func (r *Rules) Parse(expression string) (*mux.Route, error) { f := func(c rune) bool { return c == ':' } - // get function - parsedFunctions := strings.FieldsFunc(expression, f) - if len(parsedFunctions) == 0 { - return nil, errors.New("Error parsing rule: " + expression) - } - parsedFunction, ok := functions[parsedFunctions[0]] - if !ok { - return nil, errors.New("Error parsing rule: " + expression + ". Unknown function: " + parsedFunctions[0]) - } - parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...) - fargs := func(c rune) bool { - return c == ',' || c == ';' - } - // get function - parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs) - if len(parsedArgs) == 0 { - return nil, errors.New("Error parsing args from rule: " + expression) + + // Allow multiple rules separated by ; + splitRule := func(c rune) bool { + return c == ';' } - inputs := make([]reflect.Value, len(parsedArgs)) - for i := range parsedArgs { - inputs[i] = reflect.ValueOf(parsedArgs[i]) - } - method := reflect.ValueOf(parsedFunction) - if method.IsValid() { - resultRoute := method.Call(inputs)[0].Interface().(*mux.Route) - if r.err != nil { - return nil, r.err + parsedRules := strings.FieldsFunc(expression, splitRule) + + var resultRoute *mux.Route + + for _, rule := range parsedRules { + // get function + parsedFunctions := strings.FieldsFunc(rule, f) + if len(parsedFunctions) == 0 { + return nil, errors.New("Error parsing rule: " + rule) } - if resultRoute.GetError() != nil { - return nil, resultRoute.GetError() + parsedFunction, ok := functions[parsedFunctions[0]] + if !ok { + return nil, errors.New("Error parsing rule: " + rule + ". Unknown function: " + parsedFunctions[0]) + } + parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...) + fargs := func(c rune) bool { + return c == ',' + } + // get function + parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs) + if len(parsedArgs) == 0 { + return nil, errors.New("Error parsing args from rule: " + rule) + } + + inputs := make([]reflect.Value, len(parsedArgs)) + for i := range parsedArgs { + inputs[i] = reflect.ValueOf(parsedArgs[i]) + } + method := reflect.ValueOf(parsedFunction) + if method.IsValid() { + resultRoute = method.Call(inputs)[0].Interface().(*mux.Route) + if r.err != nil { + return nil, r.err + } + if resultRoute.GetError() != nil { + return nil, resultRoute.GetError() + } + + } else { + return nil, errors.New("Method not found: " + parsedFunctions[0]) } - return resultRoute, nil } - return nil, errors.New("Method not found: " + parsedFunctions[0]) + return resultRoute, nil } From 78dc28cce84b72a3662e52f6d2b027cc3e284716 Mon Sep 17 00:00:00 2001 From: Fabrice CLAEYS Date: Mon, 6 Jun 2016 15:27:16 +0200 Subject: [PATCH 2/3] test rules parsing --- rules_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 rules_test.go diff --git a/rules_test.go b/rules_test.go new file mode 100644 index 000000000..cb555a4f3 --- /dev/null +++ b/rules_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "github.com/gorilla/mux" + "net/http" + "testing" +) + +func TestParseOneRule(t *testing.T) { + + router := mux.NewRouter() + route := router.NewRoute() + serverRoute := &serverRoute{route: route} + rules := &Rules{route: serverRoute} + + expression := "Host:foo.bar" + routeResult, err := rules.Parse(expression) + + if err != nil { + t.Fatal("Error while building route for Host:foo.bar") + } + + request, err := http.NewRequest("GET", "http://foo.bar", nil) + routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult}) + + if routeMatch == false { + t.Log(err) + t.Fatal("Rule Host:foo.bar don't match") + } +} + +func TestParseTwoRules(t *testing.T) { + + router := mux.NewRouter() + route := router.NewRoute() + serverRoute := &serverRoute{route: route} + rules := &Rules{route: serverRoute} + + expression := "Host:foo.bar;Path:/foobar" + routeResult, err := rules.Parse(expression) + + if err != nil { + t.Fatal("Error while building route for Host:foo.bar;Path:/foobar") + } + + request, err := http.NewRequest("GET", "http://foo.bar/foobar", nil) + routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult}) + + if routeMatch == false { + t.Log(err) + t.Fatal("Rule Host:foo.bar;Path:/foobar don't match") + } +} From eccb5296058e2d4d39fab508ed2a82ce2ccc9103 Mon Sep 17 00:00:00 2001 From: Fabrice CLAEYS Date: Mon, 6 Jun 2016 16:07:21 +0200 Subject: [PATCH 3/3] update docs --- docs/basics.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/basics.md b/docs/basics.md index 73d9089ac..2b6331824 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -69,6 +69,8 @@ Frontends can be defined using the following rules: - `PathPrefix`: PathPrefix adds a matcher for the URL path prefixes. This matches if the given template is a prefix of the full URL path. - `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path. +You can use multiple rules by separating them by `;` + You can optionally enable `passHostHeader` to forward client `Host` header to the backend. Here is an example of frontends definition: @@ -87,13 +89,13 @@ Here is an example of frontends definition: rule = "Host: localhost, {subdomain:[a-z]+}.localhost" [frontends.frontend3] backend = "backend2" - rule = "Path:/test" + rule = "Host: test3.localhost;Path:/test" ``` - Three frontends are defined: `frontend1`, `frontend2` and `frontend3` - `frontend1` will forward the traffic to the `backend2` if the rule `Host: test.localhost, test2.localhost` is matched - `frontend2` will forward the traffic to the `backend1` if the rule `Host: localhost, {subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend) -- `frontend3` will forward the traffic to the `backend2` if the rule `Path:/test` is matched +- `frontend3` will forward the traffic to the `backend2` if the rules `Host: test3.localhost` and `Path:/test` are matched ## Backends