github.com/prebid/prebid-server@v0.275.0/adapters/tappx/tappx.go (about) 1 package tappx 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "regexp" 9 "strconv" 10 "text/template" 11 "time" 12 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/macros" 18 "github.com/prebid/prebid-server/openrtb_ext" 19 ) 20 21 const TAPPX_BIDDER_VERSION = "1.5" 22 const TYPE_CNN = "prebid" 23 24 type TappxAdapter struct { 25 endpointTemplate *template.Template 26 } 27 28 type Bidder struct { 29 Tappxkey string `json:"tappxkey"` 30 Mktag string `json:"mktag,omitempty"` 31 Bcid []string `json:"bcid,omitempty"` 32 Bcrid []string `json:"bcrid,omitempty"` 33 } 34 35 type Ext struct { 36 Bidder `json:"bidder"` 37 } 38 39 // Builder builds a new instance of the Tappx adapter for the given bidder with the given config. 40 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 41 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 42 if err != nil { 43 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 44 } 45 46 bidder := &TappxAdapter{ 47 endpointTemplate: template, 48 } 49 return bidder, nil 50 } 51 52 func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 53 if len(request.Imp) == 0 { 54 return nil, []error{&errortypes.BadInput{ 55 Message: "No impression in the bid request", 56 }} 57 } 58 59 var bidderExt adapters.ExtImpBidder 60 if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { 61 return nil, []error{&errortypes.BadInput{ 62 Message: "Error parsing bidderExt object", 63 }} 64 } 65 var tappxExt openrtb_ext.ExtImpTappx 66 if err := json.Unmarshal(bidderExt.Bidder, &tappxExt); err != nil { 67 return nil, []error{&errortypes.BadInput{ 68 Message: "Error parsing tappxExt parameters", 69 }} 70 } 71 72 ext := Ext{ 73 Bidder: Bidder{ 74 Tappxkey: tappxExt.TappxKey, 75 Mktag: tappxExt.Mktag, 76 Bcid: tappxExt.Bcid, 77 Bcrid: tappxExt.Bcrid, 78 }, 79 } 80 81 if jsonext, err := json.Marshal(ext); err == nil { 82 request.Ext = jsonext 83 } else { 84 return nil, []error{&errortypes.FailedToRequestBids{ 85 Message: "Error marshaling tappxExt parameters", 86 }} 87 } 88 89 var test int 90 test = int(request.Test) 91 92 url, err := a.buildEndpointURL(&tappxExt, test) 93 if url == "" { 94 return nil, []error{err} 95 } 96 97 if tappxExt.BidFloor > 0 { 98 request.Imp[0].BidFloor = tappxExt.BidFloor 99 } 100 101 reqJSON, err := json.Marshal(request) 102 if err != nil { 103 return nil, []error{&errortypes.BadInput{ 104 Message: "Error parsing reqJSON object", 105 }} 106 } 107 108 headers := http.Header{} 109 headers.Add("Content-Type", "application/json;charset=utf-8") 110 headers.Add("Accept", "application/json") 111 return []*adapters.RequestData{{ 112 Method: "POST", 113 Uri: url, 114 Body: reqJSON, 115 Headers: headers, 116 }}, []error{} 117 } 118 119 // Builds enpoint url based on adapter-specific pub settings from imp.ext 120 func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test int) (string, error) { 121 122 if params.Endpoint == "" { 123 return "", &errortypes.BadInput{ 124 Message: "Tappx endpoint undefined", 125 } 126 } 127 128 if params.TappxKey == "" { 129 return "", &errortypes.BadInput{ 130 Message: "Tappx key undefined", 131 } 132 } 133 134 tappxHost := "tappx.com" 135 isNewEndpoint, err := regexp.Match(`^(zz|vz)[0-9]{3,}([a-z]{2,3}|test)$`, []byte(params.Endpoint)) 136 if isNewEndpoint { 137 tappxHost = params.Endpoint + ".pub.tappx.com/rtb/" 138 } else { 139 tappxHost = "ssp.api.tappx.com/rtb/v2/" 140 } 141 142 endpointParams := macros.EndpointTemplateParams{Host: tappxHost} 143 host, err := macros.ResolveMacros(a.endpointTemplate, endpointParams) 144 145 if err != nil { 146 return "", &errortypes.BadInput{ 147 Message: "Unable to parse endpoint url template: " + err.Error(), 148 } 149 } 150 151 thisURI, err := url.Parse(host) 152 if err != nil { 153 return "", &errortypes.BadInput{ 154 Message: "Malformed URL: " + err.Error(), 155 } 156 } 157 158 if !isNewEndpoint { 159 thisURI.Path += params.Endpoint 160 } 161 162 queryParams := url.Values{} 163 164 queryParams.Add("tappxkey", params.TappxKey) 165 166 if test == 0 { 167 t := time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) 168 queryParams.Add("ts", strconv.Itoa(int(t))) 169 } 170 171 queryParams.Add("v", TAPPX_BIDDER_VERSION) 172 173 queryParams.Add("type_cnn", TYPE_CNN) 174 175 thisURI.RawQuery = queryParams.Encode() 176 177 return thisURI.String(), nil 178 } 179 180 func (a *TappxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 181 if response.StatusCode == http.StatusNoContent { 182 return nil, nil 183 } 184 185 if response.StatusCode == http.StatusBadRequest { 186 return nil, []error{&errortypes.BadInput{ 187 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 188 }} 189 } 190 191 if response.StatusCode != http.StatusOK { 192 return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} 193 } 194 195 var bidResp openrtb2.BidResponse 196 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 197 return nil, []error{err} 198 } 199 200 bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) 201 202 for _, sb := range bidResp.SeatBid { 203 for i := 0; i < len(sb.Bid); i++ { 204 bid := sb.Bid[i] 205 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 206 Bid: &bid, 207 BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), 208 }) 209 210 } 211 } 212 return bidResponse, []error{} 213 } 214 215 func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { 216 mediaType := openrtb_ext.BidTypeBanner 217 for _, imp := range imps { 218 if imp.ID == impId { 219 if imp.Video != nil { 220 mediaType = openrtb_ext.BidTypeVideo 221 } 222 return mediaType 223 } 224 } 225 return mediaType 226 }