github.com/prebid/prebid-server@v0.275.0/adapters/openx/openx.go (about)

     1  package openx
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/prebid/openrtb/v19/openrtb2"
     9  	"github.com/prebid/prebid-server/adapters"
    10  	"github.com/prebid/prebid-server/config"
    11  	"github.com/prebid/prebid-server/errortypes"
    12  	"github.com/prebid/prebid-server/openrtb_ext"
    13  )
    14  
    15  const hbconfig = "hb_pbs_1.0.0"
    16  
    17  type OpenxAdapter struct {
    18  	bidderName string
    19  	endpoint   string
    20  }
    21  
    22  type openxImpExt map[string]json.RawMessage
    23  
    24  type openxReqExt struct {
    25  	DelDomain    string `json:"delDomain,omitempty"`
    26  	Platform     string `json:"platform,omitempty"`
    27  	BidderConfig string `json:"bc"`
    28  }
    29  
    30  type openxRespExt struct {
    31  	FledgeAuctionConfigs map[string]json.RawMessage `json:"fledge_auction_configs,omitempty"`
    32  }
    33  
    34  func (a *OpenxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    35  	var errs []error
    36  	var bannerImps []openrtb2.Imp
    37  	var videoImps []openrtb2.Imp
    38  
    39  	for _, imp := range request.Imp {
    40  		// OpenX doesn't allow multi-type imp. Banner takes priority over video.
    41  		if imp.Banner != nil {
    42  			bannerImps = append(bannerImps, imp)
    43  		} else if imp.Video != nil {
    44  			videoImps = append(videoImps, imp)
    45  		}
    46  	}
    47  
    48  	var adapterRequests []*adapters.RequestData
    49  	// Make a copy as we don't want to change the original request
    50  	reqCopy := *request
    51  
    52  	reqCopy.Imp = bannerImps
    53  	adapterReq, errors := a.makeRequest(&reqCopy)
    54  	if adapterReq != nil {
    55  		adapterRequests = append(adapterRequests, adapterReq)
    56  	}
    57  	errs = append(errs, errors...)
    58  
    59  	// OpenX only supports single imp video request
    60  	for _, videoImp := range videoImps {
    61  		reqCopy.Imp = []openrtb2.Imp{videoImp}
    62  		adapterReq, errors := a.makeRequest(&reqCopy)
    63  		if adapterReq != nil {
    64  			adapterRequests = append(adapterRequests, adapterReq)
    65  		}
    66  		errs = append(errs, errors...)
    67  	}
    68  
    69  	return adapterRequests, errs
    70  }
    71  
    72  func (a *OpenxAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) {
    73  	var errs []error
    74  	var validImps []openrtb2.Imp
    75  	reqExt := openxReqExt{BidderConfig: hbconfig}
    76  
    77  	for _, imp := range request.Imp {
    78  		if err := preprocess(&imp, &reqExt); err != nil {
    79  			errs = append(errs, err)
    80  			continue
    81  		}
    82  		validImps = append(validImps, imp)
    83  	}
    84  
    85  	// If all the imps were malformed, don't bother making a server call with no impressions.
    86  	if len(validImps) == 0 {
    87  		return nil, errs
    88  	}
    89  
    90  	request.Imp = validImps
    91  
    92  	var err error
    93  	request.Ext, err = json.Marshal(reqExt)
    94  	if err != nil {
    95  		errs = append(errs, err)
    96  		return nil, errs
    97  	}
    98  
    99  	reqJSON, err := json.Marshal(request)
   100  	if err != nil {
   101  		errs = append(errs, err)
   102  		return nil, errs
   103  	}
   104  
   105  	headers := http.Header{}
   106  	headers.Add("Content-Type", "application/json;charset=utf-8")
   107  	headers.Add("Accept", "application/json")
   108  	return &adapters.RequestData{
   109  		Method:  "POST",
   110  		Uri:     a.endpoint,
   111  		Body:    reqJSON,
   112  		Headers: headers,
   113  	}, errs
   114  }
   115  
   116  // Mutate the imp to get it ready to send to openx.
   117  func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error {
   118  	var bidderExt adapters.ExtImpBidder
   119  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   120  		return &errortypes.BadInput{
   121  			Message: err.Error(),
   122  		}
   123  	}
   124  
   125  	var openxExt openrtb_ext.ExtImpOpenx
   126  	if err := json.Unmarshal(bidderExt.Bidder, &openxExt); err != nil {
   127  		return &errortypes.BadInput{
   128  			Message: err.Error(),
   129  		}
   130  	}
   131  
   132  	reqExt.DelDomain = openxExt.DelDomain
   133  	reqExt.Platform = openxExt.Platform
   134  
   135  	imp.TagID = openxExt.Unit
   136  	if imp.BidFloor == 0 && openxExt.CustomFloor > 0 {
   137  		imp.BidFloor = openxExt.CustomFloor
   138  	}
   139  
   140  	// outgoing imp.ext should be same as incoming imp.ext minus prebid and bidder
   141  	impExt := openxImpExt{}
   142  	if err := json.Unmarshal(imp.Ext, &impExt); err != nil {
   143  		return &errortypes.BadInput{
   144  			Message: err.Error(),
   145  		}
   146  	}
   147  	delete(impExt, openrtb_ext.PrebidExtKey)
   148  	delete(impExt, openrtb_ext.PrebidExtBidderKey)
   149  
   150  	if openxExt.CustomParams != nil {
   151  		var err error
   152  		if impExt["customParams"], err = json.Marshal(openxExt.CustomParams); err != nil {
   153  			return &errortypes.BadInput{
   154  				Message: err.Error(),
   155  			}
   156  		}
   157  	}
   158  
   159  	if len(impExt) > 0 {
   160  		var err error
   161  		if imp.Ext, err = json.Marshal(impExt); err != nil {
   162  			return &errortypes.BadInput{
   163  				Message: err.Error(),
   164  			}
   165  		}
   166  	} else {
   167  		imp.Ext = nil
   168  	}
   169  
   170  	if imp.Video != nil {
   171  		videoCopy := *imp.Video
   172  		if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 {
   173  			videoCopy.Ext = json.RawMessage(`{"rewarded":1}`)
   174  		} else {
   175  			videoCopy.Ext = nil
   176  		}
   177  		imp.Video = &videoCopy
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  func (a *OpenxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   184  	if response.StatusCode == http.StatusNoContent {
   185  		return nil, nil
   186  	}
   187  
   188  	if response.StatusCode == http.StatusBadRequest {
   189  		return nil, []error{&errortypes.BadInput{
   190  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   191  		}}
   192  	}
   193  
   194  	if response.StatusCode != http.StatusOK {
   195  		return nil, []error{&errortypes.BadServerResponse{
   196  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   197  		}}
   198  	}
   199  
   200  	var bidResp openrtb2.BidResponse
   201  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   202  		return nil, []error{err}
   203  	}
   204  
   205  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
   206  
   207  	// overrride default currency
   208  	if bidResp.Cur != "" {
   209  		bidResponse.Currency = bidResp.Cur
   210  	}
   211  
   212  	if bidResp.Ext != nil {
   213  		var bidRespExt openxRespExt
   214  		if err := json.Unmarshal(bidResp.Ext, &bidRespExt); err == nil && bidRespExt.FledgeAuctionConfigs != nil {
   215  			bidResponse.FledgeAuctionConfigs = make([]*openrtb_ext.FledgeAuctionConfig, 0, len(bidRespExt.FledgeAuctionConfigs))
   216  			for impId, config := range bidRespExt.FledgeAuctionConfigs {
   217  				fledgeAuctionConfig := &openrtb_ext.FledgeAuctionConfig{
   218  					ImpId:  impId,
   219  					Bidder: a.bidderName,
   220  					Config: config,
   221  				}
   222  				bidResponse.FledgeAuctionConfigs = append(bidResponse.FledgeAuctionConfigs, fledgeAuctionConfig)
   223  			}
   224  		}
   225  	}
   226  
   227  	for _, sb := range bidResp.SeatBid {
   228  		for i := range sb.Bid {
   229  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   230  				Bid:     &sb.Bid[i],
   231  				BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp),
   232  			})
   233  		}
   234  	}
   235  	return bidResponse, nil
   236  }
   237  
   238  // getMediaTypeForImp figures out which media type this bid is for.
   239  //
   240  // OpenX doesn't support multi-type impressions.
   241  // If both banner and video exist, take banner as we do not want in-banner video.
   242  func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
   243  	mediaType := openrtb_ext.BidTypeBanner
   244  	for _, imp := range imps {
   245  		if imp.ID == impId {
   246  			if imp.Banner == nil && imp.Video != nil {
   247  				mediaType = openrtb_ext.BidTypeVideo
   248  			}
   249  			return mediaType
   250  		}
   251  	}
   252  	return mediaType
   253  }
   254  
   255  // Builder builds a new instance of the Openx adapter for the given bidder with the given config.
   256  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   257  	bidder := &OpenxAdapter{
   258  		endpoint:   config.Endpoint,
   259  		bidderName: string(bidderName),
   260  	}
   261  	return bidder, nil
   262  }