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 151
	if length := len(c); length > 0 {
38 151
		return c[length-1]
39
	}
40 151
	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 151
	debugPrintWARNINGNew()
131 151
	engine := &Engine{
132 151
		RouterGroup: RouterGroup{
133 151
			Handlers: nil,
134 151
			basePath: "/",
135 151
			root:     true,
136 151
		},
137 151
		FuncMap:                template.FuncMap{},
138 151
		RedirectTrailingSlash:  true,
139 151
		RedirectFixedPath:      false,
140 151
		HandleMethodNotAllowed: false,
141 151
		ForwardedByClientIP:    true,
142 151
		AppEngine:              defaultAppEngine,
143 151
		UseRawPath:             false,
144 151
		RemoveExtraSlash:       false,
145 151
		UnescapePathValues:     true,
146 151
		MaxMultipartMemory:     defaultMultipartMemory,
147 151
		trees:                  make(methodTrees, 0, 9),
148 151
		delims:                 render.Delims{Left: "{{", Right: "}}"},
149 151
		secureJSONPrefix:       "while(1);",
150
	}
151 151
	engine.RouterGroup.engine = engine
152 151
	engine.pool.New = func() interface{} {
153 151
		return engine.allocateContext()
154
	}
155 151
	return engine
156
}
157

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

166
func (engine *Engine) allocateContext() *Context {
167 151
	v := make(Params, 0, engine.maxParams)
168 151
	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 151
	engine.delims = render.Delims{Left: left, Right: right}
174 151
	return engine
175
}
176

177
// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
178
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
179 151
	engine.secureJSONPrefix = prefix
180 151
	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 151
	left := engine.delims.Left
187 151
	right := engine.delims.Right
188 151
	templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
189

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

196 151
	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 151
	if IsDebugging() {
203 151
		engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
204 151
		return
205
	}
206

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

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

217 151
	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 151
	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 151
	engine.noRoute = handlers
228 151
	engine.rebuild404Handlers()
229
}
230

231
// NoMethod sets the handlers called when... TODO.
232
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
233 151
	engine.noMethod = handlers
234 151
	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 151
	engine.RouterGroup.Use(middleware...)
242 151
	engine.rebuild404Handlers()
243 151
	engine.rebuild405Handlers()
244 151
	return engine
245
}
246

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

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

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

260 151
	debugPrintRoute(method, path, handlers)
261

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

270
	// Update maxParams
271 151
	if paramsCount := countParams(path); paramsCount > engine.maxParams {
272 151
		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 151
		routes = iterate("", tree.method, routes, tree.root)
281
	}
282 151
	return routes
283
}
284

285
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
286 151
	path += root.path
287 151
	if len(root.handlers) > 0 {
288 151
		handlerFunc := root.handlers.Last()
289 151
		routes = append(routes, RouteInfo{
290 151
			Method:      method,
291 151
			Path:        path,
292 151
			Handler:     nameOfFunction(handlerFunc),
293 151
			HandlerFunc: handlerFunc,
294 151
		})
295
	}
296
	for _, child := range root.children {
297 151
		routes = iterate(path, method, routes, child)
298
	}
299 151
	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 151
	defer func() { debugPrintError(err) }()
307

308 151
	address := resolveAddress(addr)
309 151
	debugPrint("Listening and serving HTTP on %s\n", address)
310 151
	err = http.ListenAndServe(address, engine)
311 151
	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 151
	debugPrint("Listening and serving HTTPS on %s\n", addr)
319 151
	defer func() { debugPrintError(err) }()
320

321 151
	err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
322 151
	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 151
	debugPrint("Listening and serving HTTP on unix:/%s", file)
330 151
	defer func() { debugPrintError(err) }()
331

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

339 151
	err = http.Serve(listener, engine)
340 151
	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 151
	debugPrint("Listening and serving HTTP on fd@%d", fd)
348 151
	defer func() { debugPrintError(err) }()
349

350 151
	f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
351 151
	listener, err := net.FileListener(f)
352 151
	if err != nil {
353 151
		return
354
	}
355 151
	defer listener.Close()
356 151
	err = engine.RunListener(listener)
357 151
	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 151
	debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
364 151
	defer func() { debugPrintError(err) }()
365 151
	err = http.Serve(listener, engine)
366 151
	return
367
}
368

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

376 151
	engine.handleHTTPRequest(c)
377

378 151
	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 151
	oldIndexValue := c.index
386 151
	c.reset()
387 151
	engine.handleHTTPRequest(c)
388

389 151
	c.index = oldIndexValue
390
}
391

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

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

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

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

452
var mimePlain = []string{MIMEPlain}
453

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

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

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

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

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

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

Read our documentation on viewing source code .

Loading