Skip to content

Commit

Permalink
feat: v1.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
darkweak committed Apr 16, 2021
1 parent 9902d9d commit a132157
Show file tree
Hide file tree
Showing 1,311 changed files with 823 additions and 357,318 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
push: true
file: ./Dockerfile-prod
tags: |
darkweak/souin:latest
darkweak/souin:latest-full
darkweak/souin:${{ env.RELEASE_VERSION }}
generate-artifacts:
name: Generate cross-platform builds
Expand Down
18 changes: 0 additions & 18 deletions .traefik.yml

This file was deleted.

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ DC_BUILD=$(DC) build
DC_EXEC=$(DC) exec

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

build-and-run-caddy: ## Build caddy binary
cd plugins/caddy && xcaddy build --with github.com/darkweak/souin/plugins/caddy=./ --with github.com/darkweak/souin@latest=../.. && ./caddy run

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

health-check-prod: build-app ## Production container health check
Expand Down
71 changes: 47 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ It's RFC compatible, supporting Vary, request coalescing and other specification
It supports the [Cache-Status HTTP response header](https://httpwg.org/http-extensions/draft-ietf-httpbis-cache-header.html)

## Disclaimer
If you need redis or other custom cache providers, you have to use the full-featured version. You can read the documentation, on [the full-featured branch](https://github.com/Darkweak/Souin/tree/full-version) to discover the specific parts.
If you don't need redis or other custom cache providers, you can to use the minimal version. You can read the documentation, on [the master branch](https://github.com/Darkweak/Souin) to discover the specific parts.

## Configuration
The configuration file is stored at `/anywhere/configuration.yml`. You can edit it provided you fill at least the required parameters as shown below.
Expand Down Expand Up @@ -74,8 +74,12 @@ default_cache:
distributed: true # Use Olric distributed storage
headers: # Default headers concatenated in stored keys
- Authorization
olric: # If distributed is set to true, you have to define the olric part
url: 'olric:3320' # Olric server
cache_providers:
- all # Enable all providers by default
redis: # Redis provider configuration
url: 'redis:6379'
olric: # Olric provider configuration
url: 'olric:3320'
regex:
exclude: 'ARegexHere' # Regex to exclude from cache
log_level: INFO # Logs verbosity [ DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL, debug, info, warn, error, dpanic, panic, fatal ]
Expand All @@ -94,21 +98,25 @@ urls:
- Authorization
- 'Content-Type'
```
| Key | Description | Value example |
|:------------------------------------:|:--------------------------------------------------------------------------:|:-----------------------------------------------------------------------------:|
| `api.basepath` | BasePath for all APIs to avoid conflicts | `/your-non-conflicting-route`<br/><br/>`(default: /souin-api)` |
| `api.{api}.enable` | Enable the new API with related routes | `true`<br/><br/>`(default: false)` |
| `api.security.secret` | JWT secret key | `Any_charCanW0rk123` |
| `api.security.users` | Array of authorized users with username x password combo | `- username: admin`<br/><br/>` password: admin` |
| `api.souin.security` | Enable JWT validation to access the resource | `true`<br/><br/>`(default: false)` |
| `default_cache.headers` | List of headers to include to the cache | `- Authorization`<br/><br/>`- Content-Type`<br/><br/>`- X-Additional-Header` |
| `default_cache.cache_providers` | Your providers list to cache your data, by default it will use all systems | `- all`<br/><br/>`- ristretto`<br/><br/>`- redis` |
| `default_cache.redis.url` | The redis url, used if you enabled it in the provider section | `redis:6379` (container way) and `http://yourdomain.com:6379` (network way) |
| `default_cache.olric.url` | The olric url, used if you enabled it in the provider section | `olric:3320` (container way) and `http://yourdomain.com:3320` (network way) |
| `default_cache.regex.exclude` | The regex used to prevent paths being cached | `^[A-z]+.*$` |
| `log_level` | The log level | `One of DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL it's case insensitive` |
| `ssl_providers` | List of your providers handling certificates | `- traefik`<br/><br/>`- nginx`<br/><br/>`- apache` |
| `urls.{your url or regex}` | List of your custom configuration depending each URL or regex | 'https:\/\/yourdomain.com' |
| `urls.{your url or regex}.ttl` | Override the default TTL if defined | 99999 |
| `urls.{your url or regex}.headers` | Override the default headers if defined | `- Authorization`<br/><br/>`- 'Content-Type'` |
| `urls.{your url or regex}.providers` | Override the default providers if defined | `- redis`<br/><br/>`- 'olric'` |

| Key | Description | Value example |
|:----------------------------------:|:-------------------------------------------------------------:|:-----------------------------------------------------------------------------:|
| `api.basepath` | BasePath for all APIs to avoid conflicts | `/your-non-conflicting-route`<br/><br/>`(default: /souin-api)` |
| `api.{api}.enable` | Enable the new API with related routes | `true`<br/><br/>`(default: false)` |
| `api.security.secret` | JWT secret key | `Any_charCanW0rk123` |
| `api.security.users` | Array of authorized users with username x password combo | `- username: admin`<br/><br/>` password: admin` |
| `api.souin.security` | Enable JWT validation to access the resource | `true`<br/><br/>`(default: false)` |
| `default_cache.headers` | List of headers to include to the cache | `- Authorization`<br/><br/>`- Content-Type`<br/><br/>`- X-Additional-Header` |
| `default_cache.regex.exclude` | The regex used to prevent paths being cached | `^[A-z]+.*$` |
| `log_level` | The log level | `One of DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL it's case insensitive` |
| `ssl_providers` | List of your providers handling certificates | `- traefik`<br/><br/>`- nginx`<br/><br/>`- apache` |
| `urls.{your url or regex}` | List of your custom configuration depending each URL or regex | 'https:\/\/yourdomain.com' |
| `urls.{your url or regex}.ttl` | Override the default TTL if defined | 99999 |
| `urls.{your url or regex}.headers` | Override the default headers if defined | `- Authorization`<br/><br/>`- 'Content-Type'` |

## APIs
All endpoints are accessible through the `api.basepath` configuration line or by default through `/souin-api` to avoid named route conflicts. Be sure to define an unused route to not break your existing application.
Expand Down Expand Up @@ -139,13 +147,13 @@ See the sequence for the minimal version below

## Cache systems
Supported providers
- [Redis](https://github.com/go-redis/redis)
- [Olric](https://github.com/buraksezer/olric)
- [Redis](https://github.com/go-redis/redis)
- [Olric](https://github.com/buraksezer/olric)

The cache system sits on top of three providers at the moment. It provides an in-memory, redis and Olric cache systems because setting, getting, updating and deleting keys in these providers is as easy as it gets.
In order to do that, Redis and Olric providers need to be either on the same network as the Souin instance when using docker-compose or over the internet, then it will use by default in-memory to avoid network latency as much as possible.
Souin will return at first the in-memory response when it gives a non-empty response, then the olric followed by the redis one with same condition, or fallback to the reverse proxy otherwise.
Since 1.4.2, Souin supports [Olric](https://github.com/buraksezer/olric) to handle distributed cache.
The cache system sits on top of three providers at the moment. It provides an in-memory, redis and Olric cache systems because setting, getting, updating and deleting keys in these providers is as easy as it gets.
In order to do that, Redis and Olric providers need to be either on the same network as the Souin instance when using docker-compose or over the internet, then it will use by default in-memory to avoid network latency as much as possible.
Souin will return at first the in-memory response when it gives a non-empty response, then the olric followed by the redis one with same condition, or fallback to the reverse proxy otherwise.
Since 1.4.2, Souin supports [Olric](https://github.com/buraksezer/olric) to handle distributed cache.

### Cache invalidation
The cache invalidation is build for CRUD requests, if you're doing a GET HTTP request, it will serve the cached response when it exists, otherwise the reverse-proxy response will be served.
Expand Down Expand Up @@ -192,17 +200,32 @@ x-networks: &networks
services:
souin:
image: darkweak/souin:latest
image: darkweak/souin:latest-full
ports:
- 80:80
- 443:443
depends_on:
- olric
- redis
environment:
GOPATH: /app
volumes:
- /anywhere/traefik.json:/ssl/traefik.json
- /anywhere/configuration.yml:/configuration/configuration.yml
<<: *networks
olric:
build:
context: ./olric
dockerfile: Dockerfile-olric
target: olric
restart: on-failure
<<: *networks
redis:
image: redis:alpine
<<: *networks
networks:
your_network:
external: true
Expand Down
4 changes: 2 additions & 2 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

// Initialize contains all apis that should be enabled
func Initialize(provider types.AbstractProviderInterface, c configurationtypes.AbstractConfigurationInterface) []EndpointInterface {
func Initialize(providers map[string]types.AbstractProviderInterface, c configurationtypes.AbstractConfigurationInterface) []EndpointInterface {
security := auth.InitializeSecurity(c)
return []EndpointInterface{security, initializeSouin(provider, c, security)}
return []EndpointInterface{security, initializeSouin(providers, c, security)}
}
1 change: 1 addition & 0 deletions api/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ func TestInitialize(t *testing.T) {
if !endpoints[0].IsEnabled() {
errors.GenerateError(t, fmt.Sprintf("Endpoint should be enabled"))
}
prs["olric"].Reset()
}
33 changes: 21 additions & 12 deletions api/souin.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import (

// SouinAPI object contains informations related to the endpoints
type SouinAPI struct {
basePath string
enabled bool
provider types.AbstractProviderInterface
security *auth.SecurityAPI
basePath string
enabled bool
providers map[string]types.AbstractProviderInterface
security *auth.SecurityAPI
}

func initializeSouin(provider types.AbstractProviderInterface, configuration configurationtypes.AbstractConfigurationInterface, api *auth.SecurityAPI) *SouinAPI {
func initializeSouin(providers map[string]types.AbstractProviderInterface, configuration configurationtypes.AbstractConfigurationInterface, api *auth.SecurityAPI) *SouinAPI {
basePath := configuration.GetAPI().Souin.BasePath
enabled := configuration.GetAPI().Souin.Enable
var security *auth.SecurityAPI
Expand All @@ -31,28 +31,37 @@ func initializeSouin(provider types.AbstractProviderInterface, configuration con
return &SouinAPI{
basePath,
enabled,
provider,
providers,
security,
}
}

// BulkDelete allow user to delete multiple items with regexp
func (s *SouinAPI) BulkDelete(rg *regexp.Regexp) {
for _, key := range s.GetAll() {
if rg.Match([]byte(key)) {
s.Delete(key)
for _, v := range s.GetAll() {
for _, key := range v {
if rg.Match([]byte(key)) {
s.Delete(key)
}
}
}
}

// Delete will delete a record into the provider cache system and will update the Souin API if enabled
func (s *SouinAPI) Delete(key string) {
s.provider.Delete(key)
for _, p := range s.providers {
p.Delete(key)
}
}

// GetAll will retrieve all stored keys in the provider
func (s *SouinAPI) GetAll() []string {
return s.provider.ListKeys()
func (s *SouinAPI) GetAll() map[string][]string {
list := map[string][]string{}
for pName, p := range s.providers {
list[pName] = p.ListKeys()
}

return list
}

// GetBasePath will return the basepath for this resource
Expand Down
63 changes: 45 additions & 18 deletions api/souin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,59 +24,86 @@ func mockSouinAPI() *SouinAPI {

func TestSouinAPI_BulkDelete(t *testing.T) {
souinMock := mockSouinAPI()
souinMock.provider.Set("key", []byte("value"), tests.GetMatchedURL("key"), 20*time.Second)
souinMock.provider.Set("key2", []byte("value"), tests.GetMatchedURL("key"), 20*time.Second)
defer souinMock.providers["olric"].Reset()
for _, provider := range souinMock.providers {
provider.Set("key", []byte("value"), tests.GetMatchedURL("key"), 20*time.Second)
provider.Set("key2", []byte("value"), tests.GetMatchedURL("key"), 20*time.Second)
}
time.Sleep(3 * time.Second)
if len(souinMock.GetAll()) != 2 {
errors.GenerateError(t, "Souin API should have a record")
for _, v := range souinMock.GetAll() {
if len(v) != 2 {
errors.GenerateError(t, "Souin API should have a record")
}
}
souinMock.BulkDelete(regexp.MustCompile(".+"))
time.Sleep(5 * time.Second)
if len(souinMock.GetAll()) != 0 {
errors.GenerateError(t, "Souin API shouldn't have a record")
for _, v := range souinMock.GetAll() {
if len(v) != 0 {
errors.GenerateError(t, "Souin API should have a record")
}
}
}

func TestSouinAPI_Delete(t *testing.T) {
souinMock := mockSouinAPI()
souinMock.provider.Set("key", []byte("value"), tests.GetMatchedURL("key"), 20*time.Second)
defer souinMock.providers["olric"].Reset()
for _, provider := range souinMock.providers {
provider.Set("key", []byte("value"), tests.GetMatchedURL("key"), 20*time.Second)
}
time.Sleep(3 * time.Second)
if len(souinMock.GetAll()) != 1 {
errors.GenerateError(t, "Souin API should have a record")
for _, v := range souinMock.GetAll() {
if len(v) != 1 {
errors.GenerateError(t, "Souin API should have a record")
}
}
souinMock.Delete("key")
time.Sleep(3 * time.Second)
if len(souinMock.GetAll()) == 1 {
errors.GenerateError(t, "Souin API shouldn't have a record")
for _, v := range souinMock.GetAll() {
if len(v) == 1 {
errors.GenerateError(t, "Souin API shouldn't have a record")
}
}
}

func TestSouinAPI_GetAll(t *testing.T) {
souinMock := mockSouinAPI()
if len(souinMock.GetAll()) > 0 {
errors.GenerateError(t, "Souin API don't have any record yet")
defer souinMock.providers["olric"].Reset()
for _, v := range souinMock.GetAll() {
if len(v) > 0 {
errors.GenerateError(t, "Souin API shouldn't have a record")
}
}

souinMock.provider.Set("key", []byte("value"), tests.GetMatchedURL("key"), 6*time.Second)
for _, provider := range souinMock.providers {
provider.Set("key", []byte("value"), tests.GetMatchedURL("key"), 6*time.Second)
}
time.Sleep(3 * time.Second)
if len(souinMock.GetAll()) != 1 {
errors.GenerateError(t, "Souin API should have a record")
for _, v := range souinMock.GetAll() {
if len(v) != 1 {
errors.GenerateError(t, "Souin API should have a record")
}
}
souinMock.providers["redis"].Delete("key")
souinMock.providers["olric"].Delete("key")
time.Sleep(10 * time.Second)
if len(souinMock.GetAll()) == 1 {
errors.GenerateError(t, "Souin API shouldn't have a record")
for _, v := range souinMock.GetAll() {
if len(v) == 1 {
errors.GenerateError(t, "Souin API shouldn't have a record")
}
}
}

func TestSouinAPI_GetBasePath(t *testing.T) {
souinMock := mockSouinAPI()
defer souinMock.providers["olric"].Reset()
if souinMock.GetBasePath() != "/souinbasepath" {
errors.GenerateError(t, "Souin API should be enabled")
}
}

func TestSouinAPI_IsEnabled(t *testing.T) {
souinMock := mockSouinAPI()
defer souinMock.providers["olric"].Reset()
if !souinMock.IsEnabled() {
errors.GenerateError(t, "Souin API should be enabled")
}
Expand Down
14 changes: 5 additions & 9 deletions cache/coalescing/requestCoalescing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,22 @@ import (
"testing"
)

func commonInitializer() (*httptest.ResponseRecorder, *http.Request, *types.RetrieverResponseProperties) {
func TestServeResponse(t *testing.T) {
c := tests.MockConfiguration(tests.BaseConfiguration)
prs := providers.InitializeProvider(c)
regexpUrls := helpers.InitializeRegexp(c)
prs := providers.InitializeProvider(c)
defer prs["olric"].Reset()
rc := Initialize()
retriever := &types.RetrieverResponseProperties{
Configuration: c,
Provider: prs,
Providers: prs,
MatchedURL: tests.GetMatchedURL(tests.PATH),
RegexpUrls: regexpUrls,
Transport: rfc.NewTransport(prs),
}
r := httptest.NewRequest("GET", "http://"+tests.DOMAIN+tests.PATH, nil)
w := httptest.NewRecorder()

return w, r, retriever
}

func TestServeResponse(t *testing.T) {
rc := Initialize()
w, r, retriever := commonInitializer()
ServeResponse(
w,
r,
Expand Down
Loading

0 comments on commit a132157

Please sign in to comment.