github.com/prebid/prebid-server/v2@v2.18.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/v20/openrtb2"
     9  	"github.com/prebid/prebid-server/v2/adapters"
    10  	"github.com/prebid/prebid-server/v2/config"
    11  	"github.com/prebid/prebid-server/v2/errortypes"
    12  	"github.com/prebid/prebid-server/v2/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  		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
   114  	}, errs
   115  }
   116  
   117  // Mutate the imp to get it ready to send to openx.
   118  func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error {
   119  	var bidderExt adapters.ExtImpBidder
   120  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   121  		return &errortypes.BadInput{
   122  			Message: err.Error(),
   123  		}
   124  	}
   125  
   126  	var openxExt openrtb_ext.ExtImpOpenx
   127  	if err := json.Unmarshal(bidderExt.Bidder, &openxExt); err != nil {
   128  		return &errortypes.BadInput{
   129  			Message: err.Error(),
   130  		}
   131  	}
   132  
   133  	reqExt.DelDomain = openxExt.DelDomain
   134  	reqExt.Platform = openxExt.Platform
   135  
   136  	imp.TagID = openxExt.Unit
   137  	if imp.BidFloor == 0 && openxExt.CustomFloor > 0 {
   138  		imp.BidFloor = openxExt.CustomFloor
   139  	}
   140  
   141  	// outgoing imp.ext should be same as incoming imp.ext minus prebid and bidder
   142  	impExt := openxImpExt{}
   143  	if err := json.Unmarshal(imp.Ext, &impExt); err != nil {
   144  		return &errortypes.BadInput{
   145  			Message: err.Error(),
   146  		}
   147  	}
   148  	delete(impExt, openrtb_ext.PrebidExtKey)
   149  	delete(impExt, openrtb_ext.PrebidExtBidderKey)
   150  
   151  	if openxExt.CustomParams != nil {
   152  		var err error
   153  		if impExt["customParams"], err = json.Marshal(openxExt.CustomParams); err != nil {
   154  			return &errortypes.BadInput{
   155  				Message: err.Error(),
   156  			}
   157  		}
   158  	}
   159  
   160  	if len(impExt) > 0 {
   161  		var err error
   162  		if imp.Ext, err = json.Marshal(impExt); err != nil {
   163  			return &errortypes.BadInput{
   164  				Message: err.Error(),
   165  			}
   166  		}
   167  	} else {
   168  		imp.Ext = nil
   169  	}
   170  
   171  	if imp.Video != nil {
   172  		videoCopy := *imp.Video
   173  		if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 {
   174  			videoCopy.Ext = json.RawMessage(`{"rewarded":1}`)
   175  		} else {
   176  			videoCopy.Ext = nil
   177  		}
   178  		imp.Video = &videoCopy
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  func (a *OpenxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   185  	if response.StatusCode == http.StatusNoContent {
   186  		return nil, nil
   187  	}
   188  
   189  	if response.StatusCode == http.StatusBadRequest {
   190  		return nil, []error{&errortypes.BadInput{
   191  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   192  		}}
   193  	}
   194  
   195  	if response.StatusCode != http.StatusOK {
   196  		return nil, []error{&errortypes.BadServerResponse{
   197  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   198  		}}
   199  	}
   200  
   201  	var bidResp openrtb2.BidResponse
   202  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   203  		return nil, []error{err}
   204  	}
   205  
   206  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
   207  
   208  	// overrride default currency
   209  	if bidResp.Cur != "" {
   210  		bidResponse.Currency = bidResp.Cur
   211  	}
   212  
   213  	if bidResp.Ext != nil {
   214  		var bidRespExt openxRespExt
   215  		if err := json.Unmarshal(bidResp.Ext, &bidRespExt); err == nil && bidRespExt.FledgeAuctionConfigs != nil {
   216  			bidResponse.FledgeAuctionConfigs = make([]*openrtb_ext.FledgeAuctionConfig, 0, len(bidRespExt.FledgeAuctionConfigs))
   217  			for impId, config := range bidRespExt.FledgeAuctionConfigs {
   218  				fledgeAuctionConfig := &openrtb_ext.FledgeAuctionConfig{
   219  					ImpId:  impId,
   220  					Bidder: a.bidderName,
   221  					Config: config,
   222  				}
   223  				bidResponse.FledgeAuctionConfigs = append(bidResponse.FledgeAuctionConfigs, fledgeAuctionConfig)
   224  			}
   225  		}
   226  	}
   227  
   228  	for _, sb := range bidResp.SeatBid {
   229  		for i := range sb.Bid {
   230  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   231  				Bid:     &sb.Bid[i],
   232  				BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp),
   233  			})
   234  		}
   235  	}
   236  	return bidResponse, nil
   237  }
   238  
   239  // getMediaTypeForImp figures out which media type this bid is for.
   240  //
   241  // OpenX doesn't support multi-type impressions.
   242  // If both banner and video exist, take banner as we do not want in-banner video.
   243  func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
   244  	mediaType := openrtb_ext.BidTypeBanner
   245  	for _, imp := range imps {
   246  		if imp.ID == impId {
   247  			if imp.Banner == nil && imp.Video != nil {
   248  				mediaType = openrtb_ext.BidTypeVideo
   249  			}
   250  			return mediaType
   251  		}
   252  	}
   253  	return mediaType
   254  }
   255  
   256  // Builder builds a new instance of the Openx adapter for the given bidder with the given config.
   257  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   258  	bidder := &OpenxAdapter{
   259  		endpoint:   config.Endpoint,
   260  		bidderName: string(bidderName),
   261  	}
   262  	return bidder, nil
   263  }