fix: stripPrefix middleware with empty resulting path.

This commit is contained in:
Ludovic Fernandez 2019-11-14 10:32:05 +01:00 committed by Traefiker Bot
parent cdb2446e32
commit 7afd2dbd20
14 changed files with 426 additions and 245 deletions

View file

@ -58,4 +58,5 @@ http:
### `prefix`
`prefix` is the string to add before the current path in the requested URL. It should include the leading slash (`/`).
`prefix` is the string to add before the current path in the requested URL.
It should include the leading slash (`/`).

View file

@ -85,3 +85,85 @@ If your backend is serving assets (e.g., images or Javascript files), chances ar
Continuing on the example, the backend should return `/products/shoes/image.png` (and not `/images.png` which Traefik would likely not be able to associate with the same backend).
The `X-Forwarded-Prefix` header can be queried to build such URLs dynamically.
### `forceSlash`
_Optional, Default=true_
```yaml tab="Docker"
labels:
- "traefik.http.middlewares.example.stripprefix.prefixes=/foobar"
- "traefik.http.middlewares.example.stripprefix.forceslash=false"
```
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: example
spec:
stripPrefix:
prefixes:
- "/foobar"
forceSlash: false
```
```json tab="Marathon"
"labels": {
"traefik.http.middlewares.example.stripprefix.prefixes": "/foobar",
"traefik.http.middlewares.example.stripprefix.forceslash": "false"
}
```
```yaml tab="Rancher"
labels:
- "traefik.http.middlewares.example.stripprefix.prefixes=/foobar"
- "traefik.http.middlewares.example.stripprefix.forceSlash=false"
```
```toml tab="File (TOML)"
[http.middlewares]
[http.middlewares.example.stripPrefix]
prefixes = ["/foobar"]
forceSlash = false
```
```yaml tab="File (YAML)"
http:
middlewares:
example:
stripPrefix:
prefixes:
- "/foobar"
forceSlash: false
```
The `forceSlash` option makes sure that the resulting stripped path is not the empty string, by replacing it with `/` when necessary.
This option was added to keep the initial (non-intuitive) behavior of this middleware, in order to avoid introducing a breaking change.
It's recommended to explicitly set `forceSlash` to `false`.
??? info "Behavior examples"
- `forceSlash=true`
| Path | Prefix to strip | Result |
|------------|-----------------|--------|
| `/` | `/` | `/` |
| `/foo` | `/foo` | `/` |
| `/foo/` | `/foo` | `/` |
| `/foo/` | `/foo/` | `/` |
| `/bar` | `/foo` | `/bar` |
| `/foo/bar` | `/foo` | `/bar` |
- `forceSlash=false`
| Path | Prefix to strip | Result |
|------------|-----------------|--------|
| `/` | `/` | empty |
| `/foo` | `/foo` | empty |
| `/foo/` | `/foo` | `/` |
| `/foo/` | `/foo/` | empty |
| `/bar` | `/foo` | `/bar` |
| `/foo/bar` | `/foo` | `/bar` |

View file

@ -103,6 +103,7 @@
- "traefik.http.middlewares.middleware17.replacepathregex.regex=foobar"
- "traefik.http.middlewares.middleware17.replacepathregex.replacement=foobar"
- "traefik.http.middlewares.middleware18.retry.attempts=42"
- "traefik.http.middlewares.middleware19.stripprefix.forceslash=true"
- "traefik.http.middlewares.middleware19.stripprefix.prefixes=foobar, foobar"
- "traefik.http.middlewares.middleware20.stripprefixregex.regex=foobar, foobar"
- "traefik.http.routers.router0.entrypoints=foobar, foobar"
@ -129,38 +130,22 @@
- "traefik.http.routers.router1.tls.domains[1].main=foobar"
- "traefik.http.routers.router1.tls.domains[1].sans=foobar, foobar"
- "traefik.http.routers.router1.tls.options=foobar"
- "traefik.http.services.service0.loadbalancer.healthcheck.headers.name0=foobar"
- "traefik.http.services.service0.loadbalancer.healthcheck.headers.name1=foobar"
- "traefik.http.services.service0.loadbalancer.healthcheck.hostname=foobar"
- "traefik.http.services.service0.loadbalancer.healthcheck.interval=foobar"
- "traefik.http.services.service0.loadbalancer.healthcheck.path=foobar"
- "traefik.http.services.service0.loadbalancer.healthcheck.port=42"
- "traefik.http.services.service0.loadbalancer.healthcheck.scheme=foobar"
- "traefik.http.services.service0.loadbalancer.healthcheck.timeout=foobar"
- "traefik.http.services.service0.loadbalancer.passhostheader=true"
- "traefik.http.services.service0.loadbalancer.responseforwarding.flushinterval=foobar"
- "traefik.http.services.service0.loadbalancer.sticky=true"
- "traefik.http.services.service0.loadbalancer.sticky.cookie.httponly=true"
- "traefik.http.services.service0.loadbalancer.sticky.cookie.name=foobar"
- "traefik.http.services.service0.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.service0.loadbalancer.server.port=foobar"
- "traefik.http.services.service0.loadbalancer.server.scheme=foobar"
- "traefik.http.services.service1.loadbalancer.healthcheck.headers.name0=foobar"
- "traefik.http.services.service1.loadbalancer.healthcheck.headers.name1=foobar"
- "traefik.http.services.service1.loadbalancer.healthcheck.hostname=foobar"
- "traefik.http.services.service1.loadbalancer.healthcheck.interval=foobar"
- "traefik.http.services.service1.loadbalancer.healthcheck.path=foobar"
- "traefik.http.services.service1.loadbalancer.healthcheck.port=42"
- "traefik.http.services.service1.loadbalancer.healthcheck.scheme=foobar"
- "traefik.http.services.service1.loadbalancer.healthcheck.timeout=foobar"
- "traefik.http.services.service1.loadbalancer.passhostheader=true"
- "traefik.http.services.service1.loadbalancer.responseforwarding.flushinterval=foobar"
- "traefik.http.services.service1.loadbalancer.sticky=true"
- "traefik.http.services.service1.loadbalancer.sticky.cookie.httponly=true"
- "traefik.http.services.service1.loadbalancer.sticky.cookie.name=foobar"
- "traefik.http.services.service1.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.service1.loadbalancer.server.port=foobar"
- "traefik.http.services.service1.loadbalancer.server.scheme=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.headers.name0=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.headers.name1=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.hostname=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.interval=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.path=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.port=42"
- "traefik.http.services.service01.loadbalancer.healthcheck.scheme=foobar"
- "traefik.http.services.service01.loadbalancer.healthcheck.timeout=foobar"
- "traefik.http.services.service01.loadbalancer.passhostheader=true"
- "traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval=foobar"
- "traefik.http.services.service01.loadbalancer.sticky=true"
- "traefik.http.services.service01.loadbalancer.sticky.cookie.httponly=true"
- "traefik.http.services.service01.loadbalancer.sticky.cookie.name=foobar"
- "traefik.http.services.service01.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.service01.loadbalancer.server.port=foobar"
- "traefik.http.services.service01.loadbalancer.server.scheme=foobar"
- "traefik.tcp.routers.tcprouter0.entrypoints=foobar, foobar"
- "traefik.tcp.routers.tcprouter0.rule=foobar"
- "traefik.tcp.routers.tcprouter0.service=foobar"
@ -183,7 +168,5 @@
- "traefik.tcp.routers.tcprouter1.tls.domains[1].sans=foobar, foobar"
- "traefik.tcp.routers.tcprouter1.tls.options=foobar"
- "traefik.tcp.routers.tcprouter1.tls.passthrough=true"
- "traefik.tcp.services.tcpservice0.loadbalancer.server.port=foobar"
- "traefik.tcp.services.tcpservice0.loadbalancer.terminationdelay=100"
- "traefik.tcp.services.tcpservice1.loadbalancer.server.port=foobar"
- "traefik.tcp.services.tcpservice1.loadbalancer.terminationdelay=100"
- "traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay=42"
- "traefik.tcp.services.tcpservice01.loadbalancer.server.port=foobar"

View file

@ -245,6 +245,7 @@
[http.middlewares.Middleware19]
[http.middlewares.Middleware19.stripPrefix]
prefixes = ["foobar", "foobar"]
forceSlash = true
[http.middlewares.Middleware20]
[http.middlewares.Middleware20.stripPrefixRegex]
regex = ["foobar", "foobar"]
@ -284,25 +285,25 @@
main = "foobar"
sans = ["foobar", "foobar"]
[tcp.services]
[tcp.services.TCPService0]
[tcp.services.TCPService0.loadBalancer]
terminationDelay = 100
[tcp.services.TCPService01]
[tcp.services.TCPService01.loadBalancer]
terminationDelay = 42
[[tcp.services.TCPService0.loadBalancer.servers]]
[[tcp.services.TCPService01.loadBalancer.servers]]
address = "foobar"
[[tcp.services.TCPService0.loadBalancer.servers]]
[[tcp.services.TCPService01.loadBalancer.servers]]
address = "foobar"
[tcp.services.TCPService02]
[tcp.services.TCPService02.weighted]
[tcp.services.TCPService1]
[tcp.services.TCPService1.loadBalancer]
terminationDelay = 100
[[tcp.services.TCPService02.weighted.services]]
name = "foobar"
weight = 42
[[tcp.services.TCPService1.loadBalancer.servers]]
address = "foobar"
[[tcp.services.TCPService1.loadBalancer.servers]]
address = "foobar"
[[tcp.services.TCPService02.weighted.services]]
name = "foobar"
weight = 42
[tls]

View file

@ -2,11 +2,11 @@ http:
routers:
Router0:
entryPoints:
- foobar
- foobar
- foobar
- foobar
middlewares:
- foobar
- foobar
- foobar
- foobar
service: foobar
rule: foobar
priority: 42
@ -14,21 +14,21 @@ http:
options: foobar
certResolver: foobar
domains:
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
Router1:
entryPoints:
- foobar
- foobar
- foobar
- foobar
middlewares:
- foobar
- foobar
- foobar
- foobar
service: foobar
rule: foobar
priority: 42
@ -36,14 +36,14 @@ http:
options: foobar
certResolver: foobar
domains:
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
services:
Service01:
loadBalancer:
@ -53,8 +53,8 @@ http:
secure: true
httpOnly: true
servers:
- url: foobar
- url: foobar
- url: foobar
- url: foobar
healthCheck:
scheme: foobar
path: foobar
@ -72,17 +72,17 @@ http:
mirroring:
service: foobar
mirrors:
- name: foobar
percent: 42
- name: foobar
percent: 42
- name: foobar
percent: 42
- name: foobar
percent: 42
Service03:
weighted:
services:
- name: foobar
weight: 42
- name: foobar
weight: 42
- name: foobar
weight: 42
- name: foobar
weight: 42
sticky:
cookie:
name: foobar
@ -95,8 +95,8 @@ http:
Middleware01:
basicAuth:
users:
- foobar
- foobar
- foobar
- foobar
usersFile: foobar
realm: foobar
removeHeader: true
@ -111,8 +111,8 @@ http:
Middleware03:
chain:
middlewares:
- foobar
- foobar
- foobar
- foobar
Middleware04:
circuitBreaker:
expression: foobar
@ -121,8 +121,8 @@ http:
Middleware06:
digestAuth:
users:
- foobar
- foobar
- foobar
- foobar
usersFile: foobar
removeHeader: true
realm: foobar
@ -130,8 +130,8 @@ http:
Middleware07:
errors:
status:
- foobar
- foobar
- foobar
- foobar
service: foobar
query: foobar
Middleware08:
@ -145,8 +145,8 @@ http:
insecureSkipVerify: true
trustForwardHeader: true
authResponseHeaders:
- foobar
- foobar
- foobar
- foobar
Middleware09:
headers:
customRequestHeaders:
@ -157,23 +157,23 @@ http:
name1: foobar
accessControlAllowCredentials: true
accessControlAllowHeaders:
- foobar
- foobar
- foobar
- foobar
accessControlAllowMethods:
- foobar
- foobar
- foobar
- foobar
accessControlAllowOrigin: foobar
accessControlExposeHeaders:
- foobar
- foobar
- foobar
- foobar
accessControlMaxAge: 42
addVaryHeader: true
allowedHosts:
- foobar
- foobar
- foobar
- foobar
hostsProxyHeaders:
- foobar
- foobar
- foobar
- foobar
sslRedirect: true
sslTemporaryRedirect: true
sslHost: foobar
@ -198,13 +198,13 @@ http:
Middleware10:
ipWhiteList:
sourceRange:
- foobar
- foobar
- foobar
- foobar
ipStrategy:
depth: 42
excludedIPs:
- foobar
- foobar
- foobar
- foobar
Middleware11:
inFlightReq:
amount: 42
@ -212,8 +212,8 @@ http:
ipstrategy:
depth: 42
excludedIPs:
- foobar
- foobar
- foobar
- foobar
requestHeaderName: foobar
requestHost: true
Middleware12:
@ -247,8 +247,8 @@ http:
ipstrategy:
depth: 42
excludedIPs:
- foobar
- foobar
- foobar
- foobar
requestHeaderName: foobar
requestHost: true
Middleware14:
@ -274,19 +274,20 @@ http:
Middleware19:
stripPrefix:
prefixes:
- foobar
- foobar
- foobar
- foobar
forceSlash: true
Middleware20:
stripPrefixRegex:
regex:
- foobar
- foobar
- foobar
- foobar
tcp:
routers:
TCPRouter0:
entryPoints:
- foobar
- foobar
- foobar
- foobar
service: foobar
rule: foobar
tls:
@ -294,18 +295,18 @@ tcp:
options: foobar
certResolver: foobar
domains:
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
TCPRouter1:
entryPoints:
- foobar
- foobar
- foobar
- foobar
service: foobar
rule: foobar
tls:
@ -313,60 +314,61 @@ tcp:
options: foobar
certResolver: foobar
domains:
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
- main: foobar
sans:
- foobar
- foobar
services:
TCPService0:
TCPService01:
loadBalancer:
terminationDelay: 100
terminationDelay: 42
servers:
- address: foobar
- address: foobar
TCPService1:
loadBalancer:
terminationDelay: 100
servers:
- address: foobar
- address: foobar
- address: foobar
- address: foobar
TCPService02:
weighted:
services:
- name: foobar
weight: 42
- name: foobar
weight: 42
tls:
certificates:
- certFile: foobar
keyFile: foobar
stores:
- foobar
- foobar
- certFile: foobar
keyFile: foobar
stores:
- foobar
- foobar
- certFile: foobar
keyFile: foobar
stores:
- foobar
- foobar
- certFile: foobar
keyFile: foobar
stores:
- foobar
- foobar
options:
Options0:
minVersion: foobar
cipherSuites:
- foobar
- foobar
- foobar
- foobar
clientAuth:
caFiles:
- foobar
- foobar
- foobar
- foobar
clientAuthType: foobar
sniStrict: true
Options1:
minVersion: foobar
cipherSuites:
- foobar
- foobar
- foobar
- foobar
clientAuth:
caFiles:
- foobar
- foobar
- foobar
- foobar
clientAuthType: foobar
sniStrict: true
stores:

View file

@ -103,6 +103,7 @@
"traefik.http.middlewares.middleware17.replacepathregex.regex": "foobar",
"traefik.http.middlewares.middleware17.replacepathregex.replacement": "foobar",
"traefik.http.middlewares.middleware18.retry.attempts": "42",
"traefik.http.middlewares.middleware19.stripprefix.forceslash": "true",
"traefik.http.middlewares.middleware19.stripprefix.prefixes": "foobar, foobar",
"traefik.http.middlewares.middleware20.stripprefixregex.regex": "foobar, foobar",
"traefik.http.routers.router0.entrypoints": "foobar, foobar",
@ -110,7 +111,6 @@
"traefik.http.routers.router0.priority": "42",
"traefik.http.routers.router0.rule": "foobar",
"traefik.http.routers.router0.service": "foobar",
"traefik.http.routers.router0.tls": "true",
"traefik.http.routers.router0.tls.certresolver": "foobar",
"traefik.http.routers.router0.tls.domains[0].main": "foobar",
"traefik.http.routers.router0.tls.domains[0].sans": "foobar, foobar",
@ -122,49 +122,30 @@
"traefik.http.routers.router1.priority": "42",
"traefik.http.routers.router1.rule": "foobar",
"traefik.http.routers.router1.service": "foobar",
"traefik.http.routers.router1.tls": "true",
"traefik.http.routers.router1.tls.certresolver": "foobar",
"traefik.http.routers.router1.tls.domains[0].main": "foobar",
"traefik.http.routers.router1.tls.domains[0].sans": "foobar, foobar",
"traefik.http.routers.router1.tls.domains[1].main": "foobar",
"traefik.http.routers.router1.tls.domains[1].sans": "foobar, foobar",
"traefik.http.routers.router1.tls.options": "foobar",
"traefik.http.services.service0.loadbalancer.healthcheck.headers.name0": "foobar",
"traefik.http.services.service0.loadbalancer.healthcheck.headers.name1": "foobar",
"traefik.http.services.service0.loadbalancer.healthcheck.hostname": "foobar",
"traefik.http.services.service0.loadbalancer.healthcheck.interval": "foobar",
"traefik.http.services.service0.loadbalancer.healthcheck.path": "foobar",
"traefik.http.services.service0.loadbalancer.healthcheck.port": "42",
"traefik.http.services.service0.loadbalancer.healthcheck.scheme": "foobar",
"traefik.http.services.service0.loadbalancer.healthcheck.timeout": "foobar",
"traefik.http.services.service0.loadbalancer.passhostheader": "true",
"traefik.http.services.service0.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.http.services.service0.loadbalancer.sticky": "true",
"traefik.http.services.service0.loadbalancer.sticky.cookie.httponly": "true",
"traefik.http.services.service0.loadbalancer.sticky.cookie.name": "foobar",
"traefik.http.services.service0.loadbalancer.sticky.cookie.secure": "true",
"traefik.http.services.service0.loadbalancer.server.port": "foobar",
"traefik.http.services.service0.loadbalancer.server.scheme": "foobar",
"traefik.http.services.service1.loadbalancer.healthcheck.headers.name0": "foobar",
"traefik.http.services.service1.loadbalancer.healthcheck.headers.name1": "foobar",
"traefik.http.services.service1.loadbalancer.healthcheck.hostname": "foobar",
"traefik.http.services.service1.loadbalancer.healthcheck.interval": "foobar",
"traefik.http.services.service1.loadbalancer.healthcheck.path": "foobar",
"traefik.http.services.service1.loadbalancer.healthcheck.port": "42",
"traefik.http.services.service1.loadbalancer.healthcheck.scheme": "foobar",
"traefik.http.services.service1.loadbalancer.healthcheck.timeout": "foobar",
"traefik.http.services.service1.loadbalancer.passhostheader": "true",
"traefik.http.services.service1.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.http.services.service1.loadbalancer.sticky": "true",
"traefik.http.services.service1.loadbalancer.sticky.cookie.httponly": "true",
"traefik.http.services.service1.loadbalancer.sticky.cookie.name": "foobar",
"traefik.http.services.service1.loadbalancer.sticky.cookie.secure": "true",
"traefik.http.services.service1.loadbalancer.server.port": "foobar",
"traefik.http.services.service1.loadbalancer.server.scheme": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.headers.name0": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.headers.name1": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.hostname": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.interval": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.path": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.port": "42",
"traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar",
"traefik.http.services.service01.loadbalancer.healthcheck.timeout": "foobar",
"traefik.http.services.service01.loadbalancer.passhostheader": "true",
"traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval": "foobar",
"traefik.http.services.service01.loadbalancer.sticky.cookie.httponly": "true",
"traefik.http.services.service01.loadbalancer.sticky.cookie.name": "foobar",
"traefik.http.services.service01.loadbalancer.sticky.cookie.secure": "true",
"traefik.http.services.service01.loadbalancer.server.port": "foobar",
"traefik.http.services.service01.loadbalancer.server.scheme": "foobar",
"traefik.tcp.routers.tcprouter0.entrypoints": "foobar, foobar",
"traefik.tcp.routers.tcprouter0.rule": "foobar",
"traefik.tcp.routers.tcprouter0.service": "foobar",
"traefik.tcp.routers.tcprouter0.tls": "true",
"traefik.tcp.routers.tcprouter0.tls.certresolver": "foobar",
"traefik.tcp.routers.tcprouter0.tls.domains[0].main": "foobar",
"traefik.tcp.routers.tcprouter0.tls.domains[0].sans": "foobar, foobar",
@ -175,7 +156,6 @@
"traefik.tcp.routers.tcprouter1.entrypoints": "foobar, foobar",
"traefik.tcp.routers.tcprouter1.rule": "foobar",
"traefik.tcp.routers.tcprouter1.service": "foobar",
"traefik.tcp.routers.tcprouter1.tls": "true",
"traefik.tcp.routers.tcprouter1.tls.certresolver": "foobar",
"traefik.tcp.routers.tcprouter1.tls.domains[0].main": "foobar",
"traefik.tcp.routers.tcprouter1.tls.domains[0].sans": "foobar, foobar",
@ -183,7 +163,5 @@
"traefik.tcp.routers.tcprouter1.tls.domains[1].sans": "foobar, foobar",
"traefik.tcp.routers.tcprouter1.tls.options": "foobar",
"traefik.tcp.routers.tcprouter1.tls.passthrough": "true",
"traefik.tcp.services.tcpservice0.loadbalancer.server.port": "foobar",
"traefik.tcp.services.tcpservice0.loadbalancer.terminationDelay": "100",
"traefik.tcp.services.tcpservice1.loadbalancer.server.port": "foobar"
"traefik.tcp.services.tcpservice1.loadbalancer.terminationDelay": "100",
"traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay": "42",
"traefik.tcp.services.tcpservice01.loadbalancer.server.port": "foobar",

View file

@ -355,7 +355,13 @@ type Retry struct {
// StripPrefix holds the StripPrefix configuration.
type StripPrefix struct {
Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty"`
Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty"`
ForceSlash bool `json:"forceSlash,omitempty" toml:"forceSlash,omitempty" yaml:"forceSlash,omitempty"` // Deprecated
}
// SetDefaults Default values for a StripPrefix.
func (s *StripPrefix) SetDefaults() {
s.ForceSlash = true
}
// +k8s:deepcopy-gen=true

View file

@ -368,6 +368,7 @@ func TestDecodeConfiguration(t *testing.T) {
"foobar",
"fiibar",
},
ForceSlash: true,
},
},
"Middleware18": {
@ -771,6 +772,7 @@ func TestEncodeConfiguration(t *testing.T) {
"foobar",
"fiibar",
},
ForceSlash: true,
},
},
"Middleware18": {
@ -1091,6 +1093,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware15.ReplacePathRegex.Replacement": "foobar",
"traefik.HTTP.Middlewares.Middleware16.Retry.Attempts": "42",
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware19.Compress": "true",

View file

@ -41,23 +41,35 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name
return result, nil
}
func (ap *addPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
return ap.name, tracing.SpanKindNoneEnum
func (a *addPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
return a.name, tracing.SpanKindNoneEnum
}
func (ap *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), ap.name, typeName))
func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), a.name, typeName))
oldURLPath := req.URL.Path
req.URL.Path = ap.prefix + req.URL.Path
req.URL.Path = ensureLeadingSlash(a.prefix + req.URL.Path)
logger.Debugf("URL.Path is now %s (was %s).", req.URL.Path, oldURLPath)
if req.URL.RawPath != "" {
oldURLRawPath := req.URL.RawPath
req.URL.RawPath = ap.prefix + req.URL.RawPath
req.URL.RawPath = ensureLeadingSlash(a.prefix + req.URL.RawPath)
logger.Debugf("URL.RawPath is now %s (was %s).", req.URL.RawPath, oldURLRawPath)
}
req.RequestURI = req.URL.RequestURI()
ap.next.ServeHTTP(rw, req)
a.next.ServeHTTP(rw, req)
}
func ensureLeadingSlash(str string) string {
if str == "" {
return str
}
if str[0] == '/' {
return str
}
return "/" + str
}

View file

@ -7,7 +7,6 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/testhelpers"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -47,7 +46,6 @@ func TestNewAddPrefix(t *testing.T) {
}
func TestAddPrefix(t *testing.T) {
logrus.SetLevel(logrus.DebugLevel)
testCases := []struct {
desc string
prefix dynamic.AddPrefix
@ -61,6 +59,12 @@ func TestAddPrefix(t *testing.T) {
path: "/b",
expectedPath: "/a/b",
},
{
desc: "Works with missing leading slash",
prefix: dynamic.AddPrefix{Prefix: "a"},
path: "/",
expectedPath: "/a/",
},
{
desc: "Works with a raw path",
prefix: dynamic.AddPrefix{Prefix: "/a"},

View file

@ -20,18 +20,20 @@ const (
// stripPrefix is a middleware used to strip prefix from an URL request.
type stripPrefix struct {
next http.Handler
prefixes []string
name string
next http.Handler
prefixes []string
forceSlash bool // TODO Must be removed (breaking), the default behavior must be forceSlash=false
name string
}
// New creates a new strip prefix middleware.
func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, name string) (http.Handler, error) {
log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)).Debug("Creating middleware")
return &stripPrefix{
prefixes: config.Prefixes,
next: next,
name: name,
prefixes: config.Prefixes,
forceSlash: config.ForceSlash,
next: next,
name: name,
}, nil
}
@ -42,9 +44,9 @@ func (s *stripPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
for _, prefix := range s.prefixes {
if strings.HasPrefix(req.URL.Path, prefix) {
req.URL.Path = getPrefixStripped(req.URL.Path, prefix)
req.URL.Path = s.getPrefixStripped(req.URL.Path, prefix)
if req.URL.RawPath != "" {
req.URL.RawPath = getPrefixStripped(req.URL.RawPath, prefix)
req.URL.RawPath = s.getPrefixStripped(req.URL.RawPath, prefix)
}
s.serveRequest(rw, req, strings.TrimSpace(prefix))
return
@ -59,10 +61,25 @@ func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, pr
s.next.ServeHTTP(rw, req)
}
func getPrefixStripped(s, prefix string) string {
return ensureLeadingSlash(strings.TrimPrefix(s, prefix))
func (s *stripPrefix) getPrefixStripped(urlPath, prefix string) string {
if s.forceSlash {
// Only for compatibility reason with the previous behavior,
// but the previous behavior is wrong.
// This needs to be removed in the next breaking version.
return "/" + strings.TrimPrefix(strings.TrimPrefix(urlPath, prefix), "/")
}
return ensureLeadingSlash(strings.TrimPrefix(urlPath, prefix))
}
func ensureLeadingSlash(str string) string {
return "/" + strings.TrimPrefix(str, "/")
if str == "" {
return str
}
if str[0] == '/' {
return str
}
return "/" + str
}

View file

@ -31,6 +31,17 @@ func TestStripPrefix(t *testing.T) {
expectedStatusCode: http.StatusOK,
expectedPath: "/noprefixes",
},
{
desc: "wildcard (.*) requests (ForceSlash)",
config: dynamic.StripPrefix{
Prefixes: []string{"/"},
ForceSlash: true,
},
path: "/",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedHeader: "/",
},
{
desc: "wildcard (.*) requests",
config: dynamic.StripPrefix{
@ -38,9 +49,20 @@ func TestStripPrefix(t *testing.T) {
},
path: "/",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedPath: "",
expectedHeader: "/",
},
{
desc: "prefix and path matching (ForceSlash)",
config: dynamic.StripPrefix{
Prefixes: []string{"/stat"},
ForceSlash: true,
},
path: "/stat",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedHeader: "/stat",
},
{
desc: "prefix and path matching",
config: dynamic.StripPrefix{
@ -48,9 +70,20 @@ func TestStripPrefix(t *testing.T) {
},
path: "/stat",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedPath: "",
expectedHeader: "/stat",
},
{
desc: "path prefix on exactly matching path (ForceSlash)",
config: dynamic.StripPrefix{
Prefixes: []string{"/stat/"},
ForceSlash: true,
},
path: "/stat/",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedHeader: "/stat/",
},
{
desc: "path prefix on exactly matching path",
config: dynamic.StripPrefix{
@ -58,7 +91,7 @@ func TestStripPrefix(t *testing.T) {
},
path: "/stat/",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedPath: "",
expectedHeader: "/stat/",
},
{
@ -101,6 +134,17 @@ func TestStripPrefix(t *testing.T) {
expectedPath: "/us",
expectedHeader: "/stat",
},
{
desc: "later prefix matching (ForceSlash)",
config: dynamic.StripPrefix{
Prefixes: []string{"/mismatch", "/stat"},
ForceSlash: true,
},
path: "/stat",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedHeader: "/stat",
},
{
desc: "later prefix matching",
config: dynamic.StripPrefix{
@ -108,7 +152,7 @@ func TestStripPrefix(t *testing.T) {
},
path: "/stat",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
expectedPath: "",
expectedHeader: "/stat",
},
{
@ -162,12 +206,15 @@ func TestStripPrefix(t *testing.T) {
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", ForwardedPrefixHeader)
expectedURI := test.expectedPath
expectedRequestURI := test.expectedPath
if test.expectedRawPath != "" {
// go HTTP uses the raw path when existent in the RequestURI
expectedURI = test.expectedRawPath
expectedRequestURI = test.expectedRawPath
}
assert.Equal(t, expectedURI, requestURI, "Unexpected request URI.")
if test.expectedPath == "" {
expectedRequestURI = "/"
}
assert.Equal(t, expectedRequestURI, requestURI, "Unexpected request URI.")
})
}
}

View file

@ -60,12 +60,12 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
req.Header.Add(stripprefix.ForwardedPrefixHeader, prefix)
req.URL.Path = strings.Replace(req.URL.Path, prefix, "", 1)
req.URL.Path = ensureLeadingSlash(strings.Replace(req.URL.Path, prefix, "", 1))
if req.URL.RawPath != "" {
req.URL.RawPath = req.URL.RawPath[len(prefix):]
req.URL.RawPath = ensureLeadingSlash(req.URL.RawPath[len(prefix):])
}
req.RequestURI = ensureLeadingSlash(req.URL.RequestURI())
req.RequestURI = req.URL.RequestURI()
s.next.ServeHTTP(rw, req)
return
}
@ -75,5 +75,13 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
}
func ensureLeadingSlash(str string) string {
return "/" + strings.TrimPrefix(str, "/")
if str == "" {
return str
}
if str[0] == '/' {
return str
}
return "/" + str
}

View file

@ -31,42 +31,66 @@ func TestStripPrefixRegex(t *testing.T) {
expectedPath: "/a/test",
},
{
path: "/a/test",
path: "/a/test/",
expectedStatusCode: http.StatusOK,
expectedPath: "/a/test",
expectedPath: "/a/test/",
},
{
path: "/a/api/",
expectedStatusCode: http.StatusOK,
expectedPath: "",
expectedHeader: "/a/api/",
},
{
path: "/a/api/test",
expectedStatusCode: http.StatusOK,
expectedPath: "test",
expectedPath: "/test",
expectedHeader: "/a/api/",
},
{
path: "/a/api/test/",
expectedStatusCode: http.StatusOK,
expectedPath: "/test/",
expectedHeader: "/a/api/",
},
{
path: "/b/api/",
expectedStatusCode: http.StatusOK,
expectedPath: "",
expectedHeader: "/b/api/",
},
{
path: "/b/api",
expectedStatusCode: http.StatusOK,
expectedPath: "/b/api",
},
{
path: "/b/api/test1",
expectedStatusCode: http.StatusOK,
expectedPath: "test1",
expectedPath: "/test1",
expectedHeader: "/b/api/",
},
{
path: "/b/api2/test2",
expectedStatusCode: http.StatusOK,
expectedPath: "test2",
expectedPath: "/test2",
expectedHeader: "/b/api2/",
},
{
path: "/c/api/123/",
expectedStatusCode: http.StatusOK,
expectedPath: "",
expectedHeader: "/c/api/123/",
},
{
path: "/c/api/123",
expectedStatusCode: http.StatusOK,
expectedPath: "/c/api/123",
},
{
path: "/c/api/123/test3",
expectedStatusCode: http.StatusOK,
expectedPath: "test3",
expectedPath: "/test3",
expectedHeader: "/c/api/123/",
},
{
@ -77,8 +101,8 @@ func TestStripPrefixRegex(t *testing.T) {
{
path: "/a/api/a%2Fb",
expectedStatusCode: http.StatusOK,
expectedPath: "a/b",
expectedRawPath: "a%2Fb",
expectedPath: "/a/b",
expectedRawPath: "/a%2Fb",
expectedHeader: "/a/api/",
},
}
@ -88,11 +112,12 @@ func TestStripPrefixRegex(t *testing.T) {
t.Run(test.path, func(t *testing.T) {
t.Parallel()
var actualPath, actualRawPath, actualHeader string
var actualPath, actualRawPath, actualHeader, requestURI string
handlerPath := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
actualPath = r.URL.Path
actualRawPath = r.URL.RawPath
actualHeader = r.Header.Get(stripprefix.ForwardedPrefixHeader)
requestURI = r.RequestURI
})
handler, err := New(context.Background(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex")
require.NoError(t, err)
@ -106,6 +131,18 @@ func TestStripPrefixRegex(t *testing.T) {
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", stripprefix.ForwardedPrefixHeader)
if test.expectedPath != test.path {
expectedRequestURI := test.expectedPath
if test.expectedRawPath != "" {
// go HTTP uses the raw path when existent in the RequestURI
expectedRequestURI = test.expectedRawPath
}
if test.expectedPath == "" {
expectedRequestURI = "/"
}
assert.Equal(t, expectedRequestURI, requestURI, "Unexpected request URI.")
}
})
}
}