Merge branch v2.11 into v3.2
This commit is contained in:
commit
ca5b70e196
22 changed files with 190 additions and 68 deletions
|
@ -62,7 +62,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
|
||||||
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
|
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
|
||||||
- Circuit breakers, retry
|
- Circuit breakers, retry
|
||||||
- See the magic through its clean web UI
|
- See the magic through its clean web UI
|
||||||
- Websocket, HTTP/2, gRPC ready
|
- WebSocket, HTTP/2, gRPC ready
|
||||||
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB 2.X)
|
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB 2.X)
|
||||||
- Keeps access logs (JSON, CLF)
|
- Keeps access logs (JSON, CLF)
|
||||||
- Fast
|
- Fast
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
You can join our security mailing list to be aware of the latest announcements from our security team.
|
You can join our security mailing list to be aware of the latest announcements from our security team.
|
||||||
You can subscribe sending a mail to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
You can subscribe by sending an email to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
||||||
|
|
||||||
Reported vulnerabilities can be found on [cve.mitre.org](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=traefik).
|
Reported vulnerabilities can be found on [cve.mitre.org](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=traefik).
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ For development purposes, you can specify which tests to run by using (only work
|
||||||
|
|
||||||
Create `tailscale.secret` file in `integration` directory.
|
Create `tailscale.secret` file in `integration` directory.
|
||||||
|
|
||||||
This file need to contains a [Tailscale auth key](https://tailscale.com/kb/1085/auth-keys)
|
This file needs to contain a [Tailscale auth key](https://tailscale.com/kb/1085/auth-keys)
|
||||||
(an ephemeral, but reusable, one is recommended).
|
(an ephemeral, but reusable, one is recommended).
|
||||||
|
|
||||||
Add this section to your tailscale ACLs to auto-approve the routes for the
|
Add this section to your tailscale ACLs to auto-approve the routes for the
|
||||||
|
|
|
@ -15,13 +15,13 @@ Let's see how.
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
||||||
This [documentation](../../ "Link to the official Traefik documentation") is built with [MkDocs](https://mkdocs.org/ "Link to website of MkDocs").
|
This [documentation](../../ "Link to the official Traefik documentation") is built with [MkDocs](https://mkdocs.org/ "Link to the website of MkDocs").
|
||||||
|
|
||||||
### Method 1: `Docker` and `make`
|
### Method 1: `Docker` and `make`
|
||||||
|
|
||||||
Please make sure you have the following requirements installed:
|
Please make sure you have the following requirements installed:
|
||||||
|
|
||||||
- [Docker](https://www.docker.com/ "Link to website of Docker")
|
- [Docker](https://www.docker.com/ "Link to the website of Docker")
|
||||||
|
|
||||||
You can build the documentation and test it locally (with live reloading), using the `docs-serve` target:
|
You can build the documentation and test it locally (with live reloading), using the `docs-serve` target:
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ $ make docs-build
|
||||||
|
|
||||||
Please make sure you have the following requirements installed:
|
Please make sure you have the following requirements installed:
|
||||||
|
|
||||||
- [Python](https://www.python.org/ "Link to website of Python")
|
- [Python](https://www.python.org/ "Link to the website of Python")
|
||||||
- [pip](https://pypi.org/project/pip/ "Link to the website of pip on PyPI")
|
- [pip](https://pypi.org/project/pip/ "Link to the website of pip on PyPI")
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -32,7 +32,7 @@ The contributor should also meet one or several of the following requirements:
|
||||||
including those of other maintainers and contributors.
|
including those of other maintainers and contributors.
|
||||||
|
|
||||||
- The contributor is active on Traefik Community forums
|
- The contributor is active on Traefik Community forums
|
||||||
or other technical forums/boards such as K8S slack, Reddit, StackOverflow, hacker news.
|
or other technical forums/boards, such as K8S Slack, Reddit, StackOverflow, and Hacker News.
|
||||||
|
|
||||||
Any existing active maintainer can create an issue to discuss promoting a contributor to maintainer.
|
Any existing active maintainer can create an issue to discuss promoting a contributor to maintainer.
|
||||||
Other maintainers can vote on the issue, and if the quorum is reached, the contributor is promoted to maintainer.
|
Other maintainers can vote on the issue, and if the quorum is reached, the contributor is promoted to maintainer.
|
||||||
|
|
|
@ -17,7 +17,7 @@ or the list of [confirmed bugs](https://github.com/traefik/traefik/labels/kind%2
|
||||||
|
|
||||||
## How We Prioritize
|
## How We Prioritize
|
||||||
|
|
||||||
We wish we could review every pull request right away, but because it's a time consuming operation, it's not always possible.
|
We wish we could review every pull request right away, but because it's a time-consuming operation, it's not always possible.
|
||||||
|
|
||||||
The PRs we are able to handle the fastest are:
|
The PRs we are able to handle the fastest are:
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ This label can be used when:
|
||||||
Traefik Proxy is made by the community for the community,
|
Traefik Proxy is made by the community for the community,
|
||||||
as such the goal is to engage the community to make Traefik the best reverse proxy available.
|
as such the goal is to engage the community to make Traefik the best reverse proxy available.
|
||||||
Part of this goal is maintaining a lean codebase and ensuring code velocity.
|
Part of this goal is maintaining a lean codebase and ensuring code velocity.
|
||||||
unfortunately, this means that sometimes we will not be able to merge a pull request.
|
Unfortunately, this means that sometimes we will not be able to merge a pull request.
|
||||||
|
|
||||||
Because we respect the work you did, you will always be told why we are closing your pull request.
|
Because we respect the work you did, you will always be told why we are closing your pull request.
|
||||||
If you do not agree with our decision, do not worry; closed pull requests are effortless to recreate,
|
If you do not agree with our decision, do not worry; closed pull requests are effortless to recreate,
|
||||||
|
|
|
@ -8,7 +8,7 @@ description: "Security is a key part of Traefik Proxy. Read the technical docume
|
||||||
## Security Advisories
|
## Security Advisories
|
||||||
|
|
||||||
We strongly advise you to join our mailing list to be aware of the latest announcements from our security team.
|
We strongly advise you to join our mailing list to be aware of the latest announcements from our security team.
|
||||||
You can subscribe sending a mail to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
You can subscribe by sending an email to security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
|
||||||
|
|
||||||
## CVE
|
## CVE
|
||||||
|
|
||||||
|
|
|
@ -658,4 +658,4 @@ Please check out the [entrypoint forwarded headers connection option configurati
|
||||||
### X-Forwarded-Prefix
|
### X-Forwarded-Prefix
|
||||||
|
|
||||||
In `v2.11.14`, the `X-Forwarded-Prefix` header is now handled like the other `X-Forwarded-*` headers: Traefik removes it when it's sent from an untrusted source.
|
In `v2.11.14`, the `X-Forwarded-Prefix` header is now handled like the other `X-Forwarded-*` headers: Traefik removes it when it's sent from an untrusted source.
|
||||||
Please refer to the Forwarded headers [documentation](https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers) for more details.
|
Please refer to the Forwarded headers [documentation](../routing/entrypoints.md#forwarded-headers) for more details.
|
||||||
|
|
|
@ -79,6 +79,20 @@ If the given format is unsupported, the default (CLF) is used instead.
|
||||||
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <HTTP_status> <content-length> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_router_name>" "<Traefik_server_URL>" <request_duration_in_ms>ms
|
<remote_IP_address> - <client_user_name_if_available> [<timestamp>] "<request_method> <request_path> <request_protocol>" <HTTP_status> <content-length> "<request_referrer>" "<request_user_agent>" <number_of_requests_received_since_Traefik_started> "<Traefik_router_name>" "<Traefik_server_URL>" <request_duration_in_ms>ms
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
accessLog:
|
||||||
|
format: "json"
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[accessLog]
|
||||||
|
format = "json"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--accesslog.format=json
|
||||||
|
```
|
||||||
|
|
||||||
### `bufferingSize`
|
### `bufferingSize`
|
||||||
|
|
||||||
To write the logs in an asynchronous fashion, specify a `bufferingSize` option.
|
To write the logs in an asynchronous fashion, specify a `bufferingSize` option.
|
||||||
|
|
|
@ -70,7 +70,7 @@ And then define a routing configuration on Traefik itself with the
|
||||||
|
|
||||||
### `insecure`
|
### `insecure`
|
||||||
|
|
||||||
Enable the API in `insecure` mode, which means that the API will be available directly on the entryPoint named `traefik`.
|
Enable the API in `insecure` mode, which means that the API will be available directly on the entryPoint named `traefik`, on path `/api`.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
If the entryPoint named `traefik` is not configured, it will be automatically created on port 8080.
|
If the entryPoint named `traefik` is not configured, it will be automatically created on port 8080.
|
||||||
|
|
|
@ -37,32 +37,15 @@ Start by enabling the dashboard by using the following option from [Traefik's AP
|
||||||
on the [static configuration](../getting-started/configuration-overview.md#the-static-configuration):
|
on the [static configuration](../getting-started/configuration-overview.md#the-static-configuration):
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
api:
|
api: {}
|
||||||
# Dashboard
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
# Default: true
|
|
||||||
#
|
|
||||||
dashboard: true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[api]
|
[api]
|
||||||
# Dashboard
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
# Default: true
|
|
||||||
#
|
|
||||||
dashboard = true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# Dashboard
|
--api=true
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
# Default: true
|
|
||||||
#
|
|
||||||
--api.dashboard=true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then define a routing configuration on Traefik itself,
|
Then define a routing configuration on Traefik itself,
|
||||||
|
@ -106,27 +89,47 @@ rule = "Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashb
|
||||||
|
|
||||||
## Insecure Mode
|
## Insecure Mode
|
||||||
|
|
||||||
This mode is not recommended because it does not allow the use of security features.
|
When _insecure_ mode is enabled, one can access the dashboard on the `traefik` port (default: `8080`) of the Traefik instance,
|
||||||
|
at the following URL: `http://<Traefik IP>:8080/dashboard/` (trailing slash is mandatory).
|
||||||
|
|
||||||
To enable the "insecure mode", use the following options from [Traefik's API](./api.md#insecure):
|
This mode is **not** recommended because it does not allow security features.
|
||||||
|
For example, it is not possible to add an authentication middleware with this mode.
|
||||||
|
|
||||||
|
It should be used for testing purpose **only**.
|
||||||
|
|
||||||
|
To enable the _insecure_ mode, use the following options from [Traefik's API](./api.md#insecure):
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
api:
|
api:
|
||||||
dashboard: true
|
|
||||||
insecure: true
|
insecure: true
|
||||||
```
|
```
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
[api]
|
[api]
|
||||||
dashboard = true
|
|
||||||
insecure = true
|
insecure = true
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--api.dashboard=true --api.insecure=true
|
--api.insecure=true
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now access the dashboard on the port `8080` of the Traefik instance,
|
## Disable The Dashboard
|
||||||
at the following URL: `http://<Traefik IP>:8080/dashboard/` (trailing slash is mandatory).
|
|
||||||
|
By default, the dashboard is enabled when the API is enabled.
|
||||||
|
If necessary, the dashboard can be disabled by using the following option.
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
api:
|
||||||
|
dashboard: false
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[api]
|
||||||
|
dashboard = false
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--api.dashboard=false
|
||||||
|
```
|
||||||
|
|
||||||
{!traefik-for-business-applications.md!}
|
{!traefik-for-business-applications.md!}
|
||||||
|
|
|
@ -525,7 +525,7 @@ providers:
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.consulcatalog.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
--providers.consulcatalog.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -455,7 +455,7 @@ providers:
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
--providers.docker.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -283,7 +283,7 @@ providers:
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.ecs.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
--providers.ecs.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -432,7 +432,7 @@ providers:
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.nomad.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)"
|
--providers.nomad.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -503,7 +503,7 @@ providers:
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--providers.swarm.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
|
--providers.swarm.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)'
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -625,17 +625,17 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
||||||
|
|
||||||
handler = contenttype.DisableAutoDetection(handler)
|
handler = contenttype.DisableAutoDetection(handler)
|
||||||
|
|
||||||
|
debugConnection := os.Getenv(debugConnectionEnv) != ""
|
||||||
|
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
|
||||||
|
handler = newKeepAliveMiddleware(handler, configuration.Transport.KeepAliveMaxRequests, configuration.Transport.KeepAliveMaxTime)
|
||||||
|
}
|
||||||
|
|
||||||
if withH2c {
|
if withH2c {
|
||||||
handler = h2c.NewHandler(handler, &http2.Server{
|
handler = h2c.NewHandler(handler, &http2.Server{
|
||||||
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
|
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
debugConnection := os.Getenv(debugConnectionEnv) != ""
|
|
||||||
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
|
|
||||||
handler = newKeepAliveMiddleware(handler, configuration.Transport.KeepAliveMaxRequests, configuration.Transport.KeepAliveMaxTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
serverHTTP := &http.Server{
|
serverHTTP := &http.Server{
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0),
|
ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0),
|
||||||
|
|
|
@ -3,6 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||||
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
|
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
|
||||||
"github.com/traefik/traefik/v3/pkg/tcp"
|
"github.com/traefik/traefik/v3/pkg/tcp"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShutdownHijacked(t *testing.T) {
|
func TestShutdownHijacked(t *testing.T) {
|
||||||
|
@ -330,3 +332,53 @@ func TestKeepAliveMaxTime(t *testing.T) {
|
||||||
err = resp.Body.Close()
|
err = resp.Body.Close()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeepAliveH2c(t *testing.T) {
|
||||||
|
epConfig := &static.EntryPointsTransport{}
|
||||||
|
epConfig.SetDefaults()
|
||||||
|
epConfig.KeepAliveMaxRequests = 1
|
||||||
|
|
||||||
|
entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{
|
||||||
|
Address: ":0",
|
||||||
|
Transport: epConfig,
|
||||||
|
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||||
|
HTTP2: &static.HTTP2Config{},
|
||||||
|
}, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
router, err := tcprouter.NewRouter()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
conn, err := startEntrypoint(entryPoint, router)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
http2Transport := &http2.Transport{
|
||||||
|
AllowHTTP: true,
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: http2Transport}
|
||||||
|
|
||||||
|
resp, err := client.Get("http://" + entryPoint.listener.Addr().String())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, resp.Close)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = client.Get("http://" + entryPoint.listener.Addr().String())
|
||||||
|
require.Error(t, err)
|
||||||
|
// Unlike HTTP/1, where we can directly check `resp.Close`, HTTP/2 uses a different
|
||||||
|
// mechanism: it sends a GOAWAY frame when the connection is closing.
|
||||||
|
// We can only check the error type. The error received should be poll.ErrClosed from
|
||||||
|
// the `internal/poll` package, but we cannot directly reference the error type due to
|
||||||
|
// package restrictions. Since this error message ("use of closed network connection")
|
||||||
|
// is distinct and specific, we rely on its consistency, assuming it is stable and unlikely
|
||||||
|
// to change.
|
||||||
|
require.Contains(t, err.Error(), "use of closed network connection")
|
||||||
|
}
|
||||||
|
|
|
@ -8,11 +8,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serviceManager interface {
|
|
||||||
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
|
||||||
LaunchHealthCheck(ctx context.Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalHandlers is the internal HTTP handlers builder.
|
// InternalHandlers is the internal HTTP handlers builder.
|
||||||
type InternalHandlers struct {
|
type InternalHandlers struct {
|
||||||
api http.Handler
|
api http.Handler
|
||||||
|
@ -21,11 +16,10 @@ type InternalHandlers struct {
|
||||||
prometheus http.Handler
|
prometheus http.Handler
|
||||||
ping http.Handler
|
ping http.Handler
|
||||||
acmeHTTP http.Handler
|
acmeHTTP http.Handler
|
||||||
serviceManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInternalHandlers creates a new InternalHandlers.
|
// NewInternalHandlers creates a new InternalHandlers.
|
||||||
func NewInternalHandlers(next serviceManager, apiHandler, rest, metricsHandler, pingHandler, dashboard, acmeHTTP http.Handler) *InternalHandlers {
|
func NewInternalHandlers(apiHandler, rest, metricsHandler, pingHandler, dashboard, acmeHTTP http.Handler) *InternalHandlers {
|
||||||
return &InternalHandlers{
|
return &InternalHandlers{
|
||||||
api: apiHandler,
|
api: apiHandler,
|
||||||
dashboard: dashboard,
|
dashboard: dashboard,
|
||||||
|
@ -33,14 +27,13 @@ func NewInternalHandlers(next serviceManager, apiHandler, rest, metricsHandler,
|
||||||
prometheus: metricsHandler,
|
prometheus: metricsHandler,
|
||||||
ping: pingHandler,
|
ping: pingHandler,
|
||||||
acmeHTTP: acmeHTTP,
|
acmeHTTP: acmeHTTP,
|
||||||
serviceManager: next,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildHTTP builds an HTTP handler.
|
// BuildHTTP builds an HTTP handler.
|
||||||
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
||||||
if !strings.HasSuffix(serviceName, "@internal") {
|
if !strings.HasSuffix(serviceName, "@internal") {
|
||||||
return m.serviceManager.BuildHTTP(rootCtx, serviceName)
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
internalHandler, err := m.get(serviceName)
|
internalHandler, err := m.get(serviceName)
|
||||||
|
|
|
@ -74,13 +74,12 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build creates a service manager.
|
// Build creates a service manager.
|
||||||
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers {
|
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *Manager {
|
||||||
svcManager := NewManager(configuration.Services, f.observabilityMgr, f.routinesPool, f.transportManager, f.proxyBuilder)
|
|
||||||
|
|
||||||
var apiHandler http.Handler
|
var apiHandler http.Handler
|
||||||
if f.api != nil {
|
if f.api != nil {
|
||||||
apiHandler = f.api(configuration)
|
apiHandler = f.api(configuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewInternalHandlers(svcManager, apiHandler, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, f.acmeHTTPHandler)
|
internalHandlers := NewInternalHandlers(apiHandler, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, f.acmeHTTPHandler)
|
||||||
|
return NewManager(configuration.Services, f.observabilityMgr, f.routinesPool, f.transportManager, f.proxyBuilder, internalHandlers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,18 @@ type ProxyBuilder interface {
|
||||||
Update(configs map[string]*dynamic.ServersTransport)
|
Update(configs map[string]*dynamic.ServersTransport)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceBuilder is a Service builder.
|
||||||
|
type ServiceBuilder interface {
|
||||||
|
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Manager The service manager.
|
// Manager The service manager.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
routinePool *safe.Pool
|
routinePool *safe.Pool
|
||||||
observabilityMgr *middleware.ObservabilityMgr
|
observabilityMgr *middleware.ObservabilityMgr
|
||||||
transportManager httputil.TransportManager
|
transportManager httputil.TransportManager
|
||||||
proxyBuilder ProxyBuilder
|
proxyBuilder ProxyBuilder
|
||||||
|
serviceBuilders []ServiceBuilder
|
||||||
|
|
||||||
services map[string]http.Handler
|
services map[string]http.Handler
|
||||||
configs map[string]*runtime.ServiceInfo
|
configs map[string]*runtime.ServiceInfo
|
||||||
|
@ -60,12 +66,13 @@ type Manager struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new Manager.
|
// NewManager creates a new Manager.
|
||||||
func NewManager(configs map[string]*runtime.ServiceInfo, observabilityMgr *middleware.ObservabilityMgr, routinePool *safe.Pool, transportManager httputil.TransportManager, proxyBuilder ProxyBuilder) *Manager {
|
func NewManager(configs map[string]*runtime.ServiceInfo, observabilityMgr *middleware.ObservabilityMgr, routinePool *safe.Pool, transportManager httputil.TransportManager, proxyBuilder ProxyBuilder, serviceBuilders ...ServiceBuilder) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
routinePool: routinePool,
|
routinePool: routinePool,
|
||||||
observabilityMgr: observabilityMgr,
|
observabilityMgr: observabilityMgr,
|
||||||
transportManager: transportManager,
|
transportManager: transportManager,
|
||||||
proxyBuilder: proxyBuilder,
|
proxyBuilder: proxyBuilder,
|
||||||
|
serviceBuilders: serviceBuilders,
|
||||||
services: make(map[string]http.Handler),
|
services: make(map[string]http.Handler),
|
||||||
configs: configs,
|
configs: configs,
|
||||||
healthCheckers: make(map[string]*healthcheck.ServiceHealthChecker),
|
healthCheckers: make(map[string]*healthcheck.ServiceHealthChecker),
|
||||||
|
@ -85,6 +92,18 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
|
||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be before we get configs to handle services without config.
|
||||||
|
for _, builder := range m.serviceBuilders {
|
||||||
|
handler, err := builder.BuildHTTP(rootCtx, serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if handler != nil {
|
||||||
|
m.services[serviceName] = handler
|
||||||
|
return handler, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
conf, ok := m.configs[serviceName]
|
conf, ok := m.configs[serviceName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("the service %q does not exist", serviceName)
|
return nil, fmt.Errorf("the service %q does not exist", serviceName)
|
||||||
|
|
|
@ -450,6 +450,48 @@ func Test1xxResponses(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serviceBuilderFunc func(ctx context.Context, serviceName string) (http.Handler, error)
|
||||||
|
|
||||||
|
func (s serviceBuilderFunc) BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) {
|
||||||
|
return s(ctx, serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
type internalHandler struct{}
|
||||||
|
|
||||||
|
func (internalHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {}
|
||||||
|
|
||||||
|
func TestManager_ServiceBuilders(t *testing.T) {
|
||||||
|
var internalHandler internalHandler
|
||||||
|
|
||||||
|
manager := NewManager(map[string]*runtime.ServiceInfo{
|
||||||
|
"test@test": {
|
||||||
|
Service: &dynamic.Service{
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil, nil, &TransportManager{
|
||||||
|
roundTrippers: map[string]http.RoundTripper{
|
||||||
|
"default@internal": http.DefaultTransport,
|
||||||
|
},
|
||||||
|
}, nil, serviceBuilderFunc(func(rootCtx context.Context, serviceName string) (http.Handler, error) {
|
||||||
|
if strings.HasSuffix(serviceName, "@internal") {
|
||||||
|
return internalHandler, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
h, err := manager.BuildHTTP(context.Background(), "test@internal")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, internalHandler, h)
|
||||||
|
|
||||||
|
h, err = manager.BuildHTTP(context.Background(), "test@test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, h)
|
||||||
|
|
||||||
|
_, err = manager.BuildHTTP(context.Background(), "wrong@test")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestManager_Build(t *testing.T) {
|
func TestManager_Build(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
Loading…
Reference in a new issue