Skip to content
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

Adding GumGum server adapter #748

Merged
merged 4 commits into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions adapters/gumgum/gumgum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package gumgum

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/openrtb_ext"
"net/http"
)

type GumGumAdapter struct {
URI string
}

func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest) ([]*adapters.RequestData, []error) {
var validImps []openrtb.Imp
var trackingId string
var deviceHeight uint64
var deviceWidth uint64

numRequests := len(request.Imp)
errs := make([]error, 0, numRequests)

for i := 0; i < numRequests; i++ {
imp := request.Imp[i]
zone, devH, devW, err := preprocess(&imp)
if err != nil {
errs = append(errs, err)
} else {
if request.Imp[i].Banner != nil {
bannerCopy := *request.Imp[i].Banner
if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 {
format := bannerCopy.Format[0]
bannerCopy.W = &(format.W)
bannerCopy.H = &(format.H)
}
request.Imp[i].Banner = &bannerCopy
validImps = append(validImps, request.Imp[i])
trackingId = zone
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to do some checks if people are passing inconsistent values across the imps. Shouldn't happen in theory, but users can end up doing all sorts of crazy things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for the user to send different values (unless they decide to randomly generate this value in which case none of these values would be valid on GumGum's end)? Since whatever is in the imp.ext object is hard coded when the user sets up the client for the site right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one imp.ext for each imp, so technically it could be possible to send inconsistent values. It would be a strong indication that there is something seriously wrong with their prebid implementation to generate a mess like that. I am not going to deny this PR if there is no check, just wanted to offer it as an idea to consider.

deviceHeight = devH
deviceWidth = devW
}
}
}

if len(validImps) == 0 {
return nil, errs
}

request.Imp = validImps
request.Device = &openrtb.Device{
H: deviceHeight,
W: deviceWidth,
}

if request.Site != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice in your static/bidder-info that you also support apps... did you want to do something with the tracking ID for those here too? Maybe in req.App?

It seems a bit weird that the zone info doesn't make it anywhere into the request that goes to your servers on App requests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry that's an oversight on my end. GumGum will only support site for now so I'll correct our bidder-info file. Thanks for catching that! If I modify that file will you have to reapprove?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, but it's fine. We're active :)

siteCopy := *request.Site
siteCopy.ID = trackingId
request.Site = &siteCopy
}

reqJSON, err := json.Marshal(request)
if err != nil {
errs = append(errs, err)
return nil, errs
}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
return []*adapters.RequestData{{
Method: "POST",
Uri: g.URI,
Body: reqJSON,
Headers: headers,
}}, errs
}

func (g *GumGumAdapter) 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("Bad user input: HTTP status %d", response.StatusCode),
}}
}

if response.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode),
}}
}
var bidResp openrtb.BidResponse
if err := json.Unmarshal(response.Body, &bidResp); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Bad server response: %d. ", err),
}}
}

var errs []error
bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)

for _, sb := range bidResp.SeatBid {
for i := range sb.Bid {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &sb.Bid[i],
BidType: openrtb_ext.BidTypeBanner,
})
}
}
return bidResponse, errs
}

func preprocess(imp *openrtb.Imp) (string, uint64, uint64, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
err = &errortypes.BadInput{
Message: err.Error(),
}
return "", 0, 0, err
}

var gumgumExt openrtb_ext.ExtImpGumGum
if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil {
err = &errortypes.BadInput{
Message: err.Error(),
}
return "", 0, 0, err
}

zone := gumgumExt.Zone
deviceHeight := gumgumExt.DeviceHeight
deviceWidth := gumgumExt.DeviceWidth
return zone, deviceHeight, deviceWidth, nil
}

func NewGumGumBidder(endpoint string) *GumGumAdapter {
return &GumGumAdapter{
URI: endpoint,
}
}
10 changes: 10 additions & 0 deletions adapters/gumgum/gumgum_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gumgum

import (
"github.com/prebid/prebid-server/adapters/adapterstest"
"testing"
)

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "gumgumtest", NewGumGumBidder("https://g2.gumgum.com/providers/prbds2s/bid"))
}
111 changes: 111 additions & 0 deletions adapters/gumgum/gumgumtest/exemplary/banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"mockBidRequest": {
"device": {
"h": 300,
"w": 400
},
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 300,
"h": 250
},
{
"w": 300,
"h": 300
}
],
"w": 300,
"h": 250
},
"ext": {
"bidder": {
"zone": "dc9d6be1",
"deviceHeight": 300,
"deviceWidth": 400
}
}
}
]
},

"httpCalls": [
{
"expectedRequest": {
"uri": "https://g2.gumgum.com/providers/prbds2s/bid",
"body":{
"device": {
"h": 300,
"w": 400
},
"id": "test-request-id",
"imp": [{
"id": "test-imp-id",
"banner": {
"format": [{
"w": 300,
"h": 250
}, {
"w": 300,
"h": 300
}],
"w": 300,
"h": 250
},
"ext": {
"bidder": {
"zone": "dc9d6be1",
"deviceHeight": 300,
"deviceWidth": 400
}
}
}]
}
},
"mockResponse": {
"status": 200,
"body": {
"seatbid": [
{
"bid": [
{
"crid": "2068416",
"adm": "some-test-ad",
"adid": "2068416",
"price": 5,
"id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05",
"impid": "test-imp-id",
"cid": "4747"
}
]
}
]
}
}
}
],

"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"crid": "2068416",
"adm": "some-test-ad",
"adid": "2068416",
"price": 5,
"id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05",
"impid": "test-imp-id",
"cid": "4747"
},
"type": "banner"
}
]
}
]
}
5 changes: 5 additions & 0 deletions adapters/gumgum/gumgumtest/params/race/banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"zone": "dc9d6be1",
"deviceHeight": 240,
"deviceWidth": 400
}
54 changes: 54 additions & 0 deletions adapters/gumgum/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package gumgum

import (
"encoding/json"
"github.com/prebid/prebid-server/openrtb_ext"
"testing"
)

func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, validParam := range validParams {
if err := validator.Validate(openrtb_ext.BidderGumGum, json.RawMessage(validParam)); err != nil {
t.Errorf("Schema rejected gumgum params: %s", validParam)
}
}
}

func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, invalidParam := range invalidParams {
if err := validator.Validate(openrtb_ext.BidderGumGum, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
}

var validParams = []string{
`{"zone":"dc9d6be1", "deviceHeight":240, "deviceWidth":400}`,
}

var invalidParams = []string{
`null`,
`nil`,
``,
`{}`,
`[]`,
`true`,
`2`,
`{"zone":"12345678", "deviceHeight":100}`,
`{"zone":12345678}`,
`{"zone":""}`,
`{"placementId": 1}`,
`{"zone": true}`,
`{"placementId": 1, "zone":"1234567"}`,
`{"zone":"12345678", "deviceHeight":"200", "deviceWidth":"400"}`,
}
18 changes: 18 additions & 0 deletions adapters/gumgum/usersync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gumgum

import (
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/prebid/prebid-server/usersync"

"net/url"
"strings"
)

func NewGumGumSyncer(cfg *config.Configuration) usersync.Usersyncer {
externalURL := strings.TrimRight(cfg.ExternalURL, "/")
redirectURI := url.QueryEscape(externalURL) + "%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{gdpr}}%26gdpr_consent%3D{{gdpr_consent}}%26uid%3D"
usersyncURL := cfg.Adapters[string(openrtb_ext.BidderGumGum)].UserSyncURL
return adapters.NewSyncer("gumgum", 61, adapters.ResolveMacros(usersyncURL+redirectURI), adapters.SyncTypeIframe)
}
22 changes: 22 additions & 0 deletions adapters/gumgum/usersync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gumgum

import (
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"

"testing"
)

func TestGumGumSyncer(t *testing.T) {
syncer := NewGumGumSyncer(&config.Configuration{ExternalURL: "localhost", Adapters: map[string]config.Adapter{
string(openrtb_ext.BidderGumGum): {
UserSyncURL: "https://rtb.gumgum.com/usync/prbds2s?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&r=",
},
}})
u := syncer.GetUsersyncInfo("1", "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA")
assert.Equal(t, "https://rtb.gumgum.com/usync/prbds2s?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&r=localhost%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D", u.URL)
assert.Equal(t, "iframe", u.Type)
assert.Equal(t, uint16(61), syncer.GDPRVendorID())
assert.Equal(t, false, u.SupportCORS)
}
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb")
v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp")
v.SetDefault("adapters.rhythmone.usersync_url", "//sync.1rx.io/usersync2/rmphb?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&redir=")
v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid")
v.SetDefault("adapters.gumgum.usersync_url", "https://rtb.gumgum.com/usync/prbds2s?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&r=")

v.SetDefault("max_request_size", 1024*256)
v.SetDefault("analytics.file.filename", "")
Expand Down
2 changes: 2 additions & 0 deletions exchange/adapter_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/prebid/prebid-server/adapters/brightroll"
"github.com/prebid/prebid-server/adapters/conversant"
"github.com/prebid/prebid-server/adapters/eplanning"
"github.com/prebid/prebid-server/adapters/gumgum"
"github.com/prebid/prebid-server/adapters/ix"
"github.com/prebid/prebid-server/adapters/lifestreet"
"github.com/prebid/prebid-server/adapters/openx"
Expand All @@ -41,6 +42,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(),
openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint),
openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint),
openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint),
openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint),
openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint),
openrtb_ext.BidderRhythmone: rhythmone.NewRhythmoneBidder(cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint),
Expand Down
Loading