@marcel91200 / caddy Mirror · read-only / commits / 65195a72
Fork 0

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?