mirror of
https://github.com/traefik/traefik.git
synced 2025-05-05 23:43:01 +00:00
Merge branch v2.11 into v3.3
This commit is contained in:
commit
32ea014d07
2
.github/workflows/validate.yaml
vendored
2
.github/workflows/validate.yaml
vendored
@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: '1.23'
|
GO_VERSION: '1.23'
|
||||||
GOLANGCI_LINT_VERSION: v1.63.3
|
GOLANGCI_LINT_VERSION: v1.64.2
|
||||||
MISSPELL_VERSION: v0.6.0
|
MISSPELL_VERSION: v0.6.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
run:
|
run:
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
|
relative-path-mode: cfg
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
govet:
|
govet:
|
||||||
@ -165,6 +166,7 @@ linters-settings:
|
|||||||
linters:
|
linters:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
disable:
|
disable:
|
||||||
|
- tenv # Deprecated
|
||||||
- sqlclosecheck # not relevant (SQL)
|
- sqlclosecheck # not relevant (SQL)
|
||||||
- rowserrcheck # not relevant (SQL)
|
- rowserrcheck # not relevant (SQL)
|
||||||
- cyclop # duplicate of gocyclo
|
- cyclop # duplicate of gocyclo
|
||||||
@ -201,7 +203,6 @@ linters:
|
|||||||
- maintidx # kind of duplicate of gocyclo
|
- maintidx # kind of duplicate of gocyclo
|
||||||
- nonamedreturns # Too strict
|
- nonamedreturns # Too strict
|
||||||
- gosmopolitan # not relevant
|
- gosmopolitan # not relevant
|
||||||
- exportloopref # Not relevant since go1.22
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
# [v2.11.21](https://github.com/traefik/traefik/tree/v2.11.21) (2025-02-24)
|
||||||
|
[All Commits](https://github.com/traefik/traefik/compare/v2.11.20...v2.11.21)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme]** Bump github.com/go-acme/lego/v4 to v4.22.2 ([#11537](https://github.com/traefik/traefik/pull/11537) by [ldez](https://github.com/ldez))
|
||||||
|
- **[cli]** Bump github.com/traefik/paerser to v0.2.2 ([#11530](https://github.com/traefik/traefik/pull/11530) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[middleware]** Enable the retry middleware in the proxy ([#11536](https://github.com/traefik/traefik/pull/11536) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[middleware]** Retry should send headers on Write ([#11534](https://github.com/traefik/traefik/pull/11534) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
|
||||||
## [v3.3.3](https://github.com/traefik/traefik/tree/v3.3.3) (2025-01-31)
|
## [v3.3.3](https://github.com/traefik/traefik/tree/v3.3.3) (2025-01-31)
|
||||||
[All Commits](https://github.com/traefik/traefik/compare/v3.3.2...v3.3.3)
|
[All Commits](https://github.com/traefik/traefik/compare/v3.3.2...v3.3.3)
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
|||||||
|
|
||||||
| Provider Name | Provider Code | Environment Variables | |
|
| Provider Name | Provider Code | Environment Variables | |
|
||||||
|------------------------------------------------------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
|
|------------------------------------------------------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|
|
||||||
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
| [ACME DNS](https://github.com/joohoi/acme-dns) | `acme-dns` | `ACME_DNS_API_BASE`, `ACME_DNS_STORAGE_PATH`, `ACME_DNS_STORAGE_BASE_URL` | [Additional configuration](https://go-acme.github.io/lego/dns/acme-dns) |
|
||||||
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
|
| [Alibaba Cloud](https://www.alibabacloud.com) | `alidns` | `ALICLOUD_ACCESS_KEY`, `ALICLOUD_SECRET_KEY`, `ALICLOUD_REGION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/alidns) |
|
||||||
| [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) |
|
| [all-inkl](https://all-inkl.com) | `allinkl` | `ALL_INKL_LOGIN`, `ALL_INKL_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/allinkl) |
|
||||||
| [ArvanCloud](https://www.arvancloud.ir/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) |
|
| [ArvanCloud](https://www.arvancloud.ir/en) | `arvancloud` | `ARVANCLOUD_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/arvancloud) |
|
||||||
@ -397,6 +397,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
|||||||
| [Metaname](https://metaname.net) | `metaname` | `METANAME_ACCOUNT_REFERENCE`, `METANAME_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/metaname) |
|
| [Metaname](https://metaname.net) | `metaname` | `METANAME_ACCOUNT_REFERENCE`, `METANAME_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/metaname) |
|
||||||
| [mijn.host](https://mijn.host/) | `mijnhost` | `MIJNHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/mijnhost) |
|
| [mijn.host](https://mijn.host/) | `mijnhost` | `MIJNHOST_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/mijnhost) |
|
||||||
| [Mittwald](https://www.mittwald.de) | `mittwald` | `MITTWALD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/mittwald) |
|
| [Mittwald](https://www.mittwald.de) | `mittwald` | `MITTWALD_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/mittwald) |
|
||||||
|
| [myaddr.{tools,dev,io}](https://myaddr.tools/) | `myaddr` | `MYADDR_PRIVATE_KEYS_MAPPING` | [Additional configuration](https://go-acme.github.io/lego/dns/myaddr) |
|
||||||
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
|
| [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) |
|
||||||
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
|
| [Mythic Beasts](https://www.mythic-beasts.com) | `mythicbeasts` | `MYTHICBEASTS_USER_NAME`, `MYTHICBEASTS_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mythicbeasts) |
|
||||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
|
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | [Additional configuration](https://go-acme.github.io/lego/dns/namedotcom) |
|
||||||
@ -434,6 +435,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
|
|||||||
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
|
| [Shellrent](https://www.shellrent.com) | `shellrent` | `SHELLRENT_USERNAME`, `SHELLRENT_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/shellrent) |
|
||||||
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
| [Simply.com](https://www.simply.com/en/domains/) | `simply` | `SIMPLY_ACCOUNT_NAME`, `SIMPLY_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/simply) |
|
||||||
| [Sonic](https://www.sonic.com/) | `sonic` | `SONIC_USER_ID`, `SONIC_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/sonic) |
|
| [Sonic](https://www.sonic.com/) | `sonic` | `SONIC_USER_ID`, `SONIC_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/sonic) |
|
||||||
|
| [Spaceship](https://spaceship.com) | `spaceship` | `SPACESHIP_API_KEY`, `SPACESHIP_API_SECRET` | [Additional configuration](https://go-acme.github.io/lego/dns/spaceship) |
|
||||||
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/stackpath) |
|
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/stackpath) |
|
||||||
| [Technitium](https://technitium.com) | `technitium` | `TECHNITIUM_SERVER_BASE_URL`, `TECHNITIUM_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/technitium) |
|
| [Technitium](https://technitium.com) | `technitium` | `TECHNITIUM_SERVER_BASE_URL`, `TECHNITIUM_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/technitium) |
|
||||||
| [Tencent Cloud DNS](https://cloud.tencent.com/product/cns) | `tencentcloud` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/tencentcloud) |
|
| [Tencent Cloud DNS](https://cloud.tencent.com/product/cns) | `tencentcloud` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/tencentcloud) |
|
||||||
|
8
go.mod
8
go.mod
@ -17,7 +17,7 @@ require (
|
|||||||
github.com/docker/go-connections v0.5.0
|
github.com/docker/go-connections v0.5.0
|
||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
github.com/fsnotify/fsnotify v1.8.0
|
github.com/fsnotify/fsnotify v1.8.0
|
||||||
github.com/go-acme/lego/v4 v4.21.0
|
github.com/go-acme/lego/v4 v4.22.2
|
||||||
github.com/go-kit/kit v0.13.0
|
github.com/go-kit/kit v0.13.0
|
||||||
github.com/go-kit/log v0.2.1
|
github.com/go-kit/log v0.2.1
|
||||||
github.com/golang/protobuf v1.5.4
|
github.com/golang/protobuf v1.5.4
|
||||||
@ -63,7 +63,7 @@ require (
|
|||||||
github.com/tetratelabs/wazero v1.8.0
|
github.com/tetratelabs/wazero v1.8.0
|
||||||
github.com/tidwall/gjson v1.17.0
|
github.com/tidwall/gjson v1.17.0
|
||||||
github.com/traefik/grpc-web v0.16.0
|
github.com/traefik/grpc-web v0.16.0
|
||||||
github.com/traefik/paerser v0.2.1
|
github.com/traefik/paerser v0.2.2
|
||||||
github.com/traefik/yaegi v0.16.1
|
github.com/traefik/yaegi v0.16.1
|
||||||
github.com/unrolled/render v1.0.2
|
github.com/unrolled/render v1.0.2
|
||||||
github.com/unrolled/secure v1.0.9
|
github.com/unrolled/secure v1.0.9
|
||||||
@ -87,7 +87,6 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/log v0.8.0
|
go.opentelemetry.io/otel/sdk/log v0.8.0
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.28.0
|
go.opentelemetry.io/otel/sdk/metric v1.28.0
|
||||||
go.opentelemetry.io/otel/trace v1.32.0
|
go.opentelemetry.io/otel/trace v1.32.0
|
||||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // No tag on the repo.
|
|
||||||
golang.org/x/mod v0.22.0
|
golang.org/x/mod v0.22.0
|
||||||
golang.org/x/net v0.33.0
|
golang.org/x/net v0.33.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.10.0
|
||||||
@ -169,7 +168,6 @@ require (
|
|||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/containerd/platforms v0.2.1 // indirect
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
|
||||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||||
@ -281,6 +279,7 @@ require (
|
|||||||
github.com/nrdcg/desec v0.10.0 // indirect
|
github.com/nrdcg/desec v0.10.0 // indirect
|
||||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
github.com/nrdcg/freemyip v0.3.0 // indirect
|
github.com/nrdcg/freemyip v0.3.0 // indirect
|
||||||
|
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||||
github.com/nrdcg/goinwx v0.10.0 // indirect
|
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||||
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
@ -361,6 +360,7 @@ require (
|
|||||||
go.uber.org/zap v1.26.0 // indirect
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
golang.org/x/arch v0.4.0 // indirect
|
golang.org/x/arch v0.4.0 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||||
golang.org/x/oauth2 v0.24.0 // indirect
|
golang.org/x/oauth2 v0.24.0 // indirect
|
||||||
golang.org/x/term v0.27.0 // indirect
|
golang.org/x/term v0.27.0 // indirect
|
||||||
google.golang.org/api v0.214.0 // indirect
|
google.golang.org/api v0.214.0 // indirect
|
||||||
|
12
go.sum
12
go.sum
@ -259,8 +259,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
|
|
||||||
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
|
|
||||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
@ -357,8 +355,8 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
|
|||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
|
github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0=
|
||||||
github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
|
github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo=
|
||||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||||
@ -905,6 +903,8 @@ github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U
|
|||||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||||
github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc=
|
github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc=
|
||||||
github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM=
|
github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM=
|
||||||
|
github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
|
||||||
|
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||||
github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
|
github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
|
||||||
github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
|
github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
|
||||||
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
|
github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
|
||||||
@ -1187,8 +1187,8 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f
|
|||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY=
|
github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY=
|
||||||
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
|
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
|
||||||
github.com/traefik/paerser v0.2.1 h1:LFgeak1NmjEHF53c9ENdXdL1UMkF/lD5t+7Evsz4hH4=
|
github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ=
|
||||||
github.com/traefik/paerser v0.2.1/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24=
|
||||||
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
||||||
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
||||||
github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
|
github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
|
||||||
|
57
integration/fixtures/acme/acme_domains_1712362990.toml
Normal file
57
integration/fixtures/acme/acme_domains_1712362990.toml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
[global]
|
||||||
|
checkNewVersion = false
|
||||||
|
sendAnonymousUsage = false
|
||||||
|
|
||||||
|
[log]
|
||||||
|
level = "DEBUG"
|
||||||
|
noColor = true
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.web]
|
||||||
|
address = ":5002"
|
||||||
|
[entryPoints.websecure]
|
||||||
|
address = ":5001"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[certificatesResolvers.default.acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "/tmp/acme.json"
|
||||||
|
keyType = ""
|
||||||
|
caServer = "https://172.31.42.2:14000/dir"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[certificatesResolvers.default.acme.tlsChallenge]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[api]
|
||||||
|
insecure = true
|
||||||
|
|
||||||
|
[providers.file]
|
||||||
|
filename = "fixtures/acme/acme_domains_1712362990.toml"
|
||||||
|
|
||||||
|
## dynamic configuration ##
|
||||||
|
|
||||||
|
[http.services]
|
||||||
|
[http.services.test.loadBalancer]
|
||||||
|
[[http.services.test.loadBalancer.servers]]
|
||||||
|
url = "http://127.0.0.1:9010"
|
||||||
|
|
||||||
|
[http.routers]
|
||||||
|
[http.routers.test]
|
||||||
|
entryPoints = ["websecure"]
|
||||||
|
rule = "PathPrefix(`/`)"
|
||||||
|
service = "test"
|
||||||
|
[http.routers.test.tls]
|
||||||
|
certResolver = "default"
|
||||||
|
|
||||||
|
[[http.routers.test.tls.domains]]
|
||||||
|
main = "acme.wtf"
|
||||||
|
sans = [
|
||||||
|
"traefik.acme.wtf",
|
||||||
|
]
|
||||||
|
|
@ -50,7 +50,7 @@
|
|||||||
Rule = "Path(`/basic`)"
|
Rule = "Path(`/basic`)"
|
||||||
[http.routers.router1]
|
[http.routers.router1]
|
||||||
Service = "service1"
|
Service = "service1"
|
||||||
Middlewares = ["retry", "ratelimit-1"]
|
Middlewares = ["ratelimit-1"]
|
||||||
Rule = "Path(`/ratelimit`)"
|
Rule = "Path(`/ratelimit`)"
|
||||||
[http.routers.router2]
|
[http.routers.router2]
|
||||||
Service = "service2"
|
Service = "service2"
|
||||||
@ -58,8 +58,12 @@
|
|||||||
Rule = "Path(`/retry`)"
|
Rule = "Path(`/retry`)"
|
||||||
[http.routers.router3]
|
[http.routers.router3]
|
||||||
Service = "service3"
|
Service = "service3"
|
||||||
Middlewares = ["retry", "basic-auth"]
|
Middlewares = ["basic-auth"]
|
||||||
Rule = "Path(`/auth`)"
|
Rule = "Path(`/auth`)"
|
||||||
|
[http.routers.router4]
|
||||||
|
Service = "service4"
|
||||||
|
Middlewares = ["retry", "basic-auth"]
|
||||||
|
Rule = "Path(`/retry-auth`)"
|
||||||
[http.routers.customPing]
|
[http.routers.customPing]
|
||||||
entryPoints = ["web"]
|
entryPoints = ["web"]
|
||||||
rule = "PathPrefix(`/ping`)"
|
rule = "PathPrefix(`/ping`)"
|
||||||
@ -98,3 +102,9 @@
|
|||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
[[http.services.service3.loadBalancer.servers]]
|
[[http.services.service3.loadBalancer.servers]]
|
||||||
url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}"
|
url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}"
|
||||||
|
|
||||||
|
[http.services.service4]
|
||||||
|
[http.services.service4.loadBalancer]
|
||||||
|
passHostHeader = true
|
||||||
|
[[http.services.service4.loadBalancer.servers]]
|
||||||
|
url = "http://{{.WhoamiIP}}:{{.WhoamiPort}}"
|
||||||
|
@ -77,7 +77,7 @@ func (s *TracingSuite) TearDownTest() {
|
|||||||
s.composeStop("tempo")
|
s.composeStop("tempo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TracingSuite) TestOpentelemetryBasic_HTTP() {
|
func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() {
|
||||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||||
WhoamiIP: s.whoamiIP,
|
WhoamiIP: s.whoamiIP,
|
||||||
WhoamiPort: s.whoamiPort,
|
WhoamiPort: s.whoamiPort,
|
||||||
@ -144,7 +144,7 @@ func (s *TracingSuite) TestOpentelemetryBasic_HTTP() {
|
|||||||
s.checkTraceContent(contains)
|
s.checkTraceContent(contains)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TracingSuite) TestOpentelemetryBasic_gRPC() {
|
func (s *TracingSuite) TestOpenTelemetryBasic_gRPC() {
|
||||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||||
WhoamiIP: s.whoamiIP,
|
WhoamiIP: s.whoamiIP,
|
||||||
WhoamiPort: s.whoamiPort,
|
WhoamiPort: s.whoamiPort,
|
||||||
@ -201,7 +201,7 @@ func (s *TracingSuite) TestOpentelemetryBasic_gRPC() {
|
|||||||
s.checkTraceContent(contains)
|
s.checkTraceContent(contains)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TracingSuite) TestOpentelemetryRateLimit() {
|
func (s *TracingSuite) TestOpenTelemetryRateLimit() {
|
||||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||||
WhoamiIP: s.whoamiIP,
|
WhoamiIP: s.whoamiIP,
|
||||||
WhoamiPort: s.whoamiPort,
|
WhoamiPort: s.whoamiPort,
|
||||||
@ -248,48 +248,26 @@ func (s *TracingSuite) TestOpentelemetryRateLimit() {
|
|||||||
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.1.name": "Retry",
|
"batches.0.scopeSpans.0.spans.1.name": "Router",
|
||||||
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.2.name": "RateLimiter",
|
"batches.0.scopeSpans.0.spans.2.name": "Metrics",
|
||||||
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.3.name": "Retry",
|
"batches.0.scopeSpans.0.spans.3.name": "EntryPoint",
|
||||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER",
|
||||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.resend_count\").value.intValue": "1",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||||
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
||||||
"batches.0.scopeSpans.0.spans.4.name": "RateLimiter",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||||
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||||
"batches.0.scopeSpans.0.spans.5.name": "Retry",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "429",
|
||||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.resend_count\").value.intValue": "2",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.6.name": "Router",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.7.name": "Metrics",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_INTERNAL",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.8.name": "EntryPoint",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.kind": "SPAN_KIND_SERVER",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.query\").value.stringValue": "",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.response.status_code\").value.intValue": "429",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
|
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
|
||||||
@ -318,37 +296,33 @@ func (s *TracingSuite) TestOpentelemetryRateLimit() {
|
|||||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "ratelimit-1@file",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.4.name": "Retry",
|
"batches.0.scopeSpans.0.spans.4.name": "Router",
|
||||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.5.name": "Router",
|
"batches.0.scopeSpans.0.spans.5.name": "Metrics",
|
||||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file",
|
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.6.name": "Metrics",
|
"batches.0.scopeSpans.0.spans.6.name": "EntryPoint",
|
||||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER",
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||||
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||||
"batches.0.scopeSpans.0.spans.7.name": "EntryPoint",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
||||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_SERVER",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.path\").value.stringValue": "/ratelimit",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.query\").value.stringValue": "",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.checkTraceContent(contains)
|
s.checkTraceContent(contains)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TracingSuite) TestOpentelemetryRetry() {
|
func (s *TracingSuite) TestOpenTelemetryRetry() {
|
||||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||||
WhoamiIP: s.whoamiIP,
|
WhoamiIP: s.whoamiIP,
|
||||||
WhoamiPort: 81,
|
WhoamiPort: 81,
|
||||||
@ -471,7 +445,7 @@ func (s *TracingSuite) TestOpentelemetryRetry() {
|
|||||||
s.checkTraceContent(contains)
|
s.checkTraceContent(contains)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TracingSuite) TestOpentelemetryAuth() {
|
func (s *TracingSuite) TestOpenTelemetryAuth() {
|
||||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||||
WhoamiIP: s.whoamiIP,
|
WhoamiIP: s.whoamiIP,
|
||||||
WhoamiPort: s.whoamiPort,
|
WhoamiPort: s.whoamiPort,
|
||||||
@ -498,59 +472,90 @@ func (s *TracingSuite) TestOpentelemetryAuth() {
|
|||||||
"batches.0.scopeSpans.0.spans.0.status.message": "Authentication failed",
|
"batches.0.scopeSpans.0.spans.0.status.message": "Authentication failed",
|
||||||
"batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR",
|
"batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.1.name": "Retry",
|
"batches.0.scopeSpans.0.spans.1.name": "Router",
|
||||||
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.2.name": "BasicAuth",
|
"batches.0.scopeSpans.0.spans.2.name": "Metrics",
|
||||||
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||||
"batches.0.scopeSpans.0.spans.2.status.message": "Authentication failed",
|
|
||||||
"batches.0.scopeSpans.0.spans.2.status.code": "STATUS_CODE_ERROR",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.3.name": "Retry",
|
"batches.0.scopeSpans.0.spans.3.name": "EntryPoint",
|
||||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER",
|
||||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.resend_count\").value.intValue": "1",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||||
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
||||||
"batches.0.scopeSpans.0.spans.4.name": "BasicAuth",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||||
"batches.0.scopeSpans.0.spans.4.status.message": "Authentication failed",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||||
"batches.0.scopeSpans.0.spans.4.status.code": "STATUS_CODE_ERROR",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.5.name": "Retry",
|
|
||||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.resend_count\").value.intValue": "2",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.6.name": "Router",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.7.name": "Metrics",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_INTERNAL",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.8.name": "EntryPoint",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.kind": "SPAN_KIND_SERVER",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"url.query\").value.stringValue": "",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.8.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.checkTraceContent(contains)
|
s.checkTraceContent(contains)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TracingSuite) TestOpentelemetrySafeURL() {
|
func (s *TracingSuite) TestOpenTelemetryAuthWithRetry() {
|
||||||
|
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||||
|
WhoamiIP: s.whoamiIP,
|
||||||
|
WhoamiPort: s.whoamiPort,
|
||||||
|
IP: s.otelCollectorIP,
|
||||||
|
})
|
||||||
|
defer os.Remove(file)
|
||||||
|
|
||||||
|
s.traefikCmd(withConfigFile(file))
|
||||||
|
|
||||||
|
// wait for traefik
|
||||||
|
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth"))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/retry-auth", 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
contains := []map[string]string{
|
||||||
|
{
|
||||||
|
"batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik",
|
||||||
|
|
||||||
|
"batches.0.scopeSpans.0.spans.0.name": "BasicAuth",
|
||||||
|
"batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_INTERNAL",
|
||||||
|
"batches.0.scopeSpans.0.spans.0.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.0.status.message": "Authentication failed",
|
||||||
|
"batches.0.scopeSpans.0.spans.0.status.code": "STATUS_CODE_ERROR",
|
||||||
|
|
||||||
|
"batches.0.scopeSpans.0.spans.1.name": "Retry",
|
||||||
|
"batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL",
|
||||||
|
"batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
||||||
|
|
||||||
|
"batches.0.scopeSpans.0.spans.2.name": "Router",
|
||||||
|
"batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL",
|
||||||
|
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service4@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.router.name\").value.stringValue": "router4@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.2.attributes.#(key=\"http.route\").value.stringValue": "Path(`/retry-auth`)",
|
||||||
|
|
||||||
|
"batches.0.scopeSpans.0.spans.3.name": "Metrics",
|
||||||
|
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||||
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||||
|
|
||||||
|
"batches.0.scopeSpans.0.spans.4.name": "EntryPoint",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_SERVER",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"url.path\").value.stringValue": "/retry-auth",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"url.query\").value.stringValue": "",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.response.status_code\").value.intValue": "401",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.checkTraceContent(contains)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TracingSuite) TestOpenTelemetrySafeURL() {
|
||||||
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{
|
||||||
WhoamiIP: s.whoamiIP,
|
WhoamiIP: s.whoamiIP,
|
||||||
WhoamiPort: s.whoamiPort,
|
WhoamiPort: s.whoamiPort,
|
||||||
@ -593,30 +598,26 @@ func (s *TracingSuite) TestOpentelemetrySafeURL() {
|
|||||||
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
"batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "basic-auth@file",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.4.name": "Retry",
|
"batches.0.scopeSpans.0.spans.4.name": "Router",
|
||||||
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "retry@file",
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
||||||
|
"batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.5.name": "Router",
|
"batches.0.scopeSpans.0.spans.5.name": "Metrics",
|
||||||
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL",
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file",
|
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file",
|
|
||||||
"batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)",
|
|
||||||
|
|
||||||
"batches.0.scopeSpans.0.spans.6.name": "Metrics",
|
"batches.0.scopeSpans.0.spans.6.name": "EntryPoint",
|
||||||
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_INTERNAL",
|
"batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER",
|
||||||
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
||||||
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
||||||
"batches.0.scopeSpans.0.spans.7.name": "EntryPoint",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
||||||
"batches.0.scopeSpans.0.spans.7.kind": "SPAN_KIND_SERVER",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"url.query\").value.stringValue": "api_key=REDACTED",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"entry_point\").value.stringValue": "web",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.request.method\").value.stringValue": "GET",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.path\").value.stringValue": "/auth",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"url.query\").value.stringValue": "api_key=REDACTED",
|
"batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1",
|
|
||||||
"batches.0.scopeSpans.0.spans.7.attributes.#(key=\"http.response.status_code\").value.intValue": "200",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -357,7 +356,7 @@ func sortByName[T orderedWithName](direction string, results []T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortByFunc[T orderedWithName, U constraints.Ordered](direction string, results []T, fn func(int) U) {
|
func sortByFunc[T orderedWithName, U cmp.Ordered](direction string, results []T, fn func(int) U) {
|
||||||
// Ascending
|
// Ascending
|
||||||
if direction == ascendantSorting {
|
if direction == ascendantSorting {
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
@ -36,6 +36,48 @@ type Listener interface {
|
|||||||
// each of them about a retry attempt.
|
// each of them about a retry attempt.
|
||||||
type Listeners []Listener
|
type Listeners []Listener
|
||||||
|
|
||||||
|
// Retried exists to implement the Listener interface. It calls Retried on each of its slice entries.
|
||||||
|
func (l Listeners) Retried(req *http.Request, attempt int) {
|
||||||
|
for _, listener := range l {
|
||||||
|
listener.Retried(req, attempt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type shouldRetryContextKey struct{}
|
||||||
|
|
||||||
|
// ShouldRetry is a function allowing to enable/disable the retry middleware mechanism.
|
||||||
|
type ShouldRetry func(shouldRetry bool)
|
||||||
|
|
||||||
|
// ContextShouldRetry returns the ShouldRetry function if it has been set by the Retry middleware in the chain.
|
||||||
|
func ContextShouldRetry(ctx context.Context) ShouldRetry {
|
||||||
|
f, _ := ctx.Value(shouldRetryContextKey{}).(ShouldRetry)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapHandler wraps a given http.Handler to inject the httptrace.ClientTrace in the request context when it is needed
|
||||||
|
// by the retry middleware.
|
||||||
|
func WrapHandler(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if shouldRetry := ContextShouldRetry(req.Context()); shouldRetry != nil {
|
||||||
|
shouldRetry(true)
|
||||||
|
|
||||||
|
trace := &httptrace.ClientTrace{
|
||||||
|
WroteHeaders: func() {
|
||||||
|
shouldRetry(false)
|
||||||
|
},
|
||||||
|
WroteRequest: func(httptrace.WroteRequestInfo) {
|
||||||
|
shouldRetry(false)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
newCtx := httptrace.WithClientTrace(req.Context(), trace)
|
||||||
|
next.ServeHTTP(rw, req.WithContext(newCtx))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(rw, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// retry is a middleware that retries requests.
|
// retry is a middleware that retries requests.
|
||||||
type retry struct {
|
type retry struct {
|
||||||
attempts int
|
attempts int
|
||||||
@ -101,19 +143,13 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
req = req.WithContext(tracingCtx)
|
req = req.WithContext(tracingCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldRetry := attempts < r.attempts
|
remainAttempts := attempts < r.attempts
|
||||||
retryResponseWriter := newResponseWriter(rw, shouldRetry)
|
retryResponseWriter := newResponseWriter(rw)
|
||||||
|
|
||||||
// Disable retries when the backend already received request data
|
var shouldRetry ShouldRetry = func(shouldRetry bool) {
|
||||||
clientTrace := &httptrace.ClientTrace{
|
retryResponseWriter.SetShouldRetry(remainAttempts && shouldRetry)
|
||||||
WroteHeaders: func() {
|
|
||||||
retryResponseWriter.DisableRetries()
|
|
||||||
},
|
|
||||||
WroteRequest: func(httptrace.WroteRequestInfo) {
|
|
||||||
retryResponseWriter.DisableRetries()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
newCtx := httptrace.WithClientTrace(req.Context(), clientTrace)
|
newCtx := context.WithValue(req.Context(), shouldRetryContextKey{}, shouldRetry)
|
||||||
|
|
||||||
r.next.ServeHTTP(retryResponseWriter, req.Clone(newCtx))
|
r.next.ServeHTTP(retryResponseWriter, req.Clone(newCtx))
|
||||||
|
|
||||||
@ -164,18 +200,10 @@ func (r *retry) newBackOff() backoff.BackOff {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retried exists to implement the Listener interface. It calls Retried on each of its slice entries.
|
func newResponseWriter(rw http.ResponseWriter) *responseWriter {
|
||||||
func (l Listeners) Retried(req *http.Request, attempt int) {
|
|
||||||
for _, listener := range l {
|
|
||||||
listener.Retried(req, attempt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) *responseWriter {
|
|
||||||
return &responseWriter{
|
return &responseWriter{
|
||||||
responseWriter: rw,
|
responseWriter: rw,
|
||||||
headers: make(http.Header),
|
headers: make(http.Header),
|
||||||
shouldRetry: shouldRetry,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,8 +218,8 @@ func (r *responseWriter) ShouldRetry() bool {
|
|||||||
return r.shouldRetry
|
return r.shouldRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriter) DisableRetries() {
|
func (r *responseWriter) SetShouldRetry(shouldRetry bool) {
|
||||||
r.shouldRetry = false
|
r.shouldRetry = shouldRetry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriter) Header() http.Header {
|
func (r *responseWriter) Header() http.Header {
|
||||||
@ -205,20 +233,14 @@ func (r *responseWriter) Write(buf []byte) (int, error) {
|
|||||||
if r.ShouldRetry() {
|
if r.ShouldRetry() {
|
||||||
return len(buf), nil
|
return len(buf), nil
|
||||||
}
|
}
|
||||||
|
if !r.written {
|
||||||
|
r.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
return r.responseWriter.Write(buf)
|
return r.responseWriter.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *responseWriter) WriteHeader(code int) {
|
func (r *responseWriter) WriteHeader(code int) {
|
||||||
if r.ShouldRetry() && code == http.StatusServiceUnavailable {
|
if r.shouldRetry || r.written {
|
||||||
// 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()
|
|
||||||
// will never return true in case there was a connection established to
|
|
||||||
// the backend server and so we can be sure that the 503 was produced
|
|
||||||
// inside Traefik already and we don't have to retry in this cases.
|
|
||||||
r.DisableRetries()
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ShouldRetry() || r.written {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,12 +105,21 @@ func TestRetry(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
retryAttempts := 0
|
retryAttempts := 0
|
||||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
// This signals that a connection will be established with the backend
|
||||||
|
// to enable the Retry middleware mechanism.
|
||||||
|
shouldRetry := ContextShouldRetry(req.Context())
|
||||||
|
if shouldRetry != nil {
|
||||||
|
shouldRetry(true)
|
||||||
|
}
|
||||||
|
|
||||||
retryAttempts++
|
retryAttempts++
|
||||||
|
|
||||||
if retryAttempts > test.amountFaultyEndpoints {
|
if retryAttempts > test.amountFaultyEndpoints {
|
||||||
// calls WroteHeaders on httptrace.
|
// This signals that request headers have been sent to the backend.
|
||||||
_ = r.Write(io.Discard)
|
if shouldRetry != nil {
|
||||||
|
shouldRetry(false)
|
||||||
|
}
|
||||||
|
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
@ -152,27 +161,16 @@ func TestRetryEmptyServerList(t *testing.T) {
|
|||||||
assert.Equal(t, 0, retryListener.timesCalled)
|
assert.Equal(t, 0, retryListener.timesCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetryListeners(t *testing.T) {
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
retryListeners := Listeners{&countingRetryListener{}, &countingRetryListener{}}
|
|
||||||
|
|
||||||
retryListeners.Retried(req, 1)
|
|
||||||
retryListeners.Retried(req, 1)
|
|
||||||
|
|
||||||
for _, retryListener := range retryListeners {
|
|
||||||
listener := retryListener.(*countingRetryListener)
|
|
||||||
if listener.timesCalled != 2 {
|
|
||||||
t.Errorf("retry listener was called %d time(s), want %d time(s)", listener.timesCalled, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
|
func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
|
||||||
attempt := 0
|
attempt := 0
|
||||||
expectedHeaderName := "X-Foo-Test-2"
|
|
||||||
expectedHeaderValue := "bar"
|
expectedHeaderValue := "bar"
|
||||||
|
|
||||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
shouldRetry := ContextShouldRetry(req.Context())
|
||||||
|
if shouldRetry != nil {
|
||||||
|
shouldRetry(true)
|
||||||
|
}
|
||||||
|
|
||||||
headerName := fmt.Sprintf("X-Foo-Test-%d", attempt)
|
headerName := fmt.Sprintf("X-Foo-Test-%d", attempt)
|
||||||
rw.Header().Add(headerName, expectedHeaderValue)
|
rw.Header().Add(headerName, expectedHeaderValue)
|
||||||
if attempt < 2 {
|
if attempt < 2 {
|
||||||
@ -181,43 +179,54 @@ func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request has been successfully written to backend
|
// Request has been successfully written to backend
|
||||||
trace := httptrace.ContextClientTrace(req.Context())
|
shouldRetry(false)
|
||||||
trace.WroteHeaders()
|
|
||||||
|
|
||||||
// And we decide to answer to client
|
// And we decide to answer to client.
|
||||||
rw.WriteHeader(http.StatusNoContent)
|
rw.WriteHeader(http.StatusNoContent)
|
||||||
})
|
})
|
||||||
|
|
||||||
retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest")
|
retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
responseRecorder := httptest.NewRecorder()
|
res := httptest.NewRecorder()
|
||||||
retry.ServeHTTP(responseRecorder, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
|
retry.ServeHTTP(res, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
|
||||||
|
|
||||||
headerValue := responseRecorder.Header().Get(expectedHeaderName)
|
// The third header attempt is kept.
|
||||||
|
headerValue := res.Header().Get("X-Foo-Test-2")
|
||||||
// Validate if we have the correct header
|
assert.Equal(t, expectedHeaderValue, headerValue)
|
||||||
if headerValue != expectedHeaderValue {
|
|
||||||
t.Errorf("Expected to have %s for header %s, got %s", expectedHeaderValue, expectedHeaderName, headerValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that we don't have headers from previous attempts
|
// Validate that we don't have headers from previous attempts
|
||||||
for i := range attempt {
|
for i := range attempt {
|
||||||
headerName := fmt.Sprintf("X-Foo-Test-%d", i)
|
headerName := fmt.Sprintf("X-Foo-Test-%d", i)
|
||||||
headerValue = responseRecorder.Header().Get("headerName")
|
headerValue = res.Header().Get(headerName)
|
||||||
if headerValue != "" {
|
if headerValue != "" {
|
||||||
t.Errorf("Expected no value for header %s, got %s", headerName, headerValue)
|
t.Errorf("Expected no value for header %s, got %s", headerName, headerValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// countingRetryListener is a Listener implementation to count the times the Retried fn is called.
|
func TestRetryShouldNotLooseHeadersOnWrite(t *testing.T) {
|
||||||
type countingRetryListener struct {
|
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
timesCalled int
|
rw.Header().Add("X-Foo-Test", "bar")
|
||||||
}
|
|
||||||
|
|
||||||
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
|
// Request has been successfully written to backend.
|
||||||
l.timesCalled++
|
shouldRetry := ContextShouldRetry(req.Context())
|
||||||
|
if shouldRetry != nil {
|
||||||
|
shouldRetry(false)
|
||||||
|
}
|
||||||
|
// And we decide to answer to client without calling WriteHeader.
|
||||||
|
_, err := rw.Write([]byte("bar"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
retry.ServeHTTP(res, testhelpers.MustNewRequest(http.MethodGet, "http://test", http.NoBody))
|
||||||
|
|
||||||
|
headerValue := res.Header().Get("X-Foo-Test")
|
||||||
|
assert.Equal(t, "bar", headerValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetryWithFlush(t *testing.T) {
|
func TestRetryWithFlush(t *testing.T) {
|
||||||
@ -275,12 +284,24 @@ func TestRetryWebsocket(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
retryAttempts := 0
|
retryAttempts := 0
|
||||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
// This signals that a connection will be established with the backend
|
||||||
|
// to enable the Retry middleware mechanism.
|
||||||
|
shouldRetry := ContextShouldRetry(req.Context())
|
||||||
|
if shouldRetry != nil {
|
||||||
|
shouldRetry(true)
|
||||||
|
}
|
||||||
|
|
||||||
retryAttempts++
|
retryAttempts++
|
||||||
|
|
||||||
if retryAttempts > test.amountFaultyEndpoints {
|
if retryAttempts > test.amountFaultyEndpoints {
|
||||||
|
// This signals that request headers have been sent to the backend.
|
||||||
|
if shouldRetry != nil {
|
||||||
|
shouldRetry(false)
|
||||||
|
}
|
||||||
|
|
||||||
upgrader := websocket.Upgrader{}
|
upgrader := websocket.Upgrader{}
|
||||||
_, err := upgrader.Upgrade(rw, r, nil)
|
_, err := upgrader.Upgrade(rw, req, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
@ -387,3 +408,12 @@ func Test1xxResponses(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, 0, retryListener.timesCalled)
|
assert.Equal(t, 0, retryListener.timesCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// countingRetryListener is a Listener implementation to count the times the Retried fn is called.
|
||||||
|
type countingRetryListener struct {
|
||||||
|
timesCalled int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *countingRetryListener) Retried(req *http.Request, attempt int) {
|
||||||
|
l.timesCalled++
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
|
||||||
"github.com/traefik/traefik/v3/pkg/proxy/httputil"
|
"github.com/traefik/traefik/v3/pkg/proxy/httputil"
|
||||||
"github.com/traefik/traefik/v3/pkg/safe"
|
"github.com/traefik/traefik/v3/pkg/safe"
|
||||||
"github.com/traefik/traefik/v3/pkg/server/cookie"
|
"github.com/traefik/traefik/v3/pkg/server/cookie"
|
||||||
@ -349,6 +350,10 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
|
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
|
||||||
}
|
}
|
||||||
|
// The retry wrapping must be done just before the proxy handler,
|
||||||
|
// to make sure that the retry will not be triggered/disabled by
|
||||||
|
// middlewares in the chain.
|
||||||
|
proxy = retry.WrapHandler(proxy)
|
||||||
|
|
||||||
// Prevents from enabling observability for internal resources.
|
// Prevents from enabling observability for internal resources.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user