Add HTTPUrlRewrite Filter in Gateway API
This commit is contained in:
parent
3ca667a3d4
commit
a696f7c654
15 changed files with 754 additions and 110 deletions
|
@ -219,8 +219,6 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
|
||||||
SkipTests: []string{
|
SkipTests: []string{
|
||||||
tests.HTTPRouteMethodMatching.ShortName,
|
tests.HTTPRouteMethodMatching.ShortName,
|
||||||
tests.HTTPRouteQueryParamMatching.ShortName,
|
tests.HTTPRouteQueryParamMatching.ShortName,
|
||||||
tests.HTTPRouteRewriteHost.ShortName,
|
|
||||||
tests.HTTPRouteRewritePath.ShortName,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ type Middleware struct {
|
||||||
// Gateway API HTTPRoute filters middlewares.
|
// Gateway API HTTPRoute filters middlewares.
|
||||||
RequestHeaderModifier *RequestHeaderModifier `json:"requestHeaderModifier,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
RequestHeaderModifier *RequestHeaderModifier `json:"requestHeaderModifier,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||||
RequestRedirect *RequestRedirect `json:"requestRedirect,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
RequestRedirect *RequestRedirect `json:"requestRedirect,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||||
|
URLRewrite *URLRewrite `json:"URLRewrite,omitempty" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
@ -703,3 +704,12 @@ type RequestRedirect struct {
|
||||||
PathPrefix *string `json:"pathPrefix,omitempty"`
|
PathPrefix *string `json:"pathPrefix,omitempty"`
|
||||||
StatusCode int `json:"statusCode,omitempty"`
|
StatusCode int `json:"statusCode,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
|
// URLRewrite holds the URL rewrite middleware configuration.
|
||||||
|
type URLRewrite struct {
|
||||||
|
Hostname *string `json:"hostname,omitempty"`
|
||||||
|
Path *string `json:"path,omitempty"`
|
||||||
|
PathPrefix *string `json:"pathPrefix,omitempty"`
|
||||||
|
}
|
||||||
|
|
|
@ -869,6 +869,11 @@ func (in *Middleware) DeepCopyInto(out *Middleware) {
|
||||||
*out = new(RequestRedirect)
|
*out = new(RequestRedirect)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.URLRewrite != nil {
|
||||||
|
in, out := &in.URLRewrite, &out.URLRewrite
|
||||||
|
*out = new(URLRewrite)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2205,6 +2210,37 @@ func (in *UDPWeightedRoundRobin) DeepCopy() *UDPWeightedRoundRobin {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *URLRewrite) DeepCopyInto(out *URLRewrite) {
|
||||||
|
*out = *in
|
||||||
|
if in.Hostname != nil {
|
||||||
|
in, out := &in.Hostname, &out.Hostname
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Path != nil {
|
||||||
|
in, out := &in.Path, &out.Path
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PathPrefix != nil {
|
||||||
|
in, out := &in.PathPrefix, &out.PathPrefix
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new URLRewrite.
|
||||||
|
func (in *URLRewrite) DeepCopy() *URLRewrite {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(URLRewrite)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in Users) DeepCopyInto(out *Users) {
|
func (in Users) DeepCopyInto(out *Users) {
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,7 @@ type requestHeaderModifier struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequestHeaderModifier creates a new request header modifier middleware.
|
// NewRequestHeaderModifier creates a new request header modifier middleware.
|
||||||
func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dynamic.RequestHeaderModifier, name string) (http.Handler, error) {
|
func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dynamic.RequestHeaderModifier, name string) http.Handler {
|
||||||
logger := middlewares.GetLogger(ctx, name, typeName)
|
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||||
logger.Debug().Msg("Creating middleware")
|
logger.Debug().Msg("Creating middleware")
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dyn
|
||||||
set: config.Set,
|
set: config.Set,
|
||||||
add: config.Add,
|
add: config.Add,
|
||||||
remove: config.Remove,
|
remove: config.Remove,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
|
func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||||
)
|
)
|
||||||
|
@ -104,8 +103,7 @@ func TestRequestHeaderModifier(t *testing.T) {
|
||||||
gotHeaders = r.Header
|
gotHeaders = r.Header
|
||||||
})
|
})
|
||||||
|
|
||||||
handler, err := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier")
|
handler := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier")
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||||
if test.requestHeaders != nil {
|
if test.requestHeaders != nil {
|
||||||
|
|
|
@ -18,8 +18,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type redirect struct {
|
type redirect struct {
|
||||||
name string
|
name string
|
||||||
next http.Handler
|
next http.Handler
|
||||||
|
|
||||||
scheme *string
|
scheme *string
|
||||||
hostname *string
|
hostname *string
|
||||||
port *string
|
port *string
|
||||||
|
|
|
@ -2,7 +2,6 @@ package redirect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -15,15 +14,12 @@ import (
|
||||||
|
|
||||||
func TestRequestRedirectHandler(t *testing.T) {
|
func TestRequestRedirectHandler(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
config dynamic.RequestRedirect
|
config dynamic.RequestRedirect
|
||||||
method string
|
url string
|
||||||
url string
|
wantURL string
|
||||||
headers map[string]string
|
wantStatus int
|
||||||
secured bool
|
wantErr bool
|
||||||
expectedURL string
|
|
||||||
expectedStatus int
|
|
||||||
errorExpected bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "wrong status code",
|
desc: "wrong status code",
|
||||||
|
@ -31,44 +27,44 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Path: ptr.To("/baz"),
|
Path: ptr.To("/baz"),
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar",
|
url: "http://foo.com:80/foo/bar",
|
||||||
errorExpected: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "replace path",
|
desc: "replace path",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Path: ptr.To("/baz"),
|
Path: ptr.To("/baz"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar",
|
url: "http://foo.com:80/foo/bar",
|
||||||
expectedURL: "http://foo.com:80/baz",
|
wantURL: "http://foo.com:80/baz",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "replace path without trailing slash",
|
desc: "replace path without trailing slash",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Path: ptr.To("/baz"),
|
Path: ptr.To("/baz"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar/",
|
url: "http://foo.com:80/foo/bar/",
|
||||||
expectedURL: "http://foo.com:80/baz",
|
wantURL: "http://foo.com:80/baz",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "replace path with trailing slash",
|
desc: "replace path with trailing slash",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Path: ptr.To("/baz/"),
|
Path: ptr.To("/baz/"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar",
|
url: "http://foo.com:80/foo/bar",
|
||||||
expectedURL: "http://foo.com:80/baz/",
|
wantURL: "http://foo.com:80/baz/",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "only hostname",
|
desc: "only hostname",
|
||||||
config: dynamic.RequestRedirect{
|
config: dynamic.RequestRedirect{
|
||||||
Hostname: ptr.To("bar.com"),
|
Hostname: ptr.To("bar.com"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:8080/foo/",
|
url: "http://foo.com:8080/foo/",
|
||||||
expectedURL: "http://bar.com:8080/foo/",
|
wantURL: "http://bar.com:8080/foo/",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "replace prefix path",
|
desc: "replace prefix path",
|
||||||
|
@ -76,9 +72,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Path: ptr.To("/baz"),
|
Path: ptr.To("/baz"),
|
||||||
PathPrefix: ptr.To("/foo"),
|
PathPrefix: ptr.To("/foo"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar",
|
url: "http://foo.com:80/foo/bar",
|
||||||
expectedURL: "http://foo.com:80/baz/bar",
|
wantURL: "http://foo.com:80/baz/bar",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "replace prefix path with trailing slash",
|
desc: "replace prefix path with trailing slash",
|
||||||
|
@ -86,9 +82,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Path: ptr.To("/baz"),
|
Path: ptr.To("/baz"),
|
||||||
PathPrefix: ptr.To("/foo"),
|
PathPrefix: ptr.To("/foo"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar/",
|
url: "http://foo.com:80/foo/bar/",
|
||||||
expectedURL: "http://foo.com:80/baz/bar/",
|
wantURL: "http://foo.com:80/baz/bar/",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "replace prefix path without slash prefix",
|
desc: "replace prefix path without slash prefix",
|
||||||
|
@ -96,9 +92,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Path: ptr.To("baz"),
|
Path: ptr.To("baz"),
|
||||||
PathPrefix: ptr.To("/foo"),
|
PathPrefix: ptr.To("/foo"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar",
|
url: "http://foo.com:80/foo/bar",
|
||||||
expectedURL: "http://foo.com:80/baz/bar",
|
wantURL: "http://foo.com:80/baz/bar",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "replace prefix path without slash prefix",
|
desc: "replace prefix path without slash prefix",
|
||||||
|
@ -106,9 +102,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Path: ptr.To("/baz"),
|
Path: ptr.To("/baz"),
|
||||||
PathPrefix: ptr.To("/foo/"),
|
PathPrefix: ptr.To("/foo/"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80/foo/bar",
|
url: "http://foo.com:80/foo/bar",
|
||||||
expectedURL: "http://foo.com:80/baz/bar",
|
wantURL: "http://foo.com:80/baz/bar",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "simple redirection",
|
desc: "simple redirection",
|
||||||
|
@ -117,9 +113,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Hostname: ptr.To("foobar.com"),
|
Hostname: ptr.To("foobar.com"),
|
||||||
Port: ptr.To("443"),
|
Port: ptr.To("443"),
|
||||||
},
|
},
|
||||||
url: "http://foo.com:80",
|
url: "http://foo.com:80",
|
||||||
expectedURL: "https://foobar.com:443",
|
wantURL: "https://foobar.com:443",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS permanent",
|
desc: "HTTP to HTTPS permanent",
|
||||||
|
@ -127,9 +123,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Scheme: ptr.To("https"),
|
Scheme: ptr.To("https"),
|
||||||
StatusCode: http.StatusMovedPermanently,
|
StatusCode: http.StatusMovedPermanently,
|
||||||
},
|
},
|
||||||
url: "http://foo",
|
url: "http://foo",
|
||||||
expectedURL: "https://foo",
|
wantURL: "https://foo",
|
||||||
expectedStatus: http.StatusMovedPermanently,
|
wantStatus: http.StatusMovedPermanently,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTPS to HTTP permanent",
|
desc: "HTTPS to HTTP permanent",
|
||||||
|
@ -137,10 +133,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Scheme: ptr.To("http"),
|
Scheme: ptr.To("http"),
|
||||||
StatusCode: http.StatusMovedPermanently,
|
StatusCode: http.StatusMovedPermanently,
|
||||||
},
|
},
|
||||||
secured: true,
|
url: "https://foo",
|
||||||
url: "https://foo",
|
wantURL: "http://foo",
|
||||||
expectedURL: "http://foo",
|
wantStatus: http.StatusMovedPermanently,
|
||||||
expectedStatus: http.StatusMovedPermanently,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS",
|
desc: "HTTP to HTTPS",
|
||||||
|
@ -148,9 +143,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Scheme: ptr.To("https"),
|
Scheme: ptr.To("https"),
|
||||||
Port: ptr.To("443"),
|
Port: ptr.To("443"),
|
||||||
},
|
},
|
||||||
url: "http://foo:80",
|
url: "http://foo:80",
|
||||||
expectedURL: "https://foo:443",
|
wantURL: "https://foo:443",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
desc: "HTTP to HTTPS, with X-Forwarded-Proto",
|
||||||
|
@ -158,12 +153,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Scheme: ptr.To("https"),
|
Scheme: ptr.To("https"),
|
||||||
Port: ptr.To("443"),
|
Port: ptr.To("443"),
|
||||||
},
|
},
|
||||||
url: "http://foo:80",
|
url: "http://foo:80",
|
||||||
headers: map[string]string{
|
wantURL: "https://foo:443",
|
||||||
"X-Forwarded-Proto": "https",
|
wantStatus: http.StatusFound,
|
||||||
},
|
|
||||||
expectedURL: "https://foo:443",
|
|
||||||
expectedStatus: http.StatusFound,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTPS to HTTP",
|
desc: "HTTPS to HTTP",
|
||||||
|
@ -171,10 +163,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Scheme: ptr.To("http"),
|
Scheme: ptr.To("http"),
|
||||||
Port: ptr.To("80"),
|
Port: ptr.To("80"),
|
||||||
},
|
},
|
||||||
secured: true,
|
url: "https://foo:443",
|
||||||
url: "https://foo:443",
|
wantURL: "http://foo:80",
|
||||||
expectedURL: "http://foo:80",
|
wantStatus: http.StatusFound,
|
||||||
expectedStatus: http.StatusFound,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP to HTTP",
|
desc: "HTTP to HTTP",
|
||||||
|
@ -182,9 +173,9 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
Scheme: ptr.To("http"),
|
Scheme: ptr.To("http"),
|
||||||
Port: ptr.To("88"),
|
Port: ptr.To("88"),
|
||||||
},
|
},
|
||||||
url: "http://foo:80",
|
url: "http://foo:80",
|
||||||
expectedURL: "http://foo:88",
|
wantURL: "http://foo:88",
|
||||||
expectedStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,45 +184,32 @@ func TestRequestRedirectHandler(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||||
handler, err := NewRequestRedirect(context.Background(), next, test.config, "traefikTest")
|
|
||||||
|
|
||||||
if test.errorExpected {
|
handler, err := NewRequestRedirect(context.Background(), next, test.config, "traefikTest")
|
||||||
|
if test.wantErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, handler)
|
require.Nil(t, handler)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, handler)
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, test.url, nil)
|
||||||
|
|
||||||
|
handler.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
assert.Equal(t, test.wantStatus, recorder.Code)
|
||||||
|
switch test.wantStatus {
|
||||||
|
case http.StatusMovedPermanently, http.StatusFound:
|
||||||
|
location, err := recorder.Result().Location()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, handler)
|
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
assert.Equal(t, test.wantURL, location.String())
|
||||||
|
default:
|
||||||
method := http.MethodGet
|
location, err := recorder.Result().Location()
|
||||||
if test.method != "" {
|
require.Errorf(t, err, "Location %v", location)
|
||||||
method = test.method
|
|
||||||
}
|
|
||||||
|
|
||||||
req := httptest.NewRequest(method, test.url, nil)
|
|
||||||
if test.secured {
|
|
||||||
req.TLS = &tls.ConnectionState{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range test.headers {
|
|
||||||
req.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("X-Foo", "bar")
|
|
||||||
handler.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
assert.Equal(t, test.expectedStatus, recorder.Code)
|
|
||||||
switch test.expectedStatus {
|
|
||||||
case http.StatusMovedPermanently, http.StatusFound:
|
|
||||||
location, err := recorder.Result().Location()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, test.expectedURL, location.String())
|
|
||||||
default:
|
|
||||||
location, err := recorder.Result().Location()
|
|
||||||
require.Errorf(t, err, "Location %v", location)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
68
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go
Normal file
68
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package urlrewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeName = "URLRewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type urlRewrite struct {
|
||||||
|
name string
|
||||||
|
next http.Handler
|
||||||
|
|
||||||
|
hostname *string
|
||||||
|
path *string
|
||||||
|
pathPrefix *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewURLRewrite creates a URL rewrite middleware.
|
||||||
|
func NewURLRewrite(ctx context.Context, next http.Handler, conf dynamic.URLRewrite, name string) http.Handler {
|
||||||
|
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||||
|
logger.Debug().Msg("Creating middleware")
|
||||||
|
|
||||||
|
return urlRewrite{
|
||||||
|
name: name,
|
||||||
|
next: next,
|
||||||
|
hostname: conf.Hostname,
|
||||||
|
path: conf.Path,
|
||||||
|
pathPrefix: conf.PathPrefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u urlRewrite) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||||
|
return u.name, typeName, trace.SpanKindInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u urlRewrite) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
newPath := req.URL.Path
|
||||||
|
if u.path != nil && u.pathPrefix == nil {
|
||||||
|
newPath = *u.path
|
||||||
|
}
|
||||||
|
if u.path != nil && u.pathPrefix != nil {
|
||||||
|
newPath = path.Join(*u.path, strings.TrimPrefix(req.URL.Path, *u.pathPrefix))
|
||||||
|
|
||||||
|
// add the trailing slash if needed, as path.Join removes trailing slashes.
|
||||||
|
if strings.HasSuffix(req.URL.Path, "/") && !strings.HasSuffix(newPath, "/") {
|
||||||
|
newPath += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.Path = newPath
|
||||||
|
req.URL.RawPath = req.URL.EscapedPath()
|
||||||
|
req.RequestURI = req.URL.RequestURI()
|
||||||
|
|
||||||
|
if u.hostname != nil {
|
||||||
|
req.Host = *u.hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
u.next.ServeHTTP(rw, req)
|
||||||
|
}
|
126
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go
Normal file
126
pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package urlrewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLRewriteHandler(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
config dynamic.URLRewrite
|
||||||
|
url string
|
||||||
|
wantURL string
|
||||||
|
wantHost string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "replace path",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/bar",
|
||||||
|
wantURL: "http://foo.com/baz",
|
||||||
|
wantHost: "foo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace path without trailing slash",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/bar/",
|
||||||
|
wantURL: "http://foo.com/baz",
|
||||||
|
wantHost: "foo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace path with trailing slash",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("/baz/"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/bar",
|
||||||
|
wantURL: "http://foo.com/baz/",
|
||||||
|
wantHost: "foo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "only host",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Hostname: ptr.To("bar.com"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/",
|
||||||
|
wantURL: "http://foo.com/foo/",
|
||||||
|
wantHost: "bar.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "host and path",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Hostname: ptr.To("bar.com"),
|
||||||
|
Path: ptr.To("/baz/"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/",
|
||||||
|
wantURL: "http://foo.com/baz/",
|
||||||
|
wantHost: "bar.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
PathPrefix: ptr.To("/foo"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/bar",
|
||||||
|
wantURL: "http://foo.com/baz/bar",
|
||||||
|
wantHost: "foo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path with trailing slash",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
PathPrefix: ptr.To("/foo"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/bar/",
|
||||||
|
wantURL: "http://foo.com/baz/bar/",
|
||||||
|
wantHost: "foo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path without slash prefix",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("baz"),
|
||||||
|
PathPrefix: ptr.To("/foo"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/bar",
|
||||||
|
wantURL: "http://foo.com/baz/bar",
|
||||||
|
wantHost: "foo.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "replace prefix path without slash prefix",
|
||||||
|
config: dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("/baz"),
|
||||||
|
PathPrefix: ptr.To("/foo/"),
|
||||||
|
},
|
||||||
|
url: "http://foo.com/foo/bar",
|
||||||
|
wantURL: "http://foo.com/baz/bar",
|
||||||
|
wantHost: "foo.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||||
|
|
||||||
|
handler := NewURLRewrite(context.Background(), next, test.config, "traefikTest")
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, test.url, nil)
|
||||||
|
handler.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
assert.Equal(t, test.wantURL, req.URL.String())
|
||||||
|
assert.Equal(t, test.wantHost, req.Host)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /foo
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
||||||
|
filters:
|
||||||
|
- type: URLRewrite
|
||||||
|
urlRewrite:
|
||||||
|
hostname: www.foo.bar
|
||||||
|
path:
|
||||||
|
type: ReplacePrefixMatch
|
||||||
|
replacePrefixMatch: /xyz
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
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.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /foo
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
||||||
|
filters:
|
||||||
|
- type: URLRewrite
|
||||||
|
urlRewrite:
|
||||||
|
path:
|
||||||
|
type: ReplaceFullPath
|
||||||
|
replaceFullPath: /bar
|
|
@ -0,0 +1,55 @@
|
||||||
|
---
|
||||||
|
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.com"
|
||||||
|
rules:
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /foo
|
||||||
|
backendRefs:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
weight: 1
|
||||||
|
kind: Service
|
||||||
|
group: ""
|
||||||
|
filters:
|
||||||
|
- type: URLRewrite
|
||||||
|
urlRewrite:
|
||||||
|
hostname: www.foo.bar
|
|
@ -301,15 +301,19 @@ func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBa
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, routerName string, filters []gatev1.HTTPRouteFilter, pathMatch *gatev1.HTTPPathMatch) ([]string, error) {
|
func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, routerName string, filters []gatev1.HTTPRouteFilter, pathMatch *gatev1.HTTPPathMatch) ([]string, error) {
|
||||||
|
pm := ptr.Deref(pathMatch, gatev1.HTTPPathMatch{
|
||||||
|
Type: ptr.To(gatev1.PathMatchPathPrefix),
|
||||||
|
Value: ptr.To("/"),
|
||||||
|
})
|
||||||
|
|
||||||
middlewares := make(map[string]*dynamic.Middleware)
|
middlewares := make(map[string]*dynamic.Middleware)
|
||||||
for i, filter := range filters {
|
for i, filter := range filters {
|
||||||
|
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
||||||
switch filter.Type {
|
switch filter.Type {
|
||||||
case gatev1.HTTPRouteFilterRequestRedirect:
|
case gatev1.HTTPRouteFilterRequestRedirect:
|
||||||
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
middlewares[name] = createRequestRedirect(filter.RequestRedirect, pm)
|
||||||
middlewares[name] = createRequestRedirect(filter.RequestRedirect, pathMatch)
|
|
||||||
|
|
||||||
case gatev1.HTTPRouteFilterRequestHeaderModifier:
|
case gatev1.HTTPRouteFilterRequestHeaderModifier:
|
||||||
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
|
|
||||||
middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier)
|
||||||
|
|
||||||
case gatev1.HTTPRouteFilterExtensionRef:
|
case gatev1.HTTPRouteFilterExtensionRef:
|
||||||
|
@ -320,6 +324,14 @@ func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, route
|
||||||
|
|
||||||
middlewares[name] = middleware
|
middlewares[name] = middleware
|
||||||
|
|
||||||
|
case gatev1.HTTPRouteFilterURLRewrite:
|
||||||
|
var err error
|
||||||
|
middleware, err := createURLRewrite(filter.URLRewrite, pm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid filter %s: %w", filter.Type, err)
|
||||||
|
}
|
||||||
|
middlewares[name] = middleware
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// As per the spec: https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
|
// As per the spec: https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
|
||||||
// In all cases where incompatible or unsupported filters are
|
// In all cases where incompatible or unsupported filters are
|
||||||
|
@ -560,7 +572,7 @@ func createRequestHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch *gatev1.HTTPPathMatch) *dynamic.Middleware {
|
func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch gatev1.HTTPPathMatch) *dynamic.Middleware {
|
||||||
var hostname *string
|
var hostname *string
|
||||||
if filter.Hostname != nil {
|
if filter.Hostname != nil {
|
||||||
hostname = ptr.To(string(*filter.Hostname))
|
hostname = ptr.To(string(*filter.Hostname))
|
||||||
|
@ -599,6 +611,37 @@ func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createURLRewrite(filter *gatev1.HTTPURLRewriteFilter, pathMatch gatev1.HTTPPathMatch) (*dynamic.Middleware, error) {
|
||||||
|
if filter.Path == nil && filter.Hostname == nil {
|
||||||
|
return nil, errors.New("empty configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
var host *string
|
||||||
|
if filter.Hostname != nil {
|
||||||
|
host = ptr.To(string(*filter.Hostname))
|
||||||
|
}
|
||||||
|
|
||||||
|
var path *string
|
||||||
|
var pathPrefix *string
|
||||||
|
if filter.Path != nil {
|
||||||
|
switch filter.Path.Type {
|
||||||
|
case gatev1.FullPathHTTPPathModifier:
|
||||||
|
path = filter.Path.ReplaceFullPath
|
||||||
|
case gatev1.PrefixMatchHTTPPathModifier:
|
||||||
|
path = filter.Path.ReplacePrefixMatch
|
||||||
|
pathPrefix = pathMatch.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dynamic.Middleware{
|
||||||
|
URLRewrite: &dynamic.URLRewrite{
|
||||||
|
Hostname: host,
|
||||||
|
Path: path,
|
||||||
|
PathPrefix: pathPrefix,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getProtocol(portSpec corev1.ServicePort) string {
|
func getProtocol(portSpec corev1.ServicePort) string {
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
|
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {
|
||||||
|
|
|
@ -1734,6 +1734,212 @@ func TestLoadHTTPRoutes(t *testing.T) {
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Simple HTTPRoute URL rewrite FullPath",
|
||||||
|
paths: []string{"services.yml", "httproute/filter_url_rewrite_fullpath.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-0-7f90cf546b15efadf2f8": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-wrr",
|
||||||
|
Rule: "Host(`example.com`) && (Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Priority: 10412,
|
||||||
|
Middlewares: []string{"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0": {
|
||||||
|
URLRewrite: &dynamic.URLRewrite{
|
||||||
|
Path: ptr.To("/bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-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 URL rewrite Hostname",
|
||||||
|
paths: []string{"services.yml", "httproute/filter_url_rewrite_hostname.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-0-7f90cf546b15efadf2f8": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-wrr",
|
||||||
|
Rule: "Host(`example.com`) && (Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Priority: 10412,
|
||||||
|
Middlewares: []string{"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0": {
|
||||||
|
URLRewrite: &dynamic.URLRewrite{
|
||||||
|
Hostname: ptr.To("www.foo.bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-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 URL rewrite Combined",
|
||||||
|
paths: []string{"services.yml", "httproute/filter_url_rewrite_combined.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-0-7f90cf546b15efadf2f8": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-http-app-1-my-gateway-web-0-wrr",
|
||||||
|
Rule: "Host(`example.com`) && (Path(`/foo`) || PathPrefix(`/foo/`))",
|
||||||
|
RuleSyntax: "v3",
|
||||||
|
Priority: 10412,
|
||||||
|
Middlewares: []string{"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0": {
|
||||||
|
URLRewrite: &dynamic.URLRewrite{
|
||||||
|
Hostname: ptr.To("www.foo.bar"),
|
||||||
|
Path: ptr.To("/xyz"),
|
||||||
|
PathPrefix: ptr.To("/foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-http-app-1-my-gateway-web-0-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{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/customerrors"
|
"github.com/traefik/traefik/v3/pkg/middlewares/customerrors"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/gatewayapi/headermodifier"
|
"github.com/traefik/traefik/v3/pkg/middlewares/gatewayapi/headermodifier"
|
||||||
gapiredirect "github.com/traefik/traefik/v3/pkg/middlewares/gatewayapi/redirect"
|
gapiredirect "github.com/traefik/traefik/v3/pkg/middlewares/gatewayapi/redirect"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/middlewares/gatewayapi/urlrewrite"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/grpcweb"
|
"github.com/traefik/traefik/v3/pkg/middlewares/grpcweb"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/headers"
|
"github.com/traefik/traefik/v3/pkg/middlewares/headers"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/inflightreq"
|
"github.com/traefik/traefik/v3/pkg/middlewares/inflightreq"
|
||||||
|
@ -392,7 +393,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||||
return nil, badConf
|
return nil, badConf
|
||||||
}
|
}
|
||||||
middleware = func(next http.Handler) (http.Handler, error) {
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
return headermodifier.NewRequestHeaderModifier(ctx, next, *config.RequestHeaderModifier, middlewareName)
|
return headermodifier.NewRequestHeaderModifier(ctx, next, *config.RequestHeaderModifier, middlewareName), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,6 +406,15 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.URLRewrite != nil {
|
||||||
|
if middleware != nil {
|
||||||
|
return nil, badConf
|
||||||
|
}
|
||||||
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
|
return urlrewrite.NewURLRewrite(ctx, next, *config.URLRewrite, middlewareName), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if middleware == nil {
|
if middleware == nil {
|
||||||
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue