Skip to content

Commit 95d86d8

Browse files
julienstraefiker
authored andcommitted
Add keepTrailingSlash option
1 parent 70fa42a commit 95d86d8

File tree

6 files changed

+100
-2
lines changed

6 files changed

+100
-2
lines changed

configuration/configuration.go

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ type GlobalConfiguration struct {
8787
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
8888
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
8989
AllowMinWeightZero bool `description:"Allow weight to take 0 as minimum real value." export:"true"` // Deprecated
90+
KeepTrailingSlash bool `description:"Do not remove trailing slash." export:"true"` // Deprecated
9091
Web *WebCompatibility `description:"(Deprecated) Enable Web backend with default settings" export:"true"` // Deprecated
9192
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
9293
File *file.Provider `description:"Enable File backend with default settings" export:"true"`

docs/configuration/commons.md

+26
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@
3333
#
3434
# checkNewVersion = false
3535

36+
# Tells traefik whether it should keep the trailing slashes in the paths (e.g. /paths/) or redirect to the no trailing slash paths instead (/paths).
37+
#
38+
# Optional
39+
# Default: false
40+
#
41+
# keepTrailingSlash = false
42+
3643
# Providers throttle duration.
3744
#
3845
# Optional
@@ -103,6 +110,25 @@ If you encounter 'too many open files' errors, you can either increase this valu
103110
- `defaultEntryPoints`: Entrypoints to be used by frontends that do not specify any entrypoint.
104111
Each frontend can specify its own entrypoints.
105112

113+
- `keepTrailingSlash`: Tells Træfik whether it should keep the trailing slashes that might be present in the paths of incoming requests (true), or if it should redirect to the slashless version of the URL (default behavior: false)
114+
115+
!!! note
116+
Beware that the value of `keepTrailingSlash` can have a significant impact on the way your frontend rules are interpreted.
117+
The table below tries to sum up several behaviors depending on requests/configurations.
118+
The current default behavior is deprecated and kept for compatibility reasons.
119+
As a consequence, we encourage you to set `keepTrailingSlash` to true.
120+
121+
| Incoming request | keepTrailingSlash | Path:{value} | Behavior
122+
|----------------------|-------------------|--------------|----------------------------|
123+
| http://foo.com/path/ | false | Path:/path/ | Proceeds with the request |
124+
| http://foo.com/path/ | false | Path:/path | 301 to http://foo.com/path |
125+
| http://foo.com/path | false | Path:/path/ | Proceeds with the request |
126+
| http://foo.com/path | false | Path:/path | Proceeds with the request |
127+
| http://foo.com/path/ | true | Path:/path/ | Proceeds with the request |
128+
| http://foo.com/path/ | true | Path:/path | 404 |
129+
| http://foo.com/path | true | Path:/path/ | 404 |
130+
| http://foo.com/path | true | Path:/path | Proceeds with the request |
131+
106132

107133
## Constraints
108134

integration/basic_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,51 @@ func (s *SimpleSuite) TestMultipleProviderSameBackendName(c *check.C) {
396396
c.Assert(err, checker.IsNil)
397397

398398
}
399+
400+
func (s *SimpleSuite) TestDontKeepTrailingSlash(c *check.C) {
401+
file := s.adaptFile(c, "fixtures/keep_trailing_slash.toml", struct {
402+
KeepTrailingSlash bool
403+
}{false})
404+
defer os.Remove(file)
405+
406+
cmd, output := s.traefikCmd(withConfigFile(file))
407+
defer output(c)
408+
409+
err := cmd.Start()
410+
c.Assert(err, checker.IsNil)
411+
defer cmd.Process.Kill()
412+
413+
oldCheckRedirect := http.DefaultClient.CheckRedirect
414+
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
415+
return http.ErrUseLastResponse
416+
}
417+
418+
err = try.GetRequest("http://127.0.0.1:8000/test/foo/", 1*time.Second, try.StatusCodeIs(http.StatusMovedPermanently))
419+
c.Assert(err, checker.IsNil)
420+
421+
http.DefaultClient.CheckRedirect = oldCheckRedirect
422+
}
423+
424+
func (s *SimpleSuite) TestKeepTrailingSlash(c *check.C) {
425+
file := s.adaptFile(c, "fixtures/keep_trailing_slash.toml", struct {
426+
KeepTrailingSlash bool
427+
}{true})
428+
defer os.Remove(file)
429+
430+
cmd, output := s.traefikCmd(withConfigFile(file))
431+
defer output(c)
432+
433+
err := cmd.Start()
434+
c.Assert(err, checker.IsNil)
435+
defer cmd.Process.Kill()
436+
437+
oldCheckRedirect := http.DefaultClient.CheckRedirect
438+
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
439+
return http.ErrUseLastResponse
440+
}
441+
442+
err = try.GetRequest("http://127.0.0.1:8000/test/foo/", 1*time.Second, try.StatusCodeIs(http.StatusNotFound))
443+
c.Assert(err, checker.IsNil)
444+
445+
http.DefaultClient.CheckRedirect = oldCheckRedirect
446+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defaultEntryPoints = ["http"]
2+
3+
keepTrailingSlash = {{ .KeepTrailingSlash }}
4+
[entryPoints]
5+
[entryPoints.http]
6+
address = ":8000"
7+
8+
logLevel = "DEBUG"
9+
10+
[file]
11+
12+
# rules
13+
[backends]
14+
[backends.backend1]
15+
[backends.backend1.servers.server1]
16+
url = "http://172.17.0.2:80"
17+
weight = 1
18+
19+
[frontends]
20+
[frontends.frontend1]
21+
backend = "backend1"
22+
[frontends.frontend1.routes.test_1]
23+
rule = "Path:/test/foo"

server/server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ func buildProxyProtocolListener(entryPoint *configuration.EntryPoint, listener n
627627

628628
func (s *Server) buildInternalRouter(entryPointName string) *mux.Router {
629629
internalMuxRouter := mux.NewRouter()
630-
internalMuxRouter.StrictSlash(true)
630+
internalMuxRouter.StrictSlash(!s.globalConfiguration.KeepTrailingSlash)
631631
internalMuxRouter.SkipClean(true)
632632

633633
if entryPoint, ok := s.entryPoints[entryPointName]; ok && entryPoint.InternalRouter != nil {

server/server_configuration.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ func buildDefaultCertificate(defaultCertificate *traefiktls.Certificate) (*tls.C
633633
func (s *Server) buildDefaultHTTPRouter() *mux.Router {
634634
rt := mux.NewRouter()
635635
rt.NotFoundHandler = s.wrapHTTPHandlerWithAccessLog(http.HandlerFunc(http.NotFound), "backend not found")
636-
rt.StrictSlash(true)
636+
rt.StrictSlash(!s.globalConfiguration.KeepTrailingSlash)
637637
rt.SkipClean(true)
638638
return rt
639639
}

0 commit comments

Comments
 (0)