Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
Implement rewrite middleware; fix middleware stack bugs
Matthew Holt committed
May 20, 2019 at 23:48 UTC
65195a726d9ceff4bbf870b7baa7eff20cf35381
8 files changed
+133
-34
cmd/caddy2/main.go
+1
@@ -9,6 +9,7 @@ import (
9
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/fileserver"
10
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/headers"
11
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/reverseproxy"
12
+ _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/rewrite"
13
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
14
)
15
modules/caddyhttp/fileserver/staticfiles.go
+1
-1
@@ -104,7 +104,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error
104
if filename == "" {
105
// no files worked, so resort to fallback
106
if fsrv.Fallback != nil {
107
- fallback := fsrv.Fallback.BuildCompositeRoute(w, r)
107
+ fallback, w := fsrv.Fallback.BuildCompositeRoute(w, r)
108
return fallback.ServeHTTP(w, r)
109
}
110
return caddyhttp.Error(http.StatusNotFound, nil)
modules/caddyhttp/headers/headers.go
+14
-6
@@ -37,29 +37,36 @@ type RespHeaderOps struct {
37
}
38
39
func (h Headers) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
40
- apply(h.Request, r.Header)
40
+ repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer)
41
+ apply(h.Request, r.Header, repl)
42
if h.Response.Deferred {
43
w = &responseWriterWrapper{
44
ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w},
45
+ replacer: repl,
46
headerOps: h.Response.HeaderOps,
47
}
48
} else {
47
- apply(h.Response.HeaderOps, w.Header())
49
+ apply(h.Response.HeaderOps, w.Header(), repl)
50
}
51
return next.ServeHTTP(w, r)
52
}
53
52
-func apply(ops HeaderOps, hdr http.Header) {
54
+func apply(ops HeaderOps, hdr http.Header, repl caddy2.Replacer) {
55
for fieldName, vals := range ops.Add {
56
+ fieldName = repl.ReplaceAll(fieldName, "")
57
for _, v := range vals {
55
- hdr.Add(fieldName, v)
58
+ hdr.Add(fieldName, repl.ReplaceAll(v, ""))
59
}
60
}
61
for fieldName, vals := range ops.Set {
62
+ fieldName = repl.ReplaceAll(fieldName, "")
63
+ for i := range vals {
64
+ vals[i] = repl.ReplaceAll(vals[i], "")
65
+ }
66
hdr.Set(fieldName, strings.Join(vals, ","))
67
}
68
for _, fieldName := range ops.Delete {
62
- hdr.Del(fieldName)
69
+ hdr.Del(repl.ReplaceAll(fieldName, ""))
70
}
71
}
72
@@ -67,6 +74,7 @@ func apply(ops HeaderOps, hdr http.Header) {
74
// operations until WriteHeader is called.
75
type responseWriterWrapper struct {
76
*caddyhttp.ResponseWriterWrapper
77
+ replacer caddy2.Replacer
78
headerOps HeaderOps
79
wroteHeader bool
80
}
@@ -83,7 +91,7 @@ func (rww *responseWriterWrapper) WriteHeader(status int) {
91
return
92
}
93
rww.wroteHeader = true
86
- apply(rww.headerOps, rww.ResponseWriterWrapper.Header())
94
+ apply(rww.headerOps, rww.ResponseWriterWrapper.Header(), rww.replacer)
95
rww.ResponseWriterWrapper.WriteHeader(status)
96
}
97
modules/caddyhttp/matchers_test.go
+4
-2
@@ -227,9 +227,10 @@ func TestPathREMatcher(t *testing.T) {
227
228
// set up the fake request and its Replacer
229
req := &http.Request{URL: &url.URL{Path: tc.input}}
230
- repl := newReplacer(req, httptest.NewRecorder())
230
+ repl := caddy2.NewReplacer()
231
ctx := context.WithValue(req.Context(), caddy2.ReplacerCtxKey, repl)
232
req = req.WithContext(ctx)
233
+ addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
234
235
actual := tc.match.Match(req)
236
if actual != tc.expect {
@@ -344,9 +345,10 @@ func TestHeaderREMatcher(t *testing.T) {
345
346
// set up the fake request and its Replacer
347
req := &http.Request{Header: tc.input, URL: new(url.URL)}
347
- repl := newReplacer(req, httptest.NewRecorder())
348
+ repl := caddy2.NewReplacer()
349
ctx := context.WithValue(req.Context(), caddy2.ReplacerCtxKey, repl)
350
req = req.WithContext(ctx)
351
+ addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
352
353
actual := tc.match.Match(req)
354
if actual != tc.expect {
modules/caddyhttp/replacer.go
+1
-5
@@ -13,9 +13,7 @@ import (
13
// TODO: A simple way to format or escape or encode each value would be nice
14
// ... TODO: Should we just use templates? :-/ yeesh...
15
16
-func newReplacer(req *http.Request, w http.ResponseWriter) caddy2.Replacer {
17
- repl := caddy2.NewReplacer()
18
-
16
+func addHTTPVarsToReplacer(repl caddy2.Replacer, req *http.Request, w http.ResponseWriter) {
17
httpVars := func() map[string]string {
18
m := make(map[string]string)
19
if req != nil {
@@ -78,6 +76,4 @@ func newReplacer(req *http.Request, w http.ResponseWriter) caddy2.Replacer {
76
}
77
78
repl.Map(httpVars)
81
-
82
- return repl
79
}
modules/caddyhttp/rewrite/rewrite.go
new
+71
@@ -0,0 +1,71 @@
1
+package headers
2
+
3
+import (
4
+ "net/http"
5
+ "net/url"
6
+ "strings"
7
+
8
+ "bitbucket.org/lightcodelabs/caddy2"
9
+ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
10
+)
11
+
12
+func init() {
13
+ caddy2.RegisterModule(caddy2.Module{
14
+ Name: "http.middleware.rewrite",
15
+ New: func() (interface{}, error) { return new(Rewrite), nil },
16
+ })
17
+}
18
+
19
+// Rewrite is a middleware which can rewrite HTTP requests.
20
+type Rewrite struct {
21
+ Method string `json:"method"`
22
+ URI string `json:"uri"`
23
+ Rehandle bool `json:"rehandle"`
24
+}
25
+
26
+func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
27
+ repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer)
28
+ var rehandleNeeded bool
29
+
30
+ if rewr.Method != "" {
31
+ method := r.Method
32
+ r.Method = strings.ToUpper(repl.ReplaceAll(rewr.Method, ""))
33
+ if r.Method != method {
34
+ rehandleNeeded = true
35
+ }
36
+ }
37
+
38
+ if rewr.URI != "" {
39
+ // TODO: clean this all up, I don't think it's right
40
+
41
+ oldURI := r.RequestURI
42
+ newURI := repl.ReplaceAll(rewr.URI, "")
43
+ u, err := url.Parse(newURI)
44
+ if err != nil {
45
+ return caddyhttp.Error(http.StatusInternalServerError, err)
46
+ }
47
+
48
+ r.RequestURI = newURI
49
+
50
+ r.URL.Path = u.Path
51
+ if u.RawQuery != "" {
52
+ r.URL.RawQuery = u.RawQuery
53
+ }
54
+ if u.Fragment != "" {
55
+ r.URL.Fragment = u.Fragment
56
+ }
57
+
58
+ if newURI != oldURI {
59
+ rehandleNeeded = true
60
+ }
61
+ }
62
+
63
+ if rehandleNeeded && rewr.Rehandle {
64
+ return caddyhttp.ErrRehandle
65
+ }
66
+
67
+ return next.ServeHTTP(w, r)
68
+}
69
+
70
+// Interface guard
71
+var _ caddyhttp.MiddlewareHandler = (*Rewrite)(nil)
modules/caddyhttp/routes.go
+33
-16
@@ -65,23 +65,24 @@ func (routes RouteList) Provision(ctx caddy2.Context) error {
65
return nil
66
}
67
68
-// BuildCompositeRoute creates a chain of handlers by
69
-// applying all the matching routes.
70
-func (routes RouteList) BuildCompositeRoute(w http.ResponseWriter, r *http.Request) Handler {
68
+// BuildCompositeRoute creates a chain of handlers by applying all the matching
69
+// routes. The returned ResponseWriter should be used instead of rw.
70
+func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) (Handler, http.ResponseWriter) {
71
+ mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{rw}}
72
+
73
if len(routes) == 0 {
72
- return emptyHandler
74
+ return emptyHandler, mrw
75
}
76
77
var mid []Middleware
78
var responder Handler
77
- mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{w}}
79
groups := make(map[string]struct{})
80
81
routeLoop:
82
for _, route := range routes {
83
// see if route matches
84
for _, m := range route.matchers {
84
- if !m.Match(r) {
85
+ if !m.Match(req) {
86
continue routeLoop
87
}
88
}
@@ -102,15 +103,13 @@ routeLoop:
103
104
// apply the rest of the route
105
for _, m := range route.middleware {
105
- mid = append(mid, func(next HandlerFunc) HandlerFunc {
106
- return func(w http.ResponseWriter, r *http.Request) error {
107
- // TODO: This is where request tracing could be implemented; also
108
- // see below to trace the responder as well
109
- // TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
110
- // TODO: see what the std lib gives us in terms of stack trracing too
111
- return m.ServeHTTP(mrw, r, next)
112
- }
113
- })
106
+ // we have to be sure to wrap m outside
107
+ // of our current scope so that the
108
+ // reference to this m isn't overwritten
109
+ // on the next iteration, leaving only
110
+ // the last middleware in the chain as
111
+ // the ONLY middleware in the chain!
112
+ mid = append(mid, wrapMiddleware(m))
113
}
114
if responder == nil {
115
responder = route.responder
@@ -132,7 +131,25 @@ routeLoop:
131
stack = mid[i](stack)
132
}
133
135
- return stack
134
+ return stack, mrw
135
+}
136
+
137
+// wrapMiddleware wraps m such that it can be correctly
138
+// appended to a list of middleware. This is necessary
139
+// so that only the last middleware in a loop does not
140
+// become the only middleware of the stack, repeatedly
141
+// executed (i.e. it is necessary to keep a reference
142
+// to this m outside of the scope of a loop)!
143
+func wrapMiddleware(m MiddlewareHandler) Middleware {
144
+ return func(next HandlerFunc) HandlerFunc {
145
+ return func(w http.ResponseWriter, r *http.Request) error {
146
+ // TODO: This is where request tracing could be implemented; also
147
+ // see below to trace the responder as well
148
+ // TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
149
+ // TODO: see what the std lib gives us in terms of stack tracing too
150
+ return m.ServeHTTP(w, r, next)
151
+ }
152
+ }
153
}
154
155
type middlewareResponseWriter struct {
modules/caddyhttp/server.go
+8
-4
@@ -32,14 +32,18 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
32
return
33
}
34
35
- // set up the replacer
36
- repl := newReplacer(r, w)
35
+ // set up the context for the request
36
+ repl := caddy2.NewReplacer()
37
ctx := context.WithValue(r.Context(), caddy2.ReplacerCtxKey, repl)
38
ctx = context.WithValue(ctx, TableCtxKey, make(map[string]interface{})) // TODO: Implement this
39
r = r.WithContext(ctx)
40
41
+ // once the pointer to the request won't change
42
+ // anymore, finish setting up the replacer
43
+ addHTTPVarsToReplacer(repl, r, w)
44
+
45
// build and execute the main handler chain
42
- stack := s.Routes.BuildCompositeRoute(w, r)
46
+ stack, w := s.Routes.BuildCompositeRoute(w, r)
47
err := s.executeCompositeRoute(w, r, stack)
48
if err != nil {
49
// add the raw error value to the request context
@@ -64,7 +68,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
68
w.WriteHeader(handlerErr.StatusCode)
69
}
70
} else {
67
- errStack := s.Errors.Routes.BuildCompositeRoute(w, r)
71
+ errStack, w := s.Errors.Routes.BuildCompositeRoute(w, r)
72
err := s.executeCompositeRoute(w, r, errStack)
73
if err != nil {
74
// TODO: what should we do if the error handler has an error?