From eb99c8c7858eb3c6b0f1428367d44b751144e76b Mon Sep 17 00:00:00 2001 From: Matteo Paier Date: Mon, 2 Sep 2024 16:36:06 +0200 Subject: [PATCH] Add mirrorBody option to HTTP mirroring --- .../reference/dynamic-configuration/file.toml | 1 + .../reference/dynamic-configuration/file.yaml | 1 + .../kubernetes-crd-definition-v1.yml | 5 ++ .../kubernetes-crd-resource.yml | 1 + .../reference/dynamic-configuration/kv-ref.md | 1 + .../traefik.io_traefikservices.yaml | 5 ++ docs/content/routing/services/index.md | 7 ++ integration/fixtures/k8s/01-traefik-crd.yml | 5 ++ integration/fixtures/mirror.toml | 14 ++++ integration/simple_test.go | 40 +++++++++-- pkg/config/dynamic/http_config.go | 3 + pkg/config/dynamic/zz_generated.deepcopy.go | 5 ++ .../kubernetes/crd/kubernetes_http.go | 1 + .../crd/traefikio/v1alpha1/service.go | 3 + .../v1alpha1/zz_generated.deepcopy.go | 5 ++ pkg/provider/kv/kv_test.go | 2 + .../service/loadbalancer/mirror/mirror.go | 10 +-- .../loadbalancer/mirror/mirror_test.go | 66 +++++++++++++++---- pkg/server/service/service.go | 12 +++- 19 files changed, 165 insertions(+), 22 deletions(-) diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 62958ccd4..269467380 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -82,6 +82,7 @@ [http.services.Service03] [http.services.Service03.mirroring] service = "foobar" + mirrorBody = true maxBodySize = 42 [[http.services.Service03.mirroring.mirrors]] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index e4e82bea4..6f94af243 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -89,6 +89,7 @@ http: Service03: mirroring: service: foobar + mirrorBody: true maxBodySize: 42 mirrors: - name: foobar diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 30038945a..96816d8a4 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -2506,6 +2506,11 @@ spec: Default value is -1, which means unlimited size. format: int64 type: integer + mirrorBody: + description: |- + MirrorBody defines whether the body of the request should be mirrored. + Default value is true. + type: boolean mirrors: description: Mirrors defines the list of mirrors where Traefik will duplicate the traffic. diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml index 93f1a95ff..14e809a2e 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml @@ -63,6 +63,7 @@ spec: mirroring: name: wrr2 kind: TraefikService + mirrorBody: true # Optional maxBodySize: 2000000000 mirrors: diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index 205ef0efe..bd9b99a7a 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -264,6 +264,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` | | `traefik/http/services/Service03/mirroring/healthCheck` | `` | | `traefik/http/services/Service03/mirroring/maxBodySize` | `42` | +| `traefik/http/services/Service03/mirroring/mirrorBody` | `true` | | `traefik/http/services/Service03/mirroring/mirrors/0/name` | `foobar` | | `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` | | `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` | diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index 1e1b279d5..48e629bb8 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -121,6 +121,11 @@ spec: Default value is -1, which means unlimited size. format: int64 type: integer + mirrorBody: + description: |- + MirrorBody defines whether the body of the request should be mirrored. + Default value is true. + type: boolean mirrors: description: Mirrors defines the list of mirrors where Traefik will duplicate the traffic. diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index dad91d472..8e4cc41f0 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -1207,6 +1207,7 @@ http: The mirroring is able to mirror requests sent to a service to other services. Please note that by default the whole request is buffered in memory while it is being mirrored. See the maxBodySize option in the example below for how to modify this behaviour. +You can also omit the request body by setting the mirrorBody option to `false`. !!! info "Supported Providers" @@ -1219,6 +1220,9 @@ http: mirrored-api: mirroring: service: appv1 + # mirrorBody defines whether the request body should be mirrored. + # Default value is true. + mirrorBody: false # maxBodySize is the maximum size allowed for the body of the request. # If the body is larger, the request is not mirrored. # Default value is -1, which means unlimited size. @@ -1248,6 +1252,9 @@ http: # If the body is larger, the request is not mirrored. # Default value is -1, which means unlimited size. maxBodySize = 1024 + # mirrorBody defines whether the request body should be mirrored. + # Default value is true. + mirrorBody = false [[http.services.mirrored-api.mirroring.mirrors]] name = "appv2" percent = 10 diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 30038945a..96816d8a4 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -2506,6 +2506,11 @@ spec: Default value is -1, which means unlimited size. format: int64 type: integer + mirrorBody: + description: |- + MirrorBody defines whether the body of the request should be mirrored. + Default value is true. + type: boolean mirrors: description: Mirrors defines the list of mirrors where Traefik will duplicate the traffic. diff --git a/integration/fixtures/mirror.toml b/integration/fixtures/mirror.toml index 4c99f83b8..b28878480 100644 --- a/integration/fixtures/mirror.toml +++ b/integration/fixtures/mirror.toml @@ -28,6 +28,10 @@ service = "mirrorWithMaxBody" rule = "Path(`/whoamiWithMaxBody`)" + [http.routers.router3] + service = "mirrorWithoutBody" + rule = "Path(`/whoamiWithoutBody`)" + [http.services] [http.services.mirror.mirroring] @@ -49,6 +53,16 @@ name = "mirror2" percent = 50 + [http.services.mirrorWithoutBody.mirroring] + service = "service1" + mirrorBody = false + [[http.services.mirrorWithoutBody.mirroring.mirrors]] + name = "mirror1" + percent = 10 + [[http.services.mirrorWithoutBody.mirroring.mirrors]] + name = "mirror2" + percent = 50 + [http.services.service1.loadBalancer] [[http.services.service1.loadBalancer.servers]] diff --git a/integration/simple_test.go b/integration/simple_test.go index 018a4b67a..9aae5c1b2 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1004,8 +1004,13 @@ func (s *SimpleSuite) TestMirrorWithBody() { _, err = rand.Read(body5) require.NoError(s.T(), err) - verifyBody := func(req *http.Request) { + // forceOkResponse is used to avoid errors when Content-Length is set but no body is received + verifyBody := func(req *http.Request, canBodyBeEmpty bool) (forceOkResponse bool) { b, _ := io.ReadAll(req.Body) + if canBodyBeEmpty && req.Header.Get("NoBody") == "true" { + require.Empty(s.T(), b) + return true + } switch req.Header.Get("Size") { case "20": require.Equal(s.T(), body20, b) @@ -1014,20 +1019,25 @@ func (s *SimpleSuite) TestMirrorWithBody() { default: s.T().Fatal("Size header not present") } + return false } main := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - verifyBody(req) + verifyBody(req, false) atomic.AddInt32(&count, 1) })) mirror1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - verifyBody(req) + if verifyBody(req, true) { + rw.WriteHeader(http.StatusOK) + } atomic.AddInt32(&countMirror1, 1) })) mirror2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - verifyBody(req) + if verifyBody(req, true) { + rw.WriteHeader(http.StatusOK) + } atomic.AddInt32(&countMirror2, 1) })) @@ -1104,6 +1114,28 @@ func (s *SimpleSuite) TestMirrorWithBody() { assert.Equal(s.T(), int32(10), countTotal) assert.Equal(s.T(), int32(0), val1) assert.Equal(s.T(), int32(0), val2) + + atomic.StoreInt32(&count, 0) + atomic.StoreInt32(&countMirror1, 0) + atomic.StoreInt32(&countMirror2, 0) + + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/whoamiWithoutBody", bytes.NewBuffer(body20)) + require.NoError(s.T(), err) + req.Header.Set("Size", "20") + req.Header.Set("NoBody", "true") + for range 10 { + response, err := http.DefaultClient.Do(req) + require.NoError(s.T(), err) + assert.Equal(s.T(), http.StatusOK, response.StatusCode) + } + + countTotal = atomic.LoadInt32(&count) + val1 = atomic.LoadInt32(&countMirror1) + val2 = atomic.LoadInt32(&countMirror2) + + assert.Equal(s.T(), int32(10), countTotal) + assert.Equal(s.T(), int32(1), val1) + assert.Equal(s.T(), int32(5), val2) } func (s *SimpleSuite) TestMirrorCanceled() { diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 48b2e763c..d347df81e 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -81,6 +81,7 @@ type RouterTLSConfig struct { // Mirroring holds the Mirroring configuration. type Mirroring struct { Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"` + MirrorBody *bool `json:"mirrorBody,omitempty" toml:"mirrorBody,omitempty" yaml:"mirrorBody,omitempty" export:"true"` MaxBodySize *int64 `json:"maxBodySize,omitempty" toml:"maxBodySize,omitempty" yaml:"maxBodySize,omitempty" export:"true"` Mirrors []MirrorService `json:"mirrors,omitempty" toml:"mirrors,omitempty" yaml:"mirrors,omitempty" export:"true"` HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` @@ -88,6 +89,8 @@ type Mirroring struct { // SetDefaults Default values for a WRRService. func (m *Mirroring) SetDefaults() { + defaultMirrorBody := true + m.MirrorBody = &defaultMirrorBody var defaultMaxBodySize int64 = -1 m.MaxBodySize = &defaultMaxBodySize } diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index da909e316..890e89700 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -967,6 +967,11 @@ func (in *MirrorService) DeepCopy() *MirrorService { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Mirroring) DeepCopyInto(out *Mirroring) { *out = *in + if in.MirrorBody != nil { + in, out := &in.MirrorBody, &out.MirrorBody + *out = new(bool) + **out = **in + } if in.MaxBodySize != nil { in, out := &in.MaxBodySize, &out.MaxBodySize *out = new(int64) diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index d81a02058..8f00ec238 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -290,6 +290,7 @@ func (c configBuilder) buildMirroring(ctx context.Context, tService *traefikv1al Mirroring: &dynamic.Mirroring{ Service: fullNameMain, Mirrors: mirrorServices, + MirrorBody: tService.Spec.Mirroring.MirrorBody, MaxBodySize: tService.Spec.Mirroring.MaxBodySize, }, } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go index ed1955a8a..cdbd95b0e 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go @@ -53,6 +53,9 @@ type TraefikServiceSpec struct { type Mirroring struct { LoadBalancerSpec `json:",inline"` + // MirrorBody defines whether the body of the request should be mirrored. + // Default value is true. + MirrorBody *bool `json:"mirrorBody,omitempty"` // MaxBodySize defines the maximum size allowed for the body of the request. // If the body is larger, the request is not mirrored. // Default value is -1, which means unlimited size. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index caa1bf9ec..466cc7577 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -972,6 +972,11 @@ func (in *MirrorService) DeepCopy() *MirrorService { func (in *Mirroring) DeepCopyInto(out *Mirroring) { *out = *in in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec) + if in.MirrorBody != nil { + in, out := &in.MirrorBody, &out.MirrorBody + *out = new(bool) + **out = **in + } if in.MaxBodySize != nil { in, out := &in.MaxBodySize, &out.MaxBodySize *out = new(int64) diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index 3610ec3b5..6cfcc7b61 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -61,6 +61,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/services/Service01/loadBalancer/servers/0/url": "foobar", "traefik/http/services/Service01/loadBalancer/servers/1/url": "foobar", "traefik/http/services/Service02/mirroring/service": "foobar", + "traefik/http/services/Service02/mirroring/mirrorBody": "true", "traefik/http/services/Service02/mirroring/maxBodySize": "42", "traefik/http/services/Service02/mirroring/mirrors/0/name": "foobar", "traefik/http/services/Service02/mirroring/mirrors/0/percent": "42", @@ -676,6 +677,7 @@ func Test_buildConfiguration(t *testing.T) { "Service02": { Mirroring: &dynamic.Mirroring{ Service: "foobar", + MirrorBody: func(v bool) *bool { return &v }(true), MaxBodySize: func(v int64) *int64 { return &v }(42), Mirrors: []dynamic.MirrorService{ { diff --git a/pkg/server/service/loadbalancer/mirror/mirror.go b/pkg/server/service/loadbalancer/mirror/mirror.go index 201dade9a..91cf364a3 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror.go +++ b/pkg/server/service/loadbalancer/mirror/mirror.go @@ -25,6 +25,7 @@ type Mirroring struct { rw http.ResponseWriter routinePool *safe.Pool + mirrorBody bool maxBodySize int64 wantsHealthCheck bool @@ -33,11 +34,12 @@ type Mirroring struct { } // New returns a new instance of *Mirroring. -func New(handler http.Handler, pool *safe.Pool, maxBodySize int64, hc *dynamic.HealthCheck) *Mirroring { +func New(handler http.Handler, pool *safe.Pool, mirrorBody bool, maxBodySize int64, hc *dynamic.HealthCheck) *Mirroring { return &Mirroring{ routinePool: pool, handler: handler, rw: blackHoleResponseWriter{}, + mirrorBody: mirrorBody, maxBodySize: maxBodySize, wantsHealthCheck: hc != nil, } @@ -83,7 +85,7 @@ func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } logger := log.Ctx(req.Context()) - rr, bytesRead, err := newReusableRequest(req, m.maxBodySize) + rr, bytesRead, err := newReusableRequest(req, m.mirrorBody, m.maxBodySize) if err != nil && !errors.Is(err, errBodyTooLarge) { http.Error(rw, fmt.Sprintf("%s: creating reusable request: %v", http.StatusText(http.StatusInternalServerError), err), http.StatusInternalServerError) @@ -200,11 +202,11 @@ var errBodyTooLarge = errors.New("request body too large") // if the returned error is errBodyTooLarge, newReusableRequest also returns the // bytes that were already consumed from the request's body. -func newReusableRequest(req *http.Request, maxBodySize int64) (*reusableRequest, []byte, error) { +func newReusableRequest(req *http.Request, mirrorBody bool, maxBodySize int64) (*reusableRequest, []byte, error) { if req == nil { return nil, nil, errors.New("nil input request") } - if req.Body == nil || req.ContentLength == 0 { + if req.Body == nil || req.ContentLength == 0 || !mirrorBody { return &reusableRequest{req: req}, nil, nil } diff --git a/pkg/server/service/loadbalancer/mirror/mirror_test.go b/pkg/server/service/loadbalancer/mirror/mirror_test.go index c48637006..94a4c62a0 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror_test.go +++ b/pkg/server/service/loadbalancer/mirror/mirror_test.go @@ -21,7 +21,7 @@ func TestMirroringOn100(t *testing.T) { rw.WriteHeader(http.StatusOK) }) pool := safe.NewPool(context.Background()) - mirror := New(handler, pool, defaultMaxBodySize, nil) + mirror := New(handler, pool, true, defaultMaxBodySize, nil) err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { atomic.AddInt32(&countMirror1, 1) }), 10) @@ -50,7 +50,7 @@ func TestMirroringOn10(t *testing.T) { rw.WriteHeader(http.StatusOK) }) pool := safe.NewPool(context.Background()) - mirror := New(handler, pool, defaultMaxBodySize, nil) + mirror := New(handler, pool, true, defaultMaxBodySize, nil) err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { atomic.AddInt32(&countMirror1, 1) }), 10) @@ -74,7 +74,7 @@ func TestMirroringOn10(t *testing.T) { } func TestInvalidPercent(t *testing.T) { - mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(context.Background()), defaultMaxBodySize, nil) + mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(context.Background()), true, defaultMaxBodySize, nil) err := mirror.AddMirror(nil, -1) assert.Error(t, err) @@ -93,7 +93,7 @@ func TestHijack(t *testing.T) { rw.WriteHeader(http.StatusOK) }) pool := safe.NewPool(context.Background()) - mirror := New(handler, pool, defaultMaxBodySize, nil) + mirror := New(handler, pool, true, defaultMaxBodySize, nil) var mirrorRequest bool err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -117,7 +117,7 @@ func TestFlush(t *testing.T) { rw.WriteHeader(http.StatusOK) }) pool := safe.NewPool(context.Background()) - mirror := New(handler, pool, defaultMaxBodySize, nil) + mirror := New(handler, pool, true, defaultMaxBodySize, nil) var mirrorRequest bool err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -154,7 +154,7 @@ func TestMirroringWithBody(t *testing.T) { rw.WriteHeader(http.StatusOK) }) - mirror := New(handler, pool, defaultMaxBodySize, nil) + mirror := New(handler, pool, true, defaultMaxBodySize, nil) for range numMirrors { err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { @@ -177,13 +177,55 @@ func TestMirroringWithBody(t *testing.T) { assert.Equal(t, numMirrors, int(val)) } +func TestMirroringWithIgnoredBody(t *testing.T) { + const numMirrors = 10 + + var ( + countMirror int32 + body = []byte(`body`) + emptyBody = []byte(``) + ) + + pool := safe.NewPool(context.Background()) + + handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + assert.NotNil(t, r.Body) + bb, err := io.ReadAll(r.Body) + assert.NoError(t, err) + assert.Equal(t, body, bb) + rw.WriteHeader(http.StatusOK) + }) + + mirror := New(handler, pool, false, defaultMaxBodySize, nil) + + for range numMirrors { + err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + assert.NotNil(t, r.Body) + bb, err := io.ReadAll(r.Body) + assert.NoError(t, err) + assert.Equal(t, emptyBody, bb) + atomic.AddInt32(&countMirror, 1) + }), 100) + assert.NoError(t, err) + } + + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body)) + + mirror.ServeHTTP(httptest.NewRecorder(), req) + + pool.Stop() + + val := atomic.LoadInt32(&countMirror) + assert.Equal(t, numMirrors, int(val)) +} + func TestCloneRequest(t *testing.T) { t.Run("http request body is nil", func(t *testing.T) { req, err := http.NewRequest(http.MethodPost, "/", nil) assert.NoError(t, err) ctx := req.Context() - rr, _, err := newReusableRequest(req, defaultMaxBodySize) + rr, _, err := newReusableRequest(req, true, defaultMaxBodySize) assert.NoError(t, err) // first call @@ -208,7 +250,7 @@ func TestCloneRequest(t *testing.T) { ctx := req.Context() req.ContentLength = int64(contentLength) - rr, _, err := newReusableRequest(req, defaultMaxBodySize) + rr, _, err := newReusableRequest(req, true, defaultMaxBodySize) assert.NoError(t, err) // first call @@ -231,7 +273,7 @@ func TestCloneRequest(t *testing.T) { req, err := http.NewRequest(http.MethodPost, "/", buf) assert.NoError(t, err) - _, expectedBytes, err := newReusableRequest(req, 2) + _, expectedBytes, err := newReusableRequest(req, true, 2) assert.Error(t, err) assert.Equal(t, expectedBytes, bb[:3]) }) @@ -243,7 +285,7 @@ func TestCloneRequest(t *testing.T) { req, err := http.NewRequest(http.MethodPost, "/", buf) assert.NoError(t, err) - rr, expectedBytes, err := newReusableRequest(req, 20) + rr, expectedBytes, err := newReusableRequest(req, true, 20) assert.NoError(t, err) assert.Nil(t, expectedBytes) assert.Len(t, rr.body, 10) @@ -255,14 +297,14 @@ func TestCloneRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "/", buf) assert.NoError(t, err) - rr, expectedBytes, err := newReusableRequest(req, 20) + rr, expectedBytes, err := newReusableRequest(req, true, 20) assert.NoError(t, err) assert.Nil(t, expectedBytes) assert.Empty(t, rr.body) }) t.Run("no request given", func(t *testing.T) { - _, _, err := newReusableRequest(nil, defaultMaxBodySize) + _, _, err := newReusableRequest(nil, true, defaultMaxBodySize) assert.Error(t, err) }) } diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 247879bcf..38932f225 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -34,7 +34,10 @@ import ( "google.golang.org/grpc/status" ) -const defaultMaxBodySize int64 = -1 +const ( + defaultMirrorBody = true + defaultMaxBodySize int64 = -1 +) // RoundTripperGetter is a roundtripper getter interface. type RoundTripperGetter interface { @@ -197,11 +200,16 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M return nil, err } + mirrorBody := defaultMirrorBody + if config.MirrorBody != nil { + mirrorBody = *config.MirrorBody + } + maxBodySize := defaultMaxBodySize if config.MaxBodySize != nil { maxBodySize = *config.MaxBodySize } - handler := mirror.New(serviceHandler, m.routinePool, maxBodySize, config.HealthCheck) + handler := mirror.New(serviceHandler, m.routinePool, mirrorBody, maxBodySize, config.HealthCheck) for _, mirrorConfig := range config.Mirrors { mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name) if err != nil {