Repository: prebid/prebid-server
Pull request #1460 (2020-08-26 18:38:00)

Add Scheme Option To External Cache URL
Per the Prebid.org Slack thread: 

- Gives the host the option of specifying a scheme for the external cache url.
- Fixes a bug where partial urls would be generated if no host or path is provided by now no longer setting a url.

Default behavior matches current behavior where the url isn't generated with a protocol, allowing the browser and/or server to choose.


Patch:
diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go
index 243f1b80..ff7451f1 100644
--- a/adapters/consumable/consumable.go
+++ b/adapters/consumable/consumable.go
@@ -69,6 +69,8 @@ type decision struct {
 	CreativeID    string     `json:"creativeId,omitempty"`
 	Contents      []contents `json:"contents"`
 	ImpressionUrl *string    `json:"impressionUrl,omitempty"`
+	Width         uint64     `json:"width,omitempty"`  // Consumable extension, not defined by Adzerk
+	Height        uint64     `json:"height,omitempty"` // Consumable extension, not defined by Adzerk
 }
 
 type contents struct {
@@ -241,23 +243,13 @@ func (a *ConsumableAdapter) MakeBids(
 	for impID, decision := range serverResponse.Decisions {
 
 		if decision.Pricing != nil && decision.Pricing.ClearPrice != nil {
-
-			imp := getImp(impID, internalRequest.Imp)
-			if imp == nil {
-				errors = append(errors, &errortypes.BadServerResponse{
-					Message: fmt.Sprintf(
-						"ignoring bid id=%s, request doesn't contain any impression with id=%s", internalRequest.ID, impID),
-				})
-				continue
-			}
-
 			bid := openrtb.Bid{}
 			bid.ID = internalRequest.ID
 			bid.ImpID = impID
 			bid.Price = *decision.Pricing.ClearPrice
 			bid.AdM = retrieveAd(decision)
-			bid.W = imp.Banner.Format[0].W // TODO: Review to check if this is correct behaviour
-			bid.H = imp.Banner.Format[0].H
+			bid.W = decision.Width
+			bid.H = decision.Height
 			bid.CrID = strconv.FormatInt(decision.AdID, 10)
 			bid.Exp = 30 // TODO: Check this is intention of TTL
 
@@ -279,15 +271,6 @@ func (a *ConsumableAdapter) MakeBids(
 	return bidderResponse, errors
 }
 
-func getImp(impId string, imps []openrtb.Imp) *openrtb.Imp {
-	for _, imp := range imps {
-		if imp.ID == impId {
-			return &imp
-		}
-	}
-	return nil
-}
-
 func extractExtensions(impression openrtb.Imp) (*adapters.ExtImpBidder, *openrtb_ext.ExtImpConsumable, []error) {
 	var bidderExt adapters.ExtImpBidder
 	if err := json.Unmarshal(impression.Ext, &bidderExt); err != nil {
diff --git a/adapters/info.go b/adapters/info.go
index 732ae855..982827c9 100644
--- a/adapters/info.go
+++ b/adapters/info.go
@@ -37,6 +37,7 @@ type InfoAwareBidder struct {
 
 func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) {
 	var allowedMediaTypes parsedSupports
+
 	if request.Site != nil {
 		if !i.info.site.enabled {
 			return nil, []error{BadInput("this bidder does not support site requests")}
@@ -56,7 +57,14 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *Ext
 	// To avoid allocating new arrays and copying in the normal case, we'll make one pass to
 	// see if any imps need to be removed, and another to do the removing if necessary.
 	numToFilter, errs := i.pruneImps(request.Imp, allowedMediaTypes)
+
+	// If all imps in bid request come with unsupported media types, exit
+	if numToFilter == len(request.Imp) {
+		return nil, append(errs, BadInput("Bid request didn't contain media types supported by the bidder"))
+	}
+
 	if numToFilter != 0 {
+		// Filter out imps with unsupported media types
 		filteredImps, newErrs := i.filterImps(request.Imp, numToFilter)
 		request.Imp = filteredImps
 		errs = append(errs, newErrs...)
diff --git a/config/config.go b/config/config.go
index e3b7d8eb..c2e7dfd8 100755
--- a/config/config.go
+++ b/config/config.go
@@ -136,6 +136,10 @@ func (data *ExternalCache) validate(errs configErrors) configErrors {
 		return errs
 	}
 
+	if data.Scheme != "" && data.Scheme != "http" && data.Scheme != "https" {
+		return append(errs, errors.New("External cache Scheme must be http or https if specified"))
+	}
+
 	// Either host or path or both not empty, validate.
 	if data.Host == "" && data.Path != "" || data.Host != "" && data.Path == "" {
 		return append(errs, errors.New("External cache Host and Path must both be specified"))
@@ -486,13 +490,14 @@ type DataCache struct {
 	TTLSeconds int    `mapstructure:"ttl_seconds"`
 }
 
-// Data type where we store the external cache URL elements. This is completely unrelated to type Cache struct defined afterwards, because
-// the latter is used for internal cache URL while the following contains information of the external cache URL.
+// ExternalCache configures the externally accessible cache url.
 type ExternalCache struct {
-	Host string `mapstructure:"host"`
-	Path string `mapstructure:"path"`
+	Scheme string `mapstructure:"scheme"`
+	Host   string `mapstructure:"host"`
+	Path   string `mapstructure:"path"`
 }
 
+// Cache configures the url used internally by Prebid Server to communicate with Prebid Cache.
 type Cache struct {
 	Scheme string `mapstructure:"scheme"`
 	Host   string `mapstructure:"host"`
@@ -734,6 +739,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("cache.default_ttl_seconds.video", 0)
 	v.SetDefault("cache.default_ttl_seconds.native", 0)
 	v.SetDefault("cache.default_ttl_seconds.audio", 0)
+	v.SetDefault("external_cache.scheme", "")
 	v.SetDefault("external_cache.host", "")
 	v.SetDefault("external_cache.path", "")
 	v.SetDefault("recaptcha_secret", "")
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 1fbdfe8e..59e87669 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"math/rand"
 	"net/http"
+	"net/url"
 	"runtime/debug"
 	"sort"
 	"strconv"
@@ -107,7 +108,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 	shouldCacheBids, shouldCacheVAST := getExtCacheInfo(requestExt)
 	targData := getExtTargetData(requestExt, shouldCacheBids, shouldCacheVAST)
 	if targData != nil {
-		targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
+		_, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
 	}
 
 	debugInfo := getDebugInfo(bidRequest, requestExt)
@@ -814,24 +815,50 @@ func (e *exchange) makeBid(Bids []*pbsOrtbBid, adapter openrtb_ext.BidderName, a
 
 // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`,
 // a UUID should be found inside `a.cacheIds` or `a.vastCacheIds`. This function returns the UUID along with the internal cache URL
-func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auc *auction) (openrtb_ext.ExtBidPrebidCacheBids, bool) {
-	var cacheInfo openrtb_ext.ExtBidPrebidCacheBids
-	var cacheUUID string
-	var found bool = false
-
-	if auc != nil {
-		var extCacheHost, extCachePath string
-		if cacheUUID, found = auc.cacheIds[bid.bid]; found {
-			cacheInfo.CacheId = cacheUUID
-			extCacheHost, extCachePath = e.cache.GetExtCacheData()
-			cacheInfo.Url = extCacheHost + extCachePath + "?uuid=" + cacheUUID
-		} else if cacheUUID, found = auc.vastCacheIds[bid.bid]; found {
-			cacheInfo.CacheId = cacheUUID
-			extCacheHost, extCachePath = e.cache.GetExtCacheData()
-			cacheInfo.Url = extCacheHost + extCachePath + "?uuid=" + cacheUUID
+func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) {
+	uuid, found := findCacheID(bid, auction)
+
+	if found {
+		cacheInfo.CacheId = uuid
+		cacheInfo.Url = buildCacheURL(e.cache, uuid)
+	}
+
+	return
+}
+
+func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) {
+	if bid != nil && bid.bid != nil && auction != nil {
+		if id, found := auction.cacheIds[bid.bid]; found {
+			return id, true
+		}
+
+		if id, found := auction.vastCacheIds[bid.bid]; found {
+			return id, true
 		}
 	}
-	return cacheInfo, found
+
+	return "", false
+}
+
+func buildCacheURL(cache prebid_cache_client.Client, uuid string) string {
+	scheme, host, path := cache.GetExtCacheData()
+
+	if host == "" || path == "" {
+		return ""
+	}
+
+	query := url.Values{"uuid": []string{uuid}}
+	cacheURL := url.URL{
+		Scheme:   scheme,
+		Host:     host,
+		Path:     path,
+		RawQuery: query.Encode(),
+	}
+	cacheURL.Query()
+
+	// URLs without a scheme will begin with //, in which case we
+	// want to trim it off to keep compatbile with current behavior.
+	return strings.TrimPrefix(cacheURL.String(), "//")
 }
 
 func listBiddersWithRequests(cleanRequests map[openrtb_ext.BidderName]*openrtb.BidRequest) []openrtb_ext.BidderName {
diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go
index a5730ce7..1ad78830 100644
--- a/prebid_cache_client/client.go
+++ b/prebid_cache_client/client.go
@@ -29,8 +29,8 @@ type Client interface {
 	// logging any relevant errors to the app logs
 	PutJson(ctx context.Context, values []Cacheable) ([]string, []error)
 
-	// Serves the purpose of a getter that returns the host and the cache of the prebid-server URL
-	GetExtCacheData() (string, string)
+	// GetExtCacheData gets the scheme, host, and path of the externally accessible cache url.
+	GetExtCacheData() (scheme string, host string, path string)
 }
 
 type PayloadType string
@@ -49,23 +49,25 @@ type Cacheable struct {
 
 func NewClient(httpClient *http.Client, conf *config.Cache, extCache *config.ExternalCache, metrics pbsmetrics.MetricsEngine) Client {
 	return &clientImpl{
-		httpClient:        httpClient,
-		putUrl:            conf.GetBaseURL() + "/cache",
-		externalCacheHost: extCache.Host,
-		externalCachePath: extCache.Path,
-		metrics:           metrics,
+		httpClient:          httpClient,
+		putUrl:              conf.GetBaseURL() + "/cache",
+		externalCacheScheme: extCache.Scheme,
+		externalCacheHost:   extCache.Host,
+		externalCachePath:   extCache.Path,
+		metrics:             metrics,
 	}
 }
 
 type clientImpl struct {
-	httpClient        *http.Client
-	putUrl            string
-	externalCacheHost string
-	externalCachePath string
-	metrics           pbsmetrics.MetricsEngine
+	httpClient          *http.Client
+	putUrl              string
+	externalCacheScheme string
+	externalCacheHost   string
+	externalCachePath   string
+	metrics             pbsmetrics.MetricsEngine
 }
 
-func (c *clientImpl) GetExtCacheData() (string, string) {
+func (c *clientImpl) GetExtCacheData() (string, string, string) {
 	path := c.externalCachePath
 	if path == "/" {
 		// Only the slash for the path, remove it to empty
@@ -75,7 +77,7 @@ func (c *clientImpl) GetExtCacheData() (string, string) {
 		path = "/" + path
 	}
 
-	return c.externalCacheHost, path
+	return c.externalCacheScheme, c.externalCacheHost, path
 }
 
 func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []string, errs []error) {


Test patch:
diff --git a/adapters/info_test.go b/adapters/info_test.go
index 9c0dd16b..ce0f8869 100644
--- a/adapters/info_test.go
+++ b/adapters/info_test.go
@@ -24,6 +24,7 @@ func TestAppNotSupported(t *testing.T) {
 	}
 	constrained := adapters.EnforceBidderInfo(bidder, info)
 	bids, errs := constrained.MakeRequests(&openrtb.BidRequest{
+		Imp: []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}},
 		App: &openrtb.App{},
 	}, &adapters.ExtraRequestInfo{})
 	if !assert.Len(t, errs, 1) {
@@ -45,6 +46,7 @@ func TestSiteNotSupported(t *testing.T) {
 	}
 	constrained := adapters.EnforceBidderInfo(bidder, info)
 	bids, errs := constrained.MakeRequests(&openrtb.BidRequest{
+		Imp:  []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}},
 		Site: &openrtb.Site{},
 	}, &adapters.ExtraRequestInfo{})
 	if !assert.Len(t, errs, 1) {
@@ -69,48 +71,111 @@ func TestImpFiltering(t *testing.T) {
 	}
 
 	constrained := adapters.EnforceBidderInfo(bidder, info)
-	_, errs := constrained.MakeRequests(&openrtb.BidRequest{
-		Imp: []openrtb.Imp{
-			{
-				ID:    "imp-1",
-				Video: &openrtb.Video{},
+
+	testCases := []struct {
+		description    string
+		inBidRequest   *openrtb.BidRequest
+		expectedErrors []error
+		expectedImpLen int
+	}{
+		{
+			description: "Empty Imp array. MakeRequest() call not expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp:  []openrtb.Imp{},
+				Site: &openrtb.Site{},
 			},
-			{
-				Native: &openrtb.Native{},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"},
 			},
-			{
-				ID:     "imp-2",
-				Video:  &openrtb.Video{},
-				Native: &openrtb.Native{},
+			expectedImpLen: 0,
+		},
+		{
+			description: "Sole imp in bid request is of wrong media type. MakeRequest() call not expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{{ID: "imp-1", Video: &openrtb.Video{}}},
+				App: &openrtb.App{},
 			},
-			{
-				Banner: &openrtb.Banner{},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"},
 			},
+			expectedImpLen: 0,
+		},
+		{
+			description: "All imps in bid request of wrong media type, MakeRequest() call not expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{
+					{ID: "imp-1", Video: &openrtb.Video{}},
+					{ID: "imp-2", Native: &openrtb.Native{}},
+					{ID: "imp-3", Audio: &openrtb.Audio{}},
+				},
+				App: &openrtb.App{},
+			},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[2] uses audio, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"},
+			},
+			expectedImpLen: 0,
+		},
+		{
+			description: "Some imps with correct media type, MakeRequest() call expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{
+					{
+						ID:    "imp-1",
+						Video: &openrtb.Video{},
+					},
+					{
+						Native: &openrtb.Native{},
+					},
+					{
+						ID:     "imp-2",
+						Video:  &openrtb.Video{},
+						Native: &openrtb.Native{},
+					},
+					{
+						Banner: &openrtb.Banner{},
+					},
+				},
+				Site: &openrtb.Site{},
+			},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[2] uses native, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[3] uses banner, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[1] has no supported MediaTypes. It will be ignored"},
+				&errortypes.BadInput{Message: "request.imp[3] has no supported MediaTypes. It will be ignored"},
+			},
+			expectedImpLen: 2,
+		},
+		{
+			description: "All imps with correct media type, MakeRequest() call expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{
+					{ID: "imp-1", Video: &openrtb.Video{}},
+					{ID: "imp-2", Video: &openrtb.Video{}},
+				},
+				Site: &openrtb.Site{},
+			},
+			expectedErrors: nil,
+			expectedImpLen: 2,
 		},
-		Site: &openrtb.Site{},
-	}, &adapters.ExtraRequestInfo{})
-	if !assert.Len(t, errs, 6) {
-		return
 	}
-	assert.EqualError(t, errs[0], "request.imp[1] uses native, but this bidder doesn't support it")
-	assert.EqualError(t, errs[1], "request.imp[2] uses native, but this bidder doesn't support it")
-	assert.EqualError(t, errs[2], "request.imp[3] uses banner, but this bidder doesn't support it")
-	assert.EqualError(t, errs[3], "request.imp[1] has no supported MediaTypes. It will be ignored")
-	assert.EqualError(t, errs[4], "request.imp[3] has no supported MediaTypes. It will be ignored")
-	assert.EqualError(t, errs[5], "mock MakeRequests error")
-	assert.IsType(t, &errortypes.BadInput{}, errs[0])
-	assert.IsType(t, &errortypes.BadInput{}, errs[1])
-	assert.IsType(t, &errortypes.BadInput{}, errs[2])
-	assert.IsType(t, &errortypes.BadInput{}, errs[3])
-	assert.IsType(t, &errortypes.BadInput{}, errs[4])
 
-	req := bidder.gotRequest
-	if !assert.Len(t, req.Imp, 2) {
-		return
+	for _, test := range testCases {
+		actualAdapterRequests, actualErrs := constrained.MakeRequests(test.inBidRequest, &adapters.ExtraRequestInfo{})
+
+		// Assert the request.Imp slice was correctly filtered and if MakeRequest() was called by asserting
+		// the corresponding error messages were returned
+		for i, expectedErr := range test.expectedErrors {
+			assert.EqualError(t, expectedErr, actualErrs[i].Error(), "Test failed. Error[%d] in error list mismatch: %s", i, test.description)
+		}
+
+		// Extra MakeRequests() call check: our mockBidder returns an adapter request for every imp
+		assert.Len(t, actualAdapterRequests, test.expectedImpLen, "Test failed. Incorrect lenght of filtered imps: %s", test.description)
 	}
-	assert.Equal(t, "imp-1", req.Imp[0].ID)
-	assert.Equal(t, "imp-2", req.Imp[1].ID)
-	assert.Nil(t, req.Imp[1].Native)
 }
 
 type mockBidder struct {
@@ -118,8 +183,13 @@ type mockBidder struct {
 }
 
 func (m *mockBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	m.gotRequest = request
-	return nil, []error{errors.New("mock MakeRequests error")}
+	var adapterRequests []*adapters.RequestData
+
+	for i := 0; i < len(request.Imp); i++ {
+		adapterRequests = append(adapterRequests, &adapters.RequestData{})
+	}
+
+	return adapterRequests, nil
 }
 
 func (m *mockBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
diff --git a/config/config_test.go b/config/config_test.go
index 3da3f721..40589ac7 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -90,6 +90,21 @@ func TestExternalCacheURLValidate(t *testing.T) {
 			data:      ExternalCache{Host: "http://", Path: ""},
 			expErrors: 1,
 		},
+		{
+			desc:      "Scheme Invalid",
+			data:      ExternalCache{Scheme: "invalid", Host: "www.google.com", Path: "/path/v1"},
+			expErrors: 1,
+		},
+		{
+			desc:      "Scheme HTTP",
+			data:      ExternalCache{Scheme: "http", Host: "www.google.com", Path: "/path/v1"},
+			expErrors: 0,
+		},
+		{
+			desc:      "Scheme HTTPS",
+			data:      ExternalCache{Scheme: "https", Host: "www.google.com", Path: "/path/v1"},
+			expErrors: 0,
+		},
 	}
 	for _, test := range testCases {
 		var errs configErrors
@@ -150,6 +165,7 @@ cache:
   host: prebidcache.net
   query: uuid=%PBS_CACHE_UUID%
 external_cache:
+  scheme: https
   host: www.externalprebidcache.net
   path: /endpoints/cache
 http_client:
@@ -307,6 +323,7 @@ func TestFullConfig(t *testing.T) {
 	cmpStrings(t, "cache.scheme", cfg.CacheURL.Scheme, "http")
 	cmpStrings(t, "cache.host", cfg.CacheURL.Host, "prebidcache.net")
 	cmpStrings(t, "cache.query", cfg.CacheURL.Query, "uuid=%PBS_CACHE_UUID%")
+	cmpStrings(t, "external_cache.scheme", cfg.ExtCacheURL.Scheme, "https")
 	cmpStrings(t, "external_cache.host", cfg.ExtCacheURL.Host, "www.externalprebidcache.net")
 	cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "/endpoints/cache")
 	cmpInts(t, "http_client.max_connections_per_host", cfg.Client.MaxConnsPerHost, 10)
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 534db3c7..78715f5c 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1259,8 +1259,8 @@ func (m *mockCacheClient) PutJson(ctx context.Context, values []prebid_cache_cli
 	return []string{}, []error{}
 }
 
-func (m *mockCacheClient) GetExtCacheData() (string, string) {
-	return "", ""
+func (m *mockCacheClient) GetExtCacheData() (scheme string, host string, path string) {
+	return "", "", ""
 }
 
 type mockVideoStoredReqFetcher struct {
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
index e23c45cb..9f24682b 100644
--- a/exchange/auction_test.go
+++ b/exchange/auction_test.go
@@ -298,22 +298,27 @@ type pbsBid struct {
 	Bidder  openrtb_ext.BidderName `json:"bidder"`
 }
 
-type mockCache struct {
-	items []prebid_cache_client.Cacheable
-}
-
 type cacheComparator struct {
 	freq         int
 	expectedKeys []string
 	actualKeys   []string
 }
 
-func (c *mockCache) GetExtCacheData() (string, string) {
-	return "", ""
+type mockCache struct {
+	scheme string
+	host   string
+	path   string
+	items  []prebid_cache_client.Cacheable
+}
+
+func (c *mockCache) GetExtCacheData() (scheme string, host string, path string) {
+	return c.scheme, c.host, c.path
 }
+
 func (c *mockCache) GetPutUrl() string {
 	return ""
 }
+
 func (c *mockCache) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) {
 	c.items = values
 	return []string{"", "", "", "", ""}, nil
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index d1531237..efabb845 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -274,9 +274,10 @@ func TestDebugBehaviour(t *testing.T) {
 	}
 }
 
-func TestGetBidCacheInfo(t *testing.T) {
+func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	testUUID := "CACHE_UUID_1234"
-	testExternalCacheHost := "https://www.externalprebidcache.net"
+	testExternalCacheScheme := "https"
+	testExternalCacheHost := "www.externalprebidcache.net"
 	testExternalCachePath := "endpoints/cache"
 
 	/* 1) An adapter 											*/
@@ -292,8 +293,9 @@ func TestGetBidCacheInfo(t *testing.T) {
 			Host: "www.internalprebidcache.net",
 		},
 		ExtCacheURL: config.ExternalCache{
-			Host: testExternalCacheHost,
-			Path: testExternalCachePath,
+			Scheme: testExternalCacheScheme,
+			Host:   testExternalCacheHost,
+			Path:   testExternalCachePath,
 		},
 	}
 	adapterList := make([]openrtb_ext.BidderName, 0, 2)
@@ -421,7 +423,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 				Seat: string(bidderName),
 				Bid: []openrtb.Bid{
 					{
-						Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`),
+						Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheScheme + `://` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`),
 					},
 				},
 			},
@@ -446,6 +448,131 @@ func TestGetBidCacheInfo(t *testing.T) {
 	assert.Equal(t, expCacheURL, cacheURL, "[TestGetBidCacheInfo] cacheId field in ext should equal \"%s\" \n", expCacheURL)
 }
 
+func TestGetBidCacheInfo(t *testing.T) {
+	bid := &openrtb.Bid{ID: "42"}
+	testCases := []struct {
+		description      string
+		scheme           string
+		host             string
+		path             string
+		bid              *pbsOrtbBid
+		auction          *auction
+		expectedFound    bool
+		expectedCacheID  string
+		expectedCacheURL string
+	}{
+		{
+			description:      "JSON Cache ID",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "https://prebid.org/cache?uuid=anyID",
+		},
+		{
+			description:      "VAST Cache ID",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{vastCacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "https://prebid.org/cache?uuid=anyID",
+		},
+		{
+			description:      "Cache ID Not Found",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{},
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Scheme Not Provided",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "prebid.org/cache?uuid=anyID",
+		},
+		{
+			description:      "Host And Path Not Provided - Without Scheme",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Host And Path Not Provided - With Scheme",
+			scheme:           "https",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Nil Bid",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              nil,
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Nil Embedded Bid",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: nil},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Nil Auction",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          nil,
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+	}
+
+	for _, test := range testCases {
+		exchange := &exchange{
+			cache: &mockCache{
+				scheme: test.scheme,
+				host:   test.host,
+				path:   test.path,
+			},
+		}
+
+		cacheInfo, found := exchange.getBidCacheInfo(test.bid, test.auction)
+
+		assert.Equal(t, test.expectedFound, found, test.description+":found")
+		assert.Equal(t, test.expectedCacheID, cacheInfo.CacheId, test.description+":id")
+		assert.Equal(t, test.expectedCacheURL, cacheInfo.Url, test.description+":url")
+	}
+}
+
 func TestBidResponseCurrency(t *testing.T) {
 	// Init objects
 	cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)}
@@ -2176,8 +2303,8 @@ func mockSlowHandler(delay time.Duration, statusCode int, body string) http.Hand
 
 type wellBehavedCache struct{}
 
-func (c *wellBehavedCache) GetExtCacheData() (string, string) {
-	return "www.pbcserver.com", "/pbcache/endpoint"
+func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) {
+	return "https", "www.pbcserver.com", "/pbcache/endpoint"
 }
 
 func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) {
diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json
index f348dd1b..53a48c4e 100644
--- a/exchange/exchangetest/targeting-cache-vast.json
+++ b/exchange/exchangetest/targeting-cache-vast.json
@@ -67,7 +67,7 @@
                 "cache": {
                   "bids": {
                     "cacheId": "0",
-                    "url": "www.pbcserver.com/pbcache/endpoint?uuid=0"
+                    "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0"
                   },
                   "key": "",
                   "url": ""
diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json
index 51301530..0048ea10 100644
--- a/exchange/exchangetest/targeting-cache-zero.json
+++ b/exchange/exchangetest/targeting-cache-zero.json
@@ -70,7 +70,7 @@
                 "cache": {
                   "bids": {
                     "cacheId": "0",
-                    "url": "www.pbcserver.com/pbcache/endpoint?uuid=0"
+                    "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0"
                   },
                   "key": "",
                   "url": ""
diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go
index 5840d4ea..72fd2761 100644
--- a/prebid_cache_client/client_test.go
+++ b/prebid_cache_client/client_test.go
@@ -186,58 +186,80 @@ func TestEncodeValueToBuffer(t *testing.T) {
 func TestStripCacheHostAndPath(t *testing.T) {
 	inCacheURL := config.Cache{ExpectedTimeMillis: 10}
 	type aTest struct {
-		inExtCacheURL config.ExternalCache
-		expectedHost  string
-		expectedPath  string
+		inExtCacheURL  config.ExternalCache
+		expectedScheme string
+		expectedHost   string
+		expectedPath   string
 	}
 	testInput := []aTest{
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebid-server.prebid.org",
-				Path: "/pbcache/endpoint",
+				Scheme: "",
+				Host:   "prebid-server.prebid.org",
+				Path:   "/pbcache/endpoint",
 			},
-			expectedHost: "prebid-server.prebid.org",
-			expectedPath: "/pbcache/endpoint",
+			expectedScheme: "",
+			expectedHost:   "prebid-server.prebid.org",
+			expectedPath:   "/pbcache/endpoint",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebidcache.net",
-				Path: "",
+				Scheme: "https",
+				Host:   "prebid-server.prebid.org",
+				Path:   "/pbcache/endpoint",
 			},
-			expectedHost: "prebidcache.net",
-			expectedPath: "",
+			expectedScheme: "https",
+			expectedHost:   "prebid-server.prebid.org",
+			expectedPath:   "/pbcache/endpoint",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "",
-				Path: "",
+				Scheme: "",
+				Host:   "prebidcache.net",
+				Path:   "",
 			},
-			expectedHost: "",
-			expectedPath: "",
+			expectedScheme: "",
+			expectedHost:   "prebidcache.net",
+			expectedPath:   "",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebid-server.prebid.org",
-				Path: "pbcache/endpoint",
+				Scheme: "",
+				Host:   "",
+				Path:   "",
 			},
-			expectedHost: "prebid-server.prebid.org",
-			expectedPath: "/pbcache/endpoint",
+			expectedScheme: "",
+			expectedHost:   "",
+			expectedPath:   "",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebidcache.net",
-				Path: "/",
+				Scheme: "",
+				Host:   "prebid-server.prebid.org",
+				Path:   "pbcache/endpoint",
 			},
-			expectedHost: "prebidcache.net",
-			expectedPath: "",
+			expectedScheme: "",
+			expectedHost:   "prebid-server.prebid.org",
+			expectedPath:   "/pbcache/endpoint",
+		},
+		{
+			inExtCacheURL: config.ExternalCache{
+				Scheme: "",
+				Host:   "prebidcache.net",
+				Path:   "/",
+			},
+			expectedScheme: "",
+			expectedHost:   "prebidcache.net",
+			expectedPath:   "",
 		},
 	}
 	for _, test := range testInput {
 		cacheClient := NewClient(&http.Client{}, &inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{})
-		cHost, cPath := cacheClient.GetExtCacheData()
+		scheme, host, path := cacheClient.GetExtCacheData()
 
-		assert.Equal(t, test.expectedHost, cHost)
-		assert.Equal(t, test.expectedPath, cPath)
+		assert.Equal(t, test.expectedScheme, scheme)
+		assert.Equal(t, test.expectedHost, host)
+		assert.Equal(t, test.expectedPath, path)
 	}
 }