-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: extract ProxyForHost logic to interface (#40)
* refactor: extract ProxyForHost logic to interface this pure refactor moves the logic that builds the map of host -> backend url into an interface that could be implemented differently in the future. a new type of `Proxies` will be used to do height-based sharding of nodes based on the height of the incoming request. * add tests for Proxies * add proxy middleware config docs
- Loading branch information
1 parent
69b86f0
commit 2f8fa7d
Showing
4 changed files
with
157 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package service | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/http/httputil" | ||
|
||
"github.com/kava-labs/kava-proxy-service/config" | ||
"github.com/kava-labs/kava-proxy-service/logging" | ||
) | ||
|
||
// Proxies is an interface for getting a reverse proxy for a given request. | ||
type Proxies interface { | ||
ProxyForRequest(r *http.Request) (proxy *httputil.ReverseProxy, found bool) | ||
} | ||
|
||
// NewProxies creates a Proxies instance based on the service configuration: | ||
// - for non-sharding configuration, it returns a HostProxies | ||
// TODO: - for sharding configurations, it returns a HeightShardingProxies | ||
func NewProxies(config config.Config, serviceLogger *logging.ServiceLogger) Proxies { | ||
serviceLogger.Debug().Msg("configuring reverse proxies based solely on request host") | ||
return newHostProxies(config, serviceLogger) | ||
} | ||
|
||
// HostProxies chooses a proxy based solely on the Host of the incoming request, | ||
// and the host -> backend url map defined in the config. | ||
type HostProxies struct { | ||
proxyForHost map[string]*httputil.ReverseProxy | ||
} | ||
|
||
var _ Proxies = HostProxies{} | ||
|
||
// ProxyForRequest implements Proxies. It determines the proxy based solely on the request Host. | ||
func (hbp HostProxies) ProxyForRequest(r *http.Request) (*httputil.ReverseProxy, bool) { | ||
proxy, found := hbp.proxyForHost[r.Host] | ||
return proxy, found | ||
} | ||
|
||
// newHostProxies creates a HostProxies from the backend url map defined in the config. | ||
func newHostProxies(config config.Config, serviceLogger *logging.ServiceLogger) HostProxies { | ||
reverseProxyForHost := make(map[string]*httputil.ReverseProxy) | ||
|
||
for host, proxyBackendURL := range config.ProxyBackendHostURLMapParsed { | ||
serviceLogger.Debug().Msg(fmt.Sprintf("creating reverse proxy for host %s to %+v", host, proxyBackendURL)) | ||
|
||
targetURL := config.ProxyBackendHostURLMapParsed[host] | ||
|
||
reverseProxyForHost[host] = httputil.NewSingleHostReverseProxy(&targetURL) | ||
} | ||
|
||
return HostProxies{proxyForHost: reverseProxyForHost} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package service_test | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/http/httputil" | ||
"net/url" | ||
"testing" | ||
|
||
"github.com/kava-labs/kava-proxy-service/config" | ||
"github.com/kava-labs/kava-proxy-service/service" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func newConfig(t *testing.T, rawHostMap string) config.Config { | ||
parsed, err := config.ParseRawProxyBackendHostURLMap(rawHostMap) | ||
require.NoError(t, err) | ||
return config.Config{ | ||
ProxyBackendHostURLMapRaw: rawHostMap, | ||
ProxyBackendHostURLMapParsed: parsed, | ||
} | ||
} | ||
|
||
func TestUnitTest_NewProxies(t *testing.T) { | ||
t.Run("returns a HostProxies when sharding disabled", func(t *testing.T) { | ||
config := newConfig(t, dummyConfig.ProxyBackendHostURLMapRaw) | ||
proxies := service.NewProxies(config, dummyLogger) | ||
require.IsType(t, service.HostProxies{}, proxies) | ||
}) | ||
// TODO: HeightShardingProxies | ||
} | ||
|
||
func TestUnitTest_HostProxies(t *testing.T) { | ||
config := newConfig(t, | ||
"magic.kava.io>magicalbackend.kava.io,archive.kava.io>archivenode.kava.io,pruning.kava.io>pruningnode.kava.io", | ||
) | ||
proxies := service.NewProxies(config, dummyLogger) | ||
|
||
t.Run("ProxyForHost maps to correct proxy", func(t *testing.T) { | ||
req := mockReqForUrl("//magic.kava.io") | ||
proxy, found := proxies.ProxyForRequest(req) | ||
require.True(t, found, "expected proxy to be found") | ||
requireProxyRoutesToUrl(t, proxy, req, "magicalbackend.kava.io/") | ||
|
||
req = mockReqForUrl("https://archive.kava.io") | ||
proxy, found = proxies.ProxyForRequest(req) | ||
require.True(t, found, "expected proxy to be found") | ||
requireProxyRoutesToUrl(t, proxy, req, "archivenode.kava.io/") | ||
|
||
req = mockReqForUrl("//pruning.kava.io/some/nested/endpoint") | ||
proxy, found = proxies.ProxyForRequest(req) | ||
require.True(t, found, "expected proxy to be found") | ||
requireProxyRoutesToUrl(t, proxy, req, "pruningnode.kava.io/some/nested/endpoint") | ||
}) | ||
|
||
t.Run("ProxyForHost fails with unknown host", func(t *testing.T) { | ||
_, found := proxies.ProxyForRequest(mockReqForUrl("//unknown-host.kava.io")) | ||
require.False(t, found, "expected proxy not found for unknown host") | ||
}) | ||
} | ||
|
||
func mockReqForUrl(reqUrl string) *http.Request { | ||
parsed, err := url.Parse(reqUrl) | ||
if err != nil { | ||
panic(fmt.Sprintf("unable to parse url %s: %s", reqUrl, err)) | ||
} | ||
if parsed.Host == "" { | ||
// absolute url is required for Host to be defined. | ||
panic(fmt.Sprintf("test requires absolute url to determine host (prefix with '//' or 'https://'): found %s", reqUrl)) | ||
} | ||
return &http.Request{Host: parsed.Host, URL: parsed} | ||
} | ||
|
||
// requireProxyRoutesToUrl is a test helper that verifies that | ||
// the given proxy maps the provided request to the expected proxy backend | ||
// relies on the fact that reverse proxies are given a Director that rewrite the request's URL | ||
func requireProxyRoutesToUrl(t *testing.T, proxy *httputil.ReverseProxy, req *http.Request, expectedRoute string) { | ||
proxy.Director(req) | ||
require.Equal(t, expectedRoute, req.URL.String()) | ||
} |