mirror of
https://github.com/containous/traefik.git
synced 2025-09-24 21:44:26 +03:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d707c8ba93 | ||
|
640eb62ca1 | ||
|
216710864e | ||
|
226f20b626 | ||
|
151be83bce | ||
|
d1a8c7fa78 |
@@ -13,6 +13,7 @@ env:
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: maroilles
|
||||
- N_MAKE_JOBS: 2
|
||||
- DOCS_VERIFY_SKIP: true
|
||||
|
||||
script:
|
||||
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
||||
# 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)
|
||||
[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
|
||||
|
||||
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
|
||||
|
||||
|
30
README.md
30
README.md
@@ -4,7 +4,7 @@
|
||||
</p>
|
||||
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
[](https://docs.traefik.io)
|
||||
[](https://docs.traefik.io/v1.7)
|
||||
[](http://goreportcard.com/report/containous/traefik)
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](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
|
||||
|
||||
- [Docker](https://docs.traefik.io/configuration/backends/docker) / [Swarm mode](https://docs.traefik.io/configuration/backends/docker#docker-swarm-mode)
|
||||
- [Kubernetes](https://docs.traefik.io/configuration/backends/kubernetes)
|
||||
- [Mesos](https://docs.traefik.io/configuration/backends/mesos) / [Marathon](https://docs.traefik.io/configuration/backends/marathon)
|
||||
- [Rancher](https://docs.traefik.io/configuration/backends/rancher) (API, Metadata)
|
||||
- [Azure Service Fabric](https://docs.traefik.io/configuration/backends/servicefabric)
|
||||
- [Consul Catalog](https://docs.traefik.io/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)
|
||||
- [Eureka](https://docs.traefik.io/configuration/backends/eureka)
|
||||
- [Amazon ECS](https://docs.traefik.io/configuration/backends/ecs)
|
||||
- [Amazon DynamoDB](https://docs.traefik.io/configuration/backends/dynamodb)
|
||||
- [File](https://docs.traefik.io/configuration/backends/file)
|
||||
- [Rest](https://docs.traefik.io/configuration/backends/rest)
|
||||
- [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/v1.7/configuration/backends/kubernetes)
|
||||
- [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/v1.7/configuration/backends/rancher) (API, Metadata)
|
||||
- [Azure Service Fabric](https://docs.traefik.io/v1.7/configuration/backends/servicefabric)
|
||||
- [Consul Catalog](https://docs.traefik.io/v1.7/configuration/backends/consulcatalog)
|
||||
- [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/v1.7/configuration/backends/eureka)
|
||||
- [Amazon ECS](https://docs.traefik.io/v1.7/configuration/backends/ecs)
|
||||
- [Amazon DynamoDB](https://docs.traefik.io/v1.7/configuration/backends/dynamodb)
|
||||
- [File](https://docs.traefik.io/v1.7/configuration/backends/file)
|
||||
- [Rest](https://docs.traefik.io/v1.7/configuration/backends/rest)
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -100,7 +100,7 @@ You can access the simple HTML frontend of Traefik.
|
||||
|
||||
## 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).
|
||||
|
||||
## Support
|
||||
|
@@ -352,14 +352,14 @@ func stats(globalConfiguration *configuration.GlobalConfiguration) {
|
||||
Stats collection is enabled.
|
||||
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 :)
|
||||
More details on: https://docs.traefik.io/basics/#collected-data
|
||||
More details on: https://docs.traefik.io/v1.7/basics/#collected-data
|
||||
`)
|
||||
collect(globalConfiguration)
|
||||
} else {
|
||||
log.Info(`
|
||||
Stats collection is disabled.
|
||||
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]
|
||||
Description=Traefik
|
||||
Documentation=https://docs.traefik.io
|
||||
Documentation=https://docs.traefik.io/v1.7
|
||||
#After=network-online.target
|
||||
#AssertFileIsExecutable=/usr/bin/traefik
|
||||
#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 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):
|
||||
```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:
|
||||
|
||||
|
@@ -97,7 +97,7 @@ In compose file the entrypoint syntax is different. Notice how quotes are used:
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:v1.7
|
||||
command:
|
||||
- --defaultentrypoints=powpow
|
||||
- "--entryPoints=Name:powpow Address::42 Compress:true"
|
||||
@@ -105,7 +105,7 @@ traefik:
|
||||
or
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:v1.7
|
||||
command: --defaultentrypoints=powpow --entryPoints='Name:powpow Address::42 Compress:true'
|
||||
```
|
||||
|
||||
|
@@ -77,7 +77,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
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
|
||||
ports:
|
||||
- "80:80" # The HTTP port
|
||||
|
@@ -91,7 +91,7 @@ To watch docker events, add `--docker.watch`.
|
||||
version: "3"
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
command:
|
||||
- "--api"
|
||||
- "--entrypoints=Name:http Address::80 Redirect.EntryPoint:https"
|
||||
@@ -156,7 +156,7 @@ The initializer in a docker-compose file will be:
|
||||
|
||||
```yaml
|
||||
traefik_init:
|
||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
command:
|
||||
- "storeconfig"
|
||||
- "--api"
|
||||
@@ -177,7 +177,7 @@ And now, the Traefik part will only have the Consul configuration.
|
||||
|
||||
```yaml
|
||||
traefik:
|
||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
depends_on:
|
||||
- traefik_init
|
||||
- consul
|
||||
@@ -200,7 +200,7 @@ The new configuration will be stored in Consul, and you need to restart the Trae
|
||||
version: "3.4"
|
||||
services:
|
||||
traefik_init:
|
||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
command:
|
||||
- "storeconfig"
|
||||
- "--api"
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
depends_on:
|
||||
- consul
|
||||
traefik:
|
||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
depends_on:
|
||||
- traefik_init
|
||||
- consul
|
||||
|
@@ -50,7 +50,7 @@ version: '2'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:<stable version from https://hub.docker.com/_/traefik>
|
||||
image: traefik:<stable v1.7 from https://hub.docker.com/_/traefik>
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
|
@@ -118,7 +118,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
@@ -180,7 +180,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
|
@@ -139,7 +139,7 @@ Here is the [docker-compose file](https://docs.docker.com/compose/compose-file/)
|
||||
|
||||
```yaml
|
||||
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
|
||||
ports:
|
||||
- "80:80"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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
|
||||
ports:
|
||||
- "80:80"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
traefik:
|
||||
image: traefik
|
||||
image: traefik:v1.7
|
||||
command: -c /dev/null --api --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
||||
ports:
|
||||
- "80:80"
|
||||
|
@@ -26,7 +26,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
|
@@ -22,7 +22,7 @@ spec:
|
||||
serviceAccountName: traefik-ingress-controller
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
- image: traefik:v1.7
|
||||
name: traefik-ingress-lb
|
||||
ports:
|
||||
- name: http
|
||||
|
@@ -13,7 +13,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
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
|
||||
ports:
|
||||
- "80:80" # The HTTP port
|
||||
@@ -101,7 +101,7 @@ IP: 172.27.0.4
|
||||
|
||||
### 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!
|
||||
Whatever your infrastructure is, there is probably [an available Traefik backend](https://docs.traefik.io/#supported-backends) that will do the job.
|
||||
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/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:
|
||||
# The reverse proxy service (Traefik)
|
||||
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
|
||||
ports:
|
||||
- "80:80" # The HTTP port
|
||||
|
@@ -3,6 +3,7 @@ package accesslog
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -32,6 +33,19 @@ const (
|
||||
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 {
|
||||
logDataTable *LogData
|
||||
crr *captureRequestReader
|
||||
@@ -42,7 +56,7 @@ type logHandlerParams struct {
|
||||
type LogHandler struct {
|
||||
config *types.AccessLog
|
||||
logger *logrus.Logger
|
||||
file *os.File
|
||||
file io.WriteCloser
|
||||
mu sync.Mutex
|
||||
httpCodeRanges types.HTTPCodeRanges
|
||||
logHandlerChan chan logHandlerParams
|
||||
@@ -51,7 +65,7 @@ type LogHandler struct {
|
||||
|
||||
// NewLogHandler creates a new LogHandler
|
||||
func NewLogHandler(config *types.AccessLog) (*LogHandler, error) {
|
||||
file := os.Stdout
|
||||
var file io.WriteCloser = noopCloser{os.Stdout}
|
||||
if len(config.FilePath) > 0 {
|
||||
f, err := openAccessLogFile(config.FilePath)
|
||||
if err != nil {
|
||||
@@ -205,14 +219,15 @@ func (l *LogHandler) Close() error {
|
||||
// Rotate closes and reopens the log file to allow for rotation
|
||||
// by an external source.
|
||||
func (l *LogHandler) Rotate() error {
|
||||
var err error
|
||||
|
||||
if l.file != nil {
|
||||
defer func(f *os.File) {
|
||||
f.Close()
|
||||
}(l.file)
|
||||
if l.config.FilePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@@ -19,7 +19,10 @@ import (
|
||||
)
|
||||
|
||||
// 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
|
||||
type Handler struct {
|
||||
@@ -76,26 +79,27 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
||||
|
||||
catcher := newCodeCatcher(w, h.httpCodeRanges)
|
||||
next.ServeHTTP(catcher, req)
|
||||
if !catcher.isError {
|
||||
if !catcher.isFilteredCode() {
|
||||
return
|
||||
}
|
||||
|
||||
// check the recorder code against the configured http status code ranges
|
||||
code := catcher.getCode()
|
||||
for _, block := range h.httpCodeRanges {
|
||||
if catcher.code >= block[0] && catcher.code <= block[1] {
|
||||
log.Errorf("Caught HTTP Status Code %d, returning error page", catcher.code)
|
||||
if code >= block[0] && code <= block[1] {
|
||||
log.Errorf("Caught HTTP Status Code %d, returning error page", code)
|
||||
|
||||
var query string
|
||||
if len(h.backendQuery) > 0 {
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
w.WriteHeader(catcher.code)
|
||||
fmt.Fprint(w, http.StatusText(catcher.code))
|
||||
w.WriteHeader(code)
|
||||
fmt.Fprint(w, http.StatusText(code))
|
||||
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()))
|
||||
|
||||
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
|
||||
w.WriteHeader(catcher.code)
|
||||
w.WriteHeader(code)
|
||||
w.Write(recorderErrorPage.GetBody().Bytes())
|
||||
return
|
||||
}
|
||||
@@ -127,21 +131,39 @@ func newRequest(baseURL string) (*http.Request, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type responseInterceptor interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
getCode() int
|
||||
isFilteredCode() bool
|
||||
}
|
||||
|
||||
// codeCatcher is a response writer that detects as soon as possible whether the
|
||||
// response is a code within the ranges of codes it watches for. If it is, it
|
||||
// simply drops the data from the response. Otherwise, it forwards it directly to
|
||||
// the original client (its responseWriter) without any buffering.
|
||||
type codeCatcher struct {
|
||||
headerMap http.Header
|
||||
code int
|
||||
httpCodeRanges types.HTTPCodeRanges
|
||||
firstWrite bool
|
||||
isError bool
|
||||
responseWriter http.ResponseWriter
|
||||
err error
|
||||
headerMap http.Header
|
||||
code int
|
||||
httpCodeRanges types.HTTPCodeRanges
|
||||
firstWrite bool
|
||||
caughtFilteredCode bool
|
||||
responseWriter http.ResponseWriter
|
||||
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{
|
||||
headerMap: make(http.Header),
|
||||
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,
|
||||
firstWrite: true,
|
||||
}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &codeCatcherWithCloseNotify{catcher}
|
||||
}
|
||||
return catcher
|
||||
}
|
||||
|
||||
@@ -160,12 +185,19 @@ func (cc *codeCatcher) Header() http.Header {
|
||||
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) {
|
||||
if cc.err != nil {
|
||||
return 0, cc.err
|
||||
}
|
||||
if !cc.firstWrite {
|
||||
if cc.isError {
|
||||
if cc.caughtFilteredCode {
|
||||
// We don't care about the contents of the response,
|
||||
// since we want to serve the ones from the error page,
|
||||
// so we just drop them.
|
||||
@@ -173,25 +205,38 @@ func (cc *codeCatcher) Write(buf []byte) (int, error) {
|
||||
}
|
||||
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
|
||||
if !cc.isError {
|
||||
utils.CopyHeaders(cc.responseWriter.Header(), cc.Header())
|
||||
cc.responseWriter.WriteHeader(cc.code)
|
||||
} else {
|
||||
|
||||
// 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 cc.caughtFilteredCode {
|
||||
return len(buf), nil
|
||||
}
|
||||
return cc.responseWriter.Write(buf)
|
||||
}
|
||||
|
||||
func (cc *codeCatcher) WriteHeader(code int) {
|
||||
if cc.headersSent || cc.caughtFilteredCode {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
@@ -204,6 +249,10 @@ func (cc *codeCatcher) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
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 {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
@@ -46,6 +46,18 @@ func TestHandler(t *testing.T) {
|
||||
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",
|
||||
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) {
|
||||
w.WriteHeader(test.backendCode)
|
||||
if test.backendCode == http.StatusNotModified {
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(w, http.StatusText(test.backendCode))
|
||||
})
|
||||
|
||||
|
1
webui/.gitignore
vendored
1
webui/.gitignore
vendored
@@ -8,6 +8,7 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.quasar
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
|
@@ -25,7 +25,7 @@
|
||||
<a class="navbar-item" [href]="releaseLink" target="_blank">
|
||||
{{ version }} / {{ codename }}
|
||||
</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
|
||||
</a>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user