Merge v2.1 into master.
This commit is contained in:
commit
09c07f45ee
18 changed files with 386 additions and 82 deletions
|
@ -191,7 +191,7 @@ Use the `HTTP-01` challenge to generate and renew ACME certificates by provision
|
||||||
As described on the Let's Encrypt [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72),
|
As described on the Let's Encrypt [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72),
|
||||||
when using the `HTTP-01` challenge, `certificatesResolvers.myresolver.acme.httpChallenge.entryPoint` must be reachable by Let's Encrypt through port 80.
|
when using the `HTTP-01` challenge, `certificatesResolvers.myresolver.acme.httpChallenge.entryPoint` must be reachable by Let's Encrypt through port 80.
|
||||||
|
|
||||||
??? example "Using an EntryPoint Called http for the `httpChallenge`"
|
??? example "Using an EntryPoint Called web for the `httpChallenge`"
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
|
@ -396,6 +396,13 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
|
||||||
|
|
||||||
### `caServer`
|
### `caServer`
|
||||||
|
|
||||||
|
_Required, Default="https://acme-v02.api.letsencrypt.org/directory"_
|
||||||
|
|
||||||
|
The CA server to use:
|
||||||
|
|
||||||
|
- Let's Encrypt production server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
- Let's Encrypt staging server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
|
||||||
??? example "Using the Let's Encrypt staging server"
|
??? example "Using the Let's Encrypt staging server"
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
@ -422,6 +429,8 @@ As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/stagi
|
||||||
|
|
||||||
### `storage`
|
### `storage`
|
||||||
|
|
||||||
|
_Required, Default="acme.json"_
|
||||||
|
|
||||||
The `storage` option sets the location where your ACME certificates are saved to.
|
The `storage` option sets the location where your ACME certificates are saved to.
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
|
@ -446,13 +455,7 @@ certificatesResolvers:
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
The value can refer to some kinds of storage:
|
ACME certificates are stored in a JSON file that needs to have a `600` file mode.
|
||||||
|
|
||||||
- a JSON file
|
|
||||||
|
|
||||||
#### In a File
|
|
||||||
|
|
||||||
ACME certificates can be stored in a JSON file that needs to have a `600` file mode .
|
|
||||||
|
|
||||||
In Docker you can mount either the JSON file, or the folder containing it:
|
In Docker you can mount either the JSON file, or the folder containing it:
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,10 @@ spec:
|
||||||
port: 8080
|
port: 8080
|
||||||
tls:
|
tls:
|
||||||
certResolver: myresolver
|
certResolver: myresolver
|
||||||
|
domains:
|
||||||
|
- main: company.org
|
||||||
|
sans:
|
||||||
|
- *.company.org
|
||||||
```
|
```
|
||||||
|
|
||||||
```json tab="Marathon"
|
```json tab="Marathon"
|
||||||
|
|
|
@ -491,6 +491,30 @@ providers:
|
||||||
|
|
||||||
Defines the polling interval (in seconds) in Swarm Mode.
|
Defines the polling interval (in seconds) in Swarm Mode.
|
||||||
|
|
||||||
|
### `watch`
|
||||||
|
|
||||||
|
_Optional, Default=true_
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[providers.docker]
|
||||||
|
watch = false
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
providers:
|
||||||
|
docker:
|
||||||
|
watch: false
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--providers.docker.watch=false
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Watch Docker Swarm events.
|
||||||
|
|
||||||
### `constraints`
|
### `constraints`
|
||||||
|
|
||||||
_Optional, Default=""_
|
_Optional, Default=""_
|
||||||
|
|
|
@ -406,7 +406,7 @@ TLS key
|
||||||
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
|
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
|
||||||
|
|
||||||
`--providers.docker.watch`:
|
`--providers.docker.watch`:
|
||||||
Watch provider. (Default: ```true```)
|
Watch Docker Swarm events. (Default: ```true```)
|
||||||
|
|
||||||
`--providers.etcd`:
|
`--providers.etcd`:
|
||||||
Enable Etcd backend with default settings. (Default: ```false```)
|
Enable Etcd backend with default settings. (Default: ```false```)
|
||||||
|
|
|
@ -406,7 +406,7 @@ TLS key
|
||||||
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
|
Use the ip address from the bound port, rather than from the inner network. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_PROVIDERS_DOCKER_WATCH`:
|
`TRAEFIK_PROVIDERS_DOCKER_WATCH`:
|
||||||
Watch provider. (Default: ```true```)
|
Watch Docker Swarm events. (Default: ```true```)
|
||||||
|
|
||||||
`TRAEFIK_PROVIDERS_ETCD`:
|
`TRAEFIK_PROVIDERS_ETCD`:
|
||||||
Enable Etcd backend with default settings. (Default: ```false```)
|
Enable Etcd backend with default settings. (Default: ```false```)
|
||||||
|
|
|
@ -460,6 +460,11 @@ func (in *Headers) DeepCopyInto(out *Headers) {
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.AccessControlAllowOriginList != nil {
|
||||||
|
in, out := &in.AccessControlAllowOriginList, &out.AccessControlAllowOriginList
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
if in.AccessControlExposeHeaders != nil {
|
if in.AccessControlExposeHeaders != nil {
|
||||||
in, out := &in.AccessControlExposeHeaders, &out.AccessControlExposeHeaders
|
in, out := &in.AccessControlExposeHeaders, &out.AccessControlExposeHeaders
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
|
|
|
@ -50,14 +50,14 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry {
|
||||||
if config.AddEntryPointsLabels {
|
if config.AddEntryPointsLabels {
|
||||||
registry.epEnabled = config.AddEntryPointsLabels
|
registry.epEnabled = config.AddEntryPointsLabels
|
||||||
registry.entryPointReqsCounter = datadogClient.NewCounter(ddEntryPointReqsName, 1.0)
|
registry.entryPointReqsCounter = datadogClient.NewCounter(ddEntryPointReqsName, 1.0)
|
||||||
registry.entryPointReqDurationHistogram = datadogClient.NewHistogram(ddEntryPointReqDurationName, 1.0)
|
registry.entryPointReqDurationHistogram, _ = NewHistogramWithScale(datadogClient.NewHistogram(ddEntryPointReqDurationName, 1.0), time.Second)
|
||||||
registry.entryPointOpenConnsGauge = datadogClient.NewGauge(ddEntryPointOpenConnsName)
|
registry.entryPointOpenConnsGauge = datadogClient.NewGauge(ddEntryPointOpenConnsName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddServicesLabels {
|
if config.AddServicesLabels {
|
||||||
registry.svcEnabled = config.AddServicesLabels
|
registry.svcEnabled = config.AddServicesLabels
|
||||||
registry.serviceReqsCounter = datadogClient.NewCounter(ddMetricsServiceReqsName, 1.0)
|
registry.serviceReqsCounter = datadogClient.NewCounter(ddMetricsServiceReqsName, 1.0)
|
||||||
registry.serviceReqDurationHistogram = datadogClient.NewHistogram(ddMetricsServiceLatencyName, 1.0)
|
registry.serviceReqDurationHistogram, _ = NewHistogramWithScale(datadogClient.NewHistogram(ddMetricsServiceLatencyName, 1.0), time.Second)
|
||||||
registry.serviceRetriesCounter = datadogClient.NewCounter(ddRetriesTotalName, 1.0)
|
registry.serviceRetriesCounter = datadogClient.NewCounter(ddRetriesTotalName, 1.0)
|
||||||
registry.serviceOpenConnsGauge = datadogClient.NewGauge(ddOpenConnsName)
|
registry.serviceOpenConnsGauge = datadogClient.NewGauge(ddOpenConnsName)
|
||||||
registry.serviceServerUpGauge = datadogClient.NewGauge(ddServerUpName)
|
registry.serviceServerUpGauge = datadogClient.NewGauge(ddServerUpName)
|
||||||
|
|
|
@ -64,14 +64,14 @@ func RegisterInfluxDB(ctx context.Context, config *types.InfluxDB) Registry {
|
||||||
if config.AddEntryPointsLabels {
|
if config.AddEntryPointsLabels {
|
||||||
registry.epEnabled = config.AddEntryPointsLabels
|
registry.epEnabled = config.AddEntryPointsLabels
|
||||||
registry.entryPointReqsCounter = influxDBClient.NewCounter(influxDBEntryPointReqsName)
|
registry.entryPointReqsCounter = influxDBClient.NewCounter(influxDBEntryPointReqsName)
|
||||||
registry.entryPointReqDurationHistogram = influxDBClient.NewHistogram(influxDBEntryPointReqDurationName)
|
registry.entryPointReqDurationHistogram, _ = NewHistogramWithScale(influxDBClient.NewHistogram(influxDBEntryPointReqDurationName), time.Second)
|
||||||
registry.entryPointOpenConnsGauge = influxDBClient.NewGauge(influxDBEntryPointOpenConnsName)
|
registry.entryPointOpenConnsGauge = influxDBClient.NewGauge(influxDBEntryPointOpenConnsName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddServicesLabels {
|
if config.AddServicesLabels {
|
||||||
registry.svcEnabled = config.AddServicesLabels
|
registry.svcEnabled = config.AddServicesLabels
|
||||||
registry.serviceReqsCounter = influxDBClient.NewCounter(influxDBMetricsServiceReqsName)
|
registry.serviceReqsCounter = influxDBClient.NewCounter(influxDBMetricsServiceReqsName)
|
||||||
registry.serviceReqDurationHistogram = influxDBClient.NewHistogram(influxDBMetricsServiceLatencyName)
|
registry.serviceReqDurationHistogram, _ = NewHistogramWithScale(influxDBClient.NewHistogram(influxDBMetricsServiceLatencyName), time.Second)
|
||||||
registry.serviceRetriesCounter = influxDBClient.NewCounter(influxDBRetriesTotalName)
|
registry.serviceRetriesCounter = influxDBClient.NewCounter(influxDBRetriesTotalName)
|
||||||
registry.serviceOpenConnsGauge = influxDBClient.NewGauge(influxDBOpenConnsName)
|
registry.serviceOpenConnsGauge = influxDBClient.NewGauge(influxDBOpenConnsName)
|
||||||
registry.serviceServerUpGauge = influxDBClient.NewGauge(influxDBServerUpName)
|
registry.serviceServerUpGauge = influxDBClient.NewGauge(influxDBServerUpName)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-kit/kit/metrics"
|
"github.com/go-kit/kit/metrics"
|
||||||
"github.com/go-kit/kit/metrics/multi"
|
"github.com/go-kit/kit/metrics/multi"
|
||||||
)
|
)
|
||||||
|
@ -21,13 +24,13 @@ type Registry interface {
|
||||||
// entry point metrics
|
// entry point metrics
|
||||||
EntryPointReqsCounter() metrics.Counter
|
EntryPointReqsCounter() metrics.Counter
|
||||||
EntryPointReqsTLSCounter() metrics.Counter
|
EntryPointReqsTLSCounter() metrics.Counter
|
||||||
EntryPointReqDurationHistogram() metrics.Histogram
|
EntryPointReqDurationHistogram() ScalableHistogram
|
||||||
EntryPointOpenConnsGauge() metrics.Gauge
|
EntryPointOpenConnsGauge() metrics.Gauge
|
||||||
|
|
||||||
// service metrics
|
// service metrics
|
||||||
ServiceReqsCounter() metrics.Counter
|
ServiceReqsCounter() metrics.Counter
|
||||||
ServiceReqsTLSCounter() metrics.Counter
|
ServiceReqsTLSCounter() metrics.Counter
|
||||||
ServiceReqDurationHistogram() metrics.Histogram
|
ServiceReqDurationHistogram() ScalableHistogram
|
||||||
ServiceOpenConnsGauge() metrics.Gauge
|
ServiceOpenConnsGauge() metrics.Gauge
|
||||||
ServiceRetriesCounter() metrics.Counter
|
ServiceRetriesCounter() metrics.Counter
|
||||||
ServiceServerUpGauge() metrics.Gauge
|
ServiceServerUpGauge() metrics.Gauge
|
||||||
|
@ -49,11 +52,11 @@ func NewMultiRegistry(registries []Registry) Registry {
|
||||||
var lastConfigReloadFailureGauge []metrics.Gauge
|
var lastConfigReloadFailureGauge []metrics.Gauge
|
||||||
var entryPointReqsCounter []metrics.Counter
|
var entryPointReqsCounter []metrics.Counter
|
||||||
var entryPointReqsTLSCounter []metrics.Counter
|
var entryPointReqsTLSCounter []metrics.Counter
|
||||||
var entryPointReqDurationHistogram []metrics.Histogram
|
var entryPointReqDurationHistogram []ScalableHistogram
|
||||||
var entryPointOpenConnsGauge []metrics.Gauge
|
var entryPointOpenConnsGauge []metrics.Gauge
|
||||||
var serviceReqsCounter []metrics.Counter
|
var serviceReqsCounter []metrics.Counter
|
||||||
var serviceReqsTLSCounter []metrics.Counter
|
var serviceReqsTLSCounter []metrics.Counter
|
||||||
var serviceReqDurationHistogram []metrics.Histogram
|
var serviceReqDurationHistogram []ScalableHistogram
|
||||||
var serviceOpenConnsGauge []metrics.Gauge
|
var serviceOpenConnsGauge []metrics.Gauge
|
||||||
var serviceRetriesCounter []metrics.Counter
|
var serviceRetriesCounter []metrics.Counter
|
||||||
var serviceServerUpGauge []metrics.Gauge
|
var serviceServerUpGauge []metrics.Gauge
|
||||||
|
@ -112,11 +115,11 @@ func NewMultiRegistry(registries []Registry) Registry {
|
||||||
lastConfigReloadFailureGauge: multi.NewGauge(lastConfigReloadFailureGauge...),
|
lastConfigReloadFailureGauge: multi.NewGauge(lastConfigReloadFailureGauge...),
|
||||||
entryPointReqsCounter: multi.NewCounter(entryPointReqsCounter...),
|
entryPointReqsCounter: multi.NewCounter(entryPointReqsCounter...),
|
||||||
entryPointReqsTLSCounter: multi.NewCounter(entryPointReqsTLSCounter...),
|
entryPointReqsTLSCounter: multi.NewCounter(entryPointReqsTLSCounter...),
|
||||||
entryPointReqDurationHistogram: multi.NewHistogram(entryPointReqDurationHistogram...),
|
entryPointReqDurationHistogram: NewMultiHistogram(entryPointReqDurationHistogram...),
|
||||||
entryPointOpenConnsGauge: multi.NewGauge(entryPointOpenConnsGauge...),
|
entryPointOpenConnsGauge: multi.NewGauge(entryPointOpenConnsGauge...),
|
||||||
serviceReqsCounter: multi.NewCounter(serviceReqsCounter...),
|
serviceReqsCounter: multi.NewCounter(serviceReqsCounter...),
|
||||||
serviceReqsTLSCounter: multi.NewCounter(serviceReqsTLSCounter...),
|
serviceReqsTLSCounter: multi.NewCounter(serviceReqsTLSCounter...),
|
||||||
serviceReqDurationHistogram: multi.NewHistogram(serviceReqDurationHistogram...),
|
serviceReqDurationHistogram: NewMultiHistogram(serviceReqDurationHistogram...),
|
||||||
serviceOpenConnsGauge: multi.NewGauge(serviceOpenConnsGauge...),
|
serviceOpenConnsGauge: multi.NewGauge(serviceOpenConnsGauge...),
|
||||||
serviceRetriesCounter: multi.NewCounter(serviceRetriesCounter...),
|
serviceRetriesCounter: multi.NewCounter(serviceRetriesCounter...),
|
||||||
serviceServerUpGauge: multi.NewGauge(serviceServerUpGauge...),
|
serviceServerUpGauge: multi.NewGauge(serviceServerUpGauge...),
|
||||||
|
@ -132,11 +135,11 @@ type standardRegistry struct {
|
||||||
lastConfigReloadFailureGauge metrics.Gauge
|
lastConfigReloadFailureGauge metrics.Gauge
|
||||||
entryPointReqsCounter metrics.Counter
|
entryPointReqsCounter metrics.Counter
|
||||||
entryPointReqsTLSCounter metrics.Counter
|
entryPointReqsTLSCounter metrics.Counter
|
||||||
entryPointReqDurationHistogram metrics.Histogram
|
entryPointReqDurationHistogram ScalableHistogram
|
||||||
entryPointOpenConnsGauge metrics.Gauge
|
entryPointOpenConnsGauge metrics.Gauge
|
||||||
serviceReqsCounter metrics.Counter
|
serviceReqsCounter metrics.Counter
|
||||||
serviceReqsTLSCounter metrics.Counter
|
serviceReqsTLSCounter metrics.Counter
|
||||||
serviceReqDurationHistogram metrics.Histogram
|
serviceReqDurationHistogram ScalableHistogram
|
||||||
serviceOpenConnsGauge metrics.Gauge
|
serviceOpenConnsGauge metrics.Gauge
|
||||||
serviceRetriesCounter metrics.Counter
|
serviceRetriesCounter metrics.Counter
|
||||||
serviceServerUpGauge metrics.Gauge
|
serviceServerUpGauge metrics.Gauge
|
||||||
|
@ -174,7 +177,7 @@ func (r *standardRegistry) EntryPointReqsTLSCounter() metrics.Counter {
|
||||||
return r.entryPointReqsTLSCounter
|
return r.entryPointReqsTLSCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *standardRegistry) EntryPointReqDurationHistogram() metrics.Histogram {
|
func (r *standardRegistry) EntryPointReqDurationHistogram() ScalableHistogram {
|
||||||
return r.entryPointReqDurationHistogram
|
return r.entryPointReqDurationHistogram
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +193,7 @@ func (r *standardRegistry) ServiceReqsTLSCounter() metrics.Counter {
|
||||||
return r.serviceReqsTLSCounter
|
return r.serviceReqsTLSCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *standardRegistry) ServiceReqDurationHistogram() metrics.Histogram {
|
func (r *standardRegistry) ServiceReqDurationHistogram() ScalableHistogram {
|
||||||
return r.serviceReqDurationHistogram
|
return r.serviceReqDurationHistogram
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,3 +208,97 @@ func (r *standardRegistry) ServiceRetriesCounter() metrics.Counter {
|
||||||
func (r *standardRegistry) ServiceServerUpGauge() metrics.Gauge {
|
func (r *standardRegistry) ServiceServerUpGauge() metrics.Gauge {
|
||||||
return r.serviceServerUpGauge
|
return r.serviceServerUpGauge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScalableHistogram is a Histogram with a predefined time unit,
|
||||||
|
// used when producing observations without explicitly setting the observed value.
|
||||||
|
type ScalableHistogram interface {
|
||||||
|
With(labelValues ...string) ScalableHistogram
|
||||||
|
StartAt(t time.Time)
|
||||||
|
Observe(v float64)
|
||||||
|
ObserveDuration()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HistogramWithScale is a histogram that will convert its observed value to the specified unit.
|
||||||
|
type HistogramWithScale struct {
|
||||||
|
histogram metrics.Histogram
|
||||||
|
unit time.Duration
|
||||||
|
start time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// With implements ScalableHistogram.
|
||||||
|
func (s *HistogramWithScale) With(labelValues ...string) ScalableHistogram {
|
||||||
|
s.histogram = s.histogram.With(labelValues...)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAt implements ScalableHistogram.
|
||||||
|
func (s *HistogramWithScale) StartAt(t time.Time) {
|
||||||
|
s.start = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObserveDuration implements ScalableHistogram.
|
||||||
|
func (s *HistogramWithScale) ObserveDuration() {
|
||||||
|
if s.unit <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d := float64(time.Since(s.start).Nanoseconds()) / float64(s.unit)
|
||||||
|
if d < 0 {
|
||||||
|
d = 0
|
||||||
|
}
|
||||||
|
s.histogram.Observe(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe implements ScalableHistogram.
|
||||||
|
func (s *HistogramWithScale) Observe(v float64) {
|
||||||
|
s.histogram.Observe(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistogramWithScale returns a ScalableHistogram. It returns an error if the given unit is <= 0.
|
||||||
|
func NewHistogramWithScale(histogram metrics.Histogram, unit time.Duration) (ScalableHistogram, error) {
|
||||||
|
if unit <= 0 {
|
||||||
|
return nil, errors.New("invalid time unit")
|
||||||
|
}
|
||||||
|
return &HistogramWithScale{
|
||||||
|
histogram: histogram,
|
||||||
|
unit: unit,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiHistogram collects multiple individual histograms and treats them as a unit.
|
||||||
|
type MultiHistogram []ScalableHistogram
|
||||||
|
|
||||||
|
// NewMultiHistogram returns a multi-histogram, wrapping the passed histograms.
|
||||||
|
func NewMultiHistogram(h ...ScalableHistogram) MultiHistogram {
|
||||||
|
return MultiHistogram(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAt implements ScalableHistogram.
|
||||||
|
func (h MultiHistogram) StartAt(t time.Time) {
|
||||||
|
for _, histogram := range h {
|
||||||
|
histogram.StartAt(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObserveDuration implements ScalableHistogram.
|
||||||
|
func (h MultiHistogram) ObserveDuration() {
|
||||||
|
for _, histogram := range h {
|
||||||
|
histogram.ObserveDuration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe implements ScalableHistogram.
|
||||||
|
func (h MultiHistogram) Observe(v float64) {
|
||||||
|
for _, histogram := range h {
|
||||||
|
histogram.Observe(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With implements ScalableHistogram.
|
||||||
|
func (h MultiHistogram) With(labelValues ...string) ScalableHistogram {
|
||||||
|
next := make(MultiHistogram, len(h))
|
||||||
|
for i := range h {
|
||||||
|
next[i] = h[i].With(labelValues...)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,44 @@
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-kit/kit/metrics"
|
"github.com/go-kit/kit/metrics"
|
||||||
|
"github.com/go-kit/kit/metrics/generic"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestScalableHistogram(t *testing.T) {
|
||||||
|
h := generic.NewHistogram("test", 1)
|
||||||
|
sh, err := NewHistogramWithScale(h, time.Millisecond)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
<-ticker.C
|
||||||
|
sh.StartAt(time.Now())
|
||||||
|
<-ticker.C
|
||||||
|
sh.ObserveDuration()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
h.Print(&b)
|
||||||
|
|
||||||
|
extractedDurationString := strings.Split(strings.Split(b.String(), "\n")[1], " ")
|
||||||
|
measuredDuration, err := time.ParseDuration(extractedDurationString[0] + "ms")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.InDelta(t, 500*time.Millisecond, measuredDuration, float64(1*time.Millisecond))
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewMultiRegistry(t *testing.T) {
|
func TestNewMultiRegistry(t *testing.T) {
|
||||||
registries := []Registry{newCollectingRetryMetrics(), newCollectingRetryMetrics()}
|
registries := []Registry{newCollectingRetryMetrics(), newCollectingRetryMetrics()}
|
||||||
registry := NewMultiRegistry(registries)
|
registry := NewMultiRegistry(registries)
|
||||||
|
|
||||||
registry.ServiceReqsCounter().With("key", "requests").Add(1)
|
registry.ServiceReqsCounter().With("key", "requests").Add(1)
|
||||||
registry.ServiceReqDurationHistogram().With("key", "durations").Observe(2)
|
registry.ServiceReqDurationHistogram().With("key", "durations").Observe(float64(2))
|
||||||
registry.ServiceRetriesCounter().With("key", "retries").Add(3)
|
registry.ServiceRetriesCounter().With("key", "retries").Add(3)
|
||||||
|
|
||||||
for _, collectingRegistry := range registries {
|
for _, collectingRegistry := range registries {
|
||||||
|
@ -66,11 +92,17 @@ type histogramMock struct {
|
||||||
lastLabelValues []string
|
lastLabelValues []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *histogramMock) With(labelValues ...string) metrics.Histogram {
|
func (c *histogramMock) With(labelValues ...string) ScalableHistogram {
|
||||||
c.lastLabelValues = labelValues
|
c.lastLabelValues = labelValues
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *histogramMock) Observe(value float64) {
|
func (c *histogramMock) Start() {}
|
||||||
c.lastHistogramValue = value
|
|
||||||
|
func (c *histogramMock) StartAt(t time.Time) {}
|
||||||
|
|
||||||
|
func (c *histogramMock) ObserveDuration() {}
|
||||||
|
|
||||||
|
func (c *histogramMock) Observe(v float64) {
|
||||||
|
c.lastHistogramValue = v
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/containous/traefik/v2/pkg/log"
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
|
@ -160,7 +161,7 @@ func initStandardRegistry(config *types.Prometheus) Registry {
|
||||||
}...)
|
}...)
|
||||||
reg.entryPointReqsCounter = entryPointReqs
|
reg.entryPointReqsCounter = entryPointReqs
|
||||||
reg.entryPointReqsTLSCounter = entryPointReqsTLS
|
reg.entryPointReqsTLSCounter = entryPointReqsTLS
|
||||||
reg.entryPointReqDurationHistogram = entryPointReqDurations
|
reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(entryPointReqDurations, time.Second)
|
||||||
reg.entryPointOpenConnsGauge = entryPointOpenConns
|
reg.entryPointOpenConnsGauge = entryPointOpenConns
|
||||||
}
|
}
|
||||||
if config.AddServicesLabels {
|
if config.AddServicesLabels {
|
||||||
|
@ -201,7 +202,7 @@ func initStandardRegistry(config *types.Prometheus) Registry {
|
||||||
|
|
||||||
reg.serviceReqsCounter = serviceReqs
|
reg.serviceReqsCounter = serviceReqs
|
||||||
reg.serviceReqsTLSCounter = serviceReqsTLS
|
reg.serviceReqsTLSCounter = serviceReqsTLS
|
||||||
reg.serviceReqDurationHistogram = serviceReqDurations
|
reg.serviceReqDurationHistogram, _ = NewHistogramWithScale(serviceReqDurations, time.Second)
|
||||||
reg.serviceOpenConnsGauge = serviceOpenConns
|
reg.serviceOpenConnsGauge = serviceOpenConns
|
||||||
reg.serviceRetriesCounter = serviceRetries
|
reg.serviceRetriesCounter = serviceRetries
|
||||||
reg.serviceServerUpGauge = serviceServerUp
|
reg.serviceServerUpGauge = serviceServerUp
|
||||||
|
|
|
@ -55,14 +55,14 @@ func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry {
|
||||||
if config.AddEntryPointsLabels {
|
if config.AddEntryPointsLabels {
|
||||||
registry.epEnabled = config.AddEntryPointsLabels
|
registry.epEnabled = config.AddEntryPointsLabels
|
||||||
registry.entryPointReqsCounter = statsdClient.NewCounter(statsdEntryPointReqsName, 1.0)
|
registry.entryPointReqsCounter = statsdClient.NewCounter(statsdEntryPointReqsName, 1.0)
|
||||||
registry.entryPointReqDurationHistogram = statsdClient.NewTiming(statsdEntryPointReqDurationName, 1.0)
|
registry.entryPointReqDurationHistogram, _ = NewHistogramWithScale(statsdClient.NewTiming(statsdEntryPointReqDurationName, 1.0), time.Millisecond)
|
||||||
registry.entryPointOpenConnsGauge = statsdClient.NewGauge(statsdEntryPointOpenConnsName)
|
registry.entryPointOpenConnsGauge = statsdClient.NewGauge(statsdEntryPointOpenConnsName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddServicesLabels {
|
if config.AddServicesLabels {
|
||||||
registry.svcEnabled = config.AddServicesLabels
|
registry.svcEnabled = config.AddServicesLabels
|
||||||
registry.serviceReqsCounter = statsdClient.NewCounter(statsdMetricsServiceReqsName, 1.0)
|
registry.serviceReqsCounter = statsdClient.NewCounter(statsdMetricsServiceReqsName, 1.0)
|
||||||
registry.serviceReqDurationHistogram = statsdClient.NewTiming(statsdMetricsServiceLatencyName, 1.0)
|
registry.serviceReqDurationHistogram, _ = NewHistogramWithScale(statsdClient.NewTiming(statsdMetricsServiceLatencyName, 1.0), time.Millisecond)
|
||||||
registry.serviceRetriesCounter = statsdClient.NewCounter(statsdRetriesTotalName, 1.0)
|
registry.serviceRetriesCounter = statsdClient.NewCounter(statsdRetriesTotalName, 1.0)
|
||||||
registry.serviceOpenConnsGauge = statsdClient.NewGauge(statsdOpenConnsName)
|
registry.serviceOpenConnsGauge = statsdClient.NewGauge(statsdOpenConnsName)
|
||||||
registry.serviceServerUpGauge = statsdClient.NewGauge(statsdServerUpName)
|
registry.serviceServerUpGauge = statsdClient.NewGauge(statsdServerUpName)
|
||||||
|
|
|
@ -31,7 +31,7 @@ type metricsMiddleware struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
reqsCounter gokitmetrics.Counter
|
reqsCounter gokitmetrics.Counter
|
||||||
reqsTLSCounter gokitmetrics.Counter
|
reqsTLSCounter gokitmetrics.Counter
|
||||||
reqDurationHistogram gokitmetrics.Histogram
|
reqDurationHistogram metrics.ScalableHistogram
|
||||||
openConnsGauge gokitmetrics.Gauge
|
openConnsGauge gokitmetrics.Gauge
|
||||||
baseLabels []string
|
baseLabels []string
|
||||||
}
|
}
|
||||||
|
@ -97,13 +97,17 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||||
|
|
||||||
recorder := newResponseRecorder(rw)
|
recorder := newResponseRecorder(rw)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
m.next.ServeHTTP(recorder, req)
|
m.next.ServeHTTP(recorder, req)
|
||||||
duration := time.Since(start).Seconds()
|
|
||||||
|
|
||||||
labels = append(labels, "code", strconv.Itoa(recorder.getCode()))
|
labels = append(labels, "code", strconv.Itoa(recorder.getCode()))
|
||||||
|
|
||||||
|
histograms := m.reqDurationHistogram.With(labels...)
|
||||||
|
histograms.StartAt(start)
|
||||||
|
|
||||||
m.reqsCounter.With(labels...).Add(1)
|
m.reqsCounter.With(labels...).Add(1)
|
||||||
m.reqDurationHistogram.With(labels...).Observe(duration)
|
|
||||||
|
histograms.ObserveDuration()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRequestProtocol(req *http.Request) string {
|
func getRequestProtocol(req *http.Request) string {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package replacepath
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/containous/traefik/v2/pkg/log"
|
"github.com/containous/traefik/v2/pkg/log"
|
||||||
|
@ -40,8 +41,22 @@ func (r *replacePath) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.URL.RawPath == "" {
|
||||||
req.Header.Add(ReplacedPathHeader, req.URL.Path)
|
req.Header.Add(ReplacedPathHeader, req.URL.Path)
|
||||||
req.URL.Path = r.path
|
} else {
|
||||||
|
req.Header.Add(ReplacedPathHeader, req.URL.RawPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.RawPath = r.path
|
||||||
|
|
||||||
|
var err error
|
||||||
|
req.URL.Path, err = url.PathUnescape(req.URL.RawPath)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(middlewares.GetLoggerCtx(context.Background(), r.name, typeName)).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
req.RequestURI = req.URL.RequestURI()
|
req.RequestURI = req.URL.RequestURI()
|
||||||
|
|
||||||
r.next.ServeHTTP(rw, req)
|
r.next.ServeHTTP(rw, req)
|
||||||
|
|
|
@ -3,43 +3,93 @@ package replacepath
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReplacePath(t *testing.T) {
|
func TestReplacePath(t *testing.T) {
|
||||||
var replacementConfig = dynamic.ReplacePath{
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
path string
|
||||||
|
config dynamic.ReplacePath
|
||||||
|
expectedPath string
|
||||||
|
expectedRawPath string
|
||||||
|
expectedHeader string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "simple path",
|
||||||
|
path: "/example",
|
||||||
|
config: dynamic.ReplacePath{
|
||||||
Path: "/replacement-path",
|
Path: "/replacement-path",
|
||||||
|
},
|
||||||
|
expectedPath: "/replacement-path",
|
||||||
|
expectedRawPath: "",
|
||||||
|
expectedHeader: "/example",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "long path",
|
||||||
|
path: "/some/really/long/path",
|
||||||
|
config: dynamic.ReplacePath{
|
||||||
|
Path: "/replacement-path",
|
||||||
|
},
|
||||||
|
expectedPath: "/replacement-path",
|
||||||
|
expectedRawPath: "",
|
||||||
|
expectedHeader: "/some/really/long/path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "path with escaped value",
|
||||||
|
path: "/foo%2Fbar",
|
||||||
|
config: dynamic.ReplacePath{
|
||||||
|
Path: "/replacement-path",
|
||||||
|
},
|
||||||
|
expectedPath: "/replacement-path",
|
||||||
|
expectedRawPath: "",
|
||||||
|
expectedHeader: "/foo%2Fbar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replacement with escaped value",
|
||||||
|
path: "/path",
|
||||||
|
config: dynamic.ReplacePath{
|
||||||
|
Path: "/foo%2Fbar",
|
||||||
|
},
|
||||||
|
expectedPath: "/foo/bar",
|
||||||
|
expectedRawPath: "/foo%2Fbar",
|
||||||
|
expectedHeader: "/path",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
paths := []string{
|
for _, test := range testCases {
|
||||||
"/example",
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
"/some/really/long/path",
|
var actualPath, actualRawPath, actualHeader, requestURI string
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
t.Run(path, func(t *testing.T) {
|
|
||||||
var expectedPath, actualHeader, requestURI string
|
|
||||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
expectedPath = r.URL.Path
|
actualPath = r.URL.Path
|
||||||
|
actualRawPath = r.URL.RawPath
|
||||||
actualHeader = r.Header.Get(ReplacedPathHeader)
|
actualHeader = r.Header.Get(ReplacedPathHeader)
|
||||||
requestURI = r.RequestURI
|
requestURI = r.RequestURI
|
||||||
})
|
})
|
||||||
|
|
||||||
handler, err := New(context.Background(), next, replacementConfig, "foo-replace-path")
|
handler, err := New(context.Background(), next, test.config, "foo-replace-path")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+path, nil)
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
handler.ServeHTTP(nil, req)
|
resp, err := http.Get(server.URL + test.path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
assert.Equal(t, expectedPath, replacementConfig.Path, "Unexpected path.")
|
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
|
||||||
assert.Equal(t, path, actualHeader, "Unexpected '%s' header.", ReplacedPathHeader)
|
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", ReplacedPathHeader)
|
||||||
assert.Equal(t, expectedPath, requestURI, "Unexpected request URI.")
|
|
||||||
|
if actualRawPath == "" {
|
||||||
|
assert.Equal(t, actualPath, requestURI, "Unexpected request URI.")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, actualRawPath, requestURI, "Unexpected request URI.")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -49,10 +50,31 @@ func (rp *replacePathRegex) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(req.URL.Path) {
|
var currentPath string
|
||||||
req.Header.Add(replacepath.ReplacedPathHeader, req.URL.Path)
|
if req.URL.RawPath == "" {
|
||||||
req.URL.Path = rp.regexp.ReplaceAllString(req.URL.Path, rp.replacement)
|
currentPath = req.URL.Path
|
||||||
|
} else {
|
||||||
|
currentPath = req.URL.RawPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(currentPath) {
|
||||||
|
req.Header.Add(replacepath.ReplacedPathHeader, currentPath)
|
||||||
|
|
||||||
|
req.URL.RawPath = rp.regexp.ReplaceAllString(currentPath, rp.replacement)
|
||||||
|
|
||||||
|
// as replacement can introduce escaped characters
|
||||||
|
// Path must remain an unescaped version of RawPath
|
||||||
|
// Doesn't handle multiple times encoded replacement (`/` => `%2F` => `%252F` => ...)
|
||||||
|
var err error
|
||||||
|
req.URL.Path, err = url.PathUnescape(req.URL.RawPath)
|
||||||
|
if err != nil {
|
||||||
|
log.FromContext(middlewares.GetLoggerCtx(context.Background(), rp.name, typeName)).Error(err)
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
req.RequestURI = req.URL.RequestURI()
|
req.RequestURI = req.URL.RequestURI()
|
||||||
}
|
}
|
||||||
|
|
||||||
rp.next.ServeHTTP(rw, req)
|
rp.next.ServeHTTP(rw, req)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@ package replacepathregex
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/containous/traefik/v2/pkg/middlewares/replacepath"
|
"github.com/containous/traefik/v2/pkg/middlewares/replacepath"
|
||||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -18,6 +18,7 @@ func TestReplacePathRegex(t *testing.T) {
|
||||||
path string
|
path string
|
||||||
config dynamic.ReplacePathRegex
|
config dynamic.ReplacePathRegex
|
||||||
expectedPath string
|
expectedPath string
|
||||||
|
expectedRawPath string
|
||||||
expectedHeader string
|
expectedHeader string
|
||||||
expectsError bool
|
expectsError bool
|
||||||
}{
|
}{
|
||||||
|
@ -29,6 +30,7 @@ func TestReplacePathRegex(t *testing.T) {
|
||||||
Regex: `^/whoami/(.*)`,
|
Regex: `^/whoami/(.*)`,
|
||||||
},
|
},
|
||||||
expectedPath: "/who-am-i/and/whoami",
|
expectedPath: "/who-am-i/and/whoami",
|
||||||
|
expectedRawPath: "/who-am-i/and/whoami",
|
||||||
expectedHeader: "/whoami/and/whoami",
|
expectedHeader: "/whoami/and/whoami",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -39,6 +41,7 @@ func TestReplacePathRegex(t *testing.T) {
|
||||||
Regex: `/whoami`,
|
Regex: `/whoami`,
|
||||||
},
|
},
|
||||||
expectedPath: "/who-am-i/and/who-am-i",
|
expectedPath: "/who-am-i/and/who-am-i",
|
||||||
|
expectedRawPath: "/who-am-i/and/who-am-i",
|
||||||
expectedHeader: "/whoami/and/whoami",
|
expectedHeader: "/whoami/and/whoami",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -58,6 +61,7 @@ func TestReplacePathRegex(t *testing.T) {
|
||||||
Regex: `^(?i)/downloads/([^/]+)/([^/]+)$`,
|
Regex: `^(?i)/downloads/([^/]+)/([^/]+)$`,
|
||||||
},
|
},
|
||||||
expectedPath: "/downloads/src-source.go",
|
expectedPath: "/downloads/src-source.go",
|
||||||
|
expectedRawPath: "/downloads/src-source.go",
|
||||||
expectedHeader: "/downloads/src/source.go",
|
expectedHeader: "/downloads/src/source.go",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -70,13 +74,46 @@ func TestReplacePathRegex(t *testing.T) {
|
||||||
expectedPath: "/invalid/regexp/test",
|
expectedPath: "/invalid/regexp/test",
|
||||||
expectsError: true,
|
expectsError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "replacement with escaped char",
|
||||||
|
path: "/aaa/bbb",
|
||||||
|
config: dynamic.ReplacePathRegex{
|
||||||
|
Replacement: "/foo%2Fbar",
|
||||||
|
Regex: `/aaa/bbb`,
|
||||||
|
},
|
||||||
|
expectedPath: "/foo/bar",
|
||||||
|
expectedRawPath: "/foo%2Fbar",
|
||||||
|
expectedHeader: "/aaa/bbb",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "path and regex with escaped char",
|
||||||
|
path: "/aaa%2Fbbb",
|
||||||
|
config: dynamic.ReplacePathRegex{
|
||||||
|
Replacement: "/foo/bar",
|
||||||
|
Regex: `/aaa%2Fbbb`,
|
||||||
|
},
|
||||||
|
expectedPath: "/foo/bar",
|
||||||
|
expectedRawPath: "/foo/bar",
|
||||||
|
expectedHeader: "/aaa%2Fbbb",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "path with escaped char (no match)",
|
||||||
|
path: "/aaa%2Fbbb",
|
||||||
|
config: dynamic.ReplacePathRegex{
|
||||||
|
Replacement: "/foo/bar",
|
||||||
|
Regex: `/aaa/bbb`,
|
||||||
|
},
|
||||||
|
expectedPath: "/aaa/bbb",
|
||||||
|
expectedRawPath: "/aaa%2Fbbb",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
var actualPath, actualHeader, requestURI string
|
var actualPath, actualRawPath, actualHeader, requestURI string
|
||||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
actualPath = r.URL.Path
|
actualPath = r.URL.Path
|
||||||
|
actualRawPath = r.URL.RawPath
|
||||||
actualHeader = r.Header.Get(replacepath.ReplacedPathHeader)
|
actualHeader = r.Header.Get(replacepath.ReplacedPathHeader)
|
||||||
requestURI = r.RequestURI
|
requestURI = r.RequestURI
|
||||||
})
|
})
|
||||||
|
@ -84,20 +121,30 @@ func TestReplacePathRegex(t *testing.T) {
|
||||||
handler, err := New(context.Background(), next, test.config, "foo-replace-path-regexp")
|
handler, err := New(context.Background(), next, test.config, "foo-replace-path-regexp")
|
||||||
if test.expectsError {
|
if test.expectsError {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil)
|
server := httptest.NewServer(handler)
|
||||||
req.RequestURI = test.path
|
defer server.Close()
|
||||||
|
|
||||||
handler.ServeHTTP(nil, req)
|
resp, err := http.Get(server.URL + test.path)
|
||||||
|
require.NoError(t, err, "Unexpected error while making test request")
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
|
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
|
||||||
|
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
|
||||||
|
|
||||||
|
if actualRawPath == "" {
|
||||||
assert.Equal(t, actualPath, requestURI, "Unexpected request URI.")
|
assert.Equal(t, actualPath, requestURI, "Unexpected request URI.")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, actualRawPath, requestURI, "Unexpected request URI.")
|
||||||
|
}
|
||||||
|
|
||||||
if test.expectedHeader != "" {
|
if test.expectedHeader != "" {
|
||||||
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", replacepath.ReplacedPathHeader)
|
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", replacepath.ReplacedPathHeader)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ var _ provider.Provider = (*Provider)(nil)
|
||||||
// Provider holds configurations of the provider.
|
// Provider holds configurations of the provider.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
|
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
|
||||||
Watch bool `description:"Watch provider." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
|
Watch bool `description:"Watch Docker Swarm events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
|
||||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||||
DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"`
|
DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"`
|
||||||
TLS *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
TLS *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||||
|
|
Loading…
Reference in a new issue