-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathservice.go
221 lines (187 loc) · 5.43 KB
/
service.go
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
221
package siesta
import (
"io"
"io/ioutil"
"net/http"
"path"
"strings"
)
// Registered services keyed by base URI.
var services = map[string]*Service{}
// A Service is a container for routes with a common base URI.
// It also has two middleware chains, named "pre" and "post".
//
// The "pre" chain is run before the main handler. The first
// handler in the "pre" chain is guaranteed to run, but execution
// may quit anywhere else in the chain.
//
// If the "pre" chain executes completely, the main handler is executed.
// It is skipped otherwise.
//
// The "post" chain runs after the main handler, whether it is skipped
// or not. The first handler in the "post" chain is guaranteed to run, but
// execution may quit anywhere else in the chain if the quit function
// is called.
type Service struct {
baseURI string
trimSlash bool
pre []ContextHandler
post []ContextHandler
routes map[string]*node
notFound ContextHandler
// postExecutionFunc runs at the end of the request
postExecutionFunc func(c Context, r *http.Request, panicValue interface{})
}
// NewService returns a new Service with the given base URI
// or panics if the base URI has already been registered.
func NewService(baseURI string) *Service {
if services[baseURI] != nil {
panic("service already registered")
}
return &Service{
baseURI: path.Join("/", baseURI, "/"),
routes: map[string]*node{},
trimSlash: true,
}
}
// SetPostExecutionFunc sets a function that is executed at the end of every request.
// panicValue will be non-nil if a value was recovered after a panic.
func (s *Service) SetPostExecutionFunc(f func(c Context, r *http.Request, panicValue interface{})) {
s.postExecutionFunc = f
}
// DisableTrimSlash disables the removal of trailing slashes
// before route matching.
func (s *Service) DisableTrimSlash() {
s.trimSlash = false
}
func addToChain(f interface{}, chain []ContextHandler) []ContextHandler {
m := ToContextHandler(f)
return append(chain, m)
}
// AddPre adds f to the end of the "pre" chain.
// It panics if f cannot be converted to a ContextHandler (see Service.Route).
func (s *Service) AddPre(f interface{}) {
s.pre = addToChain(f, s.pre)
}
// AddPost adds f to the end of the "post" chain.
// It panics if f cannot be converted to a ContextHandler (see Service.Route).
func (s *Service) AddPost(f interface{}) {
s.post = addToChain(f, s.post)
}
// Service satisfies the http.Handler interface.
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.ServeHTTPInContext(NewSiestaContext(), w, r)
}
// ServeHTTPInContext serves an HTTP request within the Context c.
// A Service will run through both of its internal chains, quitting
// when requested.
func (s *Service) ServeHTTPInContext(c Context, w http.ResponseWriter, r *http.Request) {
defer func() {
var e interface{}
// Check if there was a panic
e = recover()
// Run the post execution func if we have one
if s.postExecutionFunc != nil {
s.postExecutionFunc(c, r, e)
}
if e != nil {
// Re-panic if we recovered
panic(e)
}
}()
r.ParseForm()
quit := false
for _, m := range s.pre {
m(c, w, r, func() {
quit = true
})
if quit {
// Break out of the "pre" loop, but
// continue on.
break
}
}
if !quit {
// The main handler is only run if we have not
// been signaled to quit.
if r.URL.Path != "/" && s.trimSlash {
r.URL.Path = strings.TrimRight(r.URL.Path, "/")
}
var (
handler ContextHandler
usage string
params routeParams
)
// Lookup the tree for this method
routeNode, ok := s.routes[r.Method]
if ok {
handler, usage, params, _ = routeNode.getValue(r.URL.Path)
c.Set(UsageContextKey, usage)
}
if handler == nil {
if s.notFound != nil {
// Use user-defined handler.
s.notFound(c, w, r, func() {})
} else {
// Default to the net/http NotFoundHandler.
http.NotFoundHandler().ServeHTTP(w, r)
}
} else {
for _, p := range params {
r.Form.Set(p.Key, p.Value)
}
handler(c, w, r, func() {
quit = true
})
if r.Body != nil {
io.Copy(ioutil.Discard, r.Body)
r.Body.Close()
}
}
}
quit = false
for _, m := range s.post {
m(c, w, r, func() {
quit = true
})
if quit {
return
}
}
}
// Route adds a new route to the Service.
// f must be a function with one of the following signatures:
//
// func(http.ResponseWriter, *http.Request)
// func(http.ResponseWriter, *http.Request, func())
// func(Context, http.ResponseWriter, *http.Request)
// func(Context, http.ResponseWriter, *http.Request, func())
//
// Note that Context is an interface type defined in this package.
// The last argument is a function which is called to signal the
// quitting of the current execution sequence.
func (s *Service) Route(verb, uriPath, usage string, f interface{}) {
handler := ToContextHandler(f)
if n := s.routes[verb]; n == nil {
s.routes[verb] = &node{}
}
s.routes[verb].addRoute(
path.Join(s.baseURI, strings.TrimRight(uriPath, "/")),
usage, handler)
}
// SetNotFound sets the handler for all paths that do not
// match any existing routes. It accepts the same function
// signatures that Route does with the addition of `nil`.
func (s *Service) SetNotFound(f interface{}) {
if f == nil {
s.notFound = nil
return
}
handler := ToContextHandler(f)
s.notFound = handler
}
// Register registers s by adding it as a handler to the
// DefaultServeMux in the net/http package.
func (s *Service) Register() {
http.Handle(s.baseURI, s)
}