@@ -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