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

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