diff --git a/docs/endpoints/openrtb2/auction.md b/docs/endpoints/openrtb2/auction.md
index 579ec197e2b..81c8c03d371 100644
--- a/docs/endpoints/openrtb2/auction.md
+++ b/docs/endpoints/openrtb2/auction.md
@@ -303,12 +303,16 @@ Bids can be temporarily cached on the server by sending the following data as `r
```
{
- "bids": {}
+ "bids": {},
+ "vastxml": {}
}
```
-This property has no effect unless `request.ext.prebid.targeting` is also set in the request.
-If present, Prebid Server will make a _best effort_ to include these extra `bid.ext.prebid.targeting` keys:
+Both `bids` and `vastxml` are optional, but one of the two is required. Thils property will have no effect
+unless `request.ext.prebid.targeting` is also set in the request.
+
+If `bids` is present, Prebid Server will make a _best effort_ to include these extra
+`bid.ext.prebid.targeting` keys:
- `hb_cache_id`: On the highest overall Bid in each Imp.
- `hb_cache_id_{bidderName}`: On the highest Bid from {bidderName} in each Imp.
@@ -317,7 +321,11 @@ Clients _should not assume_ that these keys will exist, just because they were r
If they exist, the value will be a UUID which can be used to fetch Bid JSON from [Prebid Cache](https://github.com/prebid/prebid-cache).
They may not exist if the host company's cache is full, having connection problems, or other issues like that.
-This is mainly intended for certain limited Prebid Mobile setups, where bids cannot be cached client-side.
+If `vastxml` is present, PBS will try to add analogous keys `hb_uuid` and `hb_uuid_{bidderName}`.
+In addition to the caveats above, these will exist _only if the relevant Bids are for Video_.
+If they exist, the values can be used to fetch the bid's VAST XML from Prebid Cache directly.
+
+These options are mainly intended for certain limited Prebid Mobile setups, where bids cannot be cached client-side.
#### GDPR
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 876303c36b3..1115b38e9b9 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -427,11 +427,14 @@ func defaultRequestExt(req *openrtb.BidRequest) (errs []error) {
PriceGranularity: openrtb_ext.PriceGranularityFromString("med"),
}
}
- if extRequest.Prebid.Cache == nil || extRequest.Prebid.Cache.Bids == nil {
+ if extRequest.Prebid.Cache == nil {
setDefaults = true
extRequest.Prebid.Cache = &openrtb_ext.ExtRequestPrebidCache{
Bids: &openrtb_ext.ExtRequestPrebidCacheBids{},
}
+ } else if extRequest.Prebid.Cache.Bids == nil {
+ setDefaults = true
+ extRequest.Prebid.Cache.Bids = &openrtb_ext.ExtRequestPrebidCacheBids{}
}
if setDefaults {
newExt, err := json.Marshal(extRequest)
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/cache-no-bids.json b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
similarity index 100%
rename from endpoints/openrtb2/sample-requests/invalid-whole/cache-no-bids.json
rename to endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/cache-bids.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/cache-bids.json
new file mode 100644
index 00000000000..884ec898dab
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/cache-bids.json
@@ -0,0 +1,29 @@
+{
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 10433394
+ }
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "cache": {
+ "bids": {}
+ },
+ "targeting": {}
+ }
+ }
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/cache-vast.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/cache-vast.json
new file mode 100644
index 00000000000..7878767af78
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/cache-vast.json
@@ -0,0 +1,28 @@
+{
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 10433394
+ }
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "cache": {
+ "vastxml": {}
+ }
+ }
+ }
+}
diff --git a/exchange/auction.go b/exchange/auction.go
index 92a728a6c90..cffadd70d88 100644
--- a/exchange/auction.go
+++ b/exchange/auction.go
@@ -2,6 +2,7 @@ package exchange
import (
"context"
+ "encoding/json"
"github.com/golang/glog"
"github.com/mxmCherry/openrtb"
@@ -54,16 +55,86 @@ func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity
a.roundedPrices = roundedPrices
}
-func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client) {
- toCache := make([]*openrtb.Bid, 0, len(a.roundedPrices))
+func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, bids bool, vast bool) {
+ if !bids && !vast {
+ return
+ }
+
+ expectNumBids := valOrZero(bids, len(a.roundedPrices))
+ expectNumVast := valOrZero(vast, len(a.roundedPrices))
+ bidIndices := make(map[int]*openrtb.Bid, expectNumBids)
+ vastIndices := make(map[int]*openrtb.Bid, expectNumVast)
+ toCache := make([]prebid_cache_client.Cacheable, 0, expectNumBids+expectNumVast)
for _, topBidsPerImp := range a.winningBidsByBidder {
for _, topBidPerBidder := range topBidsPerImp {
- toCache = append(toCache, topBidPerBidder.bid)
+ if bids {
+ if jsonBytes, err := json.Marshal(topBidPerBidder.bid); err == nil {
+ toCache = append(toCache, prebid_cache_client.Cacheable{
+ Type: prebid_cache_client.TypeJSON,
+ Data: jsonBytes,
+ })
+ bidIndices[len(toCache)-1] = topBidPerBidder.bid
+ }
+ }
+ if vast && topBidPerBidder.bidType == openrtb_ext.BidTypeVideo {
+ vast := makeVAST(topBidPerBidder.bid)
+ if jsonBytes, err := json.Marshal(vast); err == nil {
+ toCache = append(toCache, prebid_cache_client.Cacheable{
+ Type: prebid_cache_client.TypeXML,
+ Data: jsonBytes,
+ })
+ vastIndices[len(toCache)-1] = topBidPerBidder.bid
+ }
+ }
+ }
+ }
+
+ ids := cache.PutJson(ctx, toCache)
+
+ if bids {
+ a.cacheIds = make(map[*openrtb.Bid]string, len(bidIndices))
+ for index, bid := range bidIndices {
+ if ids[index] != "" {
+ a.cacheIds[bid] = ids[index]
+ }
}
}
+ if vast {
+ a.vastCacheIds = make(map[*openrtb.Bid]string, len(vastIndices))
+ for index, bid := range vastIndices {
+ if ids[index] != "" {
+ a.vastCacheIds[bid] = ids[index]
+ }
+ }
+ }
+}
- a.cacheIds = cacheBids(ctx, cache, toCache)
+// makeVAST returns some VAST XML for the given bid. If AdM is defined,
+// it takes precedence. Otherwise the Nurl will be wrapped in a redirect tag.
+func makeVAST(bid *openrtb.Bid) string {
+ if bid.AdM == "" {
+ return `` +
+ `prebid.org wrapper` +
+ `` +
+ `` +
+ ``
+ }
+ return bid.AdM
+}
+
+func valOrZero(useVal bool, val int) int {
+ if useVal {
+ return val
+ }
+ return 0
+}
+
+func maybeMake(shouldMake bool, capacity int) []prebid_cache_client.Cacheable {
+ if shouldMake {
+ return make([]prebid_cache_client.Cacheable, 0, capacity)
+ }
+ return nil
}
type auction struct {
@@ -73,6 +144,8 @@ type auction struct {
winningBidsByBidder map[string]map[openrtb_ext.BidderName]*pbsOrtbBid
// roundedPrices stores the price strings rounded for each bid according to the price granularity.
roundedPrices map[*pbsOrtbBid]string
- // cacheIds stores the UUIDs from Prebid Cache for each bid.
+ // cacheIds stores the UUIDs from Prebid Cache for fetching the full bid JSON.
cacheIds map[*openrtb.Bid]string
+ // vastCacheIds stores UUIDS from Prebid cache for fetching the VAST markup to video bids.
+ vastCacheIds map[*openrtb.Bid]string
}
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
new file mode 100644
index 00000000000..26f5ece14e9
--- /dev/null
+++ b/exchange/auction_test.go
@@ -0,0 +1,31 @@
+package exchange
+
+import (
+ "testing"
+
+ "github.com/mxmCherry/openrtb"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMakeVASTGiven(t *testing.T) {
+ const expect = ``
+ bid := &openrtb.Bid{
+ AdM: expect,
+ }
+ vast := makeVAST(bid)
+ assert.Equal(t, expect, vast)
+}
+
+func TestMakeVASTNurl(t *testing.T) {
+ const url = "http://domain.com/win-notify/1"
+ const expect = `` +
+ `prebid.org wrapper` +
+ `` +
+ `` +
+ ``
+ bid := &openrtb.Bid{
+ NURL: url,
+ }
+ vast := makeVAST(bid)
+ assert.Equal(t, expect, vast)
+}
diff --git a/exchange/cache.go b/exchange/cache.go
deleted file mode 100644
index 067a4c10c43..00000000000
--- a/exchange/cache.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package exchange
-
-import (
- "context"
- "encoding/json"
-
- "github.com/golang/glog"
- "github.com/mxmCherry/openrtb"
- "github.com/prebid/prebid-server/prebid_cache_client"
-)
-
-func cacheBids(ctx context.Context, cache prebid_cache_client.Client, bids []*openrtb.Bid) map[*openrtb.Bid]string {
- // Marshal the bids into JSON payloads. If any errors occur during marshalling, eject that bid from the array.
- // After this block, we expect "bids" and "jsonValues" to have the same number of elements in the same order.
- jsonValues := make([]json.RawMessage, 0, len(bids))
- for i := 0; i < len(bids); i++ {
- if jsonBytes, err := json.Marshal(bids[i]); err != nil {
- glog.Errorf("Error marshalling OpenRTB Bid for Prebid Cache: %v", err)
- bids = append(bids[:i], bids[i+1:]...)
- i--
- } else {
- jsonValues = append(jsonValues, jsonBytes)
- }
- }
-
- ids := cache.PutJson(ctx, jsonValues)
- toReturn := make(map[*openrtb.Bid]string, len(bids))
- for i := 0; i < len(bids); i++ {
- if ids[i] != "" {
- toReturn[bids[i]] = ids[i]
- }
- }
- return toReturn
-}
diff --git a/exchange/cache_test.go b/exchange/cache_test.go
deleted file mode 100644
index 0ad1f74f267..00000000000
--- a/exchange/cache_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package exchange
-
-import (
- "context"
- "encoding/json"
- "testing"
-
- "github.com/evanphx/json-patch"
- "github.com/mxmCherry/openrtb"
-)
-
-func TestBidSerialization(t *testing.T) {
- winningBid := &openrtb.Bid{
- ID: "bar",
- ImpID: "a",
- Price: 1.5,
- }
- otherBid := &openrtb.Bid{
- ID: "foo",
- ImpID: "a",
- Price: 0.5,
- }
-
- mockClient := &mockCacheClient{
- mockReturns: map[*openrtb.Bid]string{
- winningBid: "0",
- otherBid: "1",
- },
- }
-
- bidMap := cacheBids(context.Background(), mockClient, []*openrtb.Bid{winningBid, otherBid})
-
- assertStringValue(t, `bid "bar"`, "0", bidMap[winningBid])
- assertStringValue(t, `bid "foo"`, "1", bidMap[otherBid])
-}
-
-func TestCacheFailures(t *testing.T) {
- winningBid := &openrtb.Bid{
- ID: "bar",
- ImpID: "a",
- Price: 1.5,
- }
- otherBid := &openrtb.Bid{
- ID: "foo",
- ImpID: "a",
- Price: 0.5,
- }
-
- mockClient := &mockCacheClient{
- mockReturns: map[*openrtb.Bid]string{
- winningBid: "",
- otherBid: "1",
- },
- }
- bidMap := cacheBids(context.Background(), mockClient, []*openrtb.Bid{winningBid, otherBid})
-
- assertStringValue(t, `bid "foo"`, "1", bidMap[otherBid])
- if _, ok := bidMap[winningBid]; ok {
- t.Error("If the cache call fails, no ID should exist for that bid.")
- }
-}
-
-func TestMarshalFailure(t *testing.T) {
- badBid := &openrtb.Bid{
- ImpID: "foo",
- Price: 1,
- Ext: openrtb.RawJSON("{"),
- }
- goodBid := &openrtb.Bid{
- ImpID: "bar",
- Price: 2,
- }
-
- mockClient := &mockCacheClient{
- mockReturns: map[*openrtb.Bid]string{
- badBid: "0",
- goodBid: "1",
- },
- }
-
- bidMap := cacheBids(context.Background(), mockClient, []*openrtb.Bid{goodBid, badBid})
- if _, ok := bidMap[badBid]; ok {
- t.Errorf("bids with malformed JSON should not be cached.")
- }
- if id, ok := bidMap[goodBid]; ok {
- if id != "1" {
- t.Errorf("Wrong id for good bid. Expected 1, got %s", id)
- }
- } else {
- t.Errorf("bids with malformed JSON should not prevent other bids from being cached.")
- }
-}
-
-type mockCacheClient struct {
- mockReturns map[*openrtb.Bid]string
-}
-
-func (c *mockCacheClient) PutJson(ctx context.Context, values []json.RawMessage) []string {
- returns := make([]string, len(values))
- for i, value := range values {
- for bid, id := range c.mockReturns {
- bidBytes, _ := json.Marshal(bid)
- if jsonpatch.Equal(bidBytes, value) {
- returns[i] = id
- break
- }
- }
- }
- return returns
-}
-
-func assertStringValue(t *testing.T, object string, expect string, value string) {
- t.Helper()
- if expect != value {
- t.Errorf("Wrong value for %s, expected \"%s\", got \"%s\"", object, expect, value)
- }
-}
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 1fe805b8f3c..5e7681eb4f8 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -86,6 +86,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
// Process the request to check for targeting parameters.
var targData *targetData
shouldCacheBids := false
+ shouldCacheVAST := false
var bidAdjustmentFactors map[string]float64
if len(bidRequest.Ext) > 0 {
var requestExt openrtb_ext.ExtRequest
@@ -94,7 +95,10 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
return nil, fmt.Errorf("Error decoding Request.ext : %s", err.Error())
}
bidAdjustmentFactors = requestExt.Prebid.BidAdjustmentFactors
- shouldCacheBids = requestExt.Prebid.Cache != nil && requestExt.Prebid.Cache.Bids != nil
+ if requestExt.Prebid.Cache != nil {
+ shouldCacheBids = requestExt.Prebid.Cache.Bids != nil
+ shouldCacheVAST = requestExt.Prebid.Cache.VastXML != nil
+ }
if requestExt.Prebid.Targeting != nil {
targData = &targetData{
@@ -103,7 +107,10 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
includeBidderKeys: requestExt.Prebid.Targeting.IncludeBidderKeys,
}
if shouldCacheBids {
- targData.includeCache = true
+ targData.includeCacheBids = true
+ }
+ if shouldCacheVAST {
+ targData.includeCacheVast = true
}
}
}
@@ -117,9 +124,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
auc := newAuction(adapterBids, len(bidRequest.Imp))
if targData != nil {
auc.setRoundedPrices(targData.priceGranularity)
- if targData.includeCache {
- auc.doCache(ctx, e.cache)
- }
+ auc.doCache(ctx, e.cache, targData.includeCacheBids, targData.includeCacheVast)
targData.setTargeting(auc, bidRequest.App != nil)
}
// Build the response
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index ea988565b0a..93a3d6c0fd4 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -13,6 +13,8 @@ import (
"testing"
"time"
+ "github.com/prebid/prebid-server/prebid_cache_client"
+
"github.com/buger/jsonparser"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/config"
@@ -511,7 +513,7 @@ func mockHandler(statusCode int, getBody string, postBody string) http.Handler {
type wellBehavedCache struct{}
-func (c *wellBehavedCache) PutJson(ctx context.Context, values []json.RawMessage) []string {
+func (c *wellBehavedCache) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) []string {
ids := make([]string, len(values))
for i := 0; i < len(values); i++ {
ids[i] = strconv.Itoa(i)
diff --git a/exchange/exchangetest/targeting-cache-vast-banner.json b/exchange/exchangetest/targeting-cache-vast-banner.json
new file mode 100644
index 00000000000..a2cddfe0cbf
--- /dev/null
+++ b/exchange/exchangetest/targeting-cache-vast-banner.json
@@ -0,0 +1,85 @@
+{
+ "incomingRequest": {
+ "ortbRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 1
+ }
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "cache": {
+ "vastxml": {}
+ },
+ "targeting": {}
+ }
+ }
+ }
+ },
+ "outgoingRequests": {
+ "appnexus": {
+ "mockResponse": {
+ "pbsSeatBid": {
+ "pbsBids": [
+ {
+ "ortbBid": {
+ "id": "winning-bid",
+ "impid": "my-imp-id",
+ "price": 0.01,
+ "w": 200,
+ "h": 250,
+ "crid": "creative-1"
+ },
+ "bidType": "banner"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "response": {
+ "bids": {
+ "id": "some-request-id",
+ "seatbid": [
+ {
+ "seat": "appnexus",
+ "bid": [{
+ "id": "winning-bid",
+ "impid": "my-imp-id",
+ "price": 0.01,
+ "w": 200,
+ "h": 250,
+ "crid": "creative-1",
+ "ext": {
+ "prebid": {
+ "type": "banner",
+ "targeting": {
+ "hb_bidder": "appnexus",
+ "hb_bidder_appnexus": "appnexus",
+ "hb_pb": "0.00",
+ "hb_pb_appnexus": "0.00",
+ "hb_size": "200x250",
+ "hb_size_appnexus": "200x250",
+ "hb_creative_loadtype": "html"
+ }
+ }
+ }
+ }]
+ }
+ ]
+ }
+ }
+}
diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json
new file mode 100644
index 00000000000..a7bb4b46482
--- /dev/null
+++ b/exchange/exchangetest/targeting-cache-vast.json
@@ -0,0 +1,86 @@
+{
+ "incomingRequest": {
+ "ortbRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "test.somepage.com"
+ },
+ "imp": [
+ {
+ "id": "my-imp-id",
+ "video": {
+ "mimes": ["video/mp4"]
+ },
+ "ext": {
+ "appnexus": {
+ "placementId": 1
+ }
+ }
+ }
+ ],
+ "ext": {
+ "prebid": {
+ "cache": {
+ "vastxml": {}
+ },
+ "targeting": {}
+ }
+ }
+ }
+ },
+ "outgoingRequests": {
+ "appnexus": {
+ "mockResponse": {
+ "pbsSeatBid": {
+ "pbsBids": [
+ {
+ "ortbBid": {
+ "id": "winning-bid",
+ "impid": "my-imp-id",
+ "price": 0.01,
+ "w": 200,
+ "h": 250,
+ "crid": "creative-1"
+ },
+ "bidType": "video"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "response": {
+ "bids": {
+ "id": "some-request-id",
+ "seatbid": [
+ {
+ "seat": "appnexus",
+ "bid": [{
+ "id": "winning-bid",
+ "impid": "my-imp-id",
+ "price": 0.01,
+ "w": 200,
+ "h": 250,
+ "crid": "creative-1",
+ "ext": {
+ "prebid": {
+ "type": "video",
+ "targeting": {
+ "hb_bidder": "appnexus",
+ "hb_bidder_appnexus": "appnexus",
+ "hb_uuid": "0",
+ "hb_uuid_appnexus": "0",
+ "hb_pb": "0.00",
+ "hb_pb_appnexus": "0.00",
+ "hb_size": "200x250",
+ "hb_size_appnexus": "200x250",
+ "hb_creative_loadtype": "html"
+ }
+ }
+ }
+ }]
+ }
+ ]
+ }
+ }
+}
diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json
index 5c21f914eda..004611f4615 100644
--- a/exchange/exchangetest/targeting-cache-zero.json
+++ b/exchange/exchangetest/targeting-cache-zero.json
@@ -23,7 +23,8 @@
"ext": {
"prebid": {
"cache": {
- "bids": {}
+ "bids": {},
+ "vastxml": {}
},
"targeting": {}
}
@@ -72,6 +73,8 @@
"hb_bidder_appnexus": "appnexus",
"hb_cache_id": "0",
"hb_cache_id_appnexus": "0",
+ "hb_uuid": "1",
+ "hb_uuid_appnexus": "1",
"hb_pb": "0.00",
"hb_pb_appnexus": "0.00",
"hb_size": "200x250",
diff --git a/exchange/targeting.go b/exchange/targeting.go
index 8f66d6fec90..e74d4499cc4 100644
--- a/exchange/targeting.go
+++ b/exchange/targeting.go
@@ -20,7 +20,8 @@ type targetData struct {
priceGranularity openrtb_ext.PriceGranularity
includeWinners bool
includeBidderKeys bool
- includeCache bool
+ includeCacheBids bool
+ includeCacheVast bool
}
// setTargeting writes all the targeting params into the bids.
@@ -43,8 +44,11 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool) {
if hbSize := makeHbSize(topBidPerBidder.bid); hbSize != "" {
targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, bidderName, isOverallWinner)
}
- if cacheId, ok := auc.cacheIds[topBidPerBidder.bid]; ok {
- targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheId, bidderName, isOverallWinner)
+ if cacheID, ok := auc.cacheIds[topBidPerBidder.bid]; ok {
+ targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, bidderName, isOverallWinner)
+ }
+ if vastID, ok := auc.vastCacheIds[topBidPerBidder.bid]; ok {
+ targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, bidderName, isOverallWinner)
}
if deal := topBidPerBidder.bid.DealID; len(deal) > 0 {
targData.addKeys(targets, openrtb_ext.HbDealIdConstantKey, deal, bidderName, isOverallWinner)
diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go
index 84bb02bfefe..453f8aa2a4e 100644
--- a/openrtb_ext/bid.go
+++ b/openrtb_ext/bid.go
@@ -87,9 +87,14 @@ const (
// Other demand sources are happy to let Prebid Mobile use a Webview.
HbCreativeLoadMethodConstantKey TargetingKey = "hb_creative_loadtype"
HbDealIdConstantKey TargetingKey = "hb_deal"
- // HbCacheKey stores the UUID which can be used to fetch the bid data from prebid cache.
- // Callers should *never* assume that this exists, since the call to the cache may always fail.
- HbCacheKey TargetingKey = "hb_cache_id"
+
+ // HbCacheKey and HbVastCacheKey store UUIDs which can be used to fetch things from prebid cache.
+ // Callers should *never* assume that either of these exist, since the call to the cache may always fail.
+ //
+ // HbVastCacheKey's UUID will fetch the entire bid JSON, while HbVastCacheKey will fetch just the VAST XML.
+ // HbVastCacheKey will only ever exist for Video bids.
+ HbCacheKey TargetingKey = "hb_cache_id"
+ HbVastCacheKey TargetingKey = "hb_uuid"
// These are not keys, but values used by hbCreativeLoadMethodConstantKey
HbCreativeLoadMethodHTML string = "html"
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 0e8fffcf43a..c0175364f16 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -21,7 +21,8 @@ type ExtRequestPrebid struct {
// ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache
type ExtRequestPrebidCache struct {
- Bids *ExtRequestPrebidCacheBids `json:"bids"`
+ Bids *ExtRequestPrebidCacheBids `json:"bids"`
+ VastXML *ExtRequestPrebidCacheVAST `json:"vastxml"`
}
// UnmarshalJSON prevents nil bids arguments.
@@ -32,8 +33,8 @@ func (ert *ExtRequestPrebidCache) UnmarshalJSON(b []byte) error {
return err
}
- if proxy.Bids == nil {
- return errors.New(`request.ext.prebid.cache missing required property "bids"`)
+ if proxy.Bids == nil && proxy.VastXML == nil {
+ return errors.New(`request.ext.prebid.cache requires one of "bids" or "vastml" to be defined`)
}
*ert = ExtRequestPrebidCache(proxy)
@@ -43,6 +44,9 @@ func (ert *ExtRequestPrebidCache) UnmarshalJSON(b []byte) error {
// ExtRequestPrebidCacheBids defines the contract for bidrequest.ext.prebid.cache.bids
type ExtRequestPrebidCacheBids struct{}
+// ExtRequestPrebidCacheVAST defines the contract for bidrequest.ext.prebid.cache.vastxml
+type ExtRequestPrebidCacheVAST struct{}
+
// ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting
type ExtRequestTargeting struct {
PriceGranularity PriceGranularity `json:"pricegranularity"`
diff --git a/openrtb_ext/request_test.go b/openrtb_ext/request_test.go
index 7dac44729e7..860334af98f 100644
--- a/openrtb_ext/request_test.go
+++ b/openrtb_ext/request_test.go
@@ -4,6 +4,8 @@ import (
"encoding/json"
"reflect"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
// Test the unmashalling of the prebid extensions and setting default Price Granularity
@@ -80,14 +82,23 @@ func TestCacheIllegal(t *testing.T) {
}
}
-func TestCacheLegal(t *testing.T) {
+func TestCacheBids(t *testing.T) {
var bids ExtRequestPrebidCache
- if err := json.Unmarshal([]byte(`{"bids":{}}`), &bids); err != nil {
- t.Error("Unmarshal should succeed when cache.bids is defined.")
- }
- if bids.Bids == nil {
- t.Error("bids.Bids should not be nil.")
- }
+ assert.NoError(t, json.Unmarshal([]byte(`{"bids":{}}`), &bids))
+ assert.NotNil(t, bids.Bids)
+ assert.Nil(t, bids.VastXML)
+}
+
+func TestCacheVast(t *testing.T) {
+ var bids ExtRequestPrebidCache
+ assert.NoError(t, json.Unmarshal([]byte(`{"vastxml":{}}`), &bids))
+ assert.Nil(t, bids.Bids)
+ assert.NotNil(t, bids.VastXML)
+}
+
+func TestCacheNothing(t *testing.T) {
+ var bids ExtRequestPrebidCache
+ assert.Error(t, json.Unmarshal([]byte(`{}`), &bids))
}
type granularityTestData struct {
diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go
index 15561e81f58..da08c1bd979 100644
--- a/prebid_cache_client/client.go
+++ b/prebid_cache_client/client.go
@@ -4,12 +4,13 @@ import (
"bytes"
"context"
"encoding/json"
+ "io/ioutil"
+ "net/http"
+
"github.com/buger/jsonparser"
"github.com/golang/glog"
"github.com/prebid/prebid-server/config"
"golang.org/x/net/context/ctxhttp"
- "io/ioutil"
- "net/http"
)
// Client stores values in Prebid Cache. For more info, see https://github.com/prebid/prebid-cache
@@ -19,7 +20,19 @@ type Client interface {
// The returned string slice will always have the same number of elements as the values argument. If a
// value could not be saved, the element will be an empty string. Implementations are responsible for
// logging any relevant errors to the app logs
- PutJson(ctx context.Context, values []json.RawMessage) []string
+ PutJson(ctx context.Context, values []Cacheable) []string
+}
+
+type PayloadType string
+
+const (
+ TypeJSON PayloadType = "json"
+ TypeXML PayloadType = "xml"
+)
+
+type Cacheable struct {
+ Type PayloadType
+ Data json.RawMessage
}
func NewClient(conf *config.Cache) Client {
@@ -39,7 +52,7 @@ type clientImpl struct {
putUrl string
}
-func (c *clientImpl) PutJson(ctx context.Context, values []json.RawMessage) (uuids []string) {
+func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []string) {
if len(values) < 1 {
return nil
}
@@ -95,7 +108,7 @@ func (c *clientImpl) PutJson(ctx context.Context, values []json.RawMessage) (uui
return uuidsToReturn
}
-func encodeValues(values []json.RawMessage) ([]byte, error) {
+func encodeValues(values []Cacheable) ([]byte, error) {
// This function assumes that m is non-nil and has at least one element.
// clientImp.PutBids should respect this.
var buf bytes.Buffer
@@ -109,18 +122,15 @@ func encodeValues(values []json.RawMessage) ([]byte, error) {
return buf.Bytes(), nil
}
-func encodeValueToBuffer(value json.RawMessage, leadingComma bool, buffer *bytes.Buffer) error {
+func encodeValueToBuffer(value Cacheable, leadingComma bool, buffer *bytes.Buffer) error {
if leadingComma {
buffer.WriteByte(',')
}
- encodedBytes, err := json.Marshal(value)
- if err != nil {
- return err
- } else {
- buffer.WriteString(`{"type":"json","value":`)
- buffer.Write(encodedBytes)
- buffer.WriteByte('}')
- }
+ buffer.WriteString(`{"type":"`)
+ buffer.WriteString(string(value.Type))
+ buffer.WriteString(`","value":`)
+ buffer.Write(value.Data)
+ buffer.WriteByte('}')
return nil
}
diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go
index 4fd2a27a5b5..f72e2d08d96 100644
--- a/prebid_cache_client/client_test.go
+++ b/prebid_cache_client/client_test.go
@@ -23,7 +23,7 @@ func TestEmptyPut(t *testing.T) {
}
ids := client.PutJson(context.Background(), nil)
assertIntEqual(t, len(ids), 0)
- ids = client.PutJson(context.Background(), []json.RawMessage{})
+ ids = client.PutJson(context.Background(), []Cacheable{})
assertIntEqual(t, len(ids), 0)
}
@@ -38,7 +38,15 @@ func TestBadResponse(t *testing.T) {
httpClient: server.Client(),
putUrl: server.URL,
}
- ids := client.PutJson(context.Background(), []json.RawMessage{json.RawMessage("true"), json.RawMessage("false")})
+ ids := client.PutJson(context.Background(), []Cacheable{
+ Cacheable{
+ Type: TypeJSON,
+ Data: json.RawMessage("true"),
+ }, Cacheable{
+ Type: TypeJSON,
+ Data: json.RawMessage("false"),
+ },
+ })
assertIntEqual(t, len(ids), 2)
assertStringEqual(t, ids[0], "")
assertStringEqual(t, ids[1], "")
@@ -58,7 +66,11 @@ func TestCancelledContext(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
- ids := client.PutJson(ctx, []json.RawMessage{json.RawMessage("true")})
+ ids := client.PutJson(ctx, []Cacheable{Cacheable{
+ Type: TypeJSON,
+ Data: json.RawMessage("true"),
+ },
+ })
assertIntEqual(t, len(ids), 1)
assertStringEqual(t, ids[0], "")
}
@@ -72,7 +84,15 @@ func TestSuccessfulPut(t *testing.T) {
putUrl: server.URL,
}
- ids := client.PutJson(context.Background(), []json.RawMessage{json.RawMessage("true"), json.RawMessage("false")})
+ ids := client.PutJson(context.Background(), []Cacheable{
+ Cacheable{
+ Type: TypeJSON,
+ Data: json.RawMessage("true"),
+ }, Cacheable{
+ Type: TypeJSON,
+ Data: json.RawMessage("false"),
+ },
+ })
assertIntEqual(t, len(ids), 2)
assertStringEqual(t, ids[0], "0")
assertStringEqual(t, ids[1], "1")