Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(63)

Side by Side Diff: server/router/router.go

Issue 2043423004: Make HTTP middleware easier to use (Closed) Base URL: https://github.com/luci/luci-go@master
Patch Set: Update tests Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package router
6
7 import (
8 "net/http"
9
10 "github.com/julienschmidt/httprouter"
11 "golang.org/x/net/context"
12 )
13
14 // TODO(nishanths): Implement adapter methods to convert http style handlers to Handlers.
15
16 // Router represents the main router type.
17 type (
18 // Router is the main type for the package. It contains the base path fo r
19 // the router, a list of handlers, and configuration.
dnj (Google) 2016/06/13 18:50:42 nit: Note that Router shouldn't be instantiated by
20 Router struct {
21 hrouter *httprouter.Router
22 handlers []Handler
23 BasePath string
24 parent *Router
25 }
26
27 // Context is the argument to a Handler method. It is used to carry the
28 // HTTP request and response writer between handlers, share context,
29 // and manage flow using the Next and Abort methods.
30 Context struct {
31 Context context.Context
32 Writer http.ResponseWriter
33 Request *http.Request
34 Params httprouter.Params
35
36 handlers []Handler
37 index int
38 aborted bool
39 }
40 )
41
42 var (
43 // To ensure that Router conforms to the http.Handler interface.
44 _ http.Handler = New()
dnj (Google) 2016/06/13 18:50:42 Use: var _ http.Handler = (*Router)(nil) rather t
iannucci 2016/06/13 19:23:29 Do: _ http.Handler = (*Router)(nil) As it will
45
46 // VoidHandler is a handler that has no effect.
47 VoidHandler Handler = func(_ *Context) {}
48 )
49
50 // New creates a Router with specified initial context.
51 func New() *Router {
52 return &Router{
53 hrouter: httprouter.New(),
54 BasePath: "/",
55 }
56 }
57
58 // Use adds middleware chains to the group. The added middleware applies to
59 // all routes in the current group and in groups derived from the current group.
60 func (r *Router) Use(handlers ...Handler) {
61 r.handlers = append(r.handlers, handlers...)
62 }
63
64 // Group creates a new router with an updated base path.
65 // The new router carries over configuration from the router it derives
66 // from.
67 func (r *Router) Group(relativePath string) *Router {
dnj (Google) 2016/06/13 18:50:42 We should probably clean leading/trailing "/" from
nodir 2016/06/15 20:14:43 I think other routers use method names Path and Su
nishanths 2016/06/16 00:18:09 Chose 'Subrouter'. Open to changing the name if an
68 h := make([]Handler, len(r.handlers))
69 copy(h, r.handlers)
70 return &Router{
71 hrouter: r.hrouter,
72 handlers: h,
73 BasePath: r.BasePath + relativePath,
74 parent: r,
75 }
76 }
77
78 // GET is a shortcut for router.Handle("GET", path, handlers)
79 func (r *Router) GET(path string, handlers ...Handler) {
80 r.Handle("GET", path, handlers...)
81 }
82
83 // HEAD is a shortcut for router.Handle("HEAD", path, handlers)
84 func (r *Router) HEAD(path string, handlers ...Handler) {
85 r.Handle("HEAD", path, handlers...)
86 }
87
88 // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers)
89 func (r *Router) OPTIONS(path string, handlers ...Handler) {
90 r.Handle("OPTIONS", path, handlers...)
91 }
92
93 // POST is a shortcut for router.Handle("POST", path, handlers)
94 func (r *Router) POST(path string, handlers ...Handler) {
95 r.Handle("POST", path, handlers...)
96 }
97
98 // PUT is a shortcut for router.Handle("PUT", path, handlers)
99 func (r *Router) PUT(path string, handlers ...Handler) {
100 r.Handle("PUT", path, handlers...)
101 }
102
103 // PATCH is a shortcut for router.Handle("PATCH", path, handlers)
104 func (r *Router) PATCH(path string, handlers ...Handler) {
105 r.Handle("PATCH", path, handlers...)
106 }
107
108 // DELETE is a shortcut for router.Handle("DELETE", path, handlers)
109 func (r *Router) DELETE(path string, handlers ...Handler) {
110 r.Handle("DELETE", path, handlers...)
111 }
112
113 // Handle registers handlers for the given method and path. Handle panics if
114 // no handlers are provided.
115 func (r *Router) Handle(method, path string, handlers ...Handler) {
116 if len(handlers) == 0 {
117 panic("router: at least one handler should be specified")
118 }
119 h := r.adapt(handlers)
120 r.hrouter.Handle(method, path, h)
121 }
122
123 // ServeHTTP makes Router implement the http.Handler interface.
124 func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
125 r.hrouter.ServeHTTP(rw, req)
126 }
127
128 // copyHandlers allocates a new array and copies the
129 // the handlers from the two handler lists into it.
130 func copyHandlers(m, n []Handler) []Handler {
131 totalLen := len(m) + len(n)
132 merged := make([]Handler, totalLen)
dnj (Google) 2016/06/13 18:50:42 nit: probably cleaner to just allocate size 0, cap
nishanths 2016/06/15 18:25:08 Acknowledged.
133 copy(merged, m)
134 copy(merged[len(m):], n)
135 return merged
136 }
137
138 // adapt adapts a list of handlers into a httprouter-style handler.
139 func (r *Router) adapt(handlers []Handler) httprouter.Handle {
140 return httprouter.Handle(func(rw http.ResponseWriter, req *http.Request, p httprouter.Params) {
141 // TODO(maybe): Use a free list for making Contexts.
142 c := &Context{
143 Context: context.Background(),
144 Writer: rw,
145 Request: req,
146 Params: p,
147 handlers: copyHandlers(r.handlers, handlers),
dnj (Google) 2016/06/13 18:50:42 Do we really need to allocate/copy handlers each t
nishanths 2016/06/15 18:25:08 Thanks for catching this. Done.
148 index: -1,
149 }
150 c.Next()
151 })
152 }
153
154 // Next executes the next handler in the chain. If Next is called from inside a
155 // handler, the next registered handler begins execution immdediately.
dnj (Google) 2016/06/13 18:50:42 nit: two spaces.
156 func (c *Context) Next() {
157 c.index++
158 for ; c.index < len(c.handlers) && !c.aborted; c.index++ {
159 c.handlers[c.index](c)
160 }
161 }
iannucci 2016/06/13 19:23:29 Is the intended usecase of this to allow a middlew
162
163 // Abort prevents upcoming handlers from being executed. Calling Abort inside a
164 // handler however does not stop the execution of the current handler.
165 func (c *Context) Abort() {
166 c.aborted = true
dnj (Google) 2016/06/13 18:50:42 Just thinking here, but maybe we want "Abort()" to
nodir 2016/06/13 20:20:52 perhaps it may be simple to remove Abort and make
nodir 2016/06/13 20:26:48 and if you do that, replace "index" field in Conte
nodir 2016/06/13 20:29:36 or rather remove handlers from Context (because re
167 }
iannucci 2016/06/13 19:23:29 Yeah, I'm not convinced this is a good/safe api :/
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698