@@ -112,6 +112,15 @@ type Engine struct {
112112 // RedirectTrailingSlash is independent of this option.
113113 RedirectFixedPath bool
114114
115+ // TrailingSlashInsensitivity makes the router insensitive to trailing
116+ // slashes. It works like RedirectTrailingSlash, but instead of generating a
117+ // redirection response to the path with or without the trailing slash, it
118+ // will just go to the corresponding handler.
119+ //
120+ // Enabling this option will make RedirectTrailingSlash ineffective since
121+ // no redirection will be performed.
122+ TrailingSlashInsensitivity bool
123+
115124 // HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
116125 // current route, if the current request can not be routed.
117126 // If this is the case, the request is answered with 'Method Not Allowed'
@@ -184,12 +193,13 @@ var _ IRouter = (*Engine)(nil)
184193
185194// New returns a new blank Engine instance without any middleware attached.
186195// By default, the configuration is:
187- // - RedirectTrailingSlash: true
188- // - RedirectFixedPath: false
189- // - HandleMethodNotAllowed: false
190- // - ForwardedByClientIP: true
191- // - UseRawPath: false
192- // - UnescapePathValues: true
196+ // - RedirectTrailingSlash: true
197+ // - RedirectFixedPath: false
198+ // - TrailingSlashInsensitivity: false
199+ // - HandleMethodNotAllowed: false
200+ // - ForwardedByClientIP: true
201+ // - UseRawPath: false
202+ // - UnescapePathValues: true
193203func New (opts ... OptionFunc ) * Engine {
194204 debugPrintWARNINGNew ()
195205 engine := & Engine {
@@ -198,22 +208,23 @@ func New(opts ...OptionFunc) *Engine {
198208 basePath : "/" ,
199209 root : true ,
200210 },
201- FuncMap : template.FuncMap {},
202- RedirectTrailingSlash : true ,
203- RedirectFixedPath : false ,
204- HandleMethodNotAllowed : false ,
205- ForwardedByClientIP : true ,
206- RemoteIPHeaders : []string {"X-Forwarded-For" , "X-Real-IP" },
207- TrustedPlatform : defaultPlatform ,
208- UseRawPath : false ,
209- RemoveExtraSlash : false ,
210- UnescapePathValues : true ,
211- MaxMultipartMemory : defaultMultipartMemory ,
212- trees : make (methodTrees , 0 , 9 ),
213- delims : render.Delims {Left : "{{" , Right : "}}" },
214- secureJSONPrefix : "while(1);" ,
215- trustedProxies : []string {"0.0.0.0/0" , "::/0" },
216- trustedCIDRs : defaultTrustedCIDRs ,
211+ FuncMap : template.FuncMap {},
212+ RedirectTrailingSlash : true ,
213+ RedirectFixedPath : false ,
214+ TrailingSlashInsensitivity : false ,
215+ HandleMethodNotAllowed : false ,
216+ ForwardedByClientIP : true ,
217+ RemoteIPHeaders : []string {"X-Forwarded-For" , "X-Real-IP" },
218+ TrustedPlatform : defaultPlatform ,
219+ UseRawPath : false ,
220+ RemoveExtraSlash : false ,
221+ UnescapePathValues : true ,
222+ MaxMultipartMemory : defaultMultipartMemory ,
223+ trees : make (methodTrees , 0 , 9 ),
224+ delims : render.Delims {Left : "{{" , Right : "}}" },
225+ secureJSONPrefix : "while(1);" ,
226+ trustedProxies : []string {"0.0.0.0/0" , "::/0" },
227+ trustedCIDRs : defaultTrustedCIDRs ,
217228 }
218229 engine .engine = engine
219230 engine .pool .New = func () any {
@@ -691,6 +702,19 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
691702 return
692703 }
693704 if httpMethod != http .MethodConnect && rPath != "/" {
705+ // TrailingSlashInsensitivity has precedence over RedirectTrailingSlash.
706+ if value .tsr && engine .TrailingSlashInsensitivity {
707+ // Retry with the path with or without the trailing slash.
708+ // It should succeed because tsr is true.
709+ value = root .getValue (addOrRemoveTrailingSlash (rPath ), c .params , c .skippedNodes , unescape )
710+ if value .handlers != nil {
711+ c .handlers = value .handlers
712+ c .fullPath = value .fullPath
713+ c .Next ()
714+ c .writermem .WriteHeaderNow ()
715+ return
716+ }
717+ }
694718 if value .tsr && engine .RedirectTrailingSlash {
695719 redirectTrailingSlash (c )
696720 return
@@ -745,6 +769,14 @@ func serveError(c *Context, code int, defaultMessage []byte) {
745769 c .writermem .WriteHeaderNow ()
746770}
747771
772+ func addOrRemoveTrailingSlash (p string ) string {
773+ ret := p + "/"
774+ if length := len (p ); length > 1 && p [length - 1 ] == '/' {
775+ ret = p [:length - 1 ]
776+ }
777+ return ret
778+ }
779+
748780func redirectTrailingSlash (c * Context ) {
749781 req := c .Request
750782 p := req .URL .Path
@@ -754,10 +786,7 @@ func redirectTrailingSlash(c *Context) {
754786
755787 p = prefix + "/" + req .URL .Path
756788 }
757- req .URL .Path = p + "/"
758- if length := len (p ); length > 1 && p [length - 1 ] == '/' {
759- req .URL .Path = p [:length - 1 ]
760- }
789+ req .URL .Path = addOrRemoveTrailingSlash (p )
761790 redirectRequest (c )
762791}
763792
0 commit comments