gin-gonic / gin
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
	"sync"
15

16
	"github.com/gin-gonic/gin/internal/bytesconv"
17
	"github.com/gin-gonic/gin/render"
18
)
19

20
const defaultMultipartMemory = 32 << 20 // 32 MB
21

22
var (
23
	default404Body = []byte("404 page not found")
24
	default405Body = []byte("405 method not allowed")
25
)
26

27
var defaultAppEngine bool
28

29
// HandlerFunc defines the handler used by gin middleware as return value.
30
type HandlerFunc func(*Context)
31

32
// HandlersChain defines a HandlerFunc array.
33
type HandlersChain []HandlerFunc
34

35
// Last returns the last handler in the chain. ie. the last handler is the main one.
36
func (c HandlersChain) Last() HandlerFunc {
37 100
	if length := len(c); length > 0 {
38 100
		return c[length-1]
39
	}
40 100
	return nil
41
}
42

43
// RouteInfo represents a request route's specification which contains method and path and its handler.
44
type RouteInfo struct {
45
	Method      string
46
	Path        string
47
	Handler     string
48
	HandlerFunc HandlerFunc
49
}
50

51
// RoutesInfo defines a RouteInfo array.
52
type RoutesInfo []RouteInfo
53

54
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
55
// Create an instance of Engine, by using New() or Default()
56
type Engine struct {
57
	RouterGroup
58

59
	// Enables automatic redirection if the current route can't be matched but a
60
	// handler for the path with (without) the trailing slash exists.
61
	// For example if /foo/ is requested but a route only exists for /foo, the
62
	// client is redirected to /foo with http status code 301 for GET requests
63
	// and 307 for all other request methods.
64
	RedirectTrailingSlash bool
65

66
	// If enabled, the router tries to fix the current request path, if no
67
	// handle is registered for it.
68
	// First superfluous path elements like ../ or // are removed.
69
	// Afterwards the router does a case-insensitive lookup of the cleaned path.
70
	// If a handle can be found for this route, the router makes a redirection
71
	// to the corrected path with status code 301 for GET requests and 307 for
72
	// all other request methods.
73
	// For example /FOO and /..//Foo could be redirected to /foo.
74
	// RedirectTrailingSlash is independent of this option.
75
	RedirectFixedPath bool
76

77
	// If enabled, the router checks if another method is allowed for the
78
	// current route, if the current request can not be routed.
79
	// If this is the case, the request is answered with 'Method Not Allowed'
80
	// and HTTP status code 405.
81
	// If no other Method is allowed, the request is delegated to the NotFound
82
	// handler.
83
	HandleMethodNotAllowed bool
84
	ForwardedByClientIP    bool
85

86
	// #726 #755 If enabled, it will thrust some headers starting with
87
	// 'X-AppEngine...' for better integration with that PaaS.
88
	AppEngine bool
89

90
	// If enabled, the url.RawPath will be used to find parameters.
91
	UseRawPath bool
92

93
	// If true, the path value will be unescaped.
94
	// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
95
	// as url.Path gonna be used, which is already unescaped.
96
	UnescapePathValues bool
97

98
	// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
99
	// method call.
100
	MaxMultipartMemory int64
101

102
	// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
103
	// See the PR #1817 and issue #1644
104
	RemoveExtraSlash bool
105

106
	delims           render.Delims
107
	secureJSONPrefix string
108
	HTMLRender       render.HTMLRender
109
	FuncMap          template.FuncMap
110
	allNoRoute       HandlersChain
111
	allNoMethod      HandlersChain
112
	noRoute          HandlersChain
113
	noMethod         HandlersChain
114
	pool             sync.Pool
115
	trees            methodTrees
116
	maxParams        uint16
117
}
118

119
var _ IRouter = &Engine{}
120

121
// New returns a new blank Engine instance without any middleware attached.
122
// By default the configuration is:
123
// - RedirectTrailingSlash:  true
124
// - RedirectFixedPath:      false
125
// - HandleMethodNotAllowed: false
126
// - ForwardedByClientIP:    true
127
// - UseRawPath:             false
128
// - UnescapePathValues:     true
129
func New() *Engine {
130 100
	debugPrintWARNINGNew()
131 100
	engine := &Engine{
132 100
		RouterGroup: RouterGroup{
133 100
			Handlers: nil,
134 100
			basePath: "/",
135 100
			root:     true,
136 100
		},
137 100
		FuncMap:                template.FuncMap{},
138 100
		RedirectTrailingSlash:  true,
139 100
		RedirectFixedPath:      false,
140 100
		HandleMethodNotAllowed: false,
141 100
		ForwardedByClientIP:    true,
142 100
		AppEngine:              defaultAppEngine,
143 100
		UseRawPath:             false,
144 100
		RemoveExtraSlash:       false,
145 100
		UnescapePathValues:     true,
146 100
		MaxMultipartMemory:     defaultMultipartMemory,
147 100
		trees:                  make(methodTrees, 0, 9),
148 100
		delims:                 render.Delims{Left: "{{", Right: "}}"},
149 100
		secureJSONPrefix:       "while(1);",
150
	}
151 100
	engine.RouterGroup.engine = engine
152 100
	engine.pool.New = func() interface{} {
153 100
		return engine.allocateContext()
154
	}
155 100
	return engine
156
}
157

158
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
159
func Default() *Engine {
160 100
	debugPrintWARNINGDefault()
161 100
	engine := New()
162 100
	engine.Use(Logger(), Recovery())
163 100
	return engine
164
}
165

166
func (engine *Engine) allocateContext() *Context {
167 100
	v := make(Params, 0, engine.maxParams)
168 100
	return &Context{engine: engine, params: &v}
169
}
170

171
// Delims sets template left and right delims and returns a Engine instance.
172
func (engine *Engine) Delims(left, right string) *Engine {
173 100
	engine.delims = render.Delims{Left: left, Right: right}
174 100
	return engine
175
}
176

177
// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
178
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
179 100
	engine.secureJSONPrefix = prefix
180 100
	return engine
181
}
182

183
// LoadHTMLGlob loads HTML files identified by glob pattern
184
// and associates the result with HTML renderer.
185
func (engine *Engine) LoadHTMLGlob(pattern string) {
186 100
	left := engine.delims.Left
187 100
	right := engine.delims.Right
188 100
	templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
189

190 100
	if IsDebugging() {
191 100
		debugPrintLoadTemplate(templ)
192 100
		engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
193 100
		return
194
	}
195

196 100
	engine.SetHTMLTemplate(templ)
197
}
198

199
// LoadHTMLFiles loads a slice of HTML files
200
// and associates the result with HTML renderer.
201
func (engine *Engine) LoadHTMLFiles(files ...string) {
202 100
	if IsDebugging() {
203 100
		engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
204 100
		return
205
	}
206

207 100
	templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
208 100
	engine.SetHTMLTemplate(templ)
209
}
210

211
// SetHTMLTemplate associate a template with HTML renderer.
212
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
213 100
	if len(engine.trees) > 0 {
214 100
		debugPrintWARNINGSetHTMLTemplate()
215
	}
216

217 100
	engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
218
}
219

220
// SetFuncMap sets the FuncMap used for template.FuncMap.
221
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
222 100
	engine.FuncMap = funcMap
223
}
224

225
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
226
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
227 100
	engine.noRoute = handlers
228 100
	engine.rebuild404Handlers()
229
}
230

231
// NoMethod sets the handlers called when... TODO.
232
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
233 100
	engine.noMethod = handlers
234 100
	engine.rebuild405Handlers()
235
}
236

237
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
238
// included in the handlers chain for every single request. Even 404, 405, static files...
239
// For example, this is the right place for a logger or error management middleware.
240
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
241 100
	engine.RouterGroup.Use(middleware...)
242 100
	engine.rebuild404Handlers()
243 100
	engine.rebuild405Handlers()
244 100
	return engine
245
}
246

247
func (engine *Engine) rebuild404Handlers() {
248 100
	engine.allNoRoute = engine.combineHandlers(engine.noRoute)
249
}
250

251
func (engine *Engine) rebuild405Handlers() {
252 100
	engine.allNoMethod = engine.combineHandlers(engine.noMethod)
253
}
254

255
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
256 100
	assert1(path[0] == '/', "path must begin with '/'")
257 100
	assert1(method != "", "HTTP method can not be empty")
258 100
	assert1(len(handlers) > 0, "there must be at least one handler")
259

260 100
	debugPrintRoute(method, path, handlers)
261

262 100
	root := engine.trees.get(method)
263 100
	if root == nil {
264 100
		root = new(node)
265 100
		root.fullPath = "/"
266 100
		engine.trees = append(engine.trees, methodTree{method: method, root: root})
267
	}
268 100
	root.addRoute(path, handlers)
269

270
	// Update maxParams
271 100
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
272 100
		engine.maxParams = paramsCount
273
	}
274
}
275

276
// Routes returns a slice of registered routes, including some useful information, such as:
277
// the http method, path and the handler name.
278
func (engine *Engine) Routes() (routes RoutesInfo) {
279
	for _, tree := range engine.trees {
280 100
		routes = iterate("", tree.method, routes, tree.root)
281
	}
282 100
	return routes
283
}
284

285
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
286 100
	path += root.path
287 100
	if len(root.handlers) > 0 {
288 100
		handlerFunc := root.handlers.Last()
289 100
		routes = append(routes, RouteInfo{
290 100
			Method:      method,
291 100
			Path:        path,
292 100
			Handler:     nameOfFunction(handlerFunc),
293 100
			HandlerFunc: handlerFunc,
294 100
		})
295
	}
296
	for _, child := range root.children {
297 100
		routes = iterate(path, method, routes, child)
298
	}
299 100
	return routes
300
}
301

302
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
303
// It is a shortcut for http.ListenAndServe(addr, router)
304
// Note: this method will block the calling goroutine indefinitely unless an error happens.
305
func (engine *Engine) Run(addr ...string) (err error) {
306 100
	defer func() { debugPrintError(err) }()
307

308 100
	address := resolveAddress(addr)
309 100
	debugPrint("Listening and serving HTTP on %s\n", address)
310 100
	err = http.ListenAndServe(address, engine)
311 100
	return
312
}
313

314
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
315
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
316
// Note: this method will block the calling goroutine indefinitely unless an error happens.
317
func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
318 100
	debugPrint("Listening and serving HTTPS on %s\n", addr)
319 100
	defer func() { debugPrintError(err) }()
320

321 100
	err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
322 100
	return
323
}
324

325
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
326
// through the specified unix socket (ie. a file).
327
// Note: this method will block the calling goroutine indefinitely unless an error happens.
328
func (engine *Engine) RunUnix(file string) (err error) {
329 100
	debugPrint("Listening and serving HTTP on unix:/%s", file)
330 100
	defer func() { debugPrintError(err) }()
331

332 100
	listener, err := net.Listen("unix", file)
333 100
	if err != nil {
334 100
		return
335
	}
336 100
	defer listener.Close()
337 100
	defer os.Remove(file)
338

339 100
	err = http.Serve(listener, engine)
340 100
	return
341
}
342

343
// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
344
// through the specified file descriptor.
345
// Note: this method will block the calling goroutine indefinitely unless an error happens.
346
func (engine *Engine) RunFd(fd int) (err error) {
347 100
	debugPrint("Listening and serving HTTP on fd@%d", fd)
348 100
	defer func() { debugPrintError(err) }()
349

350 100
	f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
351 100
	listener, err := net.FileListener(f)
352 100
	if err != nil {
353 100
		return
354
	}
355 100
	defer listener.Close()
356 100
	err = engine.RunListener(listener)
357 100
	return
358
}
359

360
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
361
// through the specified net.Listener
362
func (engine *Engine) RunListener(listener net.Listener) (err error) {
363 100
	debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
364 100
	defer func() { debugPrintError(err) }()
365 100
	err = http.Serve(listener, engine)
366 100
	return
367
}
368

369
// ServeHTTP conforms to the http.Handler interface.
370
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
371 100
	c := engine.pool.Get().(*Context)
372 100
	c.writermem.reset(w)
373 100
	c.Request = req
374 100
	c.reset()
375

376 100
	engine.handleHTTPRequest(c)
377

378 100
	engine.pool.Put(c)
379
}
380

381
// HandleContext re-enter a context that has been rewritten.
382
// This can be done by setting c.Request.URL.Path to your new target.
383
// Disclaimer: You can loop yourself to death with this, use wisely.
384
func (engine *Engine) HandleContext(c *Context) {
385 100
	oldIndexValue := c.index
386 100
	c.reset()
387 100
	engine.handleHTTPRequest(c)
388

389 100
	c.index = oldIndexValue
390
}
391

392
func (engine *Engine) handleHTTPRequest(c *Context) {
393 100
	httpMethod := c.Request.Method
394 100
	rPath := c.Request.URL.Path
395 100
	unescape := false
396 100
	if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
397 100
		rPath = c.Request.URL.RawPath
398 100
		unescape = engine.UnescapePathValues
399
	}
400

401 100
	if engine.RemoveExtraSlash {
402 100
		rPath = cleanPath(rPath)
403
	}
404

405
	// Find root of the tree for the given HTTP method
406 100
	t := engine.trees
407
	for i, tl := 0, len(t); i < tl; i++ {
408 100
		if t[i].method != httpMethod {
409 100
			continue
410
		}
411 100
		root := t[i].root
412
		// Find route in tree
413 100
		value := root.getValue(rPath, c.params, unescape)
414 100
		if value.params != nil {
415 100
			c.Params = *value.params
416
		}
417 100
		if value.handlers != nil {
418 100
			c.handlers = value.handlers
419 100
			c.fullPath = value.fullPath
420 100
			c.Next()
421 100
			c.writermem.WriteHeaderNow()
422 100
			return
423
		}
424 100
		if httpMethod != "CONNECT" && rPath != "/" {
425 100
			if value.tsr && engine.RedirectTrailingSlash {
426 100
				redirectTrailingSlash(c)
427 100
				return
428
			}
429 100
			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
430 100
				return
431
			}
432
		}
433 100
		break
434
	}
435

436 100
	if engine.HandleMethodNotAllowed {
437
		for _, tree := range engine.trees {
438 100
			if tree.method == httpMethod {
439 100
				continue
440
			}
441 100
			if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
442 100
				c.handlers = engine.allNoMethod
443 100
				serveError(c, http.StatusMethodNotAllowed, default405Body)
444 100
				return
445
			}
446
		}
447
	}
448 100
	c.handlers = engine.allNoRoute
449 100
	serveError(c, http.StatusNotFound, default404Body)
450
}
451

452
var mimePlain = []string{MIMEPlain}
453

454
func serveError(c *Context, code int, defaultMessage []byte) {
455 100
	c.writermem.status = code
456 100
	c.Next()
457 100
	if c.writermem.Written() {
458 100
		return
459
	}
460 100
	if c.writermem.Status() == code {
461 100
		c.writermem.Header()["Content-Type"] = mimePlain
462 100
		_, err := c.Writer.Write(defaultMessage)
463 100
		if err != nil {
464 0
			debugPrint("cannot write message to writer during serve error: %v", err)
465
		}
466 100
		return
467
	}
468 100
	c.writermem.WriteHeaderNow()
469
}
470

471
func redirectTrailingSlash(c *Context) {
472 100
	req := c.Request
473 100
	p := req.URL.Path
474 100
	if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
475 100
		p = prefix + "/" + req.URL.Path
476
	}
477 100
	req.URL.Path = p + "/"
478 100
	if length := len(p); length > 1 && p[length-1] == '/' {
479 100
		req.URL.Path = p[:length-1]
480
	}
481 100
	redirectRequest(c)
482
}
483

484
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
485 100
	req := c.Request
486 100
	rPath := req.URL.Path
487

488 100
	if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
489 100
		req.URL.Path = bytesconv.BytesToString(fixedPath)
490 100
		redirectRequest(c)
491 100
		return true
492
	}
493 100
	return false
494
}
495

496
func redirectRequest(c *Context) {
497 100
	req := c.Request
498 100
	rPath := req.URL.Path
499 100
	rURL := req.URL.String()
500

501 100
	code := http.StatusMovedPermanently // Permanent redirect, request with GET method
502 100
	if req.Method != http.MethodGet {
503 100
		code = http.StatusTemporaryRedirect
504
	}
505 100
	debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
506 100
	http.Redirect(c.Writer, req, rURL, code)
507 100
	c.writermem.WriteHeaderNow()
508
}

Read our documentation on viewing source code .

Loading