goyave.dev/goyave/v5@v5.0.0-rc9.0.20240517145003-d3f977d0b9f3/router.go (about)

     1  package goyave
     2  
     3  import (
     4  	"errors"
     5  	"io/fs"
     6  	"net/http"
     7  	"regexp"
     8  	"strings"
     9  
    10  	"maps"
    11  	"slices"
    12  
    13  	"github.com/samber/lo"
    14  	"goyave.dev/goyave/v5/cors"
    15  	errorutil "goyave.dev/goyave/v5/util/errors"
    16  )
    17  
    18  // Common route meta keys.
    19  const (
    20  	MetaCORS = "goyave.cors"
    21  )
    22  
    23  // Special route names.
    24  const (
    25  	RouteMethodNotAllowed = "goyave.method-not-allowed"
    26  	RouteNotFound         = "goyave.not-found"
    27  )
    28  
    29  var (
    30  	errMatchMethodNotAllowed = errors.New("Method not allowed for this route")
    31  	errMatchNotFound         = errors.New("No match for this URI")
    32  
    33  	methodNotAllowedRoute = newRoute(func(response *Response, _ *Request) {
    34  		response.Status(http.StatusMethodNotAllowed)
    35  	}, RouteMethodNotAllowed)
    36  	notFoundRoute = newRoute(func(response *Response, _ *Request) {
    37  		response.Status(http.StatusNotFound)
    38  	}, RouteNotFound)
    39  )
    40  
    41  // Handler responds to an HTTP request.
    42  //
    43  // The given `Response` and `Request` value should not
    44  // be used outside of the context of an HTTP request. e.g.: passed to
    45  // a goroutine or used after the finalization step in the request lifecycle.
    46  type Handler func(response *Response, request *Request)
    47  
    48  type routeMatcher interface {
    49  	match(method string, match *routeMatch) bool
    50  }
    51  
    52  type routeMatch struct {
    53  	route       *Route
    54  	parameters  map[string]string
    55  	err         error
    56  	currentPath string
    57  }
    58  
    59  func (rm *routeMatch) mergeParams(params map[string]string) {
    60  	if rm.parameters == nil {
    61  		rm.parameters = params
    62  		return
    63  	}
    64  	for k, v := range params {
    65  		rm.parameters[k] = v
    66  	}
    67  }
    68  
    69  func (rm *routeMatch) trimCurrentPath(fullMatch string) {
    70  	length := len(fullMatch)
    71  	rm.currentPath = rm.currentPath[length:]
    72  }
    73  
    74  // Router registers routes to be matched and executes a handler.
    75  type Router struct {
    76  	server         *Server
    77  	parent         *Router
    78  	statusHandlers map[int]StatusHandler
    79  	namedRoutes    map[string]*Route
    80  	regexCache     map[string]*regexp.Regexp
    81  	Meta           map[string]any
    82  
    83  	parameterizable
    84  	middlewareHolder
    85  	globalMiddleware *middlewareHolder
    86  
    87  	prefix     string
    88  	routes     []*Route
    89  	subrouters []*Router
    90  
    91  	slashCount int
    92  }
    93  
    94  var _ http.Handler = (*Router)(nil) // implements http.Handler
    95  var _ routeMatcher = (*Router)(nil) // implements routeMatcher
    96  
    97  // NewRouter create a new root-level Router that is pre-configured with core
    98  // middleware (recovery and language), as well as status handlers
    99  // for all standard HTTP status codes.
   100  //
   101  // You don't need to manually build your router using this function.
   102  // This method can however be useful for external tooling that build
   103  // routers without starting the HTTP server. Don't forget to call
   104  // `router.ClearRegexCache()` when you are done registering routes.
   105  func NewRouter(server *Server) *Router {
   106  	router := &Router{
   107  		server:         server,
   108  		parent:         nil,
   109  		prefix:         "",
   110  		statusHandlers: make(map[int]StatusHandler, 41),
   111  		namedRoutes:    make(map[string]*Route, 5),
   112  		middlewareHolder: middlewareHolder{
   113  			middleware: nil,
   114  		},
   115  		globalMiddleware: &middlewareHolder{
   116  			middleware: make([]Middleware, 0, 2),
   117  		},
   118  		regexCache: make(map[string]*regexp.Regexp, 5),
   119  		Meta:       make(map[string]any),
   120  	}
   121  	router.StatusHandler(&PanicStatusHandler{}, http.StatusInternalServerError)
   122  	for i := 400; i <= 418; i++ {
   123  		router.StatusHandler(&ErrorStatusHandler{}, i)
   124  	}
   125  	router.StatusHandler(&ValidationStatusHandler{}, http.StatusUnprocessableEntity)
   126  	for i := 423; i <= 426; i++ {
   127  		router.StatusHandler(&ErrorStatusHandler{}, i)
   128  	}
   129  	router.StatusHandler(&ErrorStatusHandler{}, 421, 428, 429, 431, 444, 451)
   130  	router.StatusHandler(&ErrorStatusHandler{}, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511)
   131  	router.GlobalMiddleware(&recoveryMiddleware{}, &languageMiddleware{})
   132  	return router
   133  }
   134  
   135  // ClearRegexCache set internal router's regex cache used for route parameters optimisation to nil
   136  // so it can be garbage collected.
   137  // You don't need to call this function if you are using `goyave.Server`.
   138  // However, this method SHOULD be called by external tooling that build routers without starting the HTTP
   139  // server when they are done registering routes and subrouters.
   140  func (r *Router) ClearRegexCache() {
   141  	r.regexCache = nil
   142  	for _, subrouter := range r.subrouters {
   143  		subrouter.ClearRegexCache()
   144  	}
   145  }
   146  
   147  // GetParent returns the parent Router of this router (can be `nil`).
   148  func (r *Router) GetParent() *Router {
   149  	return r.parent
   150  }
   151  
   152  // GetRoutes returns the list of routes belonging to this router.
   153  func (r *Router) GetRoutes() []*Route {
   154  	cpy := make([]*Route, len(r.routes))
   155  	copy(cpy, r.routes)
   156  	return cpy
   157  }
   158  
   159  // GetSubrouters returns the list of subrouters belonging to this router.
   160  func (r *Router) GetSubrouters() []*Router {
   161  	cpy := make([]*Router, len(r.subrouters))
   162  	copy(cpy, r.subrouters)
   163  	return cpy
   164  }
   165  
   166  // GetRoute get a named route.
   167  // Returns nil if the route doesn't exist.
   168  func (r *Router) GetRoute(name string) *Route {
   169  	return r.namedRoutes[name]
   170  }
   171  
   172  // SetMeta attach a value to this router identified by the given key.
   173  //
   174  // This value is inherited by all subrouters and routes, unless they override
   175  // it at their level.
   176  func (r *Router) SetMeta(key string, value any) *Router {
   177  	r.Meta[key] = value
   178  	return r
   179  }
   180  
   181  // RemoveMeta detach the meta value identified by the given key from this router.
   182  // This doesn't remove meta using the same key from the parent routers.
   183  func (r *Router) RemoveMeta(key string) *Router {
   184  	delete(r.Meta, key)
   185  	return r
   186  }
   187  
   188  // LookupMeta value identified by the given key. If not found in this router,
   189  // the value is recursively fetched in the parent routers.
   190  //
   191  // Returns the value and `true` if found in the current router or one of the
   192  // parent routers, `nil` and `false` otherwise.
   193  func (r *Router) LookupMeta(key string) (any, bool) {
   194  	val, ok := r.Meta[key]
   195  	if ok {
   196  		return val, ok
   197  	}
   198  	if r.parent != nil {
   199  		return r.parent.LookupMeta(key)
   200  	}
   201  	return nil, false
   202  }
   203  
   204  // GlobalMiddleware apply one or more global middleware. Global middleware are
   205  // executed for every request, including when the request doesn't match any route
   206  // or if it results in "Method Not Allowed".
   207  // These middleware are global to the main Router: they will also be executed for subrouters.
   208  // Global Middleware are always executed first.
   209  // Use global middleware for logging and rate limiting for example.
   210  func (r *Router) GlobalMiddleware(middleware ...Middleware) *Router {
   211  	for _, m := range middleware {
   212  		m.Init(r.server)
   213  	}
   214  	r.globalMiddleware.middleware = append(r.globalMiddleware.middleware, middleware...)
   215  	return r
   216  }
   217  
   218  // Middleware apply one or more middleware to the route group.
   219  func (r *Router) Middleware(middleware ...Middleware) *Router {
   220  	if r.middleware == nil {
   221  		r.middleware = make([]Middleware, 0, 3)
   222  	}
   223  	for _, m := range middleware {
   224  		m.Init(r.server)
   225  	}
   226  	r.middleware = append(r.middleware, middleware...)
   227  	return r
   228  }
   229  
   230  // CORS set the CORS options for this route group.
   231  // If the options are not `nil`, the CORS middleware is automatically added globally.
   232  // To disable CORS for this router, subrouters and routes, give `nil` options.
   233  // CORS can be re-enabled for subrouters and routes on a case-by-case basis
   234  // using non-nil options.
   235  func (r *Router) CORS(options *cors.Options) *Router {
   236  	r.Meta[MetaCORS] = options
   237  	if options == nil {
   238  		return r
   239  	}
   240  	if !hasMiddleware[*corsMiddleware](r.globalMiddleware.middleware) {
   241  		r.GlobalMiddleware(&corsMiddleware{})
   242  	}
   243  	return r
   244  }
   245  
   246  // StatusHandler set a handler for responses with an empty body.
   247  // The handler will be automatically executed if the request's life-cycle reaches its end
   248  // and nothing has been written in the response body.
   249  //
   250  // Multiple status codes can be given. The handler will be executed if one of them matches.
   251  //
   252  // This method can be used to define custom error handlers for example.
   253  //
   254  // Status handlers are inherited as a copy in sub-routers. Modifying a child's status handler
   255  // will not modify its parent's.
   256  //
   257  // Codes in the 400 and 500 ranges have a default status handler.
   258  func (r *Router) StatusHandler(handler StatusHandler, status int, additionalStatuses ...int) {
   259  	handler.Init(r.server)
   260  	r.statusHandlers[status] = handler
   261  	for _, s := range additionalStatuses {
   262  		r.statusHandlers[s] = handler
   263  	}
   264  }
   265  
   266  // ServeHTTP dispatches the handler registered in the matched route.
   267  func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   268  	if req.URL.Scheme != "" && req.URL.Scheme != "http" {
   269  		address := r.server.getProxyAddress(r.server.config) + req.URL.Path
   270  		query := req.URL.Query()
   271  		if len(query) != 0 {
   272  			address += "?" + query.Encode()
   273  		}
   274  		http.Redirect(w, req, address, http.StatusPermanentRedirect)
   275  		return
   276  	}
   277  
   278  	match := routeMatch{currentPath: req.URL.Path}
   279  	r.match(req.Method, &match)
   280  	r.requestHandler(&match, w, req)
   281  }
   282  
   283  // TODO export RouteMatch and add Match with string param function
   284  
   285  func (r *Router) match(method string, match *routeMatch) bool {
   286  	// Check if router itself matches
   287  	var params []string
   288  	if r.parameterizable.regex != nil {
   289  		i := -1
   290  		if len(match.currentPath) > 0 {
   291  			// Ignore slashes in router prefix
   292  			i = nthIndex(match.currentPath[1:], "/", r.slashCount) + 1
   293  		}
   294  		if i <= 0 {
   295  			i = len(match.currentPath)
   296  		}
   297  		currentPath := match.currentPath[:i]
   298  		params = r.parameterizable.regex.FindStringSubmatch(currentPath)
   299  	} else {
   300  		params = []string{""}
   301  	}
   302  
   303  	if params != nil {
   304  		match.trimCurrentPath(params[0])
   305  		if len(params) > 1 {
   306  			match.mergeParams(r.makeParameters(params))
   307  		}
   308  
   309  		// Check in subrouters first
   310  		for _, router := range r.subrouters {
   311  			if router.match(method, match) {
   312  				if router.prefix == "" && match.route == methodNotAllowedRoute {
   313  					// This allows route groups with subrouters having empty prefix.
   314  					continue
   315  				}
   316  				return true
   317  			}
   318  		}
   319  
   320  		// Check if any route matches
   321  		for _, route := range r.routes {
   322  			if route.match(method, match) {
   323  				return true
   324  			}
   325  		}
   326  	}
   327  
   328  	if match.err == errMatchMethodNotAllowed {
   329  		match.route = methodNotAllowedRoute
   330  		return true
   331  	}
   332  
   333  	match.route = notFoundRoute
   334  	// Return true if the subrouter matched so we don't turn back and check other subrouters
   335  	return params != nil && len(params[0]) > 0
   336  }
   337  
   338  func nthIndex(str, substr string, n int) int {
   339  	index := -1
   340  	for nth := 0; nth < n; nth++ {
   341  		i := strings.Index(str, substr)
   342  		if i == -1 || i == len(str) {
   343  			return -1
   344  		}
   345  		index += i + 1
   346  		str = str[i+1:]
   347  	}
   348  	return index
   349  }
   350  
   351  func (r *Router) makeParameters(match []string) map[string]string {
   352  	return r.parameterizable.makeParameters(match, r.parameters)
   353  }
   354  
   355  // Subrouter create a new sub-router from this router.
   356  // Use subrouters to create route groups and to apply middleware to multiple routes.
   357  // CORS options are also inherited.
   358  //
   359  // Subrouters are matched before routes. For example, if you have a subrouter with a
   360  // prefix "/{name}" and a route "/route", the "/route" will never match.
   361  func (r *Router) Subrouter(prefix string) *Router {
   362  	if prefix == "/" {
   363  		prefix = ""
   364  	}
   365  
   366  	router := &Router{
   367  		server:         r.server,
   368  		parent:         r,
   369  		prefix:         prefix,
   370  		statusHandlers: maps.Clone(r.statusHandlers),
   371  		Meta:           make(map[string]any),
   372  		namedRoutes:    r.namedRoutes,
   373  		routes:         make([]*Route, 0, 5), // Typical CRUD has 5 routes
   374  		middlewareHolder: middlewareHolder{
   375  			middleware: nil,
   376  		},
   377  		globalMiddleware: r.globalMiddleware,
   378  		regexCache:       r.regexCache,
   379  	}
   380  	if prefix != "" {
   381  		router.compileParameters(router.prefix, false, r.regexCache)
   382  		router.slashCount = strings.Count(prefix, "/")
   383  	}
   384  	r.subrouters = append(r.subrouters, router)
   385  	return router
   386  }
   387  
   388  // Group create a new sub-router with an empty prefix.
   389  func (r *Router) Group() *Router {
   390  	return r.Subrouter("")
   391  }
   392  
   393  // Route register a new route.
   394  //
   395  // Multiple methods can be passed.
   396  //
   397  // If the route matches the "GET" method, the "HEAD" method is automatically added
   398  // to the matcher if it's missing.
   399  //
   400  // If the router has the CORS middleware, the "OPTIONS" method is automatically added
   401  // to the matcher if it's missing, so it allows preflight requests.
   402  //
   403  // Returns the generated route.
   404  func (r *Router) Route(methods []string, uri string, handler Handler) *Route {
   405  	return r.registerRoute(methods, uri, handler)
   406  }
   407  
   408  // Get registers a new route with the GET and HEAD methods.
   409  func (r *Router) Get(uri string, handler Handler) *Route {
   410  	return r.registerRoute([]string{http.MethodGet}, uri, handler)
   411  }
   412  
   413  // Post registers a new route with the POST method.
   414  func (r *Router) Post(uri string, handler Handler) *Route {
   415  	return r.registerRoute([]string{http.MethodPost}, uri, handler)
   416  }
   417  
   418  // Put registers a new route with the PUT method.
   419  func (r *Router) Put(uri string, handler Handler) *Route {
   420  	return r.registerRoute([]string{http.MethodPut}, uri, handler)
   421  }
   422  
   423  // Patch registers a new route with the PATCH method.
   424  func (r *Router) Patch(uri string, handler Handler) *Route {
   425  	return r.registerRoute([]string{http.MethodPatch}, uri, handler)
   426  }
   427  
   428  // Delete registers a new route with the DELETE method.
   429  func (r *Router) Delete(uri string, handler Handler) *Route {
   430  	return r.registerRoute([]string{http.MethodDelete}, uri, handler)
   431  }
   432  
   433  // Options registers a new route wit the OPTIONS method.
   434  func (r *Router) Options(uri string, handler Handler) *Route {
   435  	return r.registerRoute([]string{http.MethodOptions}, uri, handler)
   436  }
   437  
   438  // Static serve a directory and its subdirectories of static resources.
   439  // Set the "download" parameter to true if you want the files to be sent as an attachment
   440  // instead of an inline element.
   441  //
   442  // If no file is given in the url, or if the given file is a directory, the handler will
   443  // send the "index.html" file if it exists.
   444  func (r *Router) Static(fs fs.StatFS, uri string, download bool) *Route {
   445  	return r.registerRoute([]string{http.MethodGet}, uri+"{resource:.*}", staticHandler(fs, download))
   446  }
   447  
   448  func (r *Router) registerRoute(methods []string, uri string, handler Handler) *Route {
   449  	methodsSlice := slices.Clone(methods)
   450  
   451  	corsOptions, hasCORSOptions := r.LookupMeta(MetaCORS)
   452  	if hasCORSOptions && corsOptions != (*cors.Options)(nil) && !lo.Contains(methodsSlice, http.MethodOptions) {
   453  		methodsSlice = append(methodsSlice, http.MethodOptions)
   454  	}
   455  
   456  	if lo.Contains(methodsSlice, http.MethodGet) && !lo.Contains(methodsSlice, http.MethodHead) {
   457  		methodsSlice = append(methodsSlice, http.MethodHead)
   458  	}
   459  
   460  	if uri == "/" && r.parent != nil && !(r.prefix == "" && r.parent.parent == nil) {
   461  		uri = ""
   462  	}
   463  
   464  	route := &Route{
   465  		name:    "",
   466  		uri:     uri,
   467  		methods: methodsSlice,
   468  		parent:  r,
   469  		handler: handler,
   470  		Meta:    make(map[string]any),
   471  	}
   472  	route.compileParameters(route.uri, true, r.regexCache)
   473  	r.routes = append(r.routes, route)
   474  	return route
   475  }
   476  
   477  // Controller register all routes for a controller implementing the `Registrer` interface.
   478  // Automatically calls `Init()` and `RegisterRoutes()` on the given controller.
   479  func (r *Router) Controller(controller Registrer) *Router {
   480  	controller.Init(r.server)
   481  	controller.RegisterRoutes(r)
   482  	return r
   483  }
   484  
   485  func (r *Router) requestHandler(match *routeMatch, w http.ResponseWriter, rawRequest *http.Request) {
   486  	request := NewRequest(rawRequest)
   487  	request.Route = match.route
   488  	request.RouteParams = lo.Ternary(match.parameters == nil, map[string]string{}, match.parameters)
   489  	response := NewResponse(r.server, request, w)
   490  	handler := match.route.handler
   491  
   492  	// Route-specific middleware is executed after router middleware
   493  	handler = match.route.applyMiddleware(handler)
   494  
   495  	parent := match.route.parent
   496  	for parent != nil {
   497  		handler = parent.applyMiddleware(handler)
   498  		parent = parent.parent
   499  	}
   500  
   501  	handler = r.globalMiddleware.applyMiddleware(handler)
   502  
   503  	handler(response, request)
   504  
   505  	if err := r.finalize(response, request); err != nil {
   506  		r.server.Logger.Error(err)
   507  	}
   508  }
   509  
   510  // finalize the request's life-cycle.
   511  func (r *Router) finalize(response *Response, request *Request) error {
   512  	if response.empty {
   513  		if response.status == 0 {
   514  			// If the response is empty, return status 204 to
   515  			// comply with RFC 7231, 6.3.5
   516  			response.Status(http.StatusNoContent)
   517  		} else if statusHandler, ok := r.statusHandlers[response.status]; ok {
   518  			// Status has been set but body is empty.
   519  			// Execute status handler if exists.
   520  			statusHandler.Handle(response, request)
   521  		}
   522  	}
   523  
   524  	if !response.wroteHeader && !response.hijacked {
   525  		response.WriteHeader(response.status)
   526  	}
   527  
   528  	return errorutil.New(response.close())
   529  }