Skip to content

Commit

Permalink
Major refactor of PBS
Browse files Browse the repository at this point in the history
Logic in prebid-server libraries shouldn't care about adapter logic, so
adapters have been isolated and a registry to add them has been created.

Usersyncs have also been moved to their adapter package.

Router logic has been moved into it's own package and legacy
auction to it's own file.
  • Loading branch information
zachbadgett committed Sep 10, 2018
1 parent e8b6513 commit 57a580c
Show file tree
Hide file tree
Showing 98 changed files with 1,815 additions and 3,653 deletions.
136 changes: 48 additions & 88 deletions exchange/bidder.go → adapters/adapter.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package exchange
package adapters

import (
"bytes"
Expand All @@ -8,153 +8,113 @@ import (
"net/http"

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

"golang.org/x/net/context/ctxhttp"
)

// adaptedBidder defines the contract needed to participate in an Auction within an Exchange.
//
// This interface exists to help segregate core auction logic.
//
// Any logic which can be done _within a single Seat_ goes inside one of these.
// Any logic which _requires responses from all Seats_ goes inside the Exchange.
// Bid is a bid returned by an adaptedBidder.
//
// This interface differs from adapters.Bidder to help minimize code duplication across the
// adapters.Bidder implementations.
type adaptedBidder interface {
// requestBid fetches bids for the given request.
//
// An adaptedBidder *may* return two non-nil values here. Errors should describe situations which
// make the bid (or no-bid) "less than ideal." Common examples include:
//
// 1. Connection issues.
// 2. Imps with Media Types which this Bidder doesn't support.
// 3. The Context timeout expired before all expected bids were returned.
// 4. The Server sent back an unexpected Response, so some bids were ignored.
//
// Any errors will be user-facing in the API.
// Error messages should help publishers understand what might account for "bad" bids.
requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64) (*pbsOrtbSeatBid, []error)
// Bid.Bid.Ext will become "response.seatbid[i].Bid.Ext.bidder" in the final OpenRTB response.
// Bid.BidType will become "response.seatbid[i].Bid.Ext.prebid.type" in the final OpenRTB response.
// Bid.BidTargets does not need to be filled out by the Bidder. It will be set later by the exchange.d
type Bid struct {
Bid *openrtb.Bid
BidType openrtb_ext.BidType
BidTargets map[string]string
}

// pbsOrtbBid is a Bid returned by an adaptedBidder.
// SeatBid is a bid returned by an adaptedBidder.
//
// pbsOrtbBid.bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response.
// pbsOrtbBid.bidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response.
// pbsOrtbBid.bidTargets does not need to be filled out by the Bidder. It will be set later by the exchange.
type pbsOrtbBid struct {
bid *openrtb.Bid
bidType openrtb_ext.BidType
bidTargets map[string]string
// This is distinct from the openrtb.SeatBid so that the prebid-server Ext can be passed back with typesafety.
type SeatBid struct {
// Bids is the list of bids which this adaptedBidder wishes to make.
Bids []*Bid
// Currency is the currency in which the Bids are made.
// Should be a valid currency ISO code.
Currency string
// HttpCalls is the list of debugging info. It should only be populated if the request.test == 1.
// This will become response.Ext.debug.httpcalls.{bidder} on the final Response.
HttpCalls []*openrtb_ext.ExtHttpCall
// Ext contains the extension for this seatbid.
// if len(Bids) > 0, this will become response.seatbid[i].Ext.{bidder} on the final OpenRTB response.
// if len(Bids) == 0, this will be ignored because the OpenRTB spec doesn't allow a SeatBid with 0 Bids.
Ext openrtb.RawJSON
}

// pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder.
//
// This is distinct from the openrtb.SeatBid so that the prebid-server ext can be passed back with typesafety.
type pbsOrtbSeatBid struct {
// bids is the list of bids which this adaptedBidder wishes to make.
bids []*pbsOrtbBid
// currency is the currency in which the bids are made.
// Should be a valid curreny ISO code.
currency string
// httpCalls is the list of debugging info. It should only be populated if the request.test == 1.
// This will become response.ext.debug.httpcalls.{bidder} on the final Response.
httpCalls []*openrtb_ext.ExtHttpCall
// ext contains the extension for this seatbid.
// if len(bids) > 0, this will become response.seatbid[i].ext.{bidder} on the final OpenRTB response.
// if len(bids) == 0, this will be ignored because the OpenRTB spec doesn't allow a SeatBid with 0 Bids.
ext openrtb.RawJSON
type BidRequester struct {
Bidder
Client *http.Client
}

// adaptBidder converts an adapters.Bidder into an exchange.adaptedBidder.
//
// The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
// (which is being phased out and replaced by Bidder for OpenRTB auctions)
func adaptBidder(bidder adapters.Bidder, client *http.Client) adaptedBidder {
return &bidderAdapter{
func NewBidRequester(bidder Bidder, client *http.Client) *BidRequester {
return &BidRequester{
Bidder: bidder,
Client: client,
}
}

type bidderAdapter struct {
Bidder adapters.Bidder
Client *http.Client
}

func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64) (*pbsOrtbSeatBid, []error) {
func (bidder *BidRequester) RequestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64) (*SeatBid, []error) {
reqData, errs := bidder.Bidder.MakeRequests(request)

if len(reqData) == 0 {
// If the adapter failed to generate both requests and errors, this is an error.
if len(errs) == 0 {
errs = append(errs, &errortypes.FailedToRequestBids{Message: "The adapter failed to generate any bid requests, but also failed to generate an error explaining why"})
}
return nil, errs
}

// Make any HTTP requests in parallel.
// If the bidder only needs to make one, save some cycles by just using the current one.
responseChannel := make(chan *httpCallInfo, len(reqData))
if len(reqData) == 1 {
responseChannel <- bidder.doRequest(ctx, reqData[0])
} else {
for _, oneReqData := range reqData {
go func(data *adapters.RequestData) {
go func(data *RequestData) {
responseChannel <- bidder.doRequest(ctx, data)
}(oneReqData) // Method arg avoids a race condition on oneReqData
}
}

seatBid := &pbsOrtbSeatBid{
bids: make([]*pbsOrtbBid, 0, len(reqData)),
currency: "USD",
httpCalls: make([]*openrtb_ext.ExtHttpCall, 0, len(reqData)),
seatBid := &SeatBid{
Bids: make([]*Bid, 0, len(reqData)),
Currency: "USD",
HttpCalls: make([]*openrtb_ext.ExtHttpCall, 0, len(reqData)),
}

firstHTTPCallCurrency := ""

// If the bidder made multiple requests, we still want them to enter as many bids as possible...
// even if the timeout occurs sometime halfway through.
for i := 0; i < len(reqData); i++ {
httpInfo := <-responseChannel
// If this is a test bid, capture debugging info from the requests.
if request.Test == 1 {
seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
seatBid.HttpCalls = append(seatBid.HttpCalls, makeExt(httpInfo))
}

if httpInfo.err == nil {

bidResponse, moreErrs := bidder.Bidder.MakeBids(request, httpInfo.request, httpInfo.response)
errs = append(errs, moreErrs...)

if bidResponse != nil {

if bidResponse.Currency == "" {
bidResponse.Currency = "USD"
}

// Related to #281 - currency support
// Prebid can't make sure that each HTTP call returns bids with the same currency as the others.
// If a Bidder makes two HTTP calls, and their servers respond with different currencies,
// Related to #281 - Currency support
// Prebid can't make sure that each HTTP call returns Bids with the same currency as the others.
// If a bidder makes two HTTP calls, and their servers respond with different currencies,
// we will consider the first call currency as standard currency and then reject others which contradict it.
if firstHTTPCallCurrency == "" { // First HTTP call
firstHTTPCallCurrency = bidResponse.Currency
}

// TODO: #281 - Once currencies rate conversion is out, this shouldn't be an issue anymore, we will only
// need to convert the bid price based on the currency.
if firstHTTPCallCurrency == bidResponse.Currency {
for i := 0; i < len(bidResponse.Bids); i++ {
if bidResponse.Bids[i].Bid != nil {
// TODO #280: Convert the bid price
// TODO #280: Convert the Bid price
bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * bidAdjustment
}
seatBid.bids = append(seatBid.bids, &pbsOrtbBid{
bid: bidResponse.Bids[i].Bid,
bidType: bidResponse.Bids[i].BidType,
seatBid.Bids = append(seatBid.Bids, &Bid{
Bid: bidResponse.Bids[i].Bid,
BidType: bidResponse.Bids[i].BidType,
})
}
} else {
Expand Down Expand Up @@ -194,7 +154,7 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {

// doRequest makes a request, handles the response, and returns the data needed by the
// Bidder interface.
func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData) *httpCallInfo {
func (bidder *BidRequester) doRequest(ctx context.Context, req *RequestData) *httpCallInfo {
httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(req.Body))
if err != nil {
return &httpCallInfo{
Expand Down Expand Up @@ -232,7 +192,7 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques

return &httpCallInfo{
request: req,
response: &adapters.ResponseData{
response: &ResponseData{
StatusCode: httpResp.StatusCode,
Body: respBody,
Headers: httpResp.Header,
Expand All @@ -242,7 +202,7 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
}

type httpCallInfo struct {
request *adapters.RequestData
response *adapters.ResponseData
request *RequestData
response *ResponseData
err error
}
63 changes: 41 additions & 22 deletions adapters/adform/adform.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,61 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strings"

"net/url"
"strconv"
"strings"

"github.com/buger/jsonparser"
"github.com/golang/glog"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/prebid/prebid-server/pbs"

"github.com/buger/jsonparser"
"github.com/golang/glog"
"github.com/mxmCherry/openrtb"
"golang.org/x/net/context/ctxhttp"
)

const BidderAdform openrtb_ext.BidderName = "adform"

func init() {
adapters.Register(BidderAdform,
adapters.WithBidder(NewAdformBidder),
adapters.WithAdapter("adform", NewAdformAdapter),
adapters.WithUsersync(NewAdformSyncer),
)
}

type AdformAdapter struct {
http *adapters.HTTPAdapter
URL *url.URL
version string
}

func newAdformBidder(client *http.Client, endpointURL string) *AdformAdapter {
a := &adapters.HTTPAdapter{Client: client}
var uriObj *url.URL
uriObj, err := url.Parse(endpointURL)
if err != nil {
panic(fmt.Sprintf("Incorrect Adform request url %s, check the configuration, please.", endpointURL))
}
b := &AdformAdapter{
http: a,
URL: uriObj,
version: "0.1.2",
}
return b
}

func NewAdformBidder(cfg *config.Configuration, info adapters.BidderInfo) adapters.Bidder {
return adapters.EnforceBidderInfo(newAdformBidder(cfg.HttpClient, cfg.Adapters[string(BidderAdform)].Endpoint), info)
}

func NewAdformAdapter(cfg *config.Configuration) adapters.Adapter {
return newAdformBidder(adapters.NewHTTPAdapter(adapters.DefaultHTTPAdapterConfig).Client, cfg.Adapters[string(BidderAdform)].Endpoint)
}

type adformRequest struct {
tid string
userAgent string
Expand Down Expand Up @@ -85,8 +119,8 @@ func isPriceTypeValid(priceType string) (string, bool) {

// ADAPTER Interface

func NewAdformAdapter(config *adapters.HTTPAdapterConfig, endpointURL string) *AdformAdapter {
return NewAdformBidder(adapters.NewHTTPAdapter(config).Client, endpointURL)
func (a *AdformAdapter) BidderName() openrtb_ext.BidderName {
return BidderAdform
}

// used for cookies and such
Expand Down Expand Up @@ -354,21 +388,6 @@ func parseAdformBids(response []byte) ([]*adformBid, error) {

// BIDDER Interface

func NewAdformBidder(client *http.Client, endpointURL string) *AdformAdapter {
a := &adapters.HTTPAdapter{Client: client}
var uriObj *url.URL
uriObj, err := url.Parse(endpointURL)
if err != nil {
panic(fmt.Sprintf("Incorrect Adform request url %s, check the configuration, please.", endpointURL))
}

return &AdformAdapter{
http: a,
URL: uriObj,
version: "0.1.2",
}
}

func (a *AdformAdapter) MakeRequests(request *openrtb.BidRequest) ([]*adapters.RequestData, []error) {
adformRequest, errors := openRtbToAdformRequest(request)
if len(adformRequest.adUnits) == 0 {
Expand Down
15 changes: 15 additions & 0 deletions adapters/adform/usersync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package adform

import (
"net/url"

"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/usersync"
)

func NewAdformSyncer(cfg *config.Configuration) usersync.Usersyncer {
usersyncURL := cfg.Adapters[string(BidderAdform)].UserSyncURL
redirectURI := url.QueryEscape(cfg.ExternalURL) + "%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D{{gdpr}}%26gdpr_consent%3D{{gdpr_consent}}%26uid%3D%24UID"
return adapters.NewSyncer("adform", 50, adapters.ResolveMacros(usersyncURL+redirectURI), adapters.SyncTypeRedirect)
}
Loading

0 comments on commit 57a580c

Please sign in to comment.