-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrouter.go
184 lines (157 loc) · 5.05 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
// Package route is a HTTP request router.
//
// The router matches incoming requests by path to registered handlers. It
// should feel familiar to users of the net/http package.
//
// The registered path may contain parameters, of which there are two types.
//
// Named
//
// Named parameters match single path segments. They match until the next '/' or
// the path end:
//
// Path: /blog/:category/:post
//
// Requests:
// /blog/go/request-routers match: category="go", post="request-routers"
// /blog/go/request-routers/ redirect to /blog/go/request-routers
// /blog/go/ no match
// /blog/go/request-routers/comments no match
//
// Catch-all
//
// Catch-all parameters match anything until the path end. Since they match
// anything until the end, catch-all paramerters must always be the final path
// element.
//
// Path: /files/*filepath
//
// Requests:
// /files/ match: filepath=""
// /files/LICENSE match: filepath="LICENSE"
// /files/templates/article.html match: filepath="templates/article.html"
// /files match: filepath=""
//
// The value of parameters is saved as a map[string]string against the
// request. To retrieve the parameters for a request use the Vars function:
//
// vars := route.Vars(r)
//
package route
import (
"context"
"net/http"
"sync"
)
type Handler interface {
ServeErrorHTTP(w http.ResponseWriter, r *http.Request) error
}
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := h(w, r); err != nil {
panic(err)
}
}
func (h HandlerFunc) ServeErrorHTTP(w http.ResponseWriter, r *http.Request) error {
return h(w, r)
}
type nilErrorHandler struct {
http.Handler
}
func (h nilErrorHandler) ServeErrorHTTP(w http.ResponseWriter, r *http.Request) error {
h.ServeHTTP(w, r)
return nil
}
// Router is a http.Handler which can be used to dispatch requests to different
// handler functions via configurable routes
type Router struct {
// NotFoundHandler is called when no matching route is found. By default it is
// set to http.NotFoundHandler().
NotFoundHandler http.Handler
// ErrorHandler is called if an error is raised by any handler.
ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)
mu sync.RWMutex
tree *treeLookup
}
// Default is the router instance used by the Handle and HandleFunc functions.
var Default = New()
// Handle registers the handler for the given path to the Default router.
func Handle(path string, handler interface{}) {
Default.Handle(path, handler)
}
// HandleFunc registers the handler function for the given path to the Default
// router.
func HandleFunc(path string, handler interface{}) {
Default.HandleFunc(path, handler)
}
// Make sure the Router conforms with the http.Handler interface
var _ http.Handler = New()
// New returns an initialized Router.
func New() *Router {
return &Router{
NotFoundHandler: http.NotFoundHandler(),
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {},
tree: newLookup(),
}
}
// Handle registers the handler for the given path to the router.
func (r *Router) Handle(path string, handle interface{}) {
r.mu.Lock()
defer r.mu.Unlock()
if path[0] != '/' {
panic("path must begin with '/'")
}
switch v := handle.(type) {
case Handler:
r.tree.Add(path, v)
case http.Handler:
r.tree.Add(path, nilErrorHandler{v})
default:
panic("tried to register unhandleable type with Handle")
}
}
// HandleFunc registers the handler function (either `func(http.ResponseWriter,
// *http.Request)` or `func(http.ResponseWriter, *http.Request) error`) for the
// given path to the Default router.
func (r *Router) HandleFunc(path string, handler interface{}) {
switch v := handler.(type) {
case func(http.ResponseWriter, *http.Request) error:
r.Handle(path, HandlerFunc(v))
case func(http.ResponseWriter, *http.Request):
r.Handle(path, http.HandlerFunc(v))
default:
panic("tried to register unhandleable func type with HandleFunc")
}
}
// ServeHTTP dispatches the request to appropriate handler, if none can be found
// NotFoundHandler is used.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.EscapedPath()
if req.Method != "CONNECT" {
if cleanpath := cleanPath(path); cleanpath != path {
url := *req.URL
url.Path = cleanpath
http.RedirectHandler(url.String(), http.StatusMovedPermanently).ServeHTTP(w, req)
return
}
}
r.mu.RLock()
defer r.mu.RUnlock()
if handle, ps := r.tree.Get(path); handle != nil {
req = req.WithContext(context.WithValue(req.Context(), varsKey{}, ps))
err := handle.ServeErrorHTTP(w, req)
if err != nil {
r.ErrorHandler(w, req, err)
}
return
}
r.NotFoundHandler.ServeHTTP(w, req)
}
type varsKey struct{}
// Vars retrieves the parameter matches for the given request.
func Vars(r *http.Request) map[string]string {
if rv := r.Context().Value(varsKey{}); rv != nil {
return rv.(map[string]string)
}
return nil
}