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

Prebid Server adapter for Telaria #1231

Merged
merged 34 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
98e1c1c
TELARIA adapter. First Pass
Feb 21, 2020
9329c4a
Merge branch 'master' of https://github.com/prebid/prebid-server
Feb 21, 2020
eced74d
Some refactoring
Feb 21, 2020
bd482af
added the json files
Feb 24, 2020
44d6bdc
fixed some tests and added the bidder info
Feb 24, 2020
854269c
fixed some tests and added the bidder info
Feb 26, 2020
e76bbde
added default user sync ur;
Feb 26, 2020
9ad2d55
Merge branch 'master' of https://github.com/prebid/prebid-server
Feb 26, 2020
e56007c
- Handling gzipped responses from our server
Feb 26, 2020
b4e3db4
- more refactoring.
Feb 26, 2020
0427507
added the proper user sync default URL
Feb 27, 2020
3d8ae59
changed the urls from dev to prod
Feb 27, 2020
ebe24ca
Merge branch 'master' of https://github.com/prebid/prebid-server
Feb 27, 2020
9320f4a
changed up the required fields. Now AdCode in the Imp.Ext isn't requi…
Mar 3, 2020
84c68f2
Merge branch 'master' of https://github.com/prebid/prebid-server
Mar 5, 2020
59e7858
change in the return type after decompressing
Mar 7, 2020
b4bfc8d
some refactoring
Mar 10, 2020
ea5bce9
Merge branch 'master' of https://github.com/prebid/prebid-server
Mar 10, 2020
ec251d7
change in our config url
Mar 18, 2020
21dcb1f
Merge branch 'master' of https://github.com/prebid/prebid-server
Mar 18, 2020
f7b3cce
using pbs.yml to switch between our production and test URLs
Mar 20, 2020
31a69b4
setting default endpoint
Mar 21, 2020
a22719d
- fixed the issue that was preventing telaria test cases to run.
Mar 25, 2020
c8cd84f
Merge branch 'master' of https://github.com/prebid/prebid-server
Mar 25, 2020
965715f
- Modifications as per the changes requested by the maintainers.
Mar 27, 2020
65e5ff8
Merge branch 'master' of https://github.com/prebid/prebid-server
Mar 27, 2020
6df25cc
Moved the seat code to imp.ext
Apr 6, 2020
8e77271
Moved the seat code to imp.ext
Apr 6, 2020
f06705d
Merge https://github.com/prebid/prebid-server
Apr 6, 2020
22b8a2d
Added 'Telaria: ' prefix for error messages
Apr 6, 2020
53a1833
- Fixes for race conditions. Was modifying the original request objec…
Apr 7, 2020
6f7cb77
cosmetic changes.
Apr 9, 2020
63eeb39
added params_test.go
Apr 9, 2020
852eda4
Merge branch 'master' of https://github.com/prebid/prebid-server
Apr 14, 2020
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
50 changes: 50 additions & 0 deletions adapters/telaria/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package telaria

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.BidderTelaria, json.RawMessage(validParam)); err != nil {
t.Errorf("Schema rejected Telaria params: %s", validParam)
}
}
}

// TestInvalidParams makes sure that the Telaria schema rejects all the imp.ext fields we don't support.
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.BidderTelaria, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
}

var validParams = []string{
`{"adCode": "string", "seatCode": "string", "originalPublisherid": "string"}`,
}

var invalidParams = []string{
``,
`null`,
`true`,
`5`,
`4.2`,
`[]`,
`{}`,
`{"adCode": "string", "originalPublisherid": "string"}`,
`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
}
330 changes: 330 additions & 0 deletions adapters/telaria/telaria.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
package telaria

import (
"bytes"
"compress/gzip"
"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"
"strconv"
)

const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid"

type TelariaAdapter struct {
URI string
}

// This will be part of Imp[i].Ext when this adapter calls out the Telaria Ad Server
type ImpressionExtOut struct {
OriginalTagID string `json:"originalTagid"`
OriginalPublisherID string `json:"originalPublisherid"`
}

// used for cookies and such
func (a *TelariaAdapter) Name() string {
return "telaria"
}

func (a *TelariaAdapter) SkipNoCookies() bool {
return false
}

// Endpoint for Telaria Ad server
func (a *TelariaAdapter) FetchEndpoint() string {
return a.URI
}

// Checker method to ensure len(request.Imp) > 0
func (a *TelariaAdapter) CheckHasImps(request *openrtb.BidRequest) error {
if len(request.Imp) == 0 {
err := &errortypes.BadInput{
Message: "Telaria: Missing Imp Object",
}
return err
}
return nil
}

// Checking if Imp[i].Video exists and Imp[i].Banner doesn't exist
func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb.BidRequest) error {
hasVideoObject := false

for _, imp := range request.Imp {
if imp.Banner != nil {
return &errortypes.BadInput{
Message: "Telaria: Banner not supported",
}
}

hasVideoObject = hasVideoObject || imp.Video != nil
}

if !hasVideoObject {
return &errortypes.BadInput{
Message: "Telaria: Only Supports Video",
}
}

return nil
}

// Fetches the populated header object
func GetHeaders(request *openrtb.BidRequest) *http.Header {
headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("X-Openrtb-Version", "2.5")
headers.Add("Accept-Encoding", "gzip")

if request.Device != nil {
if len(request.Device.UA) > 0 {
headers.Add("User-Agent", request.Device.UA)
}

if len(request.Device.IP) > 0 {
headers.Add("X-Forwarded-For", request.Device.IP)
}

if len(request.Device.Language) > 0 {
headers.Add("Accept-Language", request.Device.Language)
}

if request.Device.DNT != nil {
headers.Add("Dnt", strconv.Itoa(int(*request.Device.DNT)))
}
}

return &headers
}

// Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format
func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpTelaria, error) {
var bidderExt adapters.ExtImpBidder
err := json.Unmarshal(imp.Ext, &bidderExt)

if err != nil {
err = &errortypes.BadInput{
Message: "Telaria: ext.bidder not provided",
}

return nil, err
}

var telariaExt openrtb_ext.ExtImpTelaria
err = json.Unmarshal(bidderExt.Bidder, &telariaExt)

if err != nil {
return nil, err
}

if telariaExt.SeatCode == "" {
return nil, &errortypes.BadInput{Message: "Telaria: Seat Code required"}
}

return &telariaExt, nil
}

// Method to fetch the original publisher ID. Note that this method must be called
// before we replace publisher.ID with seatCode
func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb.BidRequest) string {

if request.Site != nil && request.Site.Publisher != nil {
return request.Site.Publisher.ID
} else if request.App != nil && request.App.Publisher != nil {
return request.App.Publisher.ID
}

return ""
}

// Method to do a deep copy of the publisher object. It also adds the seatCode as publisher.ID
func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb.Publisher) *openrtb.Publisher {
var pub = &openrtb.Publisher{ID: seatCode}

if publisher != nil {
pub.Domain = publisher.Domain
pub.Name = publisher.Name
pub.Cat = publisher.Cat
pub.Ext = publisher.Ext
}

return pub
}

// This method changes <site/app>.publisher.id to the seatCode
func (a *TelariaAdapter) PopulatePublisherId(request *openrtb.BidRequest, seatCode string) (*openrtb.Site, *openrtb.App) {
if request.Site != nil {
siteCopy := *request.Site
siteCopy.Publisher = a.MakePublisherObject(seatCode, request.Site.Publisher)
return &siteCopy, nil
} else if request.App != nil {
appCopy := *request.App
appCopy.Publisher = a.MakePublisherObject(seatCode, request.App.Publisher)
return nil, &appCopy
}
return nil, nil
}

func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {

// make a copy of the incoming request
request := *requestIn

// ensure that the request has Impressions
if noImps := a.CheckHasImps(&request); noImps != nil {
return nil, []error{noImps}
}

// ensure that the request has a Video object
if noVideoObjectError := a.CheckHasVideoObject(&request); noVideoObjectError != nil {
return nil, []error{noVideoObjectError}
}

var seatCode string
originalPublisherID := a.FetchOriginalPublisherID(&request)

var errors []error
for i, imp := range request.Imp {
// fetch adCode & seatCode from Imp[i].Ext
telariaExt, err := a.FetchTelariaExtImpParams(&imp)
if err != nil {
errors = append(errors, err)
break
}

seatCode = telariaExt.SeatCode

// move the original tagId and the original publisher.id into the Imp[i].Ext object
request.Imp[i].Ext, err = json.Marshal(&ImpressionExtOut{request.Imp[i].TagID, originalPublisherID})
if err != nil {
errors = append(errors, err)
break
}
guscarreon marked this conversation as resolved.
Show resolved Hide resolved

// Swap the tagID with adCode
request.Imp[i].TagID = telariaExt.AdCode
}

if len(errors) > 0 {
return nil, errors
}

// Add seatCode to <Site/App>.Publisher.ID
siteObject, appObject := a.PopulatePublisherId(&request, seatCode)

request.Site = siteObject
request.App = appObject

reqJSON, err := json.Marshal(request)
if err != nil {
return nil, []error{err}
}

return []*adapters.RequestData{{
Method: "POST",
Uri: a.FetchEndpoint(),
Body: reqJSON,
Headers: *GetHeaders(&request),
}}, nil
}

// response isn't automatically decompressed. This method unzips the response if Content-Encoding is gzip
func GetResponseBody(response *adapters.ResponseData) ([]byte, error) {

if "gzip" == response.Headers.Get("Content-Encoding") {
body := bytes.NewBuffer(response.Body)
r, readerErr := gzip.NewReader(body)
if readerErr != nil {
return nil, &errortypes.BadServerResponse{
Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode),
}
}
var resB bytes.Buffer
var err error
_, err = resB.ReadFrom(r)
if err != nil {
return nil, &errortypes.BadServerResponse{
Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode),
}
}

response.Headers.Del("Content-Encoding")

return resB.Bytes(), nil
} else {
return response.Body, nil
}
}

func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error {
if response.StatusCode == http.StatusNoContent {
return &errortypes.BadInput{Message: "Telaria: Invalid Bid Request received by the server"}
}

if response.StatusCode == http.StatusBadRequest {
return &errortypes.BadInput{
Message: fmt.Sprintf("Telaria: Unexpected status code: [ %d ] ", response.StatusCode),
}
}

if response.StatusCode == http.StatusServiceUnavailable {
return &errortypes.BadInput{
Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode),
}
}

if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
return &errortypes.BadInput{
Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode),
}
}

return nil
}

func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {

httpStatusError := a.CheckResponseStatusCodes(response)
if httpStatusError != nil {
return nil, []error{httpStatusError}
}

responseBody, err := GetResponseBody(response)

if err != nil {
return nil, []error{err}
}

var bidResp openrtb.BidResponse
if err := json.Unmarshal(responseBody, &bidResp); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: "Telaria: Bad Server Response",
}}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
sb := bidResp.SeatBid[0]

for _, bid := range sb.Bid {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: openrtb_ext.BidTypeVideo,
})
}
return bidResponse, nil
}

func NewTelariaBidder(endpoint string) *TelariaAdapter {
if endpoint == "" {
endpoint = Endpoint
}

return &TelariaAdapter{
URI: endpoint,
}
}
Loading