-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OTT-130: Injecting Video Tracking Events in VAST #130
Changes from 21 commits
b6d4c5c
574d43a
ffa25db
b7bfc53
d5cdfae
de1df06
edf6712
e352622
d932936
6f26efc
1ab93c1
fcde3f4
6e4084b
1debe67
da3a63a
726b779
b9b8efa
992afa2
4abfaac
d1585a4
5d968e3
dab2ff2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,20 +3,27 @@ package events | |
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
"time" | ||
|
||
"github.com/PubMatic-OpenWrap/openrtb" | ||
accountService "github.com/PubMatic-OpenWrap/prebid-server/account" | ||
"github.com/PubMatic-OpenWrap/prebid-server/adapters" | ||
"github.com/PubMatic-OpenWrap/prebid-server/analytics" | ||
"github.com/PubMatic-OpenWrap/prebid-server/config" | ||
"github.com/PubMatic-OpenWrap/prebid-server/errortypes" | ||
"github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" | ||
"github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" | ||
"github.com/PubMatic-OpenWrap/prebid-server/stored_requests" | ||
|
||
// "github.com/beevik/etree" | ||
"github.com/PubMatic-OpenWrap/etree" | ||
"github.com/golang/glog" | ||
"github.com/julienschmidt/httprouter" | ||
) | ||
|
@@ -46,6 +53,26 @@ type CacheObject struct { | |
UUID string `json:"uuid"` | ||
} | ||
|
||
// standard VAST macros | ||
// https://interactiveadvertisingbureau.github.io/vast/vast4macros/vast4-macros-latest.html#macro-spec-adcount | ||
const ( | ||
VASTAdTypeMacro = "[ADTYPE]" | ||
VASTAppBundleMacro = "[APPBUNDLE]" | ||
VASTDomainMacro = "[DOMAIN]" | ||
VASTPageURLMacro = "[PAGEURL]" | ||
|
||
// PBS specific macros | ||
PBSEventIDMacro = "[EVENT_ID]" // macro for injecting PBS defined video event tracker id | ||
//[PBS-ACCOUNT] represents publisher id / account id | ||
PBSAccountMacro = "[PBS-ACCOUNT]" | ||
// [PBS-BIDDER] represents bidder name | ||
PBSBidderMacro = "[PBS-BIDDER]" | ||
// [ADERVERTISER_NAME] represents advertiser name | ||
PBSAdvertiserNameMacro = "[ADVERTISER_NAME]" | ||
) | ||
|
||
var trackingEvents = []string{"firstQuartile", "midpoint", "thirdQuartile", "complete"} | ||
|
||
func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos adapters.BidderInfos) httprouter.Handle { | ||
vte := &vtrackEndpoint{ | ||
Cfg: cfg, | ||
|
@@ -302,3 +329,147 @@ func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder, | |
} | ||
return json.RawMessage(vast) | ||
} | ||
|
||
//InjectVideoEventTrackers injects the video tracking events | ||
func InjectVideoEventTrackers(trackerURL, vastXML string, bid *openrtb.Bid, bidder, accountID string, timestamp int64, bidRequest *openrtb.BidRequest) ([]byte, bool, error) { | ||
// parse VAST | ||
doc := etree.NewDocument() | ||
err := doc.ReadFromString(vastXML) | ||
if nil != err { | ||
err := fmt.Sprintf("Error parsing VAST XML. '%v'", err.Error()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fmt.Errorf use instead Sprintf There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
glog.Errorf(err) | ||
return []byte(vastXML), false, errors.New(err) // false indicates events trackers are not injected | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. return err itself There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
} | ||
eventURLMap := GetVideoEventTracking(trackerURL, bid, bidder, accountID, timestamp, bidRequest, doc) | ||
trackersInjected := false | ||
// return if if no tracking URL | ||
if len(eventURLMap) == 0 { | ||
return []byte(vastXML), false, errors.New("Event URLs are not found") | ||
} | ||
|
||
// Find Creatives of Linear and NonLinear Type | ||
// Injecting Tracking Events for Companion is not supported here | ||
creatives := doc.FindElements("VAST/Ad/InLine/Creatives/Creative/Linear") | ||
creatives = append(creatives, doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/Linear")...) | ||
creatives = append(creatives, doc.FindElements("VAST/Ad/InLine/Creatives/Creative/NonLinearAds")...) | ||
creatives = append(creatives, doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/NonLinearAds")...) | ||
|
||
if "" == strings.Trim(bid.AdM, " ") || strings.HasPrefix(strings.TrimLeft(strings.TrimRight(bid.AdM, "]]>"), "<![CDATA["), "http") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
//Maintaining BidRequest Impression Map (Copied from exchange.go#applyCategoryMapping) | ||
//TODO: It should be optimized by forming once and reusing | ||
impMap := make(map[string]*openrtb.Imp) | ||
for i := range bidRequest.Imp { | ||
impMap[bidRequest.Imp[i].ID] = &bidRequest.Imp[i] | ||
} | ||
|
||
// determine which creative type to be created based on linearity | ||
if imp, ok := impMap[bid.ImpID]; ok && nil != imp.Video { | ||
// create creative object | ||
creatives = doc.FindElements("VAST/Ad/Wrapper/Creatives") | ||
// var creative *etree.Element | ||
// if len(creatives) > 0 { | ||
// creative = creatives[0] // consider only first creative | ||
// } else { | ||
creative := doc.CreateElement("Creative") | ||
creatives[0].AddChild(creative) | ||
|
||
// } | ||
|
||
switch imp.Video.Linearity { | ||
case openrtb.VideoLinearityLinearInStream: | ||
creative.AddChild(doc.CreateElement("Linear")) | ||
case openrtb.VideoLinearityNonLinearOverlay: | ||
creative.AddChild(doc.CreateElement("NonLinearAds")) | ||
default: // create both type of creatives | ||
creative.AddChild(doc.CreateElement("Linear")) | ||
creative.AddChild(doc.CreateElement("NonLinearAds")) | ||
} | ||
creatives = creative.ChildElements() // point to actual cratives | ||
} | ||
} | ||
for _, creative := range creatives { | ||
trackingEvents := creative.SelectElement("TrackingEvents") | ||
if nil == trackingEvents { | ||
trackingEvents = creative.CreateElement("TrackingEvents") | ||
creative.AddChild(trackingEvents) | ||
} | ||
// Inject | ||
for event, url := range eventURLMap { | ||
trackingEle := trackingEvents.CreateElement("Tracking") | ||
trackingEle.CreateAttr("event", event) | ||
trackingEle.SetText(fmt.Sprintf("%s", url)) | ||
trackersInjected = true | ||
} | ||
} | ||
|
||
out := []byte(vastXML) | ||
var wErr error | ||
if trackersInjected { | ||
out, wErr = doc.WriteToBytes() | ||
trackersInjected = trackersInjected && nil == wErr | ||
if nil != wErr { | ||
glog.Errorf("%v", wErr.Error()) | ||
} | ||
} | ||
return out, trackersInjected, wErr | ||
} | ||
|
||
// GetVideoEventTracking returns map containing key as event name value as associaed video event tracking URL | ||
// By default PBS will expect [EVENT_ID] macro in trackerURL to inject event information | ||
// [EVENT_ID] will be injected with one of the following values | ||
// firstQuartile, midpoint, thirdQuartile, complete | ||
// If your company can not use [EVENT_ID] and has its own macro. provide config.TrackerMacros implementation | ||
// and ensure that your macro is part of trackerURL configuration | ||
func GetVideoEventTracking(trackerURL string, bid *openrtb.Bid, bidder string, accountId string, timestamp int64, req *openrtb.BidRequest, doc *etree.Document) map[string]string { | ||
eventURLMap := make(map[string]string) | ||
if "" == strings.Trim(trackerURL, " ") { | ||
return eventURLMap | ||
} | ||
|
||
// PubMatic specific event IDs | ||
// This will go in event-config once PreBid modular design is in place | ||
eventIDMap := map[string]string{ | ||
"firstQuartile": "4", | ||
"midpoint": "3", | ||
"thirdQuartile": "5", | ||
"complete": "6", | ||
} | ||
for _, event := range trackingEvents { | ||
eventURL := trackerURL | ||
|
||
// replace standard macros | ||
eventURL = replaceMacro(eventURL, VASTAdTypeMacro, string(openrtb_ext.BidTypeVideo)) | ||
if nil != req && nil != req.App { | ||
eventURL = replaceMacro(eventURL, VASTAppBundleMacro, req.App.Bundle) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check Macro here, Domain or Bundle There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [DOMAIN] will be req.App.Bundle in case of Apps |
||
if nil != req.App.Publisher { | ||
eventURL = replaceMacro(eventURL, PBSAccountMacro, req.App.Publisher.ID) | ||
} | ||
} | ||
if nil != req && nil != req.Site { | ||
eventURL = replaceMacro(eventURL, VASTDomainMacro, req.Site.Domain) | ||
eventURL = replaceMacro(eventURL, VASTPageURLMacro, req.Site.Page) | ||
if nil != req.Site.Publisher { | ||
eventURL = replaceMacro(eventURL, PBSAccountMacro, req.Site.Publisher.ID) | ||
} | ||
} | ||
|
||
if len(bid.ADomain) > 0 { | ||
eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, strings.Join(bid.ADomain, ",")) | ||
} | ||
|
||
eventURL = replaceMacro(eventURL, PBSBidderMacro, bidder) | ||
// replace [EVENT_ID] macro with PBS defined event ID | ||
eventURL = replaceMacro(eventURL, PBSEventIDMacro, eventIDMap[event]) | ||
eventURLMap[event] = eventURL | ||
} | ||
return eventURLMap | ||
} | ||
|
||
func replaceMacro(trackerURL, macro, value string) string { | ||
if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(strings.Trim(value, " ")) > 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will it replace " [MACRO] "? Check with trimmed value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added TrimSpace |
||
trackerURL = strings.ReplaceAll(trackerURL, macro, url.QueryEscape(value)) | ||
} else { | ||
glog.Warningf("Invalid macro '%v'. Either empty or missing prefix '[' or suffix ']", macro) | ||
} | ||
return trackerURL | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
write parameter single line description
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done