From b0a8c2c50f73f52250d16fd66fe9bac1ffedabf6 Mon Sep 17 00:00:00 2001 From: Marsel Date: Thu, 14 Nov 2019 20:51:34 +0300 Subject: [PATCH] Kubient adapter (#1094) --- adapters/kubient/kubient.go | 110 ++++++++++++++++++ adapters/kubient/kubient_test.go | 10 ++ .../kubient/kubienttest/exemplary/banner.json | 97 +++++++++++++++ .../kubient/kubienttest/exemplary/video.json | 87 ++++++++++++++ .../supplemental/bad_response.json | 57 +++++++++ .../kubienttest/supplemental/status_204.json | 56 +++++++++ .../kubienttest/supplemental/status_400.json | 59 ++++++++++ config/config.go | 1 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + static/bidder-info/kubient.yaml | 12 ++ static/bidder-params/kubient.json | 7 ++ usersync/usersyncers/syncer_test.go | 3 +- 13 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 adapters/kubient/kubient.go create mode 100644 adapters/kubient/kubient_test.go create mode 100644 adapters/kubient/kubienttest/exemplary/banner.json create mode 100644 adapters/kubient/kubienttest/exemplary/video.json create mode 100644 adapters/kubient/kubienttest/supplemental/bad_response.json create mode 100644 adapters/kubient/kubienttest/supplemental/status_204.json create mode 100644 adapters/kubient/kubienttest/supplemental/status_400.json create mode 100644 static/bidder-info/kubient.yaml create mode 100644 static/bidder-params/kubient.json diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go new file mode 100644 index 00000000000..cb1fe93ff82 --- /dev/null +++ b/adapters/kubient/kubient.go @@ -0,0 +1,110 @@ +package kubient + +import ( + "encoding/json" + "fmt" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" +) + +func NewKubientBidder(endpoint string) *KubientAdapter { + return &KubientAdapter{endpoint: endpoint} +} + +// Implements Bidder interface. +type KubientAdapter struct { + endpoint string +} + +// MakeRequests prepares the HTTP requests which should be made to fetch bids. +func (adapter *KubientAdapter) MakeRequests( + openRTBRequest *openrtb.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + openRTBRequestJSON, err := json.Marshal(openRTBRequest) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: openRTBRequestJSON, + Headers: headers, + } + requestsToBidder = append(requestsToBidder, requestToBidder) + + return requestsToBidder, errs +} + +// MakeBids makes the bids +func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var errs []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 bidResp openrtb.BidResponse + + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp) + if err != nil { + errs = append(errs, err) + } else { + b := &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + } + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { + mediaType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner == nil && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType, nil + } + } + + // This shouldnt happen. Lets handle it just incase by returning an error. + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + } +} diff --git a/adapters/kubient/kubient_test.go b/adapters/kubient/kubient_test.go new file mode 100644 index 00000000000..196d7628604 --- /dev/null +++ b/adapters/kubient/kubient_test.go @@ -0,0 +1,10 @@ +package kubient + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "kubienttest", NewKubientBidder("http://127.0.0.1:5000/bid")) +} diff --git a/adapters/kubient/kubienttest/exemplary/banner.json b/adapters/kubient/kubienttest/exemplary/banner.json new file mode 100644 index 00000000000..a32c761a7d0 --- /dev/null +++ b/adapters/kubient/kubienttest/exemplary/banner.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "test-banner-request-id", + "imp": [ + { + "id": "test-imp-banner-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://127.0.0.1:5000/bid", + "body": { + "id": "test-banner-request-id", + "imp": [ + { + "id": "test-imp-banner-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-banner-request-id", + "seatbid": [ + { + "seat": "772", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-banner-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": ["advertsite.com"], + "cid": "772", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-banner-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["advertsite.com"], + "cid": "772", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "banner" + } + ] +} diff --git a/adapters/kubient/kubienttest/exemplary/video.json b/adapters/kubient/kubienttest/exemplary/video.json new file mode 100644 index 00000000000..59d32874cec --- /dev/null +++ b/adapters/kubient/kubienttest/exemplary/video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-video-request-id", + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://127.0.0.1:5000/bid", + "body": { + "id": "test-video-request-id", + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "ssp-response-id", + "seatbid": [ + { + "seat": "83", + "bid": [{ + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-video-ad", + "adomain": ["advertsite.com"], + "cid": "958", + "crid": "29681110", + "h": 576, + "w": 1024 + }] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + + "expectedBids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-video-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": ["advertsite.com"], + "cid": "83", + "crid": "29681110", + "w": 1024, + "h": 576 + }, + "type": "video" + } + ] + +} diff --git a/adapters/kubient/kubienttest/supplemental/bad_response.json b/adapters/kubient/kubienttest/supplemental/bad_response.json new file mode 100644 index 00000000000..166743cf497 --- /dev/null +++ b/adapters/kubient/kubienttest/supplemental/bad_response.json @@ -0,0 +1,57 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://127.0.0.1:5000/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "{\"id\"data.lost" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/kubient/kubienttest/supplemental/status_204.json b/adapters/kubient/kubienttest/supplemental/status_204.json new file mode 100644 index 00000000000..58bb2629a5e --- /dev/null +++ b/adapters/kubient/kubienttest/supplemental/status_204.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://127.0.0.1:5000/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/kubient/kubienttest/supplemental/status_400.json b/adapters/kubient/kubienttest/supplemental/status_400.json new file mode 100644 index 00000000000..e895f793dc1 --- /dev/null +++ b/adapters/kubient/kubienttest/supplemental/status_400.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://127.0.0.1:5000/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/config/config.go b/config/config.go index 7fce942c125..64e16cbe63b 100644 --- a/config/config.go +++ b/config/config.go @@ -695,6 +695,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.verizonmedia.disabled", true) v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") v.SetDefault("adapters.tappx.endpoint", "https://{{.Host}}") + v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid") v.SetDefault("max_request_size", 1024*256) v.SetDefault("analytics.file.filename", "") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index a2a87fae673..e38c0edfa19 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -2,6 +2,7 @@ package exchange import ( "fmt" + "github.com/prebid/prebid-server/adapters/kubient" "net/http" "strings" @@ -104,6 +105,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderImprovedigital: improvedigital.NewImprovedigitalBidder(cfg.Adapters[string(openrtb_ext.BidderImprovedigital)].Endpoint), openrtb_ext.BidderTappx: tappx.NewTappxBidder(client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderTappx))].Endpoint), openrtb_ext.BidderVerizonMedia: verizonmedia.NewVerizonMediaBidder(client, cfg.Adapters[string(openrtb_ext.BidderVerizonMedia)].Endpoint), + openrtb_ext.BidderKubient: kubient.NewKubientBidder(cfg.Adapters[string(openrtb_ext.BidderKubient)].Endpoint), openrtb_ext.BidderFacebook: audienceNetwork.NewFacebookBidder( client, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderFacebook))].PlatformID, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1e185895380..099cdc968bb 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -42,6 +42,7 @@ const ( BidderGumGum BidderName = "gumgum" BidderImprovedigital BidderName = "improvedigital" BidderIx BidderName = "ix" + BidderKubient BidderName = "kubient" BidderLifestreet BidderName = "lifestreet" BidderLockerDome BidderName = "lockerdome" BidderMgid BidderName = "mgid" @@ -112,6 +113,7 @@ var BidderMap = map[string]BidderName{ "visx": BidderVisx, "tappx": BidderTappx, "engagebdr": BidderEngageBDR, + "kubient": BidderKubient, } // BidderList returns the values of the BidderMap diff --git a/static/bidder-info/kubient.yaml b/static/bidder-info/kubient.yaml new file mode 100644 index 00000000000..406ad0e5783 --- /dev/null +++ b/static/bidder-info/kubient.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "support@kubient.com" +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video + diff --git a/static/bidder-params/kubient.json b/static/bidder-params/kubient.json new file mode 100644 index 00000000000..a75dd734ff2 --- /dev/null +++ b/static/bidder-params/kubient.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Kubient Adapter Params", + "description": "A schema which validates params accepted by the Kubient adapter", + "type": "object", + "properties": { } +} diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index f1452d3373b..8d9cc6444fa 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -61,7 +61,8 @@ func TestNewSyncerMap(t *testing.T) { } adaptersWithoutSyncers := map[openrtb_ext.BidderName]bool{ - openrtb_ext.BidderTappx: true, + openrtb_ext.BidderTappx: true, + openrtb_ext.BidderKubient: true, } for bidder, config := range cfg.Adapters {