1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
|
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyhttp
import (
"encoding/json"
"fmt"
"net/http"
"github.com/caddyserver/caddy/v2"
)
// ServerRoute represents a set of matching rules,
// middlewares, and a responder for handling HTTP
// requests.
type ServerRoute struct {
Group string `json:"group,omitempty"`
MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
Apply []json.RawMessage `json:"apply,omitempty"`
Respond json.RawMessage `json:"respond,omitempty"`
Terminal bool `json:"terminal,omitempty"`
// decoded values
matcherSets []MatcherSet
middleware []MiddlewareHandler
responder Handler
}
func (sr ServerRoute) anyMatcherSetMatches(r *http.Request) bool {
for _, ms := range sr.matcherSets {
if ms.Match(r) {
return true
}
}
// if no matchers, always match
return len(sr.matcherSets) == 0
}
// MatcherSet is a set of matchers which
// must all match in order for the request
// to be matched successfully.
type MatcherSet []RequestMatcher
// Match returns true if the request matches all
// matchers in mset.
func (mset MatcherSet) Match(r *http.Request) bool {
for _, m := range mset {
if !m.Match(r) {
return false
}
}
return true
}
// RouteList is a list of server routes that can
// create a middleware chain.
type RouteList []ServerRoute
// Provision sets up all the routes by loading the modules.
func (routes RouteList) Provision(ctx caddy.Context) error {
for i, route := range routes {
// matchers
for _, matcherSet := range route.MatcherSets {
var matchers MatcherSet
for modName, rawMsg := range matcherSet {
val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
if err != nil {
return fmt.Errorf("loading matcher module '%s': %v", modName, err)
}
matchers = append(matchers, val.(RequestMatcher))
}
routes[i].matcherSets = append(routes[i].matcherSets, matchers)
}
routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help?
// middleware
for j, rawMsg := range route.Apply {
mid, err := ctx.LoadModuleInline("middleware", "http.middleware", rawMsg)
if err != nil {
return fmt.Errorf("loading middleware module in position %d: %v", j, err)
}
routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
}
routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
// responder
if route.Respond != nil {
resp, err := ctx.LoadModuleInline("responder", "http.responders", route.Respond)
if err != nil {
return fmt.Errorf("loading responder module: %v", err)
}
routes[i].responder = resp.(Handler)
}
routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
}
return nil
}
// BuildCompositeRoute creates a chain of handlers by applying all the matching
// routes. The returned ResponseWriter should be used instead of rw.
func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) (Handler, http.ResponseWriter) {
mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{rw}}
if len(routes) == 0 {
return emptyHandler, mrw
}
var mid []Middleware
var responder Handler
groups := make(map[string]struct{})
for _, route := range routes {
// route must match at least one of the matcher sets
if !route.anyMatcherSetMatches(req) {
continue
}
// if route is part of a group, ensure only
// the first matching route in the group is
// applied
if route.Group != "" {
_, ok := groups[route.Group]
if ok {
// this group has already been satisfied
// by a matching route
continue
}
// this matching route satisfies the group
groups[route.Group] = struct{}{}
}
// apply the rest of the route
for _, m := range route.middleware {
// we have to be sure to wrap m outside
// of our current scope so that the
// reference to this m isn't overwritten
// on the next iteration, leaving only
// the last middleware in the chain as
// the ONLY middleware in the chain!
mid = append(mid, wrapMiddleware(m))
}
if responder == nil {
responder = route.responder
}
if route.Terminal {
break
}
}
// build the middleware stack, with the responder at the end
stack := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
if responder == nil {
return nil
}
mrw.allowWrites = true
return responder.ServeHTTP(w, r)
})
for i := len(mid) - 1; i >= 0; i-- {
stack = mid[i](stack)
}
return stack, mrw
}
// wrapMiddleware wraps m such that it can be correctly
// appended to a list of middleware. This separate closure
// is necessary so that only the last middleware in a loop
// does not become the only middleware of the stack,
// repeatedly executed (i.e. it is necessary to keep a
// reference to this m outside of the scope of a loop)!
func wrapMiddleware(m MiddlewareHandler) Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
// TODO: This is where request tracing could be implemented; also
// see below to trace the responder as well
// TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
// TODO: see what the std lib gives us in terms of stack tracing too
return m.ServeHTTP(w, r, next)
}
}
}
type middlewareResponseWriter struct {
*ResponseWriterWrapper
allowWrites bool
}
func (mrw middlewareResponseWriter) WriteHeader(statusCode int) {
if !mrw.allowWrites {
// technically, this is not true: middleware can write headers,
// but only after the responder handler has returned; either the
// responder did nothing with the response (sad face), or the
// middleware wrapped the response and deferred the write
panic("WriteHeader: middleware cannot write response headers")
}
mrw.ResponseWriterWrapper.WriteHeader(statusCode)
}
func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
if !mrw.allowWrites {
panic("Write: middleware cannot write to the response before responder")
}
return mrw.ResponseWriterWrapper.Write(b)
}
// Interface guard
var _ HTTPInterfaces = (*middlewareResponseWriter)(nil)
|