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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: server/router/router.go
diff --git a/server/router/router.go b/server/router/router.go
new file mode 100644
index 0000000000000000000000000000000000000000..53e3cac15e7f297df90f9959d7b43fc9cf7be188
--- /dev/null
+++ b/server/router/router.go
@@ -0,0 +1,167 @@
+// Copyright 2016 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package router
+
+import (
+ "net/http"
+
+ "github.com/julienschmidt/httprouter"
+ "golang.org/x/net/context"
+)
+
+// TODO(nishanths): Implement adapter methods to convert http style handlers to Handlers.
+
+// Router represents the main router type.
+type (
+ // Router is the main type for the package. It contains the base path for
+ // 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
+ Router struct {
+ hrouter *httprouter.Router
+ handlers []Handler
+ BasePath string
+ parent *Router
+ }
+
+ // Context is the argument to a Handler method. It is used to carry the
+ // HTTP request and response writer between handlers, share context,
+ // and manage flow using the Next and Abort methods.
+ Context struct {
+ Context context.Context
+ Writer http.ResponseWriter
+ Request *http.Request
+ Params httprouter.Params
+
+ handlers []Handler
+ index int
+ aborted bool
+ }
+)
+
+var (
+ // To ensure that Router conforms to the http.Handler interface.
+ _ 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
+
+ // VoidHandler is a handler that has no effect.
+ VoidHandler Handler = func(_ *Context) {}
+)
+
+// New creates a Router with specified initial context.
+func New() *Router {
+ return &Router{
+ hrouter: httprouter.New(),
+ BasePath: "/",
+ }
+}
+
+// Use adds middleware chains to the group. The added middleware applies to
+// all routes in the current group and in groups derived from the current group.
+func (r *Router) Use(handlers ...Handler) {
+ r.handlers = append(r.handlers, handlers...)
+}
+
+// Group creates a new router with an updated base path.
+// The new router carries over configuration from the router it derives
+// from.
+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
+ h := make([]Handler, len(r.handlers))
+ copy(h, r.handlers)
+ return &Router{
+ hrouter: r.hrouter,
+ handlers: h,
+ BasePath: r.BasePath + relativePath,
+ parent: r,
+ }
+}
+
+// GET is a shortcut for router.Handle("GET", path, handlers)
+func (r *Router) GET(path string, handlers ...Handler) {
+ r.Handle("GET", path, handlers...)
+}
+
+// HEAD is a shortcut for router.Handle("HEAD", path, handlers)
+func (r *Router) HEAD(path string, handlers ...Handler) {
+ r.Handle("HEAD", path, handlers...)
+}
+
+// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers)
+func (r *Router) OPTIONS(path string, handlers ...Handler) {
+ r.Handle("OPTIONS", path, handlers...)
+}
+
+// POST is a shortcut for router.Handle("POST", path, handlers)
+func (r *Router) POST(path string, handlers ...Handler) {
+ r.Handle("POST", path, handlers...)
+}
+
+// PUT is a shortcut for router.Handle("PUT", path, handlers)
+func (r *Router) PUT(path string, handlers ...Handler) {
+ r.Handle("PUT", path, handlers...)
+}
+
+// PATCH is a shortcut for router.Handle("PATCH", path, handlers)
+func (r *Router) PATCH(path string, handlers ...Handler) {
+ r.Handle("PATCH", path, handlers...)
+}
+
+// DELETE is a shortcut for router.Handle("DELETE", path, handlers)
+func (r *Router) DELETE(path string, handlers ...Handler) {
+ r.Handle("DELETE", path, handlers...)
+}
+
+// Handle registers handlers for the given method and path. Handle panics if
+// no handlers are provided.
+func (r *Router) Handle(method, path string, handlers ...Handler) {
+ if len(handlers) == 0 {
+ panic("router: at least one handler should be specified")
+ }
+ h := r.adapt(handlers)
+ r.hrouter.Handle(method, path, h)
+}
+
+// ServeHTTP makes Router implement the http.Handler interface.
+func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ r.hrouter.ServeHTTP(rw, req)
+}
+
+// copyHandlers allocates a new array and copies the
+// the handlers from the two handler lists into it.
+func copyHandlers(m, n []Handler) []Handler {
+ totalLen := len(m) + len(n)
+ 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.
+ copy(merged, m)
+ copy(merged[len(m):], n)
+ return merged
+}
+
+// adapt adapts a list of handlers into a httprouter-style handler.
+func (r *Router) adapt(handlers []Handler) httprouter.Handle {
+ return httprouter.Handle(func(rw http.ResponseWriter, req *http.Request, p httprouter.Params) {
+ // TODO(maybe): Use a free list for making Contexts.
+ c := &Context{
+ Context: context.Background(),
+ Writer: rw,
+ Request: req,
+ Params: p,
+ 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.
+ index: -1,
+ }
+ c.Next()
+ })
+}
+
+// Next executes the next handler in the chain. If Next is called from inside a
+// handler, the next registered handler begins execution immdediately.
dnj (Google) 2016/06/13 18:50:42 nit: two spaces.
+func (c *Context) Next() {
+ c.index++
+ for ; c.index < len(c.handlers) && !c.aborted; c.index++ {
+ c.handlers[c.index](c)
+ }
+}
iannucci 2016/06/13 19:23:29 Is the intended usecase of this to allow a middlew
+
+// Abort prevents upcoming handlers from being executed. Calling Abort inside a
+// handler however does not stop the execution of the current handler.
+func (c *Context) Abort() {
+ 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
+}
iannucci 2016/06/13 19:23:29 Yeah, I'm not convinced this is a good/safe api :/

Powered by Google App Engine
This is Rietveld 408576698