mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-25 17:56:34 +08:00
Proposal: Middleware Config (#908)
* Prototype middleware Config * Refactors * Minor refactors
This commit is contained in:
parent
cf03c9a6c8
commit
87c389f73d
|
@ -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 {
|
||||
|
|
29
caddyhttp/httpserver/path.go
Normal file
29
caddyhttp/httpserver/path.go
Normal file
|
@ -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))
|
||||
}
|
|
@ -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, "")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user