2017-02-07 21:33:23 +00:00
|
|
|
package roundrobin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
)
|
|
|
|
|
2018-07-11 08:08:03 +00:00
|
|
|
// StickySession is a mixin for load balancers that implements layer 7 (http cookie) session affinity
|
2017-02-07 21:33:23 +00:00
|
|
|
type StickySession struct {
|
2017-11-22 17:20:03 +00:00
|
|
|
cookieName string
|
2019-06-12 22:42:06 +00:00
|
|
|
options CookieOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// CookieOptions has all the options one would like to set on the affinity cookie
|
|
|
|
type CookieOptions struct {
|
|
|
|
HTTPOnly bool
|
|
|
|
Secure bool
|
2017-02-07 21:33:23 +00:00
|
|
|
}
|
|
|
|
|
2018-07-11 08:08:03 +00:00
|
|
|
// NewStickySession creates a new StickySession
|
2017-11-22 17:20:03 +00:00
|
|
|
func NewStickySession(cookieName string) *StickySession {
|
2018-07-11 08:08:03 +00:00
|
|
|
return &StickySession{cookieName: cookieName}
|
2017-02-07 21:33:23 +00:00
|
|
|
}
|
|
|
|
|
2019-06-12 22:42:06 +00:00
|
|
|
// NewStickySessionWithOptions creates a new StickySession whilst allowing for options to
|
|
|
|
// shape its affinity cookie such as "httpOnly" or "secure"
|
|
|
|
func NewStickySessionWithOptions(cookieName string, options CookieOptions) *StickySession {
|
|
|
|
return &StickySession{cookieName: cookieName, options: options}
|
|
|
|
}
|
|
|
|
|
2017-02-07 21:33:23 +00:00
|
|
|
// GetBackend returns the backend URL stored in the sticky cookie, iff the backend is still in the valid list of servers.
|
|
|
|
func (s *StickySession) GetBackend(req *http.Request, servers []*url.URL) (*url.URL, bool, error) {
|
2017-11-22 17:20:03 +00:00
|
|
|
cookie, err := req.Cookie(s.cookieName)
|
2017-02-07 21:33:23 +00:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
case http.ErrNoCookie:
|
|
|
|
return nil, false, nil
|
|
|
|
default:
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2017-11-22 17:20:03 +00:00
|
|
|
serverURL, err := url.Parse(cookie.Value)
|
2017-02-07 21:33:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2017-11-22 17:20:03 +00:00
|
|
|
if s.isBackendAlive(serverURL, servers) {
|
|
|
|
return serverURL, true, nil
|
2017-02-07 21:33:23 +00:00
|
|
|
}
|
2018-07-11 08:08:03 +00:00
|
|
|
return nil, false, nil
|
2017-02-07 21:33:23 +00:00
|
|
|
}
|
|
|
|
|
2018-07-11 08:08:03 +00:00
|
|
|
// StickBackend creates and sets the cookie
|
2017-02-07 21:33:23 +00:00
|
|
|
func (s *StickySession) StickBackend(backend *url.URL, w *http.ResponseWriter) {
|
2019-06-12 22:42:06 +00:00
|
|
|
opt := s.options
|
|
|
|
cookie := &http.Cookie{Name: s.cookieName, Value: backend.String(), Path: "/", HttpOnly: opt.HTTPOnly, Secure: opt.Secure}
|
2017-11-22 17:20:03 +00:00
|
|
|
http.SetCookie(*w, cookie)
|
2017-02-07 21:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StickySession) isBackendAlive(needle *url.URL, haystack []*url.URL) bool {
|
|
|
|
if len(haystack) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-11-22 17:20:03 +00:00
|
|
|
for _, serverURL := range haystack {
|
|
|
|
if sameURL(needle, serverURL) {
|
2017-02-07 21:33:23 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|