github.com/prebid/prebid-server/v2@v2.18.0/adapters/rtbhouse/rtbhouse.go (about) 1 package rtbhouse 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "strings" 9 10 "github.com/buger/jsonparser" 11 "github.com/prebid/openrtb/v20/openrtb2" 12 "github.com/prebid/prebid-server/v2/adapters" 13 "github.com/prebid/prebid-server/v2/config" 14 "github.com/prebid/prebid-server/v2/errortypes" 15 "github.com/prebid/prebid-server/v2/openrtb_ext" 16 ) 17 18 const ( 19 BidderCurrency string = "USD" 20 ) 21 22 // RTBHouseAdapter implements the Bidder interface. 23 type RTBHouseAdapter struct { 24 endpoint string 25 } 26 27 // Builder builds a new instance of the RTBHouse adapter for the given bidder with the given config. 28 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 29 bidder := &RTBHouseAdapter{ 30 endpoint: config.Endpoint, 31 } 32 return bidder, nil 33 } 34 35 // MakeRequests prepares the HTTP requests which should be made to fetch bids. 36 func (adapter *RTBHouseAdapter) MakeRequests( 37 openRTBRequest *openrtb2.BidRequest, 38 reqInfo *adapters.ExtraRequestInfo, 39 ) ( 40 requestsToBidder []*adapters.RequestData, 41 errs []error, 42 ) { 43 44 reqCopy := *openRTBRequest 45 reqCopy.Imp = []openrtb2.Imp{} 46 for _, imp := range openRTBRequest.Imp { 47 var bidFloorCur = imp.BidFloorCur 48 var bidFloor = imp.BidFloor 49 if bidFloorCur == "" && bidFloor == 0 { 50 rtbhouseExt, err := getImpressionExt(imp) 51 if err != nil { 52 return nil, []error{err} 53 } 54 if rtbhouseExt.BidFloor > 0 { 55 bidFloor = rtbhouseExt.BidFloor 56 bidFloorCur = BidderCurrency 57 if len(reqCopy.Cur) > 0 { 58 bidFloorCur = reqCopy.Cur[0] 59 } 60 } 61 } 62 63 // Check if imp comes with bid floor amount defined in a foreign currency 64 if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != BidderCurrency { 65 // Convert to US dollars 66 convertedValue, err := reqInfo.ConvertCurrency(bidFloor, bidFloorCur, BidderCurrency) 67 if err != nil { 68 return nil, []error{err} 69 } 70 71 bidFloorCur = BidderCurrency 72 bidFloor = convertedValue 73 } 74 75 if bidFloor > 0 && bidFloorCur == BidderCurrency { 76 // Update after conversion. All imp elements inside request.Imp are shallow copies 77 // therefore, their non-pointer values are not shared memory and are safe to modify. 78 imp.BidFloorCur = bidFloorCur 79 imp.BidFloor = bidFloor 80 } 81 82 // Set the CUR of bid to BIDDER_CURRENCY after converting all floors 83 reqCopy.Cur = []string{BidderCurrency} 84 reqCopy.Imp = append(reqCopy.Imp, imp) 85 } 86 87 openRTBRequestJSON, err := json.Marshal(reqCopy) 88 if err != nil { 89 errs = append(errs, err) 90 return nil, errs 91 } 92 93 headers := http.Header{} 94 headers.Add("Content-Type", "application/json;charset=utf-8") 95 requestToBidder := &adapters.RequestData{ 96 Method: "POST", 97 Uri: adapter.endpoint, 98 Body: openRTBRequestJSON, 99 Headers: headers, 100 ImpIDs: openrtb_ext.GetImpIDs(reqCopy.Imp), 101 } 102 requestsToBidder = append(requestsToBidder, requestToBidder) 103 104 return requestsToBidder, errs 105 } 106 107 func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpRTBHouse, error) { 108 var bidderExt adapters.ExtImpBidder 109 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 110 return nil, &errortypes.BadInput{ 111 Message: "Bidder extension not provided or can't be unmarshalled", 112 } 113 } 114 115 var rtbhouseExt openrtb_ext.ExtImpRTBHouse 116 if err := json.Unmarshal(bidderExt.Bidder, &rtbhouseExt); err != nil { 117 return nil, &errortypes.BadInput{ 118 Message: "Error while unmarshaling bidder extension", 119 } 120 } 121 122 return &rtbhouseExt, nil 123 } 124 125 const unexpectedStatusCodeFormat = "" + 126 "Unexpected status code: %d. Run with request.debug = 1 for more info" 127 128 // MakeBids unpacks the server's response into Bids. 129 func (adapter *RTBHouseAdapter) MakeBids( 130 openRTBRequest *openrtb2.BidRequest, 131 requestToBidder *adapters.RequestData, 132 bidderRawResponse *adapters.ResponseData, 133 ) ( 134 bidderResponse *adapters.BidderResponse, 135 errs []error, 136 ) { 137 switch bidderRawResponse.StatusCode { 138 case http.StatusOK: 139 break 140 case http.StatusNoContent: 141 return nil, nil 142 case http.StatusBadRequest: 143 err := &errortypes.BadInput{ 144 Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), 145 } 146 return nil, []error{err} 147 default: 148 err := &errortypes.BadServerResponse{ 149 Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), 150 } 151 return nil, []error{err} 152 } 153 154 var openRTBBidderResponse openrtb2.BidResponse 155 if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { 156 return nil, []error{err} 157 } 158 159 bidsCapacity := len(openRTBBidderResponse.SeatBid[0].Bid) 160 bidderResponse = adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) 161 var typedBid *adapters.TypedBid 162 for _, seatBid := range openRTBBidderResponse.SeatBid { 163 for _, bid := range seatBid.Bid { 164 bid := bid // pin! -> https://github.com/kyoh86/scopelint#whats-this 165 bidType, err := getMediaTypeForBid(bid) 166 if err != nil { 167 errs = append(errs, err) 168 continue 169 } else { 170 typedBid = &adapters.TypedBid{ 171 Bid: &bid, 172 BidType: bidType, 173 } 174 175 // for native bid responses fix Adm field 176 if typedBid.BidType == openrtb_ext.BidTypeNative { 177 bid.AdM, err = getNativeAdm(bid.AdM) 178 if err != nil { 179 errs = append(errs, err) 180 continue 181 } 182 } 183 184 bidderResponse.Bids = append(bidderResponse.Bids, typedBid) 185 } 186 } 187 } 188 189 bidderResponse.Currency = BidderCurrency 190 191 return bidderResponse, errs 192 193 } 194 195 func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { 196 switch bid.MType { 197 case openrtb2.MarkupBanner: 198 return openrtb_ext.BidTypeBanner, nil 199 case openrtb2.MarkupNative: 200 return openrtb_ext.BidTypeNative, nil 201 default: 202 return "", fmt.Errorf("unrecognized bid type in response from rtbhouse for bid %s", bid.ImpID) 203 } 204 } 205 206 func getNativeAdm(adm string) (string, error) { 207 nativeAdm := make(map[string]interface{}) 208 err := json.Unmarshal([]byte(adm), &nativeAdm) 209 if err != nil { 210 return adm, errors.New("unable to unmarshal native adm") 211 } 212 213 // move bid.adm.native to bid.adm 214 if _, ok := nativeAdm["native"]; ok { 215 //using jsonparser to avoid marshaling, encode escape, etc. 216 value, dataType, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative)) 217 if err != nil || dataType != jsonparser.Object { 218 return adm, errors.New("unable to get native adm") 219 } 220 adm = string(value) 221 } 222 223 return adm, nil 224 }