gin.go (23011B)
1 // Copyright 2014 Manu Martinez-Almeida. All rights reserved. 2 // Use of this source code is governed by a MIT style 3 // license that can be found in the LICENSE file. 4 5 package gin 6 7 import ( 8 "fmt" 9 "html/template" 10 "net" 11 "net/http" 12 "os" 13 "path" 14 "regexp" 15 "strings" 16 "sync" 17 18 "github.com/gin-gonic/gin/internal/bytesconv" 19 "github.com/gin-gonic/gin/render" 20 "golang.org/x/net/http2" 21 "golang.org/x/net/http2/h2c" 22 ) 23 24 const defaultMultipartMemory = 32 << 20 // 32 MB 25 26 var ( 27 default404Body = []byte("404 page not found") 28 default405Body = []byte("405 method not allowed") 29 ) 30 31 var defaultPlatform string 32 33 var defaultTrustedCIDRs = []*net.IPNet{ 34 { // 0.0.0.0/0 (IPv4) 35 IP: net.IP{0x0, 0x0, 0x0, 0x0}, 36 Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}, 37 }, 38 { // ::/0 (IPv6) 39 IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 40 Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 41 }, 42 } 43 44 var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") 45 var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") 46 47 // HandlerFunc defines the handler used by gin middleware as return value. 48 type HandlerFunc func(*Context) 49 50 // HandlersChain defines a HandlerFunc slice. 51 type HandlersChain []HandlerFunc 52 53 // Last returns the last handler in the chain. i.e. the last handler is the main one. 54 func (c HandlersChain) Last() HandlerFunc { 55 if length := len(c); length > 0 { 56 return c[length-1] 57 } 58 return nil 59 } 60 61 // RouteInfo represents a request route's specification which contains method and path and its handler. 62 type RouteInfo struct { 63 Method string 64 Path string 65 Handler string 66 HandlerFunc HandlerFunc 67 } 68 69 // RoutesInfo defines a RouteInfo slice. 70 type RoutesInfo []RouteInfo 71 72 // Trusted platforms 73 const ( 74 // PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr 75 // for determining the client's IP 76 PlatformGoogleAppEngine = "X-Appengine-Remote-Addr" 77 // PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining 78 // the client's IP 79 PlatformCloudflare = "CF-Connecting-IP" 80 ) 81 82 // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. 83 // Create an instance of Engine, by using New() or Default() 84 type Engine struct { 85 RouterGroup 86 87 // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a 88 // handler for the path with (without) the trailing slash exists. 89 // For example if /foo/ is requested but a route only exists for /foo, the 90 // client is redirected to /foo with http status code 301 for GET requests 91 // and 307 for all other request methods. 92 RedirectTrailingSlash bool 93 94 // RedirectFixedPath if enabled, the router tries to fix the current request path, if no 95 // handle is registered for it. 96 // First superfluous path elements like ../ or // are removed. 97 // Afterwards the router does a case-insensitive lookup of the cleaned path. 98 // If a handle can be found for this route, the router makes a redirection 99 // to the corrected path with status code 301 for GET requests and 307 for 100 // all other request methods. 101 // For example /FOO and /..//Foo could be redirected to /foo. 102 // RedirectTrailingSlash is independent of this option. 103 RedirectFixedPath bool 104 105 // HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the 106 // current route, if the current request can not be routed. 107 // If this is the case, the request is answered with 'Method Not Allowed' 108 // and HTTP status code 405. 109 // If no other Method is allowed, the request is delegated to the NotFound 110 // handler. 111 HandleMethodNotAllowed bool 112 113 // ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that 114 // match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was 115 // fetched, it falls back to the IP obtained from 116 // `(*gin.Context).Request.RemoteAddr`. 117 ForwardedByClientIP bool 118 119 // AppEngine was deprecated. 120 // Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD 121 // #726 #755 If enabled, it will trust some headers starting with 122 // 'X-AppEngine...' for better integration with that PaaS. 123 AppEngine bool 124 125 // UseRawPath if enabled, the url.RawPath will be used to find parameters. 126 UseRawPath bool 127 128 // UnescapePathValues if true, the path value will be unescaped. 129 // If UseRawPath is false (by default), the UnescapePathValues effectively is true, 130 // as url.Path gonna be used, which is already unescaped. 131 UnescapePathValues bool 132 133 // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. 134 // See the PR #1817 and issue #1644 135 RemoveExtraSlash bool 136 137 // RemoteIPHeaders list of headers used to obtain the client IP when 138 // `(*gin.Engine).ForwardedByClientIP` is `true` and 139 // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the 140 // network origins of list defined by `(*gin.Engine).SetTrustedProxies()`. 141 RemoteIPHeaders []string 142 143 // TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by 144 // that platform, for example to determine the client IP 145 TrustedPlatform string 146 147 // MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm 148 // method call. 149 MaxMultipartMemory int64 150 151 // UseH2C enable h2c support. 152 UseH2C bool 153 154 // ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil. 155 ContextWithFallback bool 156 157 delims render.Delims 158 secureJSONPrefix string 159 HTMLRender render.HTMLRender 160 FuncMap template.FuncMap 161 allNoRoute HandlersChain 162 allNoMethod HandlersChain 163 noRoute HandlersChain 164 noMethod HandlersChain 165 pool sync.Pool 166 trees methodTrees 167 maxParams uint16 168 maxSections uint16 169 trustedProxies []string 170 trustedCIDRs []*net.IPNet 171 } 172 173 var _ IRouter = (*Engine)(nil) 174 175 // New returns a new blank Engine instance without any middleware attached. 176 // By default, the configuration is: 177 // - RedirectTrailingSlash: true 178 // - RedirectFixedPath: false 179 // - HandleMethodNotAllowed: false 180 // - ForwardedByClientIP: true 181 // - UseRawPath: false 182 // - UnescapePathValues: true 183 func New() *Engine { 184 debugPrintWARNINGNew() 185 engine := &Engine{ 186 RouterGroup: RouterGroup{ 187 Handlers: nil, 188 basePath: "/", 189 root: true, 190 }, 191 FuncMap: template.FuncMap{}, 192 RedirectTrailingSlash: true, 193 RedirectFixedPath: false, 194 HandleMethodNotAllowed: false, 195 ForwardedByClientIP: true, 196 RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"}, 197 TrustedPlatform: defaultPlatform, 198 UseRawPath: false, 199 RemoveExtraSlash: false, 200 UnescapePathValues: true, 201 MaxMultipartMemory: defaultMultipartMemory, 202 trees: make(methodTrees, 0, 9), 203 delims: render.Delims{Left: "{{", Right: "}}"}, 204 secureJSONPrefix: "while(1);", 205 trustedProxies: []string{"0.0.0.0/0", "::/0"}, 206 trustedCIDRs: defaultTrustedCIDRs, 207 } 208 engine.RouterGroup.engine = engine 209 engine.pool.New = func() any { 210 return engine.allocateContext(engine.maxParams) 211 } 212 return engine 213 } 214 215 // Default returns an Engine instance with the Logger and Recovery middleware already attached. 216 func Default() *Engine { 217 debugPrintWARNINGDefault() 218 engine := New() 219 engine.Use(Logger(), Recovery()) 220 return engine 221 } 222 223 func (engine *Engine) Handler() http.Handler { 224 if !engine.UseH2C { 225 return engine 226 } 227 228 h2s := &http2.Server{} 229 return h2c.NewHandler(engine, h2s) 230 } 231 232 func (engine *Engine) allocateContext(maxParams uint16) *Context { 233 v := make(Params, 0, maxParams) 234 skippedNodes := make([]skippedNode, 0, engine.maxSections) 235 return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes} 236 } 237 238 // Delims sets template left and right delims and returns an Engine instance. 239 func (engine *Engine) Delims(left, right string) *Engine { 240 engine.delims = render.Delims{Left: left, Right: right} 241 return engine 242 } 243 244 // SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON. 245 func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { 246 engine.secureJSONPrefix = prefix 247 return engine 248 } 249 250 // LoadHTMLGlob loads HTML files identified by glob pattern 251 // and associates the result with HTML renderer. 252 func (engine *Engine) LoadHTMLGlob(pattern string) { 253 left := engine.delims.Left 254 right := engine.delims.Right 255 templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) 256 257 if IsDebugging() { 258 debugPrintLoadTemplate(templ) 259 engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} 260 return 261 } 262 263 engine.SetHTMLTemplate(templ) 264 } 265 266 // LoadHTMLFiles loads a slice of HTML files 267 // and associates the result with HTML renderer. 268 func (engine *Engine) LoadHTMLFiles(files ...string) { 269 if IsDebugging() { 270 engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} 271 return 272 } 273 274 templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) 275 engine.SetHTMLTemplate(templ) 276 } 277 278 // SetHTMLTemplate associate a template with HTML renderer. 279 func (engine *Engine) SetHTMLTemplate(templ *template.Template) { 280 if len(engine.trees) > 0 { 281 debugPrintWARNINGSetHTMLTemplate() 282 } 283 284 engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} 285 } 286 287 // SetFuncMap sets the FuncMap used for template.FuncMap. 288 func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { 289 engine.FuncMap = funcMap 290 } 291 292 // NoRoute adds handlers for NoRoute. It returns a 404 code by default. 293 func (engine *Engine) NoRoute(handlers ...HandlerFunc) { 294 engine.noRoute = handlers 295 engine.rebuild404Handlers() 296 } 297 298 // NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true. 299 func (engine *Engine) NoMethod(handlers ...HandlerFunc) { 300 engine.noMethod = handlers 301 engine.rebuild405Handlers() 302 } 303 304 // Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be 305 // included in the handlers chain for every single request. Even 404, 405, static files... 306 // For example, this is the right place for a logger or error management middleware. 307 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { 308 engine.RouterGroup.Use(middleware...) 309 engine.rebuild404Handlers() 310 engine.rebuild405Handlers() 311 return engine 312 } 313 314 func (engine *Engine) rebuild404Handlers() { 315 engine.allNoRoute = engine.combineHandlers(engine.noRoute) 316 } 317 318 func (engine *Engine) rebuild405Handlers() { 319 engine.allNoMethod = engine.combineHandlers(engine.noMethod) 320 } 321 322 func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { 323 assert1(path[0] == '/', "path must begin with '/'") 324 assert1(method != "", "HTTP method can not be empty") 325 assert1(len(handlers) > 0, "there must be at least one handler") 326 327 debugPrintRoute(method, path, handlers) 328 329 root := engine.trees.get(method) 330 if root == nil { 331 root = new(node) 332 root.fullPath = "/" 333 engine.trees = append(engine.trees, methodTree{method: method, root: root}) 334 } 335 root.addRoute(path, handlers) 336 337 // Update maxParams 338 if paramsCount := countParams(path); paramsCount > engine.maxParams { 339 engine.maxParams = paramsCount 340 } 341 342 if sectionsCount := countSections(path); sectionsCount > engine.maxSections { 343 engine.maxSections = sectionsCount 344 } 345 } 346 347 // Routes returns a slice of registered routes, including some useful information, such as: 348 // the http method, path and the handler name. 349 func (engine *Engine) Routes() (routes RoutesInfo) { 350 for _, tree := range engine.trees { 351 routes = iterate("", tree.method, routes, tree.root) 352 } 353 return routes 354 } 355 356 func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { 357 path += root.path 358 if len(root.handlers) > 0 { 359 handlerFunc := root.handlers.Last() 360 routes = append(routes, RouteInfo{ 361 Method: method, 362 Path: path, 363 Handler: nameOfFunction(handlerFunc), 364 HandlerFunc: handlerFunc, 365 }) 366 } 367 for _, child := range root.children { 368 routes = iterate(path, method, routes, child) 369 } 370 return routes 371 } 372 373 // Run attaches the router to a http.Server and starts listening and serving HTTP requests. 374 // It is a shortcut for http.ListenAndServe(addr, router) 375 // Note: this method will block the calling goroutine indefinitely unless an error happens. 376 func (engine *Engine) Run(addr ...string) (err error) { 377 defer func() { debugPrintError(err) }() 378 379 if engine.isUnsafeTrustedProxies() { 380 debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + 381 "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") 382 } 383 384 address := resolveAddress(addr) 385 debugPrint("Listening and serving HTTP on %s\n", address) 386 err = http.ListenAndServe(address, engine.Handler()) 387 return 388 } 389 390 func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { 391 if engine.trustedProxies == nil { 392 return nil, nil 393 } 394 395 cidr := make([]*net.IPNet, 0, len(engine.trustedProxies)) 396 for _, trustedProxy := range engine.trustedProxies { 397 if !strings.Contains(trustedProxy, "/") { 398 ip := parseIP(trustedProxy) 399 if ip == nil { 400 return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy} 401 } 402 403 switch len(ip) { 404 case net.IPv4len: 405 trustedProxy += "/32" 406 case net.IPv6len: 407 trustedProxy += "/128" 408 } 409 } 410 _, cidrNet, err := net.ParseCIDR(trustedProxy) 411 if err != nil { 412 return cidr, err 413 } 414 cidr = append(cidr, cidrNet) 415 } 416 return cidr, nil 417 } 418 419 // SetTrustedProxies set a list of network origins (IPv4 addresses, 420 // IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust 421 // request's headers that contain alternative client IP when 422 // `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies` 423 // feature is enabled by default, and it also trusts all proxies 424 // by default. If you want to disable this feature, use 425 // Engine.SetTrustedProxies(nil), then Context.ClientIP() will 426 // return the remote address directly. 427 func (engine *Engine) SetTrustedProxies(trustedProxies []string) error { 428 engine.trustedProxies = trustedProxies 429 return engine.parseTrustedProxies() 430 } 431 432 // isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true) 433 func (engine *Engine) isUnsafeTrustedProxies() bool { 434 return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::")) 435 } 436 437 // parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs 438 func (engine *Engine) parseTrustedProxies() error { 439 trustedCIDRs, err := engine.prepareTrustedCIDRs() 440 engine.trustedCIDRs = trustedCIDRs 441 return err 442 } 443 444 // isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs 445 func (engine *Engine) isTrustedProxy(ip net.IP) bool { 446 if engine.trustedCIDRs == nil { 447 return false 448 } 449 for _, cidr := range engine.trustedCIDRs { 450 if cidr.Contains(ip) { 451 return true 452 } 453 } 454 return false 455 } 456 457 // validateHeader will parse X-Forwarded-For header and return the trusted client IP address 458 func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) { 459 if header == "" { 460 return "", false 461 } 462 items := strings.Split(header, ",") 463 for i := len(items) - 1; i >= 0; i-- { 464 ipStr := strings.TrimSpace(items[i]) 465 ip := net.ParseIP(ipStr) 466 if ip == nil { 467 break 468 } 469 470 // X-Forwarded-For is appended by proxy 471 // Check IPs in reverse order and stop when find untrusted proxy 472 if (i == 0) || (!engine.isTrustedProxy(ip)) { 473 return ipStr, true 474 } 475 } 476 return "", false 477 } 478 479 // parseIP parse a string representation of an IP and returns a net.IP with the 480 // minimum byte representation or nil if input is invalid. 481 func parseIP(ip string) net.IP { 482 parsedIP := net.ParseIP(ip) 483 484 if ipv4 := parsedIP.To4(); ipv4 != nil { 485 // return ip in a 4-byte representation 486 return ipv4 487 } 488 489 // return ip in a 16-byte representation or nil 490 return parsedIP 491 } 492 493 // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. 494 // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) 495 // Note: this method will block the calling goroutine indefinitely unless an error happens. 496 func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { 497 debugPrint("Listening and serving HTTPS on %s\n", addr) 498 defer func() { debugPrintError(err) }() 499 500 if engine.isUnsafeTrustedProxies() { 501 debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + 502 "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") 503 } 504 505 err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler()) 506 return 507 } 508 509 // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests 510 // through the specified unix socket (i.e. a file). 511 // Note: this method will block the calling goroutine indefinitely unless an error happens. 512 func (engine *Engine) RunUnix(file string) (err error) { 513 debugPrint("Listening and serving HTTP on unix:/%s", file) 514 defer func() { debugPrintError(err) }() 515 516 if engine.isUnsafeTrustedProxies() { 517 debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + 518 "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") 519 } 520 521 listener, err := net.Listen("unix", file) 522 if err != nil { 523 return 524 } 525 defer listener.Close() 526 defer os.Remove(file) 527 528 err = http.Serve(listener, engine.Handler()) 529 return 530 } 531 532 // RunFd attaches the router to a http.Server and starts listening and serving HTTP requests 533 // through the specified file descriptor. 534 // Note: this method will block the calling goroutine indefinitely unless an error happens. 535 func (engine *Engine) RunFd(fd int) (err error) { 536 debugPrint("Listening and serving HTTP on fd@%d", fd) 537 defer func() { debugPrintError(err) }() 538 539 if engine.isUnsafeTrustedProxies() { 540 debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + 541 "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") 542 } 543 544 f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) 545 listener, err := net.FileListener(f) 546 if err != nil { 547 return 548 } 549 defer listener.Close() 550 err = engine.RunListener(listener) 551 return 552 } 553 554 // RunListener attaches the router to a http.Server and starts listening and serving HTTP requests 555 // through the specified net.Listener 556 func (engine *Engine) RunListener(listener net.Listener) (err error) { 557 debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) 558 defer func() { debugPrintError(err) }() 559 560 if engine.isUnsafeTrustedProxies() { 561 debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + 562 "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") 563 } 564 565 err = http.Serve(listener, engine.Handler()) 566 return 567 } 568 569 // ServeHTTP conforms to the http.Handler interface. 570 func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { 571 c := engine.pool.Get().(*Context) 572 c.writermem.reset(w) 573 c.Request = req 574 c.reset() 575 576 engine.handleHTTPRequest(c) 577 578 engine.pool.Put(c) 579 } 580 581 // HandleContext re-enters a context that has been rewritten. 582 // This can be done by setting c.Request.URL.Path to your new target. 583 // Disclaimer: You can loop yourself to deal with this, use wisely. 584 func (engine *Engine) HandleContext(c *Context) { 585 oldIndexValue := c.index 586 c.reset() 587 engine.handleHTTPRequest(c) 588 589 c.index = oldIndexValue 590 } 591 592 func (engine *Engine) handleHTTPRequest(c *Context) { 593 httpMethod := c.Request.Method 594 rPath := c.Request.URL.Path 595 unescape := false 596 if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { 597 rPath = c.Request.URL.RawPath 598 unescape = engine.UnescapePathValues 599 } 600 601 if engine.RemoveExtraSlash { 602 rPath = cleanPath(rPath) 603 } 604 605 // Find root of the tree for the given HTTP method 606 t := engine.trees 607 for i, tl := 0, len(t); i < tl; i++ { 608 if t[i].method != httpMethod { 609 continue 610 } 611 root := t[i].root 612 // Find route in tree 613 value := root.getValue(rPath, c.params, c.skippedNodes, unescape) 614 if value.params != nil { 615 c.Params = *value.params 616 } 617 if value.handlers != nil { 618 c.handlers = value.handlers 619 c.fullPath = value.fullPath 620 c.Next() 621 c.writermem.WriteHeaderNow() 622 return 623 } 624 if httpMethod != http.MethodConnect && rPath != "/" { 625 if value.tsr && engine.RedirectTrailingSlash { 626 redirectTrailingSlash(c) 627 return 628 } 629 if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { 630 return 631 } 632 } 633 break 634 } 635 636 if engine.HandleMethodNotAllowed { 637 for _, tree := range engine.trees { 638 if tree.method == httpMethod { 639 continue 640 } 641 if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil { 642 c.handlers = engine.allNoMethod 643 serveError(c, http.StatusMethodNotAllowed, default405Body) 644 return 645 } 646 } 647 } 648 c.handlers = engine.allNoRoute 649 serveError(c, http.StatusNotFound, default404Body) 650 } 651 652 var mimePlain = []string{MIMEPlain} 653 654 func serveError(c *Context, code int, defaultMessage []byte) { 655 c.writermem.status = code 656 c.Next() 657 if c.writermem.Written() { 658 return 659 } 660 if c.writermem.Status() == code { 661 c.writermem.Header()["Content-Type"] = mimePlain 662 _, err := c.Writer.Write(defaultMessage) 663 if err != nil { 664 debugPrint("cannot write message to writer during serve error: %v", err) 665 } 666 return 667 } 668 c.writermem.WriteHeaderNow() 669 } 670 671 func redirectTrailingSlash(c *Context) { 672 req := c.Request 673 p := req.URL.Path 674 if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { 675 prefix = regSafePrefix.ReplaceAllString(prefix, "") 676 prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/") 677 678 p = prefix + "/" + req.URL.Path 679 } 680 req.URL.Path = p + "/" 681 if length := len(p); length > 1 && p[length-1] == '/' { 682 req.URL.Path = p[:length-1] 683 } 684 redirectRequest(c) 685 } 686 687 func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { 688 req := c.Request 689 rPath := req.URL.Path 690 691 if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok { 692 req.URL.Path = bytesconv.BytesToString(fixedPath) 693 redirectRequest(c) 694 return true 695 } 696 return false 697 } 698 699 func redirectRequest(c *Context) { 700 req := c.Request 701 rPath := req.URL.Path 702 rURL := req.URL.String() 703 704 code := http.StatusMovedPermanently // Permanent redirect, request with GET method 705 if req.Method != http.MethodGet { 706 code = http.StatusTemporaryRedirect 707 } 708 debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) 709 http.Redirect(c.Writer, req, rURL, code) 710 c.writermem.WriteHeaderNow() 711 }