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
|
6
|
if length := len(c); length > 0 {
|
38
|
6
|
return c[length-1]
|
39
|
|
}
|
40
|
6
|
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
|
6
|
debugPrintWARNINGNew()
|
131
|
6
|
engine := &Engine{
|
132
|
6
|
RouterGroup: RouterGroup{
|
133
|
6
|
Handlers: nil,
|
134
|
6
|
basePath: "/",
|
135
|
6
|
root: true,
|
136
|
6
|
},
|
137
|
6
|
FuncMap: template.FuncMap{},
|
138
|
6
|
RedirectTrailingSlash: true,
|
139
|
6
|
RedirectFixedPath: false,
|
140
|
6
|
HandleMethodNotAllowed: false,
|
141
|
6
|
ForwardedByClientIP: true,
|
142
|
6
|
AppEngine: defaultAppEngine,
|
143
|
6
|
UseRawPath: false,
|
144
|
6
|
RemoveExtraSlash: false,
|
145
|
6
|
UnescapePathValues: true,
|
146
|
6
|
MaxMultipartMemory: defaultMultipartMemory,
|
147
|
6
|
trees: make(methodTrees, 0, 9),
|
148
|
6
|
delims: render.Delims{Left: "{{", Right: "}}"},
|
149
|
6
|
secureJSONPrefix: "while(1);",
|
150
|
|
}
|
151
|
6
|
engine.RouterGroup.engine = engine
|
152
|
6
|
engine.pool.New = func() interface{} {
|
153
|
6
|
return engine.allocateContext()
|
154
|
|
}
|
155
|
6
|
return engine
|
156
|
|
}
|
157
|
|
|
158
|
|
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
159
|
|
func Default() *Engine {
|
160
|
6
|
debugPrintWARNINGDefault()
|
161
|
6
|
engine := New()
|
162
|
6
|
engine.Use(Logger(), Recovery())
|
163
|
6
|
return engine
|
164
|
|
}
|
165
|
|
|
166
|
|
func (engine *Engine) allocateContext() *Context {
|
167
|
6
|
v := make(Params, 0, engine.maxParams)
|
168
|
6
|
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
|
6
|
engine.delims = render.Delims{Left: left, Right: right}
|
174
|
6
|
return engine
|
175
|
|
}
|
176
|
|
|
177
|
|
// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
|
178
|
|
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
179
|
6
|
engine.secureJSONPrefix = prefix
|
180
|
6
|
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
|
6
|
left := engine.delims.Left
|
187
|
6
|
right := engine.delims.Right
|
188
|
6
|
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
189
|
|
|
190
|
6
|
if IsDebugging() {
|
191
|
6
|
debugPrintLoadTemplate(templ)
|
192
|
6
|
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
|
193
|
6
|
return
|
194
|
|
}
|
195
|
|
|
196
|
6
|
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
|
6
|
if IsDebugging() {
|
203
|
6
|
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
|
204
|
6
|
return
|
205
|
|
}
|
206
|
|
|
207
|
6
|
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
|
208
|
6
|
engine.SetHTMLTemplate(templ)
|
209
|
|
}
|
210
|
|
|
211
|
|
// SetHTMLTemplate associate a template with HTML renderer.
|
212
|
|
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
213
|
6
|
if len(engine.trees) > 0 {
|
214
|
6
|
debugPrintWARNINGSetHTMLTemplate()
|
215
|
|
}
|
216
|
|
|
217
|
6
|
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
|
6
|
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
|
6
|
engine.noRoute = handlers
|
228
|
6
|
engine.rebuild404Handlers()
|
229
|
|
}
|
230
|
|
|
231
|
|
// NoMethod sets the handlers called when... TODO.
|
232
|
|
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
233
|
6
|
engine.noMethod = handlers
|
234
|
6
|
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
|
6
|
engine.RouterGroup.Use(middleware...)
|
242
|
6
|
engine.rebuild404Handlers()
|
243
|
6
|
engine.rebuild405Handlers()
|
244
|
6
|
return engine
|
245
|
|
}
|
246
|
|
|
247
|
|
func (engine *Engine) rebuild404Handlers() {
|
248
|
6
|
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
249
|
|
}
|
250
|
|
|
251
|
|
func (engine *Engine) rebuild405Handlers() {
|
252
|
6
|
engine.allNoMethod = engine.combineHandlers(engine.noMethod)
|
253
|
|
}
|
254
|
|
|
255
|
|
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
256
|
6
|
assert1(path[0] == '/', "path must begin with '/'")
|
257
|
6
|
assert1(method != "", "HTTP method can not be empty")
|
258
|
6
|
assert1(len(handlers) > 0, "there must be at least one handler")
|
259
|
|
|
260
|
6
|
debugPrintRoute(method, path, handlers)
|
261
|
|
|
262
|
6
|
root := engine.trees.get(method)
|
263
|
6
|
if root == nil {
|
264
|
6
|
root = new(node)
|
265
|
6
|
root.fullPath = "/"
|
266
|
6
|
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
267
|
|
}
|
268
|
6
|
root.addRoute(path, handlers)
|
269
|
|
|
270
|
|
// Update maxParams
|
271
|
6
|
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
272
|
6
|
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
|
6
|
routes = iterate("", tree.method, routes, tree.root)
|
281
|
|
}
|
282
|
6
|
return routes
|
283
|
|
}
|
284
|
|
|
285
|
|
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
286
|
6
|
path += root.path
|
287
|
6
|
if len(root.handlers) > 0 {
|
288
|
6
|
handlerFunc := root.handlers.Last()
|
289
|
6
|
routes = append(routes, RouteInfo{
|
290
|
6
|
Method: method,
|
291
|
6
|
Path: path,
|
292
|
6
|
Handler: nameOfFunction(handlerFunc),
|
293
|
6
|
HandlerFunc: handlerFunc,
|
294
|
6
|
})
|
295
|
|
}
|
296
|
|
for _, child := range root.children {
|
297
|
6
|
routes = iterate(path, method, routes, child)
|
298
|
|
}
|
299
|
6
|
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
|
6
|
defer func() { debugPrintError(err) }()
|
307
|
|
|
308
|
6
|
address := resolveAddress(addr)
|
309
|
6
|
debugPrint("Listening and serving HTTP on %s\n", address)
|
310
|
6
|
err = http.ListenAndServe(address, engine)
|
311
|
6
|
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
|
6
|
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
319
|
6
|
defer func() { debugPrintError(err) }()
|
320
|
|
|
321
|
6
|
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
|
322
|
6
|
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
|
6
|
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
330
|
6
|
defer func() { debugPrintError(err) }()
|
331
|
|
|
332
|
6
|
listener, err := net.Listen("unix", file)
|
333
|
6
|
if err != nil {
|
334
|
6
|
return
|
335
|
|
}
|
336
|
6
|
defer listener.Close()
|
337
|
6
|
defer os.Remove(file)
|
338
|
|
|
339
|
6
|
err = http.Serve(listener, engine)
|
340
|
6
|
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
|
6
|
debugPrint("Listening and serving HTTP on fd@%d", fd)
|
348
|
6
|
defer func() { debugPrintError(err) }()
|
349
|
|
|
350
|
6
|
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
351
|
6
|
listener, err := net.FileListener(f)
|
352
|
6
|
if err != nil {
|
353
|
6
|
return
|
354
|
|
}
|
355
|
6
|
defer listener.Close()
|
356
|
6
|
err = engine.RunListener(listener)
|
357
|
6
|
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
|
6
|
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
|
364
|
6
|
defer func() { debugPrintError(err) }()
|
365
|
6
|
err = http.Serve(listener, engine)
|
366
|
6
|
return
|
367
|
|
}
|
368
|
|
|
369
|
|
// ServeHTTP conforms to the http.Handler interface.
|
370
|
|
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
371
|
6
|
c := engine.pool.Get().(*Context)
|
372
|
6
|
c.writermem.reset(w)
|
373
|
6
|
c.Request = req
|
374
|
6
|
c.reset()
|
375
|
|
|
376
|
6
|
engine.handleHTTPRequest(c)
|
377
|
|
|
378
|
6
|
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
|
6
|
oldIndexValue := c.index
|
386
|
6
|
c.reset()
|
387
|
6
|
engine.handleHTTPRequest(c)
|
388
|
|
|
389
|
6
|
c.index = oldIndexValue
|
390
|
|
}
|
391
|
|
|
392
|
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
393
|
6
|
httpMethod := c.Request.Method
|
394
|
6
|
rPath := c.Request.URL.Path
|
395
|
6
|
unescape := false
|
396
|
6
|
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
397
|
6
|
rPath = c.Request.URL.RawPath
|
398
|
6
|
unescape = engine.UnescapePathValues
|
399
|
|
}
|
400
|
|
|
401
|
6
|
if engine.RemoveExtraSlash {
|
402
|
6
|
rPath = cleanPath(rPath)
|
403
|
|
}
|
404
|
|
|
405
|
|
// Find root of the tree for the given HTTP method
|
406
|
6
|
t := engine.trees
|
407
|
|
for i, tl := 0, len(t); i < tl; i++ {
|
408
|
6
|
if t[i].method != httpMethod {
|
409
|
6
|
continue
|
410
|
|
}
|
411
|
6
|
root := t[i].root
|
412
|
|
// Find route in tree
|
413
|
6
|
value := root.getValue(rPath, c.params, unescape)
|
414
|
6
|
if value.params != nil {
|
415
|
6
|
c.Params = *value.params
|
416
|
|
}
|
417
|
6
|
if value.handlers != nil {
|
418
|
6
|
c.handlers = value.handlers
|
419
|
6
|
c.fullPath = value.fullPath
|
420
|
6
|
c.Next()
|
421
|
6
|
c.writermem.WriteHeaderNow()
|
422
|
6
|
return
|
423
|
|
}
|
424
|
6
|
if httpMethod != "CONNECT" && rPath != "/" {
|
425
|
6
|
if value.tsr && engine.RedirectTrailingSlash {
|
426
|
6
|
redirectTrailingSlash(c)
|
427
|
6
|
return
|
428
|
|
}
|
429
|
6
|
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
430
|
6
|
return
|
431
|
|
}
|
432
|
|
}
|
433
|
6
|
break
|
434
|
|
}
|
435
|
|
|
436
|
6
|
if engine.HandleMethodNotAllowed {
|
437
|
|
for _, tree := range engine.trees {
|
438
|
6
|
if tree.method == httpMethod {
|
439
|
6
|
continue
|
440
|
|
}
|
441
|
6
|
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
|
442
|
6
|
c.handlers = engine.allNoMethod
|
443
|
6
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
444
|
6
|
return
|
445
|
|
}
|
446
|
|
}
|
447
|
|
}
|
448
|
6
|
c.handlers = engine.allNoRoute
|
449
|
6
|
serveError(c, http.StatusNotFound, default404Body)
|
450
|
|
}
|
451
|
|
|
452
|
|
var mimePlain = []string{MIMEPlain}
|
453
|
|
|
454
|
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
455
|
6
|
c.writermem.status = code
|
456
|
6
|
c.Next()
|
457
|
6
|
if c.writermem.Written() {
|
458
|
6
|
return
|
459
|
|
}
|
460
|
6
|
if c.writermem.Status() == code {
|
461
|
6
|
c.writermem.Header()["Content-Type"] = mimePlain
|
462
|
6
|
_, err := c.Writer.Write(defaultMessage)
|
463
|
6
|
if err != nil {
|
464
|
0
|
debugPrint("cannot write message to writer during serve error: %v", err)
|
465
|
|
}
|
466
|
6
|
return
|
467
|
|
}
|
468
|
6
|
c.writermem.WriteHeaderNow()
|
469
|
|
}
|
470
|
|
|
471
|
|
func redirectTrailingSlash(c *Context) {
|
472
|
6
|
req := c.Request
|
473
|
6
|
p := req.URL.Path
|
474
|
6
|
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
|
475
|
6
|
p = prefix + "/" + req.URL.Path
|
476
|
|
}
|
477
|
6
|
req.URL.Path = p + "/"
|
478
|
6
|
if length := len(p); length > 1 && p[length-1] == '/' {
|
479
|
6
|
req.URL.Path = p[:length-1]
|
480
|
|
}
|
481
|
6
|
redirectRequest(c)
|
482
|
|
}
|
483
|
|
|
484
|
|
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
485
|
6
|
req := c.Request
|
486
|
6
|
rPath := req.URL.Path
|
487
|
|
|
488
|
6
|
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
|
489
|
6
|
req.URL.Path = bytesconv.BytesToString(fixedPath)
|
490
|
6
|
redirectRequest(c)
|
491
|
6
|
return true
|
492
|
|
}
|
493
|
6
|
return false
|
494
|
|
}
|
495
|
|
|
496
|
|
func redirectRequest(c *Context) {
|
497
|
6
|
req := c.Request
|
498
|
6
|
rPath := req.URL.Path
|
499
|
6
|
rURL := req.URL.String()
|
500
|
|
|
501
|
6
|
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
502
|
6
|
if req.Method != http.MethodGet {
|
503
|
6
|
code = http.StatusTemporaryRedirect
|
504
|
|
}
|
505
|
6
|
debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
|
506
|
6
|
http.Redirect(c.Writer, req, rURL, code)
|
507
|
6
|
c.writermem.WriteHeaderNow()
|
508
|
|
}
|