Skip to content

Commit

Permalink
Fix bug with CORS headers
Browse files Browse the repository at this point in the history
  • Loading branch information
evgeniy-scherbina committed Nov 3, 2023
1 parent 92c4c9e commit 07edcdd
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 111 deletions.
9 changes: 8 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,14 @@ CACHE_INDEFINITELY=false
# CACHE_PREFIX must not contain colon symbol
CACHE_PREFIX=local-chain
# WHITELISTED_HEADERS contains comma-separated list of headers which has to be cached along with EVM JSON-RPC response
WHITELISTED_HEADERS=Server,Vary,Access-Control-Expose-Headers,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Credentials,Access-Control-Max-Age
WHITELISTED_HEADERS=Vary,Access-Control-Expose-Headers,Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Credentials,Access-Control-Max-Age
# DEFAULT_ACCESS_CONTROL_ALLOW_ORIGIN_VALUE contains default value for Access-Control-Allow-Origin header.
# NOTE: it will be used only in Cache Hit scenario.
DEFAULT_ACCESS_CONTROL_ALLOW_ORIGIN_VALUE="*"
# Map contains mapping between hostname (for ex. evm.kava.io) and corresponding value for Access-Control-Allow-Origin header.
# If hostname for specific request is missing we fallback to DEFAULT_ACCESS_CONTROL_ALLOW_ORIGIN_VALUE.
# NOTE: it will be used only in Cache Hit scenario.
HOSTNAME_TO_ACCESS_CONTROL_ALLOW_ORIGIN_VALUE_MAP_ENVIRONMENT_KEY=""

##### Database Config
POSTGRES_PASSWORD=password
Expand Down
256 changes: 156 additions & 100 deletions config/config.go

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func Validate(config Config) error {
allErrs = errors.Join(allErrs, fmt.Errorf("invalid %s specified %s, must not be empty", CACHE_PREFIX_ENVIRONMENT_KEY, config.CachePrefix))
}

if err = validateHostnameToHeaderValueMap(config.HostnameToAccessControlAllowOriginValueMapRaw, true); err != nil {
allErrs = errors.Join(allErrs, fmt.Errorf("invalid %s specified %s", HOSTNAME_TO_ACCESS_CONTROL_ALLOW_ORIGIN_VALUE_MAP_ENVIRONMENT_KEY, config.HostnameToAccessControlAllowOriginValueMapRaw), err)
}

return allErrs
}

Expand All @@ -89,6 +93,15 @@ func validateHostURLMap(raw string, allowEmpty bool) error {
return err
}

// validateHostnameToHeaderValueMap validates a raw hostname to header value map, optionally allowing the map to be empty
func validateHostnameToHeaderValueMap(raw string, allowEmpty bool) error {
_, err := ParseRawHostnameToHeaderValueMap(raw)
if allowEmpty && errors.Is(err, ErrEmptyHostnameToHeaderValueMap) {
err = nil
}
return err
}

// validateDefaultHostMapContainsHosts returns an error if there are hosts in hostMap that
// are not in defaultHostMap
// example: hosts in the pruning map should always have a default fallback backend
Expand Down
9 changes: 8 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (

const (
EthClientUserAgent = "Go-http-client/1.1"

accessControlAllowOriginHeaderName = "Access-Control-Allow-Origin"
)

var (
Expand Down Expand Up @@ -505,6 +507,9 @@ func TestE2ETestCachingMdwWithBlockNumberParam(t *testing.T) {

// check that response headers are the same
equalHeaders(t, cacheMissResp.Header, cacheHitResp.Header)

// check that CORS headers are present for cache hit scenario
require.Equal(t, cacheHitResp.Header[accessControlAllowOriginHeaderName], []string{"*"})
})
}

Expand Down Expand Up @@ -533,6 +538,7 @@ func TestE2ETestCachingMdwWithBlockNumberParam(t *testing.T) {
// equalHeaders checks that headers of headersMap1 and headersMap2 are equal
// NOTE: it completely ignores presence/absence of cachemdw.CacheHeaderKey,
// it's done in that way to allow comparison of headers for cache miss and cache hit cases
// also it ignores presence/absence of CORS headers
func equalHeaders(t *testing.T, headersMap1, headersMap2 http.Header) {
containsHeaders(t, headersMap1, headersMap2)
containsHeaders(t, headersMap2, headersMap1)
Expand All @@ -541,9 +547,10 @@ func equalHeaders(t *testing.T, headersMap1, headersMap2 http.Header) {
// containsHeaders checks that headersMap1 contains all headers from headersMap2 and that values for headers are the same
// NOTE: it completely ignores presence/absence of cachemdw.CacheHeaderKey,
// it's done in that way to allow comparison of headers for cache miss and cache hit cases
// also it ignores presence/absence of CORS headers
func containsHeaders(t *testing.T, headersMap1, headersMap2 http.Header) {
for name, value := range headersMap1 {
if name == cachemdw.CacheHeaderKey {
if name == cachemdw.CacheHeaderKey || name == "Server" || name == accessControlAllowOriginHeaderName {
continue
}

Expand Down
25 changes: 16 additions & 9 deletions service/cachemdw/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type ServiceCache struct {
cacheEnabled bool
whitelistedHeaders []string

defaultAccessControlAllowOriginValue string
hostnameToAccessControlAllowOriginValueMap map[string]string

*logging.ServiceLogger
}

Expand All @@ -40,18 +43,22 @@ func NewServiceCache(
cachePrefix string,
cacheEnabled bool,
whitelistedHeaders []string,
defaultAccessControlAllowOriginValue string,
hostnameToAccessControlAllowOriginValueMap map[string]string,
logger *logging.ServiceLogger,
) *ServiceCache {
return &ServiceCache{
cacheClient: cacheClient,
blockGetter: blockGetter,
cacheTTL: cacheTTL,
cacheIndefinitely: cacheIndefinitely,
decodedRequestContextKey: decodedRequestContextKey,
cachePrefix: cachePrefix,
cacheEnabled: cacheEnabled,
whitelistedHeaders: whitelistedHeaders,
ServiceLogger: logger,
cacheClient: cacheClient,
blockGetter: blockGetter,
cacheTTL: cacheTTL,
cacheIndefinitely: cacheIndefinitely,
decodedRequestContextKey: decodedRequestContextKey,
cachePrefix: cachePrefix,
cacheEnabled: cacheEnabled,
whitelistedHeaders: whitelistedHeaders,
defaultAccessControlAllowOriginValue: defaultAccessControlAllowOriginValue,
hostnameToAccessControlAllowOriginValueMap: hostnameToAccessControlAllowOriginValueMap,
ServiceLogger: logger,
}
}

Expand Down
2 changes: 2 additions & 0 deletions service/cachemdw/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func TestUnitTestCacheQueryResponse(t *testing.T) {
defaultCachePrefixString,
true,
[]string{},
"*",
map[string]string{},
&logger,
)

Expand Down
4 changes: 4 additions & 0 deletions service/cachemdw/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func TestUnitTestServiceCacheMiddleware(t *testing.T) {
defaultCachePrefixString,
true,
[]string{},
"*",
map[string]string{},
&logger,
)

Expand Down Expand Up @@ -120,6 +122,8 @@ func TestUnitTestServiceCacheMiddleware_CacheIsDisabled(t *testing.T) {
defaultCachePrefixString,
false,
[]string{},
"*",
map[string]string{},
&logger,
)

Expand Down
5 changes: 5 additions & 0 deletions service/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ func createProxyRequestMiddleware(next http.Handler, config config.Config, servi
for headerName, headerValue := range typedCachedResponse.HeaderMap {
w.Header().Add(headerName, headerValue)
}
// add CORS headers
accessControlAllowOriginValue := config.GetAccessControlAllowOriginValue(r.Host)
if accessControlAllowOriginValue != "" {
w.Header().Add("Access-Control-Allow-Origin", accessControlAllowOriginValue)
}
_, err := w.Write(typedCachedResponse.JsonRpcResponseResult)
if err != nil {
serviceLogger.Logger.Error().Msg(fmt.Sprintf("can't write cached response: %v", err))
Expand Down
2 changes: 2 additions & 0 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ func createServiceCache(
config.CachePrefix,
config.CacheEnabled,
config.WhitelistedHeaders,
config.DefaultAccessControlAllowOriginValue,
config.HostnameToAccessControlAllowOriginValueMap,
logger,
)

Expand Down

0 comments on commit 07edcdd

Please sign in to comment.