@marcel91200 / caddy Mirror · read-only / commits / 4a3a4181
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

Flatten HTTP handler config (#2662)

Differentiating middleware and responders has one benefit, namely that it's clear which module provides the response, but even then it's not a great advantage. Linear handler config makes a little more sense, giving greater flexibility and simplifying the core a bit, even though it's slightly awkward that handlers which are responders may not use the 'next' handler that is passed in at all.

Matthew Holt committed Jul 9, 2019 at 12:58 UTC 4a3a418156e25aae17659142a4bf9259d7702c44
9 files changed +99 -148
modules/caddyhttp/caddyhttp.go
+31 -29
@@ -322,13 +322,15 @@ func (app *App) automaticHTTPS() error {
322 MatchHost(domains),
323 },
324 },
325 - responder: Static{
326 - StatusCode: http.StatusTemporaryRedirect, // TODO: use permanent redirect instead
327 - Headers: http.Header{
328 - "Location": []string{redirTo},
329 - "Connection": []string{"close"},
325 + handlers: []MiddlewareHandler{
326 + Static{
327 + StatusCode: strconv.Itoa(http.StatusTemporaryRedirect), // TODO: use permanent redirect instead
328 + Headers: http.Header{
329 + "Location": []string{redirTo},
330 + "Connection": []string{"close"},
331 + },
332 + Close: true,
333 },
331 - Close: true,
334 },
335 })
336 }
@@ -381,34 +383,18 @@ func (app *App) listenerTaken(network, address string) bool {
383 var defaultALPN = []string{"h2", "http/1.1"}
384
385 // RequestMatcher is a type that can match to a request.
384 -// A route matcher MUST NOT modify the request.
386 +// A route matcher MUST NOT modify the request, with the
387 +// only exception being its context.
388 type RequestMatcher interface {
389 Match(*http.Request) bool
390 }
391
389 -// Middleware chains one Handler to the next by being passed
390 -// the next Handler in the chain.
391 -type Middleware func(HandlerFunc) HandlerFunc
392 -
393 -// MiddlewareHandler is a Handler that includes a reference
394 -// to the next middleware handler in the chain. Middleware
395 -// handlers MUST NOT call Write() or WriteHeader() on the
396 -// response writer; doing so will panic. See Handler godoc
397 -// for more information.
398 -type MiddlewareHandler interface {
399 - ServeHTTP(http.ResponseWriter, *http.Request, Handler) error
400 -}
401 -
392 // Handler is like http.Handler except ServeHTTP may return an error.
393 //
404 -// Middleware and responder handlers both implement this method.
405 -// Middleware must not call Write or WriteHeader on the ResponseWriter;
406 -// doing so will cause a panic. Responders should write to the response
407 -// if there was not an error.
408 -//
394 // If any handler encounters an error, it should be returned for proper
395 // handling. Return values should be propagated down the middleware chain
411 -// by returning it unchanged. Returned errors should not be re-wrapped.
396 +// by returning it unchanged. Returned errors should not be re-wrapped
397 +// if they are already HandlerError values.
398 type Handler interface {
399 ServeHTTP(http.ResponseWriter, *http.Request) error
400 }
@@ -421,9 +407,25 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
407 return f(w, r)
408 }
409
424 -// emptyHandler is used as a no-op handler, which is
425 -// sometimes better than a nil Handler pointer.
426 -var emptyHandler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { return nil }
410 +// Middleware chains one Handler to the next by being passed
411 +// the next Handler in the chain.
412 +type Middleware func(HandlerFunc) HandlerFunc
413 +
414 +// MiddlewareHandler is like Handler except it takes as a third
415 +// argument the next handler in the chain. The next handler will
416 +// never be nil, but may be a no-op handler if this is the last
417 +// handler in the chain. Handlers which act as middleware should
418 +// call the next handler's ServeHTTP method so as to propagate
419 +// the request down the chain properly. Handlers which act as
420 +// responders (content origins) need not invoke the next handler,
421 +// since the last handler in the chain should be the first to
422 +// write the response.
423 +type MiddlewareHandler interface {
424 + ServeHTTP(http.ResponseWriter, *http.Request, Handler) error
425 +}
426 +
427 +// emptyHandler is used as a no-op handler.
428 +var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error { return nil }
429
430 const (
431 // DefaultHTTPPort is the default port for HTTP.
modules/caddyhttp/encode/encode.go
+1 -1
@@ -36,7 +36,7 @@ import (
36
37 func init() {
38 caddy.RegisterModule(caddy.Module{
39 - Name: "http.middleware.encode",
39 + Name: "http.handlers.encode",
40 New: func() interface{} { return new(Encode) },
41 })
42 }
modules/caddyhttp/fileserver/staticfiles.go
+6 -6
@@ -35,7 +35,7 @@ func init() {
35 weakrand.Seed(time.Now().UnixNano())
36
37 caddy.RegisterModule(caddy.Module{
38 - Name: "http.responders.file_server",
38 + Name: "http.handlers.file_server",
39 New: func() interface{} { return new(FileServer) },
40 })
41 }
@@ -108,7 +108,7 @@ func (fsrv *FileServer) Validate() error {
108 return nil
109 }
110
111 -func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
111 +func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, _ caddyhttp.Handler) error {
112 repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
113
114 filesToHide := fsrv.transformHidePaths(repl)
@@ -119,7 +119,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) error
119 if filename == "" {
120 // no files worked, so resort to fallback
121 if fsrv.Fallback != nil {
122 - fallback, w := fsrv.Fallback.BuildCompositeRoute(w, r)
122 + fallback := fsrv.Fallback.BuildCompositeRoute(w, r)
123 return fallback.ServeHTTP(w, r)
124 }
125 return caddyhttp.Error(http.StatusNotFound, nil)
@@ -452,7 +452,7 @@ const minBackoff, maxBackoff = 2, 5
452
453 // Interface guards
454 var (
455 - _ caddy.Provisioner = (*FileServer)(nil)
456 - _ caddy.Validator = (*FileServer)(nil)
457 - _ caddyhttp.Handler = (*FileServer)(nil)
455 + _ caddy.Provisioner = (*FileServer)(nil)
456 + _ caddy.Validator = (*FileServer)(nil)
457 + _ caddyhttp.MiddlewareHandler = (*FileServer)(nil)
458 )
modules/caddyhttp/rewrite/rewrite.go
+1 -1
@@ -25,7 +25,7 @@ import (
25
26 func init() {
27 caddy.RegisterModule(caddy.Module{
28 - Name: "http.middleware.rewrite",
28 + Name: "http.handlers.rewrite",
29 New: func() interface{} { return new(Rewrite) },
30 })
31 }
modules/caddyhttp/routes.go
+40 -90
@@ -28,25 +28,19 @@ import (
28 type ServerRoute struct {
29 Group string `json:"group,omitempty"`
30 MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
31 - Apply []json.RawMessage `json:"apply,omitempty"`
32 - Respond json.RawMessage `json:"respond,omitempty"`
33 -
34 - Terminal bool `json:"terminal,omitempty"`
31 + Handle []json.RawMessage `json:"handle,omitempty"`
32 + Terminal bool `json:"terminal,omitempty"`
33
34 // decoded values
35 matcherSets []MatcherSet
38 - middleware []MiddlewareHandler
39 - responder Handler
36 + handlers []MiddlewareHandler
37 }
38
39 // Empty returns true if the route has all zero/default values.
40 func (sr ServerRoute) Empty() bool {
41 return len(sr.MatcherSets) == 0 &&
45 - len(sr.Apply) == 0 &&
46 - len(sr.Respond) == 0 &&
47 - len(sr.matcherSets) == 0 &&
48 - len(sr.middleware) == 0 &&
49 - sr.responder == nil &&
42 + len(sr.Handle) == 0 &&
43 + len(sr.handlers) == 0 &&
44 !sr.Terminal &&
45 sr.Group == ""
46 }
@@ -98,40 +92,27 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
92 }
93 routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help?
94
101 - // middleware
102 - for j, rawMsg := range route.Apply {
103 - mid, err := ctx.LoadModuleInline("middleware", "http.middleware", rawMsg)
104 - if err != nil {
105 - return fmt.Errorf("loading middleware module in position %d: %v", j, err)
106 - }
107 - routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
108 - }
109 - routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
110 -
111 - // responder
112 - if route.Respond != nil {
113 - resp, err := ctx.LoadModuleInline("responder", "http.responders", route.Respond)
95 + // handlers
96 + for j, rawMsg := range route.Handle {
97 + mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
98 if err != nil {
115 - return fmt.Errorf("loading responder module: %v", err)
99 + return fmt.Errorf("loading handler module in position %d: %v", j, err)
100 }
117 - routes[i].responder = resp.(Handler)
101 + routes[i].handlers = append(routes[i].handlers, mh.(MiddlewareHandler))
102 }
119 - routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
103 + routes[i].Handle = nil // allow GC to deallocate - TODO: Does this help?
104 }
105 return nil
106 }
107
124 -// BuildCompositeRoute creates a chain of handlers by applying all the matching
125 -// routes. The returned ResponseWriter should be used instead of rw.
126 -func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) (Handler, http.ResponseWriter) {
127 - mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{rw}}
128 -
108 +// BuildCompositeRoute creates a chain of handlers by
109 +// applying all of the matching routes.
110 +func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) Handler {
111 if len(routes) == 0 {
130 - return emptyHandler, mrw
112 + return emptyHandler
113 }
114
115 var mid []Middleware
134 - var responder Handler
116 groups := make(map[string]struct{})
117
118 for _, route := range routes {
@@ -140,9 +121,8 @@ func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Re
121 continue
122 }
123
143 - // if route is part of a group, ensure only
144 - // the first matching route in the group is
145 - // applied
124 + // if route is part of a group, ensure only the
125 + // first matching route in the group is applied
126 if route.Group != "" {
127 _, ok := groups[route.Group]
128 if ok {
@@ -155,78 +135,48 @@ func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Re
135 }
136
137 // apply the rest of the route
158 - for _, m := range route.middleware {
159 - // we have to be sure to wrap m outside
160 - // of our current scope so that the
161 - // reference to this m isn't overwritten
162 - // on the next iteration, leaving only
163 - // the last middleware in the chain as
164 - // the ONLY middleware in the chain!
165 - mid = append(mid, wrapMiddleware(m))
166 - }
167 - if responder == nil {
168 - responder = route.responder
138 + for _, mh := range route.handlers {
139 + // we have to be sure to wrap mh outside
140 + // of our current stack frame so that the
141 + // reference to this mh isn't overwritten
142 + // on the next iteration, leaving the last
143 + // middleware in the chain as the ONLY
144 + // middleware in the chain!
145 + mid = append(mid, wrapMiddleware(mh))
146 }
147 +
148 + // if this route is supposed to be last, don't
149 + // compile any more into the chain
150 if route.Terminal {
151 break
152 }
153 }
154
175 - // build the middleware stack, with the responder at the end
176 - stack := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
177 - if responder == nil {
178 - return nil
179 - }
180 - mrw.allowWrites = true
181 - return responder.ServeHTTP(w, r)
182 - })
155 + // build the middleware chain, with the responder at the end
156 + stack := emptyHandler
157 for i := len(mid) - 1; i >= 0; i-- {
158 stack = mid[i](stack)
159 }
160
187 - return stack, mrw
161 + return stack
162 }
163
164 // wrapMiddleware wraps m such that it can be correctly
191 -// appended to a list of middleware. This separate closure
192 -// is necessary so that only the last middleware in a loop
193 -// does not become the only middleware of the stack,
194 -// repeatedly executed (i.e. it is necessary to keep a
195 -// reference to this m outside of the scope of a loop)!
196 -func wrapMiddleware(m MiddlewareHandler) Middleware {
165 +// appended to a list of middleware. We can't do this
166 +// directly in a loop because it relies on a reference
167 +// to mh not changing until the execution of its handler,
168 +// which is deferred by multiple func closures. In other
169 +// words, we need to pull this particular MiddlewareHandler
170 +// pointer into its own stack frame to preserve it so it
171 +// won't be overwritten in future loop iterations.
172 +func wrapMiddleware(mh MiddlewareHandler) Middleware {
173 return func(next HandlerFunc) HandlerFunc {
174 return func(w http.ResponseWriter, r *http.Request) error {
175 // TODO: This is where request tracing could be implemented; also
176 // see below to trace the responder as well
177 // TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
178 // TODO: see what the std lib gives us in terms of stack tracing too
203 - return m.ServeHTTP(w, r, next)
179 + return mh.ServeHTTP(w, r, next)
180 }
181 }
182 }
207 -
208 -type middlewareResponseWriter struct {
209 - *ResponseWriterWrapper
210 - allowWrites bool
211 -}
212 -
213 -func (mrw middlewareResponseWriter) WriteHeader(statusCode int) {
214 - if !mrw.allowWrites {
215 - // technically, this is not true: middleware can write headers,
216 - // but only after the responder handler has returned; either the
217 - // responder did nothing with the response (sad face), or the
218 - // middleware wrapped the response and deferred the write
219 - panic("WriteHeader: middleware cannot write response headers")
220 - }
221 - mrw.ResponseWriterWrapper.WriteHeader(statusCode)
222 -}
223 -
224 -func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
225 - if !mrw.allowWrites {
226 - panic("Write: middleware cannot write to the response before responder")
227 - }
228 - return mrw.ResponseWriterWrapper.Write(b)
229 -}
230 -
231 -// Interface guard
232 -var _ HTTPInterfaces = (*middlewareResponseWriter)(nil)
modules/caddyhttp/server.go
+6 -4
@@ -65,9 +65,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
65 addHTTPVarsToReplacer(repl, r, w)
66
67 // build and execute the main handler chain
68 - stack, wrappedWriter := s.Routes.BuildCompositeRoute(w, r)
68 + stack := s.Routes.BuildCompositeRoute(w, r)
69 stack = s.wrapPrimaryRoute(stack)
70 - err := s.executeCompositeRoute(wrappedWriter, r, stack)
70 + err := s.executeCompositeRoute(w, r, stack)
71 if err != nil {
72 // add the raw error value to the request context
73 // so it can be accessed by error handlers
@@ -85,8 +85,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
85 }
86
87 if s.Errors != nil && len(s.Errors.Routes) > 0 {
88 - errStack, wrappedWriter := s.Errors.Routes.BuildCompositeRoute(w, r)
89 - err := s.executeCompositeRoute(wrappedWriter, r, errStack)
88 + errStack := s.Errors.Routes.BuildCompositeRoute(w, r)
89 + err := s.executeCompositeRoute(w, r, errStack)
90 if err != nil {
91 // TODO: what should we do if the error handler has an error?
92 log.Printf("[ERROR] [%s %s] handling error: %v", r.Method, r.RequestURI, err)
@@ -154,6 +154,8 @@ func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next
154 return next.ServeHTTP(w, r)
155 }
156
157 +// listenersUseAnyPortOtherThan returns true if there are any
158 +// listeners in s that use a port which is not otherPort.
159 func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
160 for _, lnAddr := range s.Listen {
161 _, addrs, err := caddy.ParseListenAddr(lnAddr)
modules/caddyhttp/staticresp.go
+10 -14
@@ -24,21 +24,20 @@ import (
24
25 func init() {
26 caddy.RegisterModule(caddy.Module{
27 - Name: "http.responders.static",
27 + Name: "http.handlers.static",
28 New: func() interface{} { return new(Static) },
29 })
30 }
31
32 // Static implements a simple responder for static responses.
33 type Static struct {
34 - StatusCode int `json:"status_code"` // TODO: should we turn this into a string so that only one field is needed? (string allows replacements)
35 - StatusCodeStr string `json:"status_code_str"`
36 - Headers http.Header `json:"headers"`
37 - Body string `json:"body"`
38 - Close bool `json:"close"`
34 + StatusCode string `json:"status_code"`
35 + Headers http.Header `json:"headers"`
36 + Body string `json:"body"`
37 + Close bool `json:"close"`
38 }
39
41 -func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
40 +func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
41 repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
42
43 // close the connection after responding
@@ -60,16 +59,13 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
59 }
60
61 // get the status code
63 - statusCode := s.StatusCode
64 - if statusCode == 0 && s.StatusCodeStr != "" {
65 - intVal, err := strconv.Atoi(repl.ReplaceAll(s.StatusCodeStr, ""))
62 + statusCode := http.StatusOK
63 + if s.StatusCode != "" {
64 + intVal, err := strconv.Atoi(repl.ReplaceAll(s.StatusCode, ""))
65 if err == nil {
66 statusCode = intVal
67 }
68 }
70 - if statusCode == 0 {
71 - statusCode = http.StatusOK
72 - }
69
70 // write headers
71 w.WriteHeader(statusCode)
@@ -83,4 +79,4 @@ func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
79 }
80
81 // Interface guard
86 -var _ Handler = (*Static)(nil)
82 +var _ MiddlewareHandler = (*Static)(nil)
modules/caddyhttp/staticresp_test.go
+3 -2
@@ -19,6 +19,7 @@ import (
19 "io/ioutil"
20 "net/http"
21 "net/http/httptest"
22 + "strconv"
23 "testing"
24
25 "github.com/caddyserver/caddy/v2"
@@ -29,7 +30,7 @@ func TestStaticResponseHandler(t *testing.T) {
30 w := httptest.NewRecorder()
31
32 s := Static{
32 - StatusCode: http.StatusNotFound,
33 + StatusCode: strconv.Itoa(http.StatusNotFound),
34 Headers: http.Header{
35 "X-Test": []string{"Testing"},
36 },
@@ -37,7 +38,7 @@ func TestStaticResponseHandler(t *testing.T) {
38 Close: true,
39 }
40
40 - err := s.ServeHTTP(w, r)
41 + err := s.ServeHTTP(w, r, nil)
42 if err != nil {
43 t.Errorf("did not expect an error, but got: %v", err)
44 }
modules/caddyhttp/templates/templates.go
+1 -1
@@ -28,7 +28,7 @@ import (
28
29 func init() {
30 caddy.RegisterModule(caddy.Module{
31 - Name: "http.middleware.templates",
31 + Name: "http.handlers.templates",
32 New: func() interface{} { return new(Templates) },
33 })
34 }