diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md
index 4649cf5ca..a40e18b63 100644
--- a/docs/content/routing/providers/kubernetes-gateway.md
+++ b/docs/content/routing/providers/kubernetes-gateway.md
@@ -251,39 +251,51 @@ Kubernetes cluster before creating `HTTPRoute` objects.
requestRedirect: # [27]
scheme: https # [28]
statusCode: 301 # [29]
+ - type: RequestHeaderModifier # [30]
+ requestHeaderModifier: # [31]
+ set:
+ - name: X-Foo
+ value: Bar
+ add:
+ - name: X-Bar
+ value: Foo
+ remove:
+ - X-Baz
```
-| Ref | Attribute | Description |
-|------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [1] | `parentRefs` | References the resources (usually Gateways) that a Route wants to be attached to. |
-| [2] | `name` | Name of the referent. |
-| [3] | `namespace` | Namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. |
-| [4] | `sectionName` | Name of a section within the target resource (the Listener name). |
-| [5] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. |
-| [6] | `rules` | A list of HTTP matchers, filters and actions. |
-| [7] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. |
-| [8] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. |
-| [9] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). |
-| [10] | `value` | The value of the HTTP path to match against. |
-| [11] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
-| [12] | `name` | Name of the HTTP header to be matched. |
-| [13] | `value` | Value of HTTP Header to be matched. |
-| [14] | `backendRefs` | Defines the backend(s) where matching requests should be sent. |
-| [15] | `name` | The name of the referent service. |
-| [16] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). |
-| [17] | `port` | The port of the referent service. |
-| [18] | `group` | Group is the group of the referent. Only `traefik.io` and `gateway.networking.k8s.io` values are supported. |
-| [19] | `kind` | Kind is kind of the referent. Only `TraefikService` and `Service` values are supported. |
-| [20] | `filters` | Defines the filters (middlewares) applied to the route. |
-| [21] | `type` | Defines the type of filter; ExtensionRef is used for configuring custom HTTP filters. |
-| [22] | `extensionRef` | Configuration of the custom HTTP filter. |
-| [23] | `group` | Group of the kubernetes object to reference. |
-| [24] | `kind` | Kind of the kubernetes object to reference. |
-| [25] | `name` | Name of the kubernetes object to reference. |
-| [26] | `type` | Defines the type of filter; RequestRedirect redirects a request to another location. |
-| [27] | `requestRedirect` | Configuration of redirect filter. |
-| [28] | `scheme` | Scheme is the scheme to be used in the value of the Location header in the response. |
-| [29] | `statusCode` | StatusCode is the HTTP status code to be used in response. |
+| Ref | Attribute | Description |
+|------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [1] | `parentRefs` | References the resources (usually Gateways) that a Route wants to be attached to. |
+| [2] | `name` | Name of the referent. |
+| [3] | `namespace` | Namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. |
+| [4] | `sectionName` | Name of a section within the target resource (the Listener name). |
+| [5] | `hostnames` | A set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. |
+| [6] | `rules` | A list of HTTP matchers, filters and actions. |
+| [7] | `matches` | Conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. |
+| [8] | `path` | An HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. |
+| [9] | `type` | Type of match against the path Value (supported types: `Exact`, `Prefix`). |
+| [10] | `value` | The value of the HTTP path to match against. |
+| [11] | `headers` | Conditions to select a HTTP route by matching HTTP request headers. |
+| [12] | `name` | Name of the HTTP header to be matched. |
+| [13] | `value` | Value of HTTP Header to be matched. |
+| [14] | `backendRefs` | Defines the backend(s) where matching requests should be sent. |
+| [15] | `name` | The name of the referent service. |
+| [16] | `weight` | The proportion of traffic forwarded to a targetRef, computed as weight/(sum of all weights in targetRefs). |
+| [17] | `port` | The port of the referent service. |
+| [18] | `group` | Group is the group of the referent. Only `traefik.io` and `gateway.networking.k8s.io` values are supported. |
+| [19] | `kind` | Kind is kind of the referent. Only `TraefikService` and `Service` values are supported. |
+| [20] | `filters` | Defines the filters (middlewares) applied to the route. |
+| [21] | `type` | Defines the type of filter; ExtensionRef is used for configuring custom HTTP filters. |
+| [22] | `extensionRef` | Configuration of the custom HTTP filter. |
+| [23] | `group` | Group of the kubernetes object to reference. |
+| [24] | `kind` | Kind of the kubernetes object to reference. |
+| [25] | `name` | Name of the kubernetes object to reference. |
+| [26] | `type` | Defines the type of filter; RequestRedirect redirects a request to another location. |
+| [27] | `requestRedirect` | Configuration of redirect filter. |
+| [28] | `scheme` | Scheme is the scheme to be used in the value of the Location header in the response. |
+| [29] | `statusCode` | StatusCode is the HTTP status code to be used in response. |
+| [30] | `type` | Defines the type of filter; RequestHeaderModifier modifies request headers. |
+| [31] | `requestHeaderModifier` | Configuration of RequestHeaderModifier filter. |
### Kind: `TCPRoute`
diff --git a/internal/gendoc.go b/internal/gendoc.go
index deb7757ee..67eb64003 100644
--- a/internal/gendoc.go
+++ b/internal/gendoc.go
@@ -207,7 +207,13 @@ func clean(element any) {
var svcFieldNames []string
for i := range valueSvcRoot.NumField() {
- svcFieldNames = append(svcFieldNames, valueSvcRoot.Type().Field(i).Name)
+ field := valueSvcRoot.Type().Field(i)
+ // do not create empty node for hidden config.
+ if field.Tag.Get("file") == "-" && field.Tag.Get("kv") == "-" && field.Tag.Get("label") == "-" {
+ continue
+ }
+
+ svcFieldNames = append(svcFieldNames, field.Name)
}
sort.Strings(svcFieldNames)
diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go
index a9be47306..6fbe2a0f5 100644
--- a/pkg/config/dynamic/middlewares.go
+++ b/pkg/config/dynamic/middlewares.go
@@ -39,6 +39,9 @@ type Middleware struct {
GrpcWeb *GrpcWeb `json:"grpcWeb,omitempty" toml:"grpcWeb,omitempty" yaml:"grpcWeb,omitempty" export:"true"`
Plugin map[string]PluginConf `json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty" export:"true"`
+
+ // Gateway API HTTPRoute filters middlewares.
+ RequestHeaderModifier *RequestHeaderModifier `json:"requestHeaderModifier,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
}
// +k8s:deepcopy-gen=true
@@ -673,3 +676,12 @@ type TLSClientCertificateSubjectDNInfo struct {
// Users holds a list of users.
type Users []string
+
+// +k8s:deepcopy-gen=true
+
+// RequestHeaderModifier holds the request header modifier configuration.
+type RequestHeaderModifier struct {
+ Set map[string]string `json:"set,omitempty"`
+ Add map[string]string `json:"add,omitempty"`
+ Remove []string `json:"remove,omitempty"`
+}
diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go
index a362a3f04..a5d1635cf 100644
--- a/pkg/config/dynamic/zz_generated.deepcopy.go
+++ b/pkg/config/dynamic/zz_generated.deepcopy.go
@@ -859,6 +859,11 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
(*out)[key] = *val.DeepCopy()
}
}
+ if in.RequestHeaderModifier != nil {
+ in, out := &in.RequestHeaderModifier, &out.RequestHeaderModifier
+ *out = new(RequestHeaderModifier)
+ (*in).DeepCopyInto(*out)
+ }
return
}
@@ -1067,6 +1072,41 @@ func (in *ReplacePathRegex) DeepCopy() *ReplacePathRegex {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *RequestHeaderModifier) DeepCopyInto(out *RequestHeaderModifier) {
+ *out = *in
+ if in.Set != nil {
+ in, out := &in.Set, &out.Set
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+ if in.Add != nil {
+ in, out := &in.Add, &out.Add
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+ if in.Remove != nil {
+ in, out := &in.Remove, &out.Remove
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RequestHeaderModifier.
+func (in *RequestHeaderModifier) DeepCopy() *RequestHeaderModifier {
+ if in == nil {
+ return nil
+ }
+ out := new(RequestHeaderModifier)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResponseForwarding) DeepCopyInto(out *ResponseForwarding) {
*out = *in
diff --git a/pkg/middlewares/headermodifier/request_header_modifier.go b/pkg/middlewares/headermodifier/request_header_modifier.go
new file mode 100644
index 000000000..3b197c2d5
--- /dev/null
+++ b/pkg/middlewares/headermodifier/request_header_modifier.go
@@ -0,0 +1,56 @@
+package headermodifier
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/traefik/traefik/v3/pkg/config/dynamic"
+ "github.com/traefik/traefik/v3/pkg/middlewares"
+ "go.opentelemetry.io/otel/trace"
+)
+
+const typeName = "RequestHeaderModifier"
+
+// requestHeaderModifier is a middleware used to modify the headers of an HTTP request.
+type requestHeaderModifier struct {
+ next http.Handler
+ name string
+
+ set map[string]string
+ add map[string]string
+ remove []string
+}
+
+// NewRequestHeaderModifier creates a new request header modifier middleware.
+func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dynamic.RequestHeaderModifier, name string) (http.Handler, error) {
+ logger := middlewares.GetLogger(ctx, name, typeName)
+ logger.Debug().Msg("Creating middleware")
+
+ return &requestHeaderModifier{
+ next: next,
+ name: name,
+ set: config.Set,
+ add: config.Add,
+ remove: config.Remove,
+ }, nil
+}
+
+func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
+ return r.name, typeName, trace.SpanKindUnspecified
+}
+
+func (r *requestHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ for headerName, headerValue := range r.set {
+ req.Header.Set(headerName, headerValue)
+ }
+
+ for headerName, headerValue := range r.add {
+ req.Header.Add(headerName, headerValue)
+ }
+
+ for _, headerName := range r.remove {
+ req.Header.Del(headerName)
+ }
+
+ r.next.ServeHTTP(rw, req)
+}
diff --git a/pkg/middlewares/headermodifier/request_header_modifier_test.go b/pkg/middlewares/headermodifier/request_header_modifier_test.go
new file mode 100644
index 000000000..60c446ecc
--- /dev/null
+++ b/pkg/middlewares/headermodifier/request_header_modifier_test.go
@@ -0,0 +1,121 @@
+package headermodifier
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/traefik/traefik/v3/pkg/config/dynamic"
+ "github.com/traefik/traefik/v3/pkg/testhelpers"
+)
+
+func TestRequestHeaderModifier(t *testing.T) {
+ testCases := []struct {
+ desc string
+ config dynamic.RequestHeaderModifier
+ requestHeaders http.Header
+ expectedHeaders http.Header
+ }{
+ {
+ desc: "no config",
+ config: dynamic.RequestHeaderModifier{},
+ expectedHeaders: map[string][]string{},
+ },
+ {
+ desc: "set header",
+ config: dynamic.RequestHeaderModifier{
+ Set: map[string]string{"Foo": "Bar"},
+ },
+ expectedHeaders: map[string][]string{"Foo": {"Bar"}},
+ },
+ {
+ desc: "set header with existing headers",
+ config: dynamic.RequestHeaderModifier{
+ Set: map[string]string{"Foo": "Bar"},
+ },
+ requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
+ expectedHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}},
+ },
+ {
+ desc: "set multiple headers with existing headers",
+ config: dynamic.RequestHeaderModifier{
+ Set: map[string]string{"Foo": "Bar", "Bar": "Foo"},
+ },
+ requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}},
+ expectedHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}},
+ },
+ {
+ desc: "add header",
+ config: dynamic.RequestHeaderModifier{
+ Add: map[string]string{"Foo": "Bar"},
+ },
+ expectedHeaders: map[string][]string{"Foo": {"Bar"}},
+ },
+ {
+ desc: "add header with existing headers",
+ config: dynamic.RequestHeaderModifier{
+ Add: map[string]string{"Foo": "Bar"},
+ },
+ requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
+ expectedHeaders: map[string][]string{"Foo": {"Baz", "Bar"}, "Bar": {"Foo"}},
+ },
+ {
+ desc: "add multiple headers with existing headers",
+ config: dynamic.RequestHeaderModifier{
+ Add: map[string]string{"Foo": "Bar", "Bar": "Foo"},
+ },
+ requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foobar"}},
+ expectedHeaders: map[string][]string{"Foo": {"Baz", "Bar"}, "Bar": {"Foobar", "Foo"}},
+ },
+ {
+ desc: "remove header",
+ config: dynamic.RequestHeaderModifier{
+ Remove: []string{"Foo"},
+ },
+ expectedHeaders: map[string][]string{},
+ },
+ {
+ desc: "remove header with existing headers",
+ config: dynamic.RequestHeaderModifier{
+ Remove: []string{"Foo"},
+ },
+ requestHeaders: map[string][]string{"Foo": {"Baz"}, "Bar": {"Foo"}},
+ expectedHeaders: map[string][]string{"Bar": {"Foo"}},
+ },
+ {
+ desc: "remove multiple headers with existing headers",
+ config: dynamic.RequestHeaderModifier{
+ Remove: []string{"Foo", "Bar"},
+ },
+ requestHeaders: map[string][]string{"Foo": {"Bar"}, "Bar": {"Foo"}, "Baz": {"Bar"}},
+ expectedHeaders: map[string][]string{"Baz": {"Bar"}},
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.desc, func(t *testing.T) {
+ t.Parallel()
+
+ var gotHeaders http.Header
+ next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ gotHeaders = r.Header
+ })
+
+ handler, err := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier")
+ require.NoError(t, err)
+
+ req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
+ if test.requestHeaders != nil {
+ req.Header = test.requestHeaders
+ }
+
+ resp := httptest.NewRecorder()
+ handler.ServeHTTP(resp, req)
+
+ assert.Equal(t, test.expectedHeaders, gotHeaders)
+ })
+ }
+}
diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_request_header_modifier.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_request_header_modifier.yml
new file mode 100644
index 000000000..d5cc3d0b6
--- /dev/null
+++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/filter_request_header_modifier.yml
@@ -0,0 +1,58 @@
+---
+kind: GatewayClass
+apiVersion: gateway.networking.k8s.io/v1
+metadata:
+ name: my-gateway-class
+spec:
+ controllerName: traefik.io/gateway-controller
+
+---
+kind: Gateway
+apiVersion: gateway.networking.k8s.io/v1
+metadata:
+ name: my-gateway
+ namespace: default
+spec:
+ gatewayClassName: my-gateway-class
+ listeners: # Use GatewayClass defaults for listener definition.
+ - name: http
+ protocol: HTTP
+ port: 80
+ allowedRoutes:
+ kinds:
+ - kind: HTTPRoute
+ group: gateway.networking.k8s.io
+ namespaces:
+ from: Same
+
+---
+kind: HTTPRoute
+apiVersion: gateway.networking.k8s.io/v1
+metadata:
+ name: http-app-1
+ namespace: default
+spec:
+ parentRefs:
+ - name: my-gateway
+ kind: Gateway
+ group: gateway.networking.k8s.io
+ hostnames:
+ - "example.org"
+ rules:
+ - backendRefs:
+ - name: whoami
+ port: 80
+ weight: 1
+ kind: Service
+ group: ""
+ filters:
+ - type: RequestHeaderModifier
+ requestHeaderModifier:
+ set:
+ - name: X-Foo
+ value: Bar
+ add:
+ - name: X-Bar
+ value: Foo
+ remove:
+ - X-Baz
diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go
index 01401d750..4c355c3a5 100644
--- a/pkg/provider/kubernetes/gateway/kubernetes.go
+++ b/pkg/provider/kubernetes/gateway/kubernetes.go
@@ -1921,6 +1921,11 @@ func (p *Provider) loadMiddlewares(listener gatev1.Listener, namespace string, p
}
middlewares[name] = middleware
+
+ case gatev1.HTTPRouteFilterRequestHeaderModifier:
+ middlewareName := provider.Normalize(fmt.Sprintf("%s-%s-%d", prefix, strings.ToLower(string(filter.Type)), i))
+ middlewares[middlewareName] = createRequestHeaderModifier(filter.RequestHeaderModifier)
+
default:
// As per the spec:
// https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
@@ -1950,6 +1955,28 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe
return filterFunc(string(extensionRef.Name), namespace)
}
+// createRequestHeaderModifier does not enforce/check the configuration,
+// as the spec indicates that either the webhook or CEL (since v1.0 GA Release) should enforce that.
+func createRequestHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middleware {
+ sets := map[string]string{}
+ for _, header := range filter.Set {
+ sets[string(header.Name)] = header.Value
+ }
+
+ adds := map[string]string{}
+ for _, header := range filter.Add {
+ adds[string(header.Name)] = header.Value
+ }
+
+ return &dynamic.Middleware{
+ RequestHeaderModifier: &dynamic.RequestHeaderModifier{
+ Set: sets,
+ Add: adds,
+ Remove: filter.Remove,
+ },
+ }
+}
+
func createRedirectRegexMiddleware(scheme string, filter *gatev1.HTTPRequestRedirectFilter) (*dynamic.Middleware, error) {
// Use the HTTPRequestRedirectFilter scheme if defined.
filterScheme := scheme
diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go
index f3c6f8c00..64f7c7f96 100644
--- a/pkg/provider/kubernetes/gateway/kubernetes_test.go
+++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go
@@ -1517,6 +1517,75 @@ func TestLoadHTTPRoutes(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
+ {
+ desc: "Simple HTTPRoute, request header modifier",
+ paths: []string{"services.yml", "httproute/filter_request_header_modifier.yml"},
+ entryPoints: map[string]Entrypoint{"web": {
+ Address: ":80",
+ }},
+ expected: &dynamic.Configuration{
+ UDP: &dynamic.UDPConfiguration{
+ Routers: map[string]*dynamic.UDPRouter{},
+ Services: map[string]*dynamic.UDPService{},
+ },
+ TCP: &dynamic.TCPConfiguration{
+ Routers: map[string]*dynamic.TCPRouter{},
+ Middlewares: map[string]*dynamic.TCPMiddleware{},
+ Services: map[string]*dynamic.TCPService{},
+ ServersTransports: map[string]*dynamic.TCPServersTransport{},
+ },
+ HTTP: &dynamic.HTTPConfiguration{
+ Routers: map[string]*dynamic.Router{
+ "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4": {
+ EntryPoints: []string{"web"},
+ Service: "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-wrr",
+ Rule: "Host(`example.org`) && PathPrefix(`/`)",
+ RuleSyntax: "v3",
+ Middlewares: []string{"default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-requestheadermodifier-0"},
+ },
+ },
+ Middlewares: map[string]*dynamic.Middleware{
+ "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-requestheadermodifier-0": {
+ RequestHeaderModifier: &dynamic.RequestHeaderModifier{
+ Set: map[string]string{"X-Foo": "Bar"},
+ Add: map[string]string{"X-Bar": "Foo"},
+ Remove: []string{"X-Baz"},
+ },
+ },
+ },
+ Services: map[string]*dynamic.Service{
+ "default-http-app-1-my-gateway-web-364ce6ec04c3d49b19c4-wrr": {
+ Weighted: &dynamic.WeightedRoundRobin{
+ Services: []dynamic.WRRService{
+ {
+ Name: "default-whoami-80",
+ Weight: func(i int) *int { return &i }(1),
+ },
+ },
+ },
+ },
+ "default-whoami-80": {
+ LoadBalancer: &dynamic.ServersLoadBalancer{
+ Servers: []dynamic.Server{
+ {
+ URL: "http://10.10.0.1:80",
+ },
+ {
+ URL: "http://10.10.0.2:80",
+ },
+ },
+ PassHostHeader: ptr.To(true),
+ ResponseForwarding: &dynamic.ResponseForwarding{
+ FlushInterval: ptypes.Duration(100 * time.Millisecond),
+ },
+ },
+ },
+ },
+ ServersTransports: map[string]*dynamic.ServersTransport{},
+ },
+ TLS: &dynamic.TLSConfiguration{},
+ },
+ },
{
desc: "Simple HTTPRoute, redirect HTTP to HTTPS",
paths: []string{"services.yml", "httproute/filter_http_to_https.yml"},
diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go
index 0955fbcae..2217dc58a 100644
--- a/pkg/server/middleware/middlewares.go
+++ b/pkg/server/middleware/middlewares.go
@@ -21,6 +21,7 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/contenttype"
"github.com/traefik/traefik/v3/pkg/middlewares/customerrors"
"github.com/traefik/traefik/v3/pkg/middlewares/grpcweb"
+ "github.com/traefik/traefik/v3/pkg/middlewares/headermodifier"
"github.com/traefik/traefik/v3/pkg/middlewares/headers"
"github.com/traefik/traefik/v3/pkg/middlewares/inflightreq"
"github.com/traefik/traefik/v3/pkg/middlewares/ipallowlist"
@@ -384,6 +385,16 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
}
}
+ // Gateway API HTTPRoute filters middlewares.
+ if config.RequestHeaderModifier != nil {
+ if middleware != nil {
+ return nil, badConf
+ }
+ middleware = func(next http.Handler) (http.Handler, error) {
+ return headermodifier.NewRequestHeaderModifier(ctx, next, *config.RequestHeaderModifier, middlewareName)
+ }
+ }
+
if middleware == nil {
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
}
diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue
index 4b07ad2d9..4b2c24678 100644
--- a/webui/src/components/_commons/PanelMiddlewares.vue
+++ b/webui/src/components/_commons/PanelMiddlewares.vue
@@ -1491,6 +1491,61 @@
+
+
+
+
+
+
+ Set
+
+
+ {{ key }}: {{ val }}
+
+
+
+
+
+
+
+
+
+ Add
+
+
+ {{ key }}: {{ val }}
+
+
+
+
+
+
+
+
+
+ Remove
+
+
+ {{ name }}
+
+
+
+