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:
|
exclude:
|
||||||
- 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
- '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"
|
- "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.SSLRedirect is deprecated'
|
||||||
- 'SA1019: cfg.SSLTemporaryRedirect is deprecated'
|
- 'SA1019: cfg.SSLTemporaryRedirect is deprecated'
|
||||||
- 'SA1019: cfg.SSLHost is deprecated'
|
- 'SA1019: cfg.SSLHost is deprecated'
|
||||||
|
|
|
@ -5,23 +5,24 @@ description: "Traefik Proxy's HTTP middleware lets you compress responses before
|
||||||
|
|
||||||
# Compress
|
# Compress
|
||||||
|
|
||||||
Compress Responses before Sending them to the Client
|
Compress Allows Compressing Responses before Sending them to the Client
|
||||||
{: .subtitle }
|
{: .subtitle }
|
||||||
|
|
||||||
![Compress](../../assets/img/middleware/compress.png)
|
![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
|
## Configuration Examples
|
||||||
|
|
||||||
```yaml tab="Docker"
|
```yaml tab="Docker"
|
||||||
# Enable gzip compression
|
# Enable compression
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.middlewares.test-compress.compress=true"
|
- "traefik.http.middlewares.test-compress.compress=true"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes"
|
```yaml tab="Kubernetes"
|
||||||
# Enable gzip compression
|
# Enable compression
|
||||||
apiVersion: traefik.containo.us/v1alpha1
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -31,7 +32,7 @@ spec:
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Consul Catalog"
|
```yaml tab="Consul Catalog"
|
||||||
# Enable gzip compression
|
# Enable compression
|
||||||
- "traefik.http.middlewares.test-compress.compress=true"
|
- "traefik.http.middlewares.test-compress.compress=true"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -42,13 +43,13 @@ spec:
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Rancher"
|
```yaml tab="Rancher"
|
||||||
# Enable gzip compression
|
# Enable compression
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.middlewares.test-compress.compress=true"
|
- "traefik.http.middlewares.test-compress.compress=true"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
# Enable gzip compression
|
# Enable compression
|
||||||
http:
|
http:
|
||||||
middlewares:
|
middlewares:
|
||||||
test-compress:
|
test-compress:
|
||||||
|
@ -56,7 +57,7 @@ http:
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
# Enable gzip compression
|
# Enable compression
|
||||||
[http.middlewares]
|
[http.middlewares]
|
||||||
[http.middlewares.test-compress.compress]
|
[http.middlewares.test-compress.compress]
|
||||||
```
|
```
|
||||||
|
@ -65,23 +66,34 @@ http:
|
||||||
|
|
||||||
Responses are compressed when the following criteria are all met:
|
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`, `*`, and/or `br` with or without [quality values](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values).
|
||||||
* The `Accept-Encoding` request header contains `gzip`.
|
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.
|
* The response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
|
||||||
|
* The response`Content-Type` header is not one among the [excludedContentTypes options](#excludedcontenttypes).
|
||||||
If the `Content-Type` header is not defined, or empty, the compress middleware will automatically [detect](https://mimesniff.spec.whatwg.org/) a content type.
|
* The response body is larger than the [configured minimum amount of bytes](#minresponsebodybytes) (default is `1024`).
|
||||||
It will also set the `Content-Type` header according to the detected MIME type.
|
|
||||||
|
|
||||||
## Configuration Options
|
## Configuration Options
|
||||||
|
|
||||||
### `excludedContentTypes`
|
### `excludedContentTypes`
|
||||||
|
|
||||||
|
_Optional, Default=""_
|
||||||
|
|
||||||
`excludedContentTypes` specifies a list of content types to compare the `Content-Type` header of the incoming requests and responses before compressing.
|
`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.
|
The responses with content types defined in `excludedContentTypes` are not compressed.
|
||||||
|
|
||||||
Content types are compared in a case-insensitive, whitespace-ignored manner.
|
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"
|
```yaml tab="Docker"
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.middlewares.test-compress.compress.excludedcontenttypes=text/event-stream"
|
- "traefik.http.middlewares.test-compress.compress.excludedcontenttypes=text/event-stream"
|
||||||
|
@ -130,9 +142,9 @@ http:
|
||||||
|
|
||||||
### `minResponseBodyBytes`
|
### `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.
|
Responses smaller than the specified values will not be compressed.
|
||||||
|
|
||||||
|
|
|
@ -57,15 +57,15 @@
|
||||||
path = "foobar"
|
path = "foobar"
|
||||||
method = "foobar"
|
method = "foobar"
|
||||||
port = 42
|
port = 42
|
||||||
interval = "foobar"
|
interval = "42s"
|
||||||
timeout = "foobar"
|
timeout = "42s"
|
||||||
hostname = "foobar"
|
hostname = "foobar"
|
||||||
followRedirects = true
|
followRedirects = true
|
||||||
[http.services.Service01.loadBalancer.healthCheck.headers]
|
[http.services.Service01.loadBalancer.healthCheck.headers]
|
||||||
name0 = "foobar"
|
name0 = "foobar"
|
||||||
name1 = "foobar"
|
name1 = "foobar"
|
||||||
[http.services.Service01.loadBalancer.responseForwarding]
|
[http.services.Service01.loadBalancer.responseForwarding]
|
||||||
flushInterval = "foobar"
|
flushInterval = "42s"
|
||||||
[http.services.Service02]
|
[http.services.Service02]
|
||||||
[http.services.Service02.mirroring]
|
[http.services.Service02.mirroring]
|
||||||
service = "foobar"
|
service = "foobar"
|
||||||
|
|
|
@ -62,8 +62,8 @@ http:
|
||||||
path: foobar
|
path: foobar
|
||||||
method: foobar
|
method: foobar
|
||||||
port: 42
|
port: 42
|
||||||
interval: foobar
|
interval: 42s
|
||||||
timeout: foobar
|
timeout: 42s
|
||||||
hostname: foobar
|
hostname: foobar
|
||||||
followRedirects: true
|
followRedirects: true
|
||||||
headers:
|
headers:
|
||||||
|
@ -71,7 +71,7 @@ http:
|
||||||
name1: foobar
|
name1: foobar
|
||||||
passHostHeader: true
|
passHostHeader: true
|
||||||
responseForwarding:
|
responseForwarding:
|
||||||
flushInterval: foobar
|
flushInterval: 42s
|
||||||
serversTransport: foobar
|
serversTransport: foobar
|
||||||
Service02:
|
Service02:
|
||||||
mirroring:
|
mirroring:
|
||||||
|
|
|
@ -749,7 +749,8 @@ spec:
|
||||||
excludedContentTypes:
|
excludedContentTypes:
|
||||||
description: ExcludedContentTypes defines the list of content
|
description: ExcludedContentTypes defines the list of content
|
||||||
types to compare the Content-Type header of the incoming requests
|
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:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
|
|
|
@ -214,15 +214,15 @@
|
||||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0` | `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/headers/name1` | `foobar` |
|
||||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/hostname` | `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/method` | `foobar` |
|
||||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/mode` | `foobar` |
|
| `traefik/http/services/Service01/loadBalancer/healthCheck/mode` | `foobar` |
|
||||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/path` | `foobar` |
|
| `traefik/http/services/Service01/loadBalancer/healthCheck/path` | `foobar` |
|
||||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/port` | `42` |
|
| `traefik/http/services/Service01/loadBalancer/healthCheck/port` | `42` |
|
||||||
| `traefik/http/services/Service01/loadBalancer/healthCheck/scheme` | `foobar` |
|
| `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/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/0/url` | `foobar` |
|
||||||
| `traefik/http/services/Service01/loadBalancer/servers/1/url` | `foobar` |
|
| `traefik/http/services/Service01/loadBalancer/servers/1/url` | `foobar` |
|
||||||
| `traefik/http/services/Service01/loadBalancer/serversTransport` | `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.name0": "foobar",
|
||||||
"traefik.http.services.service01.loadbalancer.healthcheck.headers.name1": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||||
"traefik.http.services.service01.loadbalancer.healthcheck.hostname": "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.path": "foobar",
|
||||||
"traefik.http.services.service01.loadbalancer.healthcheck.method": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.method": "foobar",
|
||||||
"traefik.http.services.service01.loadbalancer.healthcheck.port": "42",
|
"traefik.http.services.service01.loadbalancer.healthcheck.port": "42",
|
||||||
"traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar",
|
"traefik.http.services.service01.loadbalancer.healthcheck.scheme": "foobar",
|
||||||
"traefik.http.services.service01.loadbalancer.healthcheck.mode": "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.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.serverstransport": "foobar",
|
||||||
"traefik.http.services.service01.loadbalancer.sticky.cookie": "true",
|
"traefik.http.services.service01.loadbalancer.sticky.cookie": "true",
|
||||||
"traefik.http.services.service01.loadbalancer.sticky.cookie.httponly": "true",
|
"traefik.http.services.service01.loadbalancer.sticky.cookie.httponly": "true",
|
||||||
|
|
|
@ -172,7 +172,8 @@ spec:
|
||||||
excludedContentTypes:
|
excludedContentTypes:
|
||||||
description: ExcludedContentTypes defines the list of content
|
description: ExcludedContentTypes defines the list of content
|
||||||
types to compare the Content-Type header of the incoming requests
|
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:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
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:
|
The table below lists all the available matchers:
|
||||||
|
|
||||||
| Rule | Description |
|
| Rule | Description |
|
||||||
|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
|
|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
|
||||||
| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` |
|
| ```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` |
|
| ```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`. |
|
| ```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. |
|
| ```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. |
|
| ```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`) |
|
| ```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. |
|
| ```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. |
|
| ```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. |
|
| ```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. |
|
| ```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"
|
!!! 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]
|
[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`
|
#### `passthrough`
|
||||||
|
|
||||||
As seen above, a TLS router will terminate the TLS connection by default.
|
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/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61
|
||||||
github.com/Masterminds/sprig/v3 v3.2.2
|
github.com/Masterminds/sprig/v3 v3.2.2
|
||||||
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
|
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/aws/aws-sdk-go v1.44.47
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3
|
github.com/cenkalti/backoff/v4 v4.1.3
|
||||||
github.com/compose-spec/compose-go v1.0.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 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
||||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
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.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/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/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=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
|
|
@ -749,7 +749,8 @@ spec:
|
||||||
excludedContentTypes:
|
excludedContentTypes:
|
||||||
description: ExcludedContentTypes defines the list of content
|
description: ExcludedContentTypes defines the list of content
|
||||||
types to compare the Content-Type header of the incoming requests
|
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:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
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"
|
"url": "http://10.0.1.1:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
@ -201,7 +204,10 @@
|
||||||
"url": "http://10.0.1.2:8889"
|
"url": "http://10.0.1.2:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"status": "enabled"
|
||||||
},
|
},
|
||||||
|
@ -215,7 +221,10 @@
|
||||||
"url": "http://10.0.1.3:8889"
|
"url": "http://10.0.1.3:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"status": "enabled"
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,10 @@
|
||||||
"url": "http://10.42.0.4:80"
|
"url": "http://10.42.0.4:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"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"
|
"url": "http://10.42.0.7:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
@ -158,7 +161,10 @@
|
||||||
"url": "http://10.42.0.7:80"
|
"url": "http://10.42.0.7:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
@ -180,6 +186,9 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true,
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
},
|
||||||
"serversTransport": "default-mytransport@kubernetescrd"
|
"serversTransport": "default-mytransport@kubernetescrd"
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
|
@ -201,7 +210,10 @@
|
||||||
"url": "http://10.42.0.7:80"
|
"url": "http://10.42.0.7:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"serverStatus": {
|
"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"
|
"url": "http://10.0.1.1:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
@ -201,7 +204,10 @@
|
||||||
"url": "http://10.0.1.2:8889"
|
"url": "http://10.0.1.2:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"status": "enabled"
|
||||||
},
|
},
|
||||||
|
@ -215,7 +221,10 @@
|
||||||
"url": "http://10.0.1.3:8889"
|
"url": "http://10.0.1.3:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"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"
|
"url": "http://10.42.0.7:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"serverStatus": {
|
"serverStatus": {
|
||||||
|
|
|
@ -88,7 +88,10 @@
|
||||||
"url": "http://10.42.0.7:80"
|
"url": "http://10.42.0.7:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
|
10
integration/testdata/rawdata-ingress.json
vendored
10
integration/testdata/rawdata-ingress.json
vendored
|
@ -121,7 +121,10 @@
|
||||||
"url": "XXXX"
|
"url": "XXXX"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
@ -143,7 +146,10 @@
|
||||||
"url": "http://10.42.0.8:80"
|
"url": "http://10.42.0.8:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
|
|
@ -88,7 +88,10 @@
|
||||||
"url": "http://10.42.0.5:80"
|
"url": "http://10.42.0.5:80"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"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"
|
"url": "http://10.0.1.1:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
@ -201,7 +204,10 @@
|
||||||
"url": "http://10.0.1.2:8889"
|
"url": "http://10.0.1.2:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"status": "enabled"
|
||||||
},
|
},
|
||||||
|
@ -215,7 +221,10 @@
|
||||||
"url": "http://10.0.1.3:8889"
|
"url": "http://10.0.1.3:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"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"
|
"url": "http://10.0.1.1:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"usedBy": [
|
"usedBy": [
|
||||||
|
@ -201,7 +204,10 @@
|
||||||
"url": "http://10.0.1.2:8889"
|
"url": "http://10.0.1.2:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"status": "enabled"
|
||||||
},
|
},
|
||||||
|
@ -215,7 +221,10 @@
|
||||||
"url": "http://10.0.1.3:8889"
|
"url": "http://10.0.1.3:8889"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"passHostHeader": true
|
"passHostHeader": true,
|
||||||
|
"responseForwarding": {
|
||||||
|
"flushInterval": "100ms"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"status": "enabled"
|
"status": "enabled"
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,14 +425,14 @@
|
||||||
mode = "foobar"
|
mode = "foobar"
|
||||||
path = "foobar"
|
path = "foobar"
|
||||||
port = 42
|
port = 42
|
||||||
interval = "foobar"
|
interval = "10s"
|
||||||
timeout = "foobar"
|
timeout = "10s"
|
||||||
hostname = "foobar"
|
hostname = "foobar"
|
||||||
[http.services.Service0.loadBalancer.healthCheck.headers]
|
[http.services.Service0.loadBalancer.healthCheck.headers]
|
||||||
name0 = "foobar"
|
name0 = "foobar"
|
||||||
name1 = "foobar"
|
name1 = "foobar"
|
||||||
[http.services.Service0.loadBalancer.responseForwarding]
|
[http.services.Service0.loadBalancer.responseForwarding]
|
||||||
flushInterval = "foobar"
|
flushInterval = "10s"
|
||||||
|
|
||||||
[tcp]
|
[tcp]
|
||||||
[tcp.routers]
|
[tcp.routers]
|
||||||
|
|
|
@ -9,6 +9,19 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/types"
|
"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
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// HTTPConfiguration contains all the HTTP configuration parameters.
|
// HTTPConfiguration contains all the HTTP configuration parameters.
|
||||||
|
@ -178,8 +191,11 @@ func (l *ServersLoadBalancer) Mergeable(loadBalancer *ServersLoadBalancer) bool
|
||||||
|
|
||||||
// SetDefaults Default values for a ServersLoadBalancer.
|
// SetDefaults Default values for a ServersLoadBalancer.
|
||||||
func (l *ServersLoadBalancer) SetDefaults() {
|
func (l *ServersLoadBalancer) SetDefaults() {
|
||||||
defaultPassHostHeader := true
|
defaultPassHostHeader := DefaultPassHostHeader
|
||||||
l.PassHostHeader = &defaultPassHostHeader
|
l.PassHostHeader = &defaultPassHostHeader
|
||||||
|
|
||||||
|
l.ResponseForwarding = &ResponseForwarding{}
|
||||||
|
l.ResponseForwarding.SetDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
@ -191,7 +207,12 @@ type ResponseForwarding struct {
|
||||||
// This configuration is ignored when ReverseProxy recognizes a response as a streaming response;
|
// This configuration is ignored when ReverseProxy recognizes a response as a streaming response;
|
||||||
// for such responses, writes are flushed to the client immediately.
|
// for such responses, writes are flushed to the client immediately.
|
||||||
// Default: 100ms
|
// 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
|
// +k8s:deepcopy-gen=true
|
||||||
|
@ -212,15 +233,13 @@ func (s *Server) SetDefaults() {
|
||||||
|
|
||||||
// ServerHealthCheck holds the HealthCheck configuration.
|
// ServerHealthCheck holds the HealthCheck configuration.
|
||||||
type ServerHealthCheck struct {
|
type ServerHealthCheck struct {
|
||||||
Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,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"`
|
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"`
|
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"`
|
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"`
|
Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty" export:"true"`
|
||||||
// TODO change string to ptypes.Duration
|
Interval ptypes.Duration `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"`
|
||||||
Interval string `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"`
|
||||||
// TODO change string to ptypes.Duration
|
|
||||||
Timeout string `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"`
|
|
||||||
Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"`
|
||||||
FollowRedirects *bool `json:"followRedirects" toml:"followRedirects" yaml:"followRedirects" export:"true"`
|
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"`
|
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
|
fr := true
|
||||||
h.FollowRedirects = &fr
|
h.FollowRedirects = &fr
|
||||||
h.Mode = "http"
|
h.Mode = "http"
|
||||||
|
h.Interval = DefaultHealthCheckInterval
|
||||||
|
h.Timeout = DefaultHealthCheckTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
|
@ -161,6 +161,7 @@ func (c *CircuitBreaker) SetDefaults() {
|
||||||
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/
|
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/
|
||||||
type Compress struct {
|
type Compress struct {
|
||||||
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
// 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"`
|
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.
|
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
|
||||||
// Default: 1024.
|
// 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.name0": "foobar",
|
||||||
"traefik.http.services.Service0.loadbalancer.healthcheck.headers.name1": "foobar",
|
"traefik.http.services.Service0.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||||
"traefik.http.services.Service0.loadbalancer.healthcheck.hostname": "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.path": "foobar",
|
||||||
"traefik.http.services.Service0.loadbalancer.healthcheck.method": "foobar",
|
"traefik.http.services.Service0.loadbalancer.healthcheck.method": "foobar",
|
||||||
"traefik.http.services.Service0.loadbalancer.healthcheck.port": "42",
|
"traefik.http.services.Service0.loadbalancer.healthcheck.port": "42",
|
||||||
"traefik.http.services.Service0.loadbalancer.healthcheck.scheme": "foobar",
|
"traefik.http.services.Service0.loadbalancer.healthcheck.scheme": "foobar",
|
||||||
"traefik.http.services.Service0.loadbalancer.healthcheck.mode": "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.healthcheck.followredirects": "true",
|
||||||
"traefik.http.services.Service0.loadbalancer.passhostheader": "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.scheme": "foobar",
|
||||||
"traefik.http.services.Service0.loadbalancer.server.port": "8080",
|
"traefik.http.services.Service0.loadbalancer.server.port": "8080",
|
||||||
"traefik.http.services.Service0.loadbalancer.sticky.cookie.name": "foobar",
|
"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.name0": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.headers.name1": "foobar",
|
"traefik.http.services.Service1.loadbalancer.healthcheck.headers.name1": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.hostname": "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.path": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.method": "foobar",
|
"traefik.http.services.Service1.loadbalancer.healthcheck.method": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.port": "42",
|
"traefik.http.services.Service1.loadbalancer.healthcheck.port": "42",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.scheme": "foobar",
|
"traefik.http.services.Service1.loadbalancer.healthcheck.scheme": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.healthcheck.mode": "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.healthcheck.followredirects": "true",
|
||||||
"traefik.http.services.Service1.loadbalancer.passhostheader": "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.scheme": "foobar",
|
||||||
"traefik.http.services.Service1.loadbalancer.server.port": "8080",
|
"traefik.http.services.Service1.loadbalancer.server.port": "8080",
|
||||||
"traefik.http.services.Service1.loadbalancer.sticky": "false",
|
"traefik.http.services.Service1.loadbalancer.sticky": "false",
|
||||||
|
@ -656,8 +656,8 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
Path: "foobar",
|
Path: "foobar",
|
||||||
Method: "foobar",
|
Method: "foobar",
|
||||||
Port: 42,
|
Port: 42,
|
||||||
Interval: "foobar",
|
Interval: ptypes.Duration(time.Second),
|
||||||
Timeout: "foobar",
|
Timeout: ptypes.Duration(time.Second),
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
"name0": "foobar",
|
"name0": "foobar",
|
||||||
|
@ -667,7 +667,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
FlushInterval: "foobar",
|
FlushInterval: ptypes.Duration(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -685,8 +685,8 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
Path: "foobar",
|
Path: "foobar",
|
||||||
Method: "foobar",
|
Method: "foobar",
|
||||||
Port: 42,
|
Port: 42,
|
||||||
Interval: "foobar",
|
Interval: ptypes.Duration(time.Second),
|
||||||
Timeout: "foobar",
|
Timeout: ptypes.Duration(time.Second),
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
"name0": "foobar",
|
"name0": "foobar",
|
||||||
|
@ -696,7 +696,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
FlushInterval: "foobar",
|
FlushInterval: ptypes.Duration(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1148,8 +1148,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
Path: "foobar",
|
Path: "foobar",
|
||||||
Method: "foobar",
|
Method: "foobar",
|
||||||
Port: 42,
|
Port: 42,
|
||||||
Interval: "foobar",
|
Interval: ptypes.Duration(time.Second),
|
||||||
Timeout: "foobar",
|
Timeout: ptypes.Duration(time.Second),
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
"name0": "foobar",
|
"name0": "foobar",
|
||||||
|
@ -1158,7 +1158,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
FlushInterval: "foobar",
|
FlushInterval: ptypes.Duration(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1175,8 +1175,8 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
Path: "foobar",
|
Path: "foobar",
|
||||||
Method: "foobar",
|
Method: "foobar",
|
||||||
Port: 42,
|
Port: 42,
|
||||||
Interval: "foobar",
|
Interval: ptypes.Duration(time.Second),
|
||||||
Timeout: "foobar",
|
Timeout: ptypes.Duration(time.Second),
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
"name0": "foobar",
|
"name0": "foobar",
|
||||||
|
@ -1185,7 +1185,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
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.Headers.name1": "foobar",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Hostname": "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.Path": "foobar",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Method": "foobar",
|
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Method": "foobar",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Port": "42",
|
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Port": "42",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Scheme": "foobar",
|
"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.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.Port": "8080",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.server.Scheme": "foobar",
|
"traefik.HTTP.Services.Service0.LoadBalancer.server.Scheme": "foobar",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.Sticky.Cookie.Name": "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.name0": "foobar",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar",
|
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Hostname": "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.Path": "foobar",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Method": "foobar",
|
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Method": "foobar",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Port": "42",
|
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Port": "42",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Scheme": "foobar",
|
"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.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.Port": "8080",
|
||||||
"traefik.HTTP.Services.Service1.LoadBalancer.server.Scheme": "foobar",
|
"traefik.HTTP.Services.Service1.LoadBalancer.server.Scheme": "foobar",
|
||||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
||||||
|
|
|
@ -15,6 +15,12 @@ const (
|
||||||
StatusWarning = "warning"
|
StatusWarning = "warning"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Status of the servers.
|
||||||
|
const (
|
||||||
|
StatusUp = "UP"
|
||||||
|
StatusDown = "DOWN"
|
||||||
|
)
|
||||||
|
|
||||||
// Configuration holds the information about the currently running traefik instance.
|
// Configuration holds the information about the currently running traefik instance.
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
Routers map[string]*RouterInfo `json:"routers,omitempty"`
|
Routers map[string]*RouterInfo `json:"routers,omitempty"`
|
||||||
|
|
|
@ -2,9 +2,11 @@ package runtime_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||||
)
|
)
|
||||||
|
@ -49,7 +51,7 @@ func TestPopulateUsedBy(t *testing.T) {
|
||||||
{URL: "http://127.0.0.1:8086"},
|
{URL: "http://127.0.0.1:8086"},
|
||||||
},
|
},
|
||||||
HealthCheck: &dynamic.ServerHealthCheck{
|
HealthCheck: &dynamic.ServerHealthCheck{
|
||||||
Interval: "500ms",
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||||
Path: "/health",
|
Path: "/health",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -159,7 +161,7 @@ func TestPopulateUsedBy(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HealthCheck: &dynamic.ServerHealthCheck{
|
HealthCheck: &dynamic.ServerHealthCheck{
|
||||||
Interval: "500ms",
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||||
Path: "/health",
|
Path: "/health",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -177,7 +179,7 @@ func TestPopulateUsedBy(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HealthCheck: &dynamic.ServerHealthCheck{
|
HealthCheck: &dynamic.ServerHealthCheck{
|
||||||
Interval: "500ms",
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||||
Path: "/health",
|
Path: "/health",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,17 +8,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gokitmetrics "github.com/go-kit/kit/metrics"
|
gokitmetrics "github.com/go-kit/kit/metrics"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"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"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
@ -26,267 +21,153 @@ import (
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const modeGRPC = "grpc"
|
||||||
serverUp = "UP"
|
|
||||||
serverDown = "DOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
// StatusSetter should be implemented by a service that, when the status of a
|
||||||
HTTPMode = "http"
|
// registered target change, needs to be notified of that change.
|
||||||
GRPCMode = "grpc"
|
type StatusSetter interface {
|
||||||
)
|
SetStatus(ctx context.Context, childName string, up bool)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalancerHandler includes functionality for load-balancing management.
|
// StatusUpdater should be implemented by a service that, when its status
|
||||||
type BalancerHandler interface {
|
// changes (e.g. all if its children are down), needs to propagate upwards (to
|
||||||
ServeHTTP(w http.ResponseWriter, req *http.Request)
|
// their parent(s)) that change.
|
||||||
Balancer
|
type StatusUpdater interface {
|
||||||
|
RegisterStatusUpdater(fn func(up bool)) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// BalancerStatusHandler is an http Handler that does load-balancing,
|
type metricsHealthCheck interface {
|
||||||
// and updates its parents of its status.
|
ServiceServerUpGauge() gokitmetrics.Gauge
|
||||||
type BalancerStatusHandler interface {
|
|
||||||
BalancerHandler
|
|
||||||
StatusUpdater
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type metricsHealthcheck struct {
|
type ServiceHealthChecker struct {
|
||||||
serverUpGauge gokitmetrics.Gauge
|
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.
|
func NewServiceHealthChecker(ctx context.Context, metrics metricsHealthCheck, config *dynamic.ServerHealthCheck, service StatusSetter, info *runtime.ServiceInfo, transport http.RoundTripper, targets map[string]*url.URL) *ServiceHealthChecker {
|
||||||
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) {
|
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
|
|
||||||
logger.Debugf("Initial health check for backend: %q", backend.name)
|
interval := time.Duration(config.Interval)
|
||||||
hc.checkServersLB(ctx, backend)
|
if interval <= 0 {
|
||||||
|
logger.Error("Health check interval smaller than zero")
|
||||||
ticker := time.NewTicker(backend.Interval)
|
interval = time.Duration(dynamic.DefaultHealthCheckInterval)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backend.disabledURLs = newDisabledURLs
|
timeout := time.Duration(config.Timeout)
|
||||||
|
if timeout <= 0 {
|
||||||
for _, enabledURL := range enabledURLs {
|
logger.Error("Health check timeout smaller than zero")
|
||||||
serverUpMetricValue := float64(1)
|
timeout = time.Duration(dynamic.DefaultHealthCheckTimeout)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req = backend.setRequestOptions(req)
|
if timeout >= interval {
|
||||||
|
logger.Warnf("Health check timeout should be lower than the health check interval. Interval set to timeout + 1 second (%s).", interval)
|
||||||
client := http.Client{
|
interval = timeout + time.Second
|
||||||
Timeout: backend.Options.Timeout,
|
|
||||||
Transport: backend.Options.Transport,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !backend.FollowRedirects {
|
client := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.FollowRedirects != nil && !*config.FollowRedirects {
|
||||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("HTTP request failed: %w", err)
|
return fmt.Errorf("HTTP request failed: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -300,34 +181,61 @@ func checkHealthHTTP(serverURL *url.URL, backend *BackendConfig) error {
|
||||||
return nil
|
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.
|
// checkHealthGRPC returns an error with a meaningful description if the health check failed.
|
||||||
// Dedicated to gRPC servers implementing gRPC Health Checking Protocol v1.
|
// Dedicated to gRPC servers implementing gRPC Health Checking Protocol v1.
|
||||||
func checkHealthGRPC(serverURL *url.URL, backend *BackendConfig) error {
|
func (shc *ServiceHealthChecker) checkHealthGRPC(ctx context.Context, serverURL *url.URL) error {
|
||||||
u, err := serverURL.Parse(backend.Path)
|
u, err := serverURL.Parse(shc.config.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse server URL: %w", err)
|
return fmt.Errorf("failed to parse server URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
port := u.Port()
|
port := u.Port()
|
||||||
if backend.Options.Port != 0 {
|
if shc.config.Port != 0 {
|
||||||
port = strconv.Itoa(backend.Options.Port)
|
port = strconv.Itoa(shc.config.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverAddr := net.JoinHostPort(u.Hostname(), port)
|
serverAddr := net.JoinHostPort(u.Hostname(), port)
|
||||||
|
|
||||||
var opts []grpc.DialOption
|
var opts []grpc.DialOption
|
||||||
switch backend.Options.Scheme {
|
switch shc.config.Scheme {
|
||||||
case "http", "h2c", "":
|
case "http", "h2c", "":
|
||||||
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
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...)
|
conn, err := grpc.DialContext(ctx, serverAddr, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, context.DeadlineExceeded) {
|
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)
|
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)
|
return fmt.Errorf("gRPC server does not implement the health protocol: %w", err)
|
||||||
case codes.DeadlineExceeded:
|
case codes.DeadlineExceeded:
|
||||||
return fmt.Errorf("gRPC health check timeout: %w", err)
|
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
|
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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/config/runtime"
|
||||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||||
"github.com/vulcand/oxy/roundrobin"
|
|
||||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func TestServiceHealthChecker_newRequest(t *testing.T) {
|
||||||
healthCheckInterval = 200 * time.Millisecond
|
|
||||||
healthCheckTimeout = 100 * time.Millisecond
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetBackendsConfiguration(t *testing.T) {
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
startHealthy bool
|
targetURL string
|
||||||
mode string
|
config dynamic.ServerHealthCheck
|
||||||
server StartTestServer
|
expTarget string
|
||||||
expectedNumRemovedServers int
|
expError bool
|
||||||
expectedNumUpsertedServers int
|
expHostname string
|
||||||
expectedGaugeValue float64
|
expHeader string
|
||||||
|
expMethod string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "healthy server staying healthy",
|
desc: "no port override",
|
||||||
startHealthy: true,
|
targetURL: "http://backend1:80",
|
||||||
server: newHTTPServer(http.StatusOK),
|
config: dynamic.ServerHealthCheck{
|
||||||
expectedNumRemovedServers: 0,
|
Path: "/test",
|
||||||
expectedNumUpsertedServers: 0,
|
Port: 0,
|
||||||
expectedGaugeValue: 1,
|
},
|
||||||
|
expError: false,
|
||||||
|
expTarget: "http://backend1:80/test",
|
||||||
|
expHostname: "backend1:80",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy server staying healthy (StatusNoContent)",
|
desc: "port override",
|
||||||
startHealthy: true,
|
targetURL: "http://backend2:80",
|
||||||
server: newHTTPServer(http.StatusNoContent),
|
config: dynamic.ServerHealthCheck{
|
||||||
expectedNumRemovedServers: 0,
|
Path: "/test",
|
||||||
expectedNumUpsertedServers: 0,
|
Port: 8080,
|
||||||
expectedGaugeValue: 1,
|
},
|
||||||
|
expError: false,
|
||||||
|
expTarget: "http://backend2:8080/test",
|
||||||
|
expHostname: "backend2:8080",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy server staying healthy (StatusPermanentRedirect)",
|
desc: "no port override with no port in server URL",
|
||||||
startHealthy: true,
|
targetURL: "http://backend1",
|
||||||
server: newHTTPServer(http.StatusPermanentRedirect),
|
config: dynamic.ServerHealthCheck{
|
||||||
expectedNumRemovedServers: 0,
|
Path: "/health",
|
||||||
expectedNumUpsertedServers: 0,
|
Port: 0,
|
||||||
expectedGaugeValue: 1,
|
},
|
||||||
|
expError: false,
|
||||||
|
expTarget: "http://backend1/health",
|
||||||
|
expHostname: "backend1",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy server becoming sick",
|
desc: "port override with no port in server URL",
|
||||||
startHealthy: true,
|
targetURL: "http://backend2",
|
||||||
server: newHTTPServer(http.StatusServiceUnavailable),
|
config: dynamic.ServerHealthCheck{
|
||||||
expectedNumRemovedServers: 1,
|
Path: "/health",
|
||||||
expectedNumUpsertedServers: 0,
|
Port: 8080,
|
||||||
expectedGaugeValue: 0,
|
},
|
||||||
|
expError: false,
|
||||||
|
expTarget: "http://backend2:8080/health",
|
||||||
|
expHostname: "backend2:8080",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sick server becoming healthy",
|
desc: "scheme override",
|
||||||
startHealthy: false,
|
targetURL: "https://backend1:80",
|
||||||
server: newHTTPServer(http.StatusOK),
|
config: dynamic.ServerHealthCheck{
|
||||||
expectedNumRemovedServers: 0,
|
Scheme: "http",
|
||||||
expectedNumUpsertedServers: 1,
|
Path: "/test",
|
||||||
expectedGaugeValue: 1,
|
Port: 0,
|
||||||
|
},
|
||||||
|
expError: false,
|
||||||
|
expTarget: "http://backend1:80/test",
|
||||||
|
expHostname: "backend1:80",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sick server staying sick",
|
desc: "path with param",
|
||||||
startHealthy: false,
|
targetURL: "http://backend1:80",
|
||||||
server: newHTTPServer(http.StatusServiceUnavailable),
|
config: dynamic.ServerHealthCheck{
|
||||||
expectedNumRemovedServers: 0,
|
Path: "/health?powpow=do",
|
||||||
expectedNumUpsertedServers: 0,
|
Port: 0,
|
||||||
expectedGaugeValue: 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",
|
desc: "path with params",
|
||||||
startHealthy: true,
|
targetURL: "http://backend1:80",
|
||||||
server: newHTTPServer(http.StatusServiceUnavailable, http.StatusOK),
|
config: dynamic.ServerHealthCheck{
|
||||||
expectedNumRemovedServers: 1,
|
Path: "/health?powpow=do&do=powpow",
|
||||||
expectedNumUpsertedServers: 1,
|
Port: 0,
|
||||||
expectedGaugeValue: 1,
|
},
|
||||||
|
expError: false,
|
||||||
|
expTarget: "http://backend1:80/health?powpow=do&do=powpow",
|
||||||
|
expHostname: "backend1:80",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy grpc server staying healthy",
|
desc: "path with invalid path",
|
||||||
mode: "grpc",
|
targetURL: "http://backend1:80",
|
||||||
startHealthy: true,
|
config: dynamic.ServerHealthCheck{
|
||||||
server: newGRPCServer(healthpb.HealthCheckResponse_SERVING),
|
Path: ":",
|
||||||
expectedNumRemovedServers: 0,
|
Port: 0,
|
||||||
expectedNumUpsertedServers: 0,
|
},
|
||||||
expectedGaugeValue: 1,
|
expError: true,
|
||||||
|
expTarget: "",
|
||||||
|
expHostname: "backend1:80",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy grpc server becoming sick",
|
desc: "override hostname",
|
||||||
mode: "grpc",
|
targetURL: "http://backend1:80",
|
||||||
startHealthy: true,
|
config: dynamic.ServerHealthCheck{
|
||||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING),
|
Hostname: "myhost",
|
||||||
expectedNumRemovedServers: 1,
|
Path: "/",
|
||||||
expectedNumUpsertedServers: 0,
|
},
|
||||||
expectedGaugeValue: 0,
|
expTarget: "http://backend1:80/",
|
||||||
|
expHostname: "myhost",
|
||||||
|
expHeader: "",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sick grpc server becoming healthy",
|
desc: "not override hostname",
|
||||||
mode: "grpc",
|
targetURL: "http://backend1:80",
|
||||||
startHealthy: false,
|
config: dynamic.ServerHealthCheck{
|
||||||
server: newGRPCServer(healthpb.HealthCheckResponse_SERVING),
|
Hostname: "",
|
||||||
expectedNumRemovedServers: 0,
|
Path: "/",
|
||||||
expectedNumUpsertedServers: 1,
|
},
|
||||||
expectedGaugeValue: 1,
|
expTarget: "http://backend1:80/",
|
||||||
|
expHostname: "backend1:80",
|
||||||
|
expHeader: "",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sick grpc server staying sick",
|
desc: "custom header",
|
||||||
mode: "grpc",
|
targetURL: "http://backend1:80",
|
||||||
startHealthy: false,
|
config: dynamic.ServerHealthCheck{
|
||||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING),
|
Headers: map[string]string{"Custom-Header": "foo"},
|
||||||
expectedNumRemovedServers: 0,
|
Hostname: "",
|
||||||
expectedNumUpsertedServers: 0,
|
Path: "/",
|
||||||
expectedGaugeValue: 0,
|
},
|
||||||
|
expTarget: "http://backend1:80/",
|
||||||
|
expHostname: "backend1:80",
|
||||||
|
expHeader: "foo",
|
||||||
|
expMethod: http.MethodGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "healthy grpc server toggling to sick and back to healthy",
|
desc: "custom header with hostname override",
|
||||||
mode: "grpc",
|
targetURL: "http://backend1:80",
|
||||||
startHealthy: true,
|
config: dynamic.ServerHealthCheck{
|
||||||
server: newGRPCServer(healthpb.HealthCheckResponse_NOT_SERVING, healthpb.HealthCheckResponse_SERVING),
|
Headers: map[string]string{"Custom-Header": "foo"},
|
||||||
expectedNumRemovedServers: 1,
|
Hostname: "myhost",
|
||||||
expectedNumUpsertedServers: 1,
|
Path: "/",
|
||||||
expectedGaugeValue: 1,
|
},
|
||||||
|
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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
t.Cleanup(cancel)
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
serverURL, timeout := test.server.Start(t, cancel)
|
targetURL, timeout := test.server.Start(t, cancel)
|
||||||
|
|
||||||
lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}}
|
lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}}
|
||||||
|
|
||||||
options := Options{
|
config := &dynamic.ServerHealthCheck{
|
||||||
Mode: test.mode,
|
Mode: test.mode,
|
||||||
Path: "/path",
|
Path: "/path",
|
||||||
Interval: healthCheckInterval,
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||||
Timeout: healthCheckTimeout,
|
Timeout: ptypes.Duration(499 * time.Millisecond),
|
||||||
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})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
collectingMetrics := &testhelpers.CollectingGauge{}
|
gauge := &testhelpers.CollectingGauge{}
|
||||||
|
serviceInfo := &runtime.ServiceInfo{}
|
||||||
check := HealthCheck{
|
hc := NewServiceHealthChecker(ctx, &MetricsMock{gauge}, config, lb, serviceInfo, http.DefaultTransport, map[string]*url.URL{"test": targetURL})
|
||||||
Backends: make(map[string]*BackendConfig),
|
|
||||||
metrics: metricsHealthcheck{serverUpGauge: collectingMetrics},
|
|
||||||
}
|
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
check.execute(ctx, backend)
|
hc.Launch(ctx)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -189,392 +375,14 @@ func TestSetBackendsConfiguration(t *testing.T) {
|
||||||
lb.Lock()
|
lb.Lock()
|
||||||
defer lb.Unlock()
|
defer lb.Unlock()
|
||||||
|
|
||||||
assert.Equal(t, test.expectedNumRemovedServers, lb.numRemovedServers, "removed servers")
|
assert.Equal(t, test.expNumRemovedServers, lb.numRemovedServers, "removed servers")
|
||||||
assert.Equal(t, test.expectedNumUpsertedServers, lb.numUpsertedServers, "upserted servers")
|
assert.Equal(t, test.expNumUpsertedServers, lb.numUpsertedServers, "upserted servers")
|
||||||
assert.Equal(t, test.expectedGaugeValue, collectingMetrics.GaugeValue, "ServerUp Gauge")
|
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) {
|
func Bool(b bool) *bool {
|
||||||
type expected struct {
|
return &b
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
gokitmetrics "github.com/go-kit/kit/metrics"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||||
"github.com/vulcand/oxy/roundrobin"
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
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) {
|
func (s *GRPCServer) Check(_ context.Context, _ *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
|
||||||
stat := s.status.Pop()
|
|
||||||
if s.status.IsEmpty() {
|
if s.status.IsEmpty() {
|
||||||
s.done()
|
s.done()
|
||||||
|
return &healthpb.HealthCheckResponse{
|
||||||
|
Status: healthpb.HealthCheckResponse_SERVICE_UNKNOWN,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
stat := s.status.Pop()
|
||||||
|
|
||||||
return &healthpb.HealthCheckResponse{
|
return &healthpb.HealthCheckResponse{
|
||||||
Status: stat,
|
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 {
|
func (s *GRPCServer) Watch(_ *healthpb.HealthCheckRequest, server healthpb.Health_WatchServer) error {
|
||||||
stat := s.status.Pop()
|
|
||||||
if s.status.IsEmpty() {
|
if s.status.IsEmpty() {
|
||||||
s.done()
|
s.done()
|
||||||
|
return server.Send(&healthpb.HealthCheckResponse{
|
||||||
|
Status: healthpb.HealthCheckResponse_SERVICE_UNKNOWN,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
stat := s.status.Pop()
|
||||||
|
|
||||||
return server.Send(&healthpb.HealthCheckResponse{
|
return server.Send(&healthpb.HealthCheckResponse{
|
||||||
Status: stat,
|
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.
|
// 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 {
|
type HTTPServer struct {
|
||||||
|
@ -126,13 +133,14 @@ func newHTTPServer(healthSequence ...int) *HTTPServer {
|
||||||
// ServeHTTP returns HTTP response codes following a status sequences.
|
// ServeHTTP returns HTTP response codes following a status sequences.
|
||||||
// It calls the given 'done' function once all request health indicators have been depleted.
|
// It calls the given 'done' function once all request health indicators have been depleted.
|
||||||
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
|
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
if s.status.IsEmpty() {
|
||||||
|
s.done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
stat := s.status.Pop()
|
stat := s.status.Pop()
|
||||||
|
|
||||||
w.WriteHeader(stat)
|
w.WriteHeader(stat)
|
||||||
|
|
||||||
if s.status.IsEmpty() {
|
|
||||||
s.done()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) Start(t *testing.T, done func()) (*url.URL, time.Duration) {
|
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)
|
t.Cleanup(ts.Close)
|
||||||
|
|
||||||
// Make test timeout dependent on number of expected requests, health check interval, and a safety margin.
|
// 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 {
|
type testLoadBalancer struct {
|
||||||
|
@ -153,53 +161,20 @@ type testLoadBalancer struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
numRemovedServers int
|
numRemovedServers int
|
||||||
numUpsertedServers 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) {
|
func (lb *testLoadBalancer) SetStatus(ctx context.Context, childName string, up bool) {
|
||||||
// noop
|
if up {
|
||||||
}
|
lb.numUpsertedServers++
|
||||||
|
} else {
|
||||||
func (lb *testLoadBalancer) RemoveServer(u *url.URL) error {
|
lb.numRemovedServers++
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !found {
|
}
|
||||||
return
|
|
||||||
}
|
type MetricsMock struct {
|
||||||
|
Gauge gokitmetrics.Gauge
|
||||||
lb.servers = append(lb.servers[:i], lb.servers[i+1:]...)
|
}
|
||||||
|
|
||||||
|
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.
|
// AddOriginFields add origin fields.
|
||||||
func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
||||||
start := time.Now().UTC()
|
start := time.Now().UTC()
|
||||||
|
|
|
@ -62,13 +62,13 @@ func FromContext(ctx context.Context) (Capture, error) {
|
||||||
// Capture is the object populated by the capture middleware,
|
// Capture is the object populated by the capture middleware,
|
||||||
// holding probes that allow to gather information about the request and response.
|
// holding probes that allow to gather information about the request and response.
|
||||||
type Capture struct {
|
type Capture struct {
|
||||||
rr *readCounter
|
rr *readCounter
|
||||||
rw responseWriter
|
crw *captureResponseWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NeedsReset returns whether the given http.ResponseWriter is the capture's probe.
|
// NeedsReset returns whether the given http.ResponseWriter is the capture's probe.
|
||||||
func (c *Capture) NeedsReset(rw http.ResponseWriter) bool {
|
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
|
// 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
|
c.rr = readCounter
|
||||||
newReq.Body = 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 {
|
func (c *Capture) ResponseSize() int64 {
|
||||||
return c.rw.Size()
|
return c.crw.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Capture) StatusCode() int {
|
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,
|
// 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()
|
return r.source.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ middlewares.Stateful = &responseWriterWithCloseNotify{}
|
var _ middlewares.Stateful = &captureResponseWriter{}
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
|
|
||||||
// captureResponseWriter is a wrapper of type http.ResponseWriter
|
// captureResponseWriter is a wrapper of type http.ResponseWriter
|
||||||
// that tracks response status and size.
|
// 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)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, int64(3), rr.size)
|
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
|
package compress
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/opentracing/opentracing-go/ext"
|
"github.com/opentracing/opentracing-go/ext"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/middlewares/compress/brotli"
|
||||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const typeName = "Compress"
|
||||||
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.
|
// Compress is a middleware that allows to compress the response.
|
||||||
type compress struct {
|
type compress struct {
|
||||||
|
@ -24,6 +28,9 @@ type compress struct {
|
||||||
name string
|
name string
|
||||||
excludes []string
|
excludes []string
|
||||||
minSize int
|
minSize int
|
||||||
|
|
||||||
|
brotliHandler http.Handler
|
||||||
|
gzipHandler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new compress middleware.
|
// 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)
|
excludes = append(excludes, mediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
minSize := gzhttp.DefaultMinSize
|
minSize := DefaultMinSize
|
||||||
if conf.MinResponseBodyBytes > 0 {
|
if conf.MinResponseBodyBytes > 0 {
|
||||||
minSize = conf.MinResponseBodyBytes
|
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) {
|
func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
|
logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), c.name, typeName))
|
||||||
if err != nil {
|
|
||||||
log.FromContext(middlewares.GetLoggerCtx(context.Background(), c.name, typeName)).Debug(err)
|
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) {
|
if contains(c.excludes, mediaType) {
|
||||||
c.next.ServeHTTP(rw, req)
|
c.next.ServeHTTP(rw, req)
|
||||||
} else {
|
return
|
||||||
ctx := middlewares.GetLoggerCtx(req.Context(), c.name, typeName)
|
|
||||||
c.gzipHandler(ctx).ServeHTTP(rw, req)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
func (c *compress) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
return c.name, tracing.SpanKindNoneEnum
|
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(
|
wrapper, err := gzhttp.NewWrapper(
|
||||||
gzhttp.ExceptContentTypes(c.excludes),
|
gzhttp.ExceptContentTypes(c.excludes),
|
||||||
gzhttp.CompressionLevel(gzip.DefaultCompression),
|
gzhttp.MinSize(c.minSize),
|
||||||
gzhttp.MinSize(c.minSize))
|
)
|
||||||
if err != nil {
|
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 {
|
func contains(values []string, val string) bool {
|
||||||
|
@ -84,5 +166,6 @@ func contains(values []string, val string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package compress
|
package compress
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/andybalholm/brotli"
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -20,8 +22,81 @@ const (
|
||||||
contentTypeHeader = "Content-Type"
|
contentTypeHeader = "Content-Type"
|
||||||
varyHeader = "Vary"
|
varyHeader = "Vary"
|
||||||
gzipValue = "gzip"
|
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) {
|
func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||||
req.Header.Add(acceptEncodingHeader, gzipValue)
|
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, gzipValue, rw.Header().Get(contentEncodingHeader))
|
||||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||||
|
|
||||||
if assert.ObjectsAreEqualValues(rw.Body.Bytes(), baseBody) {
|
gr, err := gzip.NewReader(rw.Body)
|
||||||
assert.Fail(t, "expected a compressed body", "got %v", rw.Body.Bytes())
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
|
got, err := io.ReadAll(gr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, got, baseBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
||||||
|
@ -71,7 +149,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
||||||
assert.EqualValues(t, rw.Body.Bytes(), fakeCompressedBody)
|
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)
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||||
|
|
||||||
fakeBody := generateBytes(gzhttp.DefaultMinSize)
|
fakeBody := generateBytes(gzhttp.DefaultMinSize)
|
||||||
|
@ -87,7 +165,33 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
handler.ServeHTTP(rw, req)
|
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(contentEncodingHeader))
|
||||||
|
assert.Empty(t, rw.Header().Get(varyHeader))
|
||||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,8 @@ import (
|
||||||
|
|
||||||
// Compile time validation that the response recorder implements http interfaces correctly.
|
// Compile time validation that the response recorder implements http interfaces correctly.
|
||||||
var (
|
var (
|
||||||
// TODO: maybe remove at least for codeModifierWithCloseNotify.
|
_ middlewares.Stateful = &codeModifier{}
|
||||||
_ middlewares.Stateful = &codeModifierWithCloseNotify{}
|
_ middlewares.Stateful = &codeCatcher{}
|
||||||
_ middlewares.Stateful = &codeCatcherWithCloseNotify{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const typeName = "customError"
|
const typeName = "customError"
|
||||||
|
@ -124,13 +123,6 @@ func newRequest(baseURL string) (*http.Request, error) {
|
||||||
return req, nil
|
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
|
// 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
|
// 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
|
// simply drops the data from the response. Otherwise, it forwards it directly to
|
||||||
|
@ -144,27 +136,13 @@ type codeCatcher struct {
|
||||||
headersSent bool
|
headersSent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type codeCatcherWithCloseNotify struct {
|
func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) *codeCatcher {
|
||||||
*codeCatcher
|
return &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{
|
|
||||||
headerMap: make(http.Header),
|
headerMap: make(http.Header),
|
||||||
code: http.StatusOK, // If backend does not call WriteHeader on us, we consider it's a 200.
|
code: http.StatusOK, // If backend does not call WriteHeader on us, we consider it's a 200.
|
||||||
responseWriter: rw,
|
responseWriter: rw,
|
||||||
httpCodeRanges: httpCodeRanges,
|
httpCodeRanges: httpCodeRanges,
|
||||||
}
|
}
|
||||||
if _, ok := rw.(http.CloseNotifier); ok {
|
|
||||||
return &codeCatcherWithCloseNotify{catcher}
|
|
||||||
}
|
|
||||||
return catcher
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *codeCatcher) Header() http.Header {
|
func (cc *codeCatcher) Header() http.Header {
|
||||||
|
@ -240,24 +218,7 @@ func (cc *codeCatcher) Flush() {
|
||||||
|
|
||||||
// codeModifier forwards a response back to the client,
|
// codeModifier forwards a response back to the client,
|
||||||
// while enforcing a given response code.
|
// while enforcing a given response code.
|
||||||
type codeModifier interface {
|
type codeModifier struct {
|
||||||
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 {
|
|
||||||
code int // the code enforced in the response.
|
code int // the code enforced in the response.
|
||||||
|
|
||||||
// headerSent is whether the headers have already been sent,
|
// headerSent is whether the headers have already been sent,
|
||||||
|
@ -268,18 +229,17 @@ type codeModifierWithoutCloseNotify struct {
|
||||||
responseWriter http.ResponseWriter
|
responseWriter http.ResponseWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
type codeModifierWithCloseNotify struct {
|
// newCodeModifier returns a codeModifier that enforces the given code.
|
||||||
*codeModifierWithoutCloseNotify
|
func newCodeModifier(rw http.ResponseWriter, code int) *codeModifier {
|
||||||
}
|
return &codeModifier{
|
||||||
|
headerMap: make(http.Header),
|
||||||
// CloseNotify returns a channel that receives at most a
|
code: code,
|
||||||
// single value (true) when the client connection has gone away.
|
responseWriter: rw,
|
||||||
func (r *codeModifierWithCloseNotify) CloseNotify() <-chan bool {
|
}
|
||||||
return r.responseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns the response headers.
|
// Header returns the response headers.
|
||||||
func (r *codeModifierWithoutCloseNotify) Header() http.Header {
|
func (r *codeModifier) Header() http.Header {
|
||||||
if r.headerMap == nil {
|
if r.headerMap == nil {
|
||||||
r.headerMap = make(http.Header)
|
r.headerMap = make(http.Header)
|
||||||
}
|
}
|
||||||
|
@ -289,14 +249,14 @@ func (r *codeModifierWithoutCloseNotify) Header() http.Header {
|
||||||
|
|
||||||
// Write calls WriteHeader to send the enforced code,
|
// Write calls WriteHeader to send the enforced code,
|
||||||
// then writes the data directly to r.responseWriter.
|
// 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)
|
r.WriteHeader(r.code)
|
||||||
return r.responseWriter.Write(buf)
|
return r.responseWriter.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteHeader sends the headers, with the enforced code (the code in argument
|
// WriteHeader sends the headers, with the enforced code (the code in argument
|
||||||
// is always ignored), if it hasn't already been done.
|
// is always ignored), if it hasn't already been done.
|
||||||
func (r *codeModifierWithoutCloseNotify) WriteHeader(_ int) {
|
func (r *codeModifier) WriteHeader(_ int) {
|
||||||
if r.headerSent {
|
if r.headerSent {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -307,7 +267,7 @@ func (r *codeModifierWithoutCloseNotify) WriteHeader(_ int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hijack hijacks the connection.
|
// 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)
|
hijacker, ok := r.responseWriter.(http.Hijacker)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.responseWriter)
|
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.
|
// Flush sends any buffered data to the client.
|
||||||
func (r *codeModifierWithoutCloseNotify) Flush() {
|
func (r *codeModifier) Flush() {
|
||||||
r.WriteHeader(r.code)
|
r.WriteHeader(r.code)
|
||||||
|
|
||||||
if flusher, ok := r.responseWriter.(http.Flusher); ok {
|
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) {
|
func (m *mockServiceBuilder) BuildHTTP(_ context.Context, _ string) (http.Handler, error) {
|
||||||
return m.handler, nil
|
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.
|
// modifier can be nil.
|
||||||
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) http.ResponseWriter {
|
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) http.ResponseWriter {
|
||||||
rm := &responseModifier{
|
return &responseModifier{
|
||||||
req: r,
|
req: r,
|
||||||
rw: w,
|
rw: w,
|
||||||
modifier: modifier,
|
modifier: modifier,
|
||||||
code: http.StatusOK,
|
code: http.StatusOK,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := w.(http.CloseNotifier); ok {
|
|
||||||
return responseModifierWithCloseNotify{responseModifier: rm}
|
|
||||||
}
|
|
||||||
return rm
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseModifier) WriteHeader(code int) {
|
func (r *responseModifier) WriteHeader(code int) {
|
||||||
|
@ -97,12 +92,3 @@ func (r *responseModifier) Flush() {
|
||||||
flusher.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) {
|
func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
proto := getRequestProtocol(req)
|
proto := getRequestProtocol(req)
|
||||||
|
|
||||||
|
|
|
@ -60,47 +60,6 @@ func (m *collectingRetryMetrics) ServiceRetriesCounter() metrics.Counter {
|
||||||
return m.retriesCounter
|
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) {
|
func Test_getMethod(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
method string
|
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.
|
// Compile time validation that the response writer implements http interfaces correctly.
|
||||||
var _ middlewares.Stateful = &responseWriterWithCloseNotify{}
|
var _ middlewares.Stateful = &responseWriter{}
|
||||||
|
|
||||||
const (
|
const typeName = "Retry"
|
||||||
typeName = "Retry"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Listener is used to inform about retry attempts.
|
// Listener is used to inform about retry attempts.
|
||||||
type Listener interface {
|
type Listener interface {
|
||||||
|
@ -149,57 +147,44 @@ func (l Listeners) Retried(req *http.Request, attempt int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type responseWriter interface {
|
func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) *responseWriter {
|
||||||
http.ResponseWriter
|
return &responseWriter{
|
||||||
http.Flusher
|
|
||||||
ShouldRetry() bool
|
|
||||||
DisableRetries()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) responseWriter {
|
|
||||||
responseWriter := &responseWriterWithoutCloseNotify{
|
|
||||||
responseWriter: rw,
|
responseWriter: rw,
|
||||||
headers: make(http.Header),
|
headers: make(http.Header),
|
||||||
shouldRetry: shouldRetry,
|
shouldRetry: shouldRetry,
|
||||||
}
|
}
|
||||||
if _, ok := rw.(http.CloseNotifier); ok {
|
|
||||||
return &responseWriterWithCloseNotify{
|
|
||||||
responseWriterWithoutCloseNotify: responseWriter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return responseWriter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type responseWriterWithoutCloseNotify struct {
|
type responseWriter struct {
|
||||||
responseWriter http.ResponseWriter
|
responseWriter http.ResponseWriter
|
||||||
headers http.Header
|
headers http.Header
|
||||||
shouldRetry bool
|
shouldRetry bool
|
||||||
written bool
|
written bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterWithoutCloseNotify) ShouldRetry() bool {
|
func (r *responseWriter) ShouldRetry() bool {
|
||||||
return r.shouldRetry
|
return r.shouldRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterWithoutCloseNotify) DisableRetries() {
|
func (r *responseWriter) DisableRetries() {
|
||||||
r.shouldRetry = false
|
r.shouldRetry = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterWithoutCloseNotify) Header() http.Header {
|
func (r *responseWriter) Header() http.Header {
|
||||||
if r.written {
|
if r.written {
|
||||||
return r.responseWriter.Header()
|
return r.responseWriter.Header()
|
||||||
}
|
}
|
||||||
return r.headers
|
return r.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterWithoutCloseNotify) Write(buf []byte) (int, error) {
|
func (r *responseWriter) Write(buf []byte) (int, error) {
|
||||||
if r.ShouldRetry() {
|
if r.ShouldRetry() {
|
||||||
return len(buf), nil
|
return len(buf), nil
|
||||||
}
|
}
|
||||||
return r.responseWriter.Write(buf)
|
return r.responseWriter.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterWithoutCloseNotify) WriteHeader(code int) {
|
func (r *responseWriter) WriteHeader(code int) {
|
||||||
if r.ShouldRetry() && code == http.StatusServiceUnavailable {
|
if r.ShouldRetry() && code == http.StatusServiceUnavailable {
|
||||||
// We get a 503 HTTP Status Code when there is no backend server in the pool
|
// 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()
|
// 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
|
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)
|
hijacker, ok := r.responseWriter.(http.Hijacker)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.responseWriter)
|
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()
|
return hijacker.Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriterWithoutCloseNotify) Flush() {
|
func (r *responseWriter) Flush() {
|
||||||
if flusher, ok := r.responseWriter.(http.Flusher); ok {
|
if flusher, ok := r.responseWriter.(http.Flusher); ok {
|
||||||
flusher.Flush()
|
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.ResponseWriter
|
||||||
http.Hijacker
|
http.Hijacker
|
||||||
http.Flusher
|
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))
|
req = req.WithContext(tracing.WithTracing(req.Context(), e.Tracing))
|
||||||
|
|
||||||
recorder := newStatusCodeRecoder(rw, http.StatusOK)
|
recorder := newStatusCodeRecorder(rw, http.StatusOK)
|
||||||
e.next.ServeHTTP(recorder, req)
|
e.next.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
tracing.LogResponseCode(span, recorder.Status())
|
tracing.LogResponseCode(span, recorder.Status())
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (f *forwarderMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Reques
|
||||||
|
|
||||||
tracing.InjectRequestHeaders(req)
|
tracing.InjectRequestHeaders(req)
|
||||||
|
|
||||||
recorder := newStatusCodeRecoder(rw, 200)
|
recorder := newStatusCodeRecorder(rw, 200)
|
||||||
|
|
||||||
f.next.ServeHTTP(recorder, req)
|
f.next.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
|
|
@ -6,52 +6,35 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type statusCodeRecoder interface {
|
// newStatusCodeRecorder returns an initialized statusCodeRecoder.
|
||||||
http.ResponseWriter
|
func newStatusCodeRecorder(rw http.ResponseWriter, status int) *statusCodeRecorder {
|
||||||
Status() int
|
return &statusCodeRecorder{rw, status}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStatusCodeRecoder returns an initialized statusCodeRecoder.
|
type statusCodeRecorder struct {
|
||||||
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 {
|
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
status int
|
status int
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteHeader captures the status code for later retrieval.
|
// WriteHeader captures the status code for later retrieval.
|
||||||
func (s *statusCodeWithoutCloseNotify) WriteHeader(status int) {
|
func (s *statusCodeRecorder) WriteHeader(status int) {
|
||||||
s.status = status
|
s.status = status
|
||||||
s.ResponseWriter.WriteHeader(status)
|
s.ResponseWriter.WriteHeader(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status get response status.
|
// Status get response status.
|
||||||
func (s *statusCodeWithoutCloseNotify) Status() int {
|
func (s *statusCodeRecorder) Status() int {
|
||||||
return s.status
|
return s.status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hijack hijacks the connection.
|
// 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()
|
return s.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush sends any buffered data to the client.
|
// Flush sends any buffered data to the client.
|
||||||
func (s *statusCodeWithoutCloseNotify) Flush() {
|
func (s *statusCodeRecorder) Flush() {
|
||||||
if flusher, ok := s.ResponseWriter.(http.Flusher); ok {
|
if flusher, ok := s.ResponseWriter.(http.Flusher); ok {
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type statusCodeWithCloseNotify struct {
|
|
||||||
*statusCodeWithoutCloseNotify
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *statusCodeWithCloseNotify) CloseNotify() <-chan bool {
|
|
||||||
return s.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,10 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/tls"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
)
|
)
|
||||||
|
@ -63,6 +65,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -113,6 +118,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -156,6 +164,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -199,6 +210,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -247,6 +261,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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",
|
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",
|
ServersTransport: "tls-ns-dc1-dev-Test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -470,7 +493,10 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
URL: "https://127.0.0.2:444",
|
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",
|
ServersTransport: "tls-ns-dc1-dev-Test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -547,6 +573,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -557,6 +586,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -981,6 +1037,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -1743,6 +1835,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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",
|
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",
|
ServersTransport: "tls-ns-dc1-Test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2684,7 +2794,10 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
URL: "https://127.0.0.2:80",
|
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",
|
ServersTransport: "tls-ns-dc1-Test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,12 +4,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
docker "github.com/docker/docker/api/types"
|
docker "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,6 +70,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -123,6 +128,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -180,6 +188,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -230,6 +241,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -280,6 +294,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -335,6 +352,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -630,6 +656,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -995,6 +1042,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -1935,6 +2015,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -2110,6 +2202,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2290,6 +2385,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
"Test": {
|
"Test": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,10 +3,12 @@ package ecs
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,6 +66,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -114,6 +119,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -166,6 +174,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -211,6 +222,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -256,6 +270,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -306,6 +323,9 @@ func TestDefaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -570,6 +596,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -900,6 +947,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -1716,6 +1796,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -1942,6 +2037,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -1999,6 +2100,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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
|
passHostHeader := true
|
||||||
lb.PassHostHeader = &passHostHeader
|
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
|
lb.Sticky = svc.Sticky
|
||||||
|
|
||||||
|
|
|
@ -1283,7 +1283,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
|
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": {
|
"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",
|
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": {
|
"default-test-route-6b204d94623b3df4370c": {
|
||||||
|
@ -1510,6 +1516,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1578,6 +1587,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1629,6 +1641,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1686,6 +1701,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1734,6 +1752,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-test-route-77c62dfe9517144aeeaa": {
|
"default-test-route-77c62dfe9517144aeeaa": {
|
||||||
|
@ -1747,6 +1768,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1805,6 +1829,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami2-8080": {
|
"default-whoami2-8080": {
|
||||||
|
@ -1818,6 +1845,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1871,6 +1901,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1917,6 +1950,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1988,6 +2024,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami5-8080": {
|
"default-whoami5-8080": {
|
||||||
|
@ -2001,6 +2040,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-wrr2": {
|
"default-wrr2": {
|
||||||
|
@ -2028,6 +2070,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami7-8080": {
|
"default-whoami7-8080": {
|
||||||
|
@ -2041,6 +2086,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2108,6 +2156,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2187,6 +2238,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami5-8080": {
|
"default-whoami5-8080": {
|
||||||
|
@ -2200,6 +2254,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2270,6 +2327,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"foo-wrr1": {
|
"foo-wrr1": {
|
||||||
|
@ -2305,6 +2365,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"foo-mirror1": {
|
"foo-mirror1": {
|
||||||
|
@ -2328,6 +2391,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"bar-mirrored": {
|
"bar-mirrored": {
|
||||||
|
@ -2430,6 +2496,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami5-8080": {
|
"default-whoami5-8080": {
|
||||||
|
@ -2443,6 +2512,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2514,6 +2586,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami5-8080": {
|
"default-whoami5-8080": {
|
||||||
|
@ -2527,6 +2602,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2584,6 +2662,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami2-8080": {
|
"default-whoami2-8080": {
|
||||||
|
@ -2597,6 +2678,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2717,6 +2801,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2786,6 +2873,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2834,6 +2924,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2903,6 +2996,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2973,6 +3069,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3041,6 +3140,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3098,6 +3200,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3156,6 +3261,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3200,6 +3308,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3243,6 +3354,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3286,6 +3400,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3533,6 +3650,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3575,7 +3695,7 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(false),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3687,6 +3810,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3733,6 +3859,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3774,6 +3903,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3813,6 +3945,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3852,6 +3987,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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",
|
URL: "https://external.domain:443",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
ServersTransport: "default-test",
|
ServersTransport: "default-test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3963,7 +4104,10 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
URL: "https://10.10.0.6:8443",
|
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",
|
ServersTransport: "default-default-test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4036,6 +4180,9 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
"default-test-route-6b204d94623b3df4370c": {
|
"default-test-route-6b204d94623b3df4370c": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4923,6 +5070,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4998,6 +5148,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||||
|
@ -5011,6 +5164,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-test-errorpage-errorpage-service": {
|
"default-test-errorpage-errorpage-service": {
|
||||||
|
@ -5024,6 +5180,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-test-crossnamespace-route-a1963878aac7331b7950": {
|
"default-test-crossnamespace-route-a1963878aac7331b7950": {
|
||||||
|
@ -5037,6 +5196,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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",
|
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",
|
ServersTransport: "foo-test@kubernetescrd",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -5126,6 +5291,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-tr-svc-wrr1": {
|
"default-tr-svc-wrr1": {
|
||||||
|
@ -5181,6 +5349,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -5227,6 +5398,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"cross-ns-tr-svc-mirror2": {
|
"cross-ns-tr-svc-mirror2": {
|
||||||
|
@ -5251,6 +5425,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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",
|
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",
|
ServersTransport: "cross-ns-st-cross-ns@kubernetescrd",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -5387,6 +5567,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -5431,6 +5614,9 @@ func TestCrossNamespace(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -5936,6 +6122,9 @@ func TestExternalNameService(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -110,7 +110,7 @@ type LoadBalancerSpec struct {
|
||||||
// By default, passHostHeader is true.
|
// By default, passHostHeader is true.
|
||||||
PassHostHeader *bool `json:"passHostHeader,omitempty"`
|
PassHostHeader *bool `json:"passHostHeader,omitempty"`
|
||||||
// ResponseForwarding defines how Traefik forwards the response from the upstream Kubernetes Service to the client.
|
// 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.
|
// ServersTransport defines the name of ServersTransport resource to use.
|
||||||
// It allows to configure the transport between Traefik and your servers.
|
// It allows to configure the transport between Traefik and your servers.
|
||||||
// Can only be used on a Kubernetes Service.
|
// Can only be used on a Kubernetes Service.
|
||||||
|
@ -121,6 +121,15 @@ type LoadBalancerSpec struct {
|
||||||
Weight *int `json:"weight,omitempty"`
|
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.
|
// Service defines an upstream HTTP service to proxy traffic to.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
LoadBalancerSpec `json:",inline"`
|
LoadBalancerSpec `json:",inline"`
|
||||||
|
|
|
@ -559,7 +559,7 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
|
||||||
}
|
}
|
||||||
if in.ResponseForwarding != nil {
|
if in.ResponseForwarding != nil {
|
||||||
in, out := &in.ResponseForwarding, &out.ResponseForwarding
|
in, out := &in.ResponseForwarding, &out.ResponseForwarding
|
||||||
*out = new(dynamic.ResponseForwarding)
|
*out = new(ResponseForwarding)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
if in.Weight != nil {
|
if in.Weight != nil {
|
||||||
|
@ -973,6 +973,22 @@ func (in *RateLimit) DeepCopy() *RateLimit {
|
||||||
return out
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Retry) DeepCopyInto(out *Retry) {
|
func (in *Retry) DeepCopyInto(out *Retry) {
|
||||||
*out = *in
|
*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)
|
return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
svc := dynamic.Service{
|
lb := &dynamic.ServersLoadBalancer{}
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
lb.SetDefaults()
|
||||||
PassHostHeader: pointer.Bool(true),
|
|
||||||
},
|
svc := dynamic.Service{LoadBalancer: lb}
|
||||||
}
|
|
||||||
|
|
||||||
// TODO support cross namespace through ReferencePolicy
|
// TODO support cross namespace through ReferencePolicy
|
||||||
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
service, exists, err := client.GetService(namespace, string(backendRef.Name))
|
||||||
|
|
|
@ -3,9 +3,11 @@ package gateway
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider"
|
"github.com/traefik/traefik/v2/pkg/provider"
|
||||||
"github.com/traefik/traefik/v2/pkg/tls"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
|
@ -552,6 +554,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: pointer.Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: pointer.Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami2-8080": {
|
"default-whoami2-8080": {
|
||||||
|
@ -955,6 +978,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: pointer.Bool(true),
|
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),
|
PassHostHeader: pointer.Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"default-whoami2-8080": {
|
"default-whoami2-8080": {
|
||||||
|
@ -1027,6 +1056,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: pointer.Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: pointer.Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"bar-whoami-bar-80": {
|
"bar-whoami-bar-80": {
|
||||||
|
@ -1405,6 +1452,9 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: pointer.Bool(true),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: pointer.Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"bar-whoami-bar-80": {
|
"bar-whoami-bar-80": {
|
||||||
|
@ -3936,6 +3998,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: pointer.Bool(true),
|
PassHostHeader: pointer.Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": {
|
"bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": {
|
||||||
|
@ -4083,6 +4148,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: pointer.Bool(true),
|
PassHostHeader: pointer.Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": {
|
"bar-http-app-bar-my-gateway-web-a431b128267aabc954fd-wrr": {
|
||||||
|
@ -4231,6 +4299,9 @@ func TestLoadMixedRoutes(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: pointer.Bool(true),
|
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")
|
return nil, errors.New("service port not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
svc := &dynamic.Service{
|
lb := &dynamic.ServersLoadBalancer{}
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
lb.SetDefaults()
|
||||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
|
||||||
},
|
svc := &dynamic.Service{LoadBalancer: lb}
|
||||||
}
|
|
||||||
|
|
||||||
svcConfig, err := parseServiceConfig(service.Annotations)
|
svcConfig, err := parseServiceConfig(service.Annotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,8 +7,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider"
|
"github.com/traefik/traefik/v2/pkg/provider"
|
||||||
"github.com/traefik/traefik/v2/pkg/tls"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
|
@ -68,6 +70,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -115,6 +120,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Sticky: &dynamic.Sticky{
|
Sticky: &dynamic.Sticky{
|
||||||
Cookie: &dynamic.Cookie{
|
Cookie: &dynamic.Cookie{
|
||||||
Name: "foobar",
|
Name: "foobar",
|
||||||
|
@ -157,6 +165,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -191,6 +202,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -225,6 +239,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -259,6 +276,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -289,6 +309,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -319,6 +342,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-example-com-80": {
|
"testing-example-com-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.11.0.1:80",
|
URL: "http://10.11.0.1:80",
|
||||||
|
@ -350,6 +376,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -384,6 +413,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -418,6 +450,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -431,6 +466,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service2-8082": {
|
"testing-service2-8082": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.2:8080",
|
URL: "http://10.10.0.2:8080",
|
||||||
|
@ -462,6 +500,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -496,6 +537,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"default-backend": {
|
"default-backend": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -526,6 +570,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8089",
|
URL: "http://10.10.0.1:8089",
|
||||||
|
@ -556,6 +603,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-tchouk": {
|
"testing-service1-tchouk": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8089",
|
URL: "http://10.10.0.1:8089",
|
||||||
|
@ -586,6 +636,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-tchouk": {
|
"testing-service1-tchouk": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8089",
|
URL: "http://10.10.0.1:8089",
|
||||||
|
@ -620,6 +673,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-tchouk": {
|
"testing-service1-tchouk": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8089",
|
URL: "http://10.10.0.1:8089",
|
||||||
|
@ -633,6 +689,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-carotte": {
|
"testing-service1-carotte": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8090",
|
URL: "http://10.10.0.1:8090",
|
||||||
|
@ -663,6 +722,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-tchouk": {
|
"testing-service1-tchouk": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8089",
|
URL: "http://10.10.0.1:8089",
|
||||||
|
@ -697,6 +759,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-tchouk": {
|
"testing-service1-tchouk": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8089",
|
URL: "http://10.10.0.1:8089",
|
||||||
|
@ -710,6 +775,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"toto-service1-tchouk": {
|
"toto-service1-tchouk": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.11.0.1:8089",
|
URL: "http://10.11.0.1:8089",
|
||||||
|
@ -762,6 +830,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-8080": {
|
"testing-service1-8080": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.0.0.1:8080",
|
URL: "http://10.0.0.1:8080",
|
||||||
|
@ -790,6 +861,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-example-com-80": {
|
"testing-example-com-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.11.0.1:80",
|
URL: "http://10.11.0.1:80",
|
||||||
|
@ -827,6 +901,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-443": {
|
"testing-service1-443": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "https://10.10.0.1:8443",
|
URL: "https://10.10.0.1:8443",
|
||||||
|
@ -857,6 +934,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-8443": {
|
"testing-service1-8443": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "https://10.10.0.1:8443",
|
URL: "https://10.10.0.1:8443",
|
||||||
|
@ -888,6 +968,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-8443": {
|
"testing-service1-8443": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "https://10.10.0.1:8443",
|
URL: "https://10.10.0.1:8443",
|
||||||
|
@ -919,6 +1002,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"default-backend": {
|
"default-backend": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.30.0.1:8080",
|
URL: "http://10.30.0.1:8080",
|
||||||
|
@ -949,6 +1035,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1023,6 +1112,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1053,6 +1145,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1085,6 +1180,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1113,6 +1211,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1141,6 +1242,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1169,6 +1273,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1197,6 +1304,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1225,6 +1335,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1265,6 +1378,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1294,6 +1410,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1322,6 +1441,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1350,6 +1472,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1378,6 +1503,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1406,6 +1534,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1434,6 +1565,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1462,6 +1596,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1490,6 +1627,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1518,6 +1658,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-80": {
|
"testing-service1-80": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1546,6 +1689,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"testing-service1-foobar": {
|
"testing-service1-foobar": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:4711",
|
URL: "http://10.10.0.1:4711",
|
||||||
|
@ -1587,6 +1733,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
"default-backend": {
|
"default-backend": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://10.10.0.1:8080",
|
URL: "http://10.10.0.1:8080",
|
||||||
|
@ -1678,6 +1827,9 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
||||||
"testing-service1-8080": {
|
"testing-service1-8080": {
|
||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "http://traefik.wtf:8080",
|
URL: "http://traefik.wtf:8080",
|
||||||
|
@ -1710,6 +1862,9 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1738,6 +1893,9 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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/routers/Router1/service": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/path": "foobar",
|
"traefik/http/services/Service01/loadBalancer/healthCheck/path": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/port": "42",
|
"traefik/http/services/Service01/loadBalancer/healthCheck/port": "42",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/interval": "foobar",
|
"traefik/http/services/Service01/loadBalancer/healthCheck/interval": "1s",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/timeout": "foobar",
|
"traefik/http/services/Service01/loadBalancer/healthCheck/timeout": "1s",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/hostname": "foobar",
|
"traefik/http/services/Service01/loadBalancer/healthCheck/hostname": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0": "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/headers/name1": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/scheme": "foobar",
|
"traefik/http/services/Service01/loadBalancer/healthCheck/scheme": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/mode": "foobar",
|
"traefik/http/services/Service01/loadBalancer/healthCheck/mode": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/healthCheck/followredirects": "true",
|
"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/passHostHeader": "true",
|
||||||
"traefik/http/services/Service01/loadBalancer/sticky/cookie/name": "foobar",
|
"traefik/http/services/Service01/loadBalancer/sticky/cookie/name": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/sticky/cookie/secure": "true",
|
"traefik/http/services/Service01/loadBalancer/sticky/cookie/secure": "true",
|
||||||
|
@ -646,8 +646,8 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
Mode: "foobar",
|
Mode: "foobar",
|
||||||
Path: "foobar",
|
Path: "foobar",
|
||||||
Port: 42,
|
Port: 42,
|
||||||
Interval: "foobar",
|
Interval: ptypes.Duration(time.Second),
|
||||||
Timeout: "foobar",
|
Timeout: ptypes.Duration(time.Second),
|
||||||
Hostname: "foobar",
|
Hostname: "foobar",
|
||||||
FollowRedirects: func(v bool) *bool { return &v }(true),
|
FollowRedirects: func(v bool) *bool { return &v }(true),
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
|
@ -657,7 +657,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
PassHostHeader: func(v bool) *bool { return &v }(true),
|
PassHostHeader: func(v bool) *bool { return &v }(true),
|
||||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
FlushInterval: "foobar",
|
FlushInterval: ptypes.Duration(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,10 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,6 +73,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
@ -137,6 +142,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
@ -189,6 +197,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
@ -295,6 +306,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
@ -356,6 +370,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
@ -404,6 +421,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
"bar": {LoadBalancer: &dynamic.ServersLoadBalancer{
|
"bar": {LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
|
@ -412,6 +432,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
@ -456,6 +479,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -498,6 +524,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
@ -542,6 +571,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -579,6 +611,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -629,6 +664,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -669,6 +707,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -679,6 +720,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -780,6 +824,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"app2": {
|
"app2": {
|
||||||
|
@ -790,6 +837,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -843,6 +893,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"app2": {
|
"app2": {
|
||||||
|
@ -853,6 +906,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -897,6 +953,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"app2": {
|
"app2": {
|
||||||
|
@ -907,6 +966,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -958,6 +1020,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1002,6 +1067,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"app2": {
|
"app2": {
|
||||||
|
@ -1012,6 +1080,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1055,6 +1126,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1099,6 +1173,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1138,6 +1215,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -1148,6 +1228,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1355,6 +1438,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1399,6 +1485,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1442,6 +1531,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1766,6 +1858,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1826,6 +1921,9 @@ func TestBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,9 +3,11 @@ package nomad
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,6 +58,9 @@ func Test_defaultRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -333,6 +353,9 @@ func Test_buildConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -757,6 +804,9 @@ func Test_buildConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Service2": {
|
"Service2": {
|
||||||
|
@ -1372,6 +1449,9 @@ func Test_buildConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test-1234154071633021619": {
|
"Test-1234154071633021619": {
|
||||||
|
@ -2282,6 +2377,9 @@ func Test_buildConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,9 +3,11 @@ package rancher
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -58,6 +60,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -126,6 +134,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Test2": {
|
"Test2": {
|
||||||
|
@ -197,6 +211,9 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PassHostHeader: Bool(true),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
PassHostHeader: Bool(true),
|
||||||
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
|
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -82,8 +82,8 @@ func init() {
|
||||||
Scheme: "foo",
|
Scheme: "foo",
|
||||||
Path: "foo",
|
Path: "foo",
|
||||||
Port: 42,
|
Port: 42,
|
||||||
Interval: "foo",
|
Interval: ptypes.Duration(111 * time.Second),
|
||||||
Timeout: "foo",
|
Timeout: ptypes.Duration(111 * time.Second),
|
||||||
Hostname: "foo",
|
Hostname: "foo",
|
||||||
FollowRedirects: boolPtr(true),
|
FollowRedirects: boolPtr(true),
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
|
@ -92,7 +92,7 @@ func init() {
|
||||||
},
|
},
|
||||||
PassHostHeader: boolPtr(true),
|
PassHostHeader: boolPtr(true),
|
||||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||||
FlushInterval: "foo",
|
FlushInterval: ptypes.Duration(111 * time.Second),
|
||||||
},
|
},
|
||||||
ServersTransport: "foo",
|
ServersTransport: "foo",
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
|
|
|
@ -75,8 +75,8 @@
|
||||||
"scheme": "foo",
|
"scheme": "foo",
|
||||||
"path": "foo",
|
"path": "foo",
|
||||||
"port": 42,
|
"port": 42,
|
||||||
"interval": "foo",
|
"interval": "1m51s",
|
||||||
"timeout": "foo",
|
"timeout": "1m51s",
|
||||||
"hostname": "xxxx",
|
"hostname": "xxxx",
|
||||||
"followRedirects": true,
|
"followRedirects": true,
|
||||||
"headers": {
|
"headers": {
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
},
|
},
|
||||||
"passHostHeader": true,
|
"passHostHeader": true,
|
||||||
"responseForwarding": {
|
"responseForwarding": {
|
||||||
"flushInterval": "foo"
|
"flushInterval": "1m51s"
|
||||||
},
|
},
|
||||||
"serversTransport": "foo"
|
"serversTransport": "foo"
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,8 +75,8 @@
|
||||||
"scheme": "foo",
|
"scheme": "foo",
|
||||||
"path": "foo",
|
"path": "foo",
|
||||||
"port": 42,
|
"port": 42,
|
||||||
"interval": "foo",
|
"interval": "1m51s",
|
||||||
"timeout": "foo",
|
"timeout": "1m51s",
|
||||||
"hostname": "foo",
|
"hostname": "foo",
|
||||||
"followRedirects": true,
|
"followRedirects": true,
|
||||||
"headers": {
|
"headers": {
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
},
|
},
|
||||||
"passHostHeader": true,
|
"passHostHeader": true,
|
||||||
"responseForwarding": {
|
"responseForwarding": {
|
||||||
"flushInterval": "foo"
|
"flushInterval": "1m51s"
|
||||||
},
|
},
|
||||||
"serversTransport": "foo"
|
"serversTransport": "foo"
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ type middlewareBuilder interface {
|
||||||
|
|
||||||
type serviceManager interface {
|
type serviceManager interface {
|
||||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||||
LaunchHealthCheck()
|
LaunchHealthCheck(ctx context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager A route/router manager.
|
// Manager A route/router manager.
|
||||||
|
|
|
@ -7,10 +7,12 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containous/alice"
|
"github.com/containous/alice"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||||
"github.com/traefik/traefik/v2/pkg/metrics"
|
"github.com/traefik/traefik/v2/pkg/metrics"
|
||||||
|
@ -478,7 +480,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HealthCheck: &dynamic.ServerHealthCheck{
|
HealthCheck: &dynamic.ServerHealthCheck{
|
||||||
Interval: "500ms",
|
Interval: ptypes.Duration(500 * time.Millisecond),
|
||||||
Path: "/health",
|
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
|
// TODO -- Check if ProxyProtocol changes the first bytes of the request
|
||||||
br := bufio.NewReader(conn)
|
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)
|
hello, err := clientHelloInfo(br)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
@ -277,7 +288,7 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
// Peeked are the bytes that have been read from Conn for the
|
// Peeked are the bytes that have been read from Conn for the
|
||||||
// purposes of route matching, but have not yet been consumed
|
// 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
|
Peeked []byte
|
||||||
|
|
||||||
// Conn is the underlying connection.
|
// 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 {
|
func checkHTTPSTLS12(addr string, timeout time.Duration) error {
|
||||||
return checkHTTPS(addr, timeout, tls.VersionTLS12)
|
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
|
chainBuilder *middleware.ChainBuilder
|
||||||
tlsManager *tls.Manager
|
tlsManager *tls.Manager
|
||||||
|
|
||||||
|
cancelPrevState func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouterFactory creates a new RouterFactory.
|
// NewRouterFactory creates a new RouterFactory.
|
||||||
|
@ -65,7 +67,12 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
||||||
|
|
||||||
// CreateRouters creates new TCPRouters and UDPRouters.
|
// CreateRouters creates new TCPRouters and UDPRouters.
|
||||||
func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string]*tcprouter.Router, map[string]udptypes.Handler) {
|
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
|
// HTTP
|
||||||
serviceManager := f.managerFactory.Build(rtConf)
|
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)
|
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
||||||
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
||||||
|
|
||||||
serviceManager.LaunchHealthCheck()
|
serviceManager.LaunchHealthCheck(ctx)
|
||||||
|
|
||||||
// TCP
|
// TCP
|
||||||
svcTCPManager := tcp.NewManager(rtConf)
|
svcTCPManager := tcp.NewManager(rtConf)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
type serviceManager interface {
|
type serviceManager interface {
|
||||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||||
LaunchHealthCheck()
|
LaunchHealthCheck(ctx context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalHandlers is the internal HTTP handlers builder.
|
// InternalHandlers is the internal HTTP handlers builder.
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"container/heap"
|
"container/heap"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@ type Balancer struct {
|
||||||
curDeadline float64
|
curDeadline float64
|
||||||
// status is a record of which child services of the Balancer are healthy, keyed
|
// 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
|
// 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.
|
// through the SetStatus method.
|
||||||
status map[string]struct{}
|
status map[string]struct{}
|
||||||
// updaters is the list of hooks that are run (to update the Balancer
|
// 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.
|
// 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{
|
balancer := &Balancer{
|
||||||
status: make(map[string]struct{}),
|
status: make(map[string]struct{}),
|
||||||
wantsHealthCheck: hc != nil,
|
wantsHealthCheck: wantHealthCheck,
|
||||||
}
|
}
|
||||||
if sticky != nil && sticky.Cookie != nil {
|
if sticky != nil && sticky.Cookie != nil {
|
||||||
balancer.stickyCookie = &stickyCookie{
|
balancer.stickyCookie = &stickyCookie{
|
||||||
|
@ -150,10 +149,7 @@ func (b *Balancer) nextServer() (*namedHandler, error) {
|
||||||
b.mutex.Lock()
|
b.mutex.Lock()
|
||||||
defer b.mutex.Unlock()
|
defer b.mutex.Unlock()
|
||||||
|
|
||||||
if len(b.handlers) == 0 {
|
if len(b.handlers) == 0 || len(b.status) == 0 {
|
||||||
return nil, fmt.Errorf("no servers in the pool")
|
|
||||||
}
|
|
||||||
if len(b.status) == 0 {
|
|
||||||
return nil, errNoAvailableServer
|
return nil, errNoAvailableServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,9 +219,9 @@ func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
server.ServeHTTP(w, req)
|
server.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddService adds a handler.
|
// Add adds a handler.
|
||||||
// A handler with a non-positive weight is ignored.
|
// 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
|
w := 1
|
||||||
if weight != nil {
|
if weight != nil {
|
||||||
w = *weight
|
w = *weight
|
||||||
|
|
|
@ -10,31 +10,15 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"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) {
|
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.Header().Set("server", "first")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(3))
|
}), 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.Header().Set("server", "second")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), Int(1))
|
||||||
|
@ -49,23 +33,23 @@ func TestBalancer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBalancerNoService(t *testing.T) {
|
func TestBalancerNoService(t *testing.T) {
|
||||||
balancer := New(nil, nil)
|
balancer := New(nil, false)
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
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) {
|
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.Header().Set("server", "first")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), 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{}}
|
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}}
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
|
@ -80,13 +64,13 @@ type key string
|
||||||
const serviceName key = "serviceName"
|
const serviceName key = "serviceName"
|
||||||
|
|
||||||
func TestBalancerNoServiceUp(t *testing.T) {
|
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)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
}), Int(1))
|
}), 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)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
}), Int(1))
|
}), Int(1))
|
||||||
|
|
||||||
|
@ -100,14 +84,14 @@ func TestBalancerNoServiceUp(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBalancerOneServerDown(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.Header().Set("server", "first")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), 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)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
}), Int(1))
|
}), Int(1))
|
||||||
balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false)
|
balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false)
|
||||||
|
@ -121,14 +105,14 @@ func TestBalancerOneServerDown(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBalancerDownThenUp(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.Header().Set("server", "first")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), 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.Header().Set("server", "second")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), Int(1))
|
||||||
|
@ -150,35 +134,35 @@ func TestBalancerDownThenUp(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBalancerPropagate(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.Header().Set("server", "first")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), 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.Header().Set("server", "second")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), Int(1))
|
||||||
|
|
||||||
balancer2 := New(nil, &dynamic.HealthCheck{})
|
balancer2 := New(nil, true)
|
||||||
balancer2.AddService("third", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
balancer2.Add("third", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.Header().Set("server", "third")
|
rw.Header().Set("server", "third")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), 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.Header().Set("server", "fourth")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), Int(1))
|
||||||
|
|
||||||
topBalancer := New(nil, &dynamic.HealthCheck{})
|
topBalancer := New(nil, true)
|
||||||
topBalancer.AddService("balancer1", balancer1, Int(1))
|
topBalancer.Add("balancer1", balancer1, Int(1))
|
||||||
_ = balancer1.RegisterStatusUpdater(func(up bool) {
|
_ = balancer1.RegisterStatusUpdater(func(up bool) {
|
||||||
topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer1", up)
|
topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer1", up)
|
||||||
// TODO(mpl): if test gets flaky, add channel or something here to signal that
|
// TODO(mpl): if test gets flaky, add channel or something here to signal that
|
||||||
// propagation is done, and wait on it before sending request.
|
// 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) {
|
_ = balancer2.RegisterStatusUpdater(func(up bool) {
|
||||||
topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer2", up)
|
topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer2", up)
|
||||||
})
|
})
|
||||||
|
@ -223,28 +207,28 @@ func TestBalancerPropagate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBalancerAllServersZeroWeight(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.Add("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("test2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
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) {
|
func TestSticky(t *testing.T) {
|
||||||
balancer := New(&dynamic.Sticky{
|
balancer := New(&dynamic.Sticky{
|
||||||
Cookie: &dynamic.Cookie{Name: "test"},
|
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.Header().Set("server", "first")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(1))
|
}), 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.Header().Set("server", "second")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(2))
|
}), 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,
|
// 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.
|
// and that it does not "over-favor" the high-weighted ones with a biased start-up regime.
|
||||||
func TestBalancerBias(t *testing.T) {
|
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.Header().Set("server", "A")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(11))
|
}), 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.Header().Set("server", "B")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}), Int(3))
|
}), Int(3))
|
||||||
|
@ -290,3 +274,19 @@ func TestBalancerBias(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, wantSequence, recorder.sequence)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -12,8 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ptypes "github.com/traefik/paerser/types"
|
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"golang.org/x/net/http/httpguts"
|
"golang.org/x/net/http/httpguts"
|
||||||
)
|
)
|
||||||
|
@ -24,103 +21,107 @@ const StatusClientClosedRequest = 499
|
||||||
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
|
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
|
||||||
const StatusClientClosedRequestText = "Client Closed Request"
|
const StatusClientClosedRequestText = "Client Closed Request"
|
||||||
|
|
||||||
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
|
func buildSingleHostProxy(target *url.URL, passHostHeader bool, flushInterval time.Duration, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) http.Handler {
|
||||||
var flushInterval ptypes.Duration
|
return &httputil.ReverseProxy{
|
||||||
if responseForwarding != nil {
|
Director: directorBuilder(target, passHostHeader),
|
||||||
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")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Transport: roundTripper,
|
Transport: roundTripper,
|
||||||
FlushInterval: time.Duration(flushInterval),
|
FlushInterval: flushInterval,
|
||||||
BufferPool: bufferPool,
|
BufferPool: bufferPool,
|
||||||
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {
|
ErrorHandler: errorHandler,
|
||||||
statusCode := http.StatusInternalServerError
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
func directorBuilder(target *url.URL, passHostHeader bool) func(req *http.Request) {
|
||||||
case errors.Is(err, io.EOF):
|
return func(outReq *http.Request) {
|
||||||
statusCode = http.StatusBadGateway
|
outReq.URL.Scheme = target.Scheme
|
||||||
case errors.Is(err, context.Canceled):
|
outReq.URL.Host = target.Host
|
||||||
statusCode = StatusClientClosedRequest
|
|
||||||
default:
|
|
||||||
var netErr net.Error
|
|
||||||
if errors.As(err, &netErr) {
|
|
||||||
if netErr.Timeout() {
|
|
||||||
statusCode = http.StatusGatewayTimeout
|
|
||||||
} else {
|
|
||||||
statusCode = http.StatusBadGateway
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err)
|
u := outReq.URL
|
||||||
w.WriteHeader(statusCode)
|
if outReq.RequestURI != "" {
|
||||||
_, werr := w.Write([]byte(statusText(statusCode)))
|
parsedURL, err := url.ParseRequestURI(outReq.RequestURI)
|
||||||
if werr != nil {
|
if err == nil {
|
||||||
log.Debugf("Error while writing status code", werr)
|
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 {
|
func isWebSocketUpgrade(req *http.Request) bool {
|
||||||
if !httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {
|
return httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") &&
|
||||||
return false
|
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 {
|
func statusText(statusCode int) string {
|
||||||
|
|
|
@ -28,7 +28,7 @@ func BenchmarkProxy(b *testing.B) {
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)
|
||||||
|
|
||||||
pool := newBufferPool()
|
pool := newBufferPool()
|
||||||
handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool)
|
handler := buildSingleHostProxy(req.URL, false, 0, &staticTransport{res}, pool)
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|
|
@ -18,12 +18,7 @@ import (
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Bool(v bool) *bool { return &v }
|
|
||||||
|
|
||||||
func TestWebSocketTCPClose(t *testing.T) {
|
func TestWebSocketTCPClose(t *testing.T) {
|
||||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
upgrader := gorillawebsocket.Upgrader{}
|
upgrader := gorillawebsocket.Upgrader{}
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -42,7 +37,7 @@ func TestWebSocketTCPClose(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
|
|
||||||
proxyAddr := proxy.Listener.Addr().String()
|
proxyAddr := proxy.Listener.Addr().String()
|
||||||
_, conn, err := newWebsocketRequest(
|
_, conn, err := newWebsocketRequest(
|
||||||
|
@ -61,10 +56,6 @@ func TestWebSocketTCPClose(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketPingPong(t *testing.T) {
|
func TestWebSocketPingPong(t *testing.T) {
|
||||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
upgrader := gorillawebsocket.Upgrader{
|
upgrader := gorillawebsocket.Upgrader{
|
||||||
HandshakeTimeout: 10 * time.Second,
|
HandshakeTimeout: 10 * time.Second,
|
||||||
CheckOrigin: func(*http.Request) bool {
|
CheckOrigin: func(*http.Request) bool {
|
||||||
|
@ -86,17 +77,10 @@ func TestWebSocketPingPong(t *testing.T) {
|
||||||
_, _, _ = ws.ReadMessage()
|
_, _, _ = ws.ReadMessage()
|
||||||
})
|
})
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
srv := httptest.NewServer(mux)
|
||||||
mux.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
req.URL = parseURI(t, srv.URL)
|
|
||||||
f.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer proxy.Close()
|
|
||||||
|
|
||||||
serverAddr := proxy.Listener.Addr().String()
|
serverAddr := proxy.Listener.Addr().String()
|
||||||
|
|
||||||
headers := http.Header{}
|
headers := http.Header{}
|
||||||
|
@ -127,9 +111,6 @@ func TestWebSocketPingPong(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketEcho(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 := http.NewServeMux()
|
||||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||||
msg := make([]byte, 4)
|
msg := make([]byte, 4)
|
||||||
|
@ -145,17 +126,10 @@ func TestWebSocketEcho(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
srv := httptest.NewServer(mux)
|
||||||
mux.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
req.URL = parseURI(t, srv.URL)
|
|
||||||
f.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer proxy.Close()
|
|
||||||
|
|
||||||
serverAddr := proxy.Listener.Addr().String()
|
serverAddr := proxy.Listener.Addr().String()
|
||||||
|
|
||||||
headers := http.Header{}
|
headers := http.Header{}
|
||||||
|
@ -193,10 +167,6 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
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 := http.NewServeMux()
|
||||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||||
req := conn.Request()
|
req := conn.Request()
|
||||||
|
@ -208,7 +178,7 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := make([]byte, 4)
|
msg := make([]byte, 4)
|
||||||
_, err = conn.Read(msg)
|
_, err := conn.Read(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
fmt.Println(string(msg))
|
fmt.Println(string(msg))
|
||||||
|
@ -219,16 +189,10 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
srv := httptest.NewServer(mux)
|
||||||
mux.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
req.URL = parseURI(t, srv.URL)
|
|
||||||
f.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer proxy.Close()
|
|
||||||
|
|
||||||
serverAddr := proxy.Listener.Addr().String()
|
serverAddr := proxy.Listener.Addr().String()
|
||||||
|
|
||||||
|
@ -252,9 +216,6 @@ func TestWebSocketPassHost(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketServerWithoutCheckOrigin(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 {
|
upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
}}
|
}}
|
||||||
|
@ -277,7 +238,7 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
defer proxy.Close()
|
defer proxy.Close()
|
||||||
|
|
||||||
proxyAddr := proxy.Listener.Addr().String()
|
proxyAddr := proxy.Listener.Addr().String()
|
||||||
|
@ -293,9 +254,6 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketRequestWithOrigin(t *testing.T) {
|
func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
upgrader := gorillawebsocket.Upgrader{}
|
upgrader := gorillawebsocket.Upgrader{}
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
c, err := upgrader.Upgrade(w, r, nil)
|
c, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
@ -316,11 +274,11 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
defer proxy.Close()
|
defer proxy.Close()
|
||||||
|
|
||||||
proxyAddr := proxy.Listener.Addr().String()
|
proxyAddr := proxy.Listener.Addr().String()
|
||||||
_, err = newWebsocketRequest(
|
_, err := newWebsocketRequest(
|
||||||
withServer(proxyAddr),
|
withServer(proxyAddr),
|
||||||
withPath("/ws"),
|
withPath("/ws"),
|
||||||
withData("echo"),
|
withData("echo"),
|
||||||
|
@ -339,9 +297,6 @@ func TestWebSocketRequestWithOrigin(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
upgrader := gorillawebsocket.Upgrader{}
|
upgrader := gorillawebsocket.Upgrader{}
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
@ -363,7 +318,7 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
defer proxy.Close()
|
defer proxy.Close()
|
||||||
|
|
||||||
proxyAddr := proxy.Listener.Addr().String()
|
proxyAddr := proxy.Listener.Addr().String()
|
||||||
|
@ -379,18 +334,14 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketRequestWithHeadersInResponseWriter(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 := http.NewServeMux()
|
||||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}))
|
}))
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
srv := httptest.NewServer(mux)
|
||||||
mux.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
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) {
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
req.URL = parseURI(t, srv.URL)
|
req.URL = parseURI(t, srv.URL)
|
||||||
w.Header().Set("HEADER-KEY", "HEADER-VALUE")
|
w.Header().Set("HEADER-KEY", "HEADER-VALUE")
|
||||||
|
@ -403,6 +354,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
||||||
headers := http.Header{}
|
headers := http.Header{}
|
||||||
webSocketURL := "ws://" + serverAddr + "/ws"
|
webSocketURL := "ws://" + serverAddr + "/ws"
|
||||||
headers.Add("Origin", webSocketURL)
|
headers.Add("Origin", webSocketURL)
|
||||||
|
|
||||||
conn, resp, err := gorillawebsocket.DefaultDialer.Dial(webSocketURL, headers)
|
conn, resp, err := gorillawebsocket.DefaultDialer.Dial(webSocketURL, headers)
|
||||||
require.NoError(t, err, "Error during Dial with response: %+v", err, resp)
|
require.NoError(t, err, "Error during Dial with response: %+v", err, resp)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
@ -411,9 +363,6 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||||
f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
upgrader := gorillawebsocket.Upgrader{}
|
upgrader := gorillawebsocket.Upgrader{}
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
@ -435,7 +384,7 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
defer proxy.Close()
|
defer proxy.Close()
|
||||||
|
|
||||||
proxyAddr := proxy.Listener.Addr().String()
|
proxyAddr := proxy.Listener.Addr().String()
|
||||||
|
@ -451,18 +400,14 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebSocketUpgradeFailed(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 := http.NewServeMux()
|
||||||
mux.HandleFunc("/ws", func(w http.ResponseWriter, req *http.Request) {
|
mux.HandleFunc("/ws", func(w http.ResponseWriter, req *http.Request) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
})
|
})
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
srv := httptest.NewServer(mux)
|
||||||
mux.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
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) {
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
path := req.URL.Path // keep the original path
|
path := req.URL.Path // keep the original path
|
||||||
|
|
||||||
|
@ -501,9 +446,6 @@ func TestWebSocketUpgradeFailed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestForwardsWebsocketTraffic(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 := http.NewServeMux()
|
||||||
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
mux.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
|
||||||
_, err := conn.Write([]byte("ok"))
|
_, err := conn.Write([]byte("ok"))
|
||||||
|
@ -512,12 +454,10 @@ func TestForwardsWebsocketTraffic(t *testing.T) {
|
||||||
err = conn.Close()
|
err = conn.Close()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}))
|
}))
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
srv := httptest.NewServer(mux)
|
||||||
mux.ServeHTTP(w, req)
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
proxy := createProxyWithForwarder(t, f, srv.URL)
|
proxy := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
defer proxy.Close()
|
defer proxy.Close()
|
||||||
|
|
||||||
proxyAddr := proxy.Listener.Addr().String()
|
proxyAddr := proxy.Listener.Addr().String()
|
||||||
|
@ -557,15 +497,12 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||||
srv := createTLSWebsocketServer()
|
srv := createTLSWebsocketServer()
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil)
|
proxyWithoutTLSConfig := createProxyWithForwarder(t, srv.URL, http.DefaultTransport)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
proxyWithoutTLSConfig := createProxyWithForwarder(t, forwarderWithoutTLSConfig, srv.URL)
|
|
||||||
defer proxyWithoutTLSConfig.Close()
|
defer proxyWithoutTLSConfig.Close()
|
||||||
|
|
||||||
proxyAddr := proxyWithoutTLSConfig.Listener.Addr().String()
|
proxyAddr := proxyWithoutTLSConfig.Listener.Addr().String()
|
||||||
|
|
||||||
_, err = newWebsocketRequest(
|
_, err := newWebsocketRequest(
|
||||||
withServer(proxyAddr),
|
withServer(proxyAddr),
|
||||||
withPath("/ws"),
|
withPath("/ws"),
|
||||||
withData("ok"),
|
withData("ok"),
|
||||||
|
@ -576,10 +513,8 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
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()
|
defer proxyWithTLSConfig.Close()
|
||||||
|
|
||||||
proxyAddr = proxyWithTLSConfig.Listener.Addr().String()
|
proxyAddr = proxyWithTLSConfig.Listener.Addr().String()
|
||||||
|
@ -597,10 +532,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) {
|
||||||
defaultTransport := http.DefaultTransport.(*http.Transport).Clone()
|
defaultTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
defaultTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
defaultTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
|
||||||
forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, defaultTransport, nil)
|
proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(t, srv.URL, defaultTransport)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(t, forwarderWithTLSConfigFromDefaultTransport, srv.URL)
|
|
||||||
defer proxyWithTLSConfig.Close()
|
defer proxyWithTLSConfig.Close()
|
||||||
|
|
||||||
proxyAddr = proxyWithTLSConfigFromDefaultTransport.Listener.Addr().String()
|
proxyAddr = proxyWithTLSConfigFromDefaultTransport.Listener.Addr().String()
|
||||||
|
@ -705,15 +637,19 @@ func parseURI(t *testing.T, uri string) *url.URL {
|
||||||
return out
|
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()
|
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
|
path := req.URL.Path // keep the original path
|
||||||
// Set new backend URL
|
// Set new backend URL
|
||||||
req.URL = parseURI(t, url)
|
req.URL = u
|
||||||
req.URL.Path = path
|
req.URL.Path = path
|
||||||
|
|
||||||
proxy.ServeHTTP(w, req)
|
proxy.ServeHTTP(w, req)
|
||||||
}))
|
}))
|
||||||
|
t.Cleanup(srv.Close)
|
||||||
|
return srv
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,36 +4,28 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/alice"
|
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||||
"github.com/traefik/traefik/v2/pkg/healthcheck"
|
"github.com/traefik/traefik/v2/pkg/healthcheck"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"github.com/traefik/traefik/v2/pkg/metrics"
|
"github.com/traefik/traefik/v2/pkg/metrics"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares/accesslog"
|
"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"
|
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/safe"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/cookie"
|
"github.com/traefik/traefik/v2/pkg/server/cookie"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/provider"
|
"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/failover"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
|
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
|
"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
|
const defaultMaxBodySize int64 = -1
|
||||||
|
@ -43,6 +35,19 @@ type RoundTripperGetter interface {
|
||||||
Get(name string) (http.RoundTripper, error)
|
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.
|
// NewManager creates a new Manager.
|
||||||
func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager {
|
func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
|
@ -50,27 +55,13 @@ func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics
|
||||||
metricsRegistry: metricsRegistry,
|
metricsRegistry: metricsRegistry,
|
||||||
bufferPool: newBufferPool(),
|
bufferPool: newBufferPool(),
|
||||||
roundTripperManager: roundTripperManager,
|
roundTripperManager: roundTripperManager,
|
||||||
balancers: make(map[string]healthcheck.Balancers),
|
services: make(map[string]http.Handler),
|
||||||
configs: configs,
|
configs: configs,
|
||||||
|
healthCheckers: make(map[string]*healthcheck.ServiceHealthChecker),
|
||||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
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.
|
// BuildHTTP Creates a http.Handler for a service configuration.
|
||||||
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
||||||
ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName))
|
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)
|
serviceName = provider.GetQualifiedName(ctx, serviceName)
|
||||||
ctx = provider.AddInContext(ctx, serviceName)
|
ctx = provider.AddInContext(ctx, serviceName)
|
||||||
|
|
||||||
|
handler, ok := m.services[serviceName]
|
||||||
|
if ok {
|
||||||
|
return handler, nil
|
||||||
|
}
|
||||||
|
|
||||||
conf, ok := m.configs[serviceName]
|
conf, ok := m.configs[serviceName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("the service %q does not exist", serviceName)
|
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)
|
value := reflect.ValueOf(*conf.Service)
|
||||||
var count int
|
var count int
|
||||||
for i := 0; i < value.NumField(); i++ {
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
@ -101,7 +101,7 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
|
||||||
switch {
|
switch {
|
||||||
case conf.LoadBalancer != nil:
|
case conf.LoadBalancer != nil:
|
||||||
var err error
|
var err error
|
||||||
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer)
|
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conf.AddError(err, true)
|
conf.AddError(err, true)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -133,6 +133,8 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
|
||||||
return nil, sErr
|
return nil, sErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.services[serviceName] = lb
|
||||||
|
|
||||||
return lb, nil
|
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)
|
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) {
|
for _, service := range shuffle(config.Services, m.rand) {
|
||||||
serviceHandler, err := m.BuildHTTP(ctx, service.Name)
|
serviceHandler, err := m.BuildHTTP(ctx, service.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
balancer.AddService(service.Name, serviceHandler, service.Weight)
|
balancer.Add(service.Name, serviceHandler, service.Weight)
|
||||||
|
|
||||||
if config.HealthCheck == nil {
|
if config.HealthCheck == nil {
|
||||||
continue
|
continue
|
||||||
|
@ -245,216 +247,91 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string,
|
||||||
return balancer, nil
|
return balancer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName string, service *dynamic.ServersLoadBalancer) (http.Handler, error) {
|
func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName string, info *runtime.ServiceInfo) (http.Handler, error) {
|
||||||
if service.PassHostHeader == nil {
|
service := info.LoadBalancer
|
||||||
defaultPassHostHeader := true
|
|
||||||
service.PassHostHeader = &defaultPassHostHeader
|
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 {
|
if len(service.ServersTransport) > 0 {
|
||||||
service.ServersTransport = provider.GetQualifiedName(ctx, service.ServersTransport)
|
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)
|
roundTripper, err := m.roundTripperManager.Get(service.ServersTransport)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, roundTripper, m.bufferPool)
|
lb := wrr.New(service.Sticky, service.HealthCheck != nil)
|
||||||
if err != nil {
|
healthCheckTargets := make(map[string]*url.URL)
|
||||||
return nil, err
|
|
||||||
|
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) {
|
if service.HealthCheck != nil {
|
||||||
return accesslog.NewFieldHandler(next, accesslog.ServiceName, serviceName, accesslog.AddServiceFields), nil
|
m.healthCheckers[serviceName] = healthcheck.NewServiceHealthChecker(
|
||||||
}
|
ctx,
|
||||||
chain := alice.New()
|
m.metricsRegistry,
|
||||||
if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() {
|
service.HealthCheck,
|
||||||
chain = chain.Append(metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName))
|
lb,
|
||||||
|
info,
|
||||||
|
roundTripper,
|
||||||
|
healthCheckTargets,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := chain.Append(alHandler).Then(pipelining.New(ctx, fwd, "pipelining"))
|
return lb, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LaunchHealthCheck launches the health checks.
|
// LaunchHealthCheck launches the health checks.
|
||||||
func (m *Manager) LaunchHealthCheck() {
|
func (m *Manager) LaunchHealthCheck(ctx context.Context) {
|
||||||
backendConfigs := make(map[string]*healthcheck.BackendConfig)
|
for serviceName, hc := range m.healthCheckers {
|
||||||
|
ctx = log.With(ctx, log.Str(log.ServiceName, serviceName))
|
||||||
for serviceName, balancers := range m.balancers {
|
go hc.Launch(ctx)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,14 +15,10 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
"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) {
|
func TestGetLoadBalancer(t *testing.T) {
|
||||||
sm := Manager{}
|
sm := Manager{
|
||||||
|
roundTripperManager: newRtMock(),
|
||||||
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
@ -67,7 +63,8 @@ func TestGetLoadBalancer(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
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 {
|
if test.expectError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Nil(t, handler)
|
assert.Nil(t, handler)
|
||||||
|
@ -129,6 +126,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
desc: "Load balances between the two servers",
|
desc: "Load balances between the two servers",
|
||||||
serviceName: "test",
|
serviceName: "test",
|
||||||
service: &dynamic.ServersLoadBalancer{
|
service: &dynamic.ServersLoadBalancer{
|
||||||
|
PassHostHeader: Bool(true),
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: server1.URL,
|
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 {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
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.NoError(t, err)
|
||||||
assert.NotNil(t, handler)
|
assert.NotNil(t, handler)
|
||||||
|
@ -419,3 +390,21 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) {
|
||||||
_, err := manager.BuildHTTP(context.Background(), "test@file")
|
_, 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")
|
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