Skip to content

Commit

Permalink
Initial commit from asweeney and bokelley
Browse files Browse the repository at this point in the history
  • Loading branch information
bokelley committed May 1, 2017
1 parent db9f991 commit 0d092cd
Show file tree
Hide file tree
Showing 31 changed files with 7,899 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,16 @@ _testmain.go
*.exe
*.test
*.prof

# glide directory
vendor

# binary
prebid-server

# config files
pbs.yaml
inventory_url.yaml

# autogenerated version file
# static/version.txt
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
language: go
go:
- 1.8.1
- master

install:
- glide update

script:
- ./validate.sh
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM alpine
MAINTAINER Brian O'Kelley <[email protected]>
ADD prebid-server prebid-server
ADD static/* static/
EXPOSE 8000
ENTRYPOINT ["/prebid-server"]
CMD ["-v", "1", "-logtostderr"]
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,36 @@
# prebid-server
Server side component to offload prebid processing to the cloud

# How it works
The client (typically prebid.js) sends a JSON request to Prebid Server at `/auction`. See static/pbs_request.json for the format.
Prebid Server forms OpenRTB requests, sends them to the appropriate adapters, concatenates the responses, and returns them
to the client.

A few key points:
* No ranking or decisioning is performed by Prebid Server. It just proxies requests.
* No ad quality management (malware, viruses, deceptive creatives) is performed by Prebid Server
* Prebid Server does no fraud scanning and does nothing to prevent bad traffic.

# User synching
Prebid Server provides a `/setuid` endpoint that allows adapters to push in their user IDs. These are stored in a cookie named,
creatively, `uids`. To see stored cookies, call `/getuids`. To set an optout cookie, call `/optout`. When an adapter doesn't
have a synched cookie, a `no_cookie` response is returned with a usersync URL that the client should call via asynchronous pixel
or equivalent. If Prebid Server doesn't have a cookie set, a preemptive `no_cookie` response is returned to allow the client
to ask for user consent and drop a cookie.

# Logging
Prebid Server does no server-side logging. It can stream metrics to an InfluxDB endpoint, which are aggregated as a time series.
Prebid Server has no user profiling or user-data collection capabilities.

# Data integration
Prebid Server has three primary data objects that it needs to manage:
* Accounts represent publishers, and are used for metrics aggregation and terms of service adherence. Requests without an
active account will be rejected.
* Domains are compared to the HTTP Referer header; all unknown/unapproved domains will be rejected.
* Configs are used for server-side configuration of adapters, primarily for use with mobile apps where managing configs
client-side is ineffective.

# Up Next
* Refine adapters and openrtb protocol (support burl? validation of responses?)
* Support Prebid mobile SDK
* Video and native
52 changes: 52 additions & 0 deletions adapters/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package adapters

import (
"context"
"crypto/tls"
"github.com/prebid/prebid-server/pbs"
"github.com/prebid/prebid-server/ssl"
"net/http"
"time"
)

type Adapter interface {
Name() string
FamilyName() string
GetUsersyncInfo() *pbs.UsersyncInfo
Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error)
}

type HTTPAdapterConfig struct {
IdleConnTimeout time.Duration
MaxConns int
MaxIdleConnsPerHost int
MaxConnsPerHost int
}

type HTTPAdapter struct {
Transport *http.Transport
Client *http.Client
}

var DefaultHTTPAdapterConfig = &HTTPAdapterConfig{
MaxConns: 50,
MaxConnsPerHost: 10,
MaxIdleConnsPerHost: 3,
IdleConnTimeout: 60 * time.Second,
}

func NewHTTPAdapter(c *HTTPAdapterConfig) *HTTPAdapter {
ts := &http.Transport{
MaxIdleConns: c.MaxConns,
MaxIdleConnsPerHost: c.MaxConnsPerHost,
IdleConnTimeout: c.IdleConnTimeout,
TLSClientConfig: &tls.Config{RootCAs: ssl.GetRootCAPool()},
}

return &HTTPAdapter{
Transport: ts,
Client: &http.Client{
Transport: ts,
},
}
}
155 changes: 155 additions & 0 deletions adapters/appnexus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package adapters

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/prebid/prebid-server/pbs"
"io/ioutil"
"net/http"
"net/url"

"golang.org/x/net/context/ctxhttp"

"github.com/prebid/openrtb"
)

type AppNexusAdapter struct {
http *HTTPAdapter
URI string
usersyncInfo *pbs.UsersyncInfo
}

/* Name - export adapter name */
func (a *AppNexusAdapter) Name() string {
return "AppNexus"
}

// used for cookies and such
func (a *AppNexusAdapter) FamilyName() string {
return "adnxs"
}

func (a *AppNexusAdapter) GetUsersyncInfo() *pbs.UsersyncInfo {
return a.usersyncInfo
}

type appnexusParams struct {
PlacementId string `json:"placementId"`
invCode string `json:"invCode"`
member string `json:"member"`
}

func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) {
anReq := makeOpenRTBGeneric(req, bidder, a.FamilyName())
for i, unit := range bidder.AdUnits {
var params appnexusParams
err := json.Unmarshal(unit.Params, &params)
if err != nil {
return nil, err
}
if params.PlacementId == "" {
return nil, errors.New("Missing placementId param")
}
anReq.Imp[i].TagID = params.PlacementId
// TODO: support member + invCode
}

reqJSON, err := json.Marshal(anReq)
if err != nil {
return nil, err
}

if req.IsDebug {
bidder.Debug.RequestURI = a.URI
bidder.Debug.RequestBody = string(reqJSON)
}

httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(reqJSON))
httpReq.Header.Add("Content-Type", "application/json;charset=utf-8")
httpReq.Header.Add("Accept", "application/json")

anResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq)
if err != nil {
return nil, err
}

if req.IsDebug {
bidder.Debug.StatusCode = anResp.StatusCode
}

if anResp.StatusCode == 204 {
return nil, nil
}

if anResp.StatusCode != 200 {
return nil, errors.New(fmt.Sprintf("HTTP status code %d", anResp.StatusCode))
}

defer anResp.Body.Close()
body, err := ioutil.ReadAll(anResp.Body)
if err != nil {
return nil, err
}

if req.IsDebug {
bidder.Debug.ResponseBody = string(body)
}

var bidResp openrtb.BidResponse
err = json.Unmarshal(body, &bidResp)
if err != nil {
return nil, err
}

bids := make(pbs.PBSBidSlice, 0)

numBids := 0
for _, sb := range bidResp.SeatBid {
for _, bid := range sb.Bid {
numBids++

bidID := bidder.LookupBidID(bid.ImpID)
if bidID == "" {
return nil, errors.New(fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID))
}

pbid := pbs.PBSBid{
BidID: bidID,
AdUnitCode: bid.ImpID,
BidderCode: bidder.BidderCode,
Price: bid.Price,
Adm: bid.AdM,
Creative_id: bid.CrID,
Width: bid.W,
Height: bid.H,
DealId: bid.DealID,
}
bids = append(bids, &pbid)
}
}

return bids, nil
}

func NewAppNexusAdapter(config *HTTPAdapterConfig, externalURL string) *AppNexusAdapter {
a := NewHTTPAdapter(config)

redirect_uri := fmt.Sprintf("%s/setuid?bidder=adnxs&uid=$UID", externalURL)
usersyncURL := "https://ib.adnxs.com/getuid?"

info := &pbs.UsersyncInfo{
URL: fmt.Sprintf("%s%s", usersyncURL, url.QueryEscape(redirect_uri)),
Type: "redirect",
SupportCORS: false,
}

return &AppNexusAdapter{
http: a,
// TODO: Get new endpoint from sweeney
URI: "http://ib.adnxs.com/openrtb2?member_id=958",
usersyncInfo: info,
}
}
Loading

0 comments on commit 0d092cd

Please sign in to comment.