28
// in a highly flexible and performant manner.
29
type Route struct {
30
// Group is an optional name for a group to which this
31
- // route belongs. If a route belongs to a group, only
32
- // the first matching route in the group will be used.
31
+ // route belongs. Grouping a route makes it mutually
32
+ // exclusive with others in its group; if a route belongs
33
+ // to a group, only the first matching route in that group
34
+ // will be executed.
35
Group string `json:"group,omitempty"`
36
37
// The matcher sets which will be used to qualify this
36
- // route for a request. Essentially the "if" statement
37
- // of this route. Each matcher set is OR'ed, but matchers
38
+ // route for a request (essentially the "if" statement
39
+ // of this route). Each matcher set is OR'ed, but matchers
40
// within a set are AND'ed together.
41
MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"`
42
89
// If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes.
90
HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"`
91
90
- // If true, no more routes will be executed after this one, even if they matched.
92
+ // If true, no more routes will be executed after this one.
93
Terminal bool `json:"terminal,omitempty"`
94
95
// decoded values
96
MatcherSets MatcherSets `json:"-"`
97
Handlers []MiddlewareHandler `json:"-"`
98
+
99
+ middleware []Middleware
100
}
101
102
// Empty returns true if the route has all zero/default values.
115
116
// Provision sets up all the routes by loading the modules.
117
func (routes RouteList) Provision(ctx caddy.Context) error {
114
- for i, route := range routes {
118
+ for i := range routes {
119
// matchers
116
- matchersIface, err := ctx.LoadModule(&route, "MatcherSetsRaw")
120
+ matchersIface, err := ctx.LoadModule(&routes[i], "MatcherSetsRaw")
121
if err != nil {
122
return fmt.Errorf("loading matchers in route %d: %v", i, err)
123
}
127
}
128
129
// handlers
126
- handlersIface, err := ctx.LoadModule(&route, "HandlersRaw")
130
+ handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw")
131
if err != nil {
132
return fmt.Errorf("loading handler modules in route %d: %v", i, err)
133
}
134
for _, handler := range handlersIface.([]interface{}) {
135
routes[i].Handlers = append(routes[i].Handlers, handler.(MiddlewareHandler))
136
}
137
+
138
+ // pre-compile the middleware handler chain
139
+ for _, midhandler := range routes[i].Handlers {
140
+ routes[i].middleware = append(routes[i].middleware, wrapMiddleware(midhandler))
141
+ }
142
}
143
+
144
return nil
145
}
146
137
-// BuildCompositeRoute creates a chain of handlers by
138
-// applying all of the matching routes.
139
-func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
140
- if len(routes) == 0 {
141
- return emptyHandler
147
+// Compile prepares a middleware chain from the route list.
148
+// This should only be done once: after all the routes have
149
+// been provisioned, and before serving requests.
150
+func (routes RouteList) Compile() Handler {
151
+ var mid []Middleware
152
+ for _, route := range routes {
153
+ mid = append(mid, wrapRoute(route))
154
+ }
155
+ stack := emptyHandler
156
+ for i := len(mid) - 1; i >= 0; i-- {
157
+ stack = mid[i](stack)
158
}
159
+ return stack
160
+}
161
144
- var mid []Middleware
145
- groups := make(map[string]struct{})
162
+// wrapRoute wraps route with a middleware and handler so that it can
163
+// be chained in and defer evaluation of its matchers to request-time.
164
+// Like wrapMiddleware, it is vital that this wrapping takes place in
165
+// its own stack frame so as to not overwrite the reference to the
166
+// intended route by looping and changing the reference each time.
167
+func wrapRoute(route Route) Middleware {
168
+ return func(next Handler) Handler {
169
+ return HandlerFunc(func(rw http.ResponseWriter, req *http.Request) error {
170
+ // copy the next handler (it's an interface, so it's just
171
+ // a very lightweight copy of a pointer); this is important
172
+ // because this is a closure to the func below, which
173
+ // re-assigns the value as it compiles the middleware stack;
174
+ // if we don't make this copy, we'd affect the underlying
175
+ // pointer for all future request (yikes); we could
176
+ // alternatively solve this by moving the func below out of
177
+ // this closure and into a standalone package-level func,
178
+ // but I just thought this made more sense
179
+ nextCopy := next
180
147
- for _, route := range routes {
148
- // route must match at least one of the matcher sets
149
- if !route.MatcherSets.AnyMatch(req) {
150
- continue
151
- }
181
+ // route must match at least one of the matcher sets
182
+ if !route.MatcherSets.AnyMatch(req) {
183
+ return nextCopy.ServeHTTP(rw, req)
184
+ }
185
153
- // if route is part of a group, ensure only the
154
- // first matching route in the group is applied
155
- if route.Group != "" {
156
- _, ok := groups[route.Group]
157
- if ok {
158
- // this group has already been satisfied
159
- // by a matching route
160
- continue
186
+ // if route is part of a group, ensure only the
187
+ // first matching route in the group is applied
188
+ if route.Group != "" {
189
+ groups := req.Context().Value(routeGroupCtxKey).(map[string]struct{})
190
+
191
+ if _, ok := groups[route.Group]; ok {
192
+ // this group has already been
193
+ // satisfied by a matching route
194
+ return nextCopy.ServeHTTP(rw, req)
195
+ }
196
+
197
+ // this matching route satisfies the group
198
+ groups[route.Group] = struct{}{}
199
}
162
- // this matching route satisfies the group
163
- groups[route.Group] = struct{}{}
164
- }
200
166
- // apply the rest of the route
167
- for _, mh := range route.Handlers {
168
- // we have to be sure to wrap mh outside
169
- // of our current stack frame so that the
170
- // reference to this mh isn't overwritten
171
- // on the next iteration, leaving the last
172
- // middleware in the chain as the ONLY
173
- // middleware in the chain!
174
- mid = append(mid, wrapMiddleware(mh))
175
- }
201
+ // make terminal routes terminate
202
+ if route.Terminal {
203
+ nextCopy = emptyHandler
204
+ }
205
177
- // if this route is supposed to be last, don't
178
- // compile any more into the chain
179
- if route.Terminal {
180
- break
181
- }
182
- }
206
+ // compile this route's handler stack
207
+ for i := len(route.middleware) - 1; i >= 0; i-- {
208
+ nextCopy = route.middleware[i](nextCopy)
209
+ }
210
184
- // build the middleware chain, with the responder at the end
185
- stack := emptyHandler
186
- for i := len(mid) - 1; i >= 0; i-- {
187
- stack = mid[i](stack)
211
+ return nextCopy.ServeHTTP(rw, req)
212
+ })
213
}
189
-
190
- return stack
214
}
215
193
-// wrapMiddleware wraps m such that it can be correctly
194
-// appended to a list of middleware. We can't do this
195
-// directly in a loop because it relies on a reference
196
-// to mh not changing until the execution of its handler,
197
-// which is deferred by multiple func closures. In other
198
-// words, we need to pull this particular MiddlewareHandler
216
+// wrapMiddleware wraps mh such that it can be correctly
217
+// appended to a list of middleware in preparation for
218
+// compiling into a handler chain. We can't do this inline
219
+// inside a loop, because it relies on a reference to mh
220
+// not changing until the execution of its handler (which
221
+// is deferred by multiple func closures). In other words,
222
+// we need to pull this particular MiddlewareHandler
223
// pointer into its own stack frame to preserve it so it
224
// won't be overwritten in future loop iterations.
225
func wrapMiddleware(mh MiddlewareHandler) Middleware {
202
- return func(next HandlerFunc) HandlerFunc {
203
- return func(w http.ResponseWriter, r *http.Request) error {
204
- // TODO: We could wait to evaluate matchers here, just eval
205
- // the next matcher and choose the next route...
206
-
207
- // TODO: This is where request tracing could be implemented; also
208
- // see below to trace the responder as well
209
- // TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
226
+ return func(next Handler) Handler {
227
+ // copy the next handler (it's an interface, so it's
228
+ // just a very lightweight copy of a pointer); this
229
+ // is a safeguard against the handler changing the
230
+ // value, which could affect future requests (yikes)
231
+ nextCopy := next
232
+
233
+ return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
234
+ // TODO: This is where request tracing could be implemented
235
+ // TODO: Trace a diff of the request, would be cool too... see what changed since the last middleware (host, headers, URI...)
236
// TODO: see what the std lib gives us in terms of stack tracing too
211
- return mh.ServeHTTP(w, r, next)
212
- }
237
+ return mh.ServeHTTP(w, r, nextCopy)
238
+ })
239
}
240
}
241
245
type MatcherSet []RequestMatcher
246
247
// Match returns true if the request matches all
222
-// matchers in mset.
248
+// matchers in mset or if there are no matchers.
249
func (mset MatcherSet) Match(r *http.Request) bool {
250
for _, m := range mset {
251
if !m.Match(r) {
291
}
292
return nil
293
}
294
+
295
+var routeGroupCtxKey = caddy.CtxKey("route_group")