github.com/prebid/prebid-server@v0.275.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/v19/adcom1" 13 "github.com/prebid/openrtb/v19/openrtb2" 14 "github.com/prebid/prebid-server/adapters" 15 "github.com/prebid/prebid-server/config" 16 "github.com/prebid/prebid-server/errortypes" 17 "github.com/prebid/prebid-server/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); errs != nil && 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 }}, errs 82 } 83 84 func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpCadentApertureMX, error) { 85 var bidderExt adapters.ExtImpBidder 86 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 87 return nil, &errortypes.BadInput{ 88 Message: err.Error(), 89 } 90 } 91 92 var cadentExt openrtb_ext.ExtImpCadentApertureMX 93 if err := json.Unmarshal(bidderExt.Bidder, &cadentExt); err != nil { 94 return nil, &errortypes.BadInput{ 95 Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID), 96 } 97 } 98 99 tagIDValidation, err := strconv.ParseInt(cadentExt.TagID, 10, 64) 100 if err != nil || tagIDValidation == 0 { 101 return nil, &errortypes.BadInput{ 102 Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID), 103 } 104 } 105 106 if cadentExt.TagID == "" { 107 return nil, &errortypes.BadInput{ 108 Message: fmt.Sprintf("Ignoring imp id=%s, no tagid present", imp.ID), 109 } 110 } 111 112 return &cadentExt, nil 113 } 114 115 func buildImpBanner(imp *openrtb2.Imp) error { 116 117 if imp.Banner == nil { 118 return &errortypes.BadInput{ 119 Message: fmt.Sprintf("Request needs to include a Banner object"), 120 } 121 } 122 123 bannerCopy := *imp.Banner 124 banner := &bannerCopy 125 126 if banner.W == nil && banner.H == nil { 127 if len(banner.Format) == 0 { 128 return &errortypes.BadInput{ 129 Message: fmt.Sprintf("Need at least one size to build request"), 130 } 131 } 132 format := banner.Format[0] 133 banner.Format = banner.Format[1:] 134 banner.W = &format.W 135 banner.H = &format.H 136 imp.Banner = banner 137 } 138 139 return nil 140 } 141 142 func buildImpVideo(imp *openrtb2.Imp) error { 143 144 if len(imp.Video.MIMEs) == 0 { 145 return &errortypes.BadInput{ 146 Message: fmt.Sprintf("Video: missing required field mimes"), 147 } 148 } 149 150 if imp.Video.H == 0 && imp.Video.W == 0 { 151 return &errortypes.BadInput{ 152 Message: fmt.Sprintf("Video: Need at least one size to build request"), 153 } 154 } 155 156 if len(imp.Video.Protocols) > 0 { 157 videoCopy := *imp.Video 158 videoCopy.Protocols = cleanProtocol(imp.Video.Protocols) 159 imp.Video = &videoCopy 160 } 161 162 return nil 163 } 164 165 // not supporting VAST protocol 7 (VAST 4.0); 166 func cleanProtocol(protocols []adcom1.MediaCreativeSubtype) []adcom1.MediaCreativeSubtype { 167 newitems := make([]adcom1.MediaCreativeSubtype, 0, len(protocols)) 168 169 for _, i := range protocols { 170 if i != adcom1.CreativeVAST40 { 171 newitems = append(newitems, i) 172 } 173 } 174 175 return newitems 176 } 177 178 // Add Cadent required properties to Imp object 179 func addImpProps(imp *openrtb2.Imp, secure *int8, cadentExt *openrtb_ext.ExtImpCadentApertureMX) { 180 imp.TagID = cadentExt.TagID 181 imp.Secure = secure 182 183 if cadentExt.BidFloor != "" { 184 bidFloor, err := strconv.ParseFloat(cadentExt.BidFloor, 64) 185 if err != nil { 186 bidFloor = 0 187 } 188 189 if bidFloor > 0 { 190 imp.BidFloor = bidFloor 191 imp.BidFloorCur = "USD" 192 } 193 } 194 195 return 196 } 197 198 // Adding header fields to request header 199 func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { 200 if len(headerValue) > 0 { 201 headers.Add(headerName, headerValue) 202 } 203 } 204 205 // Handle request errors and formatting to be sent to Cadent 206 func preprocess(request *openrtb2.BidRequest) []error { 207 impsCount := len(request.Imp) 208 errors := make([]error, 0, impsCount) 209 resImps := make([]openrtb2.Imp, 0, impsCount) 210 secure := int8(0) 211 domain := "" 212 if request.Site != nil && request.Site.Page != "" { 213 domain = request.Site.Page 214 } else if request.App != nil { 215 if request.App.Domain != "" { 216 domain = request.App.Domain 217 } else if request.App.StoreURL != "" { 218 domain = request.App.StoreURL 219 } 220 } 221 222 pageURL, err := url.Parse(domain) 223 if err == nil && pageURL.Scheme == "https" { 224 secure = int8(1) 225 } 226 227 for _, imp := range request.Imp { 228 cadentExt, err := unpackImpExt(&imp) 229 if err != nil { 230 errors = append(errors, err) 231 continue 232 } 233 234 addImpProps(&imp, &secure, cadentExt) 235 236 if imp.Video != nil { 237 if err := buildImpVideo(&imp); err != nil { 238 errors = append(errors, err) 239 continue 240 } 241 } else if err := buildImpBanner(&imp); err != nil { 242 errors = append(errors, err) 243 continue 244 245 } 246 247 resImps = append(resImps, imp) 248 } 249 250 request.Imp = resImps 251 252 return errors 253 } 254 255 // MakeBids make the bids for the bid response. 256 func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 257 258 if response.StatusCode == http.StatusNoContent { 259 // no bid response 260 return nil, nil 261 } 262 263 if response.StatusCode != http.StatusOK { 264 return nil, []error{&errortypes.BadServerResponse{ 265 Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode), 266 }} 267 } 268 269 var bidResp openrtb2.BidResponse 270 271 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 272 return nil, []error{&errortypes.BadServerResponse{ 273 Message: fmt.Sprintf("Unable to unpackage bid response. Error: %s", err.Error()), 274 }} 275 } 276 277 bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) 278 279 for _, sb := range bidResp.SeatBid { 280 for i := range sb.Bid { 281 sb.Bid[i].ImpID = sb.Bid[i].ID 282 283 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 284 Bid: &sb.Bid[i], 285 BidType: getBidType(sb.Bid[i].AdM), 286 }) 287 } 288 } 289 290 return bidResponse, nil 291 292 } 293 294 func getBidType(bidAdm string) openrtb_ext.BidType { 295 if bidAdm != "" && ContainsAny(bidAdm, []string{"<?xml", "<vast"}) { 296 return openrtb_ext.BidTypeVideo 297 } 298 return openrtb_ext.BidTypeBanner 299 } 300 301 func ContainsAny(raw string, keys []string) bool { 302 lowerCased := strings.ToLower(raw) 303 for i := 0; i < len(keys); i++ { 304 if strings.Contains(lowerCased, keys[i]) { 305 return true 306 } 307 } 308 return false 309 310 } 311 312 // Builder builds a new instance of the Cadent Aperture MX adapter for the given bidder with the given config. 313 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 314 bidder := &adapter{ 315 endpoint: config.Endpoint, 316 testing: false, 317 } 318 return bidder, nil 319 }