forked from prebid/prebid-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvendorlist-fetching.go
149 lines (130 loc) · 4.86 KB
/
vendorlist-fetching.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package gdpr
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/golang/glog"
"github.com/prebid/go-gdpr/api"
"github.com/prebid/go-gdpr/vendorlist"
"github.com/prebid/go-gdpr/vendorlist2"
"github.com/prebid/prebid-server/config"
"golang.org/x/net/context/ctxhttp"
)
type saveVendors func(uint16, api.VendorList)
// This file provides the vendorlist-fetching function for Prebid Server.
//
// For more info, see https://github.com/prebid/prebid-server/issues/504
//
// Nothing in this file is exported. Public APIs can be found in gdpr.go
func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, TCFVer uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
// These save and load functions can be used to store & retrieve lists from our cache.
save, load := newVendorListCache()
withTimeout, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout())
defer cancel()
populateCache(withTimeout, client, urlMaker, save, TCFVer)
saveOneSometimes := newOccasionalSaver(cfg.Timeouts.ActiveTimeout(), TCFVer)
return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
list := load(id)
if list != nil {
return list, nil
}
saveOneSometimes(ctx, client, urlMaker(id, TCFVer), save)
list = load(id)
if list != nil {
return list, nil
}
return nil, fmt.Errorf("gdpr vendor list version %d does not exist, or has not been loaded yet. Try again in a few minutes", id)
}
}
// populateCache saves all the known versions of the vendor list for future use.
func populateCache(ctx context.Context, client *http.Client, urlMaker func(uint16, uint8) string, saver saveVendors, TCFVer uint8) {
latestVersion := saveOne(ctx, client, urlMaker(0, TCFVer), saver, TCFVer)
for i := uint16(1); i < latestVersion; i++ {
saveOne(ctx, client, urlMaker(i, TCFVer), saver, TCFVer)
}
}
// Make a URL which can be used to fetch a given version of the Global Vendor List. If the version is 0,
// this will fetch the latest version.
func vendorListURLMaker(version uint16, TCFVer uint8) string {
if TCFVer == 2 {
if version == 0 {
return "https://vendorlist.consensu.org/v2/vendor-list.json"
}
return "https://vendorlist.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(version)) + ".json"
}
if version == 0 {
return "https://vendorlist.consensu.org/vendorlist.json"
}
return "https://vendorlist.consensu.org/v-" + strconv.Itoa(int(version)) + "/vendorlist.json"
}
// newOccasionalSaver returns a wrapped version of saveOne() which only activates every few minutes.
//
// The goal here is to update quickly when new versions of the VendorList are released, but not wreck
// server performance if a bad CMP starts sending us malformed consent strings that advertize a version
// that doesn't exist yet.
func newOccasionalSaver(timeout time.Duration, TCFVer uint8) func(ctx context.Context, client *http.Client, url string, saver saveVendors) {
lastSaved := &atomic.Value{}
lastSaved.Store(time.Time{})
return func(ctx context.Context, client *http.Client, url string, saver saveVendors) {
now := time.Now()
if now.Sub(lastSaved.Load().(time.Time)).Minutes() > 10 {
withTimeout, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
saveOne(withTimeout, client, url, saver, TCFVer)
lastSaved.Store(now)
}
}
}
func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors, cTFVer uint8) uint16 {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
glog.Errorf("Failed to build GET %s request. Cookie syncs may be affected: %v", url, err)
return 0
}
resp, err := ctxhttp.Do(ctx, client, req)
if err != nil {
glog.Errorf("Error calling GET %s. Cookie syncs may be affected: %v", url, err)
return 0
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
glog.Errorf("Error reading response body from GET %s. Cookie syncs may be affected: %v", url, err)
return 0
}
if resp.StatusCode != http.StatusOK {
glog.Errorf("GET %s returned %d. Cookie syncs may be affected.", url, resp.StatusCode)
return 0
}
var newList api.VendorList
if cTFVer == 2 {
newList, err = vendorlist2.ParseEagerly(respBody)
} else {
newList, err = vendorlist.ParseEagerly(respBody)
}
if err != nil {
glog.Errorf("GET %s returned malformed JSON. Cookie syncs may be affected. Error was %v. Body was %s", url, err, string(respBody))
return 0
}
saver(newList.Version(), newList)
return newList.Version()
}
func newVendorListCache() (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) {
cache := &sync.Map{}
save = func(id uint16, list api.VendorList) {
cache.Store(id, list)
}
load = func(id uint16) api.VendorList {
list, ok := cache.Load(id)
if ok {
return list.(vendorlist.VendorList)
}
return nil
}
return
}