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  }