From 8c196523610a0b5a561bbfed03b25d109113c752 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Tue, 12 Nov 2024 17:06:03 +0100 Subject: [PATCH 1/7] Fix absolute link in the migration guide --- docs/content/migration/v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 0e1dd3e60..3e8ac3584 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -655,4 +655,4 @@ Please check out the [entrypoint forwarded headers connection option configurati ### 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. -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. From ef5f1b1508a3a788806f61f99c3253ab86f2d93c Mon Sep 17 00:00:00 2001 From: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:14:04 +0100 Subject: [PATCH 2/7] Improve documentation on dashboard --- docs/content/operations/api.md | 2 +- docs/content/operations/dashboard.md | 55 +++++++++++++++------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/docs/content/operations/api.md b/docs/content/operations/api.md index 7c8248c2e..2829f3ffe 100644 --- a/docs/content/operations/api.md +++ b/docs/content/operations/api.md @@ -70,7 +70,7 @@ And then define a routing configuration on Traefik itself with the ### `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 If the entryPoint named `traefik` is not configured, it will be automatically created on port 8080. diff --git a/docs/content/operations/dashboard.md b/docs/content/operations/dashboard.md index 8c3902cc8..353470cdf 100644 --- a/docs/content/operations/dashboard.md +++ b/docs/content/operations/dashboard.md @@ -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): ```yaml tab="File (YAML)" -api: - # Dashboard - # - # Optional - # Default: true - # - dashboard: true +api: {} ``` ```toml tab="File (TOML)" [api] - # Dashboard - # - # Optional - # Default: true - # - dashboard = true ``` ```bash tab="CLI" -# Dashboard -# -# Optional -# Default: true -# ---api.dashboard=true +--api=true ``` Then define a routing configuration on Traefik itself, @@ -106,27 +89,47 @@ rule = "Host(`traefik.example.com`) && PathPrefix(`/api`, `/dashboard`)" ## 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://: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)" api: - dashboard: true insecure: true ``` ```toml tab="File (TOML)" [api] - dashboard = true insecure = true ``` ```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, -at the following URL: `http://:8080/dashboard/` (trailing slash is mandatory). +## Disable The Dashboard + +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!} From 1c80f12bc289a99d0f4118103f0cabbd946baa00 Mon Sep 17 00:00:00 2001 From: davefu113 <142489013+davefu113@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:56:04 +0800 Subject: [PATCH 3/7] Apply keepalive config to h2c entrypoints --- pkg/server/server_entrypoint_tcp.go | 10 ++--- pkg/server/server_entrypoint_tcp_test.go | 52 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 5cd6cbb49..5d170685f 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -578,17 +578,17 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler = http.AllowQuerySemicolons(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 { handler = h2c.NewHandler(handler, &http2.Server{ 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{ Handler: handler, ErrorLog: httpServerLogger, diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index 4dc9ee428..f3b8865c9 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -3,6 +3,7 @@ package server import ( "bufio" "context" + "crypto/tls" "errors" "io" "net" @@ -17,6 +18,7 @@ import ( "github.com/traefik/traefik/v2/pkg/config/static" tcprouter "github.com/traefik/traefik/v2/pkg/server/router/tcp" "github.com/traefik/traefik/v2/pkg/tcp" + "golang.org/x/net/http2" ) func TestShutdownHijacked(t *testing.T) { @@ -330,3 +332,53 @@ func TestKeepAliveMaxTime(t *testing.T) { err = resp.Body.Close() 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) + 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") +} From 5658c8ac06c8cb6a84c0ccb1a897a215b462b07b Mon Sep 17 00:00:00 2001 From: Antoine <74150795+AntoineDeveloper@users.noreply.github.com> Date: Mon, 18 Nov 2024 05:42:04 -0500 Subject: [PATCH 4/7] Fix spelling, grammar, and rephrase sections for clarity in some documentation pages --- README.md | 2 +- SECURITY.md | 2 +- docs/content/contributing/building-testing.md | 2 +- docs/content/contributing/documentation.md | 6 +++--- docs/content/contributing/maintainers-guidelines.md | 2 +- docs/content/contributing/submitting-pull-requests.md | 4 ++-- docs/content/contributing/submitting-security-issues.md | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 245baf434..a82256542 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,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) - Circuit breakers, retry - 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) - Keeps access logs (JSON, CLF) - Fast diff --git a/SECURITY.md b/SECURITY.md index 7b5c4b953..c9a2670f6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,7 +1,7 @@ # Security Policy 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). diff --git a/docs/content/contributing/building-testing.md b/docs/content/contributing/building-testing.md index 20d05740a..cd71b9482 100644 --- a/docs/content/contributing/building-testing.md +++ b/docs/content/contributing/building-testing.md @@ -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. - 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). Add this section to your tailscale ACLs to auto-approve the routes for the diff --git a/docs/content/contributing/documentation.md b/docs/content/contributing/documentation.md index 3911abe4a..5c61504a0 100644 --- a/docs/content/contributing/documentation.md +++ b/docs/content/contributing/documentation.md @@ -15,13 +15,13 @@ Let's see how. ### General -This [documentation](https://doc.traefik.io/traefik/ "Link to the official Traefik documentation") is built with [MkDocs](https://mkdocs.org/ "Link to website of MkDocs"). +This [documentation](https://doc.traefik.io/traefik/ "Link to the official Traefik documentation") is built with [MkDocs](https://mkdocs.org/ "Link to the website of MkDocs"). ### Method 1: `Docker` and `make` 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: @@ -51,7 +51,7 @@ $ make docs-build 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") ```bash diff --git a/docs/content/contributing/maintainers-guidelines.md b/docs/content/contributing/maintainers-guidelines.md index 7c229917e..4fa13a0f1 100644 --- a/docs/content/contributing/maintainers-guidelines.md +++ b/docs/content/contributing/maintainers-guidelines.md @@ -32,7 +32,7 @@ The contributor should also meet one or several of the following requirements: including those of other maintainers and contributors. - 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. Other maintainers can vote on the issue, and if the quorum is reached, the contributor is promoted to maintainer. diff --git a/docs/content/contributing/submitting-pull-requests.md b/docs/content/contributing/submitting-pull-requests.md index 7488378ed..22508becc 100644 --- a/docs/content/contributing/submitting-pull-requests.md +++ b/docs/content/contributing/submitting-pull-requests.md @@ -17,7 +17,7 @@ or the list of [confirmed bugs](https://github.com/traefik/traefik/labels/kind%2 ## 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: @@ -128,7 +128,7 @@ This label can be used when: 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. 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. If you do not agree with our decision, do not worry; closed pull requests are effortless to recreate, diff --git a/docs/content/contributing/submitting-security-issues.md b/docs/content/contributing/submitting-security-issues.md index 08fbb79a2..981c7fe4c 100644 --- a/docs/content/contributing/submitting-security-issues.md +++ b/docs/content/contributing/submitting-security-issues.md @@ -8,7 +8,7 @@ description: "Security is a key part of Traefik Proxy. Read the technical docume ## Security Advisories 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 From 6baa110adbf4150e60809e64f43d9a2006f5379a Mon Sep 17 00:00:00 2001 From: bluepuma77 Date: Mon, 18 Nov 2024 11:58:04 +0100 Subject: [PATCH 5/7] Update access-logs.md, add examples for accesslog.format --- docs/content/observability/access-logs.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 2a0c83398..52e781319 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -59,6 +59,20 @@ If the given format is unsupported, the default (CLF) is used instead. - [] " " "" "" "" "" ms ``` +```yaml tab="File (YAML)" +accessLog: + format: "json" +``` + +```toml tab="File (TOML)" +[accessLog] + format = "json" +``` + +```bash tab="CLI" +--accesslog.format=json +``` + ### `bufferingSize` To write the logs in an asynchronous fashion, specify a `bufferingSize` option. From 8ffd1854db4631db9d8e26b49e1a534ddd912fe3 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Mon, 18 Nov 2024 14:40:05 +0100 Subject: [PATCH 6/7] Fix the defaultRule CLI examples --- docs/content/providers/consul-catalog.md | 2 +- docs/content/providers/docker.md | 2 +- docs/content/providers/ecs.md | 2 +- docs/content/providers/marathon.md | 2 +- docs/content/providers/nomad.md | 2 +- docs/content/providers/rancher.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/providers/consul-catalog.md b/docs/content/providers/consul-catalog.md index 9ea44de94..b27b6cb2a 100644 --- a/docs/content/providers/consul-catalog.md +++ b/docs/content/providers/consul-catalog.md @@ -525,7 +525,7 @@ providers: ``` ```bash tab="CLI" ---providers.consulcatalog.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) +--providers.consulcatalog.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)' # ... ``` diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 2a11a2e7c..c1cadd86b 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -466,7 +466,7 @@ providers: ``` ```bash tab="CLI" ---providers.docker.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) +--providers.docker.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)' # ... ``` diff --git a/docs/content/providers/ecs.md b/docs/content/providers/ecs.md index 80a11a35e..ba5e41088 100644 --- a/docs/content/providers/ecs.md +++ b/docs/content/providers/ecs.md @@ -259,7 +259,7 @@ providers: ``` ```bash tab="CLI" ---providers.ecs.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) +--providers.ecs.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)' # ... ``` diff --git a/docs/content/providers/marathon.md b/docs/content/providers/marathon.md index 0b5c2794b..d8ca6aa21 100644 --- a/docs/content/providers/marathon.md +++ b/docs/content/providers/marathon.md @@ -138,7 +138,7 @@ providers: ``` ```bash tab="CLI" ---providers.marathon.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) +--providers.marathon.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)' # ... ``` diff --git a/docs/content/providers/nomad.md b/docs/content/providers/nomad.md index 5c565b055..b9c01526c 100644 --- a/docs/content/providers/nomad.md +++ b/docs/content/providers/nomad.md @@ -374,7 +374,7 @@ providers: ``` ```bash tab="CLI" ---providers.nomad.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" +--providers.nomad.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)' # ... ``` diff --git a/docs/content/providers/rancher.md b/docs/content/providers/rancher.md index f43b70b1f..f58c508fd 100644 --- a/docs/content/providers/rancher.md +++ b/docs/content/providers/rancher.md @@ -121,7 +121,7 @@ providers: ``` ```bash tab="CLI" ---providers.rancher.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`) +--providers.rancher.defaultRule='Host(`{{ .Name }}.{{ index .Labels "customLabel"}}`)' # ... ``` From cc80568d9e60d04b97318c9b36e909d6355dc11d Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Tue, 19 Nov 2024 14:52:04 +0100 Subject: [PATCH 7/7] Fix internal handlers ServiceBuilder composition --- pkg/server/service/internalhandler.go | 23 +++++------- pkg/server/service/managerfactory.go | 7 ++-- pkg/server/service/service.go | 25 +++++++++++-- pkg/server/service/service_test.go | 52 ++++++++++++++++++++++++--- 4 files changed, 80 insertions(+), 27 deletions(-) diff --git a/pkg/server/service/internalhandler.go b/pkg/server/service/internalhandler.go index 4bd6fd258..4ec26c930 100644 --- a/pkg/server/service/internalhandler.go +++ b/pkg/server/service/internalhandler.go @@ -8,11 +8,6 @@ import ( "strings" ) -type serviceManager interface { - BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) - LaunchHealthCheck() -} - // InternalHandlers is the internal HTTP handlers builder. type InternalHandlers struct { api http.Handler @@ -21,26 +16,24 @@ type InternalHandlers struct { prometheus http.Handler ping http.Handler acmeHTTP http.Handler - serviceManager } // 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{ - api: apiHandler, - dashboard: dashboard, - rest: rest, - prometheus: metricsHandler, - ping: pingHandler, - acmeHTTP: acmeHTTP, - serviceManager: next, + api: apiHandler, + dashboard: dashboard, + rest: rest, + prometheus: metricsHandler, + ping: pingHandler, + acmeHTTP: acmeHTTP, } } // BuildHTTP builds an HTTP handler. func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) { if !strings.HasSuffix(serviceName, "@internal") { - return m.serviceManager.BuildHTTP(rootCtx, serviceName) + return nil, nil } internalHandler, err := m.get(serviceName) diff --git a/pkg/server/service/managerfactory.go b/pkg/server/service/managerfactory.go index 5db06ef40..07ba4e25d 100644 --- a/pkg/server/service/managerfactory.go +++ b/pkg/server/service/managerfactory.go @@ -71,13 +71,12 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s } // Build creates a service manager. -func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers { - svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager) - +func (f *ManagerFactory) Build(configuration *runtime.Configuration) *Manager { var apiHandler http.Handler if f.api != nil { 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.metricsRegistry, f.routinesPool, f.roundTripperManager, internalHandlers) } diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index cc43e76a2..f30770b62 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -34,22 +34,28 @@ import ( const ( defaultHealthCheckInterval = 30 * time.Second defaultHealthCheckTimeout = 5 * time.Second -) -const defaultMaxBodySize int64 = -1 + defaultMaxBodySize int64 = -1 +) // RoundTripperGetter is a roundtripper getter interface. type RoundTripperGetter interface { Get(name string) (http.RoundTripper, error) } +// ServiceBuilder is a Service builder. +type ServiceBuilder interface { + BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) +} + // NewManager creates a new Manager. -func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager { +func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter, serviceBuilders ...ServiceBuilder) *Manager { return &Manager{ routinePool: routinePool, metricsRegistry: metricsRegistry, bufferPool: newBufferPool(), roundTripperManager: roundTripperManager, + serviceBuilders: serviceBuilders, balancers: make(map[string]healthcheck.Balancers), configs: configs, rand: rand.New(rand.NewSource(time.Now().UnixNano())), @@ -62,6 +68,8 @@ type Manager struct { metricsRegistry metrics.Registry bufferPool httputil.BufferPool roundTripperManager RoundTripperGetter + serviceBuilders []ServiceBuilder + // balancers is the map of all Balancers, keyed by service name. // There is one Balancer per service handler, and there is one service handler per reference to a service // (e.g. if 2 routers refer to the same service name, 2 service handlers are created), @@ -78,6 +86,17 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H serviceName = provider.GetQualifiedName(ctx, serviceName) ctx = provider.AddInContext(ctx, serviceName) + // 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 { + return handler, nil + } + } + conf, ok := m.configs[serviceName] if !ok { return nil, fmt.Errorf("the service %q does not exist", serviceName) diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 8b6b305a9..7e73de2d2 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -18,9 +18,9 @@ import ( "github.com/traefik/traefik/v2/pkg/testhelpers" ) -type MockForwarder struct{} +type mockForwarder struct{} -func (MockForwarder) ServeHTTP(http.ResponseWriter, *http.Request) { +func (mockForwarder) ServeHTTP(http.ResponseWriter, *http.Request) { panic("implement me") } @@ -44,14 +44,14 @@ func TestGetLoadBalancer(t *testing.T) { }, }, }, - fwd: &MockForwarder{}, + fwd: &mockForwarder{}, expectError: true, }, { desc: "Succeeds when there are no servers", serviceName: "test", service: &dynamic.ServersLoadBalancer{}, - fwd: &MockForwarder{}, + fwd: &mockForwarder{}, expectError: false, }, { @@ -60,7 +60,7 @@ func TestGetLoadBalancer(t *testing.T) { service: &dynamic.ServersLoadBalancer{ Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{}}, }, - fwd: &MockForwarder{}, + fwd: &mockForwarder{}, expectError: false, }, } @@ -476,6 +476,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, &RoundTripperManager{ + roundTrippers: map[string]http.RoundTripper{ + "default@internal": http.DefaultTransport, + }, + }, 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) { testCases := []struct { desc string