diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4ba232c0..0cf1864f 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -102,7 +102,7 @@ }, { "ImportPath": "github.com/mailgun/route", - "Rev": "85cf368efd247dabbcc2e53272ef4276f94a87c4" + "Rev": "77565948d9ac90b156eb3a5e5d7d42e6482d7f27" }, { "ImportPath": "github.com/mailgun/scroll", diff --git a/Godeps/_workspace/src/github.com/mailgun/route/matcher.go b/Godeps/_workspace/src/github.com/mailgun/route/matcher.go index b0444413..fbc02613 100644 --- a/Godeps/_workspace/src/github.com/mailgun/route/matcher.go +++ b/Godeps/_workspace/src/github.com/mailgun/route/matcher.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "regexp" + "strings" ) type matcher interface { @@ -18,11 +19,11 @@ type matcher interface { } func hostTrieMatcher(hostname string) (matcher, error) { - return newTrieMatcher(hostname, &hostMapper{}, &match{}) + return newTrieMatcher(strings.ToLower(hostname), &hostMapper{}, &match{}) } func hostRegexpMatcher(hostname string) (matcher, error) { - return newRegexpMatcher(hostname, &hostMapper{}, &match{}) + return newRegexpMatcher(strings.ToLower(hostname), &hostMapper{}, &match{}) } func methodTrieMatcher(method string) (matcher, error) { @@ -105,7 +106,7 @@ func (a *andMatcher) match(req *http.Request) *match { // Regular expression matcher, takes a regular expression and requestMapper type regexpMatcher struct { - // Uses this mapper to extract a string from a request to match agains + // Uses this mapper to extract a string from a request to match against mapper requestMapper // Compiled regular expression expr *regexp.Regexp diff --git a/Godeps/_workspace/src/github.com/mailgun/route/matcher_test.go b/Godeps/_workspace/src/github.com/mailgun/route/matcher_test.go new file mode 100644 index 00000000..13227ae2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mailgun/route/matcher_test.go @@ -0,0 +1,40 @@ +package route + +import ( + "net/http" + "testing" + + . "github.com/mailgun/vulcand/Godeps/_workspace/src/gopkg.in/check.v1" +) + +func TestMatcher(t *testing.T) { TestingT(t) } + +type MatcherSuite struct { +} + +var _ = Suite(&MatcherSuite{}) + +func (s *MatcherSuite) TestHostnameCase(c *C) { + var matcher1, matcher2 matcher + var req *http.Request + var err error + + req, err = http.NewRequest("GET", "http://example.com", nil) + c.Assert(err, IsNil) + + matcher1, err = hostTrieMatcher("example.com") + c.Assert(err, IsNil) + matcher2, err = hostTrieMatcher("Example.Com") + c.Assert(err, IsNil) + + c.Assert(matcher1.match(req), Not(IsNil)) + c.Assert(matcher2.match(req), Not(IsNil)) + + matcher1, err = hostRegexpMatcher(`.*example.com`) + c.Assert(err, IsNil) + matcher2, err = hostRegexpMatcher(`.*Example.Com`) + c.Assert(err, IsNil) + + c.Assert(matcher1.match(req), Not(IsNil)) + c.Assert(matcher2.match(req), Not(IsNil)) +} diff --git a/Godeps/_workspace/src/github.com/mailgun/route/mux.go b/Godeps/_workspace/src/github.com/mailgun/route/mux.go index 0f45b75a..16498cc6 100644 --- a/Godeps/_workspace/src/github.com/mailgun/route/mux.go +++ b/Godeps/_workspace/src/github.com/mailgun/route/mux.go @@ -8,7 +8,7 @@ import ( // Mux implements router compatible with http.Handler type Mux struct { // NotFound sets handler for routes that are not found - NotFound http.Handler + notFound http.Handler router Router } @@ -16,7 +16,7 @@ type Mux struct { func NewMux() *Mux { return &Mux{ router: New(), - NotFound: &NotFound{}, + notFound: ¬Found{}, } } @@ -38,20 +38,37 @@ func (m *Mux) Remove(expr string) error { func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { h, err := m.router.Route(r) if err != nil || h == nil { - m.NotFound.ServeHTTP(w, r) + m.notFound.ServeHTTP(w, r) return } h.(http.Handler).ServeHTTP(w, r) } +func (m *Mux) SetNotFound(n http.Handler) error { + if n == nil { + return fmt.Errorf("Not Found handler cannot be nil. Operation rejected.") + } + m.notFound = n + return nil +} + +func (m *Mux) GetNotFound() http.Handler { + return m.notFound +} + +func (m *Mux) IsValid(expr string) bool { + return IsValid(expr) +} + // NotFound is a generic http.Handler for request -type NotFound struct { +type notFound struct { } // ServeHTTP returns a simple 404 Not found response -func (NotFound) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (notFound) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusNotFound) fmt.Fprint(w, "Not found") } + diff --git a/api/api.go b/api/api.go index 93e97d8b..9d738a70 100644 --- a/api/api.go +++ b/api/api.go @@ -12,6 +12,7 @@ import ( "github.com/mailgun/vulcand/anomaly" "github.com/mailgun/vulcand/engine" "github.com/mailgun/vulcand/plugin" + "github.com/mailgun/vulcand/router" ) type ProxyController struct { @@ -271,7 +272,7 @@ func (c *ProxyController) getBackend(w http.ResponseWriter, r *http.Request, par } func (c *ProxyController) upsertFrontend(w http.ResponseWriter, r *http.Request, params map[string]string, body []byte) (interface{}, error) { - frontend, ttl, err := parseFrontendPack(body) + frontend, ttl, err := parseFrontendPack(c.ng.GetRegistry().GetRouter(), body) if err != nil { return nil, formatError(err) } @@ -471,7 +472,7 @@ func parseBackendPack(v []byte) (*engine.Backend, error) { return engine.BackendFromJSON(bp.Backend) } -func parseFrontendPack(v []byte) (*engine.Frontend, time.Duration, error) { +func parseFrontendPack(router router.Router, v []byte) (*engine.Frontend, time.Duration, error) { var fp frontendReadPack if err := json.Unmarshal(v, &fp); err != nil { return nil, 0, err @@ -479,7 +480,7 @@ func parseFrontendPack(v []byte) (*engine.Frontend, time.Duration, error) { if len(fp.Frontend) == 0 { return nil, 0, &scroll.MissingFieldError{Field: "Frontend"} } - f, err := engine.FrontendFromJSON(fp.Frontend) + f, err := engine.FrontendFromJSON(router, fp.Frontend) if err != nil { return nil, 0, err } diff --git a/api/api_test.go b/api/api_test.go index e4d2fdda..5f636cb3 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -207,7 +207,7 @@ func (s *ApiSuite) TestFrontendCRUD(c *C) { c.Assert(s.client.UpsertBackend(*b), IsNil) - f, err := engine.NewHTTPFrontend("f1", b.Id, `Path("/")`, engine.HTTPFrontendSettings{}) + f, err := engine.NewHTTPFrontend(s.ng.GetRegistry().GetRouter(), "f1", b.Id, `Path("/")`, engine.HTTPFrontendSettings{}) c.Assert(err, IsNil) fk := engine.FrontendKey{Id: f.Id} @@ -260,7 +260,7 @@ func (s *ApiSuite) TestMiddlewareCRUD(c *C) { c.Assert(s.client.UpsertBackend(*b), IsNil) - f, err := engine.NewHTTPFrontend("f1", b.Id, `Path("/")`, engine.HTTPFrontendSettings{}) + f, err := engine.NewHTTPFrontend(s.ng.GetRegistry().GetRouter(), "f1", b.Id, `Path("/")`, engine.HTTPFrontendSettings{}) c.Assert(err, IsNil) fk := engine.FrontendKey{Id: f.Id} diff --git a/api/client.go b/api/client.go index e2ba1724..0d370c14 100644 --- a/api/client.go +++ b/api/client.go @@ -109,7 +109,7 @@ func (c *Client) GetFrontend(fk engine.FrontendKey) (*engine.Frontend, error) { if err != nil { return nil, err } - return engine.FrontendFromJSON(response) + return engine.FrontendFromJSON(c.Registry.GetRouter(), response) } func (c *Client) GetFrontends() ([]engine.Frontend, error) { @@ -117,7 +117,7 @@ func (c *Client) GetFrontends() ([]engine.Frontend, error) { if err != nil { return nil, err } - return engine.FrontendsFromJSON(data) + return engine.FrontendsFromJSON(c.Registry.GetRouter(), data) } func (c *Client) TopFrontends(bk *engine.BackendKey, limit int) ([]engine.Frontend, error) { @@ -131,7 +131,7 @@ func (c *Client) TopFrontends(bk *engine.BackendKey, limit int) ([]engine.Fronte if err != nil { return nil, err } - return engine.FrontendsFromJSON(response) + return engine.FrontendsFromJSON(c.Registry.GetRouter(), response) } func (c *Client) DeleteFrontend(fk engine.FrontendKey) error { diff --git a/engine/etcdng/etcd.go b/engine/etcdng/etcd.go index 49f1a2bc..0d6ce5b4 100644 --- a/engine/etcdng/etcd.go +++ b/engine/etcdng/etcd.go @@ -243,7 +243,7 @@ func (n *ng) GetFrontend(key engine.FrontendKey) (*engine.Frontend, error) { if err != nil { return nil, err } - return engine.FrontendFromJSON([]byte(bytes), key.Id) + return engine.FrontendFromJSON(n.registry.GetRouter(), []byte(bytes), key.Id) } func (n *ng) DeleteFrontend(fk engine.FrontendKey) error { diff --git a/engine/json.go b/engine/json.go index 695c16ac..f80dd365 100644 --- a/engine/json.go +++ b/engine/json.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/mailgun/vulcand/plugin" + "github.com/mailgun/vulcand/router" ) type rawServers struct { @@ -73,7 +74,7 @@ func HostsFromJSON(in []byte) ([]Host, error) { return out, nil } -func FrontendsFromJSON(in []byte) ([]Frontend, error) { +func FrontendsFromJSON(router router.Router, in []byte) ([]Frontend, error) { var rf *rawFrontends err := json.Unmarshal(in, &rf) if err != nil { @@ -81,7 +82,7 @@ func FrontendsFromJSON(in []byte) ([]Frontend, error) { } out := make([]Frontend, len(rf.Frontends)) for i, raw := range rf.Frontends { - f, err := FrontendFromJSON(raw) + f, err := FrontendFromJSON(router, raw) if err != nil { return nil, err } @@ -147,7 +148,7 @@ func KeyPairFromJSON(in []byte) (*KeyPair, error) { return NewKeyPair(c.Cert, c.Key) } -func FrontendFromJSON(in []byte, id ...string) (*Frontend, error) { +func FrontendFromJSON(router router.Router, in []byte, id ...string) (*Frontend, error) { var rf *rawFrontend if err := json.Unmarshal(in, &rf); err != nil { return nil, err @@ -164,7 +165,7 @@ func FrontendFromJSON(in []byte, id ...string) (*Frontend, error) { if len(id) != 0 { rf.Id = id[0] } - f, err := NewHTTPFrontend(rf.Id, rf.BackendId, rf.Route, s) + f, err := NewHTTPFrontend(router, rf.Id, rf.BackendId, rf.Route, s) if err != nil { return nil, err } diff --git a/engine/model.go b/engine/model.go index e654394e..57646c59 100644 --- a/engine/model.go +++ b/engine/model.go @@ -14,6 +14,7 @@ import ( "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/oxy/stream" "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/route" "github.com/mailgun/vulcand/plugin" + "github.com/mailgun/vulcand/router" ) // StatsProvider provides realtime stats abount endpoints, backends and locations @@ -267,13 +268,13 @@ func NewListener(id, protocol, network, address, scope string, settings *HTTPSLi }, nil } -func NewHTTPFrontend(id, backendId string, routeExpr string, settings HTTPFrontendSettings) (*Frontend, error) { +func NewHTTPFrontend(router router.Router, id, backendId string, routeExpr string, settings HTTPFrontendSettings) (*Frontend, error) { if len(id) == 0 || len(backendId) == 0 { return nil, fmt.Errorf("supply valid route, id, and backendId") } // Make sure location path is a valid route expression - if !route.IsValid(routeExpr) { + if !router.IsValid(routeExpr) { return nil, fmt.Errorf("route should be a valid route expression: %s", routeExpr) } diff --git a/engine/model_test.go b/engine/model_test.go index c48175f9..1a371e67 100644 --- a/engine/model_test.go +++ b/engine/model_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/route" . "github.com/mailgun/vulcand/Godeps/_workspace/src/gopkg.in/check.v1" "github.com/mailgun/vulcand/plugin" "github.com/mailgun/vulcand/plugin/connlimit" @@ -33,7 +34,7 @@ func (s *BackendSuite) TestHostBad(c *C) { } func (s *BackendSuite) TestFrontendDefaults(c *C) { - f, err := NewHTTPFrontend("f1", "b1", `Path("/home")`, HTTPFrontendSettings{}) + f, err := NewHTTPFrontend(route.NewMux(), "f1", "b1", `Path("/home")`, HTTPFrontendSettings{}) c.Assert(err, IsNil) c.Assert(f.GetId(), Equals, "f1") c.Assert(f.String(), Not(Equals), "") @@ -50,7 +51,7 @@ func (s *BackendSuite) TestNewFrontendWithOptions(c *C) { Hostname: "host1", TrustForwardHeader: true, } - f, err := NewHTTPFrontend("f1", "b1", `Path("/home")`, settings) + f, err := NewHTTPFrontend(route.NewMux(), "f1", "b1", `Path("/home")`, settings) c.Assert(err, IsNil) c.Assert(f.Id, Equals, "f1") @@ -66,11 +67,11 @@ func (s *BackendSuite) TestNewFrontendWithOptions(c *C) { func (s *BackendSuite) TestFrontendBadParams(c *C) { // Bad route - _, err := NewHTTPFrontend("f1", "b1", "/home -- afawf \\~", HTTPFrontendSettings{}) + _, err := NewHTTPFrontend(route.NewMux(), "f1", "b1", "/home -- afawf \\~", HTTPFrontendSettings{}) c.Assert(err, NotNil) // Empty params - _, err = NewHTTPFrontend("", "", "", HTTPFrontendSettings{}) + _, err = NewHTTPFrontend(route.NewMux(), "", "", "", HTTPFrontendSettings{}) c.Assert(err, NotNil) } @@ -81,7 +82,7 @@ func (s *BackendSuite) TestFrontendBadOptions(c *C) { }, } for _, s := range settings { - f, err := NewHTTPFrontend("f1", "b", `Path("/home")`, s) + f, err := NewHTTPFrontend(route.NewMux(), "f1", "b", `Path("/home")`, s) c.Assert(err, NotNil) c.Assert(f, IsNil) } @@ -317,7 +318,7 @@ func (s *BackendSuite) TestNewListenerBadParams(c *C) { } func (s *BackendSuite) TestFrontendsFromJSON(c *C) { - f, err := NewHTTPFrontend("f1", "b1", `Path("/path")`, HTTPFrontendSettings{}) + f, err := NewHTTPFrontend(route.NewMux(), "f1", "b1", `Path("/path")`, HTTPFrontendSettings{}) c.Assert(err, IsNil) bytes, err := json.Marshal(f) @@ -329,7 +330,7 @@ func (s *BackendSuite) TestFrontendsFromJSON(c *C) { r := plugin.NewRegistry() c.Assert(r.AddSpec(connlimit.GetSpec()), IsNil) - out, err := FrontendsFromJSON(bytes) + out, err := FrontendsFromJSON(route.NewMux(), bytes) c.Assert(err, IsNil) c.Assert(out, NotNil) c.Assert(out, DeepEquals, fs) diff --git a/plugin/middleware.go b/plugin/middleware.go index 4d8bcc9c..54f674c1 100644 --- a/plugin/middleware.go +++ b/plugin/middleware.go @@ -5,8 +5,9 @@ import ( "fmt" "net/http" "reflect" - + "github.com/mailgun/vulcand/router" "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/route" ) // Middleware specification, used to construct new middlewares and plug them into CLI API and backends @@ -55,11 +56,13 @@ type SpecGetter func(string) *MiddlewareSpec type Registry struct { specs []*MiddlewareSpec notFound Middleware + router router.Router } func NewRegistry() *Registry { return &Registry{ specs: []*MiddlewareSpec{}, + router: route.NewMux(), } } @@ -99,6 +102,15 @@ func (r *Registry) GetNotFoundMiddleware() Middleware { return r.notFound } +func (r *Registry) SetRouter(router router.Router) error { + r.router = router + return nil +} + +func (r *Registry) GetRouter() router.Router { + return r.router +} + func verifySignature(fn interface{}) error { t := reflect.TypeOf(fn) if t == nil || t.Kind() != reflect.Func { diff --git a/proxy/mux.go b/proxy/mux.go index c9b7687c..f4c32ceb 100644 --- a/proxy/mux.go +++ b/proxy/mux.go @@ -9,6 +9,7 @@ import ( "github.com/mailgun/vulcand/engine" "github.com/mailgun/vulcand/stapler" + "github.com/mailgun/vulcand/router" "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/log" "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/metrics" @@ -40,7 +41,7 @@ type mux struct { mtx *sync.RWMutex // Router will be shared between mulitple listeners - router *route.Mux + router router.Router // Current server stats state muxState @@ -71,7 +72,7 @@ func New(id int, st stapler.Stapler, o Options) (*mux, error) { options: o, - router: route.NewMux(), + router: o.Router, connTracker: newConnTracker(), servers: make(map[engine.ListenerKey]*srv), @@ -84,10 +85,10 @@ func New(id int, st stapler.Stapler, o Options) (*mux, error) { stapler: st, } - m.router.NotFound = &DefaultNotFound{} + m.router.SetNotFound(&DefaultNotFound{}) if o.NotFoundMiddleware != nil { - if handler, err := o.NotFoundMiddleware.NewHandler(m.router.NotFound); err == nil { - m.router.NotFound = handler + if handler, err := o.NotFoundMiddleware.NewHandler(m.router.GetNotFound()); err == nil { + m.router.SetNotFound(handler) } } @@ -605,6 +606,9 @@ func setDefaults(o Options) Options { if o.TimeProvider == nil { o.TimeProvider = &timetools.RealTime{} } + if o.Router == nil { + o.Router = route.NewMux() + } return o } diff --git a/proxy/mux_test.go b/proxy/mux_test.go index 14842060..f78e182a 100644 --- a/proxy/mux_test.go +++ b/proxy/mux_test.go @@ -1049,7 +1049,7 @@ func (s *ServerSuite) TestCustomNotFound(c *C) { st := stapler.New() m, err := New(s.lastId, st, Options{NotFoundMiddleware: &appender{append: "Custom Not Found handler"}}) c.Assert(err, IsNil) - t := reflect.TypeOf(m.router.NotFound) + t := reflect.TypeOf(m.router.GetNotFound()) c.Assert(t.String(), Equals, "*proxy.appender") } diff --git a/proxy/proxy.go b/proxy/proxy.go index 7f0aef63..f7a60ca2 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -10,6 +10,7 @@ import ( "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/timetools" "github.com/mailgun/vulcand/engine" "github.com/mailgun/vulcand/plugin" + "github.com/mailgun/vulcand/router" ) type Proxy interface { @@ -56,6 +57,7 @@ type Options struct { Files []*FileDescriptor TimeProvider timetools.TimeProvider NotFoundMiddleware plugin.Middleware + Router router.Router } type NewProxyFn func(id int) (Proxy, error) diff --git a/proxy/srv.go b/proxy/srv.go index 353f1ede..79c5b362 100644 --- a/proxy/srv.go +++ b/proxy/srv.go @@ -297,7 +297,7 @@ func scopedHandler(scope string, proxy http.Handler) (http.Handler, error) { return proxy, nil } mux := route.NewMux() - mux.NotFound = &DefaultNotFound{} + mux.SetNotFound(&DefaultNotFound{}) if err := mux.Handle(scope, proxy); err != nil { return nil, err } diff --git a/router/router.go b/router/router.go new file mode 100644 index 00000000..0ceb887e --- /dev/null +++ b/router/router.go @@ -0,0 +1,25 @@ +package router +import "net/http" + +//This interface captures all routing functionality required by vulcan. +//The routing functionality mainly comes from "github.com/mailgun/route", +type Router interface { + + //Sets the not-found handler (this handler is called when no other handlers/routes in the routing library match + SetNotFound(http.Handler) error + + //Gets the not-found handler that is currently in use by this router. + GetNotFound() http.Handler + + //Validates whether this is an acceptable route expression + IsValid(string) bool + + //Adds a new route->handler combination. The route is a string which provides the routing expression. http.Handler is called when this expression matches a request. + Handle(string, http.Handler) error + + //Removes a route. The http.Handler associated with it, will be discarded. + Remove(string) error + + //ServiceHTTP is the http.Handler implementation that allows callers to route their calls to sub-http.Handlers based on route matches. + ServeHTTP(http.ResponseWriter, *http.Request) +} \ No newline at end of file diff --git a/service/service.go b/service/service.go index b9d2a582..ab965aa0 100644 --- a/service/service.go +++ b/service/service.go @@ -307,6 +307,7 @@ func (s *Service) newProxy(id int) (proxy.Proxy, error) { MaxHeaderBytes: s.options.ServerMaxHeaderBytes, DefaultListener: constructDefaultListener(s.options), NotFoundMiddleware: s.registry.GetNotFoundMiddleware(), + Router: s.registry.GetRouter(), }) } diff --git a/testutils/testutils.go b/testutils/testutils.go index a5aba367..2b1d2228 100644 --- a/testutils/testutils.go +++ b/testutils/testutils.go @@ -8,6 +8,7 @@ import ( "net/http/httptest" "sync/atomic" + routelib "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/route" "github.com/mailgun/vulcand/engine" "github.com/mailgun/vulcand/plugin/ratelimit" ) @@ -108,7 +109,7 @@ func MakeListener(addr string, protocol string) engine.Listener { } func MakeFrontend(route string, backendId string) engine.Frontend { - f, err := engine.NewHTTPFrontend(UID("frontend"), backendId, route, engine.HTTPFrontendSettings{}) + f, err := engine.NewHTTPFrontend(routelib.NewMux(), UID("frontend"), backendId, route, engine.HTTPFrontendSettings{}) if err != nil { panic(err) } diff --git a/vctl/command/frontend.go b/vctl/command/frontend.go index 667b5892..d666c562 100644 --- a/vctl/command/frontend.go +++ b/vctl/command/frontend.go @@ -2,6 +2,7 @@ package command import ( "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/codegangsta/cli" + "github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/route" "github.com/mailgun/vulcand/engine" ) @@ -78,7 +79,7 @@ func (cmd *Command) upsertFrontendAction(c *cli.Context) { cmd.printError(err) return } - f, err := engine.NewHTTPFrontend(c.String("id"), c.String("b"), c.String("route"), settings) + f, err := engine.NewHTTPFrontend(route.NewMux(), c.String("id"), c.String("b"), c.String("route"), settings) if err != nil { cmd.printError(err) return