mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	proxy: Add new URI hashing load balancing policy (#1679)
* Add uri policy test cases * Add function definition * Add uri hashing policy * Refactor and extract hostByHashing and use in IP and URI policy * Rename to URIHash Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>
This commit is contained in:
		
							parent
							
								
									b0cf3f0d2d
								
							
						
					
					
						commit
						ebf4279e98
					
				| @ -23,6 +23,7 @@ func init() { | ||||
| 	RegisterPolicy("round_robin", func() Policy { return &RoundRobin{} }) | ||||
| 	RegisterPolicy("ip_hash", func() Policy { return &IPHash{} }) | ||||
| 	RegisterPolicy("first", func() Policy { return &First{} }) | ||||
| 	RegisterPolicy("uri_hash", func() Policy { return &URIHash{} }) | ||||
| } | ||||
| 
 | ||||
| // Random is a policy that selects up hosts from a pool at random. | ||||
| @ -106,23 +107,10 @@ func (r *RoundRobin) Select(pool HostPool, request *http.Request) *UpstreamHost | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // IPHash is a policy that selects hosts based on hashing the request IP | ||||
| type IPHash struct{} | ||||
| 
 | ||||
| func hash(s string) uint32 { | ||||
| 	h := fnv.New32a() | ||||
| 	h.Write([]byte(s)) | ||||
| 	return h.Sum32() | ||||
| } | ||||
| 
 | ||||
| // Select selects an up host from the pool based on hashing the request IP | ||||
| func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost { | ||||
| // hostByHashing returns an available host from pool based on a hashable string | ||||
| func hostByHashing(pool HostPool, s string) *UpstreamHost { | ||||
| 	poolLen := uint32(len(pool)) | ||||
| 	clientIP, _, err := net.SplitHostPort(request.RemoteAddr) | ||||
| 	if err != nil { | ||||
| 		clientIP = request.RemoteAddr | ||||
| 	} | ||||
| 	index := hash(clientIP) % poolLen | ||||
| 	index := hash(s) % poolLen | ||||
| 	for i := uint32(0); i < poolLen; i++ { | ||||
| 		index += i | ||||
| 		host := pool[index%poolLen] | ||||
| @ -133,6 +121,33 @@ func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // hash calculates a hash based on string s | ||||
| func hash(s string) uint32 { | ||||
| 	h := fnv.New32a() | ||||
| 	h.Write([]byte(s)) | ||||
| 	return h.Sum32() | ||||
| } | ||||
| 
 | ||||
| // IPHash is a policy that selects hosts based on hashing the request IP | ||||
| type IPHash struct{} | ||||
| 
 | ||||
| // Select selects an up host from the pool based on hashing the request IP | ||||
| func (r *IPHash) Select(pool HostPool, request *http.Request) *UpstreamHost { | ||||
| 	clientIP, _, err := net.SplitHostPort(request.RemoteAddr) | ||||
| 	if err != nil { | ||||
| 		clientIP = request.RemoteAddr | ||||
| 	} | ||||
| 	return hostByHashing(pool, clientIP) | ||||
| } | ||||
| 
 | ||||
| // URIHash is a policy that selects the host based on hashing the request URI | ||||
| type URIHash struct{} | ||||
| 
 | ||||
| // Select selects the host based on hashing the URI | ||||
| func (r *URIHash) Select(pool HostPool, request *http.Request) *UpstreamHost { | ||||
| 	return hostByHashing(pool, request.RequestURI) | ||||
| } | ||||
| 
 | ||||
| // First is a policy that selects the first available host | ||||
| type First struct{} | ||||
| 
 | ||||
|  | ||||
| @ -243,3 +243,62 @@ func TestFirstPolicy(t *testing.T) { | ||||
| 		t.Error("Expected first policy host to be the second host.") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestUriPolicy(t *testing.T) { | ||||
| 	pool := testPool() | ||||
| 	uriPolicy := &URIHash{} | ||||
| 
 | ||||
| 	request := httptest.NewRequest(http.MethodGet, "/test", nil) | ||||
| 	h := uriPolicy.Select(pool, request) | ||||
| 	if h != pool[0] { | ||||
| 		t.Error("Expected uri policy host to be the first host.") | ||||
| 	} | ||||
| 
 | ||||
| 	pool[0].Unhealthy = 1 | ||||
| 	h = uriPolicy.Select(pool, request) | ||||
| 	if h != pool[1] { | ||||
| 		t.Error("Expected uri policy host to be the first host.") | ||||
| 	} | ||||
| 
 | ||||
| 	request = httptest.NewRequest(http.MethodGet, "/test_2", nil) | ||||
| 	h = uriPolicy.Select(pool, request) | ||||
| 	if h != pool[1] { | ||||
| 		t.Error("Expected uri policy host to be the second host.") | ||||
| 	} | ||||
| 
 | ||||
| 	// We should be able to resize the host pool and still be able to predict | ||||
| 	// where a request will be routed with the same URI's used above | ||||
| 	pool = []*UpstreamHost{ | ||||
| 		{ | ||||
| 			Name: workableServer.URL, // this should resolve (healthcheck test) | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "http://localhost:99998", // this shouldn't | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	request = httptest.NewRequest(http.MethodGet, "/test", nil) | ||||
| 	h = uriPolicy.Select(pool, request) | ||||
| 	if h != pool[0] { | ||||
| 		t.Error("Expected uri policy host to be the first host.") | ||||
| 	} | ||||
| 
 | ||||
| 	pool[0].Unhealthy = 1 | ||||
| 	h = uriPolicy.Select(pool, request) | ||||
| 	if h != pool[1] { | ||||
| 		t.Error("Expected uri policy host to be the first host.") | ||||
| 	} | ||||
| 
 | ||||
| 	request = httptest.NewRequest(http.MethodGet, "/test_2", nil) | ||||
| 	h = uriPolicy.Select(pool, request) | ||||
| 	if h != pool[1] { | ||||
| 		t.Error("Expected uri policy host to be the second host.") | ||||
| 	} | ||||
| 
 | ||||
| 	pool[0].Unhealthy = 1 | ||||
| 	pool[1].Unhealthy = 1 | ||||
| 	h = uriPolicy.Select(pool, request) | ||||
| 	if h != nil { | ||||
| 		t.Error("Expected uri policy policy host to be nil.") | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user