go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/router/router.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package router provides an HTTP router. 16 // 17 // It wraps around julienschmidt/httprouter adding support for middlewares and 18 // subrouters. 19 package router 20 21 import ( 22 "net/http" 23 "strings" 24 25 "github.com/julienschmidt/httprouter" 26 ) 27 28 // Router is the main type for the package. To create a Router, use New. 29 type Router struct { 30 hrouter *httprouter.Router 31 middleware MiddlewareChain 32 33 // BasePath is the root path to mount routes under. 34 BasePath string 35 } 36 37 // Context contains the context, response writer, request, and params shared 38 // across Middleware and Handler functions. 39 // 40 // TODO: Remove Context in favor of Request.Context(). 41 type Context struct { 42 Writer http.ResponseWriter 43 Request *http.Request 44 Params httprouter.Params 45 HandlerPath string // the path with which the handler was registered 46 } 47 48 var _ http.Handler = (*Router)(nil) 49 50 // New creates a Router. 51 func New() *Router { 52 return &Router{ 53 hrouter: httprouter.New(), 54 middleware: NewMiddlewareChain(), 55 BasePath: "/", 56 } 57 } 58 59 // Use adds middleware chains to the group. The added middleware applies to 60 // all handlers registered on the router and to all handlers registered on 61 // routers that may be derived from the router (using Subrouter). 62 func (r *Router) Use(mc MiddlewareChain) { 63 r.middleware = r.middleware.Extend(mc...) 64 } 65 66 // Subrouter creates a new router with an updated base path. 67 // The new router copies middleware and configuration from the 68 // router it derives from. 69 func (r *Router) Subrouter(relativePath string) *Router { 70 return &Router{ 71 hrouter: r.hrouter, 72 middleware: r.middleware, 73 BasePath: makeBasePath(r.BasePath, relativePath), 74 } 75 } 76 77 // GET is a shortcut for router.Handle("GET", path, mc, h). 78 func (r *Router) GET(path string, mc MiddlewareChain, h Handler) { 79 r.Handle("GET", path, mc, h) 80 } 81 82 // HEAD is a shortcut for router.Handle("HEAD", path, mc, h). 83 func (r *Router) HEAD(path string, mc MiddlewareChain, h Handler) { 84 r.Handle("HEAD", path, mc, h) 85 } 86 87 // OPTIONS is a shortcut for router.Handle("OPTIONS", path, mc, h). 88 func (r *Router) OPTIONS(path string, mc MiddlewareChain, h Handler) { 89 r.Handle("OPTIONS", path, mc, h) 90 } 91 92 // POST is a shortcut for router.Handle("POST", path, mc, h). 93 func (r *Router) POST(path string, mc MiddlewareChain, h Handler) { 94 r.Handle("POST", path, mc, h) 95 } 96 97 // PUT is a shortcut for router.Handle("PUT", path, mc, h). 98 func (r *Router) PUT(path string, mc MiddlewareChain, h Handler) { 99 r.Handle("PUT", path, mc, h) 100 } 101 102 // PATCH is a shortcut for router.Handle("PATCH", path, mc, h). 103 func (r *Router) PATCH(path string, mc MiddlewareChain, h Handler) { 104 r.Handle("PATCH", path, mc, h) 105 } 106 107 // DELETE is a shortcut for router.Handle("DELETE", path, mc, h). 108 func (r *Router) DELETE(path string, mc MiddlewareChain, h Handler) { 109 r.Handle("DELETE", path, mc, h) 110 } 111 112 // Handle registers a middleware chain and a handler for the given method and 113 // path. len(mc)==0 is allowed. See https://godoc.org/github.com/julienschmidt/httprouter 114 // for documentation on how the path may be formatted. 115 func (r *Router) Handle(method, path string, mc MiddlewareChain, h Handler) { 116 p := makeBasePath(r.BasePath, path) 117 handle := r.adapt(mc, h, p) 118 r.hrouter.Handle(method, p, handle) 119 } 120 121 // ServeHTTP makes Router implement the http.Handler interface. 122 func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 123 r.hrouter.ServeHTTP(rw, req) 124 } 125 126 // Params parases the httprouter.Params from the supplied method and path. 127 // 128 // If nothing is registered for method/path, Params will return false. 129 func (r *Router) Params(method, path string) (httprouter.Params, bool) { 130 if h, p, _ := r.hrouter.Lookup(method, path); h != nil { 131 return p, true 132 } 133 return nil, false 134 } 135 136 // NotFound sets the handler to be called when no matching route is found. 137 func (r *Router) NotFound(mc MiddlewareChain, h Handler) { 138 handle := r.adapt(mc, h, "") 139 r.hrouter.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 140 handle(rw, req, nil) 141 }) 142 } 143 144 // Static installs handlers that serve static files. 145 func (r *Router) Static(prefix string, mc MiddlewareChain, root http.FileSystem) { 146 if !strings.HasSuffix(prefix, "/") { 147 prefix += "/" 148 } 149 h := http.FileServer(root) 150 p := makeBasePath(r.BasePath, prefix+"*static") 151 handle := r.adapt(mc, func(ctx *Context) { 152 ctx.Request.URL.Path = ctx.Params.ByName("static") 153 h.ServeHTTP(ctx.Writer, ctx.Request) 154 }, p) 155 r.hrouter.Handle("GET", p, handle) 156 r.hrouter.Handle("HEAD", p, handle) 157 } 158 159 // adapt adapts given middleware chain and handler into a httprouter-style handle. 160 func (r *Router) adapt(mc MiddlewareChain, h Handler, path string) httprouter.Handle { 161 return func(rw http.ResponseWriter, req *http.Request, p httprouter.Params) { 162 run(&Context{ 163 Writer: rw, 164 Request: req, 165 Params: p, 166 HandlerPath: path, 167 }, r.middleware, mc, h) 168 } 169 } 170 171 // makeBasePath combines the given base and relative path using "/". 172 // The result is: "/"+base+"/"+relative. Consecutive "/" are collapsed 173 // into a single "/". 174 // 175 // In addition, the following rules apply: 176 // - The "/" between base and relative exists only if either base has a 177 // trailing "/" or relative is not the empty string. 178 // - A trailing "/" is added to the result if relative has a trailing "/". 179 func makeBasePath(base, relative string) string { 180 if !strings.HasSuffix(base, "/") && relative != "" { 181 base += "/" 182 } 183 return httprouter.CleanPath(base + relative) 184 }