Skip to content

Commit

Permalink
Add Pubnative Adapter (prebid#1134)
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Hartwig authored and mansinahar committed Apr 15, 2020
1 parent 7d1e5eb commit db71903
Show file tree
Hide file tree
Showing 14 changed files with 800 additions and 2 deletions.
179 changes: 179 additions & 0 deletions adapters/pubnative/pubnative.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package pubnative

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"

"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
)

type PubnativeAdapter struct {
URI string
}

func (a *PubnativeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
impCount := len(request.Imp)
requestData := make([]*adapters.RequestData, 0, impCount)
errs := []error{}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")

err := checkRequest(request)
if err != nil {
errs = append(errs, err)
return nil, errs
}

for _, imp := range request.Imp {
requestCopy := *request
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
errs = append(errs, err)
continue
}

var pubnativeExt openrtb_ext.ExtImpPubnative
if err := json.Unmarshal(bidderExt.Bidder, &pubnativeExt); err != nil {
errs = append(errs, err)
continue
}

err := convertImpression(&imp)
if err != nil {
errs = append(errs, err)
continue
}

requestCopy.Imp = []openrtb.Imp{imp}
reqJSON, err := json.Marshal(&requestCopy)
if err != nil {
errs = append(errs, err)
return nil, errs
}

queryParams := url.Values{}
queryParams.Add("apptoken", pubnativeExt.AppAuthToken)
queryParams.Add("zoneid", strconv.Itoa(pubnativeExt.ZoneID))
queryString := queryParams.Encode()

reqData := &adapters.RequestData{
Method: "POST",
Uri: fmt.Sprintf("%s?%s", a.URI, queryString),
Body: reqJSON,
Headers: headers,
}

requestData = append(requestData, reqData)
}
return requestData, errs
}

func checkRequest(request *openrtb.BidRequest) error {
if request.Device == nil || len(request.Device.OS) == 0 {
return &errortypes.BadInput{
Message: "Impression is missing device OS information",
}
}

request.Test = 0 // don't forward test flag to PN adserver
return nil
}

func convertImpression(imp *openrtb.Imp) error {
if imp.Banner == nil && imp.Video == nil && imp.Native == nil {
return &errortypes.BadInput{
Message: "Pubnative only supports banner, video or native ads.",
}
}
if imp.Banner != nil {
err := convertBanner(imp.Banner)
if err != nil {
return err
}
}

return nil
}

// make sure that banner has openrtb 2.3-compatible size information
func convertBanner(banner *openrtb.Banner) error {
if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 {
if len(banner.Format) > 0 {
f := banner.Format[0]
banner.W = &f.W
banner.H = &f.H
} else {
return &errortypes.BadInput{
Message: "Size information missing for banner",
}
}
}
return nil
}

func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if response.StatusCode == http.StatusNoContent {
return nil, nil
}

if response.StatusCode == http.StatusBadRequest {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
}}
}

if response.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
}}
}

var parsedResponse openrtb.BidResponse
if err := json.Unmarshal(response.Body, &parsedResponse); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: err.Error(),
}}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)

for _, sb := range parsedResponse.SeatBid {
for i := 0; i < len(sb.Bid); i++ {
bid := sb.Bid[i]
if bid.Price != 0 {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
})
}
}
}
return bidResponse, nil
}

func NewPubnativeBidder(uri string) *PubnativeAdapter {
return &PubnativeAdapter{URI: uri}
}

func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType {
mediaType := openrtb_ext.BidTypeBanner
for _, imp := range imps {
if imp.ID == impId {
if imp.Video != nil {
mediaType = openrtb_ext.BidTypeVideo
} else if imp.Native != nil {
mediaType = openrtb_ext.BidTypeNative
}
return mediaType
}
}
return mediaType
}
11 changes: 11 additions & 0 deletions adapters/pubnative/pubnative_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pubnative

import (
"testing"

"github.com/prebid/prebid-server/adapters/adapterstest"
)

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "pubnativetest", NewPubnativeBidder("http://example.com/prebid"))
}
130 changes: 130 additions & 0 deletions adapters/pubnative/pubnativetest/exemplary/native.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"mockBidRequest": {
"id": "some-req-id",
"imp": [
{
"id": "some-imp-id",
"native": {
"request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}",
"ver": "1.1"
},
"ext": {
"bidder": {
"zone_id": 1,
"app_auth_token": "4fd53a12b78af4b39835de9e449c3082"
}
}
}
],
"site": {
"domain": "example.com",
"page": "example.com"
},
"device": {
"ip": "152.193.6.74",
"os": "android"
},
"user": {
"id": "db089de9-a62e-4861-a881-0ff15e052516",
"buyeruid": "8299345306627569435"
},
"tmax": 500
},
"httpcalls": [
{
"expectedRequest": {
"uri": "http://example.com/prebid?apptoken=4fd53a12b78af4b39835de9e449c3082&zoneid=1",
"body": {
"id": "some-req-id",
"imp": [
{
"id": "some-imp-id",
"ext": {
"bidder": {
"zone_id": 1,
"app_auth_token": "4fd53a12b78af4b39835de9e449c3082"
}
},
"native": {
"request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}",
"ver": "1.1"
}
}
],
"site": {
"domain": "example.com",
"page": "example.com"
},
"device": {
"ip": "152.193.6.74",
"os": "android"
},
"user": {
"id": "db089de9-a62e-4861-a881-0ff15e052516",
"buyeruid": "8299345306627569435"
},
"tmax": 500
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "some-req-id",
"seatbid": [
{
"bid": [
{
"id": "928185755156387460",
"impid": "some-imp-id",
"price": 1,
"adid": "69595837",
"adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"],\"jstracker\":\"<script src=\\\"http://www.dummyurl.js\\\"></script>\"}",
"adomain": [
"example.com"
],
"iurl": "http://example.com/cr?id=69595837",
"cid": "958",
"crid": "69595837",
"cat": [
"IAB3-1"
],
"ext": {}
}
],
"seat": "958"
}
],
"bidid": "8141327771600527856",
"cur": "USD"
}
}
}
],
"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "928185755156387460",
"impid": "some-imp-id",
"price": 1,
"adm": "{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 3000,\"h\": 2250}},{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"],\"jstracker\":\"<script src=\\\"http://www.dummyurl.js\\\"></script>\"}",
"adid": "69595837",
"adomain": [
"example.com"
],
"iurl": "http://example.com/cr?id=69595837",
"cid": "958",
"crid": "69595837",
"cat": [
"IAB3-1"
],
"ext": {}
},
"type": "native"
}
]
}
]
}
Loading

0 comments on commit db71903

Please sign in to comment.