forked from VividCortex/siesta
-
Notifications
You must be signed in to change notification settings - Fork 0
/
service.go
160 lines (133 loc) · 3.96 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
package siesta
import (
"net/http"
"path"
"regexp"
"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.
type Service struct {
baseURI string
pre []contextHandler
post []contextHandler
handlers map[*regexp.Regexp]contextHandler
routes map[string]*node
}
// 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, "/"),
handlers: make(map[*regexp.Regexp]contextHandler),
routes: map[string]*node{},
}
}
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)
}
// ServiceHTTPInContext 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) {
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 != "/" {
r.URL.Path = strings.TrimRight(r.URL.Path, "/")
}
var (
handler contextHandler
params routeParams
)
// Lookup the tree for this method
routeNode, ok := s.routes[r.Method]
if ok {
handler, params, _ = routeNode.getValue(r.URL.Path)
}
if handler == nil {
http.NotFoundHandler().ServeHTTP(w, r)
} else {
r.ParseForm()
for _, p := range params {
r.Form.Set(p.Key, p.Value)
}
handler(c, w, r, func() {
quit = true
})
}
}
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, "/")), 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)
}