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
}