Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smarty ads adapter #1500

Merged
merged 12 commits into from
Oct 1, 2020
51 changes: 51 additions & 0 deletions adapters/smartyads/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package smartyads

import (
"encoding/json"
"testing"

"github.com/prebid/prebid-server/openrtb_ext"
)

var validParams = []string{
`{ "host": "smartyadstest.com", "sourceid": "partner", "accountid": "hash" }`,
}

func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, validParam := range validParams {
if err := validator.Validate(openrtb_ext.BidderSmartyAds, json.RawMessage(validParam)); err != nil {
t.Errorf("Schema rejected SmartyAds params: %s", validParam)
}
}
}

var invalidParams = []string{
``,
`null`,
`true`,
`5`,
`4.2`,
`[]`,
`{}`,
`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
`{ "host": "smartyadstest.com", "sourceid": "partner" }`,
`{ "host": "smartyadstest.com", "accountid": "hash" }`,
}

func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, invalidParam := range invalidParams {
if err := validator.Validate(openrtb_ext.BidderSmartyAds, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
}
209 changes: 209 additions & 0 deletions adapters/smartyads/smartyads.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package smartyads

import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"text/template"

"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/macros"
"github.com/prebid/prebid-server/openrtb_ext"
)

// Implements Bidder interface.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the Go standard for comments, such as SmartyAdsAdapter ... or remove the comment entirely.

type SmartyAdsAdapter struct {
endpoint template.Template
}

func NewSmartyAdsBidder(endpointTemplate string) *SmartyAdsAdapter {
template, err := template.New("endpointTemplate").Parse(endpointTemplate)
if err != nil {
return nil
}
return &SmartyAdsAdapter{endpoint: *template}
}

func (a *SmartyAdsAdapter) CheckHasImps(request *openrtb.BidRequest) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check isn't necessary. The "core" code will not call an adapter if there are no imps.

if len(request.Imp) == 0 {
err := &errortypes.BadInput{
Message: "Missing Imp Object",
}
return err
}
return nil
}

func GetHeaders(request *openrtb.BidRequest) *http.Header {
headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("X-Openrtb-Version", "2.5")

if request.Device != nil {
if len(request.Device.UA) > 0 {
headers.Add("User-Agent", request.Device.UA)
}

if len(request.Device.IP) > 0 {
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
headers.Add("X-Forwarded-For", request.Device.IP)
}

if len(request.Device.Language) > 0 {
headers.Add("Accept-Language", request.Device.Language)
}

if request.Device.DNT != nil {
headers.Add("Dnt", strconv.Itoa(int(*request.Device.DNT)))
}
}

return &headers
}

func (a *SmartyAdsAdapter) MakeRequests(
openRTBRequest *openrtb.BidRequest,
reqInfo *adapters.ExtraRequestInfo,
) (
requestsToBidder []*adapters.RequestData,
errs []error,
) {

request := *openRTBRequest
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request that is passed to MakeRequests() is already a shallow copy of the original request. Additionally the imps in the request are shallow copies of the original imps. So I do not believe you need to make another copy here.


if noImps := a.CheckHasImps(&request); noImps != nil {
return nil, []error{noImps}
}

var errors []error
var smartyadsExt *openrtb_ext.ExtSmartyAds
var err error

for i, imp := range request.Imp {
smartyadsExt, err = a.getImpressionExt(&imp)
if err != nil {
errors = append(errors, err)
break
}
request.Imp[i].Ext = nil
}

if len(errors) > 0 {
return nil, errors
}

url, err := a.buildEndpointURL(smartyadsExt)
if err != nil {
return nil, []error{err}
}

reqJSON, err := json.Marshal(request)
if err != nil {
return nil, []error{err}
}

return []*adapters.RequestData{{
Method: http.MethodPost,
Body: reqJSON,
Uri: url,
Headers: *GetHeaders(&request),
}}, nil
}

func (a *SmartyAdsAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtSmartyAds, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: "ext.bidder not provided",
}
}
var smartyadsExt openrtb_ext.ExtSmartyAds
if err := json.Unmarshal(bidderExt.Bidder, &smartyadsExt); err != nil {
return nil, &errortypes.BadInput{
Message: "ext.bidder not provided",
}
}
return &smartyadsExt, nil
}

func (a *SmartyAdsAdapter) buildEndpointURL(params *openrtb_ext.ExtSmartyAds) (string, error) {
endpointParams := macros.EndpointTemplateParams{Host: params.Host, SourceId: params.SourceId, AccountID: params.AccountID}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adapters may not allow the publisher to specify the entire endpoint domain. I can offer more specific suggestions if you could please give us more context on why a publisher would specify the host.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! @SyntaxNode , thanks for you review! Our company has different ns for different regions such as (US_EAST, EU, SGP) . We need to use for example endpoints ns1.example.com for EU and ns2.example.com for SGP. That is main reason when I use host variable.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the region not be controlled by the PBS host (as in adapter config) rather than publisher, as the adapter host would control which endpoint is "closest"? Or if the region specific requirement is driven by which advertisers are active on an endpoint (east coat advertisers just want to bid on requests coming to the US_EAST node), then from your example have host = "ns1" and hardcode ".example.com" into your adapter's URL.

The reasoning behind this is we don't want PBS to be used as an open relay. For example, with an openended host setting, someone could have "host": "www.disney.com" and then flood PBS installs with requests which would then be forwarded to www.disney.com to try to overload their website. Given that Disney will see all the requests coming from PBS hosts rather than the original attacker, it will be most difficult or impossible to find the one responsible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hhhjort , thanks for your answer ! I will change host to hardcode ".example.com" domain with ns1 as parameter! Thank you for clarification!

return macros.ResolveMacros(a.endpoint, endpointParams)
}

func (a *SmartyAdsAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error {
if response.StatusCode == http.StatusNoContent {
return &errortypes.BadInput{Message: "No bid response"}
}

if response.StatusCode == http.StatusBadRequest {
return &errortypes.BadInput{
Message: fmt.Sprintf("Unexpected status code: [ %d ]", response.StatusCode),
}
}

if response.StatusCode == http.StatusServiceUnavailable {
return &errortypes.BadInput{
Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode),
}
}

if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
return &errortypes.BadInput{
Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode),
}
}

return nil
}

func (a *SmartyAdsAdapter) MakeBids(
openRTBRequest *openrtb.BidRequest,
requestToBidder *adapters.RequestData,
bidderRawResponse *adapters.ResponseData,
) (
bidderResponse *adapters.BidderResponse,
errs []error,
) {
httpStatusError := a.CheckResponseStatusCodes(bidderRawResponse)
if httpStatusError != nil {
return nil, []error{httpStatusError}
}

responseBody := bidderRawResponse.Body
var bidResp openrtb.BidResponse
if err := json.Unmarshal(responseBody, &bidResp); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: "Bad Server Response",
}}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
sb := bidResp.SeatBid[0]

for _, bid := range sb.Bid {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: getMediaTypeForImp(bid.ImpID, openRTBRequest.Imp),
})
}
return bidResponse, nil
}

func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType {
mediaType := openrtb_ext.BidTypeBanner
for _, imp := range imps {
if imp.ID == impId {
if imp.Video != nil {
mediaType = openrtb_ext.BidTypeVideo
} else if imp.Native != nil {
mediaType = openrtb_ext.BidTypeNative
}
return mediaType
}
}
return mediaType
}
11 changes: 11 additions & 0 deletions adapters/smartyads/smartyads_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package smartyads

import (
"testing"

"github.com/prebid/prebid-server/adapters/adapterstest"
)

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "smartyadstest", NewSmartyAdsBidder("http://{{.Host}}/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}"))
}
Loading