Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add glob path matching (an alternative to default prefix matching) #93

Merged
merged 1 commit into from
May 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type UI struct {

type Proxy struct {
Strategy string
Matcher string
MaxConn int
ShutdownWait time.Duration
DialTimeout time.Duration
Expand Down
1 change: 1 addition & 0 deletions config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var Default = &Config{
Proxy: Proxy{
MaxConn: 10000,
Strategy: "rnd",
Matcher: "prefix",
DialTimeout: 30 * time.Second,
LocalIP: LocalIPString(),
},
Expand Down
1 change: 1 addition & 0 deletions config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
2 changes: 2 additions & 0 deletions config/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions fabio.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 32 additions & 4 deletions route/matcher.go
Original file line number Diff line number Diff line change
@@ -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
}
56 changes: 56 additions & 0 deletions route/matcher_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}