* optimize logging

* add gitlab actions
* add make support
* configure docker tasks
This commit is contained in:
Daniel Thomas 2021-10-05 12:25:03 +02:00
parent 23e1432203
commit f3b25c2cbf
18 changed files with 367 additions and 308 deletions

31
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: CI
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
env:
GOFLAGS: -mod=readonly
GOPROXY: https://proxy.golang.org
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Unshallow
run: git fetch --prune --unshallow
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
- name: Test
run: go test ./...

36
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: goreleaser
on:
push:
tags:
- v*.*.*
jobs:
goreleaser:
runs-on: ubuntu-latest
env:
GOFLAGS: -mod=readonly
GOPROXY: https://proxy.golang.org
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Unshallow
run: git fetch --prune --unshallow
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }}

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
.*
!.git*
bin/contentfulproxy

View File

@ -1,30 +1,38 @@
##############################
###### STAGE: BUILD ######
##############################
FROM golang:1.14-alpine AS build-env
FROM golang:1.17-alpine AS build-env
ENV GO111MODULE=on
RUN apk add --no-cache upx
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go mod download
COPY ./ ./
RUN go mod download && go mod vendor
RUN GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -trimpath -o /contentfulproxy
RUN GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -ldflags "-w -s" -trimpath -o ./bin/contentfulproxy cmd/contentfulproxy/main.go
ENV UPX="-1"
RUN upx /src/bin/contentfulproxy
##############################
###### STAGE: PACKAGE ######
##############################
FROM alpine:3.11
FROM alpine:latest
ENV CONTENTFULPROXY_SERVER_ADDR=0.0.0.0:80
ENV LOG_JSON=1
RUN apk add --update --no-cache ca-certificates curl bash && rm -rf /var/cache/apk/*
RUN apk add --update --no-cache ca-certificates
COPY --from=build-env /contentfulproxy /usr/sbin/contentfulproxy
ENTRYPOINT ["/usr/sbin/contentfulproxy"]
CMD ["-webserver-address=$CONTENTFULPROXY_SERVER_ADDR"]
COPY --from=build-env /src/bin/contentfulproxy /usr/sbin/contentfulproxy
EXPOSE 80
# Zap
@ -33,3 +41,7 @@ EXPOSE 9100
EXPOSE 9200
# Viper
EXPOSE 9300
ENTRYPOINT ["/usr/sbin/contentfulproxy"]
CMD ["-webserver-address=$CONTENTFULPROXY_SERVER_ADDR"]

114
Makefile
View File

@ -1,47 +1,87 @@
SHELL := /bin/bash
.DEFAULT_GOAL:=help
TAG?=latest
IMAGE=foomo/contentfulproxy
#IMAGE=foomo/contentfulproxy
IMAGE=docker-registry.bestbytes.net/galeria/site/contentfulproxy
# https://hub.docker.com/repository/docker/foomo/contentfulproxy
# Utils
## === Tasks ===
all: build test
tag:
echo $(TAG)
dep:
env GO111MODULE=on go mod download && env GO111MODULE=on go mod vendor && go install -i ./vendor/...
.PHONY: clean
## Clean build
clean:
rm -fv bin/contentfulprox*
# Build
build: clean
go build -o bin/contentfulproxy
build-arch: clean
GOOS=linux GOARCH=amd64 go build -o bin/contentfulproxy-linux-amd64
GOOS=darwin GOARCH=amd64 go build -o bin/contentfulproxy-darwin-amd64
build-docker: clean build-arch
curl https://curl.haxx.se/ca/cacert.pem > .cacert.pem
docker build -q . > .image_id
docker tag `cat .image_id` $(IMAGE):$(TAG)
echo "# tagged container `cat .image_id` as $(IMAGE):$(TAG)"
rm -vf .image_id .cacert.pem
package: build
pkg/build.sh
# Docker
docker-build:
docker build -t $(IMAGE):$(TAG) .
docker-push:
docker push $(IMAGE):$(TAG)
# Testing / benchmarks
.PHONY: test
## Run tests
test:
go test -v ./...
bench:
go test -run=none -bench=. ./...
.PHONY: lint
## Run linter
lint:
golangci-lint run
.PHONY: lint.fix
## Fix lint violations
lint.fix:
golangci-lint run --fix
## === Binary ===
.PHONY: build
## Build binary
build: clean
go build -o bin/contentfulproxy cmd/contentfulproxy/main.go
.PHONY: build.arch
## Build arch binaries
build.arch: clean
GOOS=linux GOARCH=amd64 go build -o bin/contentfulproxy-linux-amd64 cmd/contentfulproxy
GOOS=darwin GOARCH=amd64 go build -o bin/contentfulproxy-darwin-amd64 cmd/contentfulproxy
## === Docker ===
.PHONY: docker.build
## Build docker image
docker.build:
docker build -t $(IMAGE):$(TAG) .
.PHONY: docker.push
## Push docker image
docker.push:
docker push $(IMAGE):$(TAG)
## === Utils ===
## Show help text
help:
@awk '{ \
if ($$0 ~ /^.PHONY: [a-zA-Z\-\_0-9]+$$/) { \
helpCommand = substr($$0, index($$0, ":") + 2); \
if (helpMessage) { \
printf "\033[36m%-23s\033[0m %s\n", \
helpCommand, helpMessage; \
helpMessage = ""; \
} \
} else if ($$0 ~ /^[a-zA-Z\-\_0-9.]+:/) { \
helpCommand = substr($$0, 0, index($$0, ":")); \
if (helpMessage) { \
printf "\033[36m%-23s\033[0m %s\n", \
helpCommand, helpMessage"\n"; \
helpMessage = ""; \
} \
} else if ($$0 ~ /^##/) { \
if (helpMessage) { \
helpMessage = helpMessage"\n "substr($$0, 3); \
} else { \
helpMessage = substr($$0, 3); \
} \
} else { \
if (helpMessage) { \
print "\n "helpMessage"\n" \
} \
helpMessage = ""; \
} \
}' \
$(MAKEFILE_LIST)

View File

@ -1,2 +1,5 @@
# contentfulproxy
an experimental proxy for read access to contentful to save your API quota
docker run -p 8080 -e SERVICE_ZAP_ENDABLED=true -e RPOXOY=a.com,b.com foomo/contentfulproxy

View File

@ -0,0 +1,66 @@
package main
import (
"context"
"github.com/foomo/contentfulproxy/packages/go/config"
"github.com/foomo/contentfulproxy/proxy"
"github.com/foomo/keel"
"github.com/foomo/keel/log"
"github.com/foomo/keel/net/http/middleware"
)
const (
ServiceName = "Contentful Proxy"
)
func main() {
svr := keel.NewServer(
keel.WithHTTPZapService(false),
keel.WithHTTPViperService(false),
keel.WithHTTPPrometheusService(false),
)
// get the logger
l := svr.Logger()
// register Closers for graceful shutdowns
svr.AddClosers()
c := svr.Config()
webhookURLs := config.DefaultWebhookURLs(c)
webserverAddress := config.DefaultWebserverAddress(c)
webserverPath := config.DefaultWebserverPath(c)
backendURL := config.DefaultBackendURL(c)
// create proxy
proxy, _ := proxy.NewProxy(
context.Background(),
log.WithServiceName(l, ServiceName),
backendURL,
webserverPath,
webhookURLs,
)
// add the service to keel
svr.AddServices(
keel.NewServiceHTTP(
log.WithServiceName(l, ServiceName),
ServiceName,
webserverAddress(),
proxy,
getMiddleWares()...,
),
)
svr.Run()
}
func getMiddleWares() []middleware.Middleware {
return []middleware.Middleware{
middleware.Logger(),
middleware.Telemetry(),
middleware.RequestID(),
middleware.Recover(),
}
}

View File

@ -1,82 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"github.com/foomo/contentfulproxy/proxy"
"github.com/foomo/keel"
"github.com/foomo/keel/log"
"github.com/foomo/keel/net/http/middleware"
)
const (
ServiceName = "Contentful Proxy"
)
func main() {
svr := keel.NewServer(
keel.WithHTTPZapService(true),
keel.WithHTTPViperService(true),
keel.WithHTTPPrometheusService(true),
)
// get the logger
l := svr.Logger()
// register Closers for graceful shutdowns
svr.AddClosers()
// define and process flags and arguments
webserverAddress := flag.String("webserver-address", ":80", "address to bind web server host:port")
webserverPath := flag.String("webserver-path", "", "path to export the webserver on")
backendURL := flag.String("backend-url", "https://cdn.contentful.com", "endpoint of the contentful api")
flag.Parse()
webhooks, err := getWebhooks()
if err != nil {
l.Fatal(err.Error())
}
// create proxy
proxy, _ := proxy.NewProxy(
context.Background(),
l,
*backendURL,
*webserverPath,
webhooks,
)
// add the service to keel
svr.AddServices(
keel.NewServiceHTTP(
log.WithServiceName(l, ServiceName),
ServiceName,
*webserverAddress,
proxy,
getMiddleWares()...,
),
)
svr.Run()
}
func getMiddleWares() []middleware.Middleware {
return []middleware.Middleware{
middleware.Logger(),
middleware.Telemetry(),
middleware.RequestID(),
middleware.Recover(),
}
}
func getWebhooks() (proxy.WebHooks, error) {
args := flag.Args()
if len(args) == 0 {
return nil, fmt.Errorf("missing webhook arguments on startup")
}
webhooks := proxy.WebHooks{}
for _, v := range args {
webhooks = append(webhooks, proxy.WebHookURL(v))
}
return webhooks, nil
}

4
go.mod
View File

@ -3,11 +3,9 @@ module github.com/foomo/contentfulproxy
go 1.17
require (
github.com/foomo/contentful v0.3.6
github.com/foomo/keel v0.3.1
github.com/stretchr/testify v1.7.0
go.uber.org/zap v1.19.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
)
require (
@ -58,6 +56,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
@ -66,5 +65,4 @@ require (
gopkg.in/ini.v1 v1.51.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
moul.io/http2curl v1.0.0 // indirect
)

4
go.sum
View File

@ -93,8 +93,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foomo/contentful v0.3.6 h1:yiwhWayrXCe0wpQGzhO32bl8AE/46T261bBqNdPODWk=
github.com/foomo/contentful v0.3.6/go.mod h1:6Pf8efSKeMbwKgVkjSFWeBsLYdUA0NTW7OUsMWzHXvY=
github.com/foomo/keel v0.3.1 h1:be9EDLWsRzCXuf2rKhS6iShSAOMFQ3GpuoWOm80OKew=
github.com/foomo/keel v0.3.1/go.mod h1:gZPfO3DjBFkauNeeurMqvLuXNBV5hNgUZj0hJfib+kk=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
@ -789,8 +787,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View File

@ -0,0 +1,29 @@
package config
import (
keelconfig "github.com/foomo/keel/config"
"github.com/spf13/viper"
)
const (
WebserverAddress = "webserver.address"
WebserverPath = "webserver.path"
BackendURL = "backend.url"
WebhookURLs = "webhook.urls"
)
func DefaultWebhookURLs(c *viper.Viper) func() []string {
return keelconfig.GetStringSlice(c, WebhookURLs, []string{})
}
func DefaultWebserverAddress(c *viper.Viper) func() string {
return keelconfig.GetString(c, WebserverAddress, ":80")
}
func DefaultWebserverPath(c *viper.Viper) func() string {
return keelconfig.GetString(c, WebserverPath, "")
}
func DefaultBackendURL(c *viper.Viper) func() string {
return keelconfig.GetString(c, BackendURL, "https://cdn.contentful.com")
}

26
packages/go/log/log.go Normal file
View File

@ -0,0 +1,26 @@
package log
import "go.uber.org/zap"
const (
ServiceRoutineKey = "service_routine"
CacheIdKey = "cache_id"
URLKey = "url"
NumberOfWaitingClientsKey = "num_waiting_clients"
)
func FServiceRoutine(name string) zap.Field {
return zap.String(ServiceRoutineKey, name)
}
func FCacheId(name string) zap.Field {
return zap.String(CacheIdKey, name)
}
func FURL(name string) zap.Field {
return zap.String(URLKey, name)
}
func FNumberOfWaitingClients(num int) zap.Field {
return zap.Int(NumberOfWaitingClientsKey, num)
}

View File

@ -1,44 +0,0 @@
Packaging & Deployment
----------------------
In order to build packages and upload to Package Cloud, please install the following requirements and run the make task.
[Package Cloud Command Line Client](https://packagecloud.io/docs#cli_install)
```
$ gem install package_cloud
```
[FPM](https://github.com/jordansissel/fpm)
```
$ gem install fpm
```
Building package
```
$ make package
```
*NOTE: you will be prompted for Package Cloud credentials.*
Testing
-------
```
$ git clone https://github.com/foomo/contentfulproxy.git
$ cd contentfulproxy
$ make test
```
Contributing
------------
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests and examples for any new or changed functionality.
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`\)
3. Commit your changes (`git commit -am 'Add some feature'`\)
4. Push to the branch (`git push origin my-new-feature`\)
5. Create new Pull Request

View File

@ -1,61 +0,0 @@
#!/bin/bash
USER="foomo"
NAME="contentfulproxy"
URL="http://www.foomo.org"
DESCRIPTION="An experimental proxy for read access to contentful to save your API quota"
LICENSE="LGPL-3.0"
# get version
VERSION=`bin/contentfulproxy --version | sed 's/contentfulproxy //'`
# create temp dir
TEMP=`pwd`/pkg/tmp
mkdir -p $TEMP
package()
{
OS=$1
ARCH=$2
TYPE=$3
TARGET=$4
# copy license file
cp LICENSE $LICENSE
# define source dir
SOURCE=`pwd`/pkg/${TYPE}
# create build folder
BUILD=${TEMP}/${NAME}-${VERSION}
#rsync -rv --exclude **/.git* --exclude /*.sh $SOURCE/ $BUILD/
# build binary
GOOS=$OS GOARCH=$ARCH go build -o $BUILD/usr/local/bin/${NAME}
# create package
fpm -s dir \
-t $TYPE \
--name $NAME \
--maintainer $USER \
--version $VERSION \
--license $LICENSE \
--description "${DESCRIPTION}" \
--architecture $ARCH \
--package $TEMP \
--url "${URL}" \
-C $BUILD \
.
# push
package_cloud push $TARGET $TEMP/${NAME}_${VERSION}_${ARCH}.${TYPE}
# cleanup
rm -rf $TEMP
rm $LICENSE
}
package linux amd64 deb foomo/contentfulproxy/ubuntu/precise
package linux amd64 deb foomo/contentfulproxy/ubuntu/trusty
#package linux amd64 rpm

View File

@ -3,6 +3,7 @@ package proxy
import (
"crypto/md5"
"encoding/hex"
"github.com/foomo/contentfulproxy/packages/go/log"
"go.uber.org/zap"
"io/ioutil"
"net/http"
@ -21,14 +22,14 @@ type cachedResponse struct {
}
type cacheMap map[cacheID]*cachedResponse
type cache struct {
type Cache struct {
sync.RWMutex
cacheMap cacheMap
webHooks WebHooks
webHooks func() []string
l *zap.Logger
}
func (c *cache) set(id cacheID, response *http.Response) (*cachedResponse, error) {
func (c *Cache) set(id cacheID, response *http.Response) (*cachedResponse, error) {
responseBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
@ -47,33 +48,39 @@ func (c *cache) set(id cacheID, response *http.Response) (*cachedResponse, error
return cr, nil
}
func (c *cache) get(id cacheID) (*cachedResponse, bool) {
func (c *Cache) get(id cacheID) (*cachedResponse, bool) {
c.RLock()
defer c.RUnlock()
response, ok := c.cacheMap[id]
return response, ok
}
func (c *cache) update() {
func (c *Cache) update() {
c.RLock()
defer c.RUnlock()
c.cacheMap = cacheMap{}
c.l.Info("flushed the cache", zap.Int("length", len(c.cacheMap)))
c.l.Info("flushed the cache")
}
func (c *cache) callWebHooks() {
type r struct {
Url WebHookURL
Resp *http.Response
Err error
func (c *Cache) callWebHooks() {
for _, url := range c.webHooks() {
go func(url string, l *zap.Logger) {
l.Info("call webhook")
_, err := http.Get(url)
if err != nil {
l.Error("error while calling webhook", zap.Error(err))
}
}(url, c.l.With(log.FURL(url)))
}
}
for _, url := range c.webHooks {
go func(url WebHookURL, l *zap.Logger) {
l.Info("call webhook", zap.String("url", string(url)))
http.Get(string(url))
}(url, c.l.With(zap.String("url", string(url))))
func NewCache(l *zap.Logger, webHooks func() []string) *Cache {
c := &Cache{
cacheMap: cacheMap{},
webHooks: webHooks,
l: l.With(log.FServiceRoutine("cache")),
}
return c
}
func getCacheIDForRequest(r *http.Request) cacheID {

View File

@ -15,9 +15,9 @@ type requestJob struct {
type jobRunner func(job requestJob, id cacheID)
func getJobRunner(c *cache, backendURL string, chanJobDone chan requestJobDone) jobRunner {
func getJobRunner(c *Cache, backendURL func() string, chanJobDone chan requestJobDone) jobRunner {
return func(job requestJob, id cacheID) {
req, err := http.NewRequest("GET", backendURL+job.request.URL.RequestURI(), nil)
req, err := http.NewRequest("GET", backendURL()+job.request.URL.RequestURI(), nil)
if err != nil {
chanJobDone <- requestJobDone{
id: id,

View File

@ -3,39 +3,37 @@ package proxy
import (
"context"
"encoding/json"
"github.com/foomo/contentfulproxy/packages/go/log"
"net/http"
"go.uber.org/zap"
)
type WebHookURL string
type WebHooks []WebHookURL
type Info struct {
WebHooks WebHooks `json:"webhooks,omitempty"`
WebHooks []string `json:"webhooks,omitempty"`
CacheLength int `json:"cachelength,omitempty"`
BackendURL string `json:"backendurl,omitempty"`
}
type Proxy struct {
l *zap.Logger
cache *cache
backendURL string
pathPrefix string
cache *Cache
backendURL func() string
pathPrefix func() string
chanRequestJob chan requestJob
chanFlushJob chan requestFlush
}
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case p.pathPrefix + "/update":
case p.pathPrefix() + "/update":
command := requestFlush("doit")
p.chanFlushJob <- command
return
case p.pathPrefix + "/info":
case p.pathPrefix() + "/info":
info := Info{
WebHooks: p.cache.webHooks,
BackendURL: p.backendURL,
WebHooks: p.cache.webHooks(),
BackendURL: p.backendURL(),
CacheLength: len(p.cache.cacheMap),
}
jsonResponse(w, info, http.StatusOK)
@ -55,14 +53,14 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
jobDone := <-chanDone
if jobDone.err != nil {
p.l.Error("cache / job error", zap.String("url", r.RequestURI))
http.Error(w, "cache / job error", http.StatusInternalServerError)
p.l.Error("Cache / job error", zap.String("url", r.RequestURI))
http.Error(w, "Cache / job error", http.StatusInternalServerError)
return
}
cachedResponse = jobDone.cachedResponse
p.l.Info("serve response after cache creation", zap.String("url", r.RequestURI), zap.String("cache id", string(cacheID)))
p.l.Info("serve response after cache creation", log.FURL(r.RequestURI), log.FCacheId(string(cacheID)))
} else {
p.l.Info("serve response from cache", zap.String("url", r.RequestURI), zap.String("cache id", string(cacheID)))
p.l.Info("serve response from cache", log.FURL(r.RequestURI), log.FCacheId(string(cacheID)))
}
for key, values := range cachedResponse.header {
for _, value := range values {
@ -71,21 +69,17 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
_, err := w.Write(cachedResponse.response)
if err != nil {
p.l.Info("writing cached response failed", zap.String("url", r.RequestURI), zap.String("cache id", string(cacheID)))
p.l.Info("writing cached response failed", log.FURL(r.RequestURI), log.FCacheId(string(cacheID)))
}
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func NewProxy(ctx context.Context, l *zap.Logger, backendURL string, pathPrefix string, webHooks WebHooks) (*Proxy, error) {
func NewProxy(ctx context.Context, l *zap.Logger, backendURL func() string, pathPrefix func() string, webHooks func() []string) (*Proxy, error) {
chanRequest := make(chan requestJob)
chanFlush := make(chan requestFlush)
c := &cache{
cacheMap: cacheMap{},
webHooks: webHooks,
l: l,
}
c := NewCache(l, webHooks)
go getLoop(ctx, l, backendURL, c, chanRequest, chanFlush)
return &Proxy{
l: l,
@ -100,8 +94,8 @@ func NewProxy(ctx context.Context, l *zap.Logger, backendURL string, pathPrefix
func getLoop(
ctx context.Context,
l *zap.Logger,
backendURL string,
c *cache,
backendURL func() string,
c *Cache,
chanRequestJob chan requestJob,
chanFlush chan requestFlush,
) {
@ -110,20 +104,20 @@ func getLoop(
jobRunner := getJobRunner(c, backendURL, chanJobDone)
for {
select {
case command := <-chanFlush:
l.Info("cache update command coming in", zap.String("flushCommand", string(command)))
case <-chanFlush:
l.Info("Cache update command coming in")
c.update()
c.callWebHooks()
case nextJob := <-chanRequestJob:
id := getCacheIDForRequest(nextJob.request)
pendingRequests[id] = append(pendingRequests[id], nextJob.chanDone)
requests := pendingRequests[id]
cacheID := getCacheIDForRequest(nextJob.request)
pendingRequests[cacheID] = append(pendingRequests[cacheID], nextJob.chanDone)
requests := pendingRequests[cacheID]
if len(requests) == 1 {
l.Info("starting jobrunner for", zap.String("uri", nextJob.request.RequestURI), zap.String("id", string(id)))
go jobRunner(nextJob, id)
l.Info("starting jobrunner for", log.FURL(nextJob.request.RequestURI), log.FCacheId(string(cacheID)))
go jobRunner(nextJob, cacheID)
}
case jobDone := <-chanJobDone:
l.Info("request complete", zap.String("id", string(jobDone.id)), zap.Int("num-waiting-clients", len(pendingRequests[jobDone.id])))
l.Info("request complete", log.FCacheId(string(jobDone.id)), log.FNumberOfWaitingClients(len(pendingRequests[jobDone.id])))
for _, chanPending := range pendingRequests[jobDone.id] {
chanPending <- jobDone
}

View File

@ -9,6 +9,7 @@ import (
"net/http/httptest"
"sync"
"testing"
"time"
)
const (
@ -40,10 +41,10 @@ func GetBackend(t *testing.T) (getStats, http.HandlerFunc) {
switch r.URL.Path {
case "/foo":
w.Write([]byte(responseFoo))
_, _ = w.Write([]byte(responseFoo))
return
case "/bar":
w.Write([]byte(responseBar))
_, _ = w.Write([]byte(responseBar))
return
}
http.Error(w, "not found", http.StatusNotFound)
@ -69,11 +70,11 @@ func GetWebHook(t *testing.T) (getStats, http.HandlerFunc) {
t.Log("webhook: url called", r.URL.Path)
switch r.URL.Path {
case "/update":
w.Write([]byte(responseUpdate))
case "/test1":
_, _ = w.Write([]byte(responseUpdate))
return
case "/update":
w.Write([]byte(responseFlush))
case "/test2":
_, _ = w.Write([]byte(responseFlush))
return
}
http.Error(w, "not found", http.StatusNotFound)
@ -92,11 +93,13 @@ func getTestServer(t *testing.T) (gs func(path string) int, ws func(path string)
p, _ := NewProxy(
context.Background(),
l,
backendServer.URL,
"",
[]WebHookURL{
WebHookURL(webHookServer.URL + "/update"),
WebHookURL(webHookServer.URL + "/update"),
func() string {return backendServer.URL},
func() string {return ""},
func() []string {
return []string{
webHookServer.URL + "/test1",
webHookServer.URL + "/test2",
}
},
)
s = httptest.NewServer(p)
@ -118,7 +121,7 @@ func TestProxy(t *testing.T) {
}
for j := 0; j < 10; j++ {
wg := sync.WaitGroup{}
for i := 0; i < 1; i++ {
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
get("/foo")
@ -129,12 +132,17 @@ func TestProxy(t *testing.T) {
}
assert.Equal(t, 1, gs("/foo"))
//
http.Get(server.URL + "/update")
// check the current status
//response, err := http.Get(server.URL + "/info")
//assert.NoError(t, err)
//
assert.Equal(t, 1, ws("/update"))
assert.Equal(t, 1, ws("/update"))
_, _ = http.Get(server.URL + "/update")
time.Sleep(time.Second * 1)
//
assert.Equal(t, 1, ws("/test1"))
assert.Equal(t, 1, ws("/test2"))
}