Skip to content
This repository was archived by the owner on Dec 22, 2022. It is now read-only.

Commit fa56225

Browse files
authored
Add AdOcean adapter (prebid#1273)
* [ADOCEAN-20132] AdOcean adapter * [ADOCEAN-20132] AdOcean adapter - support for gdpr * [ADOCEAN-20132] AdOcean adapter - tests * [ADOCEAN-20132] AdOcean adapter - user sync * [ADOCEAN-20132] AdOcean adapter - formatting * [ADOCEAN-20132] AdOcean adapter - send uuid to emitter * [ADOCEAN-20132] adocean adapter - return nil if there is no creative * [ADOCEAN-20132] AdOcean adapter - add version parameter * [ADOCEAN-20132] AdOcean adapter - optimization * [ADOCEAN-20132] AdOcean adapter - add to syncer_test.go * [ADOCEAN-20132] AdOcean adapter - changes after review: * remove whitespaces in js code on adapter initialization instead on every request * check if request.Site is not nil * reuse newUri variable * [ADOCEAN-20132] AdOcean adapter - changes after review: * do not terminate the auction on a single faulty bid * [ADOCEAN-20132] AdOcean adapter - changes after review: * remove unnecessary input parameters check * small optimization
1 parent 561fc99 commit fa56225

22 files changed

+1287
-0
lines changed

adapters/adocean/adocean.go

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
package adocean
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"math/rand"
7+
"net/http"
8+
"net/url"
9+
"regexp"
10+
"strconv"
11+
"strings"
12+
"text/template"
13+
14+
"github.com/golang/glog"
15+
"github.com/mxmCherry/openrtb"
16+
"github.com/prebid/prebid-server/adapters"
17+
"github.com/prebid/prebid-server/errortypes"
18+
"github.com/prebid/prebid-server/macros"
19+
"github.com/prebid/prebid-server/openrtb_ext"
20+
)
21+
22+
const adapterVersion = "1.0.0"
23+
const maxUriLength = 8000
24+
const measurementCode = `
25+
<script>
26+
+function() {
27+
var wu = "%s";
28+
var su = "%s".replace(/\[TIMESTAMP\]/, Date.now());
29+
30+
if (wu && !(navigator.sendBeacon && navigator.sendBeacon(wu))) {
31+
(new Image(1,1)).src = wu
32+
}
33+
34+
if (su && !(navigator.sendBeacon && navigator.sendBeacon(su))) {
35+
(new Image(1,1)).src = su
36+
}
37+
}();
38+
</script>
39+
`
40+
41+
type ResponseAdUnit struct {
42+
ID string `json:"id"`
43+
CrID string `json:"crid"`
44+
Currency string `json:"currency"`
45+
Price string `json:"price"`
46+
Width string `json:"width"`
47+
Height string `json:"height"`
48+
Code string `json:"code"`
49+
WinURL string `json:"winUrl"`
50+
StatsURL string `json:"statsUrl"`
51+
Error string `json:"error"`
52+
}
53+
54+
func NewAdOceanBidder(client *http.Client, endpointTemplateString string) *AdOceanAdapter {
55+
a := &adapters.HTTPAdapter{Client: client}
56+
endpointTemplate, err := template.New("endpointTemplate").Parse(endpointTemplateString)
57+
if err != nil {
58+
glog.Fatal("Unable to parse endpoint template")
59+
return nil
60+
}
61+
62+
whiteSpace := regexp.MustCompile(`\s+`)
63+
64+
return &AdOceanAdapter{
65+
http: a,
66+
endpointTemplate: *endpointTemplate,
67+
measurementCode: whiteSpace.ReplaceAllString(measurementCode, " "),
68+
}
69+
}
70+
71+
type AdOceanAdapter struct {
72+
http *adapters.HTTPAdapter
73+
endpointTemplate template.Template
74+
measurementCode string
75+
}
76+
77+
func (a *AdOceanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
78+
if len(request.Imp) == 0 {
79+
return nil, []error{&errortypes.BadInput{
80+
Message: "No impression in the bid request",
81+
}}
82+
}
83+
84+
consentString := ""
85+
if request.User != nil {
86+
var extUser openrtb_ext.ExtUser
87+
if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
88+
consentString = extUser.Consent
89+
}
90+
}
91+
92+
var httpRequests []*adapters.RequestData
93+
var errors []error
94+
95+
for _, auction := range request.Imp {
96+
newHttpRequest, err := a.makeRequest(httpRequests, &auction, request, consentString)
97+
if err != nil {
98+
errors = append(errors, err)
99+
} else if newHttpRequest != nil {
100+
httpRequests = append(httpRequests, newHttpRequest)
101+
}
102+
}
103+
104+
return httpRequests, errors
105+
}
106+
107+
func (a *AdOceanAdapter) makeRequest(existingRequests []*adapters.RequestData, imp *openrtb.Imp, request *openrtb.BidRequest, consentString string) (*adapters.RequestData, error) {
108+
var bidderExt adapters.ExtImpBidder
109+
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
110+
return nil, &errortypes.BadInput{
111+
Message: "Error parsing bidderExt object",
112+
}
113+
}
114+
115+
var adOceanExt openrtb_ext.ExtImpAdOcean
116+
if err := json.Unmarshal(bidderExt.Bidder, &adOceanExt); err != nil {
117+
return nil, &errortypes.BadInput{
118+
Message: "Error parsing adOceanExt parameters",
119+
}
120+
}
121+
122+
addedToExistingRequest := addToExistingRequest(existingRequests, &adOceanExt, imp.ID)
123+
if addedToExistingRequest {
124+
return nil, nil
125+
}
126+
127+
url, err := a.makeURL(&adOceanExt, imp.ID, request, consentString)
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
headers := http.Header{}
133+
headers.Add("Content-Type", "application/json;charset=utf-8")
134+
headers.Add("Accept", "application/json")
135+
136+
if request.Device != nil {
137+
headers.Add("User-Agent", request.Device.UA)
138+
139+
if request.Device.IP != "" {
140+
headers.Add("X-Forwarded-For", request.Device.IP)
141+
} else if request.Device.IPv6 != "" {
142+
headers.Add("X-Forwarded-For", request.Device.IPv6)
143+
}
144+
}
145+
146+
if request.Site != nil {
147+
headers.Add("Referer", request.Site.Page)
148+
}
149+
150+
return &adapters.RequestData{
151+
Method: "GET",
152+
Uri: url,
153+
Headers: headers,
154+
}, nil
155+
}
156+
157+
func addToExistingRequest(existingRequests []*adapters.RequestData, newParams *openrtb_ext.ExtImpAdOcean, auctionID string) bool {
158+
requestsLoop:
159+
for _, request := range existingRequests {
160+
endpointURL, _ := url.Parse(request.Uri)
161+
queryParams := endpointURL.Query()
162+
masterID := queryParams["id"][0]
163+
164+
if masterID == newParams.MasterID {
165+
aids := queryParams["aid"]
166+
for _, aid := range aids {
167+
slaveID := strings.SplitN(aid, ":", 2)[0]
168+
if slaveID == newParams.SlaveID {
169+
continue requestsLoop
170+
}
171+
}
172+
173+
queryParams.Add("aid", newParams.SlaveID+":"+auctionID)
174+
endpointURL.RawQuery = queryParams.Encode()
175+
newUri := endpointURL.String()
176+
if len(newUri) < maxUriLength {
177+
request.Uri = newUri
178+
return true
179+
}
180+
}
181+
}
182+
183+
return false
184+
}
185+
186+
func (a *AdOceanAdapter) makeURL(params *openrtb_ext.ExtImpAdOcean, auctionID string, request *openrtb.BidRequest, consentString string) (string, error) {
187+
endpointParams := macros.EndpointTemplateParams{Host: params.EmitterDomain}
188+
host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams)
189+
if err != nil {
190+
return "", &errortypes.BadInput{
191+
Message: "Unable to parse endpoint url template: " + err.Error(),
192+
}
193+
}
194+
195+
endpointURL, err := url.Parse(host)
196+
if err != nil {
197+
return "", &errortypes.BadInput{
198+
Message: "Malformed URL: " + err.Error(),
199+
}
200+
}
201+
202+
randomizedPart := 10000000 + rand.Intn(99999999-10000000)
203+
if request.Test == 1 {
204+
randomizedPart = 10000000
205+
}
206+
endpointURL.Path = "/_" + strconv.Itoa(randomizedPart) + "/ad.json"
207+
208+
queryParams := url.Values{}
209+
queryParams.Add("pbsrv_v", adapterVersion)
210+
queryParams.Add("id", params.MasterID)
211+
queryParams.Add("nc", "1")
212+
queryParams.Add("nosecure", "1")
213+
queryParams.Add("aid", params.SlaveID+":"+auctionID)
214+
if consentString != "" {
215+
queryParams.Add("gdpr_consent", consentString)
216+
queryParams.Add("gdpr", "1")
217+
}
218+
if request.User != nil && request.User.BuyerUID != "" {
219+
queryParams.Add("hcuserid", request.User.BuyerUID)
220+
}
221+
endpointURL.RawQuery = queryParams.Encode()
222+
223+
return endpointURL.String(), nil
224+
}
225+
226+
func (a *AdOceanAdapter) MakeBids(
227+
internalRequest *openrtb.BidRequest,
228+
externalRequest *adapters.RequestData,
229+
response *adapters.ResponseData,
230+
) (*adapters.BidderResponse, []error) {
231+
if response.StatusCode != http.StatusOK {
232+
return nil, []error{fmt.Errorf("Unexpected status code: %d. Network error?", response.StatusCode)}
233+
}
234+
235+
requestURL, _ := url.Parse(externalRequest.Uri)
236+
queryParams := requestURL.Query()
237+
auctionIDs := queryParams["aid"]
238+
239+
bidResponses := make([]ResponseAdUnit, 0)
240+
if err := json.Unmarshal(response.Body, &bidResponses); err != nil {
241+
return nil, []error{err}
242+
}
243+
244+
var parsedResponses = adapters.NewBidderResponseWithBidsCapacity(len(auctionIDs))
245+
var errors []error
246+
var slaveToAuctionIDMap = make(map[string]string, len(auctionIDs))
247+
248+
for _, auctionFullID := range auctionIDs {
249+
auctionIDsSlice := strings.SplitN(auctionFullID, ":", 2)
250+
slaveToAuctionIDMap[auctionIDsSlice[0]] = auctionIDsSlice[1]
251+
}
252+
253+
for _, bid := range bidResponses {
254+
if auctionID, found := slaveToAuctionIDMap[bid.ID]; found {
255+
if bid.Error == "true" {
256+
continue
257+
}
258+
259+
price, _ := strconv.ParseFloat(bid.Price, 64)
260+
width, _ := strconv.ParseUint(bid.Width, 10, 64)
261+
height, _ := strconv.ParseUint(bid.Height, 10, 64)
262+
adCode, err := a.prepareAdCodeForBid(bid)
263+
if err != nil {
264+
errors = append(errors, err)
265+
continue
266+
}
267+
268+
parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{
269+
Bid: &openrtb.Bid{
270+
ID: bid.ID,
271+
ImpID: auctionID,
272+
Price: price,
273+
AdM: adCode,
274+
CrID: bid.CrID,
275+
W: width,
276+
H: height,
277+
},
278+
BidType: openrtb_ext.BidTypeBanner,
279+
})
280+
parsedResponses.Currency = bid.Currency
281+
}
282+
}
283+
284+
return parsedResponses, errors
285+
}
286+
287+
func (a *AdOceanAdapter) prepareAdCodeForBid(bid ResponseAdUnit) (string, error) {
288+
sspCode, err := url.QueryUnescape(bid.Code)
289+
if err != nil {
290+
return "", err
291+
}
292+
293+
adCode := fmt.Sprintf(a.measurementCode, bid.WinURL, bid.StatsURL) + sspCode
294+
295+
return adCode, nil
296+
}

adapters/adocean/adocean_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package adocean
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/prebid/prebid-server/adapters/adapterstest"
8+
)
9+
10+
func TestJsonSamples(t *testing.T) {
11+
adapterstest.RunJSONBidderTest(t, "adoceantest", NewAdOceanBidder(new(http.Client), "https://{{.Host}}"))
12+
}

0 commit comments

Comments
 (0)