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 }