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.
14 "github.com/gin-gonic/gin/render"
18 // Version is Framework's version.
20 defaultMultipartMemory = 32 << 20 // 32 MB
24 default404Body = []byte("404 page not found")
25 default405Body = []byte("405 method not allowed")
29 type HandlerFunc func(*Context)
30 type HandlersChain []HandlerFunc
32 // Last returns the last handler in the chain. ie. the last handler is the main own.
33 func (c HandlersChain) Last() HandlerFunc {
34 if length := len(c); length > 0 {
40 type RouteInfo struct {
46 type RoutesInfo []RouteInfo
48 // Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
49 // Create an instance of Engine, by using New() or Default()
53 // Enables automatic redirection if the current route can't be matched but a
54 // handler for the path with (without) the trailing slash exists.
55 // For example if /foo/ is requested but a route only exists for /foo, the
56 // client is redirected to /foo with http status code 301 for GET requests
57 // and 307 for all other request methods.
58 RedirectTrailingSlash bool
60 // If enabled, the router tries to fix the current request path, if no
61 // handle is registered for it.
62 // First superfluous path elements like ../ or // are removed.
63 // Afterwards the router does a case-insensitive lookup of the cleaned path.
64 // If a handle can be found for this route, the router makes a redirection
65 // to the corrected path with status code 301 for GET requests and 307 for
66 // all other request methods.
67 // For example /FOO and /..//Foo could be redirected to /foo.
68 // RedirectTrailingSlash is independent of this option.
69 RedirectFixedPath bool
71 // If enabled, the router checks if another method is allowed for the
72 // current route, if the current request can not be routed.
73 // If this is the case, the request is answered with 'Method Not Allowed'
74 // and HTTP status code 405.
75 // If no other Method is allowed, the request is delegated to the NotFound
77 HandleMethodNotAllowed bool
78 ForwardedByClientIP bool
80 // #726 #755 If enabled, it will thrust some headers starting with
81 // 'X-AppEngine...' for better integration with that PaaS.
84 // If enabled, the url.RawPath will be used to find parameters.
87 // If true, the path value will be unescaped.
88 // If UseRawPath is false (by default), the UnescapePathValues effectively is true,
89 // as url.Path gonna be used, which is already unescaped.
90 UnescapePathValues bool
92 // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
94 MaxMultipartMemory int64
97 secureJsonPrefix string
98 HTMLRender render.HTMLRender
99 FuncMap template.FuncMap
100 allNoRoute HandlersChain
101 allNoMethod HandlersChain
102 noRoute HandlersChain
103 noMethod HandlersChain
108 var _ IRouter = &Engine{}
110 // New returns a new blank Engine instance without any middleware attached.
111 // By default the configuration is:
112 // - RedirectTrailingSlash: true
113 // - RedirectFixedPath: false
114 // - HandleMethodNotAllowed: false
115 // - ForwardedByClientIP: true
116 // - UseRawPath: false
117 // - UnescapePathValues: true
119 debugPrintWARNINGNew()
121 RouterGroup: RouterGroup{
126 FuncMap: template.FuncMap{},
127 RedirectTrailingSlash: true,
128 RedirectFixedPath: false,
129 HandleMethodNotAllowed: false,
130 ForwardedByClientIP: true,
131 AppEngine: defaultAppEngine,
133 UnescapePathValues: true,
134 MaxMultipartMemory: defaultMultipartMemory,
135 trees: make(methodTrees, 0, 9),
136 delims: render.Delims{Left: "{{", Right: "}}"},
137 secureJsonPrefix: "while(1);",
139 engine.RouterGroup.engine = engine
140 engine.pool.New = func() interface{} {
141 return engine.allocateContext()
146 // Default returns an Engine instance with the Logger and Recovery middleware already attached.
147 func Default() *Engine {
148 debugPrintWARNINGDefault()
150 engine.Use(Logger(), Recovery())
154 func (engine *Engine) allocateContext() *Context {
155 return &Context{engine: engine}
158 func (engine *Engine) Delims(left, right string) *Engine {
159 engine.delims = render.Delims{Left: left, Right: right}
163 // SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
164 func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
165 engine.secureJsonPrefix = prefix
169 // LoadHTMLGlob loads HTML files identified by glob pattern
170 // and associates the result with HTML renderer.
171 func (engine *Engine) LoadHTMLGlob(pattern string) {
172 left := engine.delims.Left
173 right := engine.delims.Right
174 templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
177 debugPrintLoadTemplate(templ)
178 engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
182 engine.SetHTMLTemplate(templ)
185 // LoadHTMLFiles loads a slice of HTML files
186 // and associates the result with HTML renderer.
187 func (engine *Engine) LoadHTMLFiles(files ...string) {
189 engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
193 templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
194 engine.SetHTMLTemplate(templ)
197 // SetHTMLTemplate associate a template with HTML renderer.
198 func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
199 if len(engine.trees) > 0 {
200 debugPrintWARNINGSetHTMLTemplate()
203 engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
206 // SetFuncMap sets the FuncMap used for template.FuncMap.
207 func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
208 engine.FuncMap = funcMap
211 // NoRoute adds handlers for NoRoute. It return a 404 code by default.
212 func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
213 engine.noRoute = handlers
214 engine.rebuild404Handlers()
217 // NoMethod sets the handlers called when... TODO.
218 func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
219 engine.noMethod = handlers
220 engine.rebuild405Handlers()
223 // Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
224 // included in the handlers chain for every single request. Even 404, 405, static files...
225 // For example, this is the right place for a logger or error management middleware.
226 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
227 engine.RouterGroup.Use(middleware...)
228 engine.rebuild404Handlers()
229 engine.rebuild405Handlers()
233 func (engine *Engine) rebuild404Handlers() {
234 engine.allNoRoute = engine.combineHandlers(engine.noRoute)
237 func (engine *Engine) rebuild405Handlers() {
238 engine.allNoMethod = engine.combineHandlers(engine.noMethod)
241 func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
242 assert1(path[0] == '/', "path must begin with '/'")
243 assert1(method != "", "HTTP method can not be empty")
244 assert1(len(handlers) > 0, "there must be at least one handler")
246 debugPrintRoute(method, path, handlers)
247 root := engine.trees.get(method)
250 engine.trees = append(engine.trees, methodTree{method: method, root: root})
252 root.addRoute(path, handlers)
255 // Routes returns a slice of registered routes, including some useful information, such as:
256 // the http method, path and the handler name.
257 func (engine *Engine) Routes() (routes RoutesInfo) {
258 for _, tree := range engine.trees {
259 routes = iterate("", tree.method, routes, tree.root)
264 func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
266 if len(root.handlers) > 0 {
267 routes = append(routes, RouteInfo{
270 Handler: nameOfFunction(root.handlers.Last()),
273 for _, child := range root.children {
274 routes = iterate(path, method, routes, child)
279 // Run attaches the router to a http.Server and starts listening and serving HTTP requests.
280 // It is a shortcut for http.ListenAndServe(addr, router)
281 // Note: this method will block the calling goroutine indefinitely unless an error happens.
282 func (engine *Engine) Run(addr ...string) (err error) {
283 defer func() { debugPrintError(err) }()
285 address := resolveAddress(addr)
286 debugPrint("Listening and serving HTTP on %s\n", address)
287 err = http.ListenAndServe(address, engine)
291 // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
292 // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
293 // Note: this method will block the calling goroutine indefinitely unless an error happens.
294 func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
295 debugPrint("Listening and serving HTTPS on %s\n", addr)
296 defer func() { debugPrintError(err) }()
298 err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
302 // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
303 // through the specified unix socket (ie. a file).
304 // Note: this method will block the calling goroutine indefinitely unless an error happens.
305 func (engine *Engine) RunUnix(file string) (err error) {
306 debugPrint("Listening and serving HTTP on unix:/%s", file)
307 defer func() { debugPrintError(err) }()
310 listener, err := net.Listen("unix", file)
314 defer listener.Close()
315 err = http.Serve(listener, engine)
319 // ServeHTTP conforms to the http.Handler interface.
320 func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
321 c := engine.pool.Get().(*Context)
326 engine.handleHTTPRequest(c)
331 // HandleContext re-enter a context that has been rewritten.
332 // This can be done by setting c.Request.URL.Path to your new target.
333 // Disclaimer: You can loop yourself to death with this, use wisely.
334 func (engine *Engine) HandleContext(c *Context) {
336 engine.handleHTTPRequest(c)
340 func (engine *Engine) handleHTTPRequest(c *Context) {
341 httpMethod := c.Request.Method
342 path := c.Request.URL.Path
344 if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
345 path = c.Request.URL.RawPath
346 unescape = engine.UnescapePathValues
349 // Find root of the tree for the given HTTP method
351 for i, tl := 0, len(t); i < tl; i++ {
352 if t[i].method != httpMethod {
356 // Find route in tree
357 handlers, params, tsr := root.getValue(path, c.Params, unescape)
359 c.handlers = handlers
362 c.writermem.WriteHeaderNow()
365 if httpMethod != "CONNECT" && path != "/" {
366 if tsr && engine.RedirectTrailingSlash {
367 redirectTrailingSlash(c)
370 if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
377 if engine.HandleMethodNotAllowed {
378 for _, tree := range engine.trees {
379 if tree.method == httpMethod {
382 if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
383 c.handlers = engine.allNoMethod
384 serveError(c, http.StatusMethodNotAllowed, default405Body)
389 c.handlers = engine.allNoRoute
390 serveError(c, http.StatusNotFound, default404Body)
393 var mimePlain = []string{MIMEPlain}
395 func serveError(c *Context, code int, defaultMessage []byte) {
396 c.writermem.status = code
398 if c.writermem.Written() {
401 if c.writermem.Status() == code {
402 c.writermem.Header()["Content-Type"] = mimePlain
403 c.Writer.Write(defaultMessage)
406 c.writermem.WriteHeaderNow()
410 func redirectTrailingSlash(c *Context) {
413 code := http.StatusMovedPermanently // Permanent redirect, request with GET method
414 if req.Method != "GET" {
415 code = http.StatusTemporaryRedirect
418 req.URL.Path = path + "/"
419 if length := len(path); length > 1 && path[length-1] == '/' {
420 req.URL.Path = path[:length-1]
422 debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
423 http.Redirect(c.Writer, req, req.URL.String(), code)
424 c.writermem.WriteHeaderNow()
427 func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
431 if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok {
432 code := http.StatusMovedPermanently // Permanent redirect, request with GET method
433 if req.Method != "GET" {
434 code = http.StatusTemporaryRedirect
436 req.URL.Path = string(fixedPath)
437 debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
438 http.Redirect(c.Writer, req, req.URL.String(), code)
439 c.writermem.WriteHeaderNow()