Commit 87c389f7 authored by Abiola Ibrahim's avatar Abiola Ibrahim Committed by Matt Holt

Proposal: Middleware Config (#908)

* Prototype middleware Config

* Refactors

* Minor refactors
parent cf03c9a6
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"strings"
"time" "time"
) )
...@@ -48,13 +47,23 @@ type ( ...@@ -48,13 +47,23 @@ type (
// RequestMatcher checks to see if current request should be handled // RequestMatcher checks to see if current request should be handled
// by underlying handler. // 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 { RequestMatcher interface {
Match(r *http.Request) bool 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. // ServeHTTP implements the Handler interface.
...@@ -62,6 +71,20 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err ...@@ -62,6 +71,20 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
return f(w, r) 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 // 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 // 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 // path to the file and true. If no index file is found, empty string
...@@ -130,21 +153,6 @@ func initCaseSettings() { ...@@ -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. // MergeRequestMatchers merges multiple RequestMatchers into one.
// This allows a middleware to use multiple RequestMatchers. // This allows a middleware to use multiple RequestMatchers.
func MergeRequestMatchers(matchers ...RequestMatcher) RequestMatcher { func MergeRequestMatchers(matchers ...RequestMatcher) RequestMatcher {
......
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))
}
...@@ -31,18 +31,13 @@ const ( ...@@ -31,18 +31,13 @@ const (
type Rewrite struct { type Rewrite struct {
Next httpserver.Handler Next httpserver.Handler
FileSys http.FileSystem FileSys http.FileSystem
Rules []Rule Rules []httpserver.HandlerConfig
} }
// ServeHTTP implements the httpserver.Handler interface. // ServeHTTP implements the httpserver.Handler interface.
func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
outer: if rule := httpserver.ConfigSelector(rw.Rules).Select(r); rule != nil {
for _, rule := range rw.Rules { switch result := rule.(Rule).Rewrite(rw.FileSys, r); result {
switch result := rule.Rewrite(rw.FileSys, r); result {
case RewriteDone:
break outer
case RewriteIgnored:
break
case RewriteStatus: case RewriteStatus:
// only valid for complex rules. // only valid for complex rules.
if cRule, ok := rule.(*ComplexRule); ok && cRule.Status != 0 { if cRule, ok := rule.(*ComplexRule); ok && cRule.Status != 0 {
...@@ -55,6 +50,7 @@ outer: ...@@ -55,6 +50,7 @@ outer:
// Rule describes an internal location rewrite rule. // Rule describes an internal location rewrite rule.
type Rule interface { type Rule interface {
httpserver.HandlerConfig
// Rewrite rewrites the internal location of the current request. // Rewrite rewrites the internal location of the current request.
Rewrite(http.FileSystem, *http.Request) Result Rewrite(http.FileSystem, *http.Request) Result
} }
...@@ -69,17 +65,20 @@ func NewSimpleRule(from, to string) SimpleRule { ...@@ -69,17 +65,20 @@ func NewSimpleRule(from, to string) SimpleRule {
return SimpleRule{from, to} 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. // Rewrite rewrites the internal location of the current request.
func (s SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) Result { 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 // take note of this rewrite for internal use by fastcgi
// all we need is the URI, not full URL // all we need is the URI, not full URL
r.Header.Set(headerFieldName, r.URL.RequestURI()) r.Header.Set(headerFieldName, r.URL.RequestURI())
// attempt rewrite // attempt rewrite
return To(fs, r, s.To, newReplacer(r)) return To(fs, r, s.To, newReplacer(r))
}
return RewriteIgnored
} }
// ComplexRule is a rewrite rule based on a regular expression // ComplexRule is a rewrite rule based on a regular expression
...@@ -105,7 +104,7 @@ type ComplexRule struct { ...@@ -105,7 +104,7 @@ type ComplexRule struct {
// NewComplexRule creates a new RegexpRule. It returns an error if regexp // NewComplexRule creates a new RegexpRule. It returns an error if regexp
// pattern (pattern) or extensions (ext) are invalid. // 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 // validate regexp if present
var r *regexp.Regexp var r *regexp.Regexp
if pattern != "" { if pattern != "" {
...@@ -126,40 +125,58 @@ func NewComplexRule(base, pattern, to string, status int, ext []string, m httpse ...@@ -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{ return &ComplexRule{
Base: base, Base: base,
To: to, To: to,
Status: status, Status: status,
Exts: ext, Exts: ext,
RequestMatcher: m, RequestMatcher: matcher,
Regexp: r, Regexp: r,
}, nil }, nil
} }
// Rewrite rewrites the internal location of the current request. // BasePath satisfies httpserver.Config
func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) { func (r *ComplexRule) BasePath() string { return r.Base }
rPath := req.URL.Path
replacer := newReplacer(req)
// validate base // Match satisfies httpserver.Config.
if !httpserver.Path(rPath).Matches(r.Base) { //
return // 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 // validate extensions
if !r.matchExt(rPath) { if !r.matchExt(req.URL.Path) {
return return false
} }
// validate regexp if present // if regex is nil, ignore
if r.Regexp != nil { if r.Regexp == nil {
// include trailing slash in regexp if present return true
start := len(r.Base)
if strings.HasSuffix(r.Base, "/") {
start--
} }
// otherwise validate regex
return r.regexpMatches(req.URL.Path) != nil
}
matches := r.FindStringSubmatch(rPath[start:]) // 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 {
matches := r.regexpMatches(req.URL.Path)
switch len(matches) { switch len(matches) {
case 0: case 0:
// no match // no match
...@@ -182,11 +199,6 @@ func (r *ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) ...@@ -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 status is present, stop rewrite and return it.
if r.Status != 0 { if r.Status != 0 {
return RewriteStatus return RewriteStatus
...@@ -228,6 +240,18 @@ func (r *ComplexRule) matchExt(rPath string) bool { ...@@ -228,6 +240,18 @@ func (r *ComplexRule) matchExt(rPath string) bool {
return true 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 { func newReplacer(r *http.Request) httpserver.Replacer {
return httpserver.NewReplacer(r, nil, "") return httpserver.NewReplacer(r, nil, "")
} }
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
func TestRewrite(t *testing.T) { func TestRewrite(t *testing.T) {
rw := Rewrite{ rw := Rewrite{
Next: httpserver.HandlerFunc(urlPrinter), Next: httpserver.HandlerFunc(urlPrinter),
Rules: []Rule{ Rules: []httpserver.HandlerConfig{
NewSimpleRule("/from", "/to"), NewSimpleRule("/from", "/to"),
NewSimpleRule("/a", "/b"), NewSimpleRule("/a", "/b"),
NewSimpleRule("/b", "/b{uri}"), NewSimpleRule("/b", "/b{uri}"),
...@@ -131,7 +131,7 @@ func TestRewrite(t *testing.T) { ...@@ -131,7 +131,7 @@ func TestRewrite(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Test %d: No error expected for rule but found %v", i, err) 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) req, err := http.NewRequest("GET", urlPath, nil)
if err != nil { if err != nil {
t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) t.Fatalf("Test %d: Could not create HTTP request: %v", i, err)
......
...@@ -36,9 +36,8 @@ func setup(c *caddy.Controller) error { ...@@ -36,9 +36,8 @@ func setup(c *caddy.Controller) error {
return nil return nil
} }
func rewriteParse(c *caddy.Controller) ([]Rule, error) { func rewriteParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) {
var simpleRules []Rule var rules []httpserver.HandlerConfig
var regexpRules []Rule
for c.Next() { for c.Next() {
var rule Rule var rule Rule
...@@ -105,16 +104,15 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) { ...@@ -105,16 +104,15 @@ func rewriteParse(c *caddy.Controller) ([]Rule, error) {
if rule, err = NewComplexRule(base, pattern, to, status, ext, matcher); err != nil { if rule, err = NewComplexRule(base, pattern, to, status, ext, matcher); err != nil {
return nil, err return nil, err
} }
regexpRules = append(regexpRules, rule) rules = append(rules, rule)
// the only unhandled case is 2 and above // the only unhandled case is 2 and above
default: default:
rule = NewSimpleRule(args[0], strings.Join(args[1:], " ")) 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 rules, nil
return append(simpleRules, regexpRules...), nil
} }
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
// To attempts rewrite. It attempts to rewrite to first valid path // To attempts rewrite. It attempts to rewrite to first valid path
// or the last path if none of the paths are valid. // 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 { func To(fs http.FileSystem, r *http.Request, to string, replacer httpserver.Replacer) Result {
tos := strings.Fields(to) tos := strings.Fields(to)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment