mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-22 10:25:46 +03:00
Proposal: Middleware Config (#908)
* Prototype middleware Config * Refactors * Minor refactors
This commit is contained in:
parent
cf03c9a6c8
commit
87c389f73d
6 changed files with 127 additions and 69 deletions
|
@ -5,7 +5,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
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() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|
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 {
|
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:
|
||||||
|
|
||||||
// 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 {
|
||||||
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 {
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// validate regexp if present
|
||||||
if r.Regexp != nil {
|
if r.Regexp != nil {
|
||||||
// include trailing slash in regexp if present
|
matches := r.regexpMatches(req.URL.Path)
|
||||||
start := len(r.Base)
|
|
||||||
if strings.HasSuffix(r.Base, "/") {
|
|
||||||
start--
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := r.FindStringSubmatch(rPath[start:])
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
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 (
|
||||||
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) {
|
||||||
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 {
|
||||||
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) {
|
||||||
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 (
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue