-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrouter.go
275 lines (224 loc) · 6.65 KB
/
router.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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// Package router contains a simple and fast HTTP requests router that supports
// named parameters and specific handlers for different methods.
package router
import (
"errors"
"fmt"
"net/http"
"strings"
)
const (
wrongParamNameChars string = `/:`
)
// Router errors.
var (
ErrParameterName error = errors.New(fmt.Sprintf("router: parameter name cannot contain any of these charactars: %#q", wrongParamNameChars))
ErrDuplicateHandler error = errors.New("router: handler for this path and method combination was already registered")
)
// A HandlerFunc represents an HTTP request handler function.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, ps Params)
// A PanicHandlerFunc represents a special handler that will be
// called in case of panic during the request handlind.
type PanicHandlerFunc func(w http.ResponseWriter, r *http.Request, err interface{})
// A Params stores parameters that were passed as a part of URI.
type Params map[string][]string
// A Router stores all routes with corresponding API handler functions.
type Router struct {
routes map[string]*pathData
PanicHandler PanicHandlerFunc
}
type pathMethods map[string]HandlerFunc
type pathData struct {
path string
param string
methods pathMethods
}
// New initializes and returns a new router.
func New() *Router {
return &Router{routes: map[string]*pathData{}}
}
// Get returns value for parameter with specified name.
// If parameter has several values, first one is returned.
func (ps Params) Get(name string) (string, bool) {
v, ok := ps[name]
if !ok || len(v) == 0 {
return "", false
}
return v[0], true
}
// ServeHTTP handles the API request. It may perform some actions before
// and/or after calling the handler function.
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Recover from panic.
defer func() {
if err := recover(); err != nil {
// Check if custom panic handler present.
if router.PanicHandler != nil {
// Call the custom panic handler.
router.PanicHandler(w, r, err)
} else {
// Write HTTP status code 500 Internal Server Error.
w.WriteHeader(http.StatusInternalServerError)
}
}
}()
// Handle HTTP request.
router.doServeHTTP(w, r)
}
func (router *Router) doServeHTTP(w http.ResponseWriter, r *http.Request) {
// Try to get path data.
pd, param := router.getPathData(r.URL.Path)
if pd == nil {
// Set status code to 404 Not Found.
w.WriteHeader(http.StatusNotFound)
return
}
// Try to get handler function for requested method.
f, ok := pd.methods[r.Method]
if !ok {
// Create a list of allowed methods.
allow := ""
for m, _ := range pd.methods {
allow += m
}
// Set Allow header.
w.Header().Set("Allow", strings.TrimSuffix(allow, ", "))
// Set status code to 405 Method Not Allowed.
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
// Parse form data.
err := r.ParseForm()
if err != nil {
panic(err)
}
// Get form parameters.
params := Params(r.Form)
// Add parameter sent as part of the URI if needed.
if pd.param != "" {
// Create new slice of values for parameter.
s := []string{param}
// Check if parameter name is used by form parameters.
if v, ok := params[pd.param]; ok {
// Insert the parameter sent as part of the URI at the beginning.
// This is needed so that Params.Get() will return it.
params[pd.param] = append(s, v...)
} else {
// Add new parameter name.
params[pd.param] = s
}
}
// Call the request handler.
f(w, r, params)
}
// Handle sets an HTTP request handler for specific method and pattern.
// Patterns support named parameters, for example:
//
// err := Handle("GET", "/api/users/:id", usersByIdHandler)
//
// will pass id parameter to handler. Only one named parameter is
// supported and it must be at the end of the URI.
//
func (r *Router) Handle(method string, pattern string, handler HandlerFunc) error {
// Parse pattern.
path, param, err := parsePattern(pattern)
if err != nil {
return err
}
// Try to get existing path data for the path.
pd, ok := r.routes[path]
if !ok {
// Create new path data.
pd = &pathData{
path: path,
param: param,
methods: pathMethods{},
}
r.routes[path] = pd
}
// Check if handler for the path is already registred.
if _, ok := pd.methods[method]; ok {
return ErrDuplicateHandler
}
// Add handler for current method.
pd.methods[method] = handler
return nil
}
// Get adds handler for GET request.
func (r *Router) Get(pattern string, handler HandlerFunc) error {
return r.Handle("GET", pattern, handler)
}
// Put adds handler for PUT request.
func (r *Router) Put(pattern string, handler HandlerFunc) error {
return r.Handle("PUT", pattern, handler)
}
// Post adds handler for POST request.
func (r *Router) Post(pattern string, handler HandlerFunc) error {
return r.Handle("POST", pattern, handler)
}
// Delete adds handler for DELETE request.
func (r *Router) Delete(pattern string, handler HandlerFunc) error {
return r.Handle("DELETE", pattern, handler)
}
func normalizePath(p string) string {
// Return root path if empty string is received.
if len(p) == 0 {
return "/"
}
// Trim slashes at the end.
s := strings.TrimRight(p, "/")
// Replace backslashes with slashes (\ -> /).
s = strings.Replace(s, "\\", "/", -1)
// Remove duplicate slashes (// -> /).
for strings.Contains(s, "//") {
s = strings.Replace(s, "//", "/", -1)
}
// Convert the string to lower.
s = strings.ToLower(s)
// Add leading slash if needed.
if p[0] != '/' {
s = "/" + s
}
// Return normalized path.
return s
}
func parsePattern(pattern string) (string, string, error) {
// Normalize pattern.
path := normalizePath(pattern)
// Check if pattern contains parameter.
var param string
i := strings.Index(path, "/:")
if i >= 0 {
// Get parameter name.
param = path[i+2:]
// Check parameter name.
if strings.ContainsAny(param, wrongParamNameChars) {
return "", "", ErrParameterName
}
// Remove parameter from the path, but keep "/:" at the end.
path = path[:i+2]
}
// Return path and named parameter name.
return path, param, nil
}
func (router *Router) getPathData(path string) (*pathData, string) {
// Normalize path.
path = normalizePath(path)
// Try to get route without named parameter.
if pd, ok := router.routes[path]; ok {
// Return path data.
return pd, ""
}
// Try to get route with named parameter.
if i := strings.LastIndex(path, "/"); i > 0 {
// Path with named parameter: remove parameter value and add "/:".
if pd, ok := router.routes[path[:i]+"/:"]; ok {
// Get parameter value.
p := path[i+1:]
// Return path data and named parameter name.
return pd, p
}
}
// Path data was not found.
return nil, ""
}