forked from prebid/prebid-cache
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 264d680
Showing
16 changed files
with
1,414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
vendor | ||
prebid-cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# FROM golang:onbuild | ||
FROM ubuntu:16.04 | ||
ARG DEBIAN_FRONTEND=noninteractive | ||
RUN apt-get update \ | ||
&& apt-get upgrade -y \ | ||
&& apt-get install -y | ||
|
||
RUN apt-get install --assume-yes apt-utils | ||
RUN apt-get install -y ca-certificates | ||
|
||
ADD ./prebid-cache /app/prebid-cache | ||
ADD ./config.yaml /app/ | ||
|
||
WORKDIR /app | ||
CMD ["./prebid-cache"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
all: | ||
@echo "" | ||
@echo " init: Install glide dependencies. Assumes go and glide are installed already." | ||
@echo " test: Run the unit tests and code style validation" | ||
@echo " build: Build a linux binary which runs prebid-cache" | ||
@echo " image: Build a docker which runs prebid-cache" | ||
@echo "" | ||
|
||
.PHONY: init test build image | ||
|
||
init: | ||
glide install | ||
|
||
# Validates the code for style and unit tests | ||
test: | ||
./validate.sh --nofmt | ||
|
||
# Run the tests and make a linux binary for the app. For details about this strategy, | ||
# see https://blog.codeship.com/building-minimal-docker-containers-for-go-applications/ | ||
build: test | ||
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo . | ||
|
||
# Build a docker image which runs the binary | ||
image: build | ||
docker build -t prebid-cache . | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# Prebid Cache | ||
|
||
This application stores short-term data for use in Prebid. | ||
|
||
It exists to support Video Ads from Prebid.js, as well as prebid-native | ||
|
||
## API | ||
|
||
### POST /cache | ||
|
||
Adds one or more values to the cache. Values can be given as either JSON or XML. A sample request is below. | ||
|
||
```json | ||
{ | ||
"puts": [ | ||
{ | ||
"type": "xml", | ||
"value": "<tag>Your XML content goes here.</tag>" | ||
}, | ||
{ | ||
"type": "json", | ||
"value": [1, true, "JSON value of any type can go here."] | ||
} | ||
] | ||
} | ||
``` | ||
|
||
If any of the `puts` are invalid, then it responds with a **400** none of the values will be retrievable. | ||
Assuming that all of the values are well-formed, then the server will respond with IDs which can be used to | ||
fetch the values later. | ||
|
||
```json | ||
{ | ||
"responses": [ | ||
{"uuid": "279971e4-70f0-4b18-bd65-5c6e7aa75d40"}, | ||
{"uuid": "147c9934-894b-4c1f-9a32-e7bb9cd15376"} | ||
] | ||
} | ||
``` | ||
|
||
|
||
### GET /cache?uuid={id} | ||
|
||
Retrieves a single value from the cache. If the `id` isn't recognized, then it will return a 404. | ||
|
||
Assuming the above POST calls have been made, here are some sample GET responses. | ||
|
||
--- | ||
|
||
**GET** */cache?uuid=279971e4-70f0-4b18-bd65-5c6e7aa75d40* | ||
|
||
``` | ||
HTTP/1.1 200 OK | ||
Content-Type: application/xml | ||
<tag>Your XML content goes here.</tag> | ||
``` | ||
|
||
--- | ||
|
||
**GET** */cache?uuid=147c9934-894b-4c1f-9a32-e7bb9cd15376* | ||
|
||
``` | ||
HTTP/1.1 200 OK | ||
Content-Type: application/json | ||
[1, true, "JSON value of any type can go here."] | ||
``` | ||
|
||
### Limitations | ||
|
||
This section does not describe permanent API contracts; it just describes limitations on the current implementation. | ||
|
||
- This application does *not* validate XML. If users `POST` malformed XML, they'll `GET` a bad response too. | ||
- No more than 10 values are allowed in a single POST request | ||
- Each cached value must be less than 10 KB | ||
|
||
## Development | ||
|
||
### Prerequisites | ||
|
||
[Golang](https://golang.org/doc/install) and [Glide](https://github.com/Masterminds/glide#install) must be installed on your system. | ||
|
||
### Automated tests | ||
|
||
`./validate.sh` runs the unit tests and reformats your code with [gofmt](https://golang.org/cmd/gofmt/). | ||
`./validate.sh --nofmt` runs the unit tests, but will _not_ reformat your code. | ||
|
||
### Manual testing | ||
|
||
Run `prebid-cache` locally with: | ||
|
||
```bash | ||
go build . | ||
./prebid-cache | ||
``` | ||
|
||
The service will respond to requests on `localhost:2424`, and the admin data will be available on `localhost:2525` | ||
|
||
### Profiling | ||
|
||
[pprof stats](http://artem.krylysov.com/blog/2017/03/13/profiling-and-optimizing-go-web-applications/) can be accessed from a running app on `localhost:2525` | ||
|
||
## Todo | ||
- Authorization (token based) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"crypto/hmac" | ||
"crypto/sha256" | ||
"encoding/base64" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"net" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
"time" | ||
|
||
"context" | ||
"crypto/tls" | ||
log "github.com/Sirupsen/logrus" | ||
"golang.org/x/net/context/ctxhttp" | ||
"net/http/httptrace" | ||
) | ||
|
||
type AzureValue struct { | ||
ID string `json:"id"` | ||
Value string `json:"value"` | ||
} | ||
|
||
type AzureTableBackend struct { | ||
Client *http.Client | ||
Account string | ||
Key string | ||
URI string | ||
} | ||
|
||
func NewAzureBackend(account string, key string) *AzureTableBackend { | ||
|
||
log.Debugf("New Azure Backend: Account %s Key %s", account, key) | ||
tr := &http.Transport{ | ||
MaxIdleConns: 200, | ||
MaxIdleConnsPerHost: 200, | ||
IdleConnTimeout: 60 * time.Second, | ||
DialContext: (&net.Dialer{ | ||
Timeout: 30 * time.Second, | ||
KeepAlive: 30 * time.Second, | ||
DualStack: true, | ||
}).DialContext, | ||
} | ||
|
||
c := &AzureTableBackend{ | ||
Account: account, | ||
Key: key, | ||
Client: &http.Client{ | ||
//TODO add to configMap | ||
Transport: tr, | ||
}, | ||
URI: fmt.Sprintf("https://%s.documents.azure.com", account), | ||
} | ||
|
||
log.Info("New Azure Client", account) | ||
|
||
return c | ||
} | ||
|
||
func (c *AzureTableBackend) signReq(verb, resourceType, resourceLink, date string) string { | ||
|
||
strToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n\n", | ||
strings.ToLower(verb), | ||
resourceType, | ||
resourceLink, | ||
strings.ToLower(date), | ||
) | ||
|
||
decodedKey, _ := base64.StdEncoding.DecodeString(c.Key) | ||
sha256 := hmac.New(sha256.New, []byte(decodedKey)) | ||
sha256.Write([]byte(strToSign)) | ||
|
||
signature := base64.StdEncoding.EncodeToString(sha256.Sum(nil)) | ||
u := url.QueryEscape(fmt.Sprintf("type=master&ver=1.0&sig=%s", signature)) | ||
|
||
return u | ||
} | ||
|
||
func formattedRequestTime() string { | ||
t := time.Now().UTC() | ||
return t.Format("Mon, 02 Jan 2006 15:04:05 GMT") | ||
} | ||
|
||
func (c *AzureTableBackend) Send(ctx context.Context, req *http.Request, resourceType string, resourceId string) (*http.Response, error) { | ||
date := formattedRequestTime() | ||
req.Header.Add("x-ms-date", date) | ||
req.Header.Add("x-ms-version", "2017-01-19") | ||
req.Header.Add("Authorization", c.signReq(req.Method, resourceType, resourceId, date)) | ||
|
||
ctx = httptrace.WithClientTrace(ctx, newHttpTracer()) | ||
|
||
resp, err := ctxhttp.Do(ctx, c.Client, req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return resp, err | ||
} | ||
|
||
func (c *AzureTableBackend) Do(ctx context.Context, method string, resourceLink string, resourceType string, resourceId string, body io.Reader) (*http.Response, error) { | ||
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", c.URI, resourceLink), body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return c.Send(ctx, req, resourceType, resourceId) | ||
} | ||
|
||
func (c *AzureTableBackend) Get(ctx context.Context, key string) (string, error) { | ||
|
||
if key == "" { | ||
return "", fmt.Errorf("Invalid Key") | ||
} | ||
|
||
// Full key for the stupid gets | ||
resourceLink := fmt.Sprintf("/dbs/prebidcache/colls/cache/docs/%s", key) | ||
resp, err := c.Do(ctx, "GET", resourceLink, "docs", resourceLink[1:], nil) | ||
if err != nil { | ||
log.Debugf("Failed to make request") | ||
return "", err | ||
} | ||
defer resp.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
log.Debugf("Failed to read the request body") | ||
return "", err | ||
} | ||
|
||
av := AzureValue{} | ||
err = json.Unmarshal(body, &av) | ||
if err != nil { | ||
log.Debugf("Failed to decode request body into JSON") | ||
return "", err | ||
} | ||
|
||
if av.Value == "" { | ||
return "", fmt.Errorf("Key not found") | ||
} | ||
|
||
return av.Value, nil | ||
} | ||
|
||
func (c *AzureTableBackend) Put(ctx context.Context, key string, value string) error { | ||
|
||
if key == "" { | ||
return fmt.Errorf("Invalid Key") | ||
} | ||
|
||
if value == "" { | ||
return fmt.Errorf("Invalid Value") | ||
} | ||
|
||
av := AzureValue{ | ||
ID: key, | ||
Value: value, | ||
} | ||
|
||
b, err := json.Marshal(&av) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
resourceLink := "/dbs/prebidcache/colls/cache/docs" | ||
resp, err := c.Do(ctx, "POST", resourceLink, "docs", "dbs/prebidcache/colls/cache", bytes.NewBuffer(b)) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
// Read the whole body so that the Transport knows it's safe to reuse the connection. | ||
// See the docs on http.Response.Body | ||
ioutil.ReadAll(resp.Body) | ||
return nil | ||
} | ||
|
||
func newHttpTracer() *httptrace.ClientTrace { | ||
return &httptrace.ClientTrace{ | ||
PutIdleConn: func(err error) { | ||
if err != nil { | ||
log.Infof("Failed adding idle connection to the pool: %v", err.Error()) | ||
} | ||
}, | ||
|
||
ConnectDone: func(network, addr string, err error) { | ||
if err != nil { | ||
log.Warnf("Failed to connect. Network: %s, Addr: %s, err: %v", network, addr, err) | ||
} | ||
}, | ||
|
||
DNSDone: func(info httptrace.DNSDoneInfo) { | ||
if info.Err != nil { | ||
log.Warnf("Failed DNS lookup: %v", info.Err) | ||
} | ||
}, | ||
|
||
TLSHandshakeDone: func(state tls.ConnectionState, err error) { | ||
if err != nil { | ||
log.Warnf("Failed TLS Handshake: %v", err) | ||
} | ||
}, | ||
|
||
WroteRequest: func(info httptrace.WroteRequestInfo) { | ||
if info.Err != nil { | ||
log.Warnf("Failed to write request: %v", info.Err) | ||
} | ||
}, | ||
} | ||
} |
Oops, something went wrong.