From c183ca00aaa537f17fe3c9db338a62d856904e49 Mon Sep 17 00:00:00 2001 From: Dara Kong Date: Thu, 5 May 2016 04:13:03 -0700 Subject: [PATCH] Add glob path matching (#93) Using "glob" matching, a backend server can register for "/foo.*" to handle routes such as "/foo.bar" and "/foo.bar.baz". The default option is still prefix matching (original behavior) --- config/config.go | 1 + config/default.go | 1 + config/load.go | 1 + config/load_test.go | 2 ++ fabio.properties | 10 ++++++++ main.go | 5 ++++ route/matcher.go | 36 ++++++++++++++++++++++++---- route/matcher_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 route/matcher_test.go diff --git a/config/config.go b/config/config.go index c14c907b8..3239a3252 100644 --- a/config/config.go +++ b/config/config.go @@ -29,6 +29,7 @@ type UI struct { type Proxy struct { Strategy string + Matcher string MaxConn int ShutdownWait time.Duration DialTimeout time.Duration diff --git a/config/default.go b/config/default.go index 40f1655a1..0193cacb4 100644 --- a/config/default.go +++ b/config/default.go @@ -9,6 +9,7 @@ var Default = &Config{ Proxy: Proxy{ MaxConn: 10000, Strategy: "rnd", + Matcher: "prefix", DialTimeout: 30 * time.Second, LocalIP: LocalIPString(), }, diff --git a/config/load.go b/config/load.go index e4b28e3e9..12e48afad 100644 --- a/config/load.go +++ b/config/load.go @@ -38,6 +38,7 @@ func fromProperties(p *properties.Properties) (cfg *Config, err error) { cfg.Proxy = Proxy{ MaxConn: intVal(p, Default.Proxy.MaxConn, "proxy.maxconn"), Strategy: stringVal(p, Default.Proxy.Strategy, "proxy.strategy"), + Matcher: stringVal(p, Default.Proxy.Matcher, "proxy.matcher"), ShutdownWait: durationVal(p, Default.Proxy.ShutdownWait, "proxy.shutdownwait"), DialTimeout: durationVal(p, Default.Proxy.DialTimeout, "proxy.dialtimeout"), ResponseHeaderTimeout: durationVal(p, Default.Proxy.ResponseHeaderTimeout, "proxy.timeout"), diff --git a/config/load_test.go b/config/load_test.go index d636c48a0..a22004104 100644 --- a/config/load_test.go +++ b/config/load_test.go @@ -15,6 +15,7 @@ func TestFromProperties(t *testing.T) { proxy.addr = :1234 proxy.localip = 4.4.4.4 proxy.strategy = rr +proxy.matcher = prefix proxy.shutdownwait = 500ms proxy.timeout = 3s proxy.dialtimeout = 60s @@ -50,6 +51,7 @@ ui.title = fabfab MaxConn: 666, LocalIP: "4.4.4.4", Strategy: "rr", + Matcher: "prefix", ShutdownWait: 500 * time.Millisecond, DialTimeout: 60 * time.Second, KeepAliveTimeout: 3 * time.Second, diff --git a/fabio.properties b/fabio.properties index 75ad5b730..895e444db 100644 --- a/fabio.properties +++ b/fabio.properties @@ -60,6 +60,16 @@ # proxy.strategy = rnd +# proxy.matcher configures the path matching algorithm. +# +# prefix: prefix matching +# glob: glob matching +# +# The default is +# +# proxy.matcher = prefix + + # proxy.shutdownwait configures the time for a graceful shutdown. # # After a signal is caught the proxy will immediately suspend diff --git a/main.go b/main.go index 5f09ba7c5..836510a34 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,11 @@ func newProxy(cfg *config.Config) *proxy.Proxy { } log.Printf("[INFO] Using routing strategy %q", cfg.Proxy.Strategy) + if err := route.SetMatcher(cfg.Proxy.Matcher); err != nil { + log.Fatal("[FATAL] ", err) + } + log.Printf("[INFO] Using routing matching %q", cfg.Proxy.Matcher) + tr := &http.Transport{ ResponseHeaderTimeout: cfg.Proxy.ResponseHeaderTimeout, MaxIdleConnsPerHost: cfg.Proxy.MaxConn, diff --git a/route/matcher.go b/route/matcher.go index 1a4801019..8014a895a 100644 --- a/route/matcher.go +++ b/route/matcher.go @@ -1,14 +1,42 @@ package route -import "strings" +import ( + "strings" + "path" + "fmt" + "log" +) // match contains the matcher function var match matcher = prefixMatcher // matcher determines whether a host/path matches a route -type matcher func(path string, r *Route) bool +type matcher func(uri string, r *Route) bool // prefixMatcher matches path to the routes' path. -func prefixMatcher(path string, r *Route) bool { - return strings.HasPrefix(path, r.Path) +func prefixMatcher(uri string, r *Route) bool { + return strings.HasPrefix(uri, r.Path) +} + +// globMatcher matches path to the routes' path using globbing. +func globMatcher(uri string, r *Route) bool { + var hasMatch, err = path.Match(r.Path, uri) + if err != nil { + log.Print("[ERROR] Glob matching error %s for path %s route %s", err, uri, r.Path) + return false + } + return hasMatch +} + +// SetMatcher sets the matcher function for the proxy. +func SetMatcher(s string) error { + switch s { + case "prefix": + match = prefixMatcher + case "glob": + match = globMatcher + default: + return fmt.Errorf("route: invalid matcher: %s", s) + } + return nil } diff --git a/route/matcher_test.go b/route/matcher_test.go new file mode 100644 index 000000000..cacfb9fca --- /dev/null +++ b/route/matcher_test.go @@ -0,0 +1,56 @@ +package route + +import ( + "testing" +) + +func TestPrefixMatcher(t *testing.T) { + routeFoo := newRoute("www.example.com", "/foo") + + tests := []struct { + uri string + want bool + route *Route + }{ + {"/fo", false, routeFoo}, + {"/foo", true, routeFoo}, + {"/fools", true, routeFoo}, + {"/bar", false, routeFoo}, + } + + for _, tt := range tests { + if got := prefixMatcher(tt.uri, tt.route); got != tt.want { + t.Errorf("%s: got %v want %v", tt.uri, got, tt.want) + } + } +} + +func TestGlobMatcher(t *testing.T) { + routeFoo := newRoute("www.example.com", "/foo") + routeFooWild := newRoute("www.example.com", "/foo.*") + + tests := []struct { + uri string + want bool + route *Route + }{ + {"/fo", false, routeFoo}, + {"/foo", true, routeFoo}, + {"/fools", false, routeFoo}, + {"/bar", false, routeFoo}, + + {"/fo", false, routeFooWild}, + {"/foo", false, routeFooWild}, + {"/fools", false, routeFooWild}, + {"/foo.", true, routeFooWild}, + {"/foo.a", true, routeFooWild}, + {"/foo.bar", true, routeFooWild}, + {"/foo.bar.baz", true, routeFooWild}, + } + + for _, tt := range tests { + if got := globMatcher(tt.uri, tt.route); got != tt.want { + t.Errorf("%s: got %v want %v", tt.uri, got, tt.want) + } + } +}