github.com/System-Glitch/goyave/v2@v2.10.3-0.20200819142921-51011e75d504/router.go (about)

     1  package goyave
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/System-Glitch/goyave/v2/config"
    10  	"github.com/System-Glitch/goyave/v2/cors"
    11  	"github.com/System-Glitch/goyave/v2/helper/filesystem"
    12  )
    13  
    14  type routeMatcher interface {
    15  	match(req *http.Request, match *routeMatch) bool
    16  }
    17  
    18  // Router registers routes to be matched and executes a handler.
    19  type Router struct {
    20  	parent            *Router
    21  	prefix            string
    22  	corsOptions       *cors.Options
    23  	hasCORSMiddleware bool
    24  
    25  	routes         []*Route
    26  	subrouters     []*Router
    27  	statusHandlers map[int]Handler
    28  	namedRoutes    map[string]*Route
    29  	middlewareHolder
    30  	parametrizeable
    31  }
    32  
    33  var _ http.Handler = (*Router)(nil) // implements http.Handler
    34  var _ routeMatcher = (*Router)(nil) // implements routeMatcher
    35  
    36  // Handler is a controller or middleware function
    37  type Handler func(*Response, *Request)
    38  
    39  type middlewareHolder struct {
    40  	middleware []Middleware
    41  }
    42  
    43  type routeMatch struct {
    44  	route       *Route
    45  	err         error
    46  	currentPath string
    47  	parameters  map[string]string
    48  }
    49  
    50  var (
    51  	errMatchMethodNotAllowed = errors.New("Method not allowed for this route")
    52  	errMatchNotFound         = errors.New("No match for this URI")
    53  
    54  	methodNotAllowedRoute = newRoute(func(response *Response, request *Request) {
    55  		response.Status(http.StatusMethodNotAllowed)
    56  	})
    57  	notFoundRoute = newRoute(func(response *Response, request *Request) {
    58  		response.Status(http.StatusNotFound)
    59  	})
    60  )
    61  
    62  func panicStatusHandler(response *Response, request *Request) { // TODO think about more possibilities for the error handler (avoid printing twice for example, access to stacktrace, etc)
    63  	response.error(response.GetError())
    64  	if response.empty {
    65  		message := map[string]string{
    66  			"error": http.StatusText(response.GetStatus()),
    67  		}
    68  		response.JSON(response.GetStatus(), message)
    69  	}
    70  }
    71  
    72  func errorStatusHandler(response *Response, request *Request) {
    73  	message := map[string]string{
    74  		"error": http.StatusText(response.GetStatus()),
    75  	}
    76  	response.JSON(response.GetStatus(), message)
    77  }
    78  
    79  func newRouter() *Router {
    80  	methodNotAllowedRoute.name = "method-not-allowed"
    81  	// Create a fresh regex cache
    82  	// This cache is set to nil when the server starts
    83  	regexCache = make(map[string]*regexp.Regexp, 5)
    84  
    85  	router := &Router{
    86  		parent:            nil,
    87  		prefix:            "",
    88  		hasCORSMiddleware: false,
    89  		statusHandlers:    make(map[int]Handler, 41),
    90  		namedRoutes:       make(map[string]*Route, 5),
    91  		middlewareHolder: middlewareHolder{
    92  			middleware: make([]Middleware, 0, 3),
    93  		},
    94  	}
    95  	router.StatusHandler(panicStatusHandler, http.StatusInternalServerError)
    96  	for i := 400; i <= 418; i++ {
    97  		router.StatusHandler(errorStatusHandler, i)
    98  	}
    99  	for i := 421; i <= 426; i++ {
   100  		router.StatusHandler(errorStatusHandler, i)
   101  	}
   102  	router.StatusHandler(errorStatusHandler, 428, 429, 431, 444, 451)
   103  	router.StatusHandler(errorStatusHandler, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511)
   104  	router.Middleware(recoveryMiddleware, parseRequestMiddleware, languageMiddleware)
   105  	return router
   106  }
   107  
   108  // ServeHTTP dispatches the handler registered in the matched route.
   109  func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   110  	protocol := config.GetString("server.protocol")
   111  	if req.URL.Scheme != "" && req.URL.Scheme != protocol {
   112  		address := getAddress(protocol) + req.URL.Path
   113  		query := req.URL.Query()
   114  		if len(query) != 0 {
   115  			address += "?" + query.Encode()
   116  		}
   117  		http.Redirect(w, req, address, http.StatusPermanentRedirect)
   118  		return
   119  	}
   120  
   121  	match := routeMatch{currentPath: req.URL.Path}
   122  	r.match(req, &match)
   123  	r.requestHandler(&match, w, req)
   124  }
   125  
   126  func (r *Router) match(req *http.Request, match *routeMatch) bool {
   127  	// Check if router itself matches
   128  	var params []string
   129  	if r.parametrizeable.regex != nil {
   130  		params = r.parametrizeable.regex.FindStringSubmatch(match.currentPath)
   131  	} else {
   132  		params = []string{""}
   133  	}
   134  
   135  	if params != nil {
   136  		match.trimCurrentPath(params[0])
   137  		if len(params) > 1 {
   138  			match.mergeParams(r.makeParameters(params))
   139  		}
   140  
   141  		// Check in subrouters first
   142  		for _, router := range r.subrouters {
   143  			if router.match(req, match) {
   144  				if router.prefix == "" && match.route == methodNotAllowedRoute {
   145  					// This allows route groups with subrouters having empty prefix.
   146  					break
   147  				}
   148  				return true
   149  			}
   150  		}
   151  
   152  		// Check if any route matches
   153  		for _, route := range r.routes {
   154  			if route.match(req, match) {
   155  				return true
   156  			}
   157  		}
   158  	}
   159  
   160  	if match.err == errMatchMethodNotAllowed {
   161  		match.route = methodNotAllowedRoute
   162  		return true
   163  	}
   164  
   165  	match.route = notFoundRoute
   166  	return false
   167  }
   168  
   169  func (r *Router) makeParameters(match []string) map[string]string {
   170  	return r.parametrizeable.makeParameters(match, r.parameters)
   171  }
   172  
   173  // Subrouter create a new sub-router from this router.
   174  // Use subrouters to create route groups and to apply middleware to multiple routes.
   175  // CORS options are also inherited.
   176  func (r *Router) Subrouter(prefix string) *Router {
   177  	if prefix == "/" {
   178  		prefix = ""
   179  	}
   180  
   181  	router := &Router{
   182  		parent:            r,
   183  		prefix:            prefix,
   184  		corsOptions:       r.corsOptions,
   185  		hasCORSMiddleware: r.hasCORSMiddleware,
   186  		statusHandlers:    r.copyStatusHandlers(),
   187  		namedRoutes:       r.namedRoutes,
   188  		routes:            make([]*Route, 0, 5), // Typical CRUD has 5 routes
   189  		middlewareHolder: middlewareHolder{
   190  			middleware: nil,
   191  		},
   192  	}
   193  	router.compileParameters(router.prefix, false)
   194  	r.subrouters = append(r.subrouters, router)
   195  	return router
   196  }
   197  
   198  // Middleware apply one or more middleware to the route group.
   199  func (r *Router) Middleware(middleware ...Middleware) {
   200  	if r.middleware == nil {
   201  		r.middleware = make([]Middleware, 0, 3)
   202  	}
   203  	r.middleware = append(r.middleware, middleware...)
   204  }
   205  
   206  // Route register a new route.
   207  //
   208  // Multiple methods can be passed using a pipe-separated string.
   209  //  "PUT|PATCH"
   210  //
   211  // The validation rules set is optional. If you don't want your route
   212  // to be validated, pass "nil".
   213  //
   214  // If the router has CORS options set, the "OPTIONS" method is automatically added
   215  // to the matcher if it's missing, so it allows preflight requests.
   216  //
   217  // Returns the generated route.
   218  func (r *Router) Route(methods string, uri string, handler Handler) *Route {
   219  	return r.registerRoute(methods, uri, handler)
   220  }
   221  
   222  func (r *Router) registerRoute(methods string, uri string, handler Handler) *Route {
   223  	if r.corsOptions != nil && !strings.Contains(methods, "OPTIONS") {
   224  		methods += "|OPTIONS"
   225  	}
   226  
   227  	if uri == "/" {
   228  		uri = ""
   229  	}
   230  
   231  	route := &Route{
   232  		name:    "",
   233  		uri:     uri,
   234  		methods: strings.Split(methods, "|"),
   235  		parent:  r,
   236  		handler: handler,
   237  	}
   238  	route.compileParameters(route.uri, true)
   239  	r.routes = append(r.routes, route)
   240  	return route
   241  }
   242  
   243  // Get registers a new route wit the GET method.
   244  func (r *Router) Get(uri string, handler Handler) *Route {
   245  	return r.registerRoute(http.MethodGet, uri, handler)
   246  }
   247  
   248  // Post registers a new route wit the POST method.
   249  func (r *Router) Post(uri string, handler Handler) *Route {
   250  	return r.registerRoute(http.MethodPost, uri, handler)
   251  }
   252  
   253  // Put registers a new route wit the PUT method.
   254  func (r *Router) Put(uri string, handler Handler) *Route {
   255  	return r.registerRoute(http.MethodPut, uri, handler)
   256  }
   257  
   258  // Patch registers a new route wit the PATCH method.
   259  func (r *Router) Patch(uri string, handler Handler) *Route {
   260  	return r.registerRoute(http.MethodPatch, uri, handler)
   261  }
   262  
   263  // Delete registers a new route wit the DELETE method.
   264  func (r *Router) Delete(uri string, handler Handler) *Route {
   265  	return r.registerRoute(http.MethodDelete, uri, handler)
   266  }
   267  
   268  // Options registers a new route wit the OPTIONS method.
   269  func (r *Router) Options(uri string, handler Handler) *Route {
   270  	return r.registerRoute(http.MethodOptions, uri, handler)
   271  }
   272  
   273  // GetRoute get a named route.
   274  // Returns nil if the route doesn't exist.
   275  func (r *Router) GetRoute(name string) *Route {
   276  	return r.namedRoutes[name]
   277  }
   278  
   279  // Static serve a directory and its subdirectories of static resources.
   280  // Set the "download" parameter to true if you want the files to be sent as an attachment
   281  // instead of an inline element.
   282  //
   283  // If no file is given in the url, or if the given file is a directory, the handler will
   284  // send the "index.html" file if it exists.
   285  func (r *Router) Static(uri string, directory string, download bool, middleware ...Middleware) {
   286  	r.registerRoute(http.MethodGet, uri+"{resource:.*}", staticHandler(directory, download)).Middleware(middleware...)
   287  }
   288  
   289  // CORS set the CORS options for this route group.
   290  // If the options are not nil, the CORS middleware is automatically added.
   291  func (r *Router) CORS(options *cors.Options) {
   292  	r.corsOptions = options
   293  	if options != nil && !r.hasCORSMiddleware {
   294  		r.Middleware(corsMiddleware)
   295  		r.hasCORSMiddleware = true
   296  	}
   297  }
   298  
   299  // StatusHandler set a handler for responses with an empty body.
   300  // The handler will be automatically executed if the request's life-cycle reaches its end
   301  // and nothing has been written in the response body.
   302  //
   303  // Multiple status codes can be given. The handler will be executed if one of them matches.
   304  //
   305  // This method can be used to define custom error handlers for example.
   306  //
   307  // Status handlers are inherited as a copy in sub-routers. Modifying a child's status handler
   308  // will not modify its parent's.
   309  //
   310  // Codes in the 400 and 500 ranges have a default status handler.
   311  func (r *Router) StatusHandler(handler Handler, status int, additionalStatuses ...int) {
   312  	r.statusHandlers[status] = handler
   313  	for _, s := range additionalStatuses {
   314  		r.statusHandlers[s] = handler
   315  	}
   316  }
   317  
   318  func staticHandler(directory string, download bool) Handler {
   319  	return func(response *Response, r *Request) {
   320  		file := r.Params["resource"]
   321  		path := cleanStaticPath(directory, file)
   322  
   323  		if filesystem.FileExists(path) {
   324  			var err error
   325  			if download {
   326  				err = response.Download(path, file[strings.LastIndex(file, "/")+1:])
   327  			} else {
   328  				err = response.File(path)
   329  			}
   330  
   331  			if err != nil {
   332  				ErrLogger.Println(err)
   333  			}
   334  		} else {
   335  			response.Status(http.StatusNotFound)
   336  		}
   337  	}
   338  }
   339  
   340  func cleanStaticPath(directory string, file string) string {
   341  	file = strings.TrimPrefix(file, "/")
   342  	path := directory + "/" + file
   343  	if filesystem.IsDirectory(path) {
   344  		if !strings.HasSuffix(path, "/") {
   345  			path += "/"
   346  		}
   347  		path += "index.html"
   348  	}
   349  	return path
   350  }
   351  
   352  func (r *Router) copyStatusHandlers() map[int]Handler {
   353  	cpy := make(map[int]Handler, len(r.statusHandlers))
   354  	for key, value := range r.statusHandlers {
   355  		cpy[key] = value
   356  	}
   357  	return cpy
   358  }
   359  
   360  func (r *Router) requestHandler(match *routeMatch, w http.ResponseWriter, rawRequest *http.Request) {
   361  	request := &Request{
   362  		httpRequest: rawRequest,
   363  		route:       match.route,
   364  		corsOptions: r.corsOptions,
   365  		Rules:       match.route.validationRules,
   366  		Params:      match.parameters,
   367  	}
   368  	response := newResponse(w, rawRequest)
   369  	handler := match.route.handler
   370  
   371  	// Validate last.
   372  	// Allows custom middleware to be executed after core
   373  	// middleware and before validation.
   374  	handler = validateRequestMiddleware(handler)
   375  
   376  	// Route-specific middleware is executed after router middleware
   377  	handler = match.route.applyMiddleware(handler)
   378  
   379  	parent := match.route.parent
   380  	for parent != nil {
   381  		handler = parent.applyMiddleware(handler)
   382  		parent = parent.parent
   383  	}
   384  
   385  	handler(response, request)
   386  
   387  	r.finalize(response, request)
   388  }
   389  
   390  // finalize the request's life-cycle.
   391  func (r *Router) finalize(response *Response, request *Request) {
   392  	if response.empty {
   393  		if response.status == 0 {
   394  			// If the response is empty, return status 204 to
   395  			// comply with RFC 7231, 6.3.5
   396  			response.Status(http.StatusNoContent)
   397  		} else if statusHandler, ok := r.statusHandlers[response.status]; ok {
   398  			// Status has been set but body is empty.
   399  			// Execute status handler if exists.
   400  			statusHandler(response, request)
   401  		}
   402  	}
   403  
   404  	if !response.wroteHeader {
   405  		response.WriteHeader(response.status)
   406  	}
   407  
   408  	response.close()
   409  }
   410  
   411  func (h *middlewareHolder) applyMiddleware(handler Handler) Handler {
   412  	for i := len(h.middleware) - 1; i >= 0; i-- {
   413  		handler = h.middleware[i](handler)
   414  	}
   415  	return handler
   416  }
   417  
   418  func (rm *routeMatch) mergeParams(params map[string]string) {
   419  	if rm.parameters == nil {
   420  		rm.parameters = params
   421  	}
   422  	for k, v := range params {
   423  		rm.parameters[k] = v
   424  	}
   425  }
   426  
   427  func (rm *routeMatch) trimCurrentPath(fullMatch string) {
   428  	rm.currentPath = rm.currentPath[len(fullMatch):]
   429  }