// 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 ( "html/template" "net" "net/http" "os" "sync" "github.com/gin-gonic/gin/render" ) const Version = "v1.0rc2" var default404Body = []byte("404 page not found") var default405Body = []byte("405 method not allowed") type ( HandlerFunc func(*Context) HandlersChain []HandlerFunc // Represents the web framework, it wraps the blazing fast httprouter multiplexer and a list of global middlewares. Engine struct { RouterGroup HTMLRender render.HTMLRender allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain pool sync.Pool trees methodTrees // 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 } ) // Returns a new blank Engine instance without any middleware attached. // The most basic configuration func New() *Engine { debugPrintWARNING() engine := &Engine{ RouterGroup: RouterGroup{ Handlers: nil, BasePath: "/", root: true, }, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, trees: make(methodTrees, 0, 9), } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { return engine.allocateContext() } return engine } // Returns a Engine instance with the Logger and Recovery already attached. func Default() *Engine { engine := New() engine.Use(Recovery(), Logger()) return engine } func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} } func (engine *Engine) LoadHTMLGlob(pattern string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Glob: pattern} } else { templ := template.Must(template.ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } } func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Files: files} } else { templ := template.Must(template.ParseFiles(files...)) engine.SetHTMLTemplate(templ) } } func (engine *Engine) SetHTMLTemplate(templ *template.Template) { engine.HTMLRender = render.HTMLProduction{Template: templ} } // Adds handlers for NoRoute. It return a 404 code by default. func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.noRoute = handlers engine.rebuild404Handlers() } // Sets the handlers called when... TODO func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.noMethod = handlers engine.rebuild405Handlers() } // Attachs a global middleware to the router. ie. the middlewares 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(middlewares ...HandlerFunc) routesInterface { engine.RouterGroup.Use(middlewares...) 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) { debugPrintRoute(method, path, handlers) if path[0] != '/' { panic("path must begin with '/'") } if method == "" { panic("HTTP method can not be empty") } if len(handlers) == 0 { panic("there must be at least one handler") } root := engine.trees.get(method) if root == nil { root = new(node) engine.trees = append(engine.trees, methodTree{ method: method, root: root, }) } root.addRoute(path, handlers) } // The router is attached 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 undefinitelly unless an error happens. func (engine *Engine) Run(addr string) (err error) { debugPrint("Listening and serving HTTP on %s\n", addr) defer func() { debugPrintError(err) }() err = http.ListenAndServe(addr, engine) return } // The router is attached to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine undefinitelly unless an error happens. func (engine *Engine) RunTLS(addr string, certFile string, 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 } // The router is attached 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 undefinitelly 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) }() os.Remove(file) listener, err := net.Listen("unix", file) if err != nil { return } defer listener.Close() err = http.Serve(listener, engine) return } // 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) } func (engine *Engine) handleHTTPRequest(context *Context) { httpMethod := context.Request.Method path := context.Request.URL.Path // 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 { root := t[i].root // Find route in tree handlers, params, tsr := root.getValue(path, context.Params) if handlers != nil { context.handlers = handlers context.Params = params context.Next() context.writermem.WriteHeaderNow() return } else if httpMethod != "CONNECT" && path != "/" { if tsr && engine.RedirectFixedPath { redirectTrailingSlash(context) return } if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) { return } } break } } // TODO: unit test if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method != httpMethod { if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil { context.handlers = engine.allNoMethod serveError(context, 405, default405Body) return } } } } context.handlers = engine.allNoRoute serveError(context, 404, default404Body) } var mimePlain = []string{MIMEPlain} func serveError(c *Context, code int, defaultMessage []byte) { c.writermem.status = code c.Next() if !c.writermem.Written() { if c.writermem.Status() == code { c.writermem.Header()["Content-Type"] = mimePlain c.Writer.Write(defaultMessage) } else { c.writermem.WriteHeaderNow() } } } func redirectTrailingSlash(c *Context) { req := c.Request path := req.URL.Path code := 301 // Permanent redirect, request with GET method if req.Method != "GET" { code = 307 } if len(path) > 1 && path[len(path)-1] == '/' { req.URL.Path = path[:len(path)-1] } else { req.URL.Path = path + "/" } debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) c.writermem.WriteHeaderNow() } func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request path := req.URL.Path fixedPath, found := root.findCaseInsensitivePath( cleanPath(path), trailingSlash, ) if found { code := 301 // Permanent redirect, request with GET method if req.Method != "GET" { code = 307 } req.URL.Path = string(fixedPath) debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) c.writermem.WriteHeaderNow() return true } return false }