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  }