mirror of
https://github.com/containous/traefik.git
synced 2025-09-30 17:44:25 +03:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d707c8ba93 | ||
|
640eb62ca1 | ||
|
216710864e | ||
|
226f20b626 | ||
|
151be83bce | ||
|
d1a8c7fa78 |
@@ -13,6 +13,7 @@ env:
|
|||||||
- VERSION: $TRAVIS_TAG
|
- VERSION: $TRAVIS_TAG
|
||||||
- CODENAME: maroilles
|
- CODENAME: maroilles
|
||||||
- N_MAKE_JOBS: 2
|
- N_MAKE_JOBS: 2
|
||||||
|
- DOCS_VERIFY_SKIP: true
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [v1.7.17](https://github.com/containous/traefik/tree/v1.7.17) (2019-09-23)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v1.7.16...v1.7.17)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[logs,middleware]** Avoid closing stdout when the accesslog handler is closed ([#5459](https://github.com/containous/traefik/pull/5459) by [nrwiersma](https://github.com/nrwiersma))
|
||||||
|
- **[middleware]** Actually send header and code during WriteHeader, if needed ([#5404](https://github.com/containous/traefik/pull/5404) by [mpl](https://github.com/mpl))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[k8s]** Add note clarifying client certificate header ([#5362](https://github.com/containous/traefik/pull/5362) by [bradjones1](https://github.com/bradjones1))
|
||||||
|
- **[webui]** Update docs links. ([#5412](https://github.com/containous/traefik/pull/5412) by [ldez](https://github.com/ldez))
|
||||||
|
- Update Traefik image version. ([#5399](https://github.com/containous/traefik/pull/5399) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
## [v1.7.16](https://github.com/containous/traefik/tree/v1.7.16) (2019-09-13)
|
## [v1.7.16](https://github.com/containous/traefik/tree/v1.7.16) (2019-09-13)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v1.7.15...v1.7.16)
|
[All Commits](https://github.com/containous/traefik/compare/v1.7.15...v1.7.16)
|
||||||
|
|
||||||
|
@@ -158,7 +158,7 @@ Integration tests must be run from the `integration/` directory and require the
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The [documentation site](https://docs.traefik.io/) is built with [mkdocs](https://mkdocs.org/)
|
The [documentation site](https://docs.traefik.io/v1.7/) is built with [mkdocs](https://mkdocs.org/)
|
||||||
|
|
||||||
### Building Documentation
|
### Building Documentation
|
||||||
|
|
||||||
|
30
README.md
30
README.md
@@ -4,7 +4,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://semaphoreci.com/containous/traefik)
|
[](https://semaphoreci.com/containous/traefik)
|
||||||
[](https://docs.traefik.io)
|
[](https://docs.traefik.io/v1.7)
|
||||||
[](http://goreportcard.com/report/containous/traefik)
|
[](http://goreportcard.com/report/containous/traefik)
|
||||||
[](https://microbadger.com/images/traefik)
|
[](https://microbadger.com/images/traefik)
|
||||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||||
@@ -70,22 +70,22 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
|
|||||||
|
|
||||||
## Supported Backends
|
## Supported Backends
|
||||||
|
|
||||||
- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
|
- [Docker](https://docs.traefik.io/v1.7/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/v1.7/configuration/backends/docker#docker-swarm-mode)
|
||||||
- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
|
- [Kubernetes](https://docs.traefik.io/v1.7/configuration/backends/kubernetes)
|
||||||
- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
|
- [Mesos](https://docs.traefik.io/v1.7/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/v1.7/configuration/backends/marathon)
|
||||||
- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
|
- [Rancher](https://docs.traefik.io/v1.7/configuration/backends/rancher) (API, Metadata)
|
||||||
- [Azure Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
|
- [Azure Service Fabric](https://docs.traefik.io/v1.7/configuration/backends/servicefabric)
|
||||||
- [Consul Catalog](https://docs.traefik.io/configuration/backends/consulcatalog)
|
- [Consul Catalog](https://docs.traefik.io/v1.7/configuration/backends/consulcatalog)
|
||||||
- [Consul](https://docs.traefik.io/configuration/backends/consul) / [Etcd](https://docs.traefik.io/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/configuration/backends/boltdb)
|
- [Consul](https://docs.traefik.io/v1.7/configuration/backends/consul) / [Etcd](https://docs.traefik.io/v1.7/configuration/backends/etcd) / [Zookeeper](https://docs.traefik.io/v1.7/configuration/backends/zookeeper) / [BoltDB](https://docs.traefik.io/v1.7/configuration/backends/boltdb)
|
||||||
- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
|
- [Eureka](https://docs.traefik.io/v1.7/configuration/backends/eureka)
|
||||||
- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
|
- [Amazon ECS](https://docs.traefik.io/v1.7/configuration/backends/ecs)
|
||||||
- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
|
- [Amazon DynamoDB](https://docs.traefik.io/v1.7/configuration/backends/dynamodb)
|
||||||
- [File](https://docs.traefik.io/configuration/backends/file)
|
- [File](https://docs.traefik.io/v1.7/configuration/backends/file)
|
||||||
- [Rest](https://docs.traefik.io/configuration/backends/rest)
|
- [Rest](https://docs.traefik.io/v1.7/configuration/backends/rest)
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
|
To get your hands on Traefik, you can use the [5-Minute Quickstart](http://docs.traefik.io/v1.7/#the-traefik-quickstart-using-docker) in our documentation (you will need Docker).
|
||||||
|
|
||||||
Alternatively, if you don't want to install anything on your computer, you can try Traefik online in this great [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
Alternatively, if you don't want to install anything on your computer, you can try Traefik online in this great [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ You can access the simple HTML frontend of Traefik.
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
|
You can find the complete documentation at [https://docs.traefik.io/v1.7](https://docs.traefik.io/v1.7).
|
||||||
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
@@ -352,14 +352,14 @@ func stats(globalConfiguration *configuration.GlobalConfiguration) {
|
|||||||
Stats collection is enabled.
|
Stats collection is enabled.
|
||||||
Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.
|
Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.
|
||||||
Help us improve Traefik by leaving this feature on :)
|
Help us improve Traefik by leaving this feature on :)
|
||||||
More details on: https://docs.traefik.io/basics/#collected-data
|
More details on: https://docs.traefik.io/v1.7/basics/#collected-data
|
||||||
`)
|
`)
|
||||||
collect(globalConfiguration)
|
collect(globalConfiguration)
|
||||||
} else {
|
} else {
|
||||||
log.Info(`
|
log.Info(`
|
||||||
Stats collection is disabled.
|
Stats collection is disabled.
|
||||||
Help us improve Traefik by turning this feature on :)
|
Help us improve Traefik by turning this feature on :)
|
||||||
More details on: https://docs.traefik.io/basics/#collected-data
|
More details on: https://docs.traefik.io/v1.7/basics/#collected-data
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Traefik
|
Description=Traefik
|
||||||
Documentation=https://docs.traefik.io
|
Documentation=https://docs.traefik.io/v1.7
|
||||||
#After=network-online.target
|
#After=network-online.target
|
||||||
#AssertFileIsExecutable=/usr/bin/traefik
|
#AssertFileIsExecutable=/usr/bin/traefik
|
||||||
#AssertPathExists=/etc/traefik/traefik.toml
|
#AssertPathExists=/etc/traefik/traefik.toml
|
||||||
|
@@ -221,7 +221,11 @@ infos:
|
|||||||
If `pem` is set, it will add a `X-Forwarded-Tls-Client-Cert` header that contains the escaped pem as value.
|
If `pem` is set, it will add a `X-Forwarded-Tls-Client-Cert` header that contains the escaped pem as value.
|
||||||
If at least one flag of the `infos` part is set, it will add a `X-Forwarded-Tls-Client-Cert-Infos` header that contains an escaped string composed of the client certificate data selected by the infos flags.
|
If at least one flag of the `infos` part is set, it will add a `X-Forwarded-Tls-Client-Cert-Infos` header that contains an escaped string composed of the client certificate data selected by the infos flags.
|
||||||
This infos part is composed like the following example (not escaped):
|
This infos part is composed like the following example (not escaped):
|
||||||
```Subject="C=FR,ST=SomeState,L=Lyon,O=Cheese,CN=*.cheese.org",NB=1531900816,NA=1563436816,SAN=*.cheese.org,*.cheese.net,cheese.in,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2```
|
```
|
||||||
|
Subject="C=FR,ST=SomeState,L=Lyon,O=Cheese,CN=*.cheese.org",NB=1531900816,NA=1563436816,SAN=*.cheese.org,*.cheese.net,cheese.in,test@cheese.org,test@cheese.net,10.0.1.0,10.0.1.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Note these options work only with certificates issued by CAs included in the applicable [EntryPoint ClientCA section](/configuration/entrypoints/#tls-mutual-authentication); certificates from other CAs are not parsed or passed through as-is.
|
||||||
|
|
||||||
<4> `traefik.ingress.kubernetes.io/rate-limit` example:
|
<4> `traefik.ingress.kubernetes.io/rate-limit` example:
|
||||||
|
|
||||||
|
@@ -97,7 +97,7 @@ In compose file the entrypoint syntax is different. Notice how quotes are used:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik
|
image: traefik:v1.7
|
||||||
command:
|
command:
|
||||||
- --defaultentrypoints=powpow
|
- --defaultentrypoints=powpow
|
||||||
- "--entryPoints=Name:powpow Address::42 Compress:true"
|
- "--entryPoints=Name:powpow Address::42 Compress:true"
|
||||||
@@ -105,7 +105,7 @@ traefik:
|
|||||||
or
|
or
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik
|
image: traefik:v1.7
|
||||||
command: --defaultentrypoints=powpow --entryPoints='Name:powpow Address::42 Compress:true'
|
command: --defaultentrypoints=powpow --entryPoints='Name:powpow Address::42 Compress:true'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -77,7 +77,7 @@ version: '3'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
reverse-proxy:
|
reverse-proxy:
|
||||||
image: traefik # The official Traefik docker image
|
image: traefik:v1.7 # The official Traefik docker image
|
||||||
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
||||||
ports:
|
ports:
|
||||||
- "80:80" # The HTTP port
|
- "80:80" # The HTTP port
|
||||||
|
@@ -91,7 +91,7 @@ To watch docker events, add `--docker.watch`.
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||||
command:
|
command:
|
||||||
- "--api"
|
- "--api"
|
||||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||||
@@ -156,7 +156,7 @@ The initializer in a docker-compose file will be:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik_init:
|
traefik_init:
|
||||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||||
command:
|
command:
|
||||||
- "storeconfig"
|
- "storeconfig"
|
||||||
- "--api"
|
- "--api"
|
||||||
@@ -177,7 +177,7 @@ And now, the Traefik part will only have the Consul configuration.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||||
depends_on:
|
depends_on:
|
||||||
- traefik_init
|
- traefik_init
|
||||||
- consul
|
- consul
|
||||||
@@ -200,7 +200,7 @@ The new configuration will be stored in Consul, and you need to restart the Trae
|
|||||||
version: "3.4"
|
version: "3.4"
|
||||||
services:
|
services:
|
||||||
traefik_init:
|
traefik_init:
|
||||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||||
command:
|
command:
|
||||||
- "storeconfig"
|
- "storeconfig"
|
||||||
- "--api"
|
- "--api"
|
||||||
@@ -229,7 +229,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- consul
|
- consul
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||||
depends_on:
|
depends_on:
|
||||||
- traefik_init
|
- traefik_init
|
||||||
- consul
|
- consul
|
||||||
|
@@ -50,7 +50,7 @@ version: '2'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
|
@@ -118,7 +118,7 @@ spec:
|
|||||||
serviceAccountName: traefik-ingress-controller
|
serviceAccountName: traefik-ingress-controller
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik:v1.7
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
@@ -180,7 +180,7 @@ spec:
|
|||||||
serviceAccountName: traefik-ingress-controller
|
serviceAccountName: traefik-ingress-controller
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik:v1.7
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
|
@@ -139,7 +139,7 @@ Here is the [docker-compose file](https://docs.docker.com/compose/compose-file/)
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||||
command: --consul --consul.endpoint=127.0.0.1:8500
|
command: --consul --consul.endpoint=127.0.0.1:8500
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
traefik:
|
traefik:
|
||||||
image: traefik
|
image: traefik:v1.7
|
||||||
command: --api --rancher --rancher.domain=rancher.localhost --rancher.endpoint=http://example.com --rancher.accesskey=XXXXXXX --rancher.secretkey=YYYYYY --logLevel=DEBUG
|
command: --api --rancher --rancher.domain=rancher.localhost --rancher.endpoint=http://example.com --rancher.accesskey=XXXXXXX --rancher.secretkey=YYYYYY --logLevel=DEBUG
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
traefik:
|
traefik:
|
||||||
image: traefik
|
image: traefik:v1.7
|
||||||
command: -c /dev/null --api --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
command: -c /dev/null --api --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
|
@@ -26,7 +26,7 @@ spec:
|
|||||||
serviceAccountName: traefik-ingress-controller
|
serviceAccountName: traefik-ingress-controller
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik:v1.7
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
|
@@ -22,7 +22,7 @@ spec:
|
|||||||
serviceAccountName: traefik-ingress-controller
|
serviceAccountName: traefik-ingress-controller
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik:v1.7
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
|
@@ -13,7 +13,7 @@ version: '3'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
reverse-proxy:
|
reverse-proxy:
|
||||||
image: traefik # The official Traefik docker image
|
image: traefik:v1.7 # The official Traefik docker image
|
||||||
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
||||||
ports:
|
ports:
|
||||||
- "80:80" # The HTTP port
|
- "80:80" # The HTTP port
|
||||||
@@ -101,7 +101,7 @@ IP: 172.27.0.4
|
|||||||
|
|
||||||
### 4 — Enjoy Traefik's Magic
|
### 4 — Enjoy Traefik's Magic
|
||||||
|
|
||||||
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/) and let Traefik work for you!
|
Now that you have a basic understanding of how Traefik can automatically create the routes to your services and load balance them, it might be time to dive into [the documentation](https://docs.traefik.io/v1.7/) and let Traefik work for you!
|
||||||
Whatever your infrastructure is, there is probably [an available Traefik backend](https://docs.traefik.io/#supported-backends) that will do the job.
|
Whatever your infrastructure is, there is probably [an available Traefik backend](https://docs.traefik.io/v1.7/#supported-backends) that will do the job.
|
||||||
|
|
||||||
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Traefik's let's encrypt integration](https://docs.traefik.io/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/user-guide/docker-and-lets-encrypt/).
|
Our recommendation would be to see for yourself how simple it is to enable HTTPS with [Traefik's let's encrypt integration](https://docs.traefik.io/v1.7/user-guide/examples/#lets-encrypt-support) using the dedicated [user guide](https://docs.traefik.io/v1.7/user-guide/docker-and-lets-encrypt/).
|
||||||
|
@@ -3,7 +3,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
# The reverse proxy service (Traefik)
|
# The reverse proxy service (Traefik)
|
||||||
reverse-proxy:
|
reverse-proxy:
|
||||||
image: traefik # The official Traefik docker image
|
image: traefik:v1.7 # The official Traefik docker image
|
||||||
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
command: --api --docker # Enables the web UI and tells Traefik to listen to docker
|
||||||
ports:
|
ports:
|
||||||
- "80:80" # The HTTP port
|
- "80:80" # The HTTP port
|
||||||
|
@@ -3,6 +3,7 @@ package accesslog
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -32,6 +33,19 @@ const (
|
|||||||
JSONFormat = "json"
|
JSONFormat = "json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type noopCloser struct {
|
||||||
|
*os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n noopCloser) Write(p []byte) (int, error) {
|
||||||
|
return n.File.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n noopCloser) Close() error {
|
||||||
|
// noop
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type logHandlerParams struct {
|
type logHandlerParams struct {
|
||||||
logDataTable *LogData
|
logDataTable *LogData
|
||||||
crr *captureRequestReader
|
crr *captureRequestReader
|
||||||
@@ -42,7 +56,7 @@ type logHandlerParams struct {
|
|||||||
type LogHandler struct {
|
type LogHandler struct {
|
||||||
config *types.AccessLog
|
config *types.AccessLog
|
||||||
logger *logrus.Logger
|
logger *logrus.Logger
|
||||||
file *os.File
|
file io.WriteCloser
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
httpCodeRanges types.HTTPCodeRanges
|
httpCodeRanges types.HTTPCodeRanges
|
||||||
logHandlerChan chan logHandlerParams
|
logHandlerChan chan logHandlerParams
|
||||||
@@ -51,7 +65,7 @@ type LogHandler struct {
|
|||||||
|
|
||||||
// NewLogHandler creates a new LogHandler
|
// NewLogHandler creates a new LogHandler
|
||||||
func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
|
func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
|
||||||
file := os.Stdout
|
var file io.WriteCloser = noopCloser{os.Stdout}
|
||||||
if len(config.FilePath) > 0 {
|
if len(config.FilePath) > 0 {
|
||||||
f, err := openAccessLogFile(config.FilePath)
|
f, err := openAccessLogFile(config.FilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -205,14 +219,15 @@ func (l *LogHandler) Close() error {
|
|||||||
// Rotate closes and reopens the log file to allow for rotation
|
// Rotate closes and reopens the log file to allow for rotation
|
||||||
// by an external source.
|
// by an external source.
|
||||||
func (l *LogHandler) Rotate() error {
|
func (l *LogHandler) Rotate() error {
|
||||||
var err error
|
if l.config.FilePath == "" {
|
||||||
|
return nil
|
||||||
if l.file != nil {
|
|
||||||
defer func(f *os.File) {
|
|
||||||
f.Close()
|
|
||||||
}(l.file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l.file != nil {
|
||||||
|
defer func(f io.Closer) { _ = f.Close() }(l.file)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
l.file, err = os.OpenFile(l.config.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
|
l.file, err = os.OpenFile(l.config.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -19,7 +19,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Compile time validation that the response recorder implements http interfaces correctly.
|
// Compile time validation that the response recorder implements http interfaces correctly.
|
||||||
var _ middlewares.Stateful = &responseRecorderWithCloseNotify{}
|
var (
|
||||||
|
_ middlewares.Stateful = &responseRecorderWithCloseNotify{}
|
||||||
|
_ middlewares.Stateful = &codeCatcherWithCloseNotify{}
|
||||||
|
)
|
||||||
|
|
||||||
// Handler is a middleware that provides the custom error pages
|
// Handler is a middleware that provides the custom error pages
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
@@ -76,26 +79,27 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
|||||||
|
|
||||||
catcher := newCodeCatcher(w, h.httpCodeRanges)
|
catcher := newCodeCatcher(w, h.httpCodeRanges)
|
||||||
next.ServeHTTP(catcher, req)
|
next.ServeHTTP(catcher, req)
|
||||||
if !catcher.isError {
|
if !catcher.isFilteredCode() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the recorder code against the configured http status code ranges
|
// check the recorder code against the configured http status code ranges
|
||||||
|
code := catcher.getCode()
|
||||||
for _, block := range h.httpCodeRanges {
|
for _, block := range h.httpCodeRanges {
|
||||||
if catcher.code >= block[0] && catcher.code <= block[1] {
|
if code >= block[0] && code <= block[1] {
|
||||||
log.Errorf("Caught HTTP Status Code %d, returning error page", catcher.code)
|
log.Errorf("Caught HTTP Status Code %d, returning error page", code)
|
||||||
|
|
||||||
var query string
|
var query string
|
||||||
if len(h.backendQuery) > 0 {
|
if len(h.backendQuery) > 0 {
|
||||||
query = "/" + strings.TrimPrefix(h.backendQuery, "/")
|
query = "/" + strings.TrimPrefix(h.backendQuery, "/")
|
||||||
query = strings.Replace(query, "{status}", strconv.Itoa(catcher.code), -1)
|
query = strings.Replace(query, "{status}", strconv.Itoa(code), -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pageReq, err := newRequest(h.backendURL + query)
|
pageReq, err := newRequest(h.backendURL + query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
w.WriteHeader(catcher.code)
|
w.WriteHeader(code)
|
||||||
fmt.Fprint(w, http.StatusText(catcher.code))
|
fmt.Fprint(w, http.StatusText(code))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +109,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
|||||||
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
|
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
|
||||||
|
|
||||||
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
|
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
|
||||||
w.WriteHeader(catcher.code)
|
w.WriteHeader(code)
|
||||||
w.Write(recorderErrorPage.GetBody().Bytes())
|
w.Write(recorderErrorPage.GetBody().Bytes())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -127,21 +131,39 @@ 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
|
||||||
// the original client (its responseWriter) without any buffering.
|
// the original client (its responseWriter) without any buffering.
|
||||||
type codeCatcher struct {
|
type codeCatcher struct {
|
||||||
headerMap http.Header
|
headerMap http.Header
|
||||||
code int
|
code int
|
||||||
httpCodeRanges types.HTTPCodeRanges
|
httpCodeRanges types.HTTPCodeRanges
|
||||||
firstWrite bool
|
firstWrite bool
|
||||||
isError bool
|
caughtFilteredCode bool
|
||||||
responseWriter http.ResponseWriter
|
responseWriter http.ResponseWriter
|
||||||
err error
|
headersSent bool
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) *codeCatcher {
|
type codeCatcherWithCloseNotify struct {
|
||||||
|
*codeCatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotify returns a channel that receives at most a
|
||||||
|
// single value (true) when the client connection has gone away.
|
||||||
|
func (cc *codeCatcherWithCloseNotify) CloseNotify() <-chan bool {
|
||||||
|
return cc.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) responseInterceptor {
|
||||||
catcher := &codeCatcher{
|
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.
|
||||||
@@ -149,6 +171,9 @@ func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges)
|
|||||||
httpCodeRanges: httpCodeRanges,
|
httpCodeRanges: httpCodeRanges,
|
||||||
firstWrite: true,
|
firstWrite: true,
|
||||||
}
|
}
|
||||||
|
if _, ok := rw.(http.CloseNotifier); ok {
|
||||||
|
return &codeCatcherWithCloseNotify{catcher}
|
||||||
|
}
|
||||||
return catcher
|
return catcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,12 +185,19 @@ func (cc *codeCatcher) Header() http.Header {
|
|||||||
return cc.headerMap
|
return cc.headerMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cc *codeCatcher) getCode() int {
|
||||||
|
return cc.code
|
||||||
|
}
|
||||||
|
|
||||||
|
// isFilteredCode returns whether the codeCatcher received a response code among the ones it is watching,
|
||||||
|
// and for which the response should be deferred to the error handler.
|
||||||
|
func (cc *codeCatcher) isFilteredCode() bool {
|
||||||
|
return cc.caughtFilteredCode
|
||||||
|
}
|
||||||
|
|
||||||
func (cc *codeCatcher) Write(buf []byte) (int, error) {
|
func (cc *codeCatcher) Write(buf []byte) (int, error) {
|
||||||
if cc.err != nil {
|
|
||||||
return 0, cc.err
|
|
||||||
}
|
|
||||||
if !cc.firstWrite {
|
if !cc.firstWrite {
|
||||||
if cc.isError {
|
if cc.caughtFilteredCode {
|
||||||
// We don't care about the contents of the response,
|
// We don't care about the contents of the response,
|
||||||
// since we want to serve the ones from the error page,
|
// since we want to serve the ones from the error page,
|
||||||
// so we just drop them.
|
// so we just drop them.
|
||||||
@@ -173,25 +205,38 @@ func (cc *codeCatcher) Write(buf []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
return cc.responseWriter.Write(buf)
|
return cc.responseWriter.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, block := range cc.httpCodeRanges {
|
|
||||||
if cc.code >= block[0] && cc.code <= block[1] {
|
|
||||||
cc.isError = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cc.firstWrite = false
|
cc.firstWrite = false
|
||||||
if !cc.isError {
|
|
||||||
utils.CopyHeaders(cc.responseWriter.Header(), cc.Header())
|
// If WriteHeader was already called from the caller, this is a NOOP.
|
||||||
cc.responseWriter.WriteHeader(cc.code)
|
// Otherwise, cc.code is actually a 200 here.
|
||||||
} else {
|
cc.WriteHeader(cc.code)
|
||||||
|
|
||||||
|
if cc.caughtFilteredCode {
|
||||||
return len(buf), nil
|
return len(buf), nil
|
||||||
}
|
}
|
||||||
return cc.responseWriter.Write(buf)
|
return cc.responseWriter.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *codeCatcher) WriteHeader(code int) {
|
func (cc *codeCatcher) WriteHeader(code int) {
|
||||||
|
if cc.headersSent || cc.caughtFilteredCode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cc.code = code
|
cc.code = code
|
||||||
|
for _, block := range cc.httpCodeRanges {
|
||||||
|
if cc.code >= block[0] && cc.code <= block[1] {
|
||||||
|
cc.caughtFilteredCode = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// it will be up to the other response recorder to send the headers,
|
||||||
|
// so it is out of our hands now.
|
||||||
|
if cc.caughtFilteredCode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.CopyHeaders(cc.responseWriter.Header(), cc.Header())
|
||||||
|
cc.responseWriter.WriteHeader(cc.code)
|
||||||
|
cc.headersSent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hijack hijacks the connection
|
// Hijack hijacks the connection
|
||||||
@@ -204,6 +249,10 @@ func (cc *codeCatcher) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|||||||
|
|
||||||
// Flush sends any buffered data to the client.
|
// Flush sends any buffered data to the client.
|
||||||
func (cc *codeCatcher) Flush() {
|
func (cc *codeCatcher) Flush() {
|
||||||
|
// If WriteHeader was already called from the caller, this is a NOOP.
|
||||||
|
// Otherwise, cc.code is actually a 200 here.
|
||||||
|
cc.WriteHeader(cc.code)
|
||||||
|
|
||||||
if flusher, ok := cc.responseWriter.(http.Flusher); ok {
|
if flusher, ok := cc.responseWriter.(http.Flusher); ok {
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
|
@@ -46,6 +46,18 @@ func TestHandler(t *testing.T) {
|
|||||||
assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusPartialContent))
|
assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusPartialContent))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "a 304, so no Write called",
|
||||||
|
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
|
||||||
|
backendCode: http.StatusNotModified,
|
||||||
|
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintln(w, "whatever, should not be called")
|
||||||
|
}),
|
||||||
|
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||||
|
assert.Equal(t, http.StatusNotModified, recorder.Code, "HTTP status")
|
||||||
|
assert.Contains(t, recorder.Body.String(), "")
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "in the range",
|
desc: "in the range",
|
||||||
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
|
errorPage: &types.ErrorPage{Backend: "error", Query: "/test", Status: []string{"500-501", "503-599"}},
|
||||||
@@ -120,6 +132,9 @@ func TestHandler(t *testing.T) {
|
|||||||
|
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(test.backendCode)
|
w.WriteHeader(test.backendCode)
|
||||||
|
if test.backendCode == http.StatusNotModified {
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Fprintln(w, http.StatusText(test.backendCode))
|
fmt.Fprintln(w, http.StatusText(test.backendCode))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
1
webui/.gitignore
vendored
1
webui/.gitignore
vendored
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
|
/.quasar
|
||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
/.idea
|
/.idea
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
<a class="navbar-item" [href]="releaseLink" target="_blank">
|
<a class="navbar-item" [href]="releaseLink" target="_blank">
|
||||||
{{ version }} / {{ codename }}
|
{{ version }} / {{ codename }}
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="https://docs.traefik.io" target="_blank">
|
<a class="navbar-item" href="https://docs.traefik.io/v1.7" target="_blank">
|
||||||
Documentation
|
Documentation
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user