github.com/prebid/prebid-server/v2@v2.18.0/adapters/orbidder/orbidder.go (about) 1 package orbidder 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strings" 8 9 "github.com/prebid/openrtb/v20/openrtb2" 10 11 "github.com/prebid/prebid-server/v2/adapters" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/errortypes" 14 "github.com/prebid/prebid-server/v2/openrtb_ext" 15 ) 16 17 type OrbidderAdapter struct { 18 endpoint string 19 } 20 21 // MakeRequests makes the HTTP requests which should be made to fetch bids from orbidder. 22 func (rcv *OrbidderAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 23 validImps, errs := getValidImpressions(request, reqInfo) 24 if len(validImps) == 0 { 25 return nil, errs 26 } 27 28 request.Imp = validImps 29 30 requestBodyJSON, err := json.Marshal(request) 31 if err != nil { 32 errs = append(errs, err) 33 return nil, errs 34 } 35 36 headers := http.Header{} 37 headers.Add("Content-Type", "application/json;charset=utf-8") 38 headers.Add("Accept", "application/json") 39 40 return []*adapters.RequestData{{ 41 Method: http.MethodPost, 42 Uri: rcv.endpoint, 43 Body: requestBodyJSON, 44 Headers: headers, 45 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 46 }}, errs 47 } 48 49 // getValidImpressions validate imps and check for bid floor currency. Convert to EUR if necessary 50 func getValidImpressions(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]openrtb2.Imp, []error) { 51 var errs []error 52 var validImps []openrtb2.Imp 53 54 for _, imp := range request.Imp { 55 if err := preprocessBidFloorCurrency(&imp, reqInfo); err != nil { 56 errs = append(errs, err) 57 continue 58 } 59 60 if err := preprocessExtensions(&imp); err != nil { 61 errs = append(errs, err) 62 continue 63 } 64 validImps = append(validImps, imp) 65 } 66 return validImps, errs 67 } 68 69 func preprocessExtensions(imp *openrtb2.Imp) error { 70 var bidderExt adapters.ExtImpBidder 71 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 72 return &errortypes.BadInput{ 73 Message: err.Error(), 74 } 75 } 76 77 var orbidderExt openrtb_ext.ExtImpOrbidder 78 if err := json.Unmarshal(bidderExt.Bidder, &orbidderExt); err != nil { 79 return &errortypes.BadInput{ 80 Message: "Wrong orbidder bidder ext: " + err.Error(), 81 } 82 } 83 84 return nil 85 } 86 87 func preprocessBidFloorCurrency(imp *openrtb2.Imp, reqInfo *adapters.ExtraRequestInfo) error { 88 // we expect every currency related data to be EUR 89 if imp.BidFloor > 0 && strings.ToUpper(imp.BidFloorCur) != "EUR" && imp.BidFloorCur != "" { 90 if convertedValue, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "EUR"); err != nil { 91 return err 92 } else { 93 imp.BidFloor = convertedValue 94 } 95 } 96 imp.BidFloorCur = "EUR" 97 return nil 98 } 99 100 // MakeBids unpacks server response into Bids. 101 func (rcv OrbidderAdapter) MakeBids(_ *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 102 if response.StatusCode == http.StatusNoContent { 103 return nil, nil 104 } 105 106 if response.StatusCode >= http.StatusInternalServerError { 107 return nil, []error{&errortypes.BadServerResponse{ 108 Message: fmt.Sprintf("Unexpected status code: %d. Dsp server internal error.", response.StatusCode), 109 }} 110 } 111 112 if response.StatusCode >= http.StatusBadRequest { 113 return nil, []error{&errortypes.BadInput{ 114 Message: fmt.Sprintf("Unexpected status code: %d. Bad request to dsp.", response.StatusCode), 115 }} 116 } 117 118 if response.StatusCode != http.StatusOK { 119 return nil, []error{&errortypes.BadServerResponse{ 120 Message: fmt.Sprintf("Unexpected status code: %d. Bad response from dsp.", response.StatusCode), 121 }} 122 } 123 124 var bidResp openrtb2.BidResponse 125 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 126 return nil, []error{err} 127 } 128 129 var bidErrs []error 130 bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) 131 for _, seatBid := range bidResp.SeatBid { 132 for i := range seatBid.Bid { 133 // later we have to add the bid as a pointer, 134 // because of this we need a variable that only exists at this loop iteration. 135 // otherwise there will be issues with multibid and pointer behavior. 136 bid := seatBid.Bid[i] 137 bidType, err := getBidType(bid) 138 if err != nil { 139 // could not determinate media type, append an error and continue with the next bid. 140 bidErrs = append(bidErrs, err) 141 continue 142 } 143 144 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 145 Bid: &bid, 146 BidType: bidType, 147 }) 148 } 149 } 150 if bidResp.Cur != "" { 151 bidResponse.Currency = bidResp.Cur 152 } 153 154 return bidResponse, bidErrs 155 } 156 157 func getBidType(bid openrtb2.Bid) (openrtb_ext.BidType, error) { 158 159 // determinate media type by bid response field mtype 160 switch bid.MType { 161 case openrtb2.MarkupBanner: 162 return openrtb_ext.BidTypeBanner, nil 163 case openrtb2.MarkupVideo: 164 return openrtb_ext.BidTypeVideo, nil 165 case openrtb2.MarkupAudio: 166 return openrtb_ext.BidTypeAudio, nil 167 case openrtb2.MarkupNative: 168 return openrtb_ext.BidTypeNative, nil 169 } 170 171 return "", &errortypes.BadInput{ 172 Message: fmt.Sprintf("Could not define media type for impression: %s", bid.ImpID), 173 } 174 } 175 176 // Builder builds a new instance of the Orbidder adapter for the given bidder with the given config. 177 func Builder(_ openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 178 bidder := &OrbidderAdapter{ 179 endpoint: config.Endpoint, 180 } 181 return bidder, nil 182 }