Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 14:30 UTC
reverseproxy: Use xxhash instead of fnv32 for LB (#6203)
* Added Faster Non-cryptographic Hash Function for Load Balancing * Ran golangci-lint * Updated hash version and hash return type
Hayder committed
Mar 29, 2024 at 12:56 UTC
74949fb0914d7d496efadf51ef2dd81e64b1b7d0
2 files changed
+88
-87
modules/caddyhttp/reverseproxy/selectionpolicies.go
+6
-5
@@ -20,7 +20,6 @@ import (
20
"encoding/hex"
21
"encoding/json"
22
"fmt"
23
- "hash/fnv"
23
weakrand "math/rand"
24
"net"
25
"net/http"
@@ -28,6 +27,8 @@ import (
27
"strings"
28
"sync/atomic"
29
30
+ "github.com/cespare/xxhash/v2"
31
+
32
"github.com/caddyserver/caddy/v2"
33
"github.com/caddyserver/caddy/v2/caddyconfig"
34
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@@ -807,7 +808,7 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
808
// see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0,
809
// https://randorithms.com/2020/12/26/rendezvous-hashing.html,
810
// and https://en.wikipedia.org/wiki/Rendezvous_hashing.
810
- var highestHash uint32
811
+ var highestHash uint64
812
var upstream *Upstream
813
for _, up := range pool {
814
if !up.Available() {
@@ -823,10 +824,10 @@ func hostByHashing(pool []*Upstream, s string) *Upstream {
824
}
825
826
// hash calculates a fast hash based on s.
826
-func hash(s string) uint32 {
827
- h := fnv.New32a()
827
+func hash(s string) uint64 {
828
+ h := xxhash.New()
829
_, _ = h.Write([]byte(s))
829
- return h.Sum32()
830
+ return h.Sum64()
831
}
832
833
func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {
modules/caddyhttp/reverseproxy/selectionpolicies_test.go
+82
-82
@@ -157,8 +157,8 @@ func TestIPHashPolicy(t *testing.T) {
157
// We should be able to predict where every request is routed.
158
req.RemoteAddr = "172.0.0.1:80"
159
h := ipHash.Select(pool, req, nil)
160
- if h != pool[1] {
161
- t.Error("Expected ip hash policy host to be the second host.")
160
+ if h != pool[0] {
161
+ t.Error("Expected ip hash policy host to be the first host.")
162
}
163
req.RemoteAddr = "172.0.0.2:80"
164
h = ipHash.Select(pool, req, nil)
@@ -167,8 +167,8 @@ func TestIPHashPolicy(t *testing.T) {
167
}
168
req.RemoteAddr = "172.0.0.3:80"
169
h = ipHash.Select(pool, req, nil)
170
- if h != pool[1] {
171
- t.Error("Expected ip hash policy host to be the second host.")
170
+ if h != pool[0] {
171
+ t.Error("Expected ip hash policy host to be the first host.")
172
}
173
req.RemoteAddr = "172.0.0.4:80"
174
h = ipHash.Select(pool, req, nil)
@@ -179,8 +179,8 @@ func TestIPHashPolicy(t *testing.T) {
179
// we should get the same results without a port
180
req.RemoteAddr = "172.0.0.1"
181
h = ipHash.Select(pool, req, nil)
182
- if h != pool[1] {
183
- t.Error("Expected ip hash policy host to be the second host.")
182
+ if h != pool[0] {
183
+ t.Error("Expected ip hash policy host to be the first host.")
184
}
185
req.RemoteAddr = "172.0.0.2"
186
h = ipHash.Select(pool, req, nil)
@@ -189,8 +189,8 @@ func TestIPHashPolicy(t *testing.T) {
189
}
190
req.RemoteAddr = "172.0.0.3"
191
h = ipHash.Select(pool, req, nil)
192
- if h != pool[1] {
193
- t.Error("Expected ip hash policy host to be the second host.")
192
+ if h != pool[0] {
193
+ t.Error("Expected ip hash policy host to be the first host.")
194
}
195
req.RemoteAddr = "172.0.0.4"
196
h = ipHash.Select(pool, req, nil)
@@ -203,8 +203,8 @@ func TestIPHashPolicy(t *testing.T) {
203
req.RemoteAddr = "172.0.0.4"
204
pool[1].setHealthy(false)
205
h = ipHash.Select(pool, req, nil)
206
- if h != pool[0] {
207
- t.Error("Expected ip hash policy host to be the first host.")
206
+ if h != pool[2] {
207
+ t.Error("Expected ip hash policy host to be the third host.")
208
}
209
210
req.RemoteAddr = "172.0.0.2"
@@ -217,8 +217,8 @@ func TestIPHashPolicy(t *testing.T) {
217
req.RemoteAddr = "172.0.0.3"
218
pool[2].setHealthy(false)
219
h = ipHash.Select(pool, req, nil)
220
- if h != pool[1] {
221
- t.Error("Expected ip hash policy host to be the second host.")
220
+ if h != pool[0] {
221
+ t.Error("Expected ip hash policy host to be the first host.")
222
}
223
req.RemoteAddr = "172.0.0.4"
224
h = ipHash.Select(pool, req, nil)
@@ -300,8 +300,8 @@ func TestClientIPHashPolicy(t *testing.T) {
300
// We should be able to predict where every request is routed.
301
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1:80")
302
h := ipHash.Select(pool, req, nil)
303
- if h != pool[1] {
304
- t.Error("Expected ip hash policy host to be the second host.")
303
+ if h != pool[0] {
304
+ t.Error("Expected ip hash policy host to be the first host.")
305
}
306
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2:80")
307
h = ipHash.Select(pool, req, nil)
@@ -310,8 +310,8 @@ func TestClientIPHashPolicy(t *testing.T) {
310
}
311
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3:80")
312
h = ipHash.Select(pool, req, nil)
313
- if h != pool[1] {
314
- t.Error("Expected ip hash policy host to be the second host.")
313
+ if h != pool[0] {
314
+ t.Error("Expected ip hash policy host to be the first host.")
315
}
316
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4:80")
317
h = ipHash.Select(pool, req, nil)
@@ -322,8 +322,8 @@ func TestClientIPHashPolicy(t *testing.T) {
322
// we should get the same results without a port
323
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.1")
324
h = ipHash.Select(pool, req, nil)
325
- if h != pool[1] {
326
- t.Error("Expected ip hash policy host to be the second host.")
325
+ if h != pool[0] {
326
+ t.Error("Expected ip hash policy host to be the first host.")
327
}
328
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
329
h = ipHash.Select(pool, req, nil)
@@ -332,8 +332,8 @@ func TestClientIPHashPolicy(t *testing.T) {
332
}
333
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
334
h = ipHash.Select(pool, req, nil)
335
- if h != pool[1] {
336
- t.Error("Expected ip hash policy host to be the second host.")
335
+ if h != pool[0] {
336
+ t.Error("Expected ip hash policy host to be the first host.")
337
}
338
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
339
h = ipHash.Select(pool, req, nil)
@@ -346,8 +346,8 @@ func TestClientIPHashPolicy(t *testing.T) {
346
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
347
pool[1].setHealthy(false)
348
h = ipHash.Select(pool, req, nil)
349
- if h != pool[0] {
350
- t.Error("Expected ip hash policy host to be the first host.")
349
+ if h != pool[2] {
350
+ t.Error("Expected ip hash policy host to be the third host.")
351
}
352
353
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.2")
@@ -360,8 +360,8 @@ func TestClientIPHashPolicy(t *testing.T) {
360
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.3")
361
pool[2].setHealthy(false)
362
h = ipHash.Select(pool, req, nil)
363
- if h != pool[1] {
364
- t.Error("Expected ip hash policy host to be the second host.")
363
+ if h != pool[0] {
364
+ t.Error("Expected ip hash policy host to be the first host.")
365
}
366
caddyhttp.SetVar(req.Context(), caddyhttp.ClientIPVarKey, "172.0.0.4")
367
h = ipHash.Select(pool, req, nil)
@@ -470,21 +470,21 @@ func TestQueryHashPolicy(t *testing.T) {
470
471
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
472
h = queryPolicy.Select(pool, request, nil)
473
- if h != pool[0] {
474
- t.Error("Expected query policy host to be the first host.")
473
+ if h != pool[1] {
474
+ t.Error("Expected query policy host to be the second host.")
475
}
476
477
request = httptest.NewRequest(http.MethodGet, "/?foo=1", nil)
478
pool[0].setHealthy(false)
479
h = queryPolicy.Select(pool, request, nil)
480
- if h != pool[1] {
481
- t.Error("Expected query policy host to be the second host.")
480
+ if h != pool[2] {
481
+ t.Error("Expected query policy host to be the third host.")
482
}
483
484
request = httptest.NewRequest(http.MethodGet, "/?foo=100000", nil)
485
h = queryPolicy.Select(pool, request, nil)
486
- if h != pool[2] {
487
- t.Error("Expected query policy host to be the third host.")
486
+ if h != pool[1] {
487
+ t.Error("Expected query policy host to be the second host.")
488
}
489
490
// We should be able to resize the host pool and still be able to predict
@@ -533,14 +533,14 @@ func TestURIHashPolicy(t *testing.T) {
533
534
request := httptest.NewRequest(http.MethodGet, "/test", nil)
535
h := uriPolicy.Select(pool, request, nil)
536
- if h != pool[1] {
537
- t.Error("Expected uri policy host to be the second host.")
536
+ if h != pool[2] {
537
+ t.Error("Expected uri policy host to be the third host.")
538
}
539
540
pool[2].setHealthy(false)
541
h = uriPolicy.Select(pool, request, nil)
542
- if h != pool[1] {
543
- t.Error("Expected uri policy host to be the second host.")
542
+ if h != pool[0] {
543
+ t.Error("Expected uri policy host to be the first host.")
544
}
545
546
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
@@ -691,54 +691,54 @@ func TestCookieHashPolicy(t *testing.T) {
691
}
692
693
func TestCookieHashPolicyWithSecureRequest(t *testing.T) {
694
- ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
695
- defer cancel()
696
- cookieHashPolicy := CookieHashSelection{}
697
- if err := cookieHashPolicy.Provision(ctx); err != nil {
698
- t.Errorf("Provision error: %v", err)
699
- t.FailNow()
700
- }
701
-
702
- pool := testPool()
703
- pool[0].Dial = "localhost:8080"
704
- pool[1].Dial = "localhost:8081"
705
- pool[2].Dial = "localhost:8082"
706
- pool[0].setHealthy(true)
707
- pool[1].setHealthy(false)
708
- pool[2].setHealthy(false)
709
-
710
- // Create a test server that serves HTTPS requests
711
- ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
712
- h := cookieHashPolicy.Select(pool, r, w)
713
- if h != pool[0] {
714
- t.Error("Expected cookieHashPolicy host to be the first only available host.")
715
- }
716
- }))
717
- defer ts.Close()
718
-
719
- // Make a new HTTPS request to the test server
720
- client := ts.Client()
721
- request, err := http.NewRequest(http.MethodGet, ts.URL+"/test", nil)
722
- if err != nil {
723
- t.Fatal(err)
724
- }
725
- response, err := client.Do(request)
726
- if err != nil {
727
- t.Fatal(err)
728
- }
729
-
730
- // Check if the cookie set is Secure and has SameSiteNone mode
731
- cookies := response.Cookies()
732
- if len(cookies) == 0 {
733
- t.Fatal("Expected a cookie to be set")
734
- }
735
- cookie := cookies[0]
736
- if !cookie.Secure {
737
- t.Error("Expected cookie Secure attribute to be true when request is secure")
738
- }
739
- if cookie.SameSite != http.SameSiteNoneMode {
740
- t.Error("Expected cookie SameSite attribute to be None when request is secure")
741
- }
694
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
695
+ defer cancel()
696
+ cookieHashPolicy := CookieHashSelection{}
697
+ if err := cookieHashPolicy.Provision(ctx); err != nil {
698
+ t.Errorf("Provision error: %v", err)
699
+ t.FailNow()
700
+ }
701
+
702
+ pool := testPool()
703
+ pool[0].Dial = "localhost:8080"
704
+ pool[1].Dial = "localhost:8081"
705
+ pool[2].Dial = "localhost:8082"
706
+ pool[0].setHealthy(true)
707
+ pool[1].setHealthy(false)
708
+ pool[2].setHealthy(false)
709
+
710
+ // Create a test server that serves HTTPS requests
711
+ ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
712
+ h := cookieHashPolicy.Select(pool, r, w)
713
+ if h != pool[0] {
714
+ t.Error("Expected cookieHashPolicy host to be the first only available host.")
715
+ }
716
+ }))
717
+ defer ts.Close()
718
+
719
+ // Make a new HTTPS request to the test server
720
+ client := ts.Client()
721
+ request, err := http.NewRequest(http.MethodGet, ts.URL+"/test", nil)
722
+ if err != nil {
723
+ t.Fatal(err)
724
+ }
725
+ response, err := client.Do(request)
726
+ if err != nil {
727
+ t.Fatal(err)
728
+ }
729
+
730
+ // Check if the cookie set is Secure and has SameSiteNone mode
731
+ cookies := response.Cookies()
732
+ if len(cookies) == 0 {
733
+ t.Fatal("Expected a cookie to be set")
734
+ }
735
+ cookie := cookies[0]
736
+ if !cookie.Secure {
737
+ t.Error("Expected cookie Secure attribute to be true when request is secure")
738
+ }
739
+ if cookie.SameSite != http.SameSiteNoneMode {
740
+ t.Error("Expected cookie SameSite attribute to be None when request is secure")
741
+ }
742
}
743
744
func TestCookieHashPolicyWithFirstFallback(t *testing.T) {