github.com/prebid/prebid-server/v2@v2.18.0/adapters/gamma/gamma.go (about) 1 package gamma 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 10 "github.com/prebid/openrtb/v20/openrtb2" 11 "github.com/prebid/openrtb/v20/openrtb3" 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 type GammaAdapter struct { 19 URI string 20 } 21 22 type gammaBid struct { 23 openrtb2.Bid //base 24 VastXML string `json:"vastXml,omitempty"` 25 VastURL string `json:"vastUrl,omitempty"` 26 } 27 28 type gammaSeatBid struct { 29 Bid []gammaBid `json:"bid"` 30 Group int8 `json:"group,omitempty"` 31 Ext json.RawMessage `json:"ext,omitempty"` 32 } 33 type gammaBidResponse struct { 34 ID string `json:"id"` 35 SeatBid []gammaSeatBid `json:"seatbid,omitempty"` 36 BidID string `json:"bidid,omitempty"` 37 Cur string `json:"cur,omitempty"` 38 CustomData string `json:"customdata,omitempty"` 39 NBR *openrtb3.NoBidReason `json:"nbr,omitempty"` 40 Ext json.RawMessage `json:"ext,omitempty"` 41 } 42 43 func checkParams(gammaExt openrtb_ext.ExtImpGamma) error { 44 if gammaExt.PartnerID == "" { 45 return &errortypes.BadInput{ 46 Message: "PartnerID is empty", 47 } 48 } 49 if gammaExt.ZoneID == "" { 50 return &errortypes.BadInput{ 51 Message: "ZoneID is empty", 52 } 53 } 54 if gammaExt.WebID == "" { 55 return &errortypes.BadInput{ 56 Message: "WebID is empty", 57 } 58 } 59 return nil 60 } 61 func (a *GammaAdapter) makeRequest(request *openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, []error) { 62 var errors []error 63 64 var bidderExt adapters.ExtImpBidder 65 err := json.Unmarshal(imp.Ext, &bidderExt) 66 if err != nil { 67 err = &errortypes.BadInput{ 68 Message: "ext.bidder not provided", 69 } 70 errors = append(errors, err) 71 return nil, errors 72 } 73 var gammaExt openrtb_ext.ExtImpGamma 74 err = json.Unmarshal(bidderExt.Bidder, &gammaExt) 75 if err != nil { 76 err = &errortypes.BadInput{ 77 Message: "ext.bidder.publisher not provided", 78 } 79 errors = append(errors, err) 80 return nil, errors 81 } 82 err = checkParams(gammaExt) 83 if err != nil { 84 errors = append(errors, err) 85 return nil, errors 86 } 87 88 thisURI := a.URI 89 thisURI = thisURI + "?id=" + gammaExt.PartnerID 90 thisURI = thisURI + "&zid=" + gammaExt.ZoneID 91 thisURI = thisURI + "&wid=" + gammaExt.WebID 92 thisURI = thisURI + "&bidid=" + imp.ID 93 thisURI = thisURI + "&hb=pbmobile" 94 if request.Device != nil { 95 if request.Device.IP != "" { 96 thisURI = thisURI + "&device_ip=" + request.Device.IP 97 } 98 if request.Device.Model != "" { 99 thisURI = thisURI + "&device_model=" + request.Device.Model 100 } 101 if request.Device.OS != "" { 102 thisURI = thisURI + "&device_os=" + request.Device.OS 103 } 104 if request.Device.UA != "" { 105 thisURI = thisURI + "&device_ua=" + url.QueryEscape(request.Device.UA) 106 } 107 if request.Device.IFA != "" { 108 thisURI = thisURI + "&device_ifa=" + request.Device.IFA 109 } 110 } 111 if request.App != nil { 112 if request.App.ID != "" { 113 thisURI = thisURI + "&app_id=" + request.App.ID 114 } 115 if request.App.Bundle != "" { 116 thisURI = thisURI + "&app_bundle=" + request.App.Bundle 117 } 118 if request.App.Name != "" { 119 thisURI = thisURI + "&app_name=" + request.App.Name 120 } 121 } 122 headers := http.Header{} 123 headers.Add("Accept", "*/*") 124 headers.Add("x-openrtb-version", "2.5") 125 if request.Device != nil { 126 addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA) 127 addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP) 128 addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language) 129 if request.Device.DNT != nil { 130 addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(*request.Device.DNT))) 131 } 132 } 133 headers.Add("Connection", "keep-alive") 134 headers.Add("cache-control", "no-cache") 135 headers.Add("Accept-Encoding", "gzip, deflate") 136 137 return &adapters.RequestData{ 138 Method: "GET", 139 Uri: thisURI, 140 Headers: headers, 141 ImpIDs: []string{imp.ID}, 142 }, errors 143 } 144 func (a *GammaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 145 errs := make([]error, 0, len(request.Imp)) 146 if len(request.Imp) == 0 { 147 err := &errortypes.BadInput{ 148 Message: "No impressions in the bid request", 149 } 150 errs = append(errs, err) 151 return nil, errs 152 } 153 var invalidImpIndex []int 154 155 for i := 0; i < len(request.Imp); i++ { 156 if request.Imp[i].Banner != nil { 157 bannerCopy := *request.Imp[i].Banner 158 if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { 159 firstFormat := bannerCopy.Format[0] 160 bannerCopy.W = &(firstFormat.W) 161 bannerCopy.H = &(firstFormat.H) 162 } 163 request.Imp[i].Banner = &bannerCopy 164 } else if request.Imp[i].Video == nil { 165 err := &errortypes.BadInput{ 166 Message: fmt.Sprintf("Gamma only supports banner and video media types. Ignoring imp id=%s", request.Imp[i].ID), 167 } 168 errs = append(errs, err) 169 invalidImpIndex = append(invalidImpIndex, i) 170 } 171 } 172 173 var adapterRequests []*adapters.RequestData 174 if len(invalidImpIndex) == 0 { 175 for _, imp := range request.Imp { 176 adapterReq, errors := a.makeRequest(request, imp) 177 if adapterReq != nil { 178 adapterRequests = append(adapterRequests, adapterReq) 179 } 180 errs = append(errs, errors...) 181 } 182 } else if len(request.Imp) == len(invalidImpIndex) { 183 //only true if every Imp was not a Banner or a Video 184 err := &errortypes.BadInput{ 185 Message: "No valid impression in the bid request", 186 } 187 errs = append(errs, err) 188 return nil, errs 189 } else { 190 var j int = 0 191 for i := 0; i < len(request.Imp); i++ { 192 if j < len(invalidImpIndex) && i == invalidImpIndex[j] { 193 j++ 194 } else { 195 adapterReq, errors := a.makeRequest(request, request.Imp[i]) 196 if adapterReq != nil { 197 adapterRequests = append(adapterRequests, adapterReq) 198 } 199 errs = append(errs, errors...) 200 } 201 } 202 } 203 204 return adapterRequests, errs 205 } 206 207 func convertBid(gBid gammaBid, mediaType openrtb_ext.BidType) *openrtb2.Bid { 208 bid := gBid.Bid 209 210 if mediaType == openrtb_ext.BidTypeVideo { 211 //Return inline VAST XML Document (Section 6.4.2) 212 if len(gBid.VastXML) > 0 { 213 if len(gBid.VastURL) > 0 { 214 bid.NURL = gBid.VastURL 215 } 216 bid.AdM = gBid.VastXML 217 } else { 218 return nil 219 } 220 } else { 221 if len(gBid.Bid.AdM) == 0 { 222 return nil 223 } 224 } 225 return &bid 226 } 227 228 func (a *GammaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 229 if response.StatusCode == http.StatusNoContent { 230 return nil, nil 231 } 232 233 if response.StatusCode == http.StatusBadRequest { 234 return nil, []error{&errortypes.BadServerResponse{ 235 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 236 }} 237 } 238 239 if response.StatusCode != http.StatusOK { 240 return nil, []error{&errortypes.BadServerResponse{ 241 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 242 }} 243 } 244 245 var gammaResp gammaBidResponse 246 if err := json.Unmarshal(response.Body, &gammaResp); err != nil { 247 return nil, []error{&errortypes.BadServerResponse{ 248 Message: fmt.Sprintf("bad server response: %d. ", err), 249 }} 250 } 251 252 //(Section 7.1 No-Bid Signaling) 253 if len(gammaResp.SeatBid) == 0 { 254 return nil, nil 255 } 256 257 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(gammaResp.SeatBid[0].Bid)) 258 errs := make([]error, 0, len(gammaResp.SeatBid[0].Bid)) 259 for _, sb := range gammaResp.SeatBid { 260 for i := range sb.Bid { 261 mediaType := getMediaTypeForImp(gammaResp.ID, internalRequest.Imp) 262 bid := convertBid(sb.Bid[i], mediaType) 263 if bid != nil { 264 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 265 Bid: bid, 266 BidType: mediaType, 267 }) 268 } else { 269 err := &errortypes.BadServerResponse{ 270 Message: fmt.Sprintf("Missing Ad Markup. Run with request.debug = 1 for more info"), 271 } 272 errs = append(errs, err) 273 } 274 } 275 } 276 return bidResponse, errs 277 } 278 279 // Adding header fields to request header 280 func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { 281 if len(headerValue) > 0 { 282 headers.Add(headerName, headerValue) 283 } 284 } 285 286 // getMediaTypeForImp figures out which media type this bid is for. 287 func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { 288 mediaType := openrtb_ext.BidTypeBanner //default type 289 for _, imp := range imps { 290 if imp.ID == impId { 291 if imp.Video != nil { 292 mediaType = openrtb_ext.BidTypeVideo 293 } 294 return mediaType 295 } 296 } 297 return mediaType 298 } 299 300 // Builder builds a new instance of the Gamma adapter for the given bidder with the given config. 301 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 302 bidder := &GammaAdapter{ 303 URI: config.Endpoint, 304 } 305 return bidder, nil 306 }