Add a mechanism to format the sticky cookie value
Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com>
This commit is contained in:
parent
70a02158e5
commit
dc8d5ef744
5 changed files with 49 additions and 6 deletions
|
@ -167,8 +167,8 @@ For now, only round robin load balancing is supported:
|
||||||
|
|
||||||
#### Sticky sessions
|
#### Sticky sessions
|
||||||
|
|
||||||
When sticky sessions are enabled, a cookie is set on the initial request and response to let the client know which server handles the first response.
|
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
|
||||||
On subsequent requests, to keep the session alive with the same server, the client should resend the same cookie.
|
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
|
||||||
|
|
||||||
!!! info "Stickiness on multiple levels"
|
!!! info "Stickiness on multiple levels"
|
||||||
|
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -77,7 +77,7 @@ require (
|
||||||
github.com/unrolled/render v1.0.2
|
github.com/unrolled/render v1.0.2
|
||||||
github.com/unrolled/secure v1.0.7
|
github.com/unrolled/secure v1.0.7
|
||||||
github.com/vdemeester/shakers v0.1.0
|
github.com/vdemeester/shakers v0.1.0
|
||||||
github.com/vulcand/oxy v1.2.0
|
github.com/vulcand/oxy v1.3.0
|
||||||
github.com/vulcand/predicate v1.1.0
|
github.com/vulcand/predicate v1.1.0
|
||||||
go.elastic.co/apm v1.7.0
|
go.elastic.co/apm v1.7.0
|
||||||
go.elastic.co/apm/module/apmot v1.7.0
|
go.elastic.co/apm/module/apmot v1.7.0
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -912,6 +912,8 @@ github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKP
|
||||||
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
|
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
|
||||||
|
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
@ -1039,8 +1041,8 @@ github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LW
|
||||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
github.com/vulcand/oxy v1.2.0 h1:Y2Wt1EgQddA/qMnp1+YXjehPsyw2Gy8CAfavkFF+fQk=
|
github.com/vulcand/oxy v1.3.0 h1:358BVHmJNLjhOrhbjq2EVJX5NQ3HxrP0d5OyHLRliX0=
|
||||||
github.com/vulcand/oxy v1.2.0/go.mod h1:nGeNTWfyYQj3ghi3W8ok7vLSkw7Gkvr0x+G/v8Wk7vM=
|
github.com/vulcand/oxy v1.3.0/go.mod h1:hN/gw/jg+GH4A+bqvznsW26Izd4jNGV6h1z3s7drRzs=
|
||||||
github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
|
github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
|
||||||
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
|
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
|
||||||
github.com/vultr/govultr/v2 v2.4.0 h1:6ySGGAsoOann0lmVNkS8grLvbAT2iYWnO4R1RVYFg0A=
|
github.com/vultr/govultr/v2 v2.4.0 h1:6ySGGAsoOann0lmVNkS8grLvbAT2iYWnO4R1RVYFg0A=
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
|
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
|
||||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
|
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
|
||||||
"github.com/vulcand/oxy/roundrobin"
|
"github.com/vulcand/oxy/roundrobin"
|
||||||
|
"github.com/vulcand/oxy/roundrobin/stickycookie"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -310,7 +311,13 @@ func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, servi
|
||||||
SameSite: convertSameSite(service.Sticky.Cookie.SameSite),
|
SameSite: convertSameSite(service.Sticky.Cookie.SameSite),
|
||||||
}
|
}
|
||||||
|
|
||||||
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySessionWithOptions(cookieName, opts)))
|
// Sticky Cookie Value
|
||||||
|
cv, err := stickycookie.NewFallbackValue(&stickycookie.RawValue{}, &stickycookie.HashValue{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySessionWithOptions(cookieName, opts).SetCookieValue(cv)))
|
||||||
|
|
||||||
logger.Debugf("Sticky session cookie name: %v", cookieName)
|
logger.Debugf("Sticky session cookie name: %v", cookieName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
serviceName string
|
serviceName string
|
||||||
service *dynamic.ServersLoadBalancer
|
service *dynamic.ServersLoadBalancer
|
||||||
responseModifier func(*http.Response) error
|
responseModifier func(*http.Response) error
|
||||||
|
cookieRawValue string
|
||||||
|
|
||||||
expected []ExpectedResult
|
expected []ExpectedResult
|
||||||
}{
|
}{
|
||||||
|
@ -258,6 +259,34 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Cookie value is backward compatible",
|
||||||
|
serviceName: "test",
|
||||||
|
service: &dynamic.ServersLoadBalancer{
|
||||||
|
Sticky: &dynamic.Sticky{
|
||||||
|
Cookie: &dynamic.Cookie{},
|
||||||
|
},
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: server1.URL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: server2.URL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cookieRawValue: "_6f743=" + server1.URL,
|
||||||
|
expected: []ExpectedResult{
|
||||||
|
{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
XFrom: "first",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
XFrom: "first",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
@ -269,6 +298,10 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
assert.NotNil(t, handler)
|
assert.NotNil(t, handler)
|
||||||
|
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://callme", nil)
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://callme", nil)
|
||||||
|
if test.cookieRawValue != "" {
|
||||||
|
req.Header.Set("Cookie", test.cookieRawValue)
|
||||||
|
}
|
||||||
|
|
||||||
for _, expected := range test.expected {
|
for _, expected := range test.expected {
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
|
@ -282,6 +315,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||||
req.Header.Set("Cookie", cookieHeader)
|
req.Header.Set("Cookie", cookieHeader)
|
||||||
assert.Equal(t, expected.SecureCookie, strings.Contains(cookieHeader, "Secure"))
|
assert.Equal(t, expected.SecureCookie, strings.Contains(cookieHeader, "Secure"))
|
||||||
assert.Equal(t, expected.HTTPOnlyCookie, strings.Contains(cookieHeader, "HttpOnly"))
|
assert.Equal(t, expected.HTTPOnlyCookie, strings.Contains(cookieHeader, "HttpOnly"))
|
||||||
|
assert.NotContains(t, cookieHeader, "://")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue