diff --git a/caddyhttp/httpserver/middleware.go b/caddyhttp/httpserver/middleware.go index 42de390e..51a23593 100644 --- a/caddyhttp/httpserver/middleware.go +++ b/caddyhttp/httpserver/middleware.go @@ -5,7 +5,6 @@ import ( "net/http" "os" "path" - "strings" "time" ) @@ -48,13 +47,23 @@ type ( // RequestMatcher checks to see if current request should be handled // by underlying handler. - // - // TODO The long term plan is to get all middleware implement this - // interface and have validation done before requests are dispatched - // to each middleware. RequestMatcher interface { Match(r *http.Request) bool } + + // HandlerConfig is a middleware configuration. + // This makes it possible for middlewares to have a common + // configuration interface. + // + // TODO The long term plan is to get all middleware implement this + // interface for configurations. + HandlerConfig interface { + RequestMatcher + BasePath() string + } + + // ConfigSelector selects a configuration. + ConfigSelector []HandlerConfig ) // ServeHTTP implements the Handler interface. @@ -62,6 +71,20 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err return f(w, r) } +// Select selects a Config. +// This chooses the config with the longest length. +func (c ConfigSelector) Select(r *http.Request) (config HandlerConfig) { + for i := range c { + if !c[i].Match(r) { + continue + } + if config == nil || len(c[i].BasePath()) > len(config.BasePath()) { + config = c[i] + } + } + return config +} + // IndexFile looks for a file in /root/fpath/indexFile for each string // in indexFiles. If an index file is found, it returns the root-relative // path to the file and true. If no index file is found, empty string @@ -130,21 +153,6 @@ func initCaseSettings() { } } -// Path represents a URI path. -type Path string - -// Matches checks to see if other matches p. -// -// Path matching will probably not always be a direct -// comparison; this method assures that paths can be -// easily and consistently matched. -func (p Path) Matches(other string) bool { - if CaseSensitivePath { - return strings.HasPrefix(string(p), other) - } - return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(other)) -} - // MergeRequestMatchers merges multiple RequestMatchers into one. // This allows a middleware to use multiple RequestMatchers. func MergeRequestMatchers(matchers ...RequestMatcher) RequestMatcher { diff --git a/caddyhttp/httpserver/path.go b/caddyhttp/httpserver/path.go new file mode 100644 index 00000000..6af5039d --- /dev/null +++ b/caddyhttp/httpserver/path.go @@ -0,0 +1,29 @@ +package httpserver + +import ( + "net/http" + "strings" +) + +// Path represents a URI path. +type Path string + +// Matches checks to see if other matches p. +// +// Path matching will probably not always be a direct +// comparison; this method assures that paths can be +// easily and consistently matched. +func (p Path) Matches(other string) bool { + if CaseSensitivePath { + return strings.HasPrefix(string(p), other) + } + return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(other)) +} + +// PathMatcher is a Path RequestMatcher. +type PathMatcher string + +// Match satisfies RequestMatcher. +func (p PathMatcher) Match(r *http.Request) bool { + return Path(r.URL.Path).Matches(string(p)) +} diff --git a/caddyhttp/rewrite/rewrite.go b/caddyhttp/rewrite/rewrite.go index dde85aeb..3ac8a793 100644 --- a/caddyhttp/rewrite/rewrite.go +++ b/caddyhttp/rewrite/rewrite.go @@ -31,18 +31,13 @@ const ( type Rewrite struct { Next httpserver.Handler FileSys http.FileSystem - Rules []Rule + Rules []httpserver.HandlerConfig } // ServeHTTP implements the httpserver.Handler interface. func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { -outer: - for _, rule := range rw.Rules { - switch result := rule.Rewrite(rw.FileSys, r); result { - case RewriteDone: - break outer - case RewriteIgnored: - break + if rule := httpserver.ConfigSelector(rw.Rules).Select(r); rule != nil { + switch result := rule.(Rule).Rewrite(rw.FileSys, r); result { case RewriteStatus: // only valid for complex rules. if cRule, ok := rule.(*ComplexRule); ok && cRule.Status != 0 { @@ -55,6 +50,7 @@ outer: // Rule describes an internal location rewrite rule. type Rule interface { + httpserver.HandlerConfig // Rewrite rewrites the internal location of the current request. Rewrite(http.FileSystem, *http.Request) Result } @@ -69,17 +65,20 @@ func NewSimpleRule(from, to string) SimpleRule { return SimpleRule{from, to} } +// BasePath satisfies httpserver.Config +func (s SimpleRule) BasePath() string { return s.From } + +// Match satisfies httpserver.Config +func (s SimpleRule) Match(r *http.Request) bool { return s.From == r.URL.Path } + // Rewrite rewrites the internal location of the current request. func (s SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) Result { - if s.From == r.URL.Path { - // take note of this rewrite for internal use by fastcgi - // all we need is the URI, not full URL - r.Header.Set(headerFieldName, r.URL.RequestURI()) + // take note of this rewrite for internal use by fastcgi + // all we need is the URI, not full URL + r.Header.Set(headerFieldName, r.URL.RequestURI()) - // attempt rewrite - return To(fs, r, s.To, newReplacer(r)) - } - return RewriteIgnored + // attempt rewrite + return To(fs, r, s.To, newReplacer(r)) } // ComplexRule is a rewrite rule based on a regular expression @@ -105,7 +104,7 @@ type ComplexRule struct { // NewComplexRule creates a new RegexpRule. It returns an error if regexp // pattern (pattern) or extensions (ext) are invalid. -func NewComplexRule(base, pattern, to string, status int, ext []string, m httpserver.RequestMatcher) (*ComplexRule, error) { +func NewComplexRule(base, pattern, to string, status int, ext []string, matcher httpserver.RequestMatcher) (*ComplexRule, error) { // validate regexp if present var r *regexp.Regexp if pattern != "" { @@ -126,40 +125,58 @@ func NewComplexRule(base, pattern, to string, status int, ext []string, m httpse } } + // use both IfMatcher and PathMatcher + matcher = httpserver.MergeRequestMatchers( + // If condition matcher + matcher, + // Base path matcher + httpserver.PathMatcher(base), + ) + return &ComplexRule{ Base: base, To: to, Status: status, Exts: ext, - RequestMatcher: m, + RequestMatcher: matcher, Regexp: r, }, nil } -// Rewrite rewrites the internal location of the current request. -func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) { - rPath := req.URL.Path - replacer := newReplacer(req) +// BasePath satisfies httpserver.Config +func (r *ComplexRule) BasePath() string { return r.Base } - // validate base - if !httpserver.Path(rPath).Matches(r.Base) { - return +// Match satisfies httpserver.Config. +// +// Though ComplexRule embeds a RequestMatcher, additional +// checks are needed which requires a custom implementation. +func (r *ComplexRule) Match(req *http.Request) bool { + // validate RequestMatcher + // includes if and path + if !r.RequestMatcher.Match(req) { + return false } // validate extensions - if !r.matchExt(rPath) { - return + if !r.matchExt(req.URL.Path) { + return false } + // if regex is nil, ignore + if r.Regexp == nil { + return true + } + // otherwise validate regex + return r.regexpMatches(req.URL.Path) != nil +} + +// Rewrite rewrites the internal location of the current request. +func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) { + replacer := newReplacer(req) + // validate regexp if present if r.Regexp != nil { - // include trailing slash in regexp if present - start := len(r.Base) - if strings.HasSuffix(r.Base, "/") { - start-- - } - - matches := r.FindStringSubmatch(rPath[start:]) + matches := r.regexpMatches(req.URL.Path) switch len(matches) { case 0: // no match @@ -182,11 +199,6 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) } } - // validate if conditions - if !r.RequestMatcher.Match(req) { - return - } - // if status is present, stop rewrite and return it. if r.Status != 0 { return RewriteStatus @@ -228,6 +240,18 @@ func (r *ComplexRule) matchExt(rPath string) bool { return true } +func (r *ComplexRule) regexpMatches(rPath string) []string { + if r.Regexp != nil { + // include trailing slash in regexp if present + start := len(r.Base) + if strings.HasSuffix(r.Base, "/") { + start-- + } + return r.FindStringSubmatch(rPath[start:]) + } + return nil +} + func newReplacer(r *http.Request) httpserver.Replacer { return httpserver.NewReplacer(r, nil, "") } diff --git a/caddyhttp/rewrite/rewrite_test.go b/caddyhttp/rewrite/rewrite_test.go index ac74291f..7a8ceadb 100644 --- a/caddyhttp/rewrite/rewrite_test.go +++ b/caddyhttp/rewrite/rewrite_test.go @@ -13,7 +13,7 @@ import ( func TestRewrite(t *testing.T) { rw := Rewrite{ Next: httpserver.HandlerFunc(urlPrinter), - Rules: []Rule{ + Rules: []httpserver.HandlerConfig{ NewSimpleRule("/from", "/to"), NewSimpleRule("/a", "/b"), NewSimpleRule("/b", "/b{uri}"), @@ -131,7 +131,7 @@ func TestRewrite(t *testing.T) { if err != nil { t.Fatalf("Test %d: No error expected for rule but found %v", i, err) } - rw.Rules = []Rule{rule} + rw.Rules = []httpserver.HandlerConfig{rule} req, err := http.NewRequest("GET", urlPath, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) diff --git a/caddyhttp/rewrite/setup.go b/caddyhttp/rewrite/setup.go index 17d55659..4e369baa 100644 --- a/caddyhttp/rewrite/setup.go +++ b/caddyhttp/rewrite/setup.go @@ -36,9 +36,8 @@ func setup(c *caddy.Controller) error { return nil } -func rewriteParse(c *caddy.Controller) ([]Rule, error) { - var simpleRules []Rule - var regexpRules []Rule +func rewriteParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) { + var rules []httpserver.HandlerConfig for c.Next() { var rule Rule @@ -105,16 +104,15 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) { if rule, err = NewComplexRule(base, pattern, to, status, ext, matcher); err != nil { return nil, err } - regexpRules = append(regexpRules, rule) + rules = append(rules, rule) // the only unhandled case is 2 and above default: rule = NewSimpleRule(args[0], strings.Join(args[1:], " ")) - simpleRules = append(simpleRules, rule) + rules = append(rules, rule) } } - // put simple rules in front to avoid regexp computation for them - return append(simpleRules, regexpRules...), nil + return rules, nil } diff --git a/caddyhttp/rewrite/to.go b/caddyhttp/rewrite/to.go index 5371920d..660014d0 100644 --- a/caddyhttp/rewrite/to.go +++ b/caddyhttp/rewrite/to.go @@ -12,7 +12,6 @@ import ( // To attempts rewrite. It attempts to rewrite to first valid path // or the last path if none of the paths are valid. -// Returns true if rewrite is successful and false otherwise. func To(fs http.FileSystem, r *http.Request, to string, replacer httpserver.Replacer) Result { tos := strings.Fields(to)