github.com/prebid/prebid-server@v0.275.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/v19/openrtb2"
    11  	"github.com/prebid/prebid-server/adapters"
    12  	"github.com/prebid/prebid-server/config"
    13  	"github.com/prebid/prebid-server/errortypes"
    14  	"github.com/prebid/prebid-server/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); errors != nil && 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  	}}, errors
    65  }
    66  
    67  func unpackImpExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpBeintoo, error) {
    68  	var bidderExt adapters.ExtImpBidder
    69  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
    70  		return nil, &errortypes.BadInput{
    71  			Message: err.Error(),
    72  		}
    73  	}
    74  
    75  	var beintooExt openrtb_ext.ExtImpBeintoo
    76  	if err := json.Unmarshal(bidderExt.Bidder, &beintooExt); err != nil {
    77  		return nil, &errortypes.BadInput{
    78  			Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID),
    79  		}
    80  	}
    81  
    82  	tagIDValidation, err := strconv.ParseInt(beintooExt.TagID, 10, 64)
    83  	if err != nil || tagIDValidation == 0 {
    84  		return nil, &errortypes.BadInput{
    85  			Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID),
    86  		}
    87  	}
    88  
    89  	return &beintooExt, nil
    90  }
    91  
    92  func buildImpBanner(imp *openrtb2.Imp) error {
    93  	imp.Ext = nil
    94  
    95  	if imp.Banner == nil {
    96  		return &errortypes.BadInput{
    97  			Message: fmt.Sprintf("Request needs to include a Banner object"),
    98  		}
    99  	}
   100  
   101  	bannerCopy := *imp.Banner
   102  	banner := &bannerCopy
   103  
   104  	if banner.W == nil && banner.H == nil {
   105  		if len(banner.Format) == 0 {
   106  			return &errortypes.BadInput{
   107  				Message: fmt.Sprintf("Need at least one size to build request"),
   108  			}
   109  		}
   110  		format := banner.Format[0]
   111  		banner.Format = banner.Format[1:]
   112  		banner.W = &format.W
   113  		banner.H = &format.H
   114  		imp.Banner = banner
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  // Add Beintoo required properties to Imp object
   121  func addImpProps(imp *openrtb2.Imp, secure *int8, BeintooExt *openrtb_ext.ExtImpBeintoo) {
   122  	imp.TagID = BeintooExt.TagID
   123  	imp.Secure = secure
   124  
   125  	if BeintooExt.BidFloor != "" {
   126  		bidFloor, err := strconv.ParseFloat(BeintooExt.BidFloor, 64)
   127  		if err != nil {
   128  			bidFloor = 0
   129  		}
   130  
   131  		if bidFloor > 0 {
   132  			imp.BidFloor = bidFloor
   133  		}
   134  	}
   135  
   136  	return
   137  }
   138  
   139  // Adding header fields to request header
   140  func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) {
   141  	if len(headerValue) > 0 {
   142  		headers.Add(headerName, headerValue)
   143  	}
   144  }
   145  
   146  // Handle request errors and formatting to be sent to Beintoo
   147  func preprocess(request *openrtb2.BidRequest) []error {
   148  	errors := make([]error, 0, len(request.Imp))
   149  	resImps := make([]openrtb2.Imp, 0, len(request.Imp))
   150  	secure := int8(0)
   151  
   152  	if request.Site != nil && request.Site.Page != "" {
   153  		pageURL, err := url.Parse(request.Site.Page)
   154  		if err == nil && pageURL.Scheme == "https" {
   155  			secure = int8(1)
   156  		}
   157  	}
   158  
   159  	for _, imp := range request.Imp {
   160  		beintooExt, err := unpackImpExt(&imp)
   161  		if err != nil {
   162  			errors = append(errors, err)
   163  			return errors
   164  		}
   165  
   166  		addImpProps(&imp, &secure, beintooExt)
   167  
   168  		if err := buildImpBanner(&imp); err != nil {
   169  			errors = append(errors, err)
   170  			return errors
   171  		}
   172  		resImps = append(resImps, imp)
   173  	}
   174  
   175  	request.Imp = resImps
   176  
   177  	return errors
   178  }
   179  
   180  // MakeBids make the bids for the bid response.
   181  func (a *BeintooAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   182  
   183  	if response.StatusCode == http.StatusNoContent {
   184  		// no bid response
   185  		return nil, nil
   186  	}
   187  
   188  	if response.StatusCode != http.StatusOK {
   189  		return nil, []error{&errortypes.BadServerResponse{
   190  			Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode),
   191  		}}
   192  	}
   193  
   194  	var bidResp openrtb2.BidResponse
   195  
   196  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   197  		return nil, []error{&errortypes.BadServerResponse{
   198  			Message: fmt.Sprintf("Unable to unpackage bid response. Error: %s", err.Error()),
   199  		}}
   200  	}
   201  
   202  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   203  
   204  	for _, sb := range bidResp.SeatBid {
   205  		for i := range sb.Bid {
   206  			sb.Bid[i].ImpID = sb.Bid[i].ID
   207  
   208  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   209  				Bid:     &sb.Bid[i],
   210  				BidType: "banner",
   211  			})
   212  		}
   213  	}
   214  
   215  	return bidResponse, nil
   216  
   217  }
   218  
   219  // Builder builds a new instance of the Beintoo adapter for the given bidder with the given config.
   220  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   221  	bidder := &BeintooAdapter{
   222  		endpoint: config.Endpoint,
   223  	}
   224  	return bidder, nil
   225  }