-
Notifications
You must be signed in to change notification settings - Fork 735
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
Add Datablocks Adapter #1095
Add Datablocks Adapter #1095
Changes from 8 commits
7ea3773
47122b6
2f809c4
0c268a7
ddb118b
59d2aeb
b5602d6
7c5295d
c7abefc
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 |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package datablocks | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/mxmCherry/openrtb" | ||
"github.com/prebid/prebid-server/adapters" | ||
"github.com/prebid/prebid-server/errortypes" | ||
"github.com/prebid/prebid-server/macros" | ||
"github.com/prebid/prebid-server/openrtb_ext" | ||
"net/http" | ||
"strconv" | ||
"text/template" | ||
) | ||
|
||
type DatablocksAdapter struct { | ||
EndpointTemplate template.Template | ||
} | ||
|
||
func (a *DatablocksAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
|
||
errs := make([]error, 0, len(request.Imp)) | ||
headers := http.Header{ | ||
"Content-Type": {"application/json"}, | ||
"Accept": {"application/json"}, | ||
} | ||
|
||
// Pull the host and source ID info from the bidder params. | ||
reqImps, err := splitImpressions(request.Imp) | ||
|
||
if err != nil { | ||
errs = append(errs, err) | ||
} | ||
|
||
requests := []*adapters.RequestData{} | ||
|
||
for reqExt, reqImp := range reqImps { | ||
request.Imp = reqImp | ||
reqJson, err := json.Marshal(request) | ||
|
||
if err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
|
||
urlParams := macros.EndpointTemplateParams{Host: reqExt.Host, SourceId: strconv.Itoa(reqExt.SourceId)} | ||
url, err := macros.ResolveMacros(a.EndpointTemplate, urlParams) | ||
|
||
if err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
|
||
request := adapters.RequestData{ | ||
Method: "POST", | ||
Uri: url, | ||
Body: reqJson, | ||
Headers: headers} | ||
|
||
requests = append(requests, &request) | ||
} | ||
|
||
return requests, errs | ||
} | ||
|
||
/* | ||
internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) | ||
*/ | ||
func (a *DatablocksAdapter) MakeBids( | ||
internalRequest *openrtb.BidRequest, | ||
externalRequest *adapters.RequestData, | ||
response *adapters.ResponseData, | ||
) (*adapters.BidderResponse, []error) { | ||
|
||
if response.StatusCode != http.StatusOK { | ||
return nil, []error{&errortypes.BadServerResponse{ | ||
Message: fmt.Sprintf("ERR, response with status %d", response.StatusCode), | ||
}} | ||
} | ||
|
||
var bidResp openrtb.BidResponse | ||
|
||
if err := json.Unmarshal(response.Body, &bidResp); err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
bidResponse := adapters.NewBidderResponse() | ||
bidResponse.Currency = bidResp.Cur | ||
|
||
for _, seatBid := range bidResp.SeatBid { | ||
for i := 0; i < len(seatBid.Bid); i++ { | ||
bid := seatBid.Bid[i] | ||
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ | ||
Bid: &bid, | ||
BidType: getMediaType(bid.ImpID, internalRequest.Imp), | ||
}) | ||
} | ||
} | ||
|
||
return bidResponse, nil | ||
} | ||
|
||
func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp, error) { | ||
|
||
var m = make(map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp) | ||
|
||
for _, imp := range imps { | ||
bidderParams, err := getBidderParams(&imp) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
_, ok := m[*bidderParams] | ||
if ok { | ||
v, _ := m[*bidderParams] | ||
m[*bidderParams] = append(v, imp) | ||
} else { | ||
m[*bidderParams] = []openrtb.Imp{imp} | ||
} | ||
} | ||
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. Nitpick:
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. Thats a good nitpick ;) |
||
|
||
return m, nil | ||
} | ||
|
||
func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpDatablocks, error) { | ||
var bidderExt adapters.ExtImpBidder | ||
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { | ||
return nil, &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Missing bidder ext: %s",err.Error()), | ||
} | ||
} | ||
var datablocksExt openrtb_ext.ExtImpDatablocks | ||
if err := json.Unmarshal(bidderExt.Bidder, &datablocksExt); err != nil { | ||
return nil, &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()), | ||
} | ||
} | ||
|
||
if datablocksExt.SourceId < 1 { | ||
return nil, &errortypes.BadInput{ | ||
Message: "Invalid/Missing SourceId", | ||
} | ||
} | ||
|
||
if len(datablocksExt.Host) < 1 { | ||
return nil, &errortypes.BadInput{ | ||
Message: "Invalid/Missing Host", | ||
} | ||
} | ||
|
||
return &datablocksExt, nil | ||
} | ||
|
||
func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType { | ||
|
||
bidType := openrtb_ext.BidTypeBanner | ||
|
||
for _, imp := range imps { | ||
if imp.ID == impID { | ||
if imp.Video != nil { | ||
bidType = openrtb_ext.BidTypeVideo | ||
break | ||
} else if imp.Native != nil { | ||
bidType = openrtb_ext.BidTypeNative | ||
break | ||
} else { | ||
bidType = openrtb_ext.BidTypeBanner | ||
break | ||
} | ||
} | ||
} | ||
|
||
return bidType | ||
} | ||
|
||
func NewDatablocksBidder(endpoint string) *DatablocksAdapter { | ||
template, err := template.New("endpointTemplate").Parse(endpoint) | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
return &DatablocksAdapter{EndpointTemplate: *template} | ||
} | ||
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. Some other adapters log a message when the template couldn't be parsed. I believe it'd make sense to add one in our context.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package datablocks | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prebid/prebid-server/adapters/adapterstest" | ||
) | ||
|
||
func TestJsonSamples(t *testing.T) { | ||
adapterstest.RunJSONBidderTest(t, "datablockstest", NewDatablocksBidder("http://{{.Host}}/openrtb2?sid={{.SourceId}}")) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
{ | ||
"mockBidRequest": | ||
{ | ||
"id": "some-request-id", | ||
"imp": [ | ||
{ | ||
"id": "some-impression-id", | ||
"banner": | ||
{ | ||
"format": [ | ||
{ | ||
"w": 300, | ||
"h": 250 | ||
}] | ||
}, | ||
"ext": | ||
{ | ||
"bidder": | ||
{ | ||
"host": "search.nutella.datablocks.net", | ||
"sourceId": 906295 | ||
} | ||
} | ||
},{ | ||
"id": "some-impression-id2", | ||
"banner": | ||
{ | ||
"format": [{ | ||
"w": 300, | ||
"h": 600 | ||
}] | ||
}, | ||
"ext": | ||
{ | ||
"bidder": | ||
{ | ||
"host": "search.nutella.datablocks.net", | ||
"sourceId": 906295 | ||
} | ||
} | ||
}], | ||
"site": | ||
{ | ||
"page": "prebid.org" | ||
}, | ||
"device": | ||
{ | ||
"ip": "8.8.8.10" | ||
}, | ||
"at": 1, | ||
"tmax": 500 | ||
}, | ||
"httpCalls": [ | ||
{ | ||
"expectedRequest": | ||
{ | ||
"uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295", | ||
"body": | ||
{ | ||
"id": "some-request-id", | ||
"imp": [ | ||
{ | ||
"id": "some-impression-id", | ||
"banner": | ||
{ | ||
"format": [ | ||
{ | ||
"w": 300, | ||
"h": 250 | ||
}] | ||
}, | ||
"ext": | ||
{ | ||
"bidder": | ||
{ | ||
"host": "search.nutella.datablocks.net", | ||
"sourceId": 906295 | ||
} | ||
} | ||
},{ | ||
"id": "some-impression-id2", | ||
"banner": | ||
{ | ||
"format": [ | ||
{ | ||
"w": 300, | ||
"h": 600 | ||
}] | ||
}, | ||
"ext": | ||
{ | ||
"bidder": | ||
{ | ||
"host": "search.nutella.datablocks.net", | ||
"sourceId": 906295 | ||
} | ||
} | ||
}], | ||
"site": | ||
{ | ||
"page": "prebid.org" | ||
}, | ||
"device": | ||
{ | ||
"ip": "8.8.8.10" | ||
}, | ||
"at": 1, | ||
"tmax": 500 | ||
} | ||
}, | ||
"mockResponse": | ||
{ | ||
"status": 200, | ||
"body": | ||
{ | ||
"id": "some-request-id", | ||
"bidid": "183975330-5-29038-2", | ||
"seatbid": [ | ||
{ | ||
"seat": "906295", | ||
"bid": [ | ||
{ | ||
"id": "2181314349", | ||
"impid": "some-impression-id", | ||
"adm": "<style>.text_preview{padding:14px;overflow:hidden;display:flex;flex-direction:column;justify-content:center;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;margin:0;padding:0;color:#333;background:#fff}.text_decription{min-width:150px;padding:5px;flex-grow:0;}.title_preview a{color:#00f;font-weight:700;text-decoration:none}.description_preview{padding:0 0 20px;overflow:hidden;text-overflow:ellipsis}.click_url_preview{color:green}.image_container{position:relative;width:160;height:120}.image_container img{max-height:100%;max-width:100%}.loading{background:url() center no-repeat #eee;height:100%;width:100%;top:0;left:0;position:absolute;border-radius:0}</style><style>.text_boundary {width:300px;height:250px;}</style><div class=\"text_preview text_boundary\"><div class=\"text_decription\"><div class=\"title_preview\"><a href=\"https://track.nutella.datablocks.net/c/267237/?fcid=2181314349\" target=\"_top\">Datablocks Inc.</a></div><div class=\"description_preview\">Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.</div><a class=\"click_url_preview\" target=\"_top\" href=\"https://track.nutella.datablocks.net/c/267237/?fcid=2181314349\">www.datablocks.net</a></div></div><img alt=\"\" src=\"https://impression.nutella.datablocks.net/i/267237/?fcid=2181314349&pixel=1\" width=\"1\" height=\"1\" ></div>", | ||
"price": 13.37, | ||
"cid": "906293", | ||
"adid": "906297", | ||
"crid": "906299", | ||
"w": 300, | ||
"h": 250 | ||
}] | ||
}], | ||
"cur": "USD", | ||
"ext": | ||
{} | ||
} | ||
} | ||
}], | ||
"expectedBidResponses": [ | ||
{ | ||
"currency": "USD", | ||
"bids": [ | ||
{ | ||
"bid": | ||
{ | ||
"id": "2181314349", | ||
"impid": "some-impression-id", | ||
"adm": "<style>.text_preview{padding:14px;overflow:hidden;display:flex;flex-direction:column;justify-content:center;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;margin:0;padding:0;color:#333;background:#fff}.text_decription{min-width:150px;padding:5px;flex-grow:0;}.title_preview a{color:#00f;font-weight:700;text-decoration:none}.description_preview{padding:0 0 20px;overflow:hidden;text-overflow:ellipsis}.click_url_preview{color:green}.image_container{position:relative;width:160;height:120}.image_container img{max-height:100%;max-width:100%}.loading{background:url() center no-repeat #eee;height:100%;width:100%;top:0;left:0;position:absolute;border-radius:0}</style><style>.text_boundary {width:300px;height:250px;}</style><div class=\"text_preview text_boundary\"><div class=\"text_decription\"><div class=\"title_preview\"><a href=\"https://track.nutella.datablocks.net/c/267237/?fcid=2181314349\" target=\"_top\">Datablocks Inc.</a></div><div class=\"description_preview\">Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.</div><a class=\"click_url_preview\" target=\"_top\" href=\"https://track.nutella.datablocks.net/c/267237/?fcid=2181314349\">www.datablocks.net</a></div></div><img alt=\"\" src=\"https://impression.nutella.datablocks.net/i/267237/?fcid=2181314349&pixel=1\" width=\"1\" height=\"1\" ></div>", | ||
"price": 13.37, | ||
"cid": "906293", | ||
"adid": "906297", | ||
"crid": "906299", | ||
"w": 300, | ||
"h": 250 | ||
}, | ||
"type": "banner" | ||
}] | ||
}] | ||
} |
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.
When
response.StatusCode == http.StatusNoContent
other adapters don't return an error, they justreturn nil, nil
. Does it make sense to throw an error in the context of this adapter? Do we risk of flooding the logs with errors everytime theresponse.StatusCode == http.StatusNoContent
condition is met?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.
Our server (right now) always responds with a 200 request with an empty seat bid for no-bid cases. I added the 204 handling and test in my latest commit anyways for future proofing of our server.