mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 14:31:02 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			479 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			479 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| package chi
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| )
 | |
| 
 | |
| var _ Router = &Mux{}
 | |
| 
 | |
| // Mux is a simple HTTP route multiplexer that parses a request path,
 | |
| // records any URL params, and executes an end handler. It implements
 | |
| // the http.Handler interface and is friendly with the standard library.
 | |
| //
 | |
| // Mux is designed to be fast, minimal and offer a powerful API for building
 | |
| // modular and composable HTTP services with a large set of handlers. It's
 | |
| // particularly useful for writing large REST API services that break a handler
 | |
| // into many smaller parts composed of middlewares and end handlers.
 | |
| type Mux struct {
 | |
| 	// The radix trie router
 | |
| 	tree *node
 | |
| 
 | |
| 	// The middleware stack
 | |
| 	middlewares []func(http.Handler) http.Handler
 | |
| 
 | |
| 	// Controls the behaviour of middleware chain generation when a mux
 | |
| 	// is registered as an inline group inside another mux.
 | |
| 	inline bool
 | |
| 	parent *Mux
 | |
| 
 | |
| 	// The computed mux handler made of the chained middleware stack and
 | |
| 	// the tree router
 | |
| 	handler http.Handler
 | |
| 
 | |
| 	// Routing context pool
 | |
| 	pool *sync.Pool
 | |
| 
 | |
| 	// Custom route not found handler
 | |
| 	notFoundHandler http.HandlerFunc
 | |
| 
 | |
| 	// Custom method not allowed handler
 | |
| 	methodNotAllowedHandler http.HandlerFunc
 | |
| }
 | |
| 
 | |
| // NewMux returns a newly initialized Mux object that implements the Router
 | |
| // interface.
 | |
| func NewMux() *Mux {
 | |
| 	mux := &Mux{tree: &node{}, pool: &sync.Pool{}}
 | |
| 	mux.pool.New = func() interface{} {
 | |
| 		return NewRouteContext()
 | |
| 	}
 | |
| 	return mux
 | |
| }
 | |
| 
 | |
| // ServeHTTP is the single method of the http.Handler interface that makes
 | |
| // Mux interoperable with the standard library. It uses a sync.Pool to get and
 | |
| // reuse routing contexts for each request.
 | |
| func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | |
| 	// Ensure the mux has some routes defined on the mux
 | |
| 	if mx.handler == nil {
 | |
| 		mx.NotFoundHandler().ServeHTTP(w, r)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Check if a routing context already exists from a parent router.
 | |
| 	rctx, _ := r.Context().Value(RouteCtxKey).(*Context)
 | |
| 	if rctx != nil {
 | |
| 		mx.handler.ServeHTTP(w, r)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Fetch a RouteContext object from the sync pool, and call the computed
 | |
| 	// mx.handler that is comprised of mx.middlewares + mx.routeHTTP.
 | |
| 	// Once the request is finished, reset the routing context and put it back
 | |
| 	// into the pool for reuse from another request.
 | |
| 	rctx = mx.pool.Get().(*Context)
 | |
| 	rctx.Reset()
 | |
| 	rctx.Routes = mx
 | |
| 	rctx.parentCtx = r.Context()
 | |
| 
 | |
| 	// NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation
 | |
| 	r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
 | |
| 
 | |
| 	// Serve the request and once its done, put the request context back in the sync pool
 | |
| 	mx.handler.ServeHTTP(w, r)
 | |
| 	mx.pool.Put(rctx)
 | |
| }
 | |
| 
 | |
| // Use appends a middleware handler to the Mux middleware stack.
 | |
| //
 | |
| // The middleware stack for any Mux will execute before searching for a matching
 | |
| // route to a specific handler, which provides opportunity to respond early,
 | |
| // change the course of the request execution, or set request-scoped values for
 | |
| // the next http.Handler.
 | |
| func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) {
 | |
| 	if mx.handler != nil {
 | |
| 		panic("chi: all middlewares must be defined before routes on a mux")
 | |
| 	}
 | |
| 	mx.middlewares = append(mx.middlewares, middlewares...)
 | |
| }
 | |
| 
 | |
| // Handle adds the route `pattern` that matches any http method to
 | |
| // execute the `handler` http.Handler.
 | |
| func (mx *Mux) Handle(pattern string, handler http.Handler) {
 | |
| 	mx.handle(mALL, pattern, handler)
 | |
| }
 | |
| 
 | |
| // HandleFunc adds the route `pattern` that matches any http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mALL, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Method adds the route `pattern` that matches `method` http method to
 | |
| // execute the `handler` http.Handler.
 | |
| func (mx *Mux) Method(method, pattern string, handler http.Handler) {
 | |
| 	m, ok := methodMap[strings.ToUpper(method)]
 | |
| 	if !ok {
 | |
| 		panic(fmt.Sprintf("chi: '%s' http method is not supported.", method))
 | |
| 	}
 | |
| 	mx.handle(m, pattern, handler)
 | |
| }
 | |
| 
 | |
| // MethodFunc adds the route `pattern` that matches `method` http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.Method(method, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Connect adds the route `pattern` that matches a CONNECT http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mCONNECT, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Delete adds the route `pattern` that matches a DELETE http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mDELETE, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Get adds the route `pattern` that matches a GET http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mGET, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Head adds the route `pattern` that matches a HEAD http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mHEAD, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Options adds the route `pattern` that matches a OPTIONS http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mOPTIONS, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Patch adds the route `pattern` that matches a PATCH http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mPATCH, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Post adds the route `pattern` that matches a POST http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mPOST, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Put adds the route `pattern` that matches a PUT http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mPUT, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // Trace adds the route `pattern` that matches a TRACE http method to
 | |
| // execute the `handlerFn` http.HandlerFunc.
 | |
| func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) {
 | |
| 	mx.handle(mTRACE, pattern, handlerFn)
 | |
| }
 | |
| 
 | |
| // NotFound sets a custom http.HandlerFunc for routing paths that could
 | |
| // not be found. The default 404 handler is `http.NotFound`.
 | |
| func (mx *Mux) NotFound(handlerFn http.HandlerFunc) {
 | |
| 	// Build NotFound handler chain
 | |
| 	m := mx
 | |
| 	h := Chain(mx.middlewares...).HandlerFunc(handlerFn).ServeHTTP
 | |
| 	if mx.inline && mx.parent != nil {
 | |
| 		m = mx.parent
 | |
| 	}
 | |
| 
 | |
| 	// Update the notFoundHandler from this point forward
 | |
| 	m.notFoundHandler = h
 | |
| 	m.updateSubRoutes(func(subMux *Mux) {
 | |
| 		if subMux.notFoundHandler == nil {
 | |
| 			subMux.NotFound(h)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the
 | |
| // method is unresolved. The default handler returns a 405 with an empty body.
 | |
| func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) {
 | |
| 	// Build MethodNotAllowed handler chain
 | |
| 	m := mx
 | |
| 	h := Chain(mx.middlewares...).HandlerFunc(handlerFn).ServeHTTP
 | |
| 	if mx.inline && mx.parent != nil {
 | |
| 		m = mx.parent
 | |
| 	}
 | |
| 
 | |
| 	// Update the methodNotAllowedHandler from this point forward
 | |
| 	m.methodNotAllowedHandler = h
 | |
| 	m.updateSubRoutes(func(subMux *Mux) {
 | |
| 		if subMux.methodNotAllowedHandler == nil {
 | |
| 			subMux.MethodNotAllowed(h)
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // With adds inline middlewares for an endpoint handler.
 | |
| func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
 | |
| 	// Similarly as in handle(), we must build the mux handler once additional
 | |
| 	// middleware registration isn't allowed for this stack, like now.
 | |
| 	if !mx.inline && mx.handler == nil {
 | |
| 		mx.updateRouteHandler()
 | |
| 	}
 | |
| 
 | |
| 	// Copy middlewares from parent inline muxs
 | |
| 	var mws Middlewares
 | |
| 	if mx.inline {
 | |
| 		mws = make(Middlewares, len(mx.middlewares))
 | |
| 		copy(mws, mx.middlewares)
 | |
| 	}
 | |
| 	mws = append(mws, middlewares...)
 | |
| 
 | |
| 	im := &Mux{
 | |
| 		pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws,
 | |
| 		notFoundHandler: mx.notFoundHandler, methodNotAllowedHandler: mx.methodNotAllowedHandler,
 | |
| 	}
 | |
| 
 | |
| 	return im
 | |
| }
 | |
| 
 | |
| // Group creates a new inline-Mux with a fresh middleware stack. It's useful
 | |
| // for a group of handlers along the same routing path that use an additional
 | |
| // set of middlewares. See _examples/.
 | |
| func (mx *Mux) Group(fn func(r Router)) Router {
 | |
| 	im := mx.With().(*Mux)
 | |
| 	if fn != nil {
 | |
| 		fn(im)
 | |
| 	}
 | |
| 	return im
 | |
| }
 | |
| 
 | |
| // Route creates a new Mux with a fresh middleware stack and mounts it
 | |
| // along the `pattern` as a subrouter. Effectively, this is a short-hand
 | |
| // call to Mount. See _examples/.
 | |
| func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
 | |
| 	if fn == nil {
 | |
| 		panic(fmt.Sprintf("chi: attempting to Route() a nil subrouter on '%s'", pattern))
 | |
| 	}
 | |
| 	subRouter := NewRouter()
 | |
| 	fn(subRouter)
 | |
| 	mx.Mount(pattern, subRouter)
 | |
| 	return subRouter
 | |
| }
 | |
| 
 | |
| // Mount attaches another http.Handler or chi Router as a subrouter along a routing
 | |
| // path. It's very useful to split up a large API as many independent routers and
 | |
| // compose them as a single service using Mount. See _examples/.
 | |
| //
 | |
| // Note that Mount() simply sets a wildcard along the `pattern` that will continue
 | |
| // routing at the `handler`, which in most cases is another chi.Router. As a result,
 | |
| // if you define two Mount() routes on the exact same pattern the mount will panic.
 | |
| func (mx *Mux) Mount(pattern string, handler http.Handler) {
 | |
| 	if handler == nil {
 | |
| 		panic(fmt.Sprintf("chi: attempting to Mount() a nil handler on '%s'", pattern))
 | |
| 	}
 | |
| 
 | |
| 	// Provide runtime safety for ensuring a pattern isn't mounted on an existing
 | |
| 	// routing pattern.
 | |
| 	if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") {
 | |
| 		panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern))
 | |
| 	}
 | |
| 
 | |
| 	// Assign sub-Router's with the parent not found & method not allowed handler if not specified.
 | |
| 	subr, ok := handler.(*Mux)
 | |
| 	if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil {
 | |
| 		subr.NotFound(mx.notFoundHandler)
 | |
| 	}
 | |
| 	if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil {
 | |
| 		subr.MethodNotAllowed(mx.methodNotAllowedHandler)
 | |
| 	}
 | |
| 
 | |
| 	mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		rctx := RouteContext(r.Context())
 | |
| 
 | |
| 		// shift the url path past the previous subrouter
 | |
| 		rctx.RoutePath = mx.nextRoutePath(rctx)
 | |
| 
 | |
| 		// reset the wildcard URLParam which connects the subrouter
 | |
| 		n := len(rctx.URLParams.Keys) - 1
 | |
| 		if n >= 0 && rctx.URLParams.Keys[n] == "*" && len(rctx.URLParams.Values) > n {
 | |
| 			rctx.URLParams.Values[n] = ""
 | |
| 		}
 | |
| 
 | |
| 		handler.ServeHTTP(w, r)
 | |
| 	})
 | |
| 
 | |
| 	if pattern == "" || pattern[len(pattern)-1] != '/' {
 | |
| 		mx.handle(mALL|mSTUB, pattern, mountHandler)
 | |
| 		mx.handle(mALL|mSTUB, pattern+"/", mountHandler)
 | |
| 		pattern += "/"
 | |
| 	}
 | |
| 
 | |
| 	method := mALL
 | |
| 	subroutes, _ := handler.(Routes)
 | |
| 	if subroutes != nil {
 | |
| 		method |= mSTUB
 | |
| 	}
 | |
| 	n := mx.handle(method, pattern+"*", mountHandler)
 | |
| 
 | |
| 	if subroutes != nil {
 | |
| 		n.subroutes = subroutes
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Routes returns a slice of routing information from the tree,
 | |
| // useful for traversing available routes of a router.
 | |
| func (mx *Mux) Routes() []Route {
 | |
| 	return mx.tree.routes()
 | |
| }
 | |
| 
 | |
| // Middlewares returns a slice of middleware handler functions.
 | |
| func (mx *Mux) Middlewares() Middlewares {
 | |
| 	return mx.middlewares
 | |
| }
 | |
| 
 | |
| // Match searches the routing tree for a handler that matches the method/path.
 | |
| // It's similar to routing a http request, but without executing the handler
 | |
| // thereafter.
 | |
| //
 | |
| // Note: the *Context state is updated during execution, so manage
 | |
| // the state carefully or make a NewRouteContext().
 | |
| func (mx *Mux) Match(rctx *Context, method, path string) bool {
 | |
| 	m, ok := methodMap[method]
 | |
| 	if !ok {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	node, _, h := mx.tree.FindRoute(rctx, m, path)
 | |
| 
 | |
| 	if node != nil && node.subroutes != nil {
 | |
| 		rctx.RoutePath = mx.nextRoutePath(rctx)
 | |
| 		return node.subroutes.Match(rctx, method, rctx.RoutePath)
 | |
| 	}
 | |
| 
 | |
| 	return h != nil
 | |
| }
 | |
| 
 | |
| // NotFoundHandler returns the default Mux 404 responder whenever a route
 | |
| // cannot be found.
 | |
| func (mx *Mux) NotFoundHandler() http.HandlerFunc {
 | |
| 	if mx.notFoundHandler != nil {
 | |
| 		return mx.notFoundHandler
 | |
| 	}
 | |
| 	return http.NotFound
 | |
| }
 | |
| 
 | |
| // MethodNotAllowedHandler returns the default Mux 405 responder whenever
 | |
| // a method cannot be resolved for a route.
 | |
| func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc {
 | |
| 	if mx.methodNotAllowedHandler != nil {
 | |
| 		return mx.methodNotAllowedHandler
 | |
| 	}
 | |
| 	return methodNotAllowedHandler
 | |
| }
 | |
| 
 | |
| // handle registers a http.Handler in the routing tree for a particular http method
 | |
| // and routing pattern.
 | |
| func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node {
 | |
| 	if len(pattern) == 0 || pattern[0] != '/' {
 | |
| 		panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern))
 | |
| 	}
 | |
| 
 | |
| 	// Build the computed routing handler for this routing pattern.
 | |
| 	if !mx.inline && mx.handler == nil {
 | |
| 		mx.updateRouteHandler()
 | |
| 	}
 | |
| 
 | |
| 	// Build endpoint handler with inline middlewares for the route
 | |
| 	var h http.Handler
 | |
| 	if mx.inline {
 | |
| 		mx.handler = http.HandlerFunc(mx.routeHTTP)
 | |
| 		h = Chain(mx.middlewares...).Handler(handler)
 | |
| 	} else {
 | |
| 		h = handler
 | |
| 	}
 | |
| 
 | |
| 	// Add the endpoint to the tree and return the node
 | |
| 	return mx.tree.InsertRoute(method, pattern, h)
 | |
| }
 | |
| 
 | |
| // routeHTTP routes a http.Request through the Mux routing tree to serve
 | |
| // the matching handler for a particular http method.
 | |
| func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
 | |
| 	// Grab the route context object
 | |
| 	rctx := r.Context().Value(RouteCtxKey).(*Context)
 | |
| 
 | |
| 	// The request routing path
 | |
| 	routePath := rctx.RoutePath
 | |
| 	if routePath == "" {
 | |
| 		if r.URL.RawPath != "" {
 | |
| 			routePath = r.URL.RawPath
 | |
| 		} else {
 | |
| 			routePath = r.URL.Path
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Check if method is supported by chi
 | |
| 	if rctx.RouteMethod == "" {
 | |
| 		rctx.RouteMethod = r.Method
 | |
| 	}
 | |
| 	method, ok := methodMap[rctx.RouteMethod]
 | |
| 	if !ok {
 | |
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Find the route
 | |
| 	if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
 | |
| 		h.ServeHTTP(w, r)
 | |
| 		return
 | |
| 	}
 | |
| 	if rctx.methodNotAllowed {
 | |
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r)
 | |
| 	} else {
 | |
| 		mx.NotFoundHandler().ServeHTTP(w, r)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (mx *Mux) nextRoutePath(rctx *Context) string {
 | |
| 	routePath := "/"
 | |
| 	nx := len(rctx.routeParams.Keys) - 1 // index of last param in list
 | |
| 	if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx {
 | |
| 		routePath = "/" + rctx.routeParams.Values[nx]
 | |
| 	}
 | |
| 	return routePath
 | |
| }
 | |
| 
 | |
| // Recursively update data on child routers.
 | |
| func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) {
 | |
| 	for _, r := range mx.tree.routes() {
 | |
| 		subMux, ok := r.SubRoutes.(*Mux)
 | |
| 		if !ok {
 | |
| 			continue
 | |
| 		}
 | |
| 		fn(subMux)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // updateRouteHandler builds the single mux handler that is a chain of the middleware
 | |
| // stack, as defined by calls to Use(), and the tree router (Mux) itself. After this
 | |
| // point, no other middlewares can be registered on this Mux's stack. But you can still
 | |
| // compose additional middlewares via Group()'s or using a chained middleware handler.
 | |
| func (mx *Mux) updateRouteHandler() {
 | |
| 	mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
 | |
| }
 | |
| 
 | |
| // methodNotAllowedHandler is a helper function to respond with a 405,
 | |
| // method not allowed.
 | |
| func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	w.WriteHeader(405)
 | |
| 	w.Write(nil)
 | |
| }
 |