github.com/prebid/prebid-server/v2@v2.18.0/prebid_cache_client/client.go (about) 1 package prebid_cache_client 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/prebid/prebid-server/v2/config" 16 "github.com/prebid/prebid-server/v2/metrics" 17 18 "github.com/buger/jsonparser" 19 "github.com/golang/glog" 20 "golang.org/x/net/context/ctxhttp" 21 ) 22 23 // Client stores values in Prebid Cache. For more info, see https://github.com/prebid/prebid-cache 24 type Client interface { 25 // PutJson stores JSON values for the given openrtb2.Bids in the cache. Null values will be 26 // 27 // The returned string slice will always have the same number of elements as the values argument. If a 28 // value could not be saved, the element will be an empty string. Implementations are responsible for 29 // logging any relevant errors to the app logs 30 PutJson(ctx context.Context, values []Cacheable) ([]string, []error) 31 32 // GetExtCacheData gets the scheme, host, and path of the externally accessible cache url. 33 GetExtCacheData() (scheme string, host string, path string) 34 } 35 36 type PayloadType string 37 38 const ( 39 TypeJSON PayloadType = "json" 40 TypeXML PayloadType = "xml" 41 ) 42 43 type Cacheable struct { 44 Type PayloadType `json:"type,omitempty"` 45 Data json.RawMessage `json:"value,omitempty"` 46 TTLSeconds int64 `json:"ttlseconds,omitempty"` 47 Key string `json:"key,omitempty"` 48 49 BidID string `json:"bidid,omitempty"` // this is "/vtrack" specific 50 Bidder string `json:"bidder,omitempty"` // this is "/vtrack" specific 51 Timestamp int64 `json:"timestamp,omitempty"` // this is "/vtrack" specific 52 } 53 54 func NewClient(httpClient *http.Client, conf *config.Cache, extCache *config.ExternalCache, metrics metrics.MetricsEngine) Client { 55 return &clientImpl{ 56 httpClient: httpClient, 57 putUrl: conf.GetBaseURL() + "/cache", 58 externalCacheScheme: extCache.Scheme, 59 externalCacheHost: extCache.Host, 60 externalCachePath: extCache.Path, 61 metrics: metrics, 62 } 63 } 64 65 type clientImpl struct { 66 httpClient *http.Client 67 putUrl string 68 externalCacheScheme string 69 externalCacheHost string 70 externalCachePath string 71 metrics metrics.MetricsEngine 72 } 73 74 func (c *clientImpl) GetExtCacheData() (string, string, string) { 75 path := c.externalCachePath 76 if path == "/" { 77 // Only the slash for the path, remove it to empty 78 path = "" 79 } else if len(path) > 0 && !strings.HasPrefix(path, "/") { 80 // Path defined but does not start with "/", prepend it 81 path = "/" + path 82 } 83 84 return c.externalCacheScheme, c.externalCacheHost, path 85 } 86 87 func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []string, errs []error) { 88 errs = make([]error, 0, 1) 89 if len(values) < 1 { 90 return nil, errs 91 } 92 93 uuidsToReturn := make([]string, len(values)) 94 95 postBody, err := encodeValues(values) 96 if err != nil { 97 logError(&errs, "Error creating JSON for prebid cache: %v", err) 98 return uuidsToReturn, errs 99 } 100 101 httpReq, err := http.NewRequest("POST", c.putUrl, bytes.NewReader(postBody)) 102 if err != nil { 103 logError(&errs, "Error creating POST request to prebid cache: %v", err) 104 return uuidsToReturn, errs 105 } 106 107 httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") 108 httpReq.Header.Add("Accept", "application/json") 109 110 startTime := time.Now() 111 anResp, err := ctxhttp.Do(ctx, c.httpClient, httpReq) 112 elapsedTime := time.Since(startTime) 113 if err != nil { 114 c.metrics.RecordPrebidCacheRequestTime(false, elapsedTime) 115 logError(&errs, "Error sending the request to Prebid Cache: %v; Duration=%v, Items=%v, Payload Size=%v", err, elapsedTime, len(values), len(postBody)) 116 return uuidsToReturn, errs 117 } 118 defer anResp.Body.Close() 119 c.metrics.RecordPrebidCacheRequestTime(true, elapsedTime) 120 121 responseBody, err := io.ReadAll(anResp.Body) 122 if anResp.StatusCode != 200 { 123 logError(&errs, "Prebid Cache call to %s returned %d: %s", c.putUrl, anResp.StatusCode, responseBody) 124 return uuidsToReturn, errs 125 } 126 127 currentIndex := 0 128 processResponse := func(uuidObj []byte, _ jsonparser.ValueType, _ int, err error) { 129 if uuid, valueType, _, err := jsonparser.Get(uuidObj, "uuid"); err != nil { 130 logError(&errs, "Prebid Cache returned a bad value at index %d. Error was: %v. Response body was: %s", currentIndex, err, string(responseBody)) 131 } else if valueType != jsonparser.String { 132 logError(&errs, "Prebid Cache returned a %v at index %d in: %v", valueType, currentIndex, string(responseBody)) 133 } else { 134 if uuidsToReturn[currentIndex], err = jsonparser.ParseString(uuid); err != nil { 135 logError(&errs, "Prebid Cache response index %d could not be parsed as string: %v", currentIndex, err) 136 uuidsToReturn[currentIndex] = "" 137 } 138 } 139 currentIndex++ 140 } 141 142 if _, err := jsonparser.ArrayEach(responseBody, processResponse, "responses"); err != nil { 143 logError(&errs, "Error interpreting Prebid Cache response: %v\nResponse was: %s", err, string(responseBody)) 144 return uuidsToReturn, errs 145 } 146 147 return uuidsToReturn, errs 148 } 149 150 func logError(errs *[]error, format string, a ...interface{}) { 151 msg := fmt.Sprintf(format, a...) 152 glog.Error(msg) 153 *errs = append(*errs, errors.New(msg)) 154 } 155 156 func encodeValues(values []Cacheable) ([]byte, error) { 157 var buf bytes.Buffer 158 buf.WriteString(`{"puts":[`) 159 for i := 0; i < len(values); i++ { 160 if err := encodeValueToBuffer(values[i], i != 0, &buf); err != nil { 161 return nil, err 162 } 163 } 164 buf.WriteString("]}") 165 return buf.Bytes(), nil 166 } 167 168 func encodeValueToBuffer(value Cacheable, leadingComma bool, buffer *bytes.Buffer) error { 169 if leadingComma { 170 buffer.WriteByte(',') 171 } 172 173 buffer.WriteString(`{"type":"`) 174 buffer.WriteString(string(value.Type)) 175 if value.TTLSeconds > 0 { 176 buffer.WriteString(`","ttlseconds":`) 177 buffer.WriteString(strconv.FormatInt(value.TTLSeconds, 10)) 178 buffer.WriteString(`,"value":`) 179 } else { 180 buffer.WriteString(`","value":`) 181 } 182 buffer.Write(value.Data) 183 if len(value.Key) > 0 { 184 buffer.WriteString(`,"key":"`) 185 buffer.WriteString(string(value.Key)) 186 buffer.WriteString(`"`) 187 } 188 189 //vtrack specific 190 if len(value.BidID) > 0 { 191 buffer.WriteString(`,"bidid":"`) 192 buffer.WriteString(string(value.BidID)) 193 buffer.WriteString(`"`) 194 } 195 196 if len(value.Bidder) > 0 { 197 buffer.WriteString(`,"bidder":"`) 198 buffer.WriteString(string(value.Bidder)) 199 buffer.WriteString(`"`) 200 } 201 202 if value.Timestamp > 0 { 203 buffer.WriteString(`,"timestamp":`) 204 buffer.WriteString(strconv.FormatInt(value.Timestamp, 10)) 205 } 206 207 buffer.WriteByte('}') 208 return nil 209 }