Add mirrorBody option to HTTP mirroring
This commit is contained in:
parent
51f7f610c9
commit
eb99c8c785
19 changed files with 165 additions and 22 deletions
|
@ -82,6 +82,7 @@
|
||||||
[http.services.Service03]
|
[http.services.Service03]
|
||||||
[http.services.Service03.mirroring]
|
[http.services.Service03.mirroring]
|
||||||
service = "foobar"
|
service = "foobar"
|
||||||
|
mirrorBody = true
|
||||||
maxBodySize = 42
|
maxBodySize = 42
|
||||||
|
|
||||||
[[http.services.Service03.mirroring.mirrors]]
|
[[http.services.Service03.mirroring.mirrors]]
|
||||||
|
|
|
@ -89,6 +89,7 @@ http:
|
||||||
Service03:
|
Service03:
|
||||||
mirroring:
|
mirroring:
|
||||||
service: foobar
|
service: foobar
|
||||||
|
mirrorBody: true
|
||||||
maxBodySize: 42
|
maxBodySize: 42
|
||||||
mirrors:
|
mirrors:
|
||||||
- name: foobar
|
- name: foobar
|
||||||
|
|
|
@ -2506,6 +2506,11 @@ spec:
|
||||||
Default value is -1, which means unlimited size.
|
Default value is -1, which means unlimited size.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
|
mirrorBody:
|
||||||
|
description: |-
|
||||||
|
MirrorBody defines whether the body of the request should be mirrored.
|
||||||
|
Default value is true.
|
||||||
|
type: boolean
|
||||||
mirrors:
|
mirrors:
|
||||||
description: Mirrors defines the list of mirrors where Traefik
|
description: Mirrors defines the list of mirrors where Traefik
|
||||||
will duplicate the traffic.
|
will duplicate the traffic.
|
||||||
|
|
|
@ -63,6 +63,7 @@ spec:
|
||||||
mirroring:
|
mirroring:
|
||||||
name: wrr2
|
name: wrr2
|
||||||
kind: TraefikService
|
kind: TraefikService
|
||||||
|
mirrorBody: true
|
||||||
# Optional
|
# Optional
|
||||||
maxBodySize: 2000000000
|
maxBodySize: 2000000000
|
||||||
mirrors:
|
mirrors:
|
||||||
|
|
|
@ -264,6 +264,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` |
|
| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` |
|
||||||
| `traefik/http/services/Service03/mirroring/healthCheck` | `` |
|
| `traefik/http/services/Service03/mirroring/healthCheck` | `` |
|
||||||
| `traefik/http/services/Service03/mirroring/maxBodySize` | `42` |
|
| `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/name` | `foobar` |
|
||||||
| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` |
|
| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` |
|
||||||
| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` |
|
| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` |
|
||||||
|
|
|
@ -121,6 +121,11 @@ spec:
|
||||||
Default value is -1, which means unlimited size.
|
Default value is -1, which means unlimited size.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
|
mirrorBody:
|
||||||
|
description: |-
|
||||||
|
MirrorBody defines whether the body of the request should be mirrored.
|
||||||
|
Default value is true.
|
||||||
|
type: boolean
|
||||||
mirrors:
|
mirrors:
|
||||||
description: Mirrors defines the list of mirrors where Traefik
|
description: Mirrors defines the list of mirrors where Traefik
|
||||||
will duplicate the traffic.
|
will duplicate the traffic.
|
||||||
|
|
|
@ -1207,6 +1207,7 @@ http:
|
||||||
The mirroring is able to mirror requests sent to a service to other services.
|
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.
|
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.
|
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"
|
!!! info "Supported Providers"
|
||||||
|
|
||||||
|
@ -1219,6 +1220,9 @@ http:
|
||||||
mirrored-api:
|
mirrored-api:
|
||||||
mirroring:
|
mirroring:
|
||||||
service: appv1
|
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.
|
# maxBodySize is the maximum size allowed for the body of the request.
|
||||||
# If the body is larger, the request is not mirrored.
|
# If the body is larger, the request is not mirrored.
|
||||||
# Default value is -1, which means unlimited size.
|
# Default value is -1, which means unlimited size.
|
||||||
|
@ -1248,6 +1252,9 @@ http:
|
||||||
# If the body is larger, the request is not mirrored.
|
# If the body is larger, the request is not mirrored.
|
||||||
# Default value is -1, which means unlimited size.
|
# Default value is -1, which means unlimited size.
|
||||||
maxBodySize = 1024
|
maxBodySize = 1024
|
||||||
|
# mirrorBody defines whether the request body should be mirrored.
|
||||||
|
# Default value is true.
|
||||||
|
mirrorBody = false
|
||||||
[[http.services.mirrored-api.mirroring.mirrors]]
|
[[http.services.mirrored-api.mirroring.mirrors]]
|
||||||
name = "appv2"
|
name = "appv2"
|
||||||
percent = 10
|
percent = 10
|
||||||
|
|
|
@ -2506,6 +2506,11 @@ spec:
|
||||||
Default value is -1, which means unlimited size.
|
Default value is -1, which means unlimited size.
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
|
mirrorBody:
|
||||||
|
description: |-
|
||||||
|
MirrorBody defines whether the body of the request should be mirrored.
|
||||||
|
Default value is true.
|
||||||
|
type: boolean
|
||||||
mirrors:
|
mirrors:
|
||||||
description: Mirrors defines the list of mirrors where Traefik
|
description: Mirrors defines the list of mirrors where Traefik
|
||||||
will duplicate the traffic.
|
will duplicate the traffic.
|
||||||
|
|
|
@ -28,6 +28,10 @@
|
||||||
service = "mirrorWithMaxBody"
|
service = "mirrorWithMaxBody"
|
||||||
rule = "Path(`/whoamiWithMaxBody`)"
|
rule = "Path(`/whoamiWithMaxBody`)"
|
||||||
|
|
||||||
|
[http.routers.router3]
|
||||||
|
service = "mirrorWithoutBody"
|
||||||
|
rule = "Path(`/whoamiWithoutBody`)"
|
||||||
|
|
||||||
|
|
||||||
[http.services]
|
[http.services]
|
||||||
[http.services.mirror.mirroring]
|
[http.services.mirror.mirroring]
|
||||||
|
@ -49,6 +53,16 @@
|
||||||
name = "mirror2"
|
name = "mirror2"
|
||||||
percent = 50
|
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]
|
||||||
[[http.services.service1.loadBalancer.servers]]
|
[[http.services.service1.loadBalancer.servers]]
|
||||||
|
|
|
@ -1004,8 +1004,13 @@ func (s *SimpleSuite) TestMirrorWithBody() {
|
||||||
_, err = rand.Read(body5)
|
_, err = rand.Read(body5)
|
||||||
require.NoError(s.T(), err)
|
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)
|
b, _ := io.ReadAll(req.Body)
|
||||||
|
if canBodyBeEmpty && req.Header.Get("NoBody") == "true" {
|
||||||
|
require.Empty(s.T(), b)
|
||||||
|
return true
|
||||||
|
}
|
||||||
switch req.Header.Get("Size") {
|
switch req.Header.Get("Size") {
|
||||||
case "20":
|
case "20":
|
||||||
require.Equal(s.T(), body20, b)
|
require.Equal(s.T(), body20, b)
|
||||||
|
@ -1014,20 +1019,25 @@ func (s *SimpleSuite) TestMirrorWithBody() {
|
||||||
default:
|
default:
|
||||||
s.T().Fatal("Size header not present")
|
s.T().Fatal("Size header not present")
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
main := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
main := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
verifyBody(req)
|
verifyBody(req, false)
|
||||||
atomic.AddInt32(&count, 1)
|
atomic.AddInt32(&count, 1)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mirror1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
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)
|
atomic.AddInt32(&countMirror1, 1)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mirror2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
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)
|
atomic.AddInt32(&countMirror2, 1)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -1104,6 +1114,28 @@ func (s *SimpleSuite) TestMirrorWithBody() {
|
||||||
assert.Equal(s.T(), int32(10), countTotal)
|
assert.Equal(s.T(), int32(10), countTotal)
|
||||||
assert.Equal(s.T(), int32(0), val1)
|
assert.Equal(s.T(), int32(0), val1)
|
||||||
assert.Equal(s.T(), int32(0), val2)
|
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() {
|
func (s *SimpleSuite) TestMirrorCanceled() {
|
||||||
|
|
|
@ -81,6 +81,7 @@ type RouterTLSConfig struct {
|
||||||
// Mirroring holds the Mirroring configuration.
|
// Mirroring holds the Mirroring configuration.
|
||||||
type Mirroring struct {
|
type Mirroring struct {
|
||||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
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"`
|
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"`
|
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"`
|
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.
|
// SetDefaults Default values for a WRRService.
|
||||||
func (m *Mirroring) SetDefaults() {
|
func (m *Mirroring) SetDefaults() {
|
||||||
|
defaultMirrorBody := true
|
||||||
|
m.MirrorBody = &defaultMirrorBody
|
||||||
var defaultMaxBodySize int64 = -1
|
var defaultMaxBodySize int64 = -1
|
||||||
m.MaxBodySize = &defaultMaxBodySize
|
m.MaxBodySize = &defaultMaxBodySize
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.MirrorBody != nil {
|
||||||
|
in, out := &in.MirrorBody, &out.MirrorBody
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.MaxBodySize != nil {
|
if in.MaxBodySize != nil {
|
||||||
in, out := &in.MaxBodySize, &out.MaxBodySize
|
in, out := &in.MaxBodySize, &out.MaxBodySize
|
||||||
*out = new(int64)
|
*out = new(int64)
|
||||||
|
|
|
@ -290,6 +290,7 @@ func (c configBuilder) buildMirroring(ctx context.Context, tService *traefikv1al
|
||||||
Mirroring: &dynamic.Mirroring{
|
Mirroring: &dynamic.Mirroring{
|
||||||
Service: fullNameMain,
|
Service: fullNameMain,
|
||||||
Mirrors: mirrorServices,
|
Mirrors: mirrorServices,
|
||||||
|
MirrorBody: tService.Spec.Mirroring.MirrorBody,
|
||||||
MaxBodySize: tService.Spec.Mirroring.MaxBodySize,
|
MaxBodySize: tService.Spec.Mirroring.MaxBodySize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,9 @@ type TraefikServiceSpec struct {
|
||||||
type Mirroring struct {
|
type Mirroring struct {
|
||||||
LoadBalancerSpec `json:",inline"`
|
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.
|
// MaxBodySize defines the maximum size allowed for the body of the request.
|
||||||
// If the body is larger, the request is not mirrored.
|
// If the body is larger, the request is not mirrored.
|
||||||
// Default value is -1, which means unlimited size.
|
// Default value is -1, which means unlimited size.
|
||||||
|
|
|
@ -972,6 +972,11 @@ func (in *MirrorService) DeepCopy() *MirrorService {
|
||||||
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
func (in *Mirroring) DeepCopyInto(out *Mirroring) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec)
|
in.LoadBalancerSpec.DeepCopyInto(&out.LoadBalancerSpec)
|
||||||
|
if in.MirrorBody != nil {
|
||||||
|
in, out := &in.MirrorBody, &out.MirrorBody
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.MaxBodySize != nil {
|
if in.MaxBodySize != nil {
|
||||||
in, out := &in.MaxBodySize, &out.MaxBodySize
|
in, out := &in.MaxBodySize, &out.MaxBodySize
|
||||||
*out = new(int64)
|
*out = new(int64)
|
||||||
|
|
|
@ -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/0/url": "foobar",
|
||||||
"traefik/http/services/Service01/loadBalancer/servers/1/url": "foobar",
|
"traefik/http/services/Service01/loadBalancer/servers/1/url": "foobar",
|
||||||
"traefik/http/services/Service02/mirroring/service": "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/maxBodySize": "42",
|
||||||
"traefik/http/services/Service02/mirroring/mirrors/0/name": "foobar",
|
"traefik/http/services/Service02/mirroring/mirrors/0/name": "foobar",
|
||||||
"traefik/http/services/Service02/mirroring/mirrors/0/percent": "42",
|
"traefik/http/services/Service02/mirroring/mirrors/0/percent": "42",
|
||||||
|
@ -676,6 +677,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
"Service02": {
|
"Service02": {
|
||||||
Mirroring: &dynamic.Mirroring{
|
Mirroring: &dynamic.Mirroring{
|
||||||
Service: "foobar",
|
Service: "foobar",
|
||||||
|
MirrorBody: func(v bool) *bool { return &v }(true),
|
||||||
MaxBodySize: func(v int64) *int64 { return &v }(42),
|
MaxBodySize: func(v int64) *int64 { return &v }(42),
|
||||||
Mirrors: []dynamic.MirrorService{
|
Mirrors: []dynamic.MirrorService{
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,7 @@ type Mirroring struct {
|
||||||
rw http.ResponseWriter
|
rw http.ResponseWriter
|
||||||
routinePool *safe.Pool
|
routinePool *safe.Pool
|
||||||
|
|
||||||
|
mirrorBody bool
|
||||||
maxBodySize int64
|
maxBodySize int64
|
||||||
wantsHealthCheck bool
|
wantsHealthCheck bool
|
||||||
|
|
||||||
|
@ -33,11 +34,12 @@ type Mirroring struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new instance of *Mirroring.
|
// 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{
|
return &Mirroring{
|
||||||
routinePool: pool,
|
routinePool: pool,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
rw: blackHoleResponseWriter{},
|
rw: blackHoleResponseWriter{},
|
||||||
|
mirrorBody: mirrorBody,
|
||||||
maxBodySize: maxBodySize,
|
maxBodySize: maxBodySize,
|
||||||
wantsHealthCheck: hc != nil,
|
wantsHealthCheck: hc != nil,
|
||||||
}
|
}
|
||||||
|
@ -83,7 +85,7 @@ func (m *Mirroring) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.Ctx(req.Context())
|
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) {
|
if err != nil && !errors.Is(err, errBodyTooLarge) {
|
||||||
http.Error(rw, fmt.Sprintf("%s: creating reusable request: %v",
|
http.Error(rw, fmt.Sprintf("%s: creating reusable request: %v",
|
||||||
http.StatusText(http.StatusInternalServerError), err), http.StatusInternalServerError)
|
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
|
// if the returned error is errBodyTooLarge, newReusableRequest also returns the
|
||||||
// bytes that were already consumed from the request's body.
|
// 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 {
|
if req == nil {
|
||||||
return nil, nil, errors.New("nil input request")
|
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
|
return &reusableRequest{req: req}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestMirroringOn100(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
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) {
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
atomic.AddInt32(&countMirror1, 1)
|
atomic.AddInt32(&countMirror1, 1)
|
||||||
}), 10)
|
}), 10)
|
||||||
|
@ -50,7 +50,7 @@ func TestMirroringOn10(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
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) {
|
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
atomic.AddInt32(&countMirror1, 1)
|
atomic.AddInt32(&countMirror1, 1)
|
||||||
}), 10)
|
}), 10)
|
||||||
|
@ -74,7 +74,7 @@ func TestMirroringOn10(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidPercent(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)
|
err := mirror.AddMirror(nil, -1)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ func TestHijack(t *testing.T) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
pool := safe.NewPool(context.Background())
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
|
|
||||||
var mirrorRequest bool
|
var mirrorRequest bool
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
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)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
pool := safe.NewPool(context.Background())
|
pool := safe.NewPool(context.Background())
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
|
|
||||||
var mirrorRequest bool
|
var mirrorRequest bool
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
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)
|
rw.WriteHeader(http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
mirror := New(handler, pool, defaultMaxBodySize, nil)
|
mirror := New(handler, pool, true, defaultMaxBodySize, nil)
|
||||||
|
|
||||||
for range numMirrors {
|
for range numMirrors {
|
||||||
err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
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))
|
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) {
|
func TestCloneRequest(t *testing.T) {
|
||||||
t.Run("http request body is nil", func(t *testing.T) {
|
t.Run("http request body is nil", func(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodPost, "/", nil)
|
req, err := http.NewRequest(http.MethodPost, "/", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
rr, _, err := newReusableRequest(req, defaultMaxBodySize)
|
rr, _, err := newReusableRequest(req, true, defaultMaxBodySize)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// first call
|
// first call
|
||||||
|
@ -208,7 +250,7 @@ func TestCloneRequest(t *testing.T) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
req.ContentLength = int64(contentLength)
|
req.ContentLength = int64(contentLength)
|
||||||
|
|
||||||
rr, _, err := newReusableRequest(req, defaultMaxBodySize)
|
rr, _, err := newReusableRequest(req, true, defaultMaxBodySize)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// first call
|
// first call
|
||||||
|
@ -231,7 +273,7 @@ func TestCloneRequest(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, expectedBytes, err := newReusableRequest(req, 2)
|
_, expectedBytes, err := newReusableRequest(req, true, 2)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, expectedBytes, bb[:3])
|
assert.Equal(t, expectedBytes, bb[:3])
|
||||||
})
|
})
|
||||||
|
@ -243,7 +285,7 @@ func TestCloneRequest(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
req, err := http.NewRequest(http.MethodPost, "/", buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rr, expectedBytes, err := newReusableRequest(req, 20)
|
rr, expectedBytes, err := newReusableRequest(req, true, 20)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, expectedBytes)
|
assert.Nil(t, expectedBytes)
|
||||||
assert.Len(t, rr.body, 10)
|
assert.Len(t, rr.body, 10)
|
||||||
|
@ -255,14 +297,14 @@ func TestCloneRequest(t *testing.T) {
|
||||||
req, err := http.NewRequest(http.MethodGet, "/", buf)
|
req, err := http.NewRequest(http.MethodGet, "/", buf)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rr, expectedBytes, err := newReusableRequest(req, 20)
|
rr, expectedBytes, err := newReusableRequest(req, true, 20)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, expectedBytes)
|
assert.Nil(t, expectedBytes)
|
||||||
assert.Empty(t, rr.body)
|
assert.Empty(t, rr.body)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no request given", func(t *testing.T) {
|
t.Run("no request given", func(t *testing.T) {
|
||||||
_, _, err := newReusableRequest(nil, defaultMaxBodySize)
|
_, _, err := newReusableRequest(nil, true, defaultMaxBodySize)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,10 @@ import (
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMaxBodySize int64 = -1
|
const (
|
||||||
|
defaultMirrorBody = true
|
||||||
|
defaultMaxBodySize int64 = -1
|
||||||
|
)
|
||||||
|
|
||||||
// RoundTripperGetter is a roundtripper getter interface.
|
// RoundTripperGetter is a roundtripper getter interface.
|
||||||
type RoundTripperGetter interface {
|
type RoundTripperGetter interface {
|
||||||
|
@ -197,11 +200,16 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mirrorBody := defaultMirrorBody
|
||||||
|
if config.MirrorBody != nil {
|
||||||
|
mirrorBody = *config.MirrorBody
|
||||||
|
}
|
||||||
|
|
||||||
maxBodySize := defaultMaxBodySize
|
maxBodySize := defaultMaxBodySize
|
||||||
if config.MaxBodySize != nil {
|
if config.MaxBodySize != nil {
|
||||||
maxBodySize = *config.MaxBodySize
|
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 {
|
for _, mirrorConfig := range config.Mirrors {
|
||||||
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name)
|
mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue