github.com/prebid/prebid-server/v2@v2.18.0/adapters/bidmachine/bidmachine.go (about) 1 package bidmachine 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "path" 9 "strconv" 10 "text/template" 11 12 "github.com/prebid/openrtb/v20/adcom1" 13 "github.com/prebid/openrtb/v20/openrtb2" 14 15 "github.com/prebid/prebid-server/v2/adapters" 16 "github.com/prebid/prebid-server/v2/config" 17 "github.com/prebid/prebid-server/v2/errortypes" 18 "github.com/prebid/prebid-server/v2/macros" 19 "github.com/prebid/prebid-server/v2/openrtb_ext" 20 ) 21 22 type adapter struct { 23 endpoint *template.Template 24 } 25 26 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 27 headers := http.Header{} 28 headers.Add("Content-Type", "application/json") 29 headers.Add("Accept", "application/json") 30 headers.Add("X-Openrtb-Version", "2.5") 31 32 impressions := request.Imp 33 result := make([]*adapters.RequestData, 0, len(impressions)) 34 errs := make([]error, 0, len(impressions)) 35 36 for _, impression := range impressions { 37 if impression.Banner != nil { 38 banner := impression.Banner 39 if banner.W == nil && banner.H == nil { 40 if banner.Format == nil { 41 errs = append(errs, &errortypes.BadInput{ 42 Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format is missing. At least one is required", 43 }) 44 continue 45 } 46 if len(banner.Format) == 0 { 47 errs = append(errs, &errortypes.BadInput{ 48 Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format array is empty. At least one is required", 49 }) 50 continue 51 } 52 } 53 54 } 55 56 var bidderExt adapters.ExtImpBidder 57 err := json.Unmarshal(impression.Ext, &bidderExt) 58 if err != nil { 59 errs = append(errs, err) 60 continue 61 } 62 63 var impressionExt openrtb_ext.ExtImpBidmachine 64 err = json.Unmarshal(bidderExt.Bidder, &impressionExt) 65 if err != nil { 66 errs = append(errs, err) 67 continue 68 } 69 url, err := a.buildEndpointURL(impressionExt) 70 if err != nil { 71 errs = append(errs, err) 72 continue 73 } 74 if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 { 75 if impression.Banner != nil && !hasRewardedBattr(impression.Banner.BAttr) { 76 bannerCopy := *impression.Banner 77 bannerCopy.BAttr = copyBAttrWithRewardedInventory(bannerCopy.BAttr) 78 impression.Banner = &bannerCopy 79 } 80 if impression.Video != nil && !hasRewardedBattr(impression.Video.BAttr) { 81 videoCopy := *impression.Video 82 videoCopy.BAttr = copyBAttrWithRewardedInventory(videoCopy.BAttr) 83 impression.Video = &videoCopy 84 } 85 } 86 request.Imp = []openrtb2.Imp{impression} 87 body, err := json.Marshal(request) 88 if err != nil { 89 errs = append(errs, err) 90 continue 91 } 92 result = append(result, &adapters.RequestData{ 93 Method: "POST", 94 Uri: url, 95 Body: body, 96 Headers: headers, 97 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 98 }) 99 } 100 101 request.Imp = impressions 102 103 return result, errs 104 } 105 106 func hasRewardedBattr(attr []adcom1.CreativeAttribute) bool { 107 for i := 0; i < len(attr); i++ { 108 if attr[i] == adcom1.AttrHasSkipButton { 109 return true 110 } 111 } 112 return false 113 } 114 115 func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 116 var errs []error 117 118 switch responseData.StatusCode { 119 case http.StatusNoContent: 120 return nil, nil 121 case http.StatusServiceUnavailable: 122 fallthrough 123 case http.StatusBadRequest: 124 fallthrough 125 case http.StatusUnauthorized: 126 fallthrough 127 case http.StatusForbidden: 128 return nil, []error{&errortypes.BadInput{ 129 Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body), 130 }} 131 case http.StatusOK: 132 break 133 default: 134 return nil, []error{&errortypes.BadServerResponse{ 135 Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body), 136 }} 137 } 138 139 var bidResponse openrtb2.BidResponse 140 err := json.Unmarshal(responseData.Body, &bidResponse) 141 if err != nil { 142 return nil, []error{&errortypes.BadServerResponse{ 143 Message: err.Error(), 144 }} 145 } 146 147 response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 148 149 for _, seatBid := range bidResponse.SeatBid { 150 for _, bid := range seatBid.Bid { 151 thisBid := bid 152 bidType := GetMediaTypeForImp(bid.ImpID, request.Imp) 153 if bidType == UndefinedMediaType { 154 errs = append(errs, &errortypes.BadServerResponse{ 155 Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID, 156 }) 157 continue 158 } 159 response.Bids = append(response.Bids, &adapters.TypedBid{ 160 Bid: &thisBid, 161 BidType: bidType, 162 }) 163 } 164 } 165 166 return response, errs 167 } 168 169 // Builder builds a new instance of the Bidmachine adapter for the given bidder with the given config. 170 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 171 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 172 if err != nil { 173 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 174 } 175 176 bidder := &adapter{ 177 endpoint: template, 178 } 179 180 return bidder, nil 181 } 182 183 const UndefinedMediaType = openrtb_ext.BidType("") 184 185 func (a *adapter) buildEndpointURL(params openrtb_ext.ExtImpBidmachine) (string, error) { 186 endpointParams := macros.EndpointTemplateParams{Host: params.Host} 187 uriString, errMacros := macros.ResolveMacros(a.endpoint, endpointParams) 188 if errMacros != nil { 189 return "", &errortypes.BadInput{ 190 Message: "Failed to resolve host macros", 191 } 192 } 193 uri, errUrl := url.Parse(uriString) 194 if errUrl != nil || uri.Scheme == "" || uri.Host == "" { 195 return "", &errortypes.BadInput{ 196 Message: "Failed to create final URL with provided host", 197 } 198 } 199 uri.Path = path.Join(uri.Path, params.Path) 200 uri.Path = path.Join(uri.Path, params.SellerID) 201 return uri.String(), nil 202 } 203 204 func copyBAttrWithRewardedInventory(src []adcom1.CreativeAttribute) []adcom1.CreativeAttribute { 205 dst := make([]adcom1.CreativeAttribute, len(src)) 206 copy(dst, src) 207 dst = append(dst, adcom1.AttrHasSkipButton) 208 return dst 209 } 210 211 func GetMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { 212 mediaType := openrtb_ext.BidTypeBanner 213 for _, imp := range imps { 214 if imp.ID == impID { 215 if imp.Banner == nil && imp.Video != nil { 216 mediaType = openrtb_ext.BidTypeVideo 217 } 218 return mediaType 219 } 220 } 221 return UndefinedMediaType 222 }