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 }