Merge github.com:traefik/traefik
This commit is contained in:
commit
ac7aa6aacc
85 changed files with 3581 additions and 2263 deletions
|
@ -134,7 +134,6 @@ issues:
|
|||
exclude:
|
||||
- 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
||||
- "should have a package comment, unless it's in another file for this package"
|
||||
- 'SA1019: http.CloseNotifier has been deprecated' # FIXME must be fixed
|
||||
- 'SA1019: cfg.SSLRedirect is deprecated'
|
||||
- 'SA1019: cfg.SSLTemporaryRedirect is deprecated'
|
||||
- 'SA1019: cfg.SSLHost is deprecated'
|
||||
|
|
|
@ -5,23 +5,24 @@ description: "Traefik Proxy's HTTP middleware lets you compress responses before
|
|||
|
||||
# Compress
|
||||
|
||||
Compress Responses before Sending them to the Client
|
||||
Compress Allows Compressing Responses before Sending them to the Client
|
||||
{: .subtitle }
|
||||
|
||||
![Compress](../../assets/img/middleware/compress.png)
|
||||
|
||||
The Compress middleware uses gzip compression.
|
||||
The Compress middleware supports gzip and Brotli compression.
|
||||
The activation of compression, and the compression method choice rely (among other things) on the request's `Accept-Encoding` header.
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
# Enable gzip compression
|
||||
# Enable compression
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-compress.compress=true"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
# Enable gzip compression
|
||||
# Enable compression
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
|
@ -31,7 +32,7 @@ spec:
|
|||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
# Enable gzip compression
|
||||
# Enable compression
|
||||
- "traefik.http.middlewares.test-compress.compress=true"
|
||||
```
|
||||
|
||||
|
@ -42,13 +43,13 @@ spec:
|
|||
```
|
||||
|
||||
```yaml tab="Rancher"
|
||||
# Enable gzip compression
|
||||
# Enable compression
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-compress.compress=true"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
# Enable gzip compression
|
||||
# Enable compression
|
||||
http:
|
||||
middlewares:
|
||||
test-compress:
|
||||
|
@ -56,7 +57,7 @@ http:
|
|||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
# Enable gzip compression
|
||||
# Enable compression
|
||||
[http.middlewares]
|
||||
[http.middlewares.test-compress.compress]
|
||||
```
|
||||
|
@ -65,23 +66,34 @@ http:
|
|||
|
||||
Responses are compressed when the following criteria are all met:
|
||||
|
||||
* The response body is larger than the configured minimum amount of bytes (default is `1024`).
|
||||
* The `Accept-Encoding` request header contains `gzip`.
|
||||
* The `Accept-Encoding` request header contains `gzip`, `*`, and/or `br` with or without [quality values](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values).
|
||||
If the `Accept-Encoding` request header is absent, it is meant as br compression is requested.
|
||||
If it is present, but its value is the empty string, then compression is disabled.
|
||||
* The response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
|
||||
|
||||
If the `Content-Type` header is not defined, or empty, the compress middleware will automatically [detect](https://mimesniff.spec.whatwg.org/) a content type.
|
||||
It will also set the `Content-Type` header according to the detected MIME type.
|
||||
* The response`Content-Type` header is not one among the [excludedContentTypes options](#excludedcontenttypes).
|
||||
* The response body is larger than the [configured minimum amount of bytes](#minresponsebodybytes) (default is `1024`).
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### `excludedContentTypes`
|
||||
|
||||
_Optional, Default=""_
|
||||
|
||||
`excludedContentTypes` specifies a list of content types to compare the `Content-Type` header of the incoming requests and responses before compressing.
|
||||
|
||||
The responses with content types defined in `excludedContentTypes` are not compressed.
|
||||
|
||||
Content types are compared in a case-insensitive, whitespace-ignored manner.
|
||||
|
||||
!!! info "In the case of gzip"
|
||||
|
||||
If the `Content-Type` header is not defined, or empty, the compress middleware will automatically [detect](https://mimesniff.spec.whatwg.org/) a content type.
|
||||
It will also set the `Content-Type` header according to the detected MIME type.
|
||||
|
||||
!!! info "gRPC"
|
||||
|
||||
Note that `application/grpc` is never compressed.
|
||||
|
||||
```yaml tab="Docker"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-compress.compress.excludedcontenttypes=text/event-stream"
|
||||
|
@ -130,9 +142,9 @@ http:
|
|||
|
||||
### `minResponseBodyBytes`
|
||||
|
||||
`minResponseBodyBytes` specifies the minimum amount of bytes a response body must have to be compressed.
|
||||
_Optional, Default=1024_
|
||||
|
||||
The default value is `1024`, which should be a reasonable value for most cases.
|
||||
`minResponseBodyBytes` specifies the minimum amount of bytes a response body must have to be compressed.
|
||||
|
||||
Responses smaller than the specified values will not be compressed.
|
||||
|
||||
|
|
|
@ -57,15 +57,15 @@
|
|||
path = "foobar"
|
||||
method = "foobar"
|
||||
port = 42
|
||||
interval = "foobar"
|
||||
timeout = "foobar"
|
||||
interval = "42s"
|
||||
timeout = "42s"
|
||||
hostname = "foobar"
|
||||
followRedirects = true
|
||||
[http.services.Service01.loadBalancer.healthCheck.headers]
|
||||
name0 = "foobar"
|
||||
name1 = "foobar"
|
||||
[http.services.Service01.loadBalancer.responseForwarding]
|
||||
flushInterval = "foobar"
|
||||
flushInterval = "42s"
|
||||
[http.services.Service02]
|
||||
[http.services.Service02.mirroring]
|
||||
service = "foobar"
|
||||
|
|
|
@ -62,8 +62,8 @@ http:
|
|||
path: foobar
|
||||
method: foobar
|
||||
port: 42
|
||||
interval: foobar
|
||||
timeout: foobar
|
||||
interval: 42s
|
||||
timeout: 42s
|
||||
hostname: foobar
|
||||
followRedirects: true
|
||||
headers:
|
||||
|
@ -71,7 +71,7 @@ http:
|
|||
name1: foobar
|
||||
passHostHeader: true
|
||||
responseForwarding:
|
||||
flushInterval: foobar
|
||||
flushInterval: 42s
|
||||
serversTransport: foobar
|
||||
Service02:
|
||||
mirroring:
|
||||
|
|
|
@ -749,7 +749,8 @@ spec:
|
|||
excludedContentTypes:
|
||||
description: ExcludedContentTypes defines the list of content
|
||||
types to compare the Content-Type header of the incoming requests
|
||||
and responses before compressing.
|
||||
and responses before compressing. `application/grpc` is always
|
||||
excluded.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
|
|
|
@ -214,15 +214,15 @@
|
|||
| `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/interval` | `42s` |
|
||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/method` | `foobar` |
|
||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/mode` | `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/healthCheck/timeout` | `42s` |
|
||||
| `traefik/http/services/Service01/loadBalancer/passHostHeader` | `true` |
|
||||
| `traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval` | `foobar` |
|
||||
| `traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval` | `42s` |
|
||||
| `traefik/http/services/Service01/loadBalancer/servers/0/url` | `foobar` |
|
||||
| `traefik/http/services/Service01/loadBalancer/servers/1/url` | `foobar` |
|
||||
| `traefik/http/services/Service01/loadBalancer/serversTransport` | `foobar` |
|
||||
|
|
|
@ -150,15 +150,15 @@
|
|||
"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.interval": "42s",
|
||||
"traefik.http.services.service01.loadbalancer.healthcheck.path": "foobar",
|
||||
"traefik.http.services.service01.loadbalancer.healthcheck.method": "foobar",
|
||||
"traefik.http.services.service01.loadbalancer.healthcheck.port": "42",
|
||||
"traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar",
|
||||
"traefik.http.services.service01.loadbalancer.healthcheck.mode": "foobar",
|
||||
"traefik.http.services.service01.loadbalancer.healthcheck.timeout": "foobar",
|
||||
"traefik.http.services.service01.loadbalancer.healthcheck.timeout": "42s",
|
||||
"traefik.http.services.service01.loadbalancer.passhostheader": "true",
|
||||
"traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval": "foobar",
|
||||
"traefik.http.services.service01.loadbalancer.responseforwarding.flushinterval": "42s",
|
||||
"traefik.http.services.service01.loadbalancer.serverstransport": "foobar",
|
||||
"traefik.http.services.service01.loadbalancer.sticky.cookie": "true",
|
||||
"traefik.http.services.service01.loadbalancer.sticky.cookie.httponly": "true",
|
||||
|
|
|
@ -172,7 +172,8 @@ spec:
|
|||
excludedContentTypes:
|
||||
description: ExcludedContentTypes defines the list of content
|
||||
types to compare the Content-Type header of the incoming requests
|
||||
and responses before compressing.
|
||||
and responses before compressing. `application/grpc` is always
|
||||
excluded.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
|
|
|
@ -233,18 +233,18 @@ If the rule is verified, the router becomes active, calls middlewares, and then
|
|||
|
||||
The table below lists all the available matchers:
|
||||
|
||||
| Rule | Description |
|
||||
|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
|
||||
| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` |
|
||||
| ```HeadersRegexp(`key`, `regexp`)``` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` |
|
||||
| ```Host(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. |
|
||||
| ```HostHeader(`example.com`, ...)``` | Same as `Host`, only exists for historical reasons. |
|
||||
| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Match the request domain. See "Regexp Syntax" below. |
|
||||
| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`) |
|
||||
| ```Path(`/path`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`, ...)``` | Match exact request path. See "Regexp Syntax" below. |
|
||||
| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. See "Regexp Syntax" below. |
|
||||
| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
||||
| ```ClientIP(`10.0.0.0/16`, `::1`)``` | Match if the request client IP is one of the given IP/CIDR. It accepts IPv4, IPv6 and CIDR formats. |
|
||||
| Rule | Description |
|
||||
|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
|
||||
| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` |
|
||||
| ```HeadersRegexp(`key`, `regexp`)``` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` |
|
||||
| ```Host(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. |
|
||||
| ```HostHeader(`example.com`, ...)``` | Same as `Host`, only exists for historical reasons. |
|
||||
| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Match the request domain. See "Regexp Syntax" below. |
|
||||
| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`) |
|
||||
| ```Path(`/path`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`, ...)``` | Match exact request path. See "Regexp Syntax" below. |
|
||||
| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. See "Regexp Syntax" below. |
|
||||
| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
||||
| ```ClientIP(`10.0.0.0/16`, `::1`)``` | Match if the request client IP is one of the given IP/CIDR. It accepts IPv4, IPv6 and CIDR formats. |
|
||||
|
||||
!!! important "Non-ASCII Domain Names"
|
||||
|
||||
|
@ -1041,6 +1041,30 @@ By default, a router with a TLS section will terminate the TLS connections, mean
|
|||
[tcp.routers.Router-1.tls]
|
||||
```
|
||||
|
||||
??? info "Postgres STARTTLS"
|
||||
|
||||
Traefik supports the Postgres STARTTLS protocol,
|
||||
which allows TLS routing for Postgres connections.
|
||||
|
||||
To do so, Traefik reads the first bytes sent by a Postgres client,
|
||||
identifies if they correspond to the message of a STARTTLS negotiation,
|
||||
and, if so, acknowledges and signals the client that it can start the TLS handshake.
|
||||
|
||||
Please note/remember that there are subtleties inherent to STARTTLS in whether
|
||||
the connection ends up being a TLS one or not. These subtleties depend on the
|
||||
`sslmode` value in the client configuration (and on the server authentication
|
||||
rules). Therefore, it is recommended to use the `require` value for the
|
||||
`sslmode`.
|
||||
|
||||
Afterwards, the TLS handshake, and routing based on TLS, can proceed as expected.
|
||||
|
||||
!!! warning "Postgres STARTTLS with TCP TLS PassThrough routers"
|
||||
|
||||
As mentioned above, the `sslmode` configuration parameter does have an impact on
|
||||
whether a STARTTLS session will succeed. In particular in the context of TCP TLS
|
||||
PassThrough, some of the values (such as `allow`) do not even make sense. Which
|
||||
is why, once more it is recommended to use the `require` value.
|
||||
|
||||
#### `passthrough`
|
||||
|
||||
As seen above, a TLS router will terminate the TLS connection by default.
|
||||
|
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
|
||||
github.com/andybalholm/brotli v1.0.4
|
||||
github.com/aws/aws-sdk-go v1.44.47
|
||||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
github.com/compose-spec/compose-go v1.0.3
|
||||
|
|
2
go.sum
2
go.sum
|
@ -212,6 +212,8 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m
|
|||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
||||
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
|
|
|
@ -749,7 +749,8 @@ spec:
|
|||
excludedContentTypes:
|
||||
description: ExcludedContentTypes defines the list of content
|
||||
types to compare the Content-Type header of the incoming requests
|
||||
and responses before compressing.
|
||||
and responses before compressing. `application/grpc` is always
|
||||
excluded.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
|
|
15
integration/testdata/rawdata-consul.json
vendored
15
integration/testdata/rawdata-consul.json
vendored
|
@ -179,7 +179,10 @@
|
|||
"url": "http://10.0.1.1:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
@ -201,7 +204,10 @@
|
|||
"url": "http://10.0.1.2:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
|
@ -215,7 +221,10 @@
|
|||
"url": "http://10.0.1.3:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
}
|
||||
|
|
|
@ -47,7 +47,10 @@
|
|||
"url": "http://10.42.0.4:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
|
18
integration/testdata/rawdata-crd.json
vendored
18
integration/testdata/rawdata-crd.json
vendored
|
@ -137,7 +137,10 @@
|
|||
"url": "http://10.42.0.7:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
@ -158,7 +161,10 @@
|
|||
"url": "http://10.42.0.7:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
@ -180,6 +186,9 @@
|
|||
}
|
||||
],
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
},
|
||||
"serversTransport": "default-mytransport@kubernetescrd"
|
||||
},
|
||||
"status": "enabled",
|
||||
|
@ -201,7 +210,10 @@
|
|||
"url": "http://10.42.0.7:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"serverStatus": {
|
||||
|
|
15
integration/testdata/rawdata-etcd.json
vendored
15
integration/testdata/rawdata-etcd.json
vendored
|
@ -179,7 +179,10 @@
|
|||
"url": "http://10.0.1.1:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
@ -201,7 +204,10 @@
|
|||
"url": "http://10.0.1.2:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
|
@ -215,7 +221,10 @@
|
|||
"url": "http://10.0.1.3:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
}
|
||||
|
|
5
integration/testdata/rawdata-gateway.json
vendored
5
integration/testdata/rawdata-gateway.json
vendored
|
@ -128,7 +128,10 @@
|
|||
"url": "http://10.42.0.7:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"serverStatus": {
|
||||
|
|
|
@ -88,7 +88,10 @@
|
|||
"url": "http://10.42.0.7:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
|
10
integration/testdata/rawdata-ingress.json
vendored
10
integration/testdata/rawdata-ingress.json
vendored
|
@ -121,7 +121,10 @@
|
|||
"url": "XXXX"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
@ -143,7 +146,10 @@
|
|||
"url": "http://10.42.0.8:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
|
|
@ -88,7 +88,10 @@
|
|||
"url": "http://10.42.0.5:80"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
|
15
integration/testdata/rawdata-redis.json
vendored
15
integration/testdata/rawdata-redis.json
vendored
|
@ -179,7 +179,10 @@
|
|||
"url": "http://10.0.1.1:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
@ -201,7 +204,10 @@
|
|||
"url": "http://10.0.1.2:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
|
@ -215,7 +221,10 @@
|
|||
"url": "http://10.0.1.3:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
}
|
||||
|
|
15
integration/testdata/rawdata-zk.json
vendored
15
integration/testdata/rawdata-zk.json
vendored
|
@ -179,7 +179,10 @@
|
|||
"url": "http://10.0.1.1:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled",
|
||||
"usedBy": [
|
||||
|
@ -201,7 +204,10 @@
|
|||
"url": "http://10.0.1.2:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
},
|
||||
|
@ -215,7 +221,10 @@
|
|||
"url": "http://10.0.1.3:8889"
|
||||
}
|
||||
],
|
||||
"passHostHeader": true
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "100ms"
|
||||
}
|
||||
},
|
||||
"status": "enabled"
|
||||
}
|
||||
|
|
|
@ -425,14 +425,14 @@
|
|||
mode = "foobar"
|
||||
path = "foobar"
|
||||
port = 42
|
||||
interval = "foobar"
|
||||
timeout = "foobar"
|
||||
interval = "10s"
|
||||
timeout = "10s"
|
||||
hostname = "foobar"
|
||||
[http.services.Service0.loadBalancer.healthCheck.headers]
|
||||
name0 = "foobar"
|
||||
name1 = "foobar"
|
||||
[http.services.Service0.loadBalancer.responseForwarding]
|
||||
flushInterval = "foobar"
|
||||
flushInterval = "10s"
|
||||
|
||||
[tcp]
|
||||
[tcp.routers]
|
||||
|
|
|
@ -9,6 +9,19 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHealthCheckInterval is the default value for the ServerHealthCheck interval.
|
||||
DefaultHealthCheckInterval = ptypes.Duration(30 * time.Second)
|
||||
// DefaultHealthCheckTimeout is the default value for the ServerHealthCheck timeout.
|
||||
DefaultHealthCheckTimeout = ptypes.Duration(5 * time.Second)
|
||||
|
||||
// DefaultPassHostHeader is the default value for the ServersLoadBalancer passHostHeader.
|
||||
DefaultPassHostHeader = true
|
||||
|
||||
// DefaultFlushInterval is the default value for the ResponseForwarding flush interval.
|
||||
DefaultFlushInterval = ptypes.Duration(100 * time.Millisecond)
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// HTTPConfiguration contains all the HTTP configuration parameters.
|
||||
|
@ -178,8 +191,11 @@ func (l *ServersLoadBalancer) Mergeable(loadBalancer *ServersLoadBalancer) bool
|
|||
|
||||
// SetDefaults Default values for a ServersLoadBalancer.
|
||||
func (l *ServersLoadBalancer) SetDefaults() {
|
||||
defaultPassHostHeader := true
|
||||
defaultPassHostHeader := DefaultPassHostHeader
|
||||
l.PassHostHeader = &defaultPassHostHeader
|
||||
|
||||
l.ResponseForwarding = &ResponseForwarding{}
|
||||
l.ResponseForwarding.SetDefaults()
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -191,7 +207,12 @@ type ResponseForwarding struct {
|
|||
// This configuration is ignored when ReverseProxy recognizes a response as a streaming response;
|
||||
// for such responses, writes are flushed to the client immediately.
|
||||
// Default: 100ms
|
||||
FlushInterval string `json:"flushInterval,omitempty" toml:"flushInterval,omitempty" yaml:"flushInterval,omitempty" export:"true"`
|
||||
FlushInterval ptypes.Duration `json:"flushInterval,omitempty" toml:"flushInterval,omitempty" yaml:"flushInterval,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults Default values for a ResponseForwarding.
|
||||
func (r *ResponseForwarding) SetDefaults() {
|
||||
r.FlushInterval = DefaultFlushInterval
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -212,15 +233,13 @@ func (s *Server) SetDefaults() {
|
|||
|
||||
// ServerHealthCheck holds the HealthCheck configuration.
|
||||
type ServerHealthCheck struct {
|
||||
Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"`
|
||||
Mode string `json:"mode,omitempty" toml:"mode,omitempty" yaml:"mode,omitempty" export:"true"`
|
||||
Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"`
|
||||
Method string `json:"method,omitempty" toml:"method,omitempty" yaml:"method,omitempty" export:"true"`
|
||||
Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty" export:"true"`
|
||||
// TODO change string to ptypes.Duration
|
||||
Interval string `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"`
|
||||
// TODO change string to ptypes.Duration
|
||||
Timeout string `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"`
|
||||
Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"`
|
||||
Mode string `json:"mode,omitempty" toml:"mode,omitempty" yaml:"mode,omitempty" export:"true"`
|
||||
Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"`
|
||||
Method string `json:"method,omitempty" toml:"method,omitempty" yaml:"method,omitempty" export:"true"`
|
||||
Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty" export:"true"`
|
||||
Interval ptypes.Duration `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"`
|
||||
Timeout ptypes.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"`
|
||||
Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"`
|
||||
FollowRedirects *bool `json:"followRedirects" toml:"followRedirects" yaml:"followRedirects" export:"true"`
|
||||
Headers map[string]string `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"`
|
||||
|
@ -231,6 +250,8 @@ func (h *ServerHealthCheck) SetDefaults() {
|
|||
fr := true
|
||||
h.FollowRedirects = &fr
|
||||
h.Mode = "http"
|
||||
h.Interval = DefaultHealthCheckInterval
|
||||
h.Timeout = DefaultHealthCheckTimeout
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -161,6 +161,7 @@ func (c *CircuitBreaker) SetDefaults() {
|
|||
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/
|
||||
type Compress struct {
|
||||
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
||||
// `application/grpc` is always excluded.
|
||||
ExcludedContentTypes []string `json:"excludedContentTypes,omitempty" toml:"excludedContentTypes,omitempty" yaml:"excludedContentTypes,omitempty" export:"true"`
|
||||
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
|
||||
// Default: 1024.
|
||||
|
|
|
@ -148,16 +148,16 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"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.interval": "1s",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.path": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.method": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.port": "42",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.scheme": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.mode": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.timeout": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.timeout": "1s",
|
||||
"traefik.http.services.Service0.loadbalancer.healthcheck.followredirects": "true",
|
||||
"traefik.http.services.Service0.loadbalancer.passhostheader": "true",
|
||||
"traefik.http.services.Service0.loadbalancer.responseforwarding.flushinterval": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.responseforwarding.flushinterval": "1s",
|
||||
"traefik.http.services.Service0.loadbalancer.server.scheme": "foobar",
|
||||
"traefik.http.services.Service0.loadbalancer.server.port": "8080",
|
||||
"traefik.http.services.Service0.loadbalancer.sticky.cookie.name": "foobar",
|
||||
|
@ -165,16 +165,16 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"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.interval": "1s",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.path": "foobar",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.method": "foobar",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.port": "42",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.scheme": "foobar",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.mode": "foobar",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.timeout": "foobar",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.timeout": "1s",
|
||||
"traefik.http.services.Service1.loadbalancer.healthcheck.followredirects": "true",
|
||||
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
|
||||
"traefik.http.services.Service1.loadbalancer.responseforwarding.flushinterval": "foobar",
|
||||
"traefik.http.services.Service1.loadbalancer.responseforwarding.flushinterval": "1s",
|
||||
"traefik.http.services.Service1.loadbalancer.server.scheme": "foobar",
|
||||
"traefik.http.services.Service1.loadbalancer.server.port": "8080",
|
||||
"traefik.http.services.Service1.loadbalancer.sticky": "false",
|
||||
|
@ -656,8 +656,8 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
Path: "foobar",
|
||||
Method: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Interval: ptypes.Duration(time.Second),
|
||||
Timeout: ptypes.Duration(time.Second),
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
|
@ -667,7 +667,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
},
|
||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
FlushInterval: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -685,8 +685,8 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
Path: "foobar",
|
||||
Method: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Interval: ptypes.Duration(time.Second),
|
||||
Timeout: ptypes.Duration(time.Second),
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
|
@ -696,7 +696,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
},
|
||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
FlushInterval: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1148,8 +1148,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
Path: "foobar",
|
||||
Method: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Interval: ptypes.Duration(time.Second),
|
||||
Timeout: ptypes.Duration(time.Second),
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
|
@ -1158,7 +1158,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
},
|
||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
FlushInterval: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1175,8 +1175,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
Path: "foobar",
|
||||
Method: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Interval: ptypes.Duration(time.Second),
|
||||
Timeout: ptypes.Duration(time.Second),
|
||||
Hostname: "foobar",
|
||||
Headers: map[string]string{
|
||||
"name0": "foobar",
|
||||
|
@ -1185,7 +1185,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
},
|
||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
FlushInterval: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1332,14 +1332,14 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
|
||||
"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.Interval": "1000000000",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Path": "foobar",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Method": "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.HealthCheck.Timeout": "1000000000",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.PassHostHeader": "true",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.ResponseForwarding.FlushInterval": "foobar",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.ResponseForwarding.FlushInterval": "1000000000",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.server.Port": "8080",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.server.Scheme": "foobar",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.Name": "foobar",
|
||||
|
@ -1348,14 +1348,14 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"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.Interval": "1000000000",
|
||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Path": "foobar",
|
||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Method": "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.HealthCheck.Timeout": "1000000000",
|
||||
"traefik.HTTP.Services.Service1.LoadBalancer.PassHostHeader": "true",
|
||||
"traefik.HTTP.Services.Service1.LoadBalancer.ResponseForwarding.FlushInterval": "foobar",
|
||||
"traefik.HTTP.Services.Service1.LoadBalancer.ResponseForwarding.FlushInterval": "1000000000",
|
||||
"traefik.HTTP.Services.Service1.LoadBalancer.server.Port": "8080",
|
||||
"traefik.HTTP.Services.Service1.LoadBalancer.server.Scheme": "foobar",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
||||
|
|
|
@ -15,6 +15,12 @@ const (
|
|||
StatusWarning = "warning"
|
||||
)
|
||||
|
||||
// Status of the servers.
|
||||
const (
|
||||
StatusUp = "UP"
|
||||
StatusDown = "DOWN"
|
||||
)
|
||||
|
||||
// Configuration holds the information about the currently running traefik instance.
|
||||
type Configuration struct {
|
||||
Routers map[string]*RouterInfo `json:"routers,omitempty"`
|
||||
|
|
|
@ -2,9 +2,11 @@ package runtime_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||
)
|
||||
|
@ -49,7 +51,7 @@ func TestPopulateUsedBy(t *testing.T) {
|
|||
{URL: "http://127.0.0.1:8086"},
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Interval: "500ms",
|
||||
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||
Path: "/health",
|
||||
},
|
||||
},
|
||||
|
@ -159,7 +161,7 @@ func TestPopulateUsedBy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Interval: "500ms",
|
||||
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||
Path: "/health",
|
||||
},
|
||||
},
|
||||
|
@ -177,7 +179,7 @@ func TestPopulateUsedBy(t *testing.T) {
|
|||
},
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Interval: "500ms",
|
||||
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||
Path: "/health",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -8,17 +8,12 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gokitmetrics "github.com/go-kit/kit/metrics"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/metrics"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
@ -26,267 +21,153 @@ import (
|
|||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
serverUp = "UP"
|
||||
serverDown = "DOWN"
|
||||
)
|
||||
const modeGRPC = "grpc"
|
||||
|
||||
const (
|
||||
HTTPMode = "http"
|
||||
GRPCMode = "grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
singleton *HealthCheck
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Balancer is the set of operations required to manage the list of servers in a load-balancer.
|
||||
type Balancer interface {
|
||||
Servers() []*url.URL
|
||||
RemoveServer(u *url.URL) error
|
||||
UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error
|
||||
// StatusSetter should be implemented by a service that, when the status of a
|
||||
// registered target change, needs to be notified of that change.
|
||||
type StatusSetter interface {
|
||||
SetStatus(ctx context.Context, childName string, up bool)
|
||||
}
|
||||
|
||||
// BalancerHandler includes functionality for load-balancing management.
|
||||
type BalancerHandler interface {
|
||||
ServeHTTP(w http.ResponseWriter, req *http.Request)
|
||||
Balancer
|
||||
// StatusUpdater should be implemented by a service that, when its status
|
||||
// changes (e.g. all if its children are down), needs to propagate upwards (to
|
||||
// their parent(s)) that change.
|
||||
type StatusUpdater interface {
|
||||
RegisterStatusUpdater(fn func(up bool)) error
|
||||
}
|
||||
|
||||
// BalancerStatusHandler is an http Handler that does load-balancing,
|
||||
// and updates its parents of its status.
|
||||
type BalancerStatusHandler interface {
|
||||
BalancerHandler
|
||||
StatusUpdater
|
||||
type metricsHealthCheck interface {
|
||||
ServiceServerUpGauge() gokitmetrics.Gauge
|
||||
}
|
||||
|
||||
type metricsHealthcheck struct {
|
||||
serverUpGauge gokitmetrics.Gauge
|
||||
type ServiceHealthChecker struct {
|
||||
balancer StatusSetter
|
||||
info *runtime.ServiceInfo
|
||||
|
||||
config *dynamic.ServerHealthCheck
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
|
||||
metrics metricsHealthCheck
|
||||
|
||||
client *http.Client
|
||||
targets map[string]*url.URL
|
||||
}
|
||||
|
||||
// Options are the public health check options.
|
||||
type Options struct {
|
||||
Headers map[string]string
|
||||
Hostname string
|
||||
Scheme string
|
||||
Mode string
|
||||
Path string
|
||||
Method string
|
||||
Port int
|
||||
FollowRedirects bool
|
||||
Transport http.RoundTripper
|
||||
Interval time.Duration
|
||||
Timeout time.Duration
|
||||
LB Balancer
|
||||
}
|
||||
|
||||
func (opt Options) String() string {
|
||||
return fmt.Sprintf("[Hostname: %s Headers: %v Path: %s Method: %s Port: %d Interval: %s Timeout: %s FollowRedirects: %v]", opt.Hostname, opt.Headers, opt.Path, opt.Method, opt.Port, opt.Interval, opt.Timeout, opt.FollowRedirects)
|
||||
}
|
||||
|
||||
type backendURL struct {
|
||||
url *url.URL
|
||||
weight int
|
||||
}
|
||||
|
||||
// BackendConfig HealthCheck configuration for a backend.
|
||||
type BackendConfig struct {
|
||||
Options
|
||||
name string
|
||||
disabledURLs []backendURL
|
||||
}
|
||||
|
||||
func (b *BackendConfig) newRequest(serverURL *url.URL) (*http.Request, error) {
|
||||
u, err := serverURL.Parse(b.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(b.Scheme) > 0 {
|
||||
u.Scheme = b.Scheme
|
||||
}
|
||||
|
||||
if b.Port != 0 {
|
||||
u.Host = net.JoinHostPort(u.Hostname(), strconv.Itoa(b.Port))
|
||||
}
|
||||
|
||||
return http.NewRequest(http.MethodGet, u.String(), http.NoBody)
|
||||
}
|
||||
|
||||
// setRequestOptions sets all request options present on the BackendConfig.
|
||||
func (b *BackendConfig) setRequestOptions(req *http.Request) *http.Request {
|
||||
if b.Options.Hostname != "" {
|
||||
req.Host = b.Options.Hostname
|
||||
}
|
||||
|
||||
for k, v := range b.Options.Headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
if b.Options.Method != "" {
|
||||
req.Method = strings.ToUpper(b.Options.Method)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// HealthCheck struct.
|
||||
type HealthCheck struct {
|
||||
Backends map[string]*BackendConfig
|
||||
metrics metricsHealthcheck
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// SetBackendsConfiguration set backends configuration.
|
||||
func (hc *HealthCheck) SetBackendsConfiguration(parentCtx context.Context, backends map[string]*BackendConfig) {
|
||||
hc.Backends = backends
|
||||
if hc.cancel != nil {
|
||||
hc.cancel()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
hc.cancel = cancel
|
||||
|
||||
for _, backend := range backends {
|
||||
currentBackend := backend
|
||||
safe.Go(func() {
|
||||
hc.execute(ctx, currentBackend)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) execute(ctx context.Context, backend *BackendConfig) {
|
||||
func NewServiceHealthChecker(ctx context.Context, metrics metricsHealthCheck, config *dynamic.ServerHealthCheck, service StatusSetter, info *runtime.ServiceInfo, transport http.RoundTripper, targets map[string]*url.URL) *ServiceHealthChecker {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
logger.Debugf("Initial health check for backend: %q", backend.name)
|
||||
hc.checkServersLB(ctx, backend)
|
||||
|
||||
ticker := time.NewTicker(backend.Interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Debugf("Stopping current health check goroutines of backend: %s", backend.name)
|
||||
return
|
||||
case <-ticker.C:
|
||||
logger.Debugf("Routine health check refresh for backend: %s", backend.name)
|
||||
hc.checkServersLB(ctx, backend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) checkServersLB(ctx context.Context, backend *BackendConfig) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
enabledURLs := backend.LB.Servers()
|
||||
|
||||
var newDisabledURLs []backendURL
|
||||
for _, disabledURL := range backend.disabledURLs {
|
||||
serverUpMetricValue := float64(0)
|
||||
|
||||
if err := checkHealth(disabledURL.url, backend); err == nil {
|
||||
logger.Warnf("Health check up: returning to server list. Backend: %q URL: %q Weight: %d",
|
||||
backend.name, disabledURL.url.String(), disabledURL.weight)
|
||||
if err = backend.LB.UpsertServer(disabledURL.url, roundrobin.Weight(disabledURL.weight)); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
serverUpMetricValue = 1
|
||||
} else {
|
||||
logger.Warnf("Health check still failing. Backend: %q URL: %q Reason: %s", backend.name, disabledURL.url.String(), err)
|
||||
newDisabledURLs = append(newDisabledURLs, disabledURL)
|
||||
}
|
||||
|
||||
labelValues := []string{"service", backend.name, "url", disabledURL.url.String()}
|
||||
hc.metrics.serverUpGauge.With(labelValues...).Set(serverUpMetricValue)
|
||||
interval := time.Duration(config.Interval)
|
||||
if interval <= 0 {
|
||||
logger.Error("Health check interval smaller than zero")
|
||||
interval = time.Duration(dynamic.DefaultHealthCheckInterval)
|
||||
}
|
||||
|
||||
backend.disabledURLs = newDisabledURLs
|
||||
|
||||
for _, enabledURL := range enabledURLs {
|
||||
serverUpMetricValue := float64(1)
|
||||
|
||||
if err := checkHealth(enabledURL, backend); err != nil {
|
||||
weight := 1
|
||||
rr, ok := backend.LB.(*roundrobin.RoundRobin)
|
||||
if ok {
|
||||
var gotWeight bool
|
||||
weight, gotWeight = rr.ServerWeight(enabledURL)
|
||||
if !gotWeight {
|
||||
weight = 1
|
||||
}
|
||||
}
|
||||
|
||||
logger.Warnf("Health check failed, removing from server list. Backend: %q URL: %q Weight: %d Reason: %s",
|
||||
backend.name, enabledURL.String(), weight, err)
|
||||
if err := backend.LB.RemoveServer(enabledURL); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
backend.disabledURLs = append(backend.disabledURLs, backendURL{enabledURL, weight})
|
||||
serverUpMetricValue = 0
|
||||
}
|
||||
|
||||
labelValues := []string{"service", backend.name, "url", enabledURL.String()}
|
||||
hc.metrics.serverUpGauge.With(labelValues...).Set(serverUpMetricValue)
|
||||
}
|
||||
}
|
||||
|
||||
// GetHealthCheck returns the health check which is guaranteed to be a singleton.
|
||||
func GetHealthCheck(registry metrics.Registry) *HealthCheck {
|
||||
once.Do(func() {
|
||||
singleton = newHealthCheck(registry)
|
||||
})
|
||||
return singleton
|
||||
}
|
||||
|
||||
func newHealthCheck(registry metrics.Registry) *HealthCheck {
|
||||
return &HealthCheck{
|
||||
Backends: make(map[string]*BackendConfig),
|
||||
metrics: metricsHealthcheck{
|
||||
serverUpGauge: registry.ServiceServerUpGauge(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewBackendConfig Instantiate a new BackendConfig.
|
||||
func NewBackendConfig(options Options, backendName string) *BackendConfig {
|
||||
return &BackendConfig{
|
||||
Options: options,
|
||||
name: backendName,
|
||||
}
|
||||
}
|
||||
|
||||
// checkHealth calls the proper health check function depending on the
|
||||
// backend config mode, defaults to HTTP.
|
||||
func checkHealth(serverURL *url.URL, backend *BackendConfig) error {
|
||||
if backend.Options.Mode == GRPCMode {
|
||||
return checkHealthGRPC(serverURL, backend)
|
||||
}
|
||||
return checkHealthHTTP(serverURL, backend)
|
||||
}
|
||||
|
||||
// checkHealthHTTP returns an error with a meaningful description if the health check failed.
|
||||
// Dedicated to HTTP servers.
|
||||
func checkHealthHTTP(serverURL *url.URL, backend *BackendConfig) error {
|
||||
req, err := backend.newRequest(serverURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create HTTP request: %w", err)
|
||||
timeout := time.Duration(config.Timeout)
|
||||
if timeout <= 0 {
|
||||
logger.Error("Health check timeout smaller than zero")
|
||||
timeout = time.Duration(dynamic.DefaultHealthCheckTimeout)
|
||||
}
|
||||
|
||||
req = backend.setRequestOptions(req)
|
||||
|
||||
client := http.Client{
|
||||
Timeout: backend.Options.Timeout,
|
||||
Transport: backend.Options.Transport,
|
||||
if timeout >= interval {
|
||||
logger.Warnf("Health check timeout should be lower than the health check interval. Interval set to timeout + 1 second (%s).", interval)
|
||||
interval = timeout + time.Second
|
||||
}
|
||||
|
||||
if !backend.FollowRedirects {
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
if config.FollowRedirects != nil && !*config.FollowRedirects {
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
return &ServiceHealthChecker{
|
||||
balancer: service,
|
||||
info: info,
|
||||
config: config,
|
||||
interval: interval,
|
||||
timeout: timeout,
|
||||
targets: targets,
|
||||
client: client,
|
||||
metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
func (shc *ServiceHealthChecker) Launch(ctx context.Context) {
|
||||
ticker := time.NewTicker(shc.interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
for proxyName, target := range shc.targets {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
up := true
|
||||
serverUpMetricValue := float64(1)
|
||||
|
||||
if err := shc.executeHealthCheck(ctx, shc.config, target); err != nil {
|
||||
// The context is canceled when the dynamic configuration is refreshed.
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
|
||||
log.FromContext(ctx).
|
||||
WithField("targetURL", target.String()).
|
||||
WithError(err).
|
||||
Warn("Health check failed.")
|
||||
|
||||
up = false
|
||||
serverUpMetricValue = float64(0)
|
||||
}
|
||||
|
||||
shc.balancer.SetStatus(ctx, proxyName, up)
|
||||
|
||||
statusStr := runtime.StatusDown
|
||||
if up {
|
||||
statusStr = runtime.StatusUp
|
||||
}
|
||||
|
||||
shc.info.UpdateServerStatus(target.String(), statusStr)
|
||||
|
||||
shc.metrics.ServiceServerUpGauge().
|
||||
With("service", proxyName).
|
||||
With("url", target.String()).
|
||||
Set(serverUpMetricValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (shc *ServiceHealthChecker) executeHealthCheck(ctx context.Context, config *dynamic.ServerHealthCheck, target *url.URL) error {
|
||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(shc.timeout))
|
||||
defer cancel()
|
||||
|
||||
if config.Mode == modeGRPC {
|
||||
return shc.checkHealthGRPC(ctx, target)
|
||||
}
|
||||
return shc.checkHealthHTTP(ctx, target)
|
||||
}
|
||||
|
||||
// checkHealthHTTP returns an error with a meaningful description if the health check failed.
|
||||
// Dedicated to HTTP servers.
|
||||
func (shc *ServiceHealthChecker) checkHealthHTTP(ctx context.Context, target *url.URL) error {
|
||||
req, err := shc.newRequest(ctx, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create HTTP request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := shc.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTP request failed: %w", err)
|
||||
}
|
||||
|
@ -300,34 +181,61 @@ func checkHealthHTTP(serverURL *url.URL, backend *BackendConfig) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (shc *ServiceHealthChecker) newRequest(ctx context.Context, target *url.URL) (*http.Request, error) {
|
||||
u, err := target.Parse(shc.config.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(shc.config.Scheme) > 0 {
|
||||
u.Scheme = shc.config.Scheme
|
||||
}
|
||||
|
||||
if shc.config.Port != 0 {
|
||||
u.Host = net.JoinHostPort(u.Hostname(), strconv.Itoa(shc.config.Port))
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, shc.config.Method, u.String(), http.NoBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
||||
}
|
||||
|
||||
if shc.config.Hostname != "" {
|
||||
req.Host = shc.config.Hostname
|
||||
}
|
||||
|
||||
for k, v := range shc.config.Headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// checkHealthGRPC returns an error with a meaningful description if the health check failed.
|
||||
// Dedicated to gRPC servers implementing gRPC Health Checking Protocol v1.
|
||||
func checkHealthGRPC(serverURL *url.URL, backend *BackendConfig) error {
|
||||
u, err := serverURL.Parse(backend.Path)
|
||||
func (shc *ServiceHealthChecker) checkHealthGRPC(ctx context.Context, serverURL *url.URL) error {
|
||||
u, err := serverURL.Parse(shc.config.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse server URL: %w", err)
|
||||
}
|
||||
|
||||
port := u.Port()
|
||||
if backend.Options.Port != 0 {
|
||||
port = strconv.Itoa(backend.Options.Port)
|
||||
if shc.config.Port != 0 {
|
||||
port = strconv.Itoa(shc.config.Port)
|
||||
}
|
||||
|
||||
serverAddr := net.JoinHostPort(u.Hostname(), port)
|
||||
|
||||
var opts []grpc.DialOption
|
||||
switch backend.Options.Scheme {
|
||||
switch shc.config.Scheme {
|
||||
case "http", "h2c", "":
|
||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), backend.Options.Timeout)
|
||||
defer cancel()
|
||||
|
||||
conn, err := grpc.DialContext(ctx, serverAddr, opts...)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return fmt.Errorf("fail to connect to %s within %s: %w", serverAddr, backend.Options.Timeout, err)
|
||||
return fmt.Errorf("fail to connect to %s within %s: %w", serverAddr, shc.config.Timeout, err)
|
||||
}
|
||||
return fmt.Errorf("fail to connect to %s: %w", serverAddr, err)
|
||||
}
|
||||
|
@ -341,6 +249,8 @@ func checkHealthGRPC(serverURL *url.URL, backend *BackendConfig) error {
|
|||
return fmt.Errorf("gRPC server does not implement the health protocol: %w", err)
|
||||
case codes.DeadlineExceeded:
|
||||
return fmt.Errorf("gRPC health check timeout: %w", err)
|
||||
case codes.Canceled:
|
||||
return context.Canceled
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,155 +263,3 @@ func checkHealthGRPC(serverURL *url.URL, backend *BackendConfig) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatusUpdater should be implemented by a service that, when its status
|
||||
// changes (e.g. all if its children are down), needs to propagate upwards (to
|
||||
// their parent(s)) that change.
|
||||
type StatusUpdater interface {
|
||||
RegisterStatusUpdater(fn func(up bool)) error
|
||||
}
|
||||
|
||||
// NewLBStatusUpdater returns a new LbStatusUpdater.
|
||||
func NewLBStatusUpdater(bh BalancerHandler, info *runtime.ServiceInfo, hc *dynamic.ServerHealthCheck) *LbStatusUpdater {
|
||||
return &LbStatusUpdater{
|
||||
BalancerHandler: bh,
|
||||
serviceInfo: info,
|
||||
wantsHealthCheck: hc != nil,
|
||||
}
|
||||
}
|
||||
|
||||
// LbStatusUpdater wraps a BalancerHandler and a ServiceInfo,
|
||||
// so it can keep track of the status of a server in the ServiceInfo.
|
||||
type LbStatusUpdater struct {
|
||||
BalancerHandler
|
||||
serviceInfo *runtime.ServiceInfo // can be nil
|
||||
updaters []func(up bool)
|
||||
wantsHealthCheck bool
|
||||
}
|
||||
|
||||
// RegisterStatusUpdater adds fn to the list of hooks that are run when the
|
||||
// status of the Balancer changes.
|
||||
// Not thread safe.
|
||||
func (lb *LbStatusUpdater) RegisterStatusUpdater(fn func(up bool)) error {
|
||||
if !lb.wantsHealthCheck {
|
||||
return errors.New("healthCheck not enabled in config for this loadbalancer service")
|
||||
}
|
||||
|
||||
lb.updaters = append(lb.updaters, fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveServer removes the given server from the BalancerHandler,
|
||||
// and updates the status of the server to "DOWN".
|
||||
func (lb *LbStatusUpdater) RemoveServer(u *url.URL) error {
|
||||
// TODO(mpl): when we have the freedom to change the signature of RemoveServer
|
||||
// (kinda stuck because of oxy for now), let's pass around a context to improve
|
||||
// logging.
|
||||
ctx := context.TODO()
|
||||
upBefore := len(lb.BalancerHandler.Servers()) > 0
|
||||
err := lb.BalancerHandler.RemoveServer(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lb.serviceInfo != nil {
|
||||
lb.serviceInfo.UpdateServerStatus(u.String(), serverDown)
|
||||
}
|
||||
log.FromContext(ctx).Debugf("child %s now %s", u.String(), serverDown)
|
||||
|
||||
if !upBefore {
|
||||
// we were already down, and we still are, no need to propagate.
|
||||
log.FromContext(ctx).Debugf("Still %s, no need to propagate", serverDown)
|
||||
return nil
|
||||
}
|
||||
if len(lb.BalancerHandler.Servers()) > 0 {
|
||||
// we were up, and we still are, no need to propagate
|
||||
log.FromContext(ctx).Debugf("Still %s, no need to propagate", serverUp)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.FromContext(ctx).Debugf("Propagating new %s status", serverDown)
|
||||
for _, fn := range lb.updaters {
|
||||
fn(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpsertServer adds the given server to the BalancerHandler,
|
||||
// and updates the status of the server to "UP".
|
||||
func (lb *LbStatusUpdater) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
|
||||
ctx := context.TODO()
|
||||
upBefore := len(lb.BalancerHandler.Servers()) > 0
|
||||
err := lb.BalancerHandler.UpsertServer(u, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lb.serviceInfo != nil {
|
||||
lb.serviceInfo.UpdateServerStatus(u.String(), serverUp)
|
||||
}
|
||||
log.FromContext(ctx).Debugf("child %s now %s", u.String(), serverUp)
|
||||
|
||||
if upBefore {
|
||||
// we were up, and we still are, no need to propagate
|
||||
log.FromContext(ctx).Debugf("Still %s, no need to propagate", serverUp)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.FromContext(ctx).Debugf("Propagating new %s status", serverUp)
|
||||
for _, fn := range lb.updaters {
|
||||
fn(true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Balancers is a list of Balancers(s) that implements the Balancer interface.
|
||||
type Balancers []Balancer
|
||||
|
||||
// Servers returns the deduplicated server URLs from all the Balancer.
|
||||
// Note that the deduplication is only possible because all the underlying
|
||||
// balancers are of the same kind (the oxy implementation).
|
||||
// The comparison property is the same as the one found at:
|
||||
// https://github.com/vulcand/oxy/blob/fb2728c857b7973a27f8de2f2190729c0f22cf49/roundrobin/rr.go#L347.
|
||||
func (b Balancers) Servers() []*url.URL {
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
var servers []*url.URL
|
||||
for _, lb := range b {
|
||||
for _, server := range lb.Servers() {
|
||||
key := serverKey(server)
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
servers = append(servers, server)
|
||||
seen[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
// RemoveServer removes the given server from all the Balancer,
|
||||
// and updates the status of the server to "DOWN".
|
||||
func (b Balancers) RemoveServer(u *url.URL) error {
|
||||
for _, lb := range b {
|
||||
if err := lb.RemoveServer(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpsertServer adds the given server to all the Balancer,
|
||||
// and updates the status of the server to "UP".
|
||||
func (b Balancers) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
|
||||
for _, lb := range b {
|
||||
if err := lb.UpsertServer(u, options...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func serverKey(u *url.URL) string {
|
||||
return u.Path + u.Host + u.Scheme
|
||||
}
|
||||
|
|
|
@ -11,127 +11,324 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
const (
|
||||
healthCheckInterval = 200 * time.Millisecond
|
||||
healthCheckTimeout = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
func TestSetBackendsConfiguration(t *testing.T) {
|
||||
func TestServiceHealthChecker_newRequest(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
startHealthy bool
|
||||
mode string
|
||||
server StartTestServer
|
||||
expectedNumRemovedServers int
|
||||
expectedNumUpsertedServers int
|
||||
expectedGaugeValue float64
|
||||
desc string
|
||||
targetURL string
|
||||
config dynamic.ServerHealthCheck
|
||||
expTarget string
|
||||
expError bool
|
||||
expHostname string
|
||||
expHeader string
|
||||
expMethod string
|
||||
}{
|
||||
{
|
||||
desc: "healthy server staying healthy",
|
||||
startHealthy: true,
|
||||
server: newHTTPServer(http.StatusOK),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "no port override",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: "/test",
|
||||
Port: 0,
|
||||
},
|
||||
expError: false,
|
||||
expTarget: "http://backend1:80/test",
|
||||
expHostname: "backend1:80",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "healthy server staying healthy (StatusNoContent)",
|
||||
startHealthy: true,
|
||||
server: newHTTPServer(http.StatusNoContent),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "port override",
|
||||
targetURL: "http://backend2:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: "/test",
|
||||
Port: 8080,
|
||||
},
|
||||
expError: false,
|
||||
expTarget: "http://backend2:8080/test",
|
||||
expHostname: "backend2:8080",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "healthy server staying healthy (StatusPermanentRedirect)",
|
||||
startHealthy: true,
|
||||
server: newHTTPServer(http.StatusPermanentRedirect),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "no port override with no port in server URL",
|
||||
targetURL: "http://backend1",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: "/health",
|
||||
Port: 0,
|
||||
},
|
||||
expError: false,
|
||||
expTarget: "http://backend1/health",
|
||||
expHostname: "backend1",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "healthy server becoming sick",
|
||||
startHealthy: true,
|
||||
server: newHTTPServer(http.StatusServiceUnavailable),
|
||||
expectedNumRemovedServers: 1,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 0,
|
||||
desc: "port override with no port in server URL",
|
||||
targetURL: "http://backend2",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: "/health",
|
||||
Port: 8080,
|
||||
},
|
||||
expError: false,
|
||||
expTarget: "http://backend2:8080/health",
|
||||
expHostname: "backend2:8080",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "sick server becoming healthy",
|
||||
startHealthy: false,
|
||||
server: newHTTPServer(http.StatusOK),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 1,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "scheme override",
|
||||
targetURL: "https://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Scheme: "http",
|
||||
Path: "/test",
|
||||
Port: 0,
|
||||
},
|
||||
expError: false,
|
||||
expTarget: "http://backend1:80/test",
|
||||
expHostname: "backend1:80",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "sick server staying sick",
|
||||
startHealthy: false,
|
||||
server: newHTTPServer(http.StatusServiceUnavailable),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 0,
|
||||
desc: "path with param",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: "/health?powpow=do",
|
||||
Port: 0,
|
||||
},
|
||||
expError: false,
|
||||
expTarget: "http://backend1:80/health?powpow=do",
|
||||
expHostname: "backend1:80",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "healthy server toggling to sick and back to healthy",
|
||||
startHealthy: true,
|
||||
server: newHTTPServer(http.StatusServiceUnavailable, http.StatusOK),
|
||||
expectedNumRemovedServers: 1,
|
||||
expectedNumUpsertedServers: 1,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "path with params",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: "/health?powpow=do&do=powpow",
|
||||
Port: 0,
|
||||
},
|
||||
expError: false,
|
||||
expTarget: "http://backend1:80/health?powpow=do&do=powpow",
|
||||
expHostname: "backend1:80",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "healthy grpc server staying healthy",
|
||||
mode: "grpc",
|
||||
startHealthy: true,
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_SERVING),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "path with invalid path",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: ":",
|
||||
Port: 0,
|
||||
},
|
||||
expError: true,
|
||||
expTarget: "",
|
||||
expHostname: "backend1:80",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "healthy grpc server becoming sick",
|
||||
mode: "grpc",
|
||||
startHealthy: true,
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING),
|
||||
expectedNumRemovedServers: 1,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 0,
|
||||
desc: "override hostname",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Hostname: "myhost",
|
||||
Path: "/",
|
||||
},
|
||||
expTarget: "http://backend1:80/",
|
||||
expHostname: "myhost",
|
||||
expHeader: "",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "sick grpc server becoming healthy",
|
||||
mode: "grpc",
|
||||
startHealthy: false,
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_SERVING),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 1,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "not override hostname",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Hostname: "",
|
||||
Path: "/",
|
||||
},
|
||||
expTarget: "http://backend1:80/",
|
||||
expHostname: "backend1:80",
|
||||
expHeader: "",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "sick grpc server staying sick",
|
||||
mode: "grpc",
|
||||
startHealthy: false,
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING),
|
||||
expectedNumRemovedServers: 0,
|
||||
expectedNumUpsertedServers: 0,
|
||||
expectedGaugeValue: 0,
|
||||
desc: "custom header",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Headers: map[string]string{"Custom-Header": "foo"},
|
||||
Hostname: "",
|
||||
Path: "/",
|
||||
},
|
||||
expTarget: "http://backend1:80/",
|
||||
expHostname: "backend1:80",
|
||||
expHeader: "foo",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "healthy grpc server toggling to sick and back to healthy",
|
||||
mode: "grpc",
|
||||
startHealthy: true,
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING, healthpb.HealthCheckResponse_SERVING),
|
||||
expectedNumRemovedServers: 1,
|
||||
expectedNumUpsertedServers: 1,
|
||||
expectedGaugeValue: 1,
|
||||
desc: "custom header with hostname override",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Headers: map[string]string{"Custom-Header": "foo"},
|
||||
Hostname: "myhost",
|
||||
Path: "/",
|
||||
},
|
||||
expTarget: "http://backend1:80/",
|
||||
expHostname: "myhost",
|
||||
expHeader: "foo",
|
||||
expMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "custom method",
|
||||
targetURL: "http://backend1:80",
|
||||
config: dynamic.ServerHealthCheck{
|
||||
Path: "/",
|
||||
Method: http.MethodHead,
|
||||
},
|
||||
expTarget: "http://backend1:80/",
|
||||
expHostname: "backend1:80",
|
||||
expMethod: http.MethodHead,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
shc := ServiceHealthChecker{config: &test.config}
|
||||
|
||||
u := testhelpers.MustParseURL(test.targetURL)
|
||||
req, err := shc.newRequest(context.Background(), u)
|
||||
|
||||
if test.expError {
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, req)
|
||||
} else {
|
||||
require.NoError(t, err, "failed to create new request")
|
||||
require.NotNil(t, req)
|
||||
|
||||
assert.Equal(t, test.expTarget, req.URL.String())
|
||||
assert.Equal(t, test.expHeader, req.Header.Get("Custom-Header"))
|
||||
assert.Equal(t, test.expHostname, req.Host)
|
||||
assert.Equal(t, test.expMethod, req.Method)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceHealthChecker_checkHealthHTTP_NotFollowingRedirects(t *testing.T) {
|
||||
redirectServerCalled := false
|
||||
redirectTestServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
redirectServerCalled = true
|
||||
}))
|
||||
defer redirectTestServer.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(dynamic.DefaultHealthCheckTimeout))
|
||||
defer cancel()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Add("location", redirectTestServer.URL)
|
||||
rw.WriteHeader(http.StatusSeeOther)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
config := &dynamic.ServerHealthCheck{
|
||||
Path: "/path",
|
||||
FollowRedirects: Bool(false),
|
||||
Interval: dynamic.DefaultHealthCheckInterval,
|
||||
Timeout: dynamic.DefaultHealthCheckTimeout,
|
||||
}
|
||||
healthChecker := NewServiceHealthChecker(ctx, nil, config, nil, nil, http.DefaultTransport, nil)
|
||||
|
||||
err := healthChecker.checkHealthHTTP(ctx, testhelpers.MustParseURL(server.URL))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, redirectServerCalled, "HTTP redirect must not be followed")
|
||||
}
|
||||
|
||||
func TestServiceHealthChecker_Launch(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
mode string
|
||||
server StartTestServer
|
||||
expNumRemovedServers int
|
||||
expNumUpsertedServers int
|
||||
expGaugeValue float64
|
||||
targetStatus string
|
||||
}{
|
||||
{
|
||||
desc: "healthy server staying healthy",
|
||||
server: newHTTPServer(http.StatusOK),
|
||||
expNumRemovedServers: 0,
|
||||
expNumUpsertedServers: 1,
|
||||
expGaugeValue: 1,
|
||||
targetStatus: runtime.StatusUp,
|
||||
},
|
||||
{
|
||||
desc: "healthy server staying healthy (StatusNoContent)",
|
||||
server: newHTTPServer(http.StatusNoContent),
|
||||
expNumRemovedServers: 0,
|
||||
expNumUpsertedServers: 1,
|
||||
expGaugeValue: 1,
|
||||
targetStatus: runtime.StatusUp,
|
||||
},
|
||||
{
|
||||
desc: "healthy server staying healthy (StatusPermanentRedirect)",
|
||||
server: newHTTPServer(http.StatusPermanentRedirect),
|
||||
expNumRemovedServers: 0,
|
||||
expNumUpsertedServers: 1,
|
||||
expGaugeValue: 1,
|
||||
targetStatus: runtime.StatusUp,
|
||||
},
|
||||
{
|
||||
desc: "healthy server becoming sick",
|
||||
server: newHTTPServer(http.StatusServiceUnavailable),
|
||||
expNumRemovedServers: 1,
|
||||
expNumUpsertedServers: 0,
|
||||
expGaugeValue: 0,
|
||||
targetStatus: runtime.StatusDown,
|
||||
},
|
||||
{
|
||||
desc: "healthy server toggling to sick and back to healthy",
|
||||
server: newHTTPServer(http.StatusServiceUnavailable, http.StatusOK),
|
||||
expNumRemovedServers: 1,
|
||||
expNumUpsertedServers: 1,
|
||||
expGaugeValue: 1,
|
||||
targetStatus: runtime.StatusUp,
|
||||
},
|
||||
{
|
||||
desc: "healthy server toggling to healthy and go to sick",
|
||||
server: newHTTPServer(http.StatusOK, http.StatusServiceUnavailable),
|
||||
expNumRemovedServers: 1,
|
||||
expNumUpsertedServers: 1,
|
||||
expGaugeValue: 0,
|
||||
targetStatus: runtime.StatusDown,
|
||||
},
|
||||
{
|
||||
desc: "healthy grpc server staying healthy",
|
||||
mode: "grpc",
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_SERVING),
|
||||
expNumRemovedServers: 0,
|
||||
expNumUpsertedServers: 1,
|
||||
expGaugeValue: 1,
|
||||
targetStatus: runtime.StatusUp,
|
||||
},
|
||||
{
|
||||
desc: "healthy grpc server becoming sick",
|
||||
mode: "grpc",
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING),
|
||||
expNumRemovedServers: 1,
|
||||
expNumUpsertedServers: 0,
|
||||
expGaugeValue: 0,
|
||||
targetStatus: runtime.StatusDown,
|
||||
},
|
||||
{
|
||||
desc: "healthy grpc server toggling to sick and back to healthy",
|
||||
mode: "grpc",
|
||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING, healthpb.HealthCheckResponse_SERVING),
|
||||
expNumRemovedServers: 1,
|
||||
expNumUpsertedServers: 1,
|
||||
expGaugeValue: 1,
|
||||
targetStatus: runtime.StatusUp,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -145,37 +342,26 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
serverURL, timeout := test.server.Start(t, cancel)
|
||||
targetURL, timeout := test.server.Start(t, cancel)
|
||||
|
||||
lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}}
|
||||
|
||||
options := Options{
|
||||
config := &dynamic.ServerHealthCheck{
|
||||
Mode: test.mode,
|
||||
Path: "/path",
|
||||
Interval: healthCheckInterval,
|
||||
Timeout: healthCheckTimeout,
|
||||
LB: lb,
|
||||
}
|
||||
backend := NewBackendConfig(options, "backendName")
|
||||
|
||||
if test.startHealthy {
|
||||
lb.servers = append(lb.servers, serverURL)
|
||||
} else {
|
||||
backend.disabledURLs = append(backend.disabledURLs, backendURL{url: serverURL, weight: 1})
|
||||
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||
Timeout: ptypes.Duration(499 * time.Millisecond),
|
||||
}
|
||||
|
||||
collectingMetrics := &testhelpers.CollectingGauge{}
|
||||
|
||||
check := HealthCheck{
|
||||
Backends: make(map[string]*BackendConfig),
|
||||
metrics: metricsHealthcheck{serverUpGauge: collectingMetrics},
|
||||
}
|
||||
gauge := &testhelpers.CollectingGauge{}
|
||||
serviceInfo := &runtime.ServiceInfo{}
|
||||
hc := NewServiceHealthChecker(ctx, &MetricsMock{gauge}, config, lb, serviceInfo, http.DefaultTransport, map[string]*url.URL{"test": targetURL})
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
check.execute(ctx, backend)
|
||||
hc.Launch(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
|
@ -189,392 +375,14 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
|||
lb.Lock()
|
||||
defer lb.Unlock()
|
||||
|
||||
assert.Equal(t, test.expectedNumRemovedServers, lb.numRemovedServers, "removed servers")
|
||||
assert.Equal(t, test.expectedNumUpsertedServers, lb.numUpsertedServers, "upserted servers")
|
||||
assert.Equal(t, test.expectedGaugeValue, collectingMetrics.GaugeValue, "ServerUp Gauge")
|
||||
assert.Equal(t, test.expNumRemovedServers, lb.numRemovedServers, "removed servers")
|
||||
assert.Equal(t, test.expNumUpsertedServers, lb.numUpsertedServers, "upserted servers")
|
||||
assert.Equal(t, test.expGaugeValue, gauge.GaugeValue, "ServerUp Gauge")
|
||||
assert.Equal(t, serviceInfo.GetAllStatus(), map[string]string{targetURL.String(): test.targetStatus})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequest(t *testing.T) {
|
||||
type expected struct {
|
||||
err bool
|
||||
value string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
serverURL string
|
||||
options Options
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "no port override",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Path: "/test",
|
||||
Port: 0,
|
||||
},
|
||||
expected: expected{
|
||||
err: false,
|
||||
value: "http://backend1:80/test",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "port override",
|
||||
serverURL: "http://backend2:80",
|
||||
options: Options{
|
||||
Path: "/test",
|
||||
Port: 8080,
|
||||
},
|
||||
expected: expected{
|
||||
err: false,
|
||||
value: "http://backend2:8080/test",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no port override with no port in server URL",
|
||||
serverURL: "http://backend1",
|
||||
options: Options{
|
||||
Path: "/health",
|
||||
Port: 0,
|
||||
},
|
||||
expected: expected{
|
||||
err: false,
|
||||
value: "http://backend1/health",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "port override with no port in server URL",
|
||||
serverURL: "http://backend2",
|
||||
options: Options{
|
||||
Path: "/health",
|
||||
Port: 8080,
|
||||
},
|
||||
expected: expected{
|
||||
err: false,
|
||||
value: "http://backend2:8080/health",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "scheme override",
|
||||
serverURL: "https://backend1:80",
|
||||
options: Options{
|
||||
Scheme: "http",
|
||||
Path: "/test",
|
||||
Port: 0,
|
||||
},
|
||||
expected: expected{
|
||||
err: false,
|
||||
value: "http://backend1:80/test",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "path with param",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Path: "/health?powpow=do",
|
||||
Port: 0,
|
||||
},
|
||||
expected: expected{
|
||||
err: false,
|
||||
value: "http://backend1:80/health?powpow=do",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "path with params",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Path: "/health?powpow=do&do=powpow",
|
||||
Port: 0,
|
||||
},
|
||||
expected: expected{
|
||||
err: false,
|
||||
value: "http://backend1:80/health?powpow=do&do=powpow",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "path with invalid path",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Path: ":",
|
||||
Port: 0,
|
||||
},
|
||||
expected: expected{
|
||||
err: true,
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
backend := NewBackendConfig(test.options, "backendName")
|
||||
|
||||
u := testhelpers.MustParseURL(test.serverURL)
|
||||
|
||||
req, err := backend.newRequest(u)
|
||||
|
||||
if test.expected.err {
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, nil)
|
||||
} else {
|
||||
require.NoError(t, err, "failed to create new backend request")
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, test.expected.value, req.URL.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestOptions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
serverURL string
|
||||
options Options
|
||||
expectedHostname string
|
||||
expectedHeader string
|
||||
expectedMethod string
|
||||
}{
|
||||
{
|
||||
desc: "override hostname",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Hostname: "myhost",
|
||||
Path: "/",
|
||||
},
|
||||
expectedHostname: "myhost",
|
||||
expectedHeader: "",
|
||||
expectedMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "not override hostname",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Hostname: "",
|
||||
Path: "/",
|
||||
},
|
||||
expectedHostname: "backend1:80",
|
||||
expectedHeader: "",
|
||||
expectedMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "custom header",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Headers: map[string]string{"Custom-Header": "foo"},
|
||||
Hostname: "",
|
||||
Path: "/",
|
||||
},
|
||||
expectedHostname: "backend1:80",
|
||||
expectedHeader: "foo",
|
||||
expectedMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "custom header with hostname override",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Headers: map[string]string{"Custom-Header": "foo"},
|
||||
Hostname: "myhost",
|
||||
Path: "/",
|
||||
},
|
||||
expectedHostname: "myhost",
|
||||
expectedHeader: "foo",
|
||||
expectedMethod: http.MethodGet,
|
||||
},
|
||||
{
|
||||
desc: "custom method",
|
||||
serverURL: "http://backend1:80",
|
||||
options: Options{
|
||||
Path: "/",
|
||||
Method: http.MethodHead,
|
||||
},
|
||||
expectedHostname: "backend1:80",
|
||||
expectedMethod: http.MethodHead,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
backend := NewBackendConfig(test.options, "backendName")
|
||||
|
||||
u, err := url.Parse(test.serverURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := backend.newRequest(u)
|
||||
require.NoError(t, err, "failed to create new backend request")
|
||||
|
||||
req = backend.setRequestOptions(req)
|
||||
|
||||
assert.Equal(t, "http://backend1:80/", req.URL.String())
|
||||
assert.Equal(t, test.expectedHostname, req.Host)
|
||||
assert.Equal(t, test.expectedHeader, req.Header.Get("Custom-Header"))
|
||||
assert.Equal(t, test.expectedMethod, req.Method)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBalancers_Servers(t *testing.T) {
|
||||
server1, err := url.Parse("http://foo.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
balancer1, err := roundrobin.New(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = balancer1.UpsertServer(server1)
|
||||
require.NoError(t, err)
|
||||
|
||||
server2, err := url.Parse("http://foo.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
balancer2, err := roundrobin.New(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = balancer2.UpsertServer(server2)
|
||||
require.NoError(t, err)
|
||||
|
||||
balancers := Balancers([]Balancer{balancer1, balancer2})
|
||||
|
||||
want, err := url.Parse("http://foo.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(balancers.Servers()))
|
||||
assert.Equal(t, want, balancers.Servers()[0])
|
||||
}
|
||||
|
||||
func TestBalancers_UpsertServer(t *testing.T) {
|
||||
balancer1, err := roundrobin.New(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
balancer2, err := roundrobin.New(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
want, err := url.Parse("http://foo.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
balancers := Balancers([]Balancer{balancer1, balancer2})
|
||||
|
||||
err = balancers.UpsertServer(want)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(balancer1.Servers()))
|
||||
assert.Equal(t, want, balancer1.Servers()[0])
|
||||
|
||||
assert.Equal(t, 1, len(balancer2.Servers()))
|
||||
assert.Equal(t, want, balancer2.Servers()[0])
|
||||
}
|
||||
|
||||
func TestBalancers_RemoveServer(t *testing.T) {
|
||||
server, err := url.Parse("http://foo.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
balancer1, err := roundrobin.New(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = balancer1.UpsertServer(server)
|
||||
require.NoError(t, err)
|
||||
|
||||
balancer2, err := roundrobin.New(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = balancer2.UpsertServer(server)
|
||||
require.NoError(t, err)
|
||||
|
||||
balancers := Balancers([]Balancer{balancer1, balancer2})
|
||||
|
||||
err = balancers.RemoveServer(server)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(balancer1.Servers()))
|
||||
assert.Equal(t, 0, len(balancer2.Servers()))
|
||||
}
|
||||
|
||||
func TestLBStatusUpdater(t *testing.T) {
|
||||
lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}}
|
||||
svInfo := &runtime.ServiceInfo{}
|
||||
lbsu := NewLBStatusUpdater(lb, svInfo, nil)
|
||||
newServer, err := url.Parse("http://foo.com")
|
||||
assert.NoError(t, err)
|
||||
err = lbsu.UpsertServer(newServer, roundrobin.Weight(1))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(lbsu.Servers()), 1)
|
||||
assert.Equal(t, len(lbsu.BalancerHandler.(*testLoadBalancer).Options()), 1)
|
||||
statuses := svInfo.GetAllStatus()
|
||||
assert.Equal(t, len(statuses), 1)
|
||||
for k, v := range statuses {
|
||||
assert.Equal(t, k, newServer.String())
|
||||
assert.Equal(t, v, serverUp)
|
||||
break
|
||||
}
|
||||
err = lbsu.RemoveServer(newServer)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(lbsu.Servers()), 0)
|
||||
statuses = svInfo.GetAllStatus()
|
||||
assert.Equal(t, len(statuses), 1)
|
||||
for k, v := range statuses {
|
||||
assert.Equal(t, k, newServer.String())
|
||||
assert.Equal(t, v, serverDown)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotFollowingRedirects(t *testing.T) {
|
||||
redirectServerCalled := false
|
||||
redirectTestServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
redirectServerCalled = true
|
||||
}))
|
||||
defer redirectTestServer.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Add("location", redirectTestServer.URL)
|
||||
rw.WriteHeader(http.StatusSeeOther)
|
||||
cancel()
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
lb := &testLoadBalancer{
|
||||
RWMutex: &sync.RWMutex{},
|
||||
servers: []*url.URL{testhelpers.MustParseURL(server.URL)},
|
||||
}
|
||||
|
||||
backend := NewBackendConfig(Options{
|
||||
Path: "/path",
|
||||
Interval: healthCheckInterval,
|
||||
Timeout: healthCheckTimeout,
|
||||
LB: lb,
|
||||
FollowRedirects: false,
|
||||
}, "backendName")
|
||||
|
||||
collectingMetrics := &testhelpers.CollectingGauge{}
|
||||
check := HealthCheck{
|
||||
Backends: make(map[string]*BackendConfig),
|
||||
metrics: metricsHealthcheck{serverUpGauge: collectingMetrics},
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
check.execute(ctx, backend)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
timeout := time.Duration(int(healthCheckInterval) + 500)
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
t.Fatal("test did not complete in time")
|
||||
case <-ctx.Done():
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
assert.False(t, redirectServerCalled, "HTTP redirect must not be followed")
|
||||
func Bool(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
|
|
@ -10,9 +10,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
gokitmetrics "github.com/go-kit/kit/metrics"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"google.golang.org/grpc"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
@ -64,10 +65,13 @@ func newGRPCServer(healthSequence ...healthpb.HealthCheckResponse_ServingStatus)
|
|||
}
|
||||
|
||||
func (s *GRPCServer) Check(_ context.Context, _ *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
|
||||
stat := s.status.Pop()
|
||||
if s.status.IsEmpty() {
|
||||
s.done()
|
||||
return &healthpb.HealthCheckResponse{
|
||||
Status: healthpb.HealthCheckResponse_SERVICE_UNKNOWN,
|
||||
}, nil
|
||||
}
|
||||
stat := s.status.Pop()
|
||||
|
||||
return &healthpb.HealthCheckResponse{
|
||||
Status: stat,
|
||||
|
@ -75,10 +79,13 @@ func (s *GRPCServer) Check(_ context.Context, _ *healthpb.HealthCheckRequest) (*
|
|||
}
|
||||
|
||||
func (s *GRPCServer) Watch(_ *healthpb.HealthCheckRequest, server healthpb.Health_WatchServer) error {
|
||||
stat := s.status.Pop()
|
||||
if s.status.IsEmpty() {
|
||||
s.done()
|
||||
return server.Send(&healthpb.HealthCheckResponse{
|
||||
Status: healthpb.HealthCheckResponse_SERVICE_UNKNOWN,
|
||||
})
|
||||
}
|
||||
stat := s.status.Pop()
|
||||
|
||||
return server.Send(&healthpb.HealthCheckResponse{
|
||||
Status: stat,
|
||||
|
@ -105,7 +112,7 @@ func (s *GRPCServer) Start(t *testing.T, done func()) (*url.URL, time.Duration)
|
|||
}()
|
||||
|
||||
// Make test timeout dependent on number of expected requests, health check interval, and a safety margin.
|
||||
return testhelpers.MustParseURL("http://" + listener.Addr().String()), time.Duration(len(s.status.sequence)*int(healthCheckInterval) + 500)
|
||||
return testhelpers.MustParseURL("http://" + listener.Addr().String()), time.Duration(len(s.status.sequence)*int(dynamic.DefaultHealthCheckInterval) + 500)
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
|
@ -126,13 +133,14 @@ func newHTTPServer(healthSequence ...int) *HTTPServer {
|
|||
// ServeHTTP returns HTTP response codes following a status sequences.
|
||||
// It calls the given 'done' function once all request health indicators have been depleted.
|
||||
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
|
||||
if s.status.IsEmpty() {
|
||||
s.done()
|
||||
return
|
||||
}
|
||||
|
||||
stat := s.status.Pop()
|
||||
|
||||
w.WriteHeader(stat)
|
||||
|
||||
if s.status.IsEmpty() {
|
||||
s.done()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) Start(t *testing.T, done func()) (*url.URL, time.Duration) {
|
||||
|
@ -144,7 +152,7 @@ func (s *HTTPServer) Start(t *testing.T, done func()) (*url.URL, time.Duration)
|
|||
t.Cleanup(ts.Close)
|
||||
|
||||
// Make test timeout dependent on number of expected requests, health check interval, and a safety margin.
|
||||
return testhelpers.MustParseURL(ts.URL), time.Duration(len(s.status.sequence)*int(healthCheckInterval) + 500)
|
||||
return testhelpers.MustParseURL(ts.URL), time.Duration(len(s.status.sequence)*int(dynamic.DefaultHealthCheckInterval) + 500)
|
||||
}
|
||||
|
||||
type testLoadBalancer struct {
|
||||
|
@ -153,53 +161,20 @@ type testLoadBalancer struct {
|
|||
*sync.RWMutex
|
||||
numRemovedServers int
|
||||
numUpsertedServers int
|
||||
servers []*url.URL
|
||||
// options is just to make sure that LBStatusUpdater forwards options on Upsert to its BalancerHandler
|
||||
options []roundrobin.ServerOption
|
||||
}
|
||||
|
||||
func (lb *testLoadBalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// noop
|
||||
}
|
||||
|
||||
func (lb *testLoadBalancer) RemoveServer(u *url.URL) error {
|
||||
lb.Lock()
|
||||
defer lb.Unlock()
|
||||
lb.numRemovedServers++
|
||||
lb.removeServer(u)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lb *testLoadBalancer) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
|
||||
lb.Lock()
|
||||
defer lb.Unlock()
|
||||
lb.numUpsertedServers++
|
||||
lb.servers = append(lb.servers, u)
|
||||
lb.options = append(lb.options, options...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lb *testLoadBalancer) Servers() []*url.URL {
|
||||
return lb.servers
|
||||
}
|
||||
|
||||
func (lb *testLoadBalancer) Options() []roundrobin.ServerOption {
|
||||
return lb.options
|
||||
}
|
||||
|
||||
func (lb *testLoadBalancer) removeServer(u *url.URL) {
|
||||
var i int
|
||||
var serverURL *url.URL
|
||||
found := false
|
||||
for i, serverURL = range lb.servers {
|
||||
if *serverURL == *u {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
func (lb *testLoadBalancer) SetStatus(ctx context.Context, childName string, up bool) {
|
||||
if up {
|
||||
lb.numUpsertedServers++
|
||||
} else {
|
||||
lb.numRemovedServers++
|
||||
}
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
lb.servers = append(lb.servers[:i], lb.servers[i+1:]...)
|
||||
}
|
||||
|
||||
type MetricsMock struct {
|
||||
Gauge gokitmetrics.Gauge
|
||||
}
|
||||
|
||||
func (m *MetricsMock) ServiceServerUpGauge() gokitmetrics.Gauge {
|
||||
return m.Gauge
|
||||
}
|
||||
|
|
|
@ -41,14 +41,6 @@ func (f *FieldHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// AddServiceFields add service fields.
|
||||
func AddServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
||||
data.Core[ServiceURL] = req.URL // note that this is *not* the original incoming URL
|
||||
data.Core[ServiceAddr] = req.URL.Host
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// AddOriginFields add origin fields.
|
||||
func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
||||
start := time.Now().UTC()
|
||||
|
|
|
@ -62,13 +62,13 @@ func FromContext(ctx context.Context) (Capture, error) {
|
|||
// Capture is the object populated by the capture middleware,
|
||||
// holding probes that allow to gather information about the request and response.
|
||||
type Capture struct {
|
||||
rr *readCounter
|
||||
rw responseWriter
|
||||
rr *readCounter
|
||||
crw *captureResponseWriter
|
||||
}
|
||||
|
||||
// NeedsReset returns whether the given http.ResponseWriter is the capture's probe.
|
||||
func (c *Capture) NeedsReset(rw http.ResponseWriter) bool {
|
||||
return c.rw != rw
|
||||
return c.crw.rw != rw
|
||||
}
|
||||
|
||||
// Reset returns a new handler that renews the Capture's probes, and inserts
|
||||
|
@ -83,18 +83,18 @@ func (c *Capture) Reset(next http.Handler) http.Handler {
|
|||
c.rr = readCounter
|
||||
newReq.Body = readCounter
|
||||
}
|
||||
c.rw = newResponseWriter(rw)
|
||||
c.crw = &captureResponseWriter{rw: rw}
|
||||
|
||||
next.ServeHTTP(c.rw, newReq)
|
||||
next.ServeHTTP(c.crw, newReq)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Capture) ResponseSize() int64 {
|
||||
return c.rw.Size()
|
||||
return c.crw.Size()
|
||||
}
|
||||
|
||||
func (c *Capture) StatusCode() int {
|
||||
return c.rw.Status()
|
||||
return c.crw.Status()
|
||||
}
|
||||
|
||||
// RequestSize returns the size of the request's body if it applies,
|
||||
|
@ -123,22 +123,7 @@ func (r *readCounter) Close() error {
|
|||
return r.source.Close()
|
||||
}
|
||||
|
||||
var _ middlewares.Stateful = &responseWriterWithCloseNotify{}
|
||||
|
||||
type responseWriter interface {
|
||||
http.ResponseWriter
|
||||
Size() int64
|
||||
Status() int
|
||||
}
|
||||
|
||||
func newResponseWriter(rw http.ResponseWriter) responseWriter {
|
||||
capt := &captureResponseWriter{rw: rw}
|
||||
if _, ok := rw.(http.CloseNotifier); !ok {
|
||||
return capt
|
||||
}
|
||||
|
||||
return &responseWriterWithCloseNotify{capt}
|
||||
}
|
||||
var _ middlewares.Stateful = &captureResponseWriter{}
|
||||
|
||||
// captureResponseWriter is a wrapper of type http.ResponseWriter
|
||||
// that tracks response status and size.
|
||||
|
@ -189,13 +174,3 @@ func (crw *captureResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error)
|
|||
|
||||
return nil, nil, fmt.Errorf("not a hijacker: %T", crw.rw)
|
||||
}
|
||||
|
||||
type responseWriterWithCloseNotify struct {
|
||||
*captureResponseWriter
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (r *responseWriterWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.rw.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
|
|
@ -189,44 +189,3 @@ func TestRequestReader(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), rr.size)
|
||||
}
|
||||
|
||||
type rwWithCloseNotify struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (r *rwWithCloseNotify) CloseNotify() <-chan bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestCloseNotifier(t *testing.T) {
|
||||
testCases := []struct {
|
||||
rw http.ResponseWriter
|
||||
desc string
|
||||
implementsCloseNotifier bool
|
||||
}{
|
||||
{
|
||||
rw: httptest.NewRecorder(),
|
||||
desc: "does not implement CloseNotifier",
|
||||
implementsCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
rw: &rwWithCloseNotify{httptest.NewRecorder()},
|
||||
desc: "implements CloseNotifier",
|
||||
implementsCloseNotifier: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, ok := test.rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, ok)
|
||||
|
||||
rw := newResponseWriter(test.rw)
|
||||
_, impl := rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, impl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
338
pkg/middlewares/compress/brotli/brotli.go
Normal file
338
pkg/middlewares/compress/brotli/brotli.go
Normal file
|
@ -0,0 +1,338 @@
|
|||
package brotli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
)
|
||||
|
||||
const (
|
||||
vary = "Vary"
|
||||
acceptEncoding = "Accept-Encoding"
|
||||
contentEncoding = "Content-Encoding"
|
||||
contentLength = "Content-Length"
|
||||
contentType = "Content-Type"
|
||||
)
|
||||
|
||||
// Config is the Brotli handler configuration.
|
||||
type Config struct {
|
||||
// ExcludedContentTypes is the list of content types for which we should not compress.
|
||||
ExcludedContentTypes []string
|
||||
// MinSize is the minimum size (in bytes) required to enable compression.
|
||||
MinSize int
|
||||
}
|
||||
|
||||
// NewWrapper returns a new Brotli compressing wrapper.
|
||||
func NewWrapper(cfg Config) (func(http.Handler) http.HandlerFunc, error) {
|
||||
if cfg.MinSize < 0 {
|
||||
return nil, fmt.Errorf("minimum size must be greater than or equal to zero")
|
||||
}
|
||||
|
||||
var contentTypes []parsedContentType
|
||||
for _, v := range cfg.ExcludedContentTypes {
|
||||
mediaType, params, err := mime.ParseMediaType(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing media type: %w", err)
|
||||
}
|
||||
|
||||
contentTypes = append(contentTypes, parsedContentType{mediaType, params})
|
||||
}
|
||||
|
||||
return func(h http.Handler) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add(vary, acceptEncoding)
|
||||
|
||||
brw := &responseWriter{
|
||||
rw: rw,
|
||||
bw: brotli.NewWriter(rw),
|
||||
minSize: cfg.MinSize,
|
||||
statusCode: http.StatusOK,
|
||||
excludedContentTypes: contentTypes,
|
||||
}
|
||||
defer brw.close()
|
||||
|
||||
h.ServeHTTP(brw, r)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: check whether we want to implement content-type sniffing (as gzip does)
|
||||
// TODO: check whether we should support Accept-Ranges (as gzip does, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges)
|
||||
type responseWriter struct {
|
||||
rw http.ResponseWriter
|
||||
bw *brotli.Writer
|
||||
|
||||
minSize int
|
||||
excludedContentTypes []parsedContentType
|
||||
|
||||
buf []byte
|
||||
hijacked bool
|
||||
compressionStarted bool
|
||||
compressionDisabled bool
|
||||
headersSent bool
|
||||
|
||||
// Mostly needed to avoid calling bw.Flush/bw.Close when no data was
|
||||
// written in bw.
|
||||
seenData bool
|
||||
|
||||
statusCodeSet bool
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (r *responseWriter) Header() http.Header {
|
||||
return r.rw.Header()
|
||||
}
|
||||
|
||||
func (r *responseWriter) WriteHeader(statusCode int) {
|
||||
if r.statusCodeSet {
|
||||
return
|
||||
}
|
||||
|
||||
r.statusCode = statusCode
|
||||
r.statusCodeSet = true
|
||||
}
|
||||
|
||||
func (r *responseWriter) Write(p []byte) (int, error) {
|
||||
// i.e. has write ever been called at least once with non nil data.
|
||||
if !r.seenData && len(p) > 0 {
|
||||
r.seenData = true
|
||||
}
|
||||
|
||||
// We do not compress, either for contentEncoding or contentType reasons.
|
||||
if r.compressionDisabled {
|
||||
return r.rw.Write(p)
|
||||
}
|
||||
|
||||
// We have already buffered more than minSize,
|
||||
// We are now in compression cruise mode until the end of times.
|
||||
if r.compressionStarted {
|
||||
// If compressionStarted we assume we have sent headers already
|
||||
return r.bw.Write(p)
|
||||
}
|
||||
|
||||
// If we detect a contentEncoding, we know we are never going to compress.
|
||||
if r.rw.Header().Get(contentEncoding) != "" {
|
||||
r.compressionDisabled = true
|
||||
return r.rw.Write(p)
|
||||
}
|
||||
|
||||
// Disable compression according to user wishes in excludedContentTypes.
|
||||
if ct := r.rw.Header().Get(contentType); ct != "" {
|
||||
mediaType, params, err := mime.ParseMediaType(ct)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing media type: %w", err)
|
||||
}
|
||||
|
||||
for _, excludedContentType := range r.excludedContentTypes {
|
||||
if excludedContentType.equals(mediaType, params) {
|
||||
r.compressionDisabled = true
|
||||
return r.rw.Write(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We buffer until we know whether to compress (i.e. when we reach minSize received).
|
||||
if len(r.buf)+len(p) < r.minSize {
|
||||
r.buf = append(r.buf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// If we ever make it here, we have received at least minSize, which means we want to compress,
|
||||
// and we are going to send headers right away.
|
||||
r.compressionStarted = true
|
||||
|
||||
// Since we know we are going to compress we will never be able to know the actual length.
|
||||
r.rw.Header().Del(contentLength)
|
||||
|
||||
r.rw.Header().Set(contentEncoding, "br")
|
||||
r.rw.WriteHeader(r.statusCode)
|
||||
r.headersSent = true
|
||||
|
||||
// Start with sending what we have previously buffered, before actually writing
|
||||
// the bytes in argument.
|
||||
n, err := r.bw.Write(r.buf)
|
||||
if err != nil {
|
||||
r.buf = r.buf[n:]
|
||||
// Return zero because we haven't taken care of the bytes in argument yet.
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If we wrote less than what we wanted, we need to reclaim the leftovers + the bytes in argument,
|
||||
// and keep them for a subsequent Write.
|
||||
if n < len(r.buf) {
|
||||
r.buf = r.buf[n:]
|
||||
r.buf = append(r.buf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Otherwise just reset the buffer.
|
||||
r.buf = r.buf[:0]
|
||||
|
||||
// Now that we emptied the buffer, we can actually write the given bytes.
|
||||
return r.bw.Write(p)
|
||||
}
|
||||
|
||||
// Flush flushes data to the appropriate underlying writer(s), although it does
|
||||
// not guarantee that all buffered data will be sent.
|
||||
// If not enough bytes have been written to determine whether to enable compression,
|
||||
// no flushing will take place.
|
||||
func (r *responseWriter) Flush() {
|
||||
if !r.seenData {
|
||||
// we should not flush if there never was any data, because flushing the bw
|
||||
// (just like closing) would send some extra end of compressionStarted stream bytes.
|
||||
return
|
||||
}
|
||||
|
||||
// It was already established by Write that compression is disabled, we only
|
||||
// have to flush the uncompressed writer.
|
||||
if r.compressionDisabled {
|
||||
if rw, ok := r.rw.(http.Flusher); ok {
|
||||
rw.Flush()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Here, nothing was ever written either to rw or to bw (since we're still
|
||||
// waiting to decide whether to compress), so we do not need to flush anything.
|
||||
// Note that we diverge with klauspost's gzip behavior, where they instead
|
||||
// force compression and flush whatever was in the buffer in this case.
|
||||
if !r.compressionStarted {
|
||||
return
|
||||
}
|
||||
|
||||
// Conversely, we here know that something was already written to bw (or is
|
||||
// going to be written right after anyway), so bw will have to be flushed.
|
||||
// Also, since we know that bw writes to rw, but (apparently) never flushes it,
|
||||
// we have to do it ourselves.
|
||||
defer func() {
|
||||
// because we also ignore the error returned by Write anyway
|
||||
_ = r.bw.Flush()
|
||||
|
||||
if rw, ok := r.rw.(http.Flusher); ok {
|
||||
rw.Flush()
|
||||
}
|
||||
}()
|
||||
|
||||
// We empty whatever is left of the buffer that Write never took care of.
|
||||
n, err := r.bw.Write(r.buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// And just like in Write we also handle "short writes".
|
||||
if n < len(r.buf) {
|
||||
r.buf = r.buf[n:]
|
||||
return
|
||||
}
|
||||
|
||||
r.buf = r.buf[:0]
|
||||
}
|
||||
|
||||
func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacker, ok := r.rw.(http.Hijacker); ok {
|
||||
// We only make use of r.hijacked in close (and not in Write/WriteHeader)
|
||||
// because we want to let the stdlib catch the error on writes, as
|
||||
// they already do a good job of logging it.
|
||||
r.hijacked = true
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.rw)
|
||||
}
|
||||
|
||||
// close closes the underlying writers if/when appropriate.
|
||||
// Note that the compressed writer should not be closed if we never used it,
|
||||
// as it would otherwise send some extra "end of compression" bytes.
|
||||
// Close also makes sure to flush whatever was left to write from the buffer.
|
||||
func (r *responseWriter) close() error {
|
||||
if r.hijacked {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We have to take care of statusCode ourselves (in case there was never any
|
||||
// call to Write or WriteHeader before us) as it's the only header we buffer.
|
||||
if !r.headersSent {
|
||||
r.rw.WriteHeader(r.statusCode)
|
||||
r.headersSent = true
|
||||
}
|
||||
|
||||
// Nothing was ever written anywhere, nothing to flush.
|
||||
if !r.seenData {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If compression was disabled, there never was anything in the buffer to flush,
|
||||
// and nothing was ever written to bw.
|
||||
if r.compressionDisabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(r.buf) == 0 {
|
||||
// If we got here we know compression has started, so we can safely flush on bw.
|
||||
return r.bw.Close()
|
||||
}
|
||||
|
||||
// There is still data in the buffer, because we never reached minSize (to
|
||||
// determine whether to compress). We therefore flush it uncompressed.
|
||||
if !r.compressionStarted {
|
||||
n, err := r.rw.Write(r.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n < len(r.buf) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// There is still data in the buffer, simply because Write did not take care of it all.
|
||||
// We flush it to the compressed writer.
|
||||
n, err := r.bw.Write(r.buf)
|
||||
if err != nil {
|
||||
r.bw.Close()
|
||||
return err
|
||||
}
|
||||
if n < len(r.buf) {
|
||||
r.bw.Close()
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return r.bw.Close()
|
||||
}
|
||||
|
||||
// parsedContentType is the parsed representation of one of the inputs to ContentTypes.
|
||||
// From https://github.com/klauspost/compress/blob/master/gzhttp/compress.go#L401.
|
||||
type parsedContentType struct {
|
||||
mediaType string
|
||||
params map[string]string
|
||||
}
|
||||
|
||||
// equals returns whether this content type matches another content type.
|
||||
func (p parsedContentType) equals(mediaType string, params map[string]string) bool {
|
||||
if p.mediaType != mediaType {
|
||||
return false
|
||||
}
|
||||
|
||||
// if p has no params, don't care about other's params
|
||||
if len(p.params) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// if p has any params, they must be identical to other's.
|
||||
if len(p.params) != len(params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for k, v := range p.params {
|
||||
if w, ok := params[k]; !ok || v != w {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
618
pkg/middlewares/compress/brotli/brotli_test.go
Normal file
618
pkg/middlewares/compress/brotli/brotli_test.go
Normal file
|
@ -0,0 +1,618 @@
|
|||
package brotli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
smallTestBody = []byte("aaabbc" + strings.Repeat("aaabbbccc", 9) + "aaabbbc")
|
||||
bigTestBody = []byte(strings.Repeat(strings.Repeat("aaabbbccc", 66)+" ", 6) + strings.Repeat("aaabbbccc", 66))
|
||||
)
|
||||
|
||||
func Test_Vary(t *testing.T) {
|
||||
h := newTestHandler(t, smallTestBody)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Equal(t, acceptEncoding, rw.Header().Get(vary))
|
||||
}
|
||||
|
||||
func Test_SmallBodyNoCompression(t *testing.T) {
|
||||
h := newTestHandler(t, smallTestBody)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
// With less than 1024 bytes the response should not be compressed.
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
assert.Empty(t, rw.Header().Get(contentEncoding))
|
||||
assert.Equal(t, smallTestBody, rw.Body.Bytes())
|
||||
}
|
||||
|
||||
func Test_AlreadyCompressed(t *testing.T) {
|
||||
h := newTestHandler(t, bigTestBody)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/compressed", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, bigTestBody, rw.Body.Bytes())
|
||||
}
|
||||
|
||||
func Test_NoBody(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
statusCode int
|
||||
body []byte
|
||||
}{
|
||||
{
|
||||
desc: "status no content",
|
||||
statusCode: http.StatusNoContent,
|
||||
body: nil,
|
||||
},
|
||||
{
|
||||
desc: "status not modified",
|
||||
statusCode: http.StatusNotModified,
|
||||
body: nil,
|
||||
},
|
||||
{
|
||||
desc: "status OK with empty body",
|
||||
statusCode: http.StatusOK,
|
||||
body: []byte{},
|
||||
},
|
||||
{
|
||||
desc: "status OK with nil body",
|
||||
statusCode: http.StatusOK,
|
||||
body: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(test.statusCode)
|
||||
|
||||
_, err := rw.Write(test.body)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
body, err := io.ReadAll(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, rw.Header().Get(contentEncoding))
|
||||
assert.Empty(t, body)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MinSize(t *testing.T) {
|
||||
cfg := Config{
|
||||
MinSize: 128,
|
||||
}
|
||||
|
||||
var bodySize int
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
for i := 0; i < bodySize; i++ {
|
||||
// We make sure to Write at least once less than minSize so that both
|
||||
// cases below go through the same algo: i.e. they start buffering
|
||||
// because they haven't reached minSize.
|
||||
_, err := rw.Write([]byte{'x'})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
},
|
||||
))
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", &bytes.Buffer{})
|
||||
req.Header.Add(acceptEncoding, "br")
|
||||
|
||||
// Short response is not compressed
|
||||
bodySize = cfg.MinSize - 1
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Empty(t, rw.Result().Header.Get(contentEncoding))
|
||||
|
||||
// Long response is compressed
|
||||
bodySize = cfg.MinSize
|
||||
rw = httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, "br", rw.Result().Header.Get(contentEncoding))
|
||||
}
|
||||
|
||||
func Test_MultipleWriteHeader(t *testing.T) {
|
||||
h := mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
// We ensure that the subsequent call to WriteHeader is a noop.
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, rw.Code)
|
||||
}
|
||||
|
||||
func Test_FlushBeforeWrite(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.(http.Flusher).Flush()
|
||||
|
||||
_, err := rw.Write(bigTestBody)
|
||||
require.NoError(t, err)
|
||||
})))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, "br", res.Header.Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
}
|
||||
|
||||
func Test_FlushAfterWrite(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err := rw.Write(bigTestBody[0:1])
|
||||
require.NoError(t, err)
|
||||
|
||||
rw.(http.Flusher).Flush()
|
||||
for _, b := range bigTestBody[1:] {
|
||||
_, err := rw.Write([]byte{b})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, "br", res.Header.Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
}
|
||||
|
||||
func Test_FlushAfterWriteNil(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err := rw.Write(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
rw.(http.Flusher).Flush()
|
||||
})))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Empty(t, res.Header.Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, got)
|
||||
}
|
||||
|
||||
func Test_FlushAfterAllWrites(t *testing.T) {
|
||||
srv := httptest.NewServer(mustNewWrapper(t, Config{MinSize: 1024})(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
for i := range bigTestBody {
|
||||
_, err := rw.Write(bigTestBody[i : i+1])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
rw.(http.Flusher).Flush()
|
||||
})))
|
||||
defer srv.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
|
||||
require.NoError(t, err)
|
||||
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
assert.Equal(t, "br", res.Header.Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(res.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
}
|
||||
|
||||
func Test_ExcludedContentTypes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
contentType string
|
||||
excludedContentTypes []string
|
||||
expCompression bool
|
||||
}{
|
||||
{
|
||||
desc: "Always compress when content types are empty",
|
||||
contentType: "",
|
||||
excludedContentTypes: []string{},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match",
|
||||
contentType: "application/json",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME no match",
|
||||
contentType: "text/xml",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with no other directive ignores non-MIME directives",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with other directives requires all directives be equal, different charset",
|
||||
contentType: "application/json; charset=ascii",
|
||||
excludedContentTypes: []string{"application/json; charset=utf-8"},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with other directives requires all directives be equal, same charset",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
excludedContentTypes: []string{"application/json; charset=utf-8"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with other directives requires all directives be equal, missing charset",
|
||||
contentType: "application/json",
|
||||
excludedContentTypes: []string{"application/json; charset=ascii"},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match case insensitive",
|
||||
contentType: "Application/Json",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME match ignore whitespace",
|
||||
contentType: "application/json;charset=utf-8",
|
||||
excludedContentTypes: []string{"application/json; charset=utf-8"},
|
||||
expCompression: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := Config{
|
||||
MinSize: 1024,
|
||||
ExcludedContentTypes: test.excludedContentTypes,
|
||||
}
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set(contentType, test.contentType)
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err := rw.Write(bigTestBody)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
|
||||
if test.expCompression {
|
||||
assert.Equal(t, "br", rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(rw.Body))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
} else {
|
||||
assert.NotEqual(t, "br", rw.Header().Get("Content-Encoding"))
|
||||
|
||||
got, err := io.ReadAll(rw.Body)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FlushExcludedContentTypes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
contentType string
|
||||
excludedContentTypes []string
|
||||
expCompression bool
|
||||
}{
|
||||
{
|
||||
desc: "Always compress when content types are empty",
|
||||
contentType: "",
|
||||
excludedContentTypes: []string{},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match",
|
||||
contentType: "application/json",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME no match",
|
||||
contentType: "text/xml",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with no other directive ignores non-MIME directives",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with other directives requires all directives be equal, different charset",
|
||||
contentType: "application/json; charset=ascii",
|
||||
excludedContentTypes: []string{"application/json; charset=utf-8"},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with other directives requires all directives be equal, same charset",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
excludedContentTypes: []string{"application/json; charset=utf-8"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME match with other directives requires all directives be equal, missing charset",
|
||||
contentType: "application/json",
|
||||
excludedContentTypes: []string{"application/json; charset=ascii"},
|
||||
expCompression: true,
|
||||
},
|
||||
{
|
||||
desc: "MIME match case insensitive",
|
||||
contentType: "Application/Json",
|
||||
excludedContentTypes: []string{"application/json"},
|
||||
expCompression: false,
|
||||
},
|
||||
{
|
||||
desc: "MIME match ignore whitespace",
|
||||
contentType: "application/json;charset=utf-8",
|
||||
excludedContentTypes: []string{"application/json; charset=utf-8"},
|
||||
expCompression: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := Config{
|
||||
MinSize: 1024,
|
||||
ExcludedContentTypes: test.excludedContentTypes,
|
||||
}
|
||||
h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set(contentType, test.contentType)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
tb := bigTestBody
|
||||
for len(tb) > 0 {
|
||||
// Write 100 bytes per run
|
||||
// Detection should not be affected (we send 100 bytes)
|
||||
toWrite := 100
|
||||
if toWrite > len(tb) {
|
||||
toWrite = len(tb)
|
||||
}
|
||||
|
||||
_, err := rw.Write(tb[:toWrite])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Flush between each write
|
||||
rw.(http.Flusher).Flush()
|
||||
tb = tb[toWrite:]
|
||||
}
|
||||
}))
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
|
||||
req.Header.Set(acceptEncoding, "br")
|
||||
|
||||
// This doesn't allow checking flushes, but we validate if content is correct.
|
||||
rw := httptest.NewRecorder()
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rw.Code)
|
||||
|
||||
if test.expCompression {
|
||||
assert.Equal(t, "br", rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(rw.Body))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
} else {
|
||||
assert.NotEqual(t, "br", rw.Header().Get(contentEncoding))
|
||||
|
||||
got, err := io.ReadAll(rw.Body)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, bigTestBody, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustNewWrapper(t *testing.T, cfg Config) func(http.Handler) http.HandlerFunc {
|
||||
t.Helper()
|
||||
|
||||
w, err := NewWrapper(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func newTestHandler(t *testing.T, body []byte) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
return mustNewWrapper(t, Config{MinSize: 1024})(
|
||||
http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path == "/compressed" {
|
||||
rw.Header().Set("Content-Encoding", "br")
|
||||
}
|
||||
|
||||
_, err := rw.Write(body)
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestParseContentType_equals(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
pct parsedContentType
|
||||
mediaType string
|
||||
params map[string]string
|
||||
expect assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
desc: "empty parsed content type",
|
||||
expect: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "simple content type",
|
||||
pct: parsedContentType{
|
||||
mediaType: "plain/text",
|
||||
},
|
||||
mediaType: "plain/text",
|
||||
expect: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "content type with params",
|
||||
pct: parsedContentType{
|
||||
mediaType: "plain/text",
|
||||
params: map[string]string{
|
||||
"charset": "utf8",
|
||||
},
|
||||
},
|
||||
mediaType: "plain/text",
|
||||
params: map[string]string{
|
||||
"charset": "utf8",
|
||||
},
|
||||
expect: assert.True,
|
||||
},
|
||||
{
|
||||
desc: "different content type",
|
||||
pct: parsedContentType{
|
||||
mediaType: "plain/text",
|
||||
},
|
||||
mediaType: "application/json",
|
||||
expect: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "content type with params",
|
||||
pct: parsedContentType{
|
||||
mediaType: "plain/text",
|
||||
params: map[string]string{
|
||||
"charset": "utf8",
|
||||
},
|
||||
},
|
||||
mediaType: "plain/text",
|
||||
params: map[string]string{
|
||||
"charset": "latin-1",
|
||||
},
|
||||
expect: assert.False,
|
||||
},
|
||||
{
|
||||
desc: "different number of parameters",
|
||||
pct: parsedContentType{
|
||||
mediaType: "plain/text",
|
||||
params: map[string]string{
|
||||
"charset": "utf8",
|
||||
},
|
||||
},
|
||||
mediaType: "plain/text",
|
||||
params: map[string]string{
|
||||
"charset": "utf8",
|
||||
"q": "0.8",
|
||||
},
|
||||
expect: assert.False,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
test.expect(t, test.pct.equals(test.mediaType, test.params))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,22 +1,26 @@
|
|||
package compress
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/compress/brotli"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
)
|
||||
|
||||
const (
|
||||
typeName = "Compress"
|
||||
)
|
||||
const typeName = "Compress"
|
||||
|
||||
// DefaultMinSize is the default minimum size (in bytes) required to enable compression.
|
||||
// See https://github.com/klauspost/compress/blob/9559b037e79ad673c71f6ef7c732c00949014cd2/gzhttp/compress.go#L47.
|
||||
const DefaultMinSize = 1024
|
||||
|
||||
// Compress is a middleware that allows to compress the response.
|
||||
type compress struct {
|
||||
|
@ -24,6 +28,9 @@ type compress struct {
|
|||
name string
|
||||
excludes []string
|
||||
minSize int
|
||||
|
||||
brotliHandler http.Handler
|
||||
gzipHandler http.Handler
|
||||
}
|
||||
|
||||
// New creates a new compress middleware.
|
||||
|
@ -40,42 +47,117 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
|
|||
excludes = append(excludes, mediaType)
|
||||
}
|
||||
|
||||
minSize := gzhttp.DefaultMinSize
|
||||
minSize := DefaultMinSize
|
||||
if conf.MinResponseBodyBytes > 0 {
|
||||
minSize = conf.MinResponseBodyBytes
|
||||
}
|
||||
|
||||
return &compress{next: next, name: name, excludes: excludes, minSize: minSize}, nil
|
||||
c := &compress{
|
||||
next: next,
|
||||
name: name,
|
||||
excludes: excludes,
|
||||
minSize: minSize,
|
||||
}
|
||||
|
||||
var err error
|
||||
c.brotliHandler, err = c.newBrotliHandler()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.gzipHandler, err = c.newGzipHandler()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
log.FromContext(middlewares.GetLoggerCtx(context.Background(), c.name, typeName)).Debug(err)
|
||||
logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), c.name, typeName))
|
||||
|
||||
if req.Method == http.MethodHead {
|
||||
c.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Debug("Unable to parse MIME type")
|
||||
}
|
||||
|
||||
// Notably for text/event-stream requests the response should not be compressed.
|
||||
// See https://github.com/traefik/traefik/issues/2576
|
||||
if contains(c.excludes, mediaType) {
|
||||
c.next.ServeHTTP(rw, req)
|
||||
} else {
|
||||
ctx := middlewares.GetLoggerCtx(req.Context(), c.name, typeName)
|
||||
c.gzipHandler(ctx).ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
// Client allows us to do whatever we want, so we br compress.
|
||||
// See https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.3
|
||||
acceptEncoding, ok := req.Header["Accept-Encoding"]
|
||||
if !ok {
|
||||
c.brotliHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if encodingAccepts(acceptEncoding, "br") {
|
||||
c.brotliHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if encodingAccepts(acceptEncoding, "gzip") {
|
||||
c.gzipHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
c.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (c *compress) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return c.name, tracing.SpanKindNoneEnum
|
||||
}
|
||||
|
||||
func (c *compress) gzipHandler(ctx context.Context) http.Handler {
|
||||
func (c *compress) newGzipHandler() (http.Handler, error) {
|
||||
wrapper, err := gzhttp.NewWrapper(
|
||||
gzhttp.ExceptContentTypes(c.excludes),
|
||||
gzhttp.CompressionLevel(gzip.DefaultCompression),
|
||||
gzhttp.MinSize(c.minSize))
|
||||
gzhttp.MinSize(c.minSize),
|
||||
)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Error(err)
|
||||
return nil, fmt.Errorf("new gzip wrapper: %w", err)
|
||||
}
|
||||
|
||||
return wrapper(c.next)
|
||||
return wrapper(c.next), nil
|
||||
}
|
||||
|
||||
func (c *compress) newBrotliHandler() (http.Handler, error) {
|
||||
cfg := brotli.Config{
|
||||
ExcludedContentTypes: c.excludes,
|
||||
MinSize: c.minSize,
|
||||
}
|
||||
|
||||
wrapper, err := brotli.NewWrapper(cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new brotli wrapper: %w", err)
|
||||
}
|
||||
|
||||
return wrapper(c.next), nil
|
||||
}
|
||||
|
||||
func encodingAccepts(acceptEncoding []string, typ string) bool {
|
||||
for _, ae := range acceptEncoding {
|
||||
for _, e := range strings.Split(ae, ",") {
|
||||
parsed := strings.Split(strings.TrimSpace(e), ";")
|
||||
if len(parsed) == 0 {
|
||||
continue
|
||||
}
|
||||
if parsed[0] == typ || parsed[0] == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func contains(values []string, val string) bool {
|
||||
|
@ -84,5 +166,6 @@ func contains(values []string, val string) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package compress
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -20,8 +22,81 @@ const (
|
|||
contentTypeHeader = "Content-Type"
|
||||
varyHeader = "Vary"
|
||||
gzipValue = "gzip"
|
||||
brotliValue = "br"
|
||||
)
|
||||
|
||||
func TestNegotiation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
acceptEncHeader string
|
||||
expEncoding string
|
||||
}{
|
||||
{
|
||||
desc: "no accept header",
|
||||
expEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "unsupported accept header",
|
||||
acceptEncHeader: "notreal",
|
||||
expEncoding: "",
|
||||
},
|
||||
{
|
||||
desc: "accept any header",
|
||||
acceptEncHeader: "*",
|
||||
expEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "gzip accept header",
|
||||
acceptEncHeader: "gzip",
|
||||
expEncoding: "gzip",
|
||||
},
|
||||
{
|
||||
desc: "br accept header",
|
||||
acceptEncHeader: "br",
|
||||
expEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "multi accept header, prefer br",
|
||||
acceptEncHeader: "br;q=0.8, gzip;q=0.6",
|
||||
expEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "multi accept header, prefer br",
|
||||
acceptEncHeader: "gzip;q=1.0, br;q=0.8",
|
||||
expEncoding: "br",
|
||||
},
|
||||
{
|
||||
desc: "multi accept header list, prefer br",
|
||||
acceptEncHeader: "gzip, br",
|
||||
expEncoding: "br",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
if test.acceptEncHeader != "" {
|
||||
req.Header.Add(acceptEncodingHeader, test.acceptEncHeader)
|
||||
}
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
_, _ = rw.Write(generateBytes(10))
|
||||
})
|
||||
handler, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: 1}, "testing")
|
||||
require.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expEncoding, rw.Header().Get(contentEncodingHeader))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
|
@ -41,9 +116,12 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
|
|||
assert.Equal(t, gzipValue, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||
|
||||
if assert.ObjectsAreEqualValues(rw.Body.Bytes(), baseBody) {
|
||||
assert.Fail(t, "expected a compressed body", "got %v", rw.Body.Bytes())
|
||||
}
|
||||
gr, err := gzip.NewReader(rw.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := io.ReadAll(gr)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, got, baseBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
||||
|
@ -71,7 +149,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
|||
assert.EqualValues(t, rw.Body.Bytes(), fakeCompressedBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
|
||||
func TestShouldCompressWhenNoAcceptEncodingHeader(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
|
||||
fakeBody := generateBytes(gzhttp.DefaultMinSize)
|
||||
|
@ -87,7 +165,33 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
|
|||
rw := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, brotliValue, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||
|
||||
got, err := io.ReadAll(brotli.NewReader(rw.Body))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, got, fakeBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressHeadRequest(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodHead, "http://localhost", nil)
|
||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
||||
|
||||
fakeBody := generateBytes(gzhttp.DefaultMinSize)
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
_, err := rw.Write(fakeBody)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
|
||||
require.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Empty(t, rw.Header().Get(varyHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,9 +21,8 @@ import (
|
|||
|
||||
// Compile time validation that the response recorder implements http interfaces correctly.
|
||||
var (
|
||||
// TODO: maybe remove at least for codeModifierWithCloseNotify.
|
||||
_ middlewares.Stateful = &codeModifierWithCloseNotify{}
|
||||
_ middlewares.Stateful = &codeCatcherWithCloseNotify{}
|
||||
_ middlewares.Stateful = &codeModifier{}
|
||||
_ middlewares.Stateful = &codeCatcher{}
|
||||
)
|
||||
|
||||
const typeName = "customError"
|
||||
|
@ -124,13 +123,6 @@ func newRequest(baseURL string) (*http.Request, error) {
|
|||
return req, nil
|
||||
}
|
||||
|
||||
type responseInterceptor interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
getCode() int
|
||||
isFilteredCode() bool
|
||||
}
|
||||
|
||||
// codeCatcher is a response writer that detects as soon as possible whether the
|
||||
// response is a code within the ranges of codes it watches for. If it is, it
|
||||
// simply drops the data from the response. Otherwise, it forwards it directly to
|
||||
|
@ -144,27 +136,13 @@ type codeCatcher struct {
|
|||
headersSent bool
|
||||
}
|
||||
|
||||
type codeCatcherWithCloseNotify struct {
|
||||
*codeCatcher
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (cc *codeCatcherWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return cc.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) responseInterceptor {
|
||||
catcher := &codeCatcher{
|
||||
func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) *codeCatcher {
|
||||
return &codeCatcher{
|
||||
headerMap: make(http.Header),
|
||||
code: http.StatusOK, // If backend does not call WriteHeader on us, we consider it's a 200.
|
||||
responseWriter: rw,
|
||||
httpCodeRanges: httpCodeRanges,
|
||||
}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &codeCatcherWithCloseNotify{catcher}
|
||||
}
|
||||
return catcher
|
||||
}
|
||||
|
||||
func (cc *codeCatcher) Header() http.Header {
|
||||
|
@ -240,24 +218,7 @@ func (cc *codeCatcher) Flush() {
|
|||
|
||||
// codeModifier forwards a response back to the client,
|
||||
// while enforcing a given response code.
|
||||
type codeModifier interface {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
// newCodeModifier returns a codeModifier that enforces the given code.
|
||||
func newCodeModifier(rw http.ResponseWriter, code int) codeModifier {
|
||||
codeMod := &codeModifierWithoutCloseNotify{
|
||||
headerMap: make(http.Header),
|
||||
code: code,
|
||||
responseWriter: rw,
|
||||
}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &codeModifierWithCloseNotify{codeMod}
|
||||
}
|
||||
return codeMod
|
||||
}
|
||||
|
||||
type codeModifierWithoutCloseNotify struct {
|
||||
type codeModifier struct {
|
||||
code int // the code enforced in the response.
|
||||
|
||||
// headerSent is whether the headers have already been sent,
|
||||
|
@ -268,18 +229,17 @@ type codeModifierWithoutCloseNotify struct {
|
|||
responseWriter http.ResponseWriter
|
||||
}
|
||||
|
||||
type codeModifierWithCloseNotify struct {
|
||||
*codeModifierWithoutCloseNotify
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (r *codeModifierWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
// newCodeModifier returns a codeModifier that enforces the given code.
|
||||
func newCodeModifier(rw http.ResponseWriter, code int) *codeModifier {
|
||||
return &codeModifier{
|
||||
headerMap: make(http.Header),
|
||||
code: code,
|
||||
responseWriter: rw,
|
||||
}
|
||||
}
|
||||
|
||||
// Header returns the response headers.
|
||||
func (r *codeModifierWithoutCloseNotify) Header() http.Header {
|
||||
func (r *codeModifier) Header() http.Header {
|
||||
if r.headerMap == nil {
|
||||
r.headerMap = make(http.Header)
|
||||
}
|
||||
|
@ -289,14 +249,14 @@ func (r *codeModifierWithoutCloseNotify) Header() http.Header {
|
|||
|
||||
// Write calls WriteHeader to send the enforced code,
|
||||
// then writes the data directly to r.responseWriter.
|
||||
func (r *codeModifierWithoutCloseNotify) Write(buf []byte) (int, error) {
|
||||
func (r *codeModifier) Write(buf []byte) (int, error) {
|
||||
r.WriteHeader(r.code)
|
||||
return r.responseWriter.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader sends the headers, with the enforced code (the code in argument
|
||||
// is always ignored), if it hasn't already been done.
|
||||
func (r *codeModifierWithoutCloseNotify) WriteHeader(_ int) {
|
||||
func (r *codeModifier) WriteHeader(_ int) {
|
||||
if r.headerSent {
|
||||
return
|
||||
}
|
||||
|
@ -307,7 +267,7 @@ func (r *codeModifierWithoutCloseNotify) WriteHeader(_ int) {
|
|||
}
|
||||
|
||||
// Hijack hijacks the connection.
|
||||
func (r *codeModifierWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
func (r *codeModifier) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker, ok := r.responseWriter.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.responseWriter)
|
||||
|
@ -316,7 +276,7 @@ func (r *codeModifierWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter,
|
|||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (r *codeModifierWithoutCloseNotify) Flush() {
|
||||
func (r *codeModifier) Flush() {
|
||||
r.WriteHeader(r.code)
|
||||
|
||||
if flusher, ok := r.responseWriter.(http.Flusher); ok {
|
||||
|
|
|
@ -188,50 +188,3 @@ type mockServiceBuilder struct {
|
|||
func (m *mockServiceBuilder) BuildHTTP(_ context.Context, _ string) (http.Handler, error) {
|
||||
return m.handler, nil
|
||||
}
|
||||
|
||||
func TestNewResponseRecorder(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
rw http.ResponseWriter
|
||||
expected http.ResponseWriter
|
||||
}{
|
||||
{
|
||||
desc: "Without Close Notify",
|
||||
rw: httptest.NewRecorder(),
|
||||
expected: &codeModifierWithoutCloseNotify{},
|
||||
},
|
||||
{
|
||||
desc: "With Close Notify",
|
||||
rw: &mockRWCloseNotify{},
|
||||
expected: &codeModifierWithCloseNotify{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rec := newCodeModifier(test.rw, 0)
|
||||
assert.IsType(t, rec, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockRWCloseNotify struct{}
|
||||
|
||||
func (m *mockRWCloseNotify) CloseNotify() <-chan bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m *mockRWCloseNotify) Header() http.Header {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m *mockRWCloseNotify) Write([]byte) (int, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m *mockRWCloseNotify) WriteHeader(int) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package emptybackendhandler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/healthcheck"
|
||||
)
|
||||
|
||||
// EmptyBackend is a middleware that checks whether the current Backend
|
||||
// has at least one active Server in respect to the healthchecks and if this
|
||||
// is not the case, it will stop the middleware chain and respond with 503.
|
||||
type emptyBackend struct {
|
||||
healthcheck.BalancerStatusHandler
|
||||
}
|
||||
|
||||
// New creates a new EmptyBackend middleware.
|
||||
func New(lb healthcheck.BalancerStatusHandler) http.Handler {
|
||||
return &emptyBackend{BalancerStatusHandler: lb}
|
||||
}
|
||||
|
||||
// ServeHTTP responds with 503 when there is no active Server and otherwise
|
||||
// invokes the next handler in the middleware chain.
|
||||
func (e *emptyBackend) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if len(e.BalancerStatusHandler.Servers()) != 0 {
|
||||
e.BalancerStatusHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package emptybackendhandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
)
|
||||
|
||||
func TestEmptyBackendHandler(t *testing.T) {
|
||||
testCases := []struct {
|
||||
amountServer int
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
amountServer: 0,
|
||||
expectedStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
{
|
||||
amountServer: 1,
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(fmt.Sprintf("amount servers %d", test.amountServer), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler := New(&healthCheckLoadBalancer{amountServer: test.amountServer})
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, recorder.Result().StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type healthCheckLoadBalancer struct {
|
||||
amountServer int
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) RegisterStatusUpdater(fn func(up bool)) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) Servers() []*url.URL {
|
||||
servers := make([]*url.URL, lb.amountServer)
|
||||
for i := 0; i < lb.amountServer; i++ {
|
||||
servers = append(servers, testhelpers.MustParseURL("http://localhost"))
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) RemoveServer(u *url.URL) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) UpsertServer(u *url.URL, options ...roundrobin.ServerOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) ServerWeight(u *url.URL) (int, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) NextServer() (*url.URL, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (lb *healthCheckLoadBalancer) Next() http.Handler {
|
||||
return nil
|
||||
}
|
|
@ -23,17 +23,12 @@ type responseModifier struct {
|
|||
|
||||
// modifier can be nil.
|
||||
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) http.ResponseWriter {
|
||||
rm := &responseModifier{
|
||||
return &responseModifier{
|
||||
req: r,
|
||||
rw: w,
|
||||
modifier: modifier,
|
||||
code: http.StatusOK,
|
||||
}
|
||||
|
||||
if _, ok := w.(http.CloseNotifier); ok {
|
||||
return responseModifierWithCloseNotify{responseModifier: rm}
|
||||
}
|
||||
return rm
|
||||
}
|
||||
|
||||
func (r *responseModifier) WriteHeader(code int) {
|
||||
|
@ -97,12 +92,3 @@ func (r *responseModifier) Flush() {
|
|||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type responseModifierWithCloseNotify struct {
|
||||
*responseModifier
|
||||
}
|
||||
|
||||
// CloseNotify implements http.CloseNotifier.
|
||||
func (r *responseModifierWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.responseModifier.rw.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
|
|
@ -104,13 +104,6 @@ func WrapRouterHandler(ctx context.Context, registry metrics.Registry, routerNam
|
|||
}
|
||||
}
|
||||
|
||||
// WrapServiceHandler Wraps metrics service to alice.Constructor.
|
||||
func WrapServiceHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
return NewServiceMiddleware(ctx, next, registry, serviceName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
proto := getRequestProtocol(req)
|
||||
|
||||
|
|
|
@ -60,47 +60,6 @@ func (m *collectingRetryMetrics) ServiceRetriesCounter() metrics.Counter {
|
|||
return m.retriesCounter
|
||||
}
|
||||
|
||||
type rwWithCloseNotify struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (r *rwWithCloseNotify) CloseNotify() <-chan bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestCloseNotifier(t *testing.T) {
|
||||
testCases := []struct {
|
||||
rw http.ResponseWriter
|
||||
desc string
|
||||
implementsCloseNotifier bool
|
||||
}{
|
||||
{
|
||||
rw: httptest.NewRecorder(),
|
||||
desc: "does not implement CloseNotifier",
|
||||
implementsCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
rw: &rwWithCloseNotify{httptest.NewRecorder()},
|
||||
desc: "implements CloseNotifier",
|
||||
implementsCloseNotifier: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, ok := test.rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, ok)
|
||||
|
||||
rw := newResponseRecorder(test.rw)
|
||||
_, impl := rw.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementsCloseNotifier, impl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getMethod(t *testing.T) {
|
||||
testCases := []struct {
|
||||
method string
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type recorder interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
getCode() int
|
||||
}
|
||||
|
||||
func newResponseRecorder(rw http.ResponseWriter) recorder {
|
||||
rec := &responseRecorder{
|
||||
ResponseWriter: rw,
|
||||
statusCode: http.StatusOK,
|
||||
}
|
||||
if _, ok := rw.(http.CloseNotifier); !ok {
|
||||
return rec
|
||||
}
|
||||
return &responseRecorderWithCloseNotify{rec}
|
||||
}
|
||||
|
||||
// responseRecorder captures information from the response and preserves it for
|
||||
// later analysis.
|
||||
type responseRecorder struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
type responseRecorderWithCloseNotify struct {
|
||||
*responseRecorder
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone away.
|
||||
func (r *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (r *responseRecorder) getCode() int {
|
||||
return r.statusCode
|
||||
}
|
||||
|
||||
// WriteHeader captures the status code for later retrieval.
|
||||
func (r *responseRecorder) WriteHeader(status int) {
|
||||
r.ResponseWriter.WriteHeader(status)
|
||||
r.statusCode = status
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection.
|
||||
func (r *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return r.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (r *responseRecorder) Flush() {
|
||||
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package pipelining
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
)
|
||||
|
||||
const (
|
||||
typeName = "Pipelining"
|
||||
)
|
||||
|
||||
// pipelining returns a middleware.
|
||||
type pipelining struct {
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// New returns a new pipelining instance.
|
||||
func New(ctx context.Context, next http.Handler, name string) http.Handler {
|
||||
log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)).Debug("Creating middleware")
|
||||
|
||||
return &pipelining{
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipelining) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
// https://github.com/golang/go/blob/3d59583836630cf13ec4bfbed977d27b1b7adbdc/src/net/http/server.go#L201-L218
|
||||
if r.Method == http.MethodPut || r.Method == http.MethodPost {
|
||||
p.next.ServeHTTP(rw, r)
|
||||
} else {
|
||||
p.next.ServeHTTP(&writerWithoutCloseNotify{rw}, r)
|
||||
}
|
||||
}
|
||||
|
||||
// writerWithoutCloseNotify helps to disable closeNotify.
|
||||
type writerWithoutCloseNotify struct {
|
||||
W http.ResponseWriter
|
||||
}
|
||||
|
||||
// Header returns the response headers.
|
||||
func (w *writerWithoutCloseNotify) Header() http.Header {
|
||||
return w.W.Header()
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
func (w *writerWithoutCloseNotify) Write(buf []byte) (int, error) {
|
||||
return w.W.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with the provided status code.
|
||||
func (w *writerWithoutCloseNotify) WriteHeader(code int) {
|
||||
w.W.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (w *writerWithoutCloseNotify) Flush() {
|
||||
if f, ok := w.W.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection.
|
||||
func (w *writerWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.W.(http.Hijacker).Hijack()
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package pipelining
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type recorderWithCloseNotify struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (r *recorderWithCloseNotify) CloseNotify() <-chan bool {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
HTTPMethod string
|
||||
implementCloseNotifier bool
|
||||
}{
|
||||
{
|
||||
desc: "should not implement CloseNotifier with GET method",
|
||||
HTTPMethod: http.MethodGet,
|
||||
implementCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
desc: "should implement CloseNotifier with PUT method",
|
||||
HTTPMethod: http.MethodPut,
|
||||
implementCloseNotifier: true,
|
||||
},
|
||||
{
|
||||
desc: "should implement CloseNotifier with POST method",
|
||||
HTTPMethod: http.MethodPost,
|
||||
implementCloseNotifier: true,
|
||||
},
|
||||
{
|
||||
desc: "should not implement CloseNotifier with GET method",
|
||||
HTTPMethod: http.MethodHead,
|
||||
implementCloseNotifier: false,
|
||||
},
|
||||
{
|
||||
desc: "should not implement CloseNotifier with PROPFIND method",
|
||||
HTTPMethod: "PROPFIND",
|
||||
implementCloseNotifier: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := w.(http.CloseNotifier)
|
||||
assert.Equal(t, test.implementCloseNotifier, ok)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
handler := New(context.Background(), nextHandler, "pipe")
|
||||
|
||||
req := httptest.NewRequest(test.HTTPMethod, "http://localhost", nil)
|
||||
|
||||
handler.ServeHTTP(&recorderWithCloseNotify{httptest.NewRecorder()}, req)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -20,11 +20,9 @@ import (
|
|||
)
|
||||
|
||||
// Compile time validation that the response writer implements http interfaces correctly.
|
||||
var _ middlewares.Stateful = &responseWriterWithCloseNotify{}
|
||||
var _ middlewares.Stateful = &responseWriter{}
|
||||
|
||||
const (
|
||||
typeName = "Retry"
|
||||
)
|
||||
const typeName = "Retry"
|
||||
|
||||
// Listener is used to inform about retry attempts.
|
||||
type Listener interface {
|
||||
|
@ -149,57 +147,44 @@ func (l Listeners) Retried(req *http.Request, attempt int) {
|
|||
}
|
||||
}
|
||||
|
||||
type responseWriter interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
ShouldRetry() bool
|
||||
DisableRetries()
|
||||
}
|
||||
|
||||
func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) responseWriter {
|
||||
responseWriter := &responseWriterWithoutCloseNotify{
|
||||
func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) *responseWriter {
|
||||
return &responseWriter{
|
||||
responseWriter: rw,
|
||||
headers: make(http.Header),
|
||||
shouldRetry: shouldRetry,
|
||||
}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &responseWriterWithCloseNotify{
|
||||
responseWriterWithoutCloseNotify: responseWriter,
|
||||
}
|
||||
}
|
||||
return responseWriter
|
||||
}
|
||||
|
||||
type responseWriterWithoutCloseNotify struct {
|
||||
type responseWriter struct {
|
||||
responseWriter http.ResponseWriter
|
||||
headers http.Header
|
||||
shouldRetry bool
|
||||
written bool
|
||||
}
|
||||
|
||||
func (r *responseWriterWithoutCloseNotify) ShouldRetry() bool {
|
||||
func (r *responseWriter) ShouldRetry() bool {
|
||||
return r.shouldRetry
|
||||
}
|
||||
|
||||
func (r *responseWriterWithoutCloseNotify) DisableRetries() {
|
||||
func (r *responseWriter) DisableRetries() {
|
||||
r.shouldRetry = false
|
||||
}
|
||||
|
||||
func (r *responseWriterWithoutCloseNotify) Header() http.Header {
|
||||
func (r *responseWriter) Header() http.Header {
|
||||
if r.written {
|
||||
return r.responseWriter.Header()
|
||||
}
|
||||
return r.headers
|
||||
}
|
||||
|
||||
func (r *responseWriterWithoutCloseNotify) Write(buf []byte) (int, error) {
|
||||
func (r *responseWriter) Write(buf []byte) (int, error) {
|
||||
if r.ShouldRetry() {
|
||||
return len(buf), nil
|
||||
}
|
||||
return r.responseWriter.Write(buf)
|
||||
}
|
||||
|
||||
func (r *responseWriterWithoutCloseNotify) WriteHeader(code int) {
|
||||
func (r *responseWriter) WriteHeader(code int) {
|
||||
if r.ShouldRetry() && code == http.StatusServiceUnavailable {
|
||||
// We get a 503 HTTP Status Code when there is no backend server in the pool
|
||||
// to which the request could be sent. Also, note that r.ShouldRetry()
|
||||
|
@ -226,7 +211,7 @@ func (r *responseWriterWithoutCloseNotify) WriteHeader(code int) {
|
|||
r.written = true
|
||||
}
|
||||
|
||||
func (r *responseWriterWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker, ok := r.responseWriter.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.responseWriter)
|
||||
|
@ -234,16 +219,8 @@ func (r *responseWriterWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter
|
|||
return hijacker.Hijack()
|
||||
}
|
||||
|
||||
func (r *responseWriterWithoutCloseNotify) Flush() {
|
||||
func (r *responseWriter) Flush() {
|
||||
if flusher, ok := r.responseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type responseWriterWithCloseNotify struct {
|
||||
*responseWriterWithoutCloseNotify
|
||||
}
|
||||
|
||||
func (r *responseWriterWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
|
|
@ -8,5 +8,4 @@ type Stateful interface {
|
|||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Reque
|
|||
|
||||
req = req.WithContext(tracing.WithTracing(req.Context(), e.Tracing))
|
||||
|
||||
recorder := newStatusCodeRecoder(rw, http.StatusOK)
|
||||
recorder := newStatusCodeRecorder(rw, http.StatusOK)
|
||||
e.next.ServeHTTP(recorder, req)
|
||||
|
||||
tracing.LogResponseCode(span, recorder.Status())
|
||||
|
|
|
@ -51,7 +51,7 @@ func (f *forwarderMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Reques
|
|||
|
||||
tracing.InjectRequestHeaders(req)
|
||||
|
||||
recorder := newStatusCodeRecoder(rw, 200)
|
||||
recorder := newStatusCodeRecorder(rw, 200)
|
||||
|
||||
f.next.ServeHTTP(recorder, req)
|
||||
|
||||
|
|
|
@ -6,52 +6,35 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
type statusCodeRecoder interface {
|
||||
http.ResponseWriter
|
||||
Status() int
|
||||
// newStatusCodeRecorder returns an initialized statusCodeRecoder.
|
||||
func newStatusCodeRecorder(rw http.ResponseWriter, status int) *statusCodeRecorder {
|
||||
return &statusCodeRecorder{rw, status}
|
||||
}
|
||||
|
||||
// newStatusCodeRecoder returns an initialized statusCodeRecoder.
|
||||
func newStatusCodeRecoder(rw http.ResponseWriter, status int) statusCodeRecoder {
|
||||
recorder := &statusCodeWithoutCloseNotify{rw, status}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &statusCodeWithCloseNotify{recorder}
|
||||
}
|
||||
return recorder
|
||||
}
|
||||
|
||||
type statusCodeWithoutCloseNotify struct {
|
||||
type statusCodeRecorder struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
// WriteHeader captures the status code for later retrieval.
|
||||
func (s *statusCodeWithoutCloseNotify) WriteHeader(status int) {
|
||||
func (s *statusCodeRecorder) WriteHeader(status int) {
|
||||
s.status = status
|
||||
s.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
// Status get response status.
|
||||
func (s *statusCodeWithoutCloseNotify) Status() int {
|
||||
func (s *statusCodeRecorder) Status() int {
|
||||
return s.status
|
||||
}
|
||||
|
||||
// Hijack hijacks the connection.
|
||||
func (s *statusCodeWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
func (s *statusCodeRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return s.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (s *statusCodeWithoutCloseNotify) Flush() {
|
||||
func (s *statusCodeRecorder) Flush() {
|
||||
if flusher, ok := s.ResponseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type statusCodeWithCloseNotify struct {
|
||||
*statusCodeWithoutCloseNotify
|
||||
}
|
||||
|
||||
func (s *statusCodeWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return s.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
)
|
||||
|
@ -63,6 +65,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -113,6 +118,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -156,6 +164,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -199,6 +210,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -247,6 +261,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -332,6 +349,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -384,7 +404,10 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
URL: "https://127.0.0.1:443",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "tls-ns-dc1-dev-Test",
|
||||
},
|
||||
},
|
||||
|
@ -470,7 +493,10 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
URL: "https://127.0.0.2:444",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "tls-ns-dc1-dev-Test",
|
||||
},
|
||||
},
|
||||
|
@ -547,6 +573,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -557,6 +586,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -616,6 +648,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -672,6 +707,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -731,6 +769,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -779,6 +820,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -829,6 +873,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -871,6 +918,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -926,6 +976,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -971,6 +1024,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -981,6 +1037,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1145,6 +1204,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1192,6 +1254,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1265,6 +1330,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1325,6 +1393,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1401,6 +1472,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1458,6 +1532,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1529,6 +1606,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1592,6 +1672,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1640,6 +1723,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1689,6 +1775,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1733,6 +1822,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -1743,6 +1835,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1948,6 +2043,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2007,6 +2105,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2392,6 +2493,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2475,6 +2579,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2673,7 +2780,10 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
URL: "https://127.0.0.1:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "tls-ns-dc1-Test",
|
||||
},
|
||||
},
|
||||
|
@ -2684,7 +2794,10 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
URL: "https://127.0.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "tls-ns-dc1-Test",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,12 +4,14 @@ import (
|
|||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
docker "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
|
@ -68,6 +70,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -123,6 +128,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -180,6 +188,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -230,6 +241,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -280,6 +294,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -335,6 +352,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -546,6 +566,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -620,6 +643,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -630,6 +656,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -705,6 +734,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -761,6 +793,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -819,6 +854,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -869,6 +907,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -932,6 +973,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -985,6 +1029,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -995,6 +1042,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1052,6 +1102,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1280,6 +1333,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1335,6 +1391,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1427,6 +1486,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1506,6 +1568,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1607,6 +1672,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1681,6 +1749,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1777,6 +1848,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1856,6 +1930,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1925,6 +2002,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -1935,6 +2015,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1991,6 +2074,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2048,6 +2134,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2100,6 +2189,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -2110,6 +2202,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2290,6 +2385,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"Test": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2529,6 +2627,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2596,6 +2697,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3042,6 +3146,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3208,6 +3315,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,10 +3,12 @@ package ecs
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
|
@ -64,6 +66,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -114,6 +119,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -166,6 +174,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -211,6 +222,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -256,6 +270,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -306,6 +323,9 @@ func TestDefaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -496,6 +516,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -560,6 +583,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -570,6 +596,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -635,6 +664,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -686,6 +718,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -739,6 +774,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -784,6 +822,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -842,6 +883,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -890,6 +934,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -900,6 +947,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -952,6 +1002,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1145,6 +1198,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1195,6 +1251,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1277,6 +1336,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1346,6 +1408,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1432,6 +1497,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1496,6 +1564,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1577,6 +1648,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1647,6 +1721,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1706,6 +1783,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -1716,6 +1796,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1767,6 +1850,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1819,6 +1905,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1871,6 +1960,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1932,6 +2024,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -1942,6 +2037,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1989,6 +2087,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -1999,6 +2100,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2257,6 +2361,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2319,6 +2426,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2725,6 +2835,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -307,7 +307,13 @@ func (c configBuilder) buildServersLB(namespace string, svc v1alpha1.LoadBalance
|
|||
passHostHeader := true
|
||||
lb.PassHostHeader = &passHostHeader
|
||||
}
|
||||
lb.ResponseForwarding = conf.ResponseForwarding
|
||||
|
||||
if conf.ResponseForwarding != nil && conf.ResponseForwarding.FlushInterval != "" {
|
||||
err := lb.ResponseForwarding.FlushInterval.Set(conf.ResponseForwarding.FlushInterval)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse flushInterval: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
lb.Sticky = svc.Sticky
|
||||
|
||||
|
|
|
@ -1283,7 +1283,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-external-svc-with-ipv6-8080": {
|
||||
|
@ -1293,7 +1296,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7347]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
|
@ -1510,6 +1516,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1578,6 +1587,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1629,6 +1641,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1686,6 +1701,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1734,6 +1752,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-route-77c62dfe9517144aeeaa": {
|
||||
|
@ -1747,6 +1768,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1805,6 +1829,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami2-8080": {
|
||||
|
@ -1818,6 +1845,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1871,6 +1901,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1917,6 +1950,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1988,6 +2024,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami5-8080": {
|
||||
|
@ -2001,6 +2040,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-wrr2": {
|
||||
|
@ -2028,6 +2070,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami7-8080": {
|
||||
|
@ -2041,6 +2086,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2108,6 +2156,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2187,6 +2238,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami5-8080": {
|
||||
|
@ -2200,6 +2254,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2270,6 +2327,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo-wrr1": {
|
||||
|
@ -2305,6 +2365,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo-mirror1": {
|
||||
|
@ -2328,6 +2391,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar-mirrored": {
|
||||
|
@ -2430,6 +2496,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami5-8080": {
|
||||
|
@ -2443,6 +2512,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2514,6 +2586,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami5-8080": {
|
||||
|
@ -2527,6 +2602,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2584,6 +2662,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami2-8080": {
|
||||
|
@ -2597,6 +2678,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2717,6 +2801,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2786,6 +2873,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2834,6 +2924,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2903,6 +2996,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2973,6 +3069,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3041,6 +3140,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3098,6 +3200,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3156,6 +3261,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3200,6 +3308,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3243,6 +3354,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3286,6 +3400,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3533,6 +3650,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3575,7 +3695,7 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(false),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: "10s"},
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: ptypes.Duration(10 * time.Second)},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3630,6 +3750,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3687,6 +3810,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3733,6 +3859,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3774,6 +3903,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3813,6 +3945,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3852,6 +3987,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3949,7 +4087,10 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
URL: "https://external.domain:443",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "default-test",
|
||||
},
|
||||
},
|
||||
|
@ -3963,7 +4104,10 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
URL: "https://10.10.0.6:8443",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "default-default-test",
|
||||
},
|
||||
},
|
||||
|
@ -4036,6 +4180,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
"default-test-route-6b204d94623b3df4370c": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -4923,6 +5070,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -4998,6 +5148,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
|
@ -5011,6 +5164,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-errorpage-errorpage-service": {
|
||||
|
@ -5024,6 +5180,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-a1963878aac7331b7950": {
|
||||
|
@ -5037,6 +5196,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -5111,7 +5273,10 @@ func TestCrossNamespace(t *testing.T) {
|
|||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "foo-test@kubernetescrd",
|
||||
},
|
||||
},
|
||||
|
@ -5126,6 +5291,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-tr-svc-wrr1": {
|
||||
|
@ -5181,6 +5349,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -5227,6 +5398,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-mirror2": {
|
||||
|
@ -5251,6 +5425,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -5294,7 +5471,10 @@ func TestCrossNamespace(t *testing.T) {
|
|||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
ServersTransport: "cross-ns-st-cross-ns@kubernetescrd",
|
||||
},
|
||||
},
|
||||
|
@ -5387,6 +5567,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -5431,6 +5614,9 @@ func TestCrossNamespace(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -5936,6 +6122,9 @@ func TestExternalNameService(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -110,7 +110,7 @@ type LoadBalancerSpec struct {
|
|||
// By default, passHostHeader is true.
|
||||
PassHostHeader *bool `json:"passHostHeader,omitempty"`
|
||||
// ResponseForwarding defines how Traefik forwards the response from the upstream Kubernetes Service to the client.
|
||||
ResponseForwarding *dynamic.ResponseForwarding `json:"responseForwarding,omitempty"`
|
||||
ResponseForwarding *ResponseForwarding `json:"responseForwarding,omitempty"`
|
||||
// ServersTransport defines the name of ServersTransport resource to use.
|
||||
// It allows to configure the transport between Traefik and your servers.
|
||||
// Can only be used on a Kubernetes Service.
|
||||
|
@ -121,6 +121,15 @@ type LoadBalancerSpec struct {
|
|||
Weight *int `json:"weight,omitempty"`
|
||||
}
|
||||
|
||||
type ResponseForwarding struct {
|
||||
// FlushInterval defines the interval, in milliseconds, in between flushes to the client while copying the response body.
|
||||
// A negative value means to flush immediately after each write to the client.
|
||||
// This configuration is ignored when ReverseProxy recognizes a response as a streaming response;
|
||||
// for such responses, writes are flushed to the client immediately.
|
||||
// Default: 100ms
|
||||
FlushInterval string `json:"flushInterval,omitempty"`
|
||||
}
|
||||
|
||||
// Service defines an upstream HTTP service to proxy traffic to.
|
||||
type Service struct {
|
||||
LoadBalancerSpec `json:",inline"`
|
||||
|
|
|
@ -559,7 +559,7 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
|
|||
}
|
||||
if in.ResponseForwarding != nil {
|
||||
in, out := &in.ResponseForwarding, &out.ResponseForwarding
|
||||
*out = new(dynamic.ResponseForwarding)
|
||||
*out = new(ResponseForwarding)
|
||||
**out = **in
|
||||
}
|
||||
if in.Weight != nil {
|
||||
|
@ -973,6 +973,22 @@ func (in *RateLimit) DeepCopy() *RateLimit {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ResponseForwarding) DeepCopyInto(out *ResponseForwarding) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseForwarding.
|
||||
func (in *ResponseForwarding) DeepCopy() *ResponseForwarding {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ResponseForwarding)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Retry) DeepCopyInto(out *Retry) {
|
||||
*out = *in
|
||||
|
|
|
@ -1443,11 +1443,10 @@ func loadServices(client Client, namespace string, backendRefs []v1alpha2.HTTPBa
|
|||
return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||
}
|
||||
|
||||
svc := dynamic.Service{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
},
|
||||
}
|
||||
lb := &dynamic.ServersLoadBalancer{}
|
||||
lb.SetDefaults()
|
||||
|
||||
svc := dynamic.Service{LoadBalancer: lb}
|
||||
|
||||
// TODO support cross namespace through ReferencePolicy
|
||||
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
||||
|
|
|
@ -3,9 +3,11 @@ package gateway
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/provider"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
|
@ -552,6 +554,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -642,6 +647,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -698,6 +706,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -762,6 +773,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -817,6 +831,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -872,6 +889,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -942,6 +962,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami2-8080": {
|
||||
|
@ -955,6 +978,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1014,6 +1040,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami2-8080": {
|
||||
|
@ -1027,6 +1056,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1103,6 +1135,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1188,6 +1223,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1267,6 +1305,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1322,6 +1363,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1392,6 +1436,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar-whoami-bar-80": {
|
||||
|
@ -1405,6 +1452,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1460,6 +1510,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3548,6 +3601,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3722,6 +3778,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3923,6 +3982,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar-whoami-bar-80": {
|
||||
|
@ -3936,6 +3998,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": {
|
||||
|
@ -4083,6 +4148,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": {
|
||||
|
@ -4231,6 +4299,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: pointer.Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -517,11 +517,10 @@ func (p *Provider) loadService(client Client, namespace string, backend networki
|
|||
return nil, errors.New("service port not found")
|
||||
}
|
||||
|
||||
svc := &dynamic.Service{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||
},
|
||||
}
|
||||
lb := &dynamic.ServersLoadBalancer{}
|
||||
lb.SetDefaults()
|
||||
|
||||
svc := &dynamic.Service{LoadBalancer: lb}
|
||||
|
||||
svcConfig, err := parseServiceConfig(service.Annotations)
|
||||
if err != nil {
|
||||
|
|
|
@ -7,8 +7,10 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/provider"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
|
@ -68,6 +70,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -115,6 +120,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Sticky: &dynamic.Sticky{
|
||||
Cookie: &dynamic.Cookie{
|
||||
Name: "foobar",
|
||||
|
@ -157,6 +165,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -191,6 +202,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -225,6 +239,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -259,6 +276,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -289,6 +309,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -319,6 +342,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-example-com-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.11.0.1:80",
|
||||
|
@ -350,6 +376,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -384,6 +413,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -418,6 +450,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -431,6 +466,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service2-8082": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.2:8080",
|
||||
|
@ -462,6 +500,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -496,6 +537,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"default-backend": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -526,6 +570,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8089",
|
||||
|
@ -556,6 +603,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-tchouk": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8089",
|
||||
|
@ -586,6 +636,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-tchouk": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8089",
|
||||
|
@ -620,6 +673,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-tchouk": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8089",
|
||||
|
@ -633,6 +689,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-carotte": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8090",
|
||||
|
@ -663,6 +722,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-tchouk": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8089",
|
||||
|
@ -697,6 +759,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-tchouk": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8089",
|
||||
|
@ -710,6 +775,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"toto-service1-tchouk": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.11.0.1:8089",
|
||||
|
@ -762,6 +830,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.0.0.1:8080",
|
||||
|
@ -790,6 +861,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-example-com-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.11.0.1:80",
|
||||
|
@ -827,6 +901,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-443": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "https://10.10.0.1:8443",
|
||||
|
@ -857,6 +934,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-8443": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "https://10.10.0.1:8443",
|
||||
|
@ -888,6 +968,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-8443": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "https://10.10.0.1:8443",
|
||||
|
@ -919,6 +1002,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"default-backend": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.30.0.1:8080",
|
||||
|
@ -949,6 +1035,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1023,6 +1112,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1053,6 +1145,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1085,6 +1180,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1113,6 +1211,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1141,6 +1242,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1169,6 +1273,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1197,6 +1304,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1225,6 +1335,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1265,6 +1378,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1294,6 +1410,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1322,6 +1441,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1350,6 +1472,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1378,6 +1503,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1406,6 +1534,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1434,6 +1565,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1462,6 +1596,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1490,6 +1627,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1518,6 +1658,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1546,6 +1689,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"testing-service1-foobar": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:4711",
|
||||
|
@ -1587,6 +1733,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
"default-backend": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
|
@ -1678,6 +1827,9 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
|||
"testing-service1-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://traefik.wtf:8080",
|
||||
|
@ -1710,6 +1862,9 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1738,6 +1893,9 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -42,15 +42,15 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/http/routers/Router1/service": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/path": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/port": "42",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/interval": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/timeout": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/interval": "1s",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/timeout": "1s",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/hostname": "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/scheme": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/mode": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/healthCheck/followredirects": "true",
|
||||
"traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval": "1s",
|
||||
"traefik/http/services/Service01/loadBalancer/passHostHeader": "true",
|
||||
"traefik/http/services/Service01/loadBalancer/sticky/cookie/name": "foobar",
|
||||
"traefik/http/services/Service01/loadBalancer/sticky/cookie/secure": "true",
|
||||
|
@ -646,8 +646,8 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
Mode: "foobar",
|
||||
Path: "foobar",
|
||||
Port: 42,
|
||||
Interval: "foobar",
|
||||
Timeout: "foobar",
|
||||
Interval: ptypes.Duration(time.Second),
|
||||
Timeout: ptypes.Duration(time.Second),
|
||||
Hostname: "foobar",
|
||||
FollowRedirects: func(v bool) *bool { return &v }(true),
|
||||
Headers: map[string]string{
|
||||
|
@ -657,7 +657,7 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: "foobar",
|
||||
FlushInterval: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,10 +4,12 @@ import (
|
|||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gambol99/go-marathon"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
|
@ -71,6 +73,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
|
@ -137,6 +142,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
|
@ -189,6 +197,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
|
@ -295,6 +306,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
|
@ -356,6 +370,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
|
@ -404,6 +421,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
"bar": {LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
|
@ -412,6 +432,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
|
@ -456,6 +479,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -498,6 +524,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
}},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
|
@ -542,6 +571,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -579,6 +611,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -629,6 +664,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -669,6 +707,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -679,6 +720,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -780,6 +824,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"app2": {
|
||||
|
@ -790,6 +837,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -843,6 +893,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"app2": {
|
||||
|
@ -853,6 +906,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -897,6 +953,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"app2": {
|
||||
|
@ -907,6 +966,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -958,6 +1020,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1002,6 +1067,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"app2": {
|
||||
|
@ -1012,6 +1080,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1055,6 +1126,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1099,6 +1173,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1138,6 +1215,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -1148,6 +1228,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1355,6 +1438,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1399,6 +1485,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1442,6 +1531,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1766,6 +1858,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1826,6 +1921,9 @@ func TestBuildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,9 +3,11 @@ package nomad
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
|
@ -56,6 +58,9 @@ func Test_defaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -106,6 +111,9 @@ func Test_defaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -148,6 +156,9 @@ func Test_defaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -195,6 +206,9 @@ func Test_defaultRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -265,6 +279,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -323,6 +340,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -333,6 +353,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -392,6 +415,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -448,6 +474,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -507,6 +536,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -555,6 +587,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -605,6 +640,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -647,6 +685,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -702,6 +743,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -747,6 +791,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -757,6 +804,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -922,6 +972,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -969,6 +1022,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1043,6 +1099,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1104,6 +1163,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1160,6 +1222,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1221,6 +1286,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1269,6 +1337,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1318,6 +1389,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1362,6 +1436,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Service2": {
|
||||
|
@ -1372,6 +1449,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1547,6 +1627,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1606,6 +1689,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1991,6 +2077,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2074,6 +2163,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2272,6 +2364,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test-1234154071633021619": {
|
||||
|
@ -2282,6 +2377,9 @@ func Test_buildConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,9 +3,11 @@ package rancher
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
|
@ -58,6 +60,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -116,6 +121,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -126,6 +134,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -187,6 +198,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Test2": {
|
||||
|
@ -197,6 +211,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -247,6 +264,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -353,6 +373,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -435,6 +458,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -494,6 +520,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -599,6 +628,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -910,6 +942,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -981,6 +1016,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -82,8 +82,8 @@ func init() {
|
|||
Scheme: "foo",
|
||||
Path: "foo",
|
||||
Port: 42,
|
||||
Interval: "foo",
|
||||
Timeout: "foo",
|
||||
Interval: ptypes.Duration(111 * time.Second),
|
||||
Timeout: ptypes.Duration(111 * time.Second),
|
||||
Hostname: "foo",
|
||||
FollowRedirects: boolPtr(true),
|
||||
Headers: map[string]string{
|
||||
|
@ -92,7 +92,7 @@ func init() {
|
|||
},
|
||||
PassHostHeader: boolPtr(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: "foo",
|
||||
FlushInterval: ptypes.Duration(111 * time.Second),
|
||||
},
|
||||
ServersTransport: "foo",
|
||||
Servers: []dynamic.Server{
|
||||
|
|
|
@ -75,8 +75,8 @@
|
|||
"scheme": "foo",
|
||||
"path": "foo",
|
||||
"port": 42,
|
||||
"interval": "foo",
|
||||
"timeout": "foo",
|
||||
"interval": "1m51s",
|
||||
"timeout": "1m51s",
|
||||
"hostname": "xxxx",
|
||||
"followRedirects": true,
|
||||
"headers": {
|
||||
|
@ -85,7 +85,7 @@
|
|||
},
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "foo"
|
||||
"flushInterval": "1m51s"
|
||||
},
|
||||
"serversTransport": "foo"
|
||||
}
|
||||
|
|
|
@ -75,8 +75,8 @@
|
|||
"scheme": "foo",
|
||||
"path": "foo",
|
||||
"port": 42,
|
||||
"interval": "foo",
|
||||
"timeout": "foo",
|
||||
"interval": "1m51s",
|
||||
"timeout": "1m51s",
|
||||
"hostname": "foo",
|
||||
"followRedirects": true,
|
||||
"headers": {
|
||||
|
@ -85,7 +85,7 @@
|
|||
},
|
||||
"passHostHeader": true,
|
||||
"responseForwarding": {
|
||||
"flushInterval": "foo"
|
||||
"flushInterval": "1m51s"
|
||||
},
|
||||
"serversTransport": "foo"
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ type middlewareBuilder interface {
|
|||
|
||||
type serviceManager interface {
|
||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||
LaunchHealthCheck()
|
||||
LaunchHealthCheck(ctx context.Context)
|
||||
}
|
||||
|
||||
// Manager A route/router manager.
|
||||
|
|
|
@ -7,10 +7,12 @@ import (
|
|||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v2/pkg/metrics"
|
||||
|
@ -478,7 +480,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
HealthCheck: &dynamic.ServerHealthCheck{
|
||||
Interval: "500ms",
|
||||
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||
Path: "/health",
|
||||
},
|
||||
},
|
||||
|
|
161
pkg/server/router/tcp/postgres.go
Normal file
161
pkg/server/router/tcp/postgres.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
tcpmuxer "github.com/traefik/traefik/v2/pkg/muxer/tcp"
|
||||
"github.com/traefik/traefik/v2/pkg/tcp"
|
||||
)
|
||||
|
||||
var (
|
||||
PostgresStartTLSMsg = []byte{0, 0, 0, 8, 4, 210, 22, 47} // int32(8) + int32(80877103)
|
||||
PostgresStartTLSReply = []byte{83} // S
|
||||
)
|
||||
|
||||
// isPostgres determines whether the buffer contains the Postgres STARTTLS message.
|
||||
func isPostgres(br *bufio.Reader) (bool, error) {
|
||||
// Peek the first 8 bytes individually to prevent blocking on peek
|
||||
// if the underlying conn does not send enough bytes.
|
||||
// It could happen if a protocol start by sending less than 8 bytes,
|
||||
// and expect a response before proceeding.
|
||||
for i := 1; i < len(PostgresStartTLSMsg)+1; i++ {
|
||||
peeked, err := br.Peek(i)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error while Peeking first bytes: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(peeked, PostgresStartTLSMsg[:i]) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// servePostgres serves a connection with a Postgres client negotiating a STARTTLS session.
|
||||
// It handles TCP TLS routing, after accepting to start the STARTTLS session.
|
||||
func (r *Router) servePostgres(conn tcp.WriteCloser) {
|
||||
_, err := conn.Write(PostgresStartTLSReply)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
|
||||
b := make([]byte, len(PostgresStartTLSMsg))
|
||||
_, err = br.Read(b)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
hello, err := clientHelloInfo(br)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if !hello.isTLS {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error while reading TCP connection data: %v", err)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Contains also TCP TLS passthrough routes.
|
||||
handlerTCPTLS, _ := r.muxerTCPTLS.Match(connData)
|
||||
if handlerTCPTLS == nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// We are in TLS mode and if the handler is not TLSHandler, we are in passthrough.
|
||||
proxiedConn := r.GetConn(conn, hello.peeked)
|
||||
if _, ok := handlerTCPTLS.(*tcp.TLSHandler); !ok {
|
||||
proxiedConn = &postgresConn{WriteCloser: proxiedConn}
|
||||
}
|
||||
|
||||
handlerTCPTLS.ServeTCP(proxiedConn)
|
||||
}
|
||||
|
||||
// postgresConn is a tcp.WriteCloser that will negotiate a TLS session (STARTTLS),
|
||||
// before exchanging any data.
|
||||
// It enforces that the STARTTLS negotiation with the peer is successful.
|
||||
type postgresConn struct {
|
||||
tcp.WriteCloser
|
||||
|
||||
starttlsMsgSent bool // whether we have already sent the STARTTLS handshake to the backend.
|
||||
starttlsReplyReceived bool // whether we have already received the STARTTLS handshake reply from the backend.
|
||||
|
||||
// errChan makes sure that an error is returned if the first operation to ever
|
||||
// happen on a postgresConn is a Write (because it should instead be a Read).
|
||||
errChanMu sync.Mutex
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// Read reads bytes from the underlying connection (tcp.WriteCloser).
|
||||
// On first call, it actually only injects the PostgresStartTLSMsg,
|
||||
// in order to behave as a Postgres TLS client that initiates a STARTTLS handshake.
|
||||
// Read does not support concurrent calls.
|
||||
func (c *postgresConn) Read(p []byte) (n int, err error) {
|
||||
if c.starttlsMsgSent {
|
||||
if err := <-c.errChan; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return c.WriteCloser.Read(p)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
c.starttlsMsgSent = true
|
||||
c.errChanMu.Lock()
|
||||
c.errChan = make(chan error)
|
||||
c.errChanMu.Unlock()
|
||||
}()
|
||||
|
||||
copy(p, PostgresStartTLSMsg)
|
||||
return len(PostgresStartTLSMsg), nil
|
||||
}
|
||||
|
||||
// Write writes bytes to the underlying connection (tcp.WriteCloser).
|
||||
// On first call, it checks that the bytes to write (the ones provided by the backend)
|
||||
// match the PostgresStartTLSReply, and if yes it drops them (as the STARTTLS
|
||||
// handshake between the client and traefik has already taken place). Otherwise, an
|
||||
// error is transmitted through c.errChan, so that the second Read call gets it and
|
||||
// returns it up the stack.
|
||||
// Write does not support concurrent calls.
|
||||
func (c *postgresConn) Write(p []byte) (n int, err error) {
|
||||
if c.starttlsReplyReceived {
|
||||
return c.WriteCloser.Write(p)
|
||||
}
|
||||
|
||||
c.errChanMu.Lock()
|
||||
if c.errChan == nil {
|
||||
c.errChanMu.Unlock()
|
||||
return 0, errors.New("initial read never happened")
|
||||
}
|
||||
c.errChanMu.Unlock()
|
||||
|
||||
defer func() {
|
||||
c.starttlsReplyReceived = true
|
||||
}()
|
||||
|
||||
if len(p) != 1 || p[0] != PostgresStartTLSReply[0] {
|
||||
c.errChan <- errors.New("invalid response from Postgres server")
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
close(c.errChan)
|
||||
|
||||
return 1, nil
|
||||
}
|
|
@ -108,6 +108,17 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
|||
|
||||
// TODO -- Check if ProxyProtocol changes the first bytes of the request
|
||||
br := bufio.NewReader(conn)
|
||||
postgres, err := isPostgres(br)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if postgres {
|
||||
r.servePostgres(r.GetConn(conn, getPeeked(br)))
|
||||
return
|
||||
}
|
||||
|
||||
hello, err := clientHelloInfo(br)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
|
@ -277,7 +288,7 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
|
|||
type Conn struct {
|
||||
// Peeked are the bytes that have been read from Conn for the
|
||||
// purposes of route matching, but have not yet been consumed
|
||||
// by Read calls. It set to nil by Read when fully consumed.
|
||||
// by Read calls. It is set to nil by Read when fully consumed.
|
||||
Peeked []byte
|
||||
|
||||
// Conn is the underlying connection.
|
||||
|
|
|
@ -922,3 +922,89 @@ func checkHTTPSTLS10(addr string, timeout time.Duration) error {
|
|||
func checkHTTPSTLS12(addr string, timeout time.Duration) error {
|
||||
return checkHTTPS(addr, timeout, tls.VersionTLS12)
|
||||
}
|
||||
|
||||
func TestPostgres(t *testing.T) {
|
||||
router, err := NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
// This test requires to have a TLS route, but does not actually check the
|
||||
// content of the handler. It would require to code a TLS handshake to
|
||||
// check the SNI and content of the handlerFunc.
|
||||
err = router.AddRouteTLS("HostSNI(`test.localhost`)", 0, nil, &tls.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.AddRoute("HostSNI(`*`)", 0, tcp2.HandlerFunc(func(conn tcp2.WriteCloser) {
|
||||
_, _ = conn.Write([]byte("OK"))
|
||||
_ = conn.Close()
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
mockConn := NewMockConn()
|
||||
go router.ServeTCP(mockConn)
|
||||
|
||||
mockConn.dataRead <- PostgresStartTLSMsg
|
||||
b := <-mockConn.dataWrite
|
||||
require.Equal(t, PostgresStartTLSReply, b)
|
||||
|
||||
mockConn = NewMockConn()
|
||||
go router.ServeTCP(mockConn)
|
||||
|
||||
mockConn.dataRead <- []byte("HTTP")
|
||||
b = <-mockConn.dataWrite
|
||||
require.Equal(t, []byte("OK"), b)
|
||||
}
|
||||
|
||||
func NewMockConn() *MockConn {
|
||||
return &MockConn{
|
||||
dataRead: make(chan []byte),
|
||||
dataWrite: make(chan []byte),
|
||||
}
|
||||
}
|
||||
|
||||
type MockConn struct {
|
||||
dataRead chan []byte
|
||||
dataWrite chan []byte
|
||||
}
|
||||
|
||||
func (m *MockConn) Read(b []byte) (n int, err error) {
|
||||
temp := <-m.dataRead
|
||||
copy(b, temp)
|
||||
return len(temp), nil
|
||||
}
|
||||
|
||||
func (m *MockConn) Write(b []byte) (n int, err error) {
|
||||
m.dataWrite <- b
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (m *MockConn) Close() error {
|
||||
close(m.dataRead)
|
||||
close(m.dataWrite)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockConn) LocalAddr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockConn) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{}
|
||||
}
|
||||
|
||||
func (m *MockConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockConn) CloseWrite() error {
|
||||
close(m.dataRead)
|
||||
close(m.dataWrite)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ type RouterFactory struct {
|
|||
|
||||
chainBuilder *middleware.ChainBuilder
|
||||
tlsManager *tls.Manager
|
||||
|
||||
cancelPrevState func()
|
||||
}
|
||||
|
||||
// NewRouterFactory creates a new RouterFactory.
|
||||
|
@ -65,7 +67,12 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
|||
|
||||
// CreateRouters creates new TCPRouters and UDPRouters.
|
||||
func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string]*tcprouter.Router, map[string]udptypes.Handler) {
|
||||
ctx := context.Background()
|
||||
if f.cancelPrevState != nil {
|
||||
f.cancelPrevState()
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
ctx, f.cancelPrevState = context.WithCancel(context.Background())
|
||||
|
||||
// HTTP
|
||||
serviceManager := f.managerFactory.Build(rtConf)
|
||||
|
@ -77,7 +84,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
|||
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
||||
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
||||
|
||||
serviceManager.LaunchHealthCheck()
|
||||
serviceManager.LaunchHealthCheck(ctx)
|
||||
|
||||
// TCP
|
||||
svcTCPManager := tcp.NewManager(rtConf)
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
type serviceManager interface {
|
||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||
LaunchHealthCheck()
|
||||
LaunchHealthCheck(ctx context.Context)
|
||||
}
|
||||
|
||||
// InternalHandlers is the internal HTTP handlers builder.
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"container/heap"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
|
@ -39,7 +38,7 @@ type Balancer struct {
|
|||
curDeadline float64
|
||||
// status is a record of which child services of the Balancer are healthy, keyed
|
||||
// by name of child service. A service is initially added to the map when it is
|
||||
// created via AddService, and it is later removed or added to the map as needed,
|
||||
// created via Add, and it is later removed or added to the map as needed,
|
||||
// through the SetStatus method.
|
||||
status map[string]struct{}
|
||||
// updaters is the list of hooks that are run (to update the Balancer
|
||||
|
@ -48,10 +47,10 @@ type Balancer struct {
|
|||
}
|
||||
|
||||
// New creates a new load balancer.
|
||||
func New(sticky *dynamic.Sticky, hc *dynamic.HealthCheck) *Balancer {
|
||||
func New(sticky *dynamic.Sticky, wantHealthCheck bool) *Balancer {
|
||||
balancer := &Balancer{
|
||||
status: make(map[string]struct{}),
|
||||
wantsHealthCheck: hc != nil,
|
||||
wantsHealthCheck: wantHealthCheck,
|
||||
}
|
||||
if sticky != nil && sticky.Cookie != nil {
|
||||
balancer.stickyCookie = &stickyCookie{
|
||||
|
@ -150,10 +149,7 @@ func (b *Balancer) nextServer() (*namedHandler, error) {
|
|||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
if len(b.handlers) == 0 {
|
||||
return nil, fmt.Errorf("no servers in the pool")
|
||||
}
|
||||
if len(b.status) == 0 {
|
||||
if len(b.handlers) == 0 || len(b.status) == 0 {
|
||||
return nil, errNoAvailableServer
|
||||
}
|
||||
|
||||
|
@ -223,9 +219,9 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
server.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
// AddService adds a handler.
|
||||
// Add adds a handler.
|
||||
// A handler with a non-positive weight is ignored.
|
||||
func (b *Balancer) AddService(name string, handler http.Handler, weight *int) {
|
||||
func (b *Balancer) Add(name string, handler http.Handler, weight *int) {
|
||||
w := 1
|
||||
if weight != nil {
|
||||
w = *weight
|
||||
|
|
|
@ -10,31 +10,15 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
)
|
||||
|
||||
func Int(v int) *int { return &v }
|
||||
|
||||
type responseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
save map[string]int
|
||||
sequence []string
|
||||
status []int
|
||||
}
|
||||
|
||||
func (r *responseRecorder) WriteHeader(statusCode int) {
|
||||
r.save[r.Header().Get("server")]++
|
||||
r.sequence = append(r.sequence, r.Header().Get("server"))
|
||||
r.status = append(r.status, statusCode)
|
||||
r.ResponseRecorder.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func TestBalancer(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(3))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "second")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
@ -49,23 +33,23 @@ func TestBalancer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBalancerNoService(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode)
|
||||
}
|
||||
|
||||
func TestBalancerOneServerZeroWeight(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
|
||||
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}}
|
||||
for i := 0; i < 3; i++ {
|
||||
|
@ -80,13 +64,13 @@ type key string
|
|||
const serviceName key = "serviceName"
|
||||
|
||||
func TestBalancerNoServiceUp(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}), Int(1))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}), Int(1))
|
||||
|
||||
|
@ -100,14 +84,14 @@ func TestBalancerNoServiceUp(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBalancerOneServerDown(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}), Int(1))
|
||||
balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false)
|
||||
|
@ -121,14 +105,14 @@ func TestBalancerOneServerDown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBalancerDownThenUp(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "second")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
@ -150,35 +134,35 @@ func TestBalancerDownThenUp(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBalancerPropagate(t *testing.T) {
|
||||
balancer1 := New(nil, &dynamic.HealthCheck{})
|
||||
balancer1 := New(nil, true)
|
||||
|
||||
balancer1.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer1.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
balancer1.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer1.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "second")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
balancer2 := New(nil, &dynamic.HealthCheck{})
|
||||
balancer2.AddService("third", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer2 := New(nil, true)
|
||||
balancer2.Add("third", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "third")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
balancer2.AddService("fourth", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer2.Add("fourth", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "fourth")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
topBalancer := New(nil, &dynamic.HealthCheck{})
|
||||
topBalancer.AddService("balancer1", balancer1, Int(1))
|
||||
topBalancer := New(nil, true)
|
||||
topBalancer.Add("balancer1", balancer1, Int(1))
|
||||
_ = balancer1.RegisterStatusUpdater(func(up bool) {
|
||||
topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer1", up)
|
||||
// TODO(mpl): if test gets flaky, add channel or something here to signal that
|
||||
// propagation is done, and wait on it before sending request.
|
||||
})
|
||||
topBalancer.AddService("balancer2", balancer2, Int(1))
|
||||
topBalancer.Add("balancer2", balancer2, Int(1))
|
||||
_ = balancer2.RegisterStatusUpdater(func(up bool) {
|
||||
topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer2", up)
|
||||
})
|
||||
|
@ -223,28 +207,28 @@ func TestBalancerPropagate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBalancerAllServersZeroWeight(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
balancer.AddService("test", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
balancer.AddService("test2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
balancer.Add("test", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
balancer.Add("test2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, recorder.Result().StatusCode)
|
||||
}
|
||||
|
||||
func TestSticky(t *testing.T) {
|
||||
balancer := New(&dynamic.Sticky{
|
||||
Cookie: &dynamic.Cookie{Name: "test"},
|
||||
}, nil)
|
||||
}, false)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "second")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(2))
|
||||
|
@ -268,14 +252,14 @@ func TestSticky(t *testing.T) {
|
|||
// TestBalancerBias makes sure that the WRR algorithm spreads elements evenly right from the start,
|
||||
// and that it does not "over-favor" the high-weighted ones with a biased start-up regime.
|
||||
func TestBalancerBias(t *testing.T) {
|
||||
balancer := New(nil, nil)
|
||||
balancer := New(nil, false)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "A")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(11))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "B")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(3))
|
||||
|
@ -290,3 +274,19 @@ func TestBalancerBias(t *testing.T) {
|
|||
|
||||
assert.Equal(t, wantSequence, recorder.sequence)
|
||||
}
|
||||
|
||||
func Int(v int) *int { return &v }
|
||||
|
||||
type responseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
save map[string]int
|
||||
sequence []string
|
||||
status []int
|
||||
}
|
||||
|
||||
func (r *responseRecorder) WriteHeader(statusCode int) {
|
||||
r.save[r.Header().Get("server")]++
|
||||
r.sequence = append(r.sequence, r.Header().Get("server"))
|
||||
r.status = append(r.status, statusCode)
|
||||
r.ResponseRecorder.WriteHeader(statusCode)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -12,8 +11,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
@ -24,103 +21,107 @@ const StatusClientClosedRequest = 499
|
|||
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
|
||||
const StatusClientClosedRequestText = "Client Closed Request"
|
||||
|
||||
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
|
||||
var flushInterval ptypes.Duration
|
||||
if responseForwarding != nil {
|
||||
err := flushInterval.Set(responseForwarding.FlushInterval)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating flush interval: %w", err)
|
||||
}
|
||||
}
|
||||
if flushInterval == 0 {
|
||||
flushInterval = ptypes.Duration(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Director: func(outReq *http.Request) {
|
||||
u := outReq.URL
|
||||
if outReq.RequestURI != "" {
|
||||
parsedURL, err := url.ParseRequestURI(outReq.RequestURI)
|
||||
if err == nil {
|
||||
u = parsedURL
|
||||
}
|
||||
}
|
||||
|
||||
outReq.URL.Path = u.Path
|
||||
outReq.URL.RawPath = u.RawPath
|
||||
outReq.URL.RawQuery = strings.ReplaceAll(u.RawQuery, ";", "&")
|
||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
if _, ok := outReq.Header["User-Agent"]; !ok {
|
||||
outReq.Header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
// Do not pass client Host header unless optsetter PassHostHeader is set.
|
||||
if passHostHeader != nil && !*passHostHeader {
|
||||
outReq.Host = outReq.URL.Host
|
||||
}
|
||||
|
||||
// Even if the websocket RFC says that headers should be case-insensitive,
|
||||
// some servers need Sec-WebSocket-Key, Sec-WebSocket-Extensions, Sec-WebSocket-Accept,
|
||||
// Sec-WebSocket-Protocol and Sec-WebSocket-Version to be case-sensitive.
|
||||
// https://tools.ietf.org/html/rfc6455#page-20
|
||||
if isWebSocketUpgrade(outReq) {
|
||||
outReq.Header["Sec-WebSocket-Key"] = outReq.Header["Sec-Websocket-Key"]
|
||||
outReq.Header["Sec-WebSocket-Extensions"] = outReq.Header["Sec-Websocket-Extensions"]
|
||||
outReq.Header["Sec-WebSocket-Accept"] = outReq.Header["Sec-Websocket-Accept"]
|
||||
outReq.Header["Sec-WebSocket-Protocol"] = outReq.Header["Sec-Websocket-Protocol"]
|
||||
outReq.Header["Sec-WebSocket-Version"] = outReq.Header["Sec-Websocket-Version"]
|
||||
delete(outReq.Header, "Sec-Websocket-Key")
|
||||
delete(outReq.Header, "Sec-Websocket-Extensions")
|
||||
delete(outReq.Header, "Sec-Websocket-Accept")
|
||||
delete(outReq.Header, "Sec-Websocket-Protocol")
|
||||
delete(outReq.Header, "Sec-Websocket-Version")
|
||||
}
|
||||
},
|
||||
func buildSingleHostProxy(target *url.URL, passHostHeader bool, flushInterval time.Duration, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) http.Handler {
|
||||
return &httputil.ReverseProxy{
|
||||
Director: directorBuilder(target, passHostHeader),
|
||||
Transport: roundTripper,
|
||||
FlushInterval: time.Duration(flushInterval),
|
||||
FlushInterval: flushInterval,
|
||||
BufferPool: bufferPool,
|
||||
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
ErrorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
statusCode = http.StatusBadGateway
|
||||
case errors.Is(err, context.Canceled):
|
||||
statusCode = StatusClientClosedRequest
|
||||
default:
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
if netErr.Timeout() {
|
||||
statusCode = http.StatusGatewayTimeout
|
||||
} else {
|
||||
statusCode = http.StatusBadGateway
|
||||
}
|
||||
}
|
||||
}
|
||||
func directorBuilder(target *url.URL, passHostHeader bool) func(req *http.Request) {
|
||||
return func(outReq *http.Request) {
|
||||
outReq.URL.Scheme = target.Scheme
|
||||
outReq.URL.Host = target.Host
|
||||
|
||||
log.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
|
||||
w.WriteHeader(statusCode)
|
||||
_, werr := w.Write([]byte(statusText(statusCode)))
|
||||
if werr != nil {
|
||||
log.Debugf("Error while writing status code", werr)
|
||||
u := outReq.URL
|
||||
if outReq.RequestURI != "" {
|
||||
parsedURL, err := url.ParseRequestURI(outReq.RequestURI)
|
||||
if err == nil {
|
||||
u = parsedURL
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
outReq.URL.Path = u.Path
|
||||
outReq.URL.RawPath = u.RawPath
|
||||
outReq.URL.RawQuery = strings.ReplaceAll(u.RawQuery, ";", "&")
|
||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
if _, ok := outReq.Header["User-Agent"]; !ok {
|
||||
outReq.Header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
// Do not pass client Host header unless PassHostHeader is set.
|
||||
if !passHostHeader {
|
||||
outReq.Host = outReq.URL.Host
|
||||
}
|
||||
|
||||
cleanWebSocketHeaders(outReq)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanWebSocketHeaders Even if the websocket RFC says that headers should be case-insensitive,
|
||||
// some servers need Sec-WebSocket-Key, Sec-WebSocket-Extensions, Sec-WebSocket-Accept,
|
||||
// Sec-WebSocket-Protocol and Sec-WebSocket-Version to be case-sensitive.
|
||||
// https://tools.ietf.org/html/rfc6455#page-20
|
||||
func cleanWebSocketHeaders(req *http.Request) {
|
||||
if !isWebSocketUpgrade(req) {
|
||||
return
|
||||
}
|
||||
|
||||
return proxy, nil
|
||||
req.Header["Sec-WebSocket-Key"] = req.Header["Sec-Websocket-Key"]
|
||||
delete(req.Header, "Sec-Websocket-Key")
|
||||
|
||||
req.Header["Sec-WebSocket-Extensions"] = req.Header["Sec-Websocket-Extensions"]
|
||||
delete(req.Header, "Sec-Websocket-Extensions")
|
||||
|
||||
req.Header["Sec-WebSocket-Accept"] = req.Header["Sec-Websocket-Accept"]
|
||||
delete(req.Header, "Sec-Websocket-Accept")
|
||||
|
||||
req.Header["Sec-WebSocket-Protocol"] = req.Header["Sec-Websocket-Protocol"]
|
||||
delete(req.Header, "Sec-Websocket-Protocol")
|
||||
|
||||
req.Header["Sec-WebSocket-Version"] = req.Header["Sec-Websocket-Version"]
|
||||
delete(req.Header, "Sec-Websocket-Version")
|
||||
}
|
||||
|
||||
func isWebSocketUpgrade(req *http.Request) bool {
|
||||
if !httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {
|
||||
return false
|
||||
return httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") &&
|
||||
strings.EqualFold(req.Header.Get("Upgrade"), "websocket")
|
||||
}
|
||||
|
||||
func errorHandler(w http.ResponseWriter, req *http.Request, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
statusCode = http.StatusBadGateway
|
||||
case errors.Is(err, context.Canceled):
|
||||
statusCode = StatusClientClosedRequest
|
||||
default:
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
if netErr.Timeout() {
|
||||
statusCode = http.StatusGatewayTimeout
|
||||
} else {
|
||||
statusCode = http.StatusBadGateway
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.EqualFold(req.Header.Get("Upgrade"), "websocket")
|
||||
logger := log.FromContext(req.Context())
|
||||
logger.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
|
||||
|
||||
w.WriteHeader(statusCode)
|
||||
if _, werr := w.Write([]byte(statusText(statusCode))); werr != nil {
|
||||
logger.Debugf("Error while writing status code", werr)
|
||||
}
|
||||
}
|
||||
|
||||
func statusText(statusCode int) string {
|
||||
|
|
|
@ -28,7 +28,7 @@ func BenchmarkProxy(b *testing.B) {
|
|||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||
|
||||
pool := newBufferPool()
|
||||
handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool)
|
||||
handler := buildSingleHostProxy(req.URL, false, 0, &staticTransport{res}, pool)
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
|
|
@ -18,12 +18,7 @@ import (
|
|||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
func Bool(v bool) *bool { return &v }
|
||||
|
||||
func TestWebSocketTCPClose(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -42,7 +37,7 @@ func TestWebSocketTCPClose(t *testing.T) {
|
|||
}))
|
||||
defer srv.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
|
||||
proxyAddr := proxy.Listener.Addr().String()
|
||||
_, conn, err := newWebsocketRequest(
|
||||
|
@ -61,10 +56,6 @@ func TestWebSocketTCPClose(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketPingPong(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
CheckOrigin: func(*http.Request) bool {
|
||||
|
@ -86,17 +77,10 @@ func TestWebSocketPingPong(t *testing.T) {
|
|||
_, _, _ = ws.ReadMessage()
|
||||
})
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
}))
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = parseURI(t, srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
serverAddr := proxy.Listener.Addr().String()
|
||||
|
||||
headers := http.Header{}
|
||||
|
@ -127,9 +111,6 @@ func TestWebSocketPingPong(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketEcho(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
msg := make([]byte, 4)
|
||||
|
@ -145,17 +126,10 @@ func TestWebSocketEcho(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
}))
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = parseURI(t, srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
serverAddr := proxy.Listener.Addr().String()
|
||||
|
||||
headers := http.Header{}
|
||||
|
@ -193,10 +167,6 @@ func TestWebSocketPassHost(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
f, err := buildProxy(Bool(test.passHost), nil, http.DefaultTransport, nil)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
req := conn.Request()
|
||||
|
@ -208,7 +178,7 @@ func TestWebSocketPassHost(t *testing.T) {
|
|||
}
|
||||
|
||||
msg := make([]byte, 4)
|
||||
_, err = conn.Read(msg)
|
||||
_, err := conn.Read(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Println(string(msg))
|
||||
|
@ -219,16 +189,10 @@ func TestWebSocketPassHost(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
}))
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = parseURI(t, srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
|
||||
serverAddr := proxy.Listener.Addr().String()
|
||||
|
||||
|
@ -252,9 +216,6 @@ func TestWebSocketPassHost(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
}}
|
||||
|
@ -277,7 +238,7 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
|||
}))
|
||||
defer srv.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
defer proxy.Close()
|
||||
|
||||
proxyAddr := proxy.Listener.Addr().String()
|
||||
|
@ -293,9 +254,6 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
|
@ -316,11 +274,11 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
|
|||
}))
|
||||
defer srv.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
defer proxy.Close()
|
||||
|
||||
proxyAddr := proxy.Listener.Addr().String()
|
||||
_, err = newWebsocketRequest(
|
||||
_, err := newWebsocketRequest(
|
||||
withServer(proxyAddr),
|
||||
withPath("/ws"),
|
||||
withData("echo"),
|
||||
|
@ -339,9 +297,6 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
|
@ -363,7 +318,7 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
|||
}))
|
||||
defer srv.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
defer proxy.Close()
|
||||
|
||||
proxyAddr := proxy.Listener.Addr().String()
|
||||
|
@ -379,18 +334,14 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
conn.Close()
|
||||
}))
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
}))
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
f := buildSingleHostProxy(parseURI(t, srv.URL), true, 0, http.DefaultTransport, nil)
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = parseURI(t, srv.URL)
|
||||
w.Header().Set("HEADER-KEY", "HEADER-VALUE")
|
||||
|
@ -403,6 +354,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
|||
headers := http.Header{}
|
||||
webSocketURL := "ws://" + serverAddr + "/ws"
|
||||
headers.Add("Origin", webSocketURL)
|
||||
|
||||
conn, resp, err := gorillawebsocket.DefaultDialer.Dial(webSocketURL, headers)
|
||||
require.NoError(t, err, "Error during Dial with response: %+v", err, resp)
|
||||
defer conn.Close()
|
||||
|
@ -411,9 +363,6 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
upgrader := gorillawebsocket.Upgrader{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
|
@ -435,7 +384,7 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
|||
}))
|
||||
defer srv.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
defer proxy.Close()
|
||||
|
||||
proxyAddr := proxy.Listener.Addr().String()
|
||||
|
@ -451,18 +400,14 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWebSocketUpgradeFailed(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/ws", func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
})
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
}))
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
f := buildSingleHostProxy(parseURI(t, srv.URL), true, 0, http.DefaultTransport, nil)
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path // keep the original path
|
||||
|
||||
|
@ -501,9 +446,6 @@ func TestWebSocketUpgradeFailed(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestForwardsWebsocketTraffic(t *testing.T) {
|
||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||
_, err := conn.Write([]byte("ok"))
|
||||
|
@ -512,12 +454,10 @@ func TestForwardsWebsocketTraffic(t *testing.T) {
|
|||
err = conn.Close()
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
mux.ServeHTTP(w, req)
|
||||
}))
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
||||
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
defer proxy.Close()
|
||||
|
||||
proxyAddr := proxy.Listener.Addr().String()
|
||||
|
@ -557,15 +497,12 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
|||
srv := createTLSWebsocketServer()
|
||||
defer srv.Close()
|
||||
|
||||
forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyWithoutTLSConfig := createProxyWithForwarder(t, forwarderWithoutTLSConfig, srv.URL)
|
||||
proxyWithoutTLSConfig := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||
defer proxyWithoutTLSConfig.Close()
|
||||
|
||||
proxyAddr := proxyWithoutTLSConfig.Listener.Addr().String()
|
||||
|
||||
_, err = newWebsocketRequest(
|
||||
_, err := newWebsocketRequest(
|
||||
withServer(proxyAddr),
|
||||
withPath("/ws"),
|
||||
withData("ok"),
|
||||
|
@ -576,10 +513,8 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
|||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
forwarderWithTLSConfig, err := buildProxy(Bool(true), nil, transport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyWithTLSConfig := createProxyWithForwarder(t, forwarderWithTLSConfig, srv.URL)
|
||||
proxyWithTLSConfig := createProxyWithForwarder(t, srv.URL, transport)
|
||||
defer proxyWithTLSConfig.Close()
|
||||
|
||||
proxyAddr = proxyWithTLSConfig.Listener.Addr().String()
|
||||
|
@ -597,10 +532,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
|||
defaultTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
defaultTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, defaultTransport, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(t, forwarderWithTLSConfigFromDefaultTransport, srv.URL)
|
||||
proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(t, srv.URL, defaultTransport)
|
||||
defer proxyWithTLSConfig.Close()
|
||||
|
||||
proxyAddr = proxyWithTLSConfigFromDefaultTransport.Listener.Addr().String()
|
||||
|
@ -705,15 +637,19 @@ func parseURI(t *testing.T, uri string) *url.URL {
|
|||
return out
|
||||
}
|
||||
|
||||
func createProxyWithForwarder(t *testing.T, proxy http.Handler, url string) *httptest.Server {
|
||||
func createProxyWithForwarder(t *testing.T, uri string, transport http.RoundTripper) *httptest.Server {
|
||||
t.Helper()
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
u := parseURI(t, uri)
|
||||
proxy := buildSingleHostProxy(u, true, 0, transport, nil)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path // keep the original path
|
||||
// Set new backend URL
|
||||
req.URL = parseURI(t, url)
|
||||
req.URL = u
|
||||
req.URL.Path = path
|
||||
|
||||
proxy.ServeHTTP(w, req)
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
|
|
@ -4,36 +4,28 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v2/pkg/healthcheck"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/metrics"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/emptybackendhandler"
|
||||
metricsMiddle "github.com/traefik/traefik/v2/pkg/middlewares/metrics"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/pipelining"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/traefik/traefik/v2/pkg/server/cookie"
|
||||
"github.com/traefik/traefik/v2/pkg/server/provider"
|
||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/failover"
|
||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
|
||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/roundrobin/stickycookie"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHealthCheckInterval = 30 * time.Second
|
||||
defaultHealthCheckTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
const defaultMaxBodySize int64 = -1
|
||||
|
@ -43,6 +35,19 @@ type RoundTripperGetter interface {
|
|||
Get(name string) (http.RoundTripper, error)
|
||||
}
|
||||
|
||||
// Manager The service manager.
|
||||
type Manager struct {
|
||||
routinePool *safe.Pool
|
||||
metricsRegistry metrics.Registry
|
||||
bufferPool httputil.BufferPool
|
||||
roundTripperManager RoundTripperGetter
|
||||
|
||||
services map[string]http.Handler
|
||||
configs map[string]*runtime.ServiceInfo
|
||||
healthCheckers map[string]*healthcheck.ServiceHealthChecker
|
||||
rand *rand.Rand // For the initial shuffling of load-balancers.
|
||||
}
|
||||
|
||||
// NewManager creates a new Manager.
|
||||
func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager {
|
||||
return &Manager{
|
||||
|
@ -50,27 +55,13 @@ func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics
|
|||
metricsRegistry: metricsRegistry,
|
||||
bufferPool: newBufferPool(),
|
||||
roundTripperManager: roundTripperManager,
|
||||
balancers: make(map[string]healthcheck.Balancers),
|
||||
services: make(map[string]http.Handler),
|
||||
configs: configs,
|
||||
healthCheckers: make(map[string]*healthcheck.ServiceHealthChecker),
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
}
|
||||
|
||||
// Manager The service manager.
|
||||
type Manager struct {
|
||||
routinePool *safe.Pool
|
||||
metricsRegistry metrics.Registry
|
||||
bufferPool httputil.BufferPool
|
||||
roundTripperManager RoundTripperGetter
|
||||
// balancers is the map of all Balancers, keyed by service name.
|
||||
// There is one Balancer per service handler, and there is one service handler per reference to a service
|
||||
// (e.g. if 2 routers refer to the same service name, 2 service handlers are created),
|
||||
// which is why there is not just one Balancer per service name.
|
||||
balancers map[string]healthcheck.Balancers
|
||||
configs map[string]*runtime.ServiceInfo
|
||||
rand *rand.Rand // For the initial shuffling of load-balancers.
|
||||
}
|
||||
|
||||
// BuildHTTP Creates a http.Handler for a service configuration.
|
||||
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
||||
ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName))
|
||||
|
@ -78,11 +69,20 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
|
|||
serviceName = provider.GetQualifiedName(ctx, serviceName)
|
||||
ctx = provider.AddInContext(ctx, serviceName)
|
||||
|
||||
handler, ok := m.services[serviceName]
|
||||
if ok {
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
conf, ok := m.configs[serviceName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the service %q does not exist", serviceName)
|
||||
}
|
||||
|
||||
if conf.Status == runtime.StatusDisabled {
|
||||
return nil, errors.New(strings.Join(conf.Err, ", "))
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(*conf.Service)
|
||||
var count int
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
|
@ -101,7 +101,7 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
|
|||
switch {
|
||||
case conf.LoadBalancer != nil:
|
||||
var err error
|
||||
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer)
|
||||
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf)
|
||||
if err != nil {
|
||||
conf.AddError(err, true)
|
||||
return nil, err
|
||||
|
@ -133,6 +133,8 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
|
|||
return nil, sErr
|
||||
}
|
||||
|
||||
m.services[serviceName] = lb
|
||||
|
||||
return lb, nil
|
||||
}
|
||||
|
||||
|
@ -214,14 +216,14 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
|
|||
config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName)
|
||||
}
|
||||
|
||||
balancer := wrr.New(config.Sticky, config.HealthCheck)
|
||||
balancer := wrr.New(config.Sticky, config.HealthCheck != nil)
|
||||
for _, service := range shuffle(config.Services, m.rand) {
|
||||
serviceHandler, err := m.BuildHTTP(ctx, service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
balancer.AddService(service.Name, serviceHandler, service.Weight)
|
||||
balancer.Add(service.Name, serviceHandler, service.Weight)
|
||||
|
||||
if config.HealthCheck == nil {
|
||||
continue
|
||||
|
@ -245,216 +247,91 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
|
|||
return balancer, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName string, service *dynamic.ServersLoadBalancer) (http.Handler, error) {
|
||||
if service.PassHostHeader == nil {
|
||||
defaultPassHostHeader := true
|
||||
service.PassHostHeader = &defaultPassHostHeader
|
||||
func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName string, info *runtime.ServiceInfo) (http.Handler, error) {
|
||||
service := info.LoadBalancer
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
logger.Debug("Creating load-balancer")
|
||||
|
||||
// TODO: should we keep this config value as Go is now handling stream response correctly?
|
||||
flushInterval := dynamic.DefaultFlushInterval
|
||||
if service.ResponseForwarding != nil {
|
||||
flushInterval = service.ResponseForwarding.FlushInterval
|
||||
}
|
||||
|
||||
if len(service.ServersTransport) > 0 {
|
||||
service.ServersTransport = provider.GetQualifiedName(ctx, service.ServersTransport)
|
||||
}
|
||||
|
||||
if service.Sticky != nil && service.Sticky.Cookie != nil {
|
||||
service.Sticky.Cookie.Name = cookie.GetName(service.Sticky.Cookie.Name, serviceName)
|
||||
}
|
||||
|
||||
// We make sure that the PassHostHeader value is defined to avoid panics.
|
||||
passHostHeader := dynamic.DefaultPassHostHeader
|
||||
if service.PassHostHeader != nil {
|
||||
passHostHeader = *service.PassHostHeader
|
||||
}
|
||||
|
||||
roundTripper, err := m.roundTripperManager.Get(service.ServersTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, roundTripper, m.bufferPool)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
lb := wrr.New(service.Sticky, service.HealthCheck != nil)
|
||||
healthCheckTargets := make(map[string]*url.URL)
|
||||
|
||||
for _, server := range shuffle(service.Servers, m.rand) {
|
||||
hasher := fnv.New64a()
|
||||
_, _ = hasher.Write([]byte(server.URL)) // this will never return an error.
|
||||
|
||||
proxyName := fmt.Sprintf("%x", hasher.Sum(nil))
|
||||
|
||||
target, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing server URL %s: %w", server.URL, err)
|
||||
}
|
||||
|
||||
logger.WithField(log.ServerName, proxyName).Debugf("Creating server %s", target)
|
||||
|
||||
proxy := buildSingleHostProxy(target, passHostHeader, time.Duration(flushInterval), roundTripper, m.bufferPool)
|
||||
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, nil)
|
||||
|
||||
if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() {
|
||||
proxy = metricsMiddle.NewServiceMiddleware(ctx, proxy, m.metricsRegistry, serviceName)
|
||||
}
|
||||
|
||||
lb.Add(proxyName, proxy, nil)
|
||||
|
||||
// servers are considered UP by default.
|
||||
info.UpdateServerStatus(target.String(), runtime.StatusUp)
|
||||
|
||||
healthCheckTargets[proxyName] = target
|
||||
}
|
||||
|
||||
alHandler := func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, accesslog.ServiceName, serviceName, accesslog.AddServiceFields), nil
|
||||
}
|
||||
chain := alice.New()
|
||||
if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() {
|
||||
chain = chain.Append(metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName))
|
||||
if service.HealthCheck != nil {
|
||||
m.healthCheckers[serviceName] = healthcheck.NewServiceHealthChecker(
|
||||
ctx,
|
||||
m.metricsRegistry,
|
||||
service.HealthCheck,
|
||||
lb,
|
||||
info,
|
||||
roundTripper,
|
||||
healthCheckTargets,
|
||||
)
|
||||
}
|
||||
|
||||
handler, err := chain.Append(alHandler).Then(pipelining.New(ctx, fwd, "pipelining"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
balancer, err := m.getLoadBalancer(ctx, serviceName, service, handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO rename and checks
|
||||
m.balancers[serviceName] = append(m.balancers[serviceName], balancer)
|
||||
|
||||
// Empty (backend with no servers)
|
||||
return emptybackendhandler.New(balancer), nil
|
||||
return lb, nil
|
||||
}
|
||||
|
||||
// LaunchHealthCheck launches the health checks.
|
||||
func (m *Manager) LaunchHealthCheck() {
|
||||
backendConfigs := make(map[string]*healthcheck.BackendConfig)
|
||||
|
||||
for serviceName, balancers := range m.balancers {
|
||||
ctx := log.With(context.Background(), log.Str(log.ServiceName, serviceName))
|
||||
|
||||
service := m.configs[serviceName].LoadBalancer
|
||||
|
||||
// Health Check
|
||||
hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck)
|
||||
if hcOpts == nil {
|
||||
continue
|
||||
}
|
||||
hcOpts.Transport, _ = m.roundTripperManager.Get(service.ServersTransport)
|
||||
log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts)
|
||||
|
||||
backendConfigs[serviceName] = healthcheck.NewBackendConfig(*hcOpts, serviceName)
|
||||
}
|
||||
|
||||
healthcheck.GetHealthCheck(m.metricsRegistry).SetBackendsConfiguration(context.Background(), backendConfigs)
|
||||
}
|
||||
|
||||
func buildHealthCheckOptions(ctx context.Context, lb healthcheck.Balancer, backend string, hc *dynamic.ServerHealthCheck) *healthcheck.Options {
|
||||
if hc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
if hc.Path == "" {
|
||||
logger.Errorf("Ignoring heath check configuration for '%s': no path provided", backend)
|
||||
return nil
|
||||
}
|
||||
|
||||
interval := defaultHealthCheckInterval
|
||||
if hc.Interval != "" {
|
||||
intervalOverride, err := time.ParseDuration(hc.Interval)
|
||||
switch {
|
||||
case err != nil:
|
||||
logger.Errorf("Illegal health check interval for '%s': %s", backend, err)
|
||||
case intervalOverride <= 0:
|
||||
logger.Errorf("Health check interval smaller than zero for service '%s'", backend)
|
||||
default:
|
||||
interval = intervalOverride
|
||||
}
|
||||
}
|
||||
|
||||
timeout := defaultHealthCheckTimeout
|
||||
if hc.Timeout != "" {
|
||||
timeoutOverride, err := time.ParseDuration(hc.Timeout)
|
||||
switch {
|
||||
case err != nil:
|
||||
logger.Errorf("Illegal health check timeout for backend '%s': %s", backend, err)
|
||||
case timeoutOverride <= 0:
|
||||
logger.Errorf("Health check timeout smaller than zero for backend '%s', backend", backend)
|
||||
default:
|
||||
timeout = timeoutOverride
|
||||
}
|
||||
}
|
||||
|
||||
if timeout >= interval {
|
||||
logger.Warnf("Health check timeout for backend '%s' should be lower than the health check interval. Interval set to timeout + 1 second (%s).", backend, interval)
|
||||
}
|
||||
|
||||
followRedirects := true
|
||||
if hc.FollowRedirects != nil {
|
||||
followRedirects = *hc.FollowRedirects
|
||||
}
|
||||
|
||||
mode := healthcheck.HTTPMode
|
||||
switch hc.Mode {
|
||||
case "":
|
||||
mode = healthcheck.HTTPMode
|
||||
case healthcheck.GRPCMode, healthcheck.HTTPMode:
|
||||
mode = hc.Mode
|
||||
default:
|
||||
logger.Errorf("Illegal health check mode for backend '%s'", backend)
|
||||
}
|
||||
|
||||
return &healthcheck.Options{
|
||||
Scheme: hc.Scheme,
|
||||
Mode: mode,
|
||||
Path: hc.Path,
|
||||
Method: hc.Method,
|
||||
Port: hc.Port,
|
||||
Interval: interval,
|
||||
Timeout: timeout,
|
||||
LB: lb,
|
||||
Hostname: hc.Hostname,
|
||||
Headers: hc.Headers,
|
||||
FollowRedirects: followRedirects,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, service *dynamic.ServersLoadBalancer, fwd http.Handler) (healthcheck.BalancerStatusHandler, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
logger.Debug("Creating load-balancer")
|
||||
|
||||
var options []roundrobin.LBOption
|
||||
|
||||
var cookieName string
|
||||
if service.Sticky != nil && service.Sticky.Cookie != nil {
|
||||
cookieName = cookie.GetName(service.Sticky.Cookie.Name, serviceName)
|
||||
|
||||
opts := roundrobin.CookieOptions{
|
||||
HTTPOnly: service.Sticky.Cookie.HTTPOnly,
|
||||
Secure: service.Sticky.Cookie.Secure,
|
||||
SameSite: convertSameSite(service.Sticky.Cookie.SameSite),
|
||||
}
|
||||
|
||||
// Sticky Cookie Value
|
||||
cv, err := stickycookie.NewFallbackValue(&stickycookie.RawValue{}, &stickycookie.HashValue{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySessionWithOptions(cookieName, opts).SetCookieValue(cv)))
|
||||
|
||||
logger.Debugf("Sticky session cookie name: %v", cookieName)
|
||||
}
|
||||
|
||||
lb, err := roundrobin.New(fwd, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lbsu := healthcheck.NewLBStatusUpdater(lb, m.configs[serviceName], service.HealthCheck)
|
||||
if err := m.upsertServers(ctx, lbsu, service.Servers); err != nil {
|
||||
return nil, fmt.Errorf("error configuring load balancer for service %s: %w", serviceName, err)
|
||||
}
|
||||
|
||||
return lbsu, nil
|
||||
}
|
||||
|
||||
func (m *Manager) upsertServers(ctx context.Context, lb healthcheck.BalancerHandler, servers []dynamic.Server) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
for name, srv := range shuffle(servers, m.rand) {
|
||||
u, err := url.Parse(srv.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing server URL %s: %w", srv.URL, err)
|
||||
}
|
||||
|
||||
logger.WithField(log.ServerName, name).Debugf("Creating server %d %s", name, u)
|
||||
|
||||
if err := lb.UpsertServer(u, roundrobin.Weight(1)); err != nil {
|
||||
return fmt.Errorf("error adding server %s to load balancer: %w", srv.URL, err)
|
||||
}
|
||||
|
||||
// TODO Handle Metrics
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSameSite(sameSite string) http.SameSite {
|
||||
switch sameSite {
|
||||
case "none":
|
||||
return http.SameSiteNoneMode
|
||||
case "lax":
|
||||
return http.SameSiteLaxMode
|
||||
case "strict":
|
||||
return http.SameSiteStrictMode
|
||||
default:
|
||||
return 0
|
||||
func (m *Manager) LaunchHealthCheck(ctx context.Context) {
|
||||
for serviceName, hc := range m.healthCheckers {
|
||||
ctx = log.With(ctx, log.Str(log.ServiceName, serviceName))
|
||||
go hc.Launch(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,14 +15,10 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
)
|
||||
|
||||
type MockForwarder struct{}
|
||||
|
||||
func (MockForwarder) ServeHTTP(http.ResponseWriter, *http.Request) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestGetLoadBalancer(t *testing.T) {
|
||||
sm := Manager{}
|
||||
sm := Manager{
|
||||
roundTripperManager: newRtMock(),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -67,7 +63,8 @@ func TestGetLoadBalancer(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
handler, err := sm.getLoadBalancer(context.Background(), test.serviceName, test.service, test.fwd)
|
||||
serviceInfo := &runtime.ServiceInfo{Service: &dynamic.Service{LoadBalancer: test.service}}
|
||||
handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, serviceInfo)
|
||||
if test.expectError {
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, handler)
|
||||
|
@ -129,6 +126,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
desc: "Load balances between the two servers",
|
||||
serviceName: "test",
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server1.URL,
|
||||
|
@ -258,40 +256,13 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Cookie value is backward compatible",
|
||||
serviceName: "test",
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Sticky: &dynamic.Sticky{
|
||||
Cookie: &dynamic.Cookie{},
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server1.URL,
|
||||
},
|
||||
{
|
||||
URL: server2.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
cookieRawValue: "_6f743=" + server1.URL,
|
||||
expected: []ExpectedResult{
|
||||
{
|
||||
StatusCode: http.StatusOK,
|
||||
XFrom: "first",
|
||||
},
|
||||
{
|
||||
StatusCode: http.StatusOK,
|
||||
XFrom: "first",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service)
|
||||
serviceInfo := &runtime.ServiceInfo{Service: &dynamic.Service{LoadBalancer: test.service}}
|
||||
handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, serviceInfo)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, handler)
|
||||
|
@ -419,3 +390,21 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) {
|
|||
_, err := manager.BuildHTTP(context.Background(), "test@file")
|
||||
assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
|
||||
}
|
||||
|
||||
func Bool(v bool) *bool { return &v }
|
||||
|
||||
type MockForwarder struct{}
|
||||
|
||||
func (MockForwarder) ServeHTTP(http.ResponseWriter, *http.Request) {
|
||||
panic("not available")
|
||||
}
|
||||
|
||||
type rtMock struct{}
|
||||
|
||||
func newRtMock() RoundTripperGetter {
|
||||
return &rtMock{}
|
||||
}
|
||||
|
||||
func (r *rtMock) Get(_ string) (http.RoundTripper, error) {
|
||||
return http.DefaultTransport, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue