@marcel91200 / caddy Mirror · read-only / commits / 6e0849d4
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

reverseproxy: Implement cookie hash selection policy (#3809)

* add CookieHashSelection for session affinity * add CookieHashSelection for session affinity * register module * reverse_proxy: Add and fix cookie lb_policy * reverse_proxy: Manage hmac.write error on cookie hash selection * reverse_proxy: fix some comments * reverse_proxy: variable `cookieValue` is inside the else block * reverse_proxy: Abstract duplicate nuanced logic of reservoir sampling into a function * reverse_proxy: Set a default secret is indeed useless * reverse_proxy: add configuration syntax for cookie lb_policy * reverse_proxy: doc typo and improvement Co-authored-by: utick <123liuqingdong@163.com>

Dimitri Masson committed Nov 20, 2020 at 20:39 UTC 6e0849d4c2855375b66974f85eb9840d05197694
3 files changed +208 -63
modules/caddyhttp/reverseproxy/reverseproxy.go
+2 -2
@@ -368,7 +368,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
368 var proxyErr error
369 for {
370 // choose an available upstream
371 - upstream := h.LoadBalancing.SelectionPolicy.Select(h.Upstreams, r)
371 + upstream := h.LoadBalancing.SelectionPolicy.Select(h.Upstreams, r, w)
372 if upstream == nil {
373 if proxyErr == nil {
374 proxyErr = fmt.Errorf("no upstreams available")
@@ -816,7 +816,7 @@ type LoadBalancing struct {
816
817 // Selector selects an available upstream from the pool.
818 type Selector interface {
819 - Select(UpstreamPool, *http.Request) *Upstream
819 + Select(UpstreamPool, *http.Request, http.ResponseWriter) *Upstream
820 }
821
822 // Hop-by-hop headers. These are removed when sent to the backend.
modules/caddyhttp/reverseproxy/selectionpolicies.go
+123 -26
@@ -15,6 +15,9 @@
15 package reverseproxy
16
17 import (
18 + "crypto/hmac"
19 + "crypto/sha256"
20 + "encoding/hex"
21 "fmt"
22 "hash/fnv"
23 weakrand "math/rand"
@@ -37,6 +40,7 @@ func init() {
40 caddy.RegisterModule(IPHashSelection{})
41 caddy.RegisterModule(URIHashSelection{})
42 caddy.RegisterModule(HeaderHashSelection{})
43 + caddy.RegisterModule(CookieHashSelection{})
44
45 weakrand.Seed(time.Now().UTC().UnixNano())
46 }
@@ -54,24 +58,8 @@ func (RandomSelection) CaddyModule() caddy.ModuleInfo {
58 }
59
60 // Select returns an available host, if any.
57 -func (r RandomSelection) Select(pool UpstreamPool, request *http.Request) *Upstream {
58 - // use reservoir sampling because the number of available
59 - // hosts isn't known: https://en.wikipedia.org/wiki/Reservoir_sampling
60 - var randomHost *Upstream
61 - var count int
62 - for _, upstream := range pool {
63 - if !upstream.Available() {
64 - continue
65 - }
66 - // (n % 1 == 0) holds for all n, therefore a
67 - // upstream will always be chosen if there is at
68 - // least one available
69 - count++
70 - if (weakrand.Int() % count) == 0 {
71 - randomHost = upstream
72 - }
73 - }
74 - return randomHost
61 +func (r RandomSelection) Select(pool UpstreamPool, request *http.Request, _ http.ResponseWriter) *Upstream {
62 + return selectRandomHost(pool)
63 }
64
65 // UnmarshalCaddyfile sets up the module from Caddyfile tokens.
@@ -134,7 +122,7 @@ func (r RandomChoiceSelection) Validate() error {
122 }
123
124 // Select returns an available host, if any.
137 -func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *http.Request) *Upstream {
125 +func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream {
126 k := r.Choose
127 if k > len(pool) {
128 k = len(pool)
@@ -174,7 +162,7 @@ func (LeastConnSelection) CaddyModule() caddy.ModuleInfo {
162 // Select selects the up host with the least number of connections in the
163 // pool. If more than one host has the same least number of connections,
164 // one of the hosts is chosen at random.
177 -func (LeastConnSelection) Select(pool UpstreamPool, _ *http.Request) *Upstream {
165 +func (LeastConnSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream {
166 var bestHost *Upstream
167 var count int
168 leastReqs := -1
@@ -227,7 +215,7 @@ func (RoundRobinSelection) CaddyModule() caddy.ModuleInfo {
215 }
216
217 // Select returns an available host, if any.
230 -func (r *RoundRobinSelection) Select(pool UpstreamPool, _ *http.Request) *Upstream {
218 +func (r *RoundRobinSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream {
219 n := uint32(len(pool))
220 if n == 0 {
221 return nil
@@ -265,7 +253,7 @@ func (FirstSelection) CaddyModule() caddy.ModuleInfo {
253 }
254
255 // Select returns an available host, if any.
268 -func (FirstSelection) Select(pool UpstreamPool, _ *http.Request) *Upstream {
256 +func (FirstSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream {
257 for _, host := range pool {
258 if host.Available() {
259 return host
@@ -297,7 +285,7 @@ func (IPHashSelection) CaddyModule() caddy.ModuleInfo {
285 }
286
287 // Select returns an available host, if any.
300 -func (IPHashSelection) Select(pool UpstreamPool, req *http.Request) *Upstream {
288 +func (IPHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream {
289 clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
290 if err != nil {
291 clientIP = req.RemoteAddr
@@ -328,7 +316,7 @@ func (URIHashSelection) CaddyModule() caddy.ModuleInfo {
316 }
317
318 // Select returns an available host, if any.
331 -func (URIHashSelection) Select(pool UpstreamPool, req *http.Request) *Upstream {
319 +func (URIHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream {
320 return hostByHashing(pool, req.RequestURI)
321 }
322
@@ -358,7 +346,7 @@ func (HeaderHashSelection) CaddyModule() caddy.ModuleInfo {
346 }
347
348 // Select returns an available host, if any.
361 -func (s HeaderHashSelection) Select(pool UpstreamPool, req *http.Request) *Upstream {
349 +func (s HeaderHashSelection) Select(pool UpstreamPool, req *http.Request, _ http.ResponseWriter) *Upstream {
350 if s.Field == "" {
351 return nil
352 }
@@ -371,7 +359,7 @@ func (s HeaderHashSelection) Select(pool UpstreamPool, req *http.Request) *Upstr
359
360 val := req.Header.Get(s.Field)
361 if val == "" {
374 - return RandomSelection{}.Select(pool, req)
362 + return RandomSelection{}.Select(pool, req, nil)
363 }
364 return hostByHashing(pool, val)
365 }
@@ -387,6 +375,114 @@ func (s *HeaderHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
375 return nil
376 }
377
378 +// CookieHashSelection is a policy that selects
379 +// a host based on a given cookie name.
380 +type CookieHashSelection struct {
381 + // The HTTP cookie name whose value is to be hashed and used for upstream selection.
382 + Name string `json:"name,omitempty"`
383 + // Secret to hash (Hmac256) chosen upstream in cookie
384 + Secret string `json:"secret,omitempty"`
385 +}
386 +
387 +// CaddyModule returns the Caddy module information.
388 +func (CookieHashSelection) CaddyModule() caddy.ModuleInfo {
389 + return caddy.ModuleInfo{
390 + ID: "http.reverse_proxy.selection_policies.cookie",
391 + New: func() caddy.Module { return new(CookieHashSelection) },
392 + }
393 +}
394 +
395 +// Select returns an available host, if any.
396 +func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Request, w http.ResponseWriter) *Upstream {
397 + if s.Name == "" {
398 + s.Name = "lb"
399 + }
400 + cookie, err := req.Cookie(s.Name)
401 + // If there's no cookie, select new random host
402 + if err != nil || cookie == nil {
403 + return selectNewHostWithCookieHashSelection(pool, w, s.Secret, s.Name)
404 + } else {
405 + // If the cookie is present, loop over the available upstreams until we find a match
406 + cookieValue := cookie.Value
407 + for _, upstream := range pool {
408 + if !upstream.Available() {
409 + continue
410 + }
411 + sha, err := hashCookie(s.Secret, upstream.Dial)
412 + if err == nil && sha == cookieValue {
413 + return upstream
414 + }
415 + }
416 + }
417 + // If there is no matching host, select new random host
418 + return selectNewHostWithCookieHashSelection(pool, w, s.Secret, s.Name)
419 +}
420 +
421 +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
422 +// lb_policy cookie [<name> [<secret>]]
423 +//
424 +// By default name is `lb`
425 +func (s *CookieHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
426 + args := d.RemainingArgs()
427 + switch len(args) {
428 + case 1:
429 + case 2:
430 + s.Name = args[1]
431 + case 3:
432 + s.Name = args[1]
433 + s.Secret = args[2]
434 + default:
435 + return d.ArgErr()
436 + }
437 + return nil
438 +}
439 +
440 +// Select a new Host randomly and add a sticky session cookie
441 +func selectNewHostWithCookieHashSelection(pool []*Upstream, w http.ResponseWriter, cookieSecret string, cookieName string) *Upstream {
442 + randomHost := selectRandomHost(pool)
443 +
444 + if randomHost != nil {
445 + // Hash (HMAC with some key for privacy) the upstream.Dial string as the cookie value
446 + sha, err := hashCookie(cookieSecret, randomHost.Dial)
447 + if err == nil {
448 + // write the cookie.
449 + http.SetCookie(w, &http.Cookie{Name: cookieName, Value: sha, Secure: false})
450 + }
451 + }
452 + return randomHost
453 +}
454 +
455 +// hashCookie hashes (HMAC 256) some data with the secret
456 +func hashCookie(secret string, data string) (string, error) {
457 + h := hmac.New(sha256.New, []byte(secret))
458 + _, err := h.Write([]byte(data))
459 + if err != nil {
460 + return "", err
461 + }
462 + return hex.EncodeToString(h.Sum(nil)), nil
463 +}
464 +
465 +// selectRandomHost returns a random available host
466 +func selectRandomHost(pool []*Upstream) *Upstream {
467 + // use reservoir sampling because the number of available
468 + // hosts isn't known: https://en.wikipedia.org/wiki/Reservoir_sampling
469 + var randomHost *Upstream
470 + var count int
471 + for _, upstream := range pool {
472 + if !upstream.Available() {
473 + continue
474 + }
475 + // (n % 1 == 0) holds for all n, therefore a
476 + // upstream will always be chosen if there is at
477 + // least one available
478 + count++
479 + if (weakrand.Int() % count) == 0 {
480 + randomHost = upstream
481 + }
482 + }
483 + return randomHost
484 +}
485 +
486 // leastRequests returns the host with the
487 // least number of active requests to it.
488 // If more than one host has the same
@@ -454,6 +550,7 @@ var (
550 _ Selector = (*IPHashSelection)(nil)
551 _ Selector = (*URIHashSelection)(nil)
552 _ Selector = (*HeaderHashSelection)(nil)
553 + _ Selector = (*CookieHashSelection)(nil)
554
555 _ caddy.Validator = (*RandomChoiceSelection)(nil)
556 _ caddy.Provisioner = (*RandomChoiceSelection)(nil)
modules/caddyhttp/reverseproxy/selectionpolicies_test.go
+83 -35
@@ -33,37 +33,37 @@ func TestRoundRobinPolicy(t *testing.T) {
33 rrPolicy := new(RoundRobinSelection)
34 req, _ := http.NewRequest("GET", "/", nil)
35
36 - h := rrPolicy.Select(pool, req)
36 + h := rrPolicy.Select(pool, req, nil)
37 // First selected host is 1, because counter starts at 0
38 // and increments before host is selected
39 if h != pool[1] {
40 t.Error("Expected first round robin host to be second host in the pool.")
41 }
42 - h = rrPolicy.Select(pool, req)
42 + h = rrPolicy.Select(pool, req, nil)
43 if h != pool[2] {
44 t.Error("Expected second round robin host to be third host in the pool.")
45 }
46 - h = rrPolicy.Select(pool, req)
46 + h = rrPolicy.Select(pool, req, nil)
47 if h != pool[0] {
48 t.Error("Expected third round robin host to be first host in the pool.")
49 }
50 // mark host as down
51 pool[1].SetHealthy(false)
52 - h = rrPolicy.Select(pool, req)
52 + h = rrPolicy.Select(pool, req, nil)
53 if h != pool[2] {
54 t.Error("Expected to skip down host.")
55 }
56 // mark host as up
57 pool[1].SetHealthy(true)
58
59 - h = rrPolicy.Select(pool, req)
59 + h = rrPolicy.Select(pool, req, nil)
60 if h == pool[2] {
61 t.Error("Expected to balance evenly among healthy hosts")
62 }
63 // mark host as full
64 pool[1].CountRequest(1)
65 pool[1].MaxRequests = 1
66 - h = rrPolicy.Select(pool, req)
66 + h = rrPolicy.Select(pool, req, nil)
67 if h != pool[2] {
68 t.Error("Expected to skip full host.")
69 }
@@ -76,12 +76,12 @@ func TestLeastConnPolicy(t *testing.T) {
76
77 pool[0].CountRequest(10)
78 pool[1].CountRequest(10)
79 - h := lcPolicy.Select(pool, req)
79 + h := lcPolicy.Select(pool, req, nil)
80 if h != pool[2] {
81 t.Error("Expected least connection host to be third host.")
82 }
83 pool[2].CountRequest(100)
84 - h = lcPolicy.Select(pool, req)
84 + h = lcPolicy.Select(pool, req, nil)
85 if h != pool[0] && h != pool[1] {
86 t.Error("Expected least connection host to be first or second host.")
87 }
@@ -94,44 +94,44 @@ func TestIPHashPolicy(t *testing.T) {
94
95 // We should be able to predict where every request is routed.
96 req.RemoteAddr = "172.0.0.1:80"
97 - h := ipHash.Select(pool, req)
97 + h := ipHash.Select(pool, req, nil)
98 if h != pool[1] {
99 t.Error("Expected ip hash policy host to be the second host.")
100 }
101 req.RemoteAddr = "172.0.0.2:80"
102 - h = ipHash.Select(pool, req)
102 + h = ipHash.Select(pool, req, nil)
103 if h != pool[1] {
104 t.Error("Expected ip hash policy host to be the second host.")
105 }
106 req.RemoteAddr = "172.0.0.3:80"
107 - h = ipHash.Select(pool, req)
107 + h = ipHash.Select(pool, req, nil)
108 if h != pool[2] {
109 t.Error("Expected ip hash policy host to be the third host.")
110 }
111 req.RemoteAddr = "172.0.0.4:80"
112 - h = ipHash.Select(pool, req)
112 + h = ipHash.Select(pool, req, nil)
113 if h != pool[1] {
114 t.Error("Expected ip hash policy host to be the second host.")
115 }
116
117 // we should get the same results without a port
118 req.RemoteAddr = "172.0.0.1"
119 - h = ipHash.Select(pool, req)
119 + h = ipHash.Select(pool, req, nil)
120 if h != pool[1] {
121 t.Error("Expected ip hash policy host to be the second host.")
122 }
123 req.RemoteAddr = "172.0.0.2"
124 - h = ipHash.Select(pool, req)
124 + h = ipHash.Select(pool, req, nil)
125 if h != pool[1] {
126 t.Error("Expected ip hash policy host to be the second host.")
127 }
128 req.RemoteAddr = "172.0.0.3"
129 - h = ipHash.Select(pool, req)
129 + h = ipHash.Select(pool, req, nil)
130 if h != pool[2] {
131 t.Error("Expected ip hash policy host to be the third host.")
132 }
133 req.RemoteAddr = "172.0.0.4"
134 - h = ipHash.Select(pool, req)
134 + h = ipHash.Select(pool, req, nil)
135 if h != pool[1] {
136 t.Error("Expected ip hash policy host to be the second host.")
137 }
@@ -140,13 +140,13 @@ func TestIPHashPolicy(t *testing.T) {
140 // healthy host is available
141 req.RemoteAddr = "172.0.0.1"
142 pool[1].SetHealthy(false)
143 - h = ipHash.Select(pool, req)
143 + h = ipHash.Select(pool, req, nil)
144 if h != pool[2] {
145 t.Error("Expected ip hash policy host to be the third host.")
146 }
147
148 req.RemoteAddr = "172.0.0.2"
149 - h = ipHash.Select(pool, req)
149 + h = ipHash.Select(pool, req, nil)
150 if h != pool[2] {
151 t.Error("Expected ip hash policy host to be the third host.")
152 }
@@ -154,12 +154,12 @@ func TestIPHashPolicy(t *testing.T) {
154
155 req.RemoteAddr = "172.0.0.3"
156 pool[2].SetHealthy(false)
157 - h = ipHash.Select(pool, req)
157 + h = ipHash.Select(pool, req, nil)
158 if h != pool[0] {
159 t.Error("Expected ip hash policy host to be the first host.")
160 }
161 req.RemoteAddr = "172.0.0.4"
162 - h = ipHash.Select(pool, req)
162 + h = ipHash.Select(pool, req, nil)
163 if h != pool[1] {
164 t.Error("Expected ip hash policy host to be the second host.")
165 }
@@ -171,22 +171,22 @@ func TestIPHashPolicy(t *testing.T) {
171 {Host: new(upstreamHost)},
172 }
173 req.RemoteAddr = "172.0.0.1:80"
174 - h = ipHash.Select(pool, req)
174 + h = ipHash.Select(pool, req, nil)
175 if h != pool[0] {
176 t.Error("Expected ip hash policy host to be the first host.")
177 }
178 req.RemoteAddr = "172.0.0.2:80"
179 - h = ipHash.Select(pool, req)
179 + h = ipHash.Select(pool, req, nil)
180 if h != pool[1] {
181 t.Error("Expected ip hash policy host to be the second host.")
182 }
183 req.RemoteAddr = "172.0.0.3:80"
184 - h = ipHash.Select(pool, req)
184 + h = ipHash.Select(pool, req, nil)
185 if h != pool[0] {
186 t.Error("Expected ip hash policy host to be the first host.")
187 }
188 req.RemoteAddr = "172.0.0.4:80"
189 - h = ipHash.Select(pool, req)
189 + h = ipHash.Select(pool, req, nil)
190 if h != pool[1] {
191 t.Error("Expected ip hash policy host to be the second host.")
192 }
@@ -194,7 +194,7 @@ func TestIPHashPolicy(t *testing.T) {
194 // We should get nil when there are no healthy hosts
195 pool[0].SetHealthy(false)
196 pool[1].SetHealthy(false)
197 - h = ipHash.Select(pool, req)
197 + h = ipHash.Select(pool, req, nil)
198 if h != nil {
199 t.Error("Expected ip hash policy host to be nil.")
200 }
@@ -205,13 +205,13 @@ func TestFirstPolicy(t *testing.T) {
205 firstPolicy := new(FirstSelection)
206 req := httptest.NewRequest(http.MethodGet, "/", nil)
207
208 - h := firstPolicy.Select(pool, req)
208 + h := firstPolicy.Select(pool, req, nil)
209 if h != pool[0] {
210 t.Error("Expected first policy host to be the first host.")
211 }
212
213 pool[0].SetHealthy(false)
214 - h = firstPolicy.Select(pool, req)
214 + h = firstPolicy.Select(pool, req, nil)
215 if h != pool[1] {
216 t.Error("Expected first policy host to be the second host.")
217 }
@@ -222,19 +222,19 @@ func TestURIHashPolicy(t *testing.T) {
222 uriPolicy := new(URIHashSelection)
223
224 request := httptest.NewRequest(http.MethodGet, "/test", nil)
225 - h := uriPolicy.Select(pool, request)
225 + h := uriPolicy.Select(pool, request, nil)
226 if h != pool[0] {
227 t.Error("Expected uri policy host to be the first host.")
228 }
229
230 pool[0].SetHealthy(false)
231 - h = uriPolicy.Select(pool, request)
231 + h = uriPolicy.Select(pool, request, nil)
232 if h != pool[1] {
233 t.Error("Expected uri policy host to be the first host.")
234 }
235
236 request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
237 - h = uriPolicy.Select(pool, request)
237 + h = uriPolicy.Select(pool, request, nil)
238 if h != pool[1] {
239 t.Error("Expected uri policy host to be the second host.")
240 }
@@ -247,26 +247,26 @@ func TestURIHashPolicy(t *testing.T) {
247 }
248
249 request = httptest.NewRequest(http.MethodGet, "/test", nil)
250 - h = uriPolicy.Select(pool, request)
250 + h = uriPolicy.Select(pool, request, nil)
251 if h != pool[0] {
252 t.Error("Expected uri policy host to be the first host.")
253 }
254
255 pool[0].SetHealthy(false)
256 - h = uriPolicy.Select(pool, request)
256 + h = uriPolicy.Select(pool, request, nil)
257 if h != pool[1] {
258 t.Error("Expected uri policy host to be the first host.")
259 }
260
261 request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
262 - h = uriPolicy.Select(pool, request)
262 + h = uriPolicy.Select(pool, request, nil)
263 if h != pool[1] {
264 t.Error("Expected uri policy host to be the second host.")
265 }
266
267 pool[0].SetHealthy(false)
268 pool[1].SetHealthy(false)
269 - h = uriPolicy.Select(pool, request)
269 + h = uriPolicy.Select(pool, request, nil)
270 if h != nil {
271 t.Error("Expected uri policy policy host to be nil.")
272 }
@@ -311,7 +311,7 @@ func TestRandomChoicePolicy(t *testing.T) {
311 randomChoicePolicy := new(RandomChoiceSelection)
312 randomChoicePolicy.Choose = 2
313
314 - h := randomChoicePolicy.Select(pool, request)
314 + h := randomChoicePolicy.Select(pool, request, nil)
315
316 if h == nil {
317 t.Error("RandomChoicePolicy should not return nil")
@@ -322,3 +322,51 @@ func TestRandomChoicePolicy(t *testing.T) {
322 }
323
324 }
325 +
326 +func TestCookieHashPolicy(t *testing.T) {
327 + pool := testPool()
328 + pool[0].Dial = "localhost:8080"
329 + pool[1].Dial = "localhost:8081"
330 + pool[2].Dial = "localhost:8082"
331 + pool[0].SetHealthy(true)
332 + pool[1].SetHealthy(false)
333 + pool[2].SetHealthy(false)
334 + request := httptest.NewRequest(http.MethodGet, "/test", nil)
335 + w := httptest.NewRecorder()
336 + cookieHashPolicy := new(CookieHashSelection)
337 + h := cookieHashPolicy.Select(pool, request, w)
338 + cookie_server1 := w.Result().Cookies()[0]
339 + if cookie_server1 == nil {
340 + t.Error("cookieHashPolicy should set a cookie")
341 + }
342 + if cookie_server1.Name != "lb" {
343 + t.Error("cookieHashPolicy should set a cookie with name lb")
344 + }
345 + if h != pool[0] {
346 + t.Error("Expected cookieHashPolicy host to be the first only available host.")
347 + }
348 + pool[1].SetHealthy(true)
349 + pool[2].SetHealthy(true)
350 + request = httptest.NewRequest(http.MethodGet, "/test", nil)
351 + w = httptest.NewRecorder()
352 + request.AddCookie(cookie_server1)
353 + h = cookieHashPolicy.Select(pool, request, w)
354 + if h != pool[0] {
355 + t.Error("Expected cookieHashPolicy host to stick to the first host (matching cookie).")
356 + }
357 + s := w.Result().Cookies()
358 + if len(s) != 0 {
359 + t.Error("Expected cookieHashPolicy to not set a new cookie.")
360 + }
361 + pool[0].SetHealthy(false)
362 + request = httptest.NewRequest(http.MethodGet, "/test", nil)
363 + w = httptest.NewRecorder()
364 + request.AddCookie(cookie_server1)
365 + h = cookieHashPolicy.Select(pool, request, w)
366 + if h == pool[0] {
367 + t.Error("Expected cookieHashPolicy to select a new host.")
368 + }
369 + if w.Result().Cookies() == nil {
370 + t.Error("Expected cookieHashPolicy to set a new cookie.")
371 + }
372 +}