trout
trout/route.go
Publish under MIT license. Add a license so this package can officially be used by any project, basically.
1 package trout
3 import (
4 "net/http"
5 "strconv"
6 "strings"
7 "time"
8 )
10 var (
11 default404Handler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12 w.WriteHeader(http.StatusNotFound)
13 w.Write([]byte("404 Not Found"))
14 return
15 }))
16 default405Handler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 w.WriteHeader(http.StatusMethodNotAllowed)
18 w.Write([]byte("405 Method Not Allowed"))
19 return
20 }))
21 )
23 // RequestVars returns easy-to-access mappings of parameters to values for URL templates. Any {parameter} in
24 // your URL template will be available in the returned Header as a slice of strings, one for each instance of
25 // the {parameter}. In the case of a parameter name being used more than once in the same URL template, the
26 // values will be in the slice in the order they appeared in the template.
27 //
28 // Values can easily be accessed by using the .Get() method of the returned Header, though to access multiple
29 // values, they must be accessed through the map. All parameters use http.CanonicalHeaderKey for their formatting.
30 // When using .Get(), the parameter name will be transformed automatically. When utilising the Header as a map,
31 // the parameter name needs to have http.CanonicalHeaderKey applied manually.
32 func RequestVars(r *http.Request) http.Header {
33 res := http.Header{}
34 for h, v := range r.Header {
35 stripped := strings.TrimPrefix(h, http.CanonicalHeaderKey("Trout-Param-"))
36 if stripped != h {
37 res[stripped] = v
38 }
39 }
40 return res
41 }
43 // Router defines a set of Endpoints that map requests to the http.Handlers. The http.Handler assigned to
44 // Handle404, if set, will be called when no Endpoint matches the current request. The http.Handler assigned
45 // to Handle405, if set, will be called when an Endpoint matches the current request, but has no http.Handler
46 // set for the HTTP method that the request used. Should either of these properties be unset, a default
47 // http.Handler will be used.
48 //
49 // The Router type is safe for use with empty values, but makes no attempt at concurrency-safety in adding
50 // Endpoints or in setting properties. It should also be noted that the adding Endpoints while simultaneously
51 // routing requests will lead to undefined and (almost certainly) undesirable behaviour. Routers are intended
52 // to be initialised with a set of Endpoints, and then start serving requests. Using them outside of this use
53 // case is unsupported.
54 type Router struct {
55 t *trie
56 Handle404 http.Handler
57 Handle405 http.Handler
58 }
60 func (router *Router) serve404(w http.ResponseWriter, r *http.Request, t time.Time) {
61 h := default404Handler
62 if router.Handle404 != nil {
63 h = router.Handle404
64 }
65 r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(t).Nanoseconds(), 10))
66 h.ServeHTTP(w, r)
67 }
69 func (router *Router) serve405(w http.ResponseWriter, r *http.Request, t time.Time) {
70 h := default405Handler
71 if router.Handle405 != nil {
72 h = router.Handle405
73 }
74 r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(t).Nanoseconds(), 10))
75 h.ServeHTTP(w, r)
76 }
78 func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
79 start := time.Now()
80 if router.t == nil {
81 router.serve404(w, r, start)
82 return
83 }
84 pieces := strings.Split(strings.ToLower(strings.Trim(r.URL.Path, "/")), "/")
85 router.t.RLock()
86 defer router.t.RUnlock()
87 branches := make([]*branch, len(pieces))
88 path, ok := router.t.match(pieces)
89 if !ok {
90 router.serve404(w, r, start)
91 return
92 }
93 b := router.t.branch
94 for i, pos := range path {
95 b = b.children[pos]
96 branches[i] = b
97 }
98 v := vars(branches, pieces)
99 for key, vals := range v {
100 r.Header[http.CanonicalHeaderKey("Trout-Param-"+key)] = vals
101 }
102 ms := make([]string, len(b.methods))
103 i := 0
104 for m := range b.methods {
105 ms[i] = m
106 i = i + 1
107 }
108 r.Header[http.CanonicalHeaderKey("Trout-Methods")] = ms
109 h := b.methods[r.Method]
110 if h == nil {
111 router.serve405(w, r, start)
112 return
113 }
114 r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(start).Nanoseconds(), 10))
115 h.ServeHTTP(w, r)
116 }
118 // Endpoint defines a new Endpoint on the Router. The Endpoint should be a URL template, using curly braces
119 // to denote parameters that should be filled at runtime. For example, `{id}` denotes a parameter named `id`
120 // that should be filled with whatever the request has in that space.
121 //
122 // Parameters are always `/`-separated strings. There is no support for regular expressions or other limitations
123 // on what may be in those strings. A parameter is simply defined as "whatever is between these two / characters".
124 func (router *Router) Endpoint(e string) *Endpoint {
125 e = strings.Trim(e, "/")
126 e = strings.ToLower(e)
127 pieces := strings.Split(e, "/")
128 if router.t == nil {
129 router.t = &trie{}
130 }
131 router.t.Lock()
132 defer router.t.Unlock()
133 if router.t.branch == nil {
134 router.t.branch = &branch{
135 parent: nil,
136 children: []*branch{},
137 key: "",
138 isParam: false,
139 methods: map[string]http.Handler{},
140 }
141 }
142 closest := findClosestLeaf(pieces, router.t.branch)
143 b := router.t.branch
144 for _, pos := range closest {
145 b = b.children[pos]
146 }
147 if len(closest) == len(pieces) {
148 return (*Endpoint)(b)
149 }
150 offset := len(closest)
151 for i := offset; i < len(pieces); i++ {
152 piece := pieces[i]
153 var isParam bool
154 if len(piece) > 0 && piece[0:1] == "{" && piece[len(piece)-1:] == "}" {
155 isParam = true
156 piece = piece[1 : len(piece)-1]
157 }
158 b = b.addChild(piece, isParam)
159 }
160 return (*Endpoint)(b)
161 }
163 func vars(path []*branch, pieces []string) map[string][]string {
164 v := map[string][]string{}
165 for pos, p := range path {
166 if !p.isParam {
167 continue
168 }
169 _, ok := v[p.key]
170 if !ok {
171 v[p.key] = []string{pieces[pos]}
172 continue
173 }
174 v[p.key] = append(v[p.key], pieces[pos])
175 }
176 return v
177 }
179 func findClosestLeaf(pieces []string, b *branch) []int {
180 offset := 0
181 path := []int{}
182 longest := []int{}
183 num := len(pieces)
184 for i := 0; i < num; i++ {
185 piece := pieces[i]
186 var isParam bool
187 if len(piece) > 0 && piece[0:1] == "{" && piece[len(piece)-1:] == "}" {
188 isParam = true
189 piece = piece[1 : len(piece)-1]
190 }
191 offset = pickNextRoute(b, offset, piece, isParam)
192 if offset == -1 {
193 if len(path) == 0 {
194 // exhausted our options, bail
195 break
196 }
197 // no match, maybe save this and backup
198 if len(path) > len(longest) {
199 longest = append([]int{}, path...) // copy them over so they don't get modified
200 }
201 path, offset = backup(path)
202 offset = offset + 1
203 b = b.parent
204 i = i - 2
205 } else {
206 path = append(path, offset)
207 b = b.children[offset]
208 offset = 0
209 }
210 }
211 if len(longest) < len(path) {
212 longest = append([]int{}, path...)
213 }
214 return longest
215 }
217 func pickNextRoute(b *branch, offset int, input string, variable bool) int {
218 count := len(b.children)
219 for i := offset; i < count; i++ {
220 if b.children[i].key == input && b.isParam == variable {
221 return i
222 }
223 }
224 return -1
225 }
227 // Endpoint defines a single URL template that requests can be matched against. It uses
228 // URL parameters to accept variables in the URL structure and make them available to
229 // the Handlers associated with the Endpoint.
230 type Endpoint branch
232 // Handler associates the passed http.Handler with the Endpoint. This http.Handler will be
233 // used for all requests, regardless of the HTTP method they are using, unless overridden by
234 // the Methods method. Endpoints without a http.Handler associated with them will not be
235 // considered matches for requests, unless the request was made using an HTTP method that the
236 // Endpoint has an http.Handler mapped to.
237 func (e *Endpoint) Handler(h http.Handler) {
238 (*branch)(e).setHandler("", h)
239 }
241 // Methods simple returns a Methods object that will enable the mapping of the passed HTTP
242 // request methods to a Methods object. On its own, this function does not modify anything. It
243 // should, instead, be used as a friendly shorthand to get to the Methods.Handler method.
244 func (e *Endpoint) Methods(m ...string) Methods {
245 return Methods{
246 e: e,
247 m: m,
248 }
249 }
251 // Methods defines a pairing of an Endpoint to the HTTP request methods that should be mapped to
252 // specific http.Handlers. Its sole purpose is to enable the Methods.Handler method.
253 type Methods struct {
254 e *Endpoint
255 m []string
256 }
258 // Handler maps a Methods object to a specific http.Handler. This overrides the http.Handler
259 // associated with the Endpoint to only handle specific HTTP method(s).
260 func (m Methods) Handler(h http.Handler) {
261 b := (*branch)(m.e)
262 for _, method := range m.m {
263 b.setHandler(method, h)
264 }
265 }