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