// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. package gin import ( "fmt" "html/template" "net" "net/http" "os" "path" "path/filepath" "sort" "strings" "sync" "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/render" ) const defaultMultipartMemory = 32 << 20 // 32 MB var ( default404Body = []byte("404 page not found") default405Body = []byte("405 method not allowed") ) var defaultAppEngine bool // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) // HandlersChain defines a HandlerFunc array. type HandlersChain []HandlerFunc // Last returns the last handler in the chain. ie. the last handler is the main one. func (c HandlersChain) Last() HandlerFunc { if length := len(c); length > 0 { return c[length-1] } return nil } // RouteInfo represents a request route's specification which contains method and path and its handler. type RouteInfo struct { Method string Path string Handler string HandlerFunc HandlerFunc } // RoutesInfo defines a RouteInfo array. type RoutesInfo []RouteInfo // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. // Create an instance of Engine, by using New() or Default() type Engine struct { RouterGroup // Enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the // client is redirected to /foo with http status code 301 for GET requests // and 307 for all other request methods. RedirectTrailingSlash bool // If enabled, the router tries to fix the current request path, if no // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. // If a handle can be found for this route, the router makes a redirection // to the corrected path with status code 301 for GET requests and 307 for // all other request methods. // For example /FOO and /..//Foo could be redirected to /foo. // RedirectTrailingSlash is independent of this option. RedirectFixedPath bool // If enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. // If no other Method is allowed, the request is delegated to the NotFound // handler. HandleMethodNotAllowed bool ForwardedByClientIP bool // #726 #755 If enabled, it will thrust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool // If enabled, the url.RawPath will be used to find parameters. UseRawPath bool // If true, the path value will be unescaped. // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // method call. MaxMultipartMemory int64 // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. // See the PR #1817 and issue #1644 RemoveExtraSlash bool delims render.Delims secureJSONPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain pool sync.Pool trees methodTrees } var _ IRouter = &Engine{} // New returns a new blank Engine instance without any middleware attached. // By default the configuration is: // - RedirectTrailingSlash: true // - RedirectFixedPath: false // - HandleMethodNotAllowed: false // - ForwardedByClientIP: true // - UseRawPath: false // - UnescapePathValues: true func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ Handlers: nil, basePath: "/", root: true, }, FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, AppEngine: defaultAppEngine, UseRawPath: false, RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJSONPrefix: "while(1);", } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { return engine.allocateContext() } return engine } // Default returns an Engine instance with the Logger and Recovery middleware already attached. func Default() *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine } func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} } // Delims sets template left and right delims and returns a Engine instance. func (engine *Engine) Delims(left, right string) *Engine { engine.delims = render.Delims{Left: left, Right: right} return engine } // SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON. func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { engine.secureJSONPrefix = prefix return engine } // LoadHTMLGlob loads HTML files identified by glob pattern // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLGlob(pattern string) { left := engine.delims.Left right := engine.delims.Right templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) if IsDebugging() { debugPrintLoadTemplate(templ) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} return } engine.SetHTMLTemplate(templ) } // LoadHTMLFiles loads a slice of HTML files // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} return } templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) engine.SetHTMLTemplate(templ) } // LoadHTMLFilesRecursively loads recursively a slice of HTML files mathing an extension // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLFilesRecursively(folder string, extensionsAllowed []string) { files := []string{} sort.Strings(extensionsAllowed) filepath.Walk(folder, func(path string, info os.FileInfo, err error) error { extension := strings.ToLower(filepath.Ext(path)) indexFound := sort.SearchStrings(extensionsAllowed, extension) if indexFound < len(extensionsAllowed) && extensionsAllowed[indexFound] == extension { files = append(files, path) } return nil }) engine.LoadHTMLFiles(files...) } // SetHTMLTemplate associate a template with HTML renderer. func (engine *Engine) SetHTMLTemplate(templ *template.Template) { if len(engine.trees) > 0 { debugPrintWARNINGSetHTMLTemplate() } engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} } // SetFuncMap sets the FuncMap used for template.FuncMap. func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { engine.FuncMap = funcMap } // NoRoute adds handlers for NoRoute. It return a 404 code by default. func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.noRoute = handlers engine.rebuild404Handlers() } // NoMethod sets the handlers called when... TODO. func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.noMethod = handlers engine.rebuild405Handlers() } // Use attaches a global middleware to the router. ie. the middleware attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine } func (engine *Engine) rebuild404Handlers() { engine.allNoRoute = engine.combineHandlers(engine.noRoute) } func (engine *Engine) rebuild405Handlers() { engine.allNoMethod = engine.combineHandlers(engine.noMethod) } func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) root := engine.trees.get(method) if root == nil { root = new(node) root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) } // Routes returns a slice of registered routes, including some useful information, such as: // the http method, path and the handler name. func (engine *Engine) Routes() (routes RoutesInfo) { for _, tree := range engine.trees { routes = iterate("", tree.method, routes, tree.root) } return routes } func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { path += root.path if len(root.handlers) > 0 { handlerFunc := root.handlers.Last() routes = append(routes, RouteInfo{ Method: method, Path: path, Handler: nameOfFunction(handlerFunc), HandlerFunc: handlerFunc, }) } for _, child := range root.children { routes = iterate(path, method, routes, child) } return routes } // Run attaches the router to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine) return } // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { debugPrint("Listening and serving HTTPS on %s\n", addr) defer func() { debugPrintError(err) }() err = http.ListenAndServeTLS(addr, certFile, keyFile, engine) return } // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file). // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) RunUnix(file string) (err error) { debugPrint("Listening and serving HTTP on unix:/%s", file) defer func() { debugPrintError(err) }() listener, err := net.Listen("unix", file) if err != nil { return } defer listener.Close() defer os.Remove(file) err = http.Serve(listener, engine) return } // RunFd attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified file descriptor. // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) RunFd(fd int) (err error) { debugPrint("Listening and serving HTTP on fd@%d", fd) defer func() { debugPrintError(err) }() f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) listener, err := net.FileListener(f) if err != nil { return } defer listener.Close() err = engine.RunListener(listener) return } // RunListener attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified net.Listener func (engine *Engine) RunListener(listener net.Listener) (err error) { debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) defer func() { debugPrintError(err) }() err = http.Serve(listener, engine) return } // ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) c.writermem.reset(w) c.Request = req c.reset() engine.handleHTTPRequest(c) engine.pool.Put(c) } // HandleContext re-enter a context that has been rewritten. // This can be done by setting c.Request.URL.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { oldIndexValue := c.index c.reset() engine.handleHTTPRequest(c) c.index = oldIndexValue } func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method rPath := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } if engine.RemoveExtraSlash { rPath = cleanPath(rPath) } // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { if t[i].method != httpMethod { continue } root := t[i].root // Find route in tree value := root.getValue(rPath, c.Params, unescape) if value.handlers != nil { c.handlers = value.handlers c.Params = value.params c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && rPath != "/" { if value.tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } break } if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method == httpMethod { continue } if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) } var mimePlain = []string{MIMEPlain} func serveError(c *Context, code int, defaultMessage []byte) { c.writermem.status = code c.Next() if c.writermem.Written() { return } if c.writermem.Status() == code { c.writermem.Header()["Content-Type"] = mimePlain _, err := c.Writer.Write(defaultMessage) if err != nil { debugPrint("cannot write message to writer during serve error: %v", err) } return } c.writermem.WriteHeaderNow() } func redirectTrailingSlash(c *Context) { req := c.Request p := req.URL.Path if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { p = prefix + "/" + req.URL.Path } req.URL.Path = p + "/" if length := len(p); length > 1 && p[length-1] == '/' { req.URL.Path = p[:length-1] } redirectRequest(c) } func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request rPath := req.URL.Path if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok { req.URL.Path = bytesconv.BytesToString(fixedPath) redirectRequest(c) return true } return false } func redirectRequest(c *Context) { req := c.Request rPath := req.URL.Path rURL := req.URL.String() code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != http.MethodGet { code = http.StatusTemporaryRedirect } debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) http.Redirect(c.Writer, req, rURL, code) c.writermem.WriteHeaderNow() }