github.com/prebid/prebid-server/v2@v2.18.0/adapters/beintoo/beintoo.go (about)

     1  package beintoo
     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/prebid-server/v2/adapters"
    12  	"github.com/prebid/prebid-server/v2/config"
    13  	"github.com/prebid/prebid-server/v2/errortypes"
    14  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    15  )
    16  
    17  type BeintooAdapter struct {
    18  	endpoint string
    19  }
    20  
    21  func (a *BeintooAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    22  	var errors []error
    23  
    24  	if len(request.Imp) == 0 {
    25  		return nil, []error{&errortypes.BadInput{
    26  			Message: fmt.Sprintf("No Imps in Bid Request"),
    27  		}}
    28  	}
    29  
    30  	if errors := preprocess(request); len(errors) > 0 {
    31  		return nil, append(errors, &errortypes.BadInput{
    32  			Message: fmt.Sprintf("Error in preprocess of Imp, err: %s", errors),
    33  		})
    34  	}
    35  
    36  	data, err := json.Marshal(request)
    37  	if err != nil {
    38  		return nil, []error{&errortypes.BadInput{
    39  			Message: fmt.Sprintf("Error in packaging request to JSON"),
    40  		}}
    41  	}
    42  
    43  	headers := http.Header{}
    44  	headers.Add("Content-Type", "application/json;charset=utf-8")
    45  	headers.Add("Accept", "application/json")
    46  
    47  	if request.Device != nil {
    48  		addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA)
    49  		addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP)
    50  		addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language)
    51  		if request.Device.DNT != nil {
    52  			addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(*request.Device.DNT)))
    53  		}
    54  	}
    55  	if request.Site != nil {
    56  		addHeaderIfNonEmpty(headers, "Referer", request.Site.Page)
    57  	}
    58  
    59  	return []*adapters.RequestData{{
    60  		Method:  "POST",
    61  		Uri:     a.endpoint,
    62  		Body:    data,
    63  		Headers: headers,
    64  		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
    65  	}}, errors
    66  }
    67  
    68  func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpBeintoo, error) {
    69  	var bidderExt adapters.ExtImpBidder
    70  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
    71  		return nil, &errortypes.BadInput{
    72  			Message: err.Error(),
    73  		}
    74  	}
    75  
    76  	var beintooExt openrtb_ext.ExtImpBeintoo
    77  	if err := json.Unmarshal(bidderExt.Bidder, &beintooExt); err != nil {
    78  		return nil, &errortypes.BadInput{
    79  			Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID),
    80  		}
    81  	}
    82  
    83  	tagIDValidation, err := strconv.ParseInt(beintooExt.TagID, 10, 64)
    84  	if err != nil || tagIDValidation == 0 {
    85  		return nil, &errortypes.BadInput{
    86  			Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID),
    87  		}
    88  	}
    89  
    90  	return &beintooExt, nil
    91  }
    92  
    93  func buildImpBanner(imp *openrtb2.Imp) error {
    94  	imp.Ext = nil
    95  
    96  	if imp.Banner == nil {
    97  		return &errortypes.BadInput{
    98  			Message: fmt.Sprintf("Request needs to include a Banner object"),
    99  		}
   100  	}
   101  
   102  	bannerCopy := *imp.Banner
   103  	banner := &bannerCopy
   104  
   105  	if banner.W == nil && banner.H == nil {
   106  		if len(banner.Format) == 0 {
   107  			return &errortypes.BadInput{
   108  				Message: fmt.Sprintf("Need at least one size to build request"),
   109  			}
   110  		}
   111  		format := banner.Format[0]
   112  		banner.Format = banner.Format[1:]
   113  		banner.W = &format.W
   114  		banner.H = &format.H
   115  		imp.Banner = banner
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // Add Beintoo required properties to Imp object
   122  func addImpProps(imp *openrtb2.Imp, secure *int8, BeintooExt *openrtb_ext.ExtImpBeintoo) {
   123  	imp.TagID = BeintooExt.TagID
   124  	imp.Secure = secure
   125  
   126  	if BeintooExt.BidFloor != "" {
   127  		bidFloor, err := strconv.ParseFloat(BeintooExt.BidFloor, 64)
   128  		if err != nil {
   129  			bidFloor = 0
   130  		}
   131  
   132  		if bidFloor > 0 {
   133  			imp.BidFloor = bidFloor
   134  		}
   135  	}
   136  }
   137  
   138  // Adding header fields to request header
   139  func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) {
   140  	if len(headerValue) > 0 {
   141  		headers.Add(headerName, headerValue)
   142  	}
   143  }
   144  
   145  // Handle request errors and formatting to be sent to Beintoo
   146  func preprocess(request *openrtb2.BidRequest) []error {
   147  	errors := make([]error, 0, len(request.Imp))
   148  	resImps := make([]openrtb2.Imp, 0, len(request.Imp))
   149  	secure := int8(0)
   150  
   151  	if request.Site != nil && request.Site.Page != "" {
   152  		pageURL, err := url.Parse(request.Site.Page)
   153  		if err == nil && pageURL.Scheme == "https" {
   154  			secure = int8(1)
   155  		}
   156  	}
   157  
   158  	for _, imp := range request.Imp {
   159  		beintooExt, err := unpackImpExt(&imp)
   160  		if err != nil {
   161  			errors = append(errors, err)
   162  			return errors
   163  		}
   164  
   165  		addImpProps(&imp, &secure, beintooExt)
   166  
   167  		if err := buildImpBanner(&imp); err != nil {
   168  			errors = append(errors, err)
   169  			return errors
   170  		}
   171  		resImps = append(resImps, imp)
   172  	}
   173  
   174  	request.Imp = resImps
   175  
   176  	return errors
   177  }
   178  
   179  // MakeBids make the bids for the bid response.
   180  func (a *BeintooAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   181  
   182  	if response.StatusCode == http.StatusNoContent {
   183  		// no bid response
   184  		return nil, nil
   185  	}
   186  
   187  	if response.StatusCode != http.StatusOK {
   188  		return nil, []error{&errortypes.BadServerResponse{
   189  			Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode),
   190  		}}
   191  	}
   192  
   193  	var bidResp openrtb2.BidResponse
   194  
   195  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   196  		return nil, []error{&errortypes.BadServerResponse{
   197  			Message: fmt.Sprintf("Unable to unpackage bid response. Error: %s", err.Error()),
   198  		}}
   199  	}
   200  
   201  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   202  
   203  	for _, sb := range bidResp.SeatBid {
   204  		for i := range sb.Bid {
   205  			sb.Bid[i].ImpID = sb.Bid[i].ID
   206  
   207  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   208  				Bid:     &sb.Bid[i],
   209  				BidType: "banner",
   210  			})
   211  		}
   212  	}
   213  
   214  	return bidResponse, nil
   215  
   216  }
   217  
   218  // Builder builds a new instance of the Beintoo adapter for the given bidder with the given config.
   219  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   220  	bidder := &BeintooAdapter{
   221  		endpoint: config.Endpoint,
   222  	}
   223  	return bidder, nil
   224  }