Skip to content

Commit

Permalink
Setup - first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
darkweak committed Nov 20, 2019
0 parents commit 65b4a78
Show file tree
Hide file tree
Showing 23 changed files with 747 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .env.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CACHE_PORT=80
CACHE_TLS_PORT=443
REDIS_URL=redis:6379
TTL=10
REVERSE_PROXY=http://traefik

REGEX=ARegexHere
7 changes: 7 additions & 0 deletions .env.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CACHE_PORT=80
CACHE_TLS_PORT=443
REDIS_URL=redis:6379
TTL=10
REVERSE_PROXY=http://traefik

REGEX=ARegexHere
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/cmd/*
.env
docker-compose.yml
Dockerfile
/.idea/
23 changes: 23 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
sudo: required
language: go

addons:
apt:
packages:
- docker-ce

git:
depth: 1

notifications:
email: false

services:
- docker

before_install:
- make create-network
- make build-dev

script:
- make validate
18 changes: 18 additions & 0 deletions Dockerfile-dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM golang:1.13-alpine

RUN apk update && apk upgrade && \
apk add --no-cache bash git openssh gcc libc-dev

RUN mkdir -p /app/src/github.com/darkweak/souin
ADD ./*.go /app/src/github.com/darkweak/souin/
ADD ./cache /app/src/github.com/darkweak/souin/cache
ADD ./default/server.* /app/src/github.com/darkweak/souin/

WORKDIR /app/src/github.com/darkweak/souin
ENV GOPATH /app
RUN go get ./...
RUN go get -u golang.org/x/lint/golint

EXPOSE 80

CMD ["go", "run", "main.go"]
24 changes: 24 additions & 0 deletions Dockerfile-prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM golang:1.13-alpine

RUN apk update && apk upgrade && \
apk add --no-cache bash git openssh gcc libc-dev

RUN mkdir -p /app/src/github.com/darkweak/cmd
RUN mkdir -p /app/src/github.com/darkweak/souin
ADD ./*.go /app/src/github.com/darkweak/souin/
ADD ./cache /app/src/github.com/darkweak/souin/cache
ADD ./default/server.* /app/src/github.com/darkweak/souin/
ADD ./entrypoint.sh /app/src/github.com/darkweak/souin/entrypoint.sh

WORKDIR /app/src/github.com/darkweak/souin
ENV GOPATH /app
ENV GOOS linux
ENV GOARCH arm
RUN go get ./...
RUN go get -u golang.org/x/lint/golint
RUN go install
RUN chmod 755 ./entrypoint.sh

EXPOSE 80

ENTRYPOINT ["./entrypoint.sh"]
43 changes: 43 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.PHONY: build-app build-dev create-network down env-dev env-prod help lint tests up validate

DC=docker-compose
DC_BUILD=$(DC) build
DC_EXEC=$(DC) exec

build-app: env-prod ## Build containers with prod env vars
$(DC_BUILD) souin
$(MAKE) up

build-dev: env-dev ## Build containers with dev env vars
$(DC_BUILD) souin
$(MAKE) up

create-network: ## Create network
docker network create your_network

down: ## Down containers
$(DC) down --remove-orphans

env-dev: ## Up container with dev env vars
cp Dockerfile-dev Dockerfile
cp docker-compose.yml.dev docker-compose.yml
cp .env.dev .env

env-prod: ## Up container with prod env vars
cp Dockerfile-prod Dockerfile
cp docker-compose.yml.prod docker-compose.yml
cp .env.prod .env

help:
@grep -E '(^[0-9a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-25s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/'

lint: ## Run lint
$(DC_EXEC) souin /app/bin/golint ./cache

tests: ## Run tests
$(DC_EXEC) souin go test -v ./...

up: ## Up containers
$(DC) up -d --remove-orphans

validate: lint tests ## Run lint and tests
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<p align="center"><a href="https://github.com/darkweak/souin"><img src="docs/img/logo.svg?sanitize=true" alt="Souin logo"></a></p>

# Souin Table of Contents
1. [Souin reverse-proxy cache](#project-description)
2. [Environment variables](#environment-variables)
2.1. [Required variables](#required-variables)
2.2. [Optional variables](#optional-variables)
3. [Cache system](#cache-system)
4. [Exemples](#exemples)
4.1. [Træfik container](#træfik-container)

[![Travis CI](https://travis-ci.com/Darkweak/Souin.svg?branch=master)](https://travis-ci.com/Darkweak/Souin)

# <img src="docs/img/logo.svg?sanitize=true" alt="Souin logo" width="30" height="30">ouin reverse-proxy cache

## Project description
Souin is a new cache system for every reverse-proxy. It will be placed on top of your reverse-proxy like Apache, NGinx or Traefik.
As it's written in go, it can be deployed on any server and with docker integration, it will be easy to implement it on top of Swarm or kubernetes instance.

## Environment variables

### Required variables
| Variable | Description | Value exemple |
|:---:|:---:|:---:|
|`CACHE_PORT`|The HTTP port Souin will be running to|`80`|
|`CACHE_TLS_PORT`|The TLS port Souin will be running to|`443`|
|`REDIS_URL`|The redis instance URL|- `http://redis` (Container way)<br/>`http://localhost:6379` (Local way)|
|`TTL`|Duration to cache request (in seconds)|10|
|`REVERSE_PROXY`|The reverse-proxy instance URL like Apache, Nginx, Træfik, etc...|- `http://yourservice` (Container way)<br/>`http://localhost:81` (Local way)|

### Optional variables
| Variable | Description | Value exemple |
|:---:|:---:|:---:|
|`REGEX`|The regex to define URL to not store in cache|`http://domain.com/mypath`|

## Cache system
The cache is set into redis instance, because we can set, get, update and delete keys as easy as possible.
To perform with that, redis should be on the same network than Souin instance if you are using docker-compose, then both should be on the same server if you use binaries
Asynchronously, Souin will request redis instance and the reverse-proxy to get at least one valid response and return to the client the first response caught by Souin.

### Cache invalidation
The cache invalidation is made for CRUD requests, if you're doing a GET HTTP request, it will serve the cached response if exists then the reverse-proxy response will be served.
If you're doing a POST, PUT, PATCH or DELETE HTTP request, the related cached get request will be dropped and the list endpoint will be dropped too
It works very well with plain [API Platform](https://api-platform.com) integration (but not custom actions for now) and CRUD routes

## Exemples

### Træfik container
[Træfik](https://traefik.io) is a modern reverse-proxy and help you to manage full container architecure projects.

```yaml
# your-traefik-instance/docker-compose.yml
version: '3.4'

x-networks: &networks
networks:
- your_network

services:
traefik:
image: traefik:v2.0
ports:
- "81:80" # Note the 81 to 80 port declaration
- "444:443" # Note the 444 to 443 port declaration
command: --providers.docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock
<<: *networks

# your other services here...

networks:
your_network:
external: true
```
```yaml
# your-souin-instance/docker-compose.yml
version: '3.4'

x-networks: &networks
networks:
- your_network

services:
souin:
build:
context: .
ports:
- ${CACHE_PORT}:80
- ${CACHE_TLS_PORT}:443
depends_on:
- redis
environment:
REDIS_URL: ${REDIS_URL}
TTL: ${TTL}
CACHE_PORT: ${CACHE_PORT}
CACHE_TLS_PORT: ${CACHE_TLS_PORT}
REVERSE_PROXY: ${REVERSE_PROXY}
REGEX: ${REGEX}
GOPATH: /app
volumes:
- ./cmd:/app/cmd
<<: *networks

redis:
image: redis:alpine
<<: *networks

networks:
your_network:
external: true
```
9 changes: 9 additions & 0 deletions cache/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cache

import (
"testing"
)

func generateError(t *testing.T, text string) {
t.Errorf("An error occurred : %s", text)
}
55 changes: 55 additions & 0 deletions cache/redisConnection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cache

import (
"github.com/go-redis/redis"
"time"
"os"
"strconv"
"regexp"
)

func redisClientConnectionFactory() *redis.Client {
return redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_URL"),
DB: 0,
Password: "",
})
}

func pathnameNotInRegex(pathname string) bool {
b, _ := regexp.Match(os.Getenv("REGEX"), []byte(pathname))
return !b
}

func getRequestInCache(pathname string) ReverseResponse {
client := redisClientConnectionFactory()
val2, err := client.Get(pathname).Result()

if err != nil {
return ReverseResponse{"", nil, nil}
}

return ReverseResponse{val2, nil, nil}
}

func deleteKey(key string) {
client := redisClientConnectionFactory();
client.Do("del", key)
}

func deleteKeys(regex string) {
client := redisClientConnectionFactory();
for _, i := range client.Keys(regex).Val() {
client.Do("del", i)
}
}

func setRequestInCache(pathname string, data []byte) {
client := redisClientConnectionFactory()
value, _ := strconv.Atoi(os.Getenv("TTL"))

err := client.Set(pathname, string(data), time.Duration(value) * time.Second).Err()
if err != nil {
panic(err)
}
}
36 changes: 36 additions & 0 deletions cache/redisConnection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cache

import (
"testing"
"time"
"fmt"
)

const VALUE = "My first data"

func populateRedisWithFakeData() {
client := redisClientConnectionFactory()
duration := time.Duration(120) * time.Second
basePath := "/testing"
domain := "domain.com"

client.Set(domain+basePath, "testing value is here for "+basePath, duration)
for i := 0; i < 25; i++ {
client.Set(domain+basePath+"/"+string(i), "testing value is here for my first init of "+basePath+"/"+string(i), duration)
}
}

func TestIShouldBeAbleToReadAndWriteDataInRedis(t *testing.T) {
client := redisClientConnectionFactory()
err := client.Set("Test", string(VALUE), time.Duration(10)*time.Second).Err()
if err != nil {
generateError(t, "Impossible to set redis variable")
}
res, err := client.Get("Test").Result()
if err != nil {
generateError(t, "Retrieving data from redis")
}
if VALUE != res {
generateError(t, fmt.Sprintf("%s not corresponding to %s", res, VALUE))
}
}
Loading

0 comments on commit 65b4a78

Please sign in to comment.