github.com/prebid/prebid-server/v2@v2.18.0/adapters/cadent_aperture_mx/cadentaperturemx.go (about) 1 package cadentaperturemx 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/prebid/openrtb/v20/adcom1" 13 "github.com/prebid/openrtb/v20/openrtb2" 14 "github.com/prebid/prebid-server/v2/adapters" 15 "github.com/prebid/prebid-server/v2/config" 16 "github.com/prebid/prebid-server/v2/errortypes" 17 "github.com/prebid/prebid-server/v2/openrtb_ext" 18 ) 19 20 type adapter struct { 21 endpoint string 22 testing bool 23 } 24 25 func buildEndpoint(endpoint string, testing bool, timeout int64) string { 26 if timeout == 0 { 27 timeout = 1000 28 } 29 if testing { 30 // for passing validation tests 31 return endpoint + "?t=1000&ts=2060541160" 32 } 33 return endpoint + "?t=" + strconv.FormatInt(timeout, 10) + "&ts=" + strconv.FormatInt(time.Now().Unix(), 10) + "&src=pbserver" 34 } 35 36 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 37 var errs []error 38 39 if len(request.Imp) == 0 { 40 return nil, []error{&errortypes.BadInput{ 41 Message: fmt.Sprintf("No Imps in Bid Request"), 42 }} 43 } 44 45 if errs := preprocess(request); len(errs) > 0 { 46 return nil, append(errs, &errortypes.BadInput{ 47 Message: fmt.Sprintf("Error in preprocess of Imp, err: %s", errs), 48 }) 49 } 50 51 data, err := json.Marshal(request) 52 if err != nil { 53 return nil, []error{&errortypes.BadInput{ 54 Message: fmt.Sprintf("Error in packaging request to JSON"), 55 }} 56 } 57 58 headers := http.Header{} 59 headers.Add("Content-Type", "application/json;charset=utf-8") 60 headers.Add("Accept", "application/json") 61 62 if request.Device != nil { 63 addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA) 64 addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP) 65 addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language) 66 if request.Device.DNT != nil { 67 addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(*request.Device.DNT))) 68 } 69 } 70 if request.Site != nil { 71 addHeaderIfNonEmpty(headers, "Referer", request.Site.Page) 72 } 73 74 url := buildEndpoint(a.endpoint, a.testing, request.TMax) 75 76 return []*adapters.RequestData{{ 77 Method: "POST", 78 Uri: url, 79 Body: data, 80 Headers: headers, 81 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 82 }}, errs 83 } 84 85 func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpCadentApertureMX, error) { 86 var bidderExt adapters.ExtImpBidder 87 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 88 return nil, &errortypes.BadInput{ 89 Message: err.Error(), 90 } 91 } 92 93 var cadentExt openrtb_ext.ExtImpCadentApertureMX 94 if err := json.Unmarshal(bidderExt.Bidder, &cadentExt); err != nil { 95 return nil, &errortypes.BadInput{ 96 Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID), 97 } 98 } 99 100 tagIDValidation, err := strconv.ParseInt(cadentExt.TagID, 10, 64) 101 if err != nil || tagIDValidation == 0 { 102 return nil, &errortypes.BadInput{ 103 Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID), 104 } 105 } 106 107 if cadentExt.TagID == "" { 108 return nil, &errortypes.BadInput{ 109 Message: fmt.Sprintf("Ignoring imp id=%s, no tagid present", imp.ID), 110 } 111 } 112 113 return &cadentExt, nil 114 } 115 116 func buildImpBanner(imp *openrtb2.Imp) error { 117 118 if imp.Banner == nil { 119 return &errortypes.BadInput{ 120 Message: fmt.Sprintf("Request needs to include a Banner object"), 121 } 122 } 123 124 bannerCopy := *imp.Banner 125 banner := &bannerCopy 126 127 if banner.W == nil && banner.H == nil { 128 if len(banner.Format) == 0 { 129 return &errortypes.BadInput{ 130 Message: fmt.Sprintf("Need at least one size to build request"), 131 } 132 } 133 format := banner.Format[0] 134 banner.Format = banner.Format[1:] 135 banner.W = &format.W 136 banner.H = &format.H 137 imp.Banner = banner 138 } 139 140 return nil 141 } 142 143 func buildImpVideo(imp *openrtb2.Imp) error { 144 145 if len(imp.Video.MIMEs) == 0 { 146 return &errortypes.BadInput{ 147 Message: fmt.Sprintf("Video: missing required field mimes"), 148 } 149 } 150 151 if (imp.Video.H == nil || *imp.Video.H == 0) && (imp.Video.W == nil || *imp.Video.W == 0) { 152 return &errortypes.BadInput{ 153 Message: fmt.Sprintf("Video: Need at least one size to build request"), 154 } 155 } 156 157 if len(imp.Video.Protocols) > 0 { 158 videoCopy := *imp.Video 159 videoCopy.Protocols = cleanProtocol(imp.Video.Protocols) 160 imp.Video = &videoCopy 161 } 162 163 return nil 164 } 165 166 // not supporting VAST protocol 7 (VAST 4.0); 167 func cleanProtocol(protocols []adcom1.MediaCreativeSubtype) []adcom1.MediaCreativeSubtype { 168 newitems := make([]adcom1.MediaCreativeSubtype, 0, len(protocols)) 169 170 for _, i := range protocols { 171 if i != adcom1.CreativeVAST40 { 172 newitems = append(newitems, i) 173 } 174 } 175 176 return newitems 177 } 178 179 // Add Cadent required properties to Imp object 180 func addImpProps(imp *openrtb2.Imp, secure *int8, cadentExt *openrtb_ext.ExtImpCadentApertureMX) { 181 imp.TagID = cadentExt.TagID 182 imp.Secure = secure 183 184 if cadentExt.BidFloor != "" { 185 bidFloor, err := strconv.ParseFloat(cadentExt.BidFloor, 64) 186 if err != nil { 187 bidFloor = 0 188 } 189 190 if bidFloor > 0 { 191 imp.BidFloor = bidFloor 192 imp.BidFloorCur = "USD" 193 } 194 } 195 } 196 197 // Adding header fields to request header 198 func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { 199 if len(headerValue) > 0 { 200 headers.Add(headerName, headerValue) 201 } 202 } 203 204 // Handle request errors and formatting to be sent to Cadent 205 func preprocess(request *openrtb2.BidRequest) []error { 206 impsCount := len(request.Imp) 207 errors := make([]error, 0, impsCount) 208 resImps := make([]openrtb2.Imp, 0, impsCount) 209 secure := int8(0) 210 domain := "" 211 if request.Site != nil && request.Site.Page != "" { 212 domain = request.Site.Page 213 } else if request.App != nil { 214 if request.App.Domain != "" { 215 domain = request.App.Domain 216 } else if request.App.StoreURL != "" { 217 domain = request.App.StoreURL 218 } 219 } 220 221 pageURL, err := url.Parse(domain) 222 if err == nil && pageURL.Scheme == "https" { 223 secure = int8(1) 224 } 225 226 for _, imp := range request.Imp { 227 cadentExt, err := unpackImpExt(&imp) 228 if err != nil { 229 errors = append(errors, err) 230 continue 231 } 232 233 addImpProps(&imp, &secure, cadentExt) 234 235 if imp.Video != nil { 236 if err := buildImpVideo(&imp); err != nil { 237 errors = append(errors, err) 238 continue 239 } 240 } else if err := buildImpBanner(&imp); err != nil { 241 errors = append(errors, err) 242 continue 243 244 } 245 246 resImps = append(resImps, imp) 247 } 248 249 request.Imp = resImps 250 251 return errors 252 } 253 254 // MakeBids make the bids for the bid response. 255 func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 256 257 if response.StatusCode == http.StatusNoContent { 258 // no bid response 259 return nil, nil 260 } 261 262 if response.StatusCode != http.StatusOK { 263 return nil, []error{&errortypes.BadServerResponse{ 264 Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode), 265 }} 266 } 267 268 var bidResp openrtb2.BidResponse 269 270 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 271 return nil, []error{&errortypes.BadServerResponse{ 272 Message: fmt.Sprintf("Unable to unpackage bid response. Error: %s", err.Error()), 273 }} 274 } 275 276 bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) 277 278 for _, sb := range bidResp.SeatBid { 279 for i := range sb.Bid { 280 sb.Bid[i].ImpID = sb.Bid[i].ID 281 282 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 283 Bid: &sb.Bid[i], 284 BidType: getBidType(sb.Bid[i].AdM), 285 }) 286 } 287 } 288 289 return bidResponse, nil 290 291 } 292 293 func getBidType(bidAdm string) openrtb_ext.BidType { 294 if bidAdm != "" && ContainsAny(bidAdm, []string{"<?xml", "<vast"}) { 295 return openrtb_ext.BidTypeVideo 296 } 297 return openrtb_ext.BidTypeBanner 298 } 299 300 func ContainsAny(raw string, keys []string) bool { 301 lowerCased := strings.ToLower(raw) 302 for i := 0; i < len(keys); i++ { 303 if strings.Contains(lowerCased, keys[i]) { 304 return true 305 } 306 } 307 return false 308 309 } 310 311 // Builder builds a new instance of the Cadent Aperture MX adapter for the given bidder with the given config. 312 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 313 bidder := &adapter{ 314 endpoint: config.Endpoint, 315 testing: false, 316 } 317 return bidder, nil 318 }