Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: chenyahui/gin-cache
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.7.0
Choose a base ref
...
head repository: chenyahui/gin-cache
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on May 5, 2022

  1. Copy the full SHA
    c60dacc View commit details

Commits on Jul 27, 2022

  1. fix: module name declares

    Yong-suk.Jung committed Jul 27, 2022
    Copy the full SHA
    c4a6be3 View commit details

Commits on Jul 30, 2022

  1. Merge pull request #16 from hare85/feature/module_declares

    fix: module name declares
    chenyahui authored Jul 30, 2022
    Copy the full SHA
    b6151dd View commit details

Commits on Sep 2, 2022

  1. Copy the full SHA
    19318c7 View commit details

Commits on Dec 20, 2022

  1. Copy the full SHA
    7f8c3e5 View commit details
  2. Copy the full SHA
    8384207 View commit details

Commits on Dec 21, 2022

  1. feat: add WithOnCacheMiss option

    This is similar to WithOnCacheHit. It is also triggered on error (as
    this is technically a cache miss). This is useful if you want to keep
    metrics about hit and miss without having another middleware.
    vincentbernat committed Dec 21, 2022
    Copy the full SHA
    6f54afb View commit details

Commits on Dec 30, 2022

  1. Merge pull request #29 from vincentbernat/feature/cache-miss

    feat: add WithOnCacheMiss option
    chenyahui authored Dec 30, 2022
    Copy the full SHA
    cd1fa6c View commit details

Commits on Feb 17, 2023

  1. Copy the full SHA
    c2da0b6 View commit details

Commits on Feb 20, 2023

  1. Merge pull request #32 from rts-gordon/main

    upgrade go-redis to v8.11.5
    chenyahui authored Feb 20, 2023
    Copy the full SHA
    b885ae3 View commit details

Commits on Nov 16, 2023

  1. Copy the full SHA
    a2f9e2b View commit details

Commits on Nov 17, 2023

  1. Copy the full SHA
    fedf110 View commit details
  2. chore: update golangci-lint

    chenyahui committed Nov 17, 2023
    Copy the full SHA
    83df4de View commit details

Commits on Dec 29, 2023

  1. Copy the full SHA
    e3df7ae View commit details
  2. fix lint

    chenyahui committed Dec 29, 2023
    Copy the full SHA
    bece16d View commit details
  3. Update go.yml

    chenyahui committed Dec 29, 2023
    Copy the full SHA
    d23f9fd View commit details
  4. update go.mod

    chenyahui committed Dec 29, 2023
    Copy the full SHA
    feb4bdc View commit details
  5. fix test on go 1.13

    chenyahui committed Dec 29, 2023
    Copy the full SHA
    45e29b1 View commit details
  6. fix test on go 1.13

    chenyahui committed Dec 29, 2023
    Copy the full SHA
    406f5aa View commit details

Commits on May 16, 2024

  1. Copy the full SHA
    ccb9bfc View commit details

Commits on May 17, 2024

  1. add TestCacheByRequestURICustomCacheStrategy

    tung committed May 17, 2024
    Copy the full SHA
    5795247 View commit details
  2. Merge pull request #40 from joripage/cache-strategy-for-CacheByReques…

    …tURI
    
    use WithCacheStrategyByRequest for CacheByRequestURI
    chenyahui authored May 17, 2024
    Copy the full SHA
    fd2780f View commit details
Showing with 308 additions and 53 deletions.
  1. +49 −0 .github/workflows/codeql.yml
  2. +11 −9 .github/workflows/go.yml
  3. +25 −8 cache.go
  4. +109 −12 cache_test.go
  5. +45 −0 examples/options/main.go
  6. +4 −5 go.mod
  7. +23 −16 go.sum
  8. +40 −1 option.go
  9. +2 −2 persist/memory.go
49 changes: 49 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"

on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '0 17 * * 5'

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest

permissions:
# required for all workflows
security-events: write

strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
# TODO: Enable for javascript later
language: [ 'go']

steps:
- name: Checkout repository
uses: actions/checkout@v3

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
20 changes: 11 additions & 9 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -3,33 +3,35 @@ name: Go
on:
push:
branches: [ main ]
paths-ignore:
- '**.md'
pull_request:
branches: [ main ]

paths-ignore:
- '**.md'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Setup go
uses: actions/setup-go@v2
uses: actions/setup-go@v3.5.0
with:
go-version: '^1.13'
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup golangci-lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
version: v1.45.2
args: --verbose
version: v1.54
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3.5.0
with:
go-version: 1.16
go-version: 1.13

- name: Build
run: go build -v ./...
33 changes: 25 additions & 8 deletions cache.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package cache
import (
"bytes"
"encoding/gob"
"errors"
"net/http"
"net/url"
"sort"
@@ -85,15 +86,18 @@ func cache(
return
}

if err != persist.ErrCacheMiss {
if !errors.Is(err, persist.ErrCacheMiss) {
cfg.logger.Errorf("get cache error: %s, cache key: %s", err, cacheKey)
}
cfg.missCacheCallback(c)
}

// cache miss, then call the backend

// use responseCacheWriter in order to record the response
cacheWriter := &responseCacheWriter{ResponseWriter: c.Writer}
cacheWriter := &responseCacheWriter{
ResponseWriter: c.Writer,
}
c.Writer = cacheWriter

inFlight := false
@@ -110,7 +114,7 @@ func cache(
inFlight = true

respCache := &ResponseCache{}
respCache.fillWithCacheWriter(cacheWriter)
respCache.fillWithCacheWriter(cacheWriter, cfg)

// only cache 2xx response
if !c.IsAborted() && cacheWriter.Status() < 300 && cacheWriter.Status() >= 200 {
@@ -133,6 +137,10 @@ func cache(
func CacheByRequestURI(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) gin.HandlerFunc {
cfg := newConfigByOpts(opts...)

if cfg.getCacheStrategyByRequest != nil {
return cache(defaultCacheStore, defaultExpire, cfg)
}

var cacheStrategy GetCacheStrategyByRequest
if cfg.ignoreQueryOrder {
cacheStrategy = func(c *gin.Context) (bool, Strategy) {
@@ -211,15 +219,22 @@ type ResponseCache struct {
Data []byte
}

func (c *ResponseCache) fillWithCacheWriter(cacheWriter *responseCacheWriter) {
func (c *ResponseCache) fillWithCacheWriter(cacheWriter *responseCacheWriter, cfg *Config) {
c.Status = cacheWriter.Status()
c.Data = cacheWriter.body.Bytes()
c.Header = cacheWriter.Header().Clone()
if !cfg.withoutHeader {
c.Header = cacheWriter.Header().Clone()

for _, headerKey := range cfg.discardHeaders {
c.Header.Del(headerKey)
}
}
}

// responseCacheWriter
type responseCacheWriter struct {
gin.ResponseWriter

body bytes.Buffer
}

@@ -242,9 +257,11 @@ func replyWithCache(

c.Writer.WriteHeader(respCache.Status)

for key, values := range respCache.Header {
for _, val := range values {
c.Writer.Header().Set(key, val)
if !cfg.withoutHeader {
for key, values := range respCache.Header {
for _, val := range values {
c.Writer.Header().Set(key, val)
}
}
}

121 changes: 109 additions & 12 deletions cache_test.go
Original file line number Diff line number Diff line change
@@ -2,17 +2,18 @@ package cache

import (
"fmt"
"github.com/stretchr/testify/require"
"math/rand"
"net/http"
"net/http/httptest"
"sync"
"sync/atomic"
"testing"
"time"

"github.com/chenyahui/gin-cache/persist"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func init() {
@@ -53,6 +54,26 @@ func TestCacheByRequestPath(t *testing.T) {
assert.Equal(t, w1.Code, w2.Code)
}

func TestCacheHitMissCallback(t *testing.T) {
var cacheHitCount, cacheMissCount int32
memoryStore := persist.NewMemoryStore(1 * time.Minute)
cachePathMiddleware := CacheByRequestPath(memoryStore, 3*time.Second,
WithOnHitCache(func(c *gin.Context) {
atomic.AddInt32(&cacheHitCount, 1)
}),
WithOnMissCache(func(c *gin.Context) {
atomic.AddInt32(&cacheMissCount, 1)
}),
)

mockHttpRequest(cachePathMiddleware, "/cache?uid=u1", true)
mockHttpRequest(cachePathMiddleware, "/cache?uid=u2", true)
mockHttpRequest(cachePathMiddleware, "/cache?uid=u3", true)

assert.Equal(t, cacheHitCount, int32(2))
assert.Equal(t, cacheMissCount, int32(1))
}

func TestCacheDuration(t *testing.T) {
memoryStore := persist.NewMemoryStore(1 * time.Minute)
cacheURIMiddleware := CacheByRequestURI(memoryStore, 3*time.Second)
@@ -109,17 +130,14 @@ func TestHeader(t *testing.T) {

{
engine.ServeHTTP(testWriter, testRequest)
values := testWriter.Header().Values("test_header_key")
assert.Equal(t, 1, len(values))
assert.Equal(t, "test_header_value2", values[0])

value := testWriter.Header().Get("test_header_key")
assert.Equal(t, "test_header_value2", value)
}

{
engine.ServeHTTP(testWriter, testRequest)
values := testWriter.Header().Values("test_header_key")
assert.Equal(t, 1, len(values))
assert.Equal(t, "test_header_value2", values[0])
value := testWriter.Header().Get("test_header_key")
assert.Equal(t, "test_header_value2", value)
}
}

@@ -203,21 +221,100 @@ func TestCacheByRequestURIIgnoreOrder(t *testing.T) {
const prefixKey = "#prefix#"

func TestPrefixKey(t *testing.T) {

memoryStore := persist.NewMemoryStore(1 * time.Minute)
cacheURIMiddleware := CacheByRequestPath(
cachePathMiddleware := CacheByRequestPath(
memoryStore,
3*time.Second,
WithPrefixKey(prefixKey),
)

requestPath := "/cache"

w1 := mockHttpRequest(cacheURIMiddleware, requestPath, true)
w1 := mockHttpRequest(cachePathMiddleware, requestPath, true)

err := memoryStore.Delete(prefixKey + requestPath)
require.NoError(t, err)

w2 := mockHttpRequest(cacheURIMiddleware, requestPath, true)
w2 := mockHttpRequest(cachePathMiddleware, requestPath, true)
assert.NotEqual(t, w1.Body, w2.Body)
}

func TestWithDiscardHeaders(t *testing.T) {
const headerKey = "RandKey"

memoryStore := persist.NewMemoryStore(1 * time.Minute)
cachePathMiddleware := CacheByRequestPath(
memoryStore,
3*time.Second,
WithDiscardHeaders([]string{
headerKey,
}),
)

_, engine := gin.CreateTestContext(httptest.NewRecorder())

engine.GET("/cache", cachePathMiddleware, func(c *gin.Context) {
c.Header(headerKey, fmt.Sprintf("rand:%d", rand.Int()))
c.String(http.StatusOK, "value")
})

testRequest := httptest.NewRequest(http.MethodGet, "/cache", nil)

{
testWriter := httptest.NewRecorder()
engine.ServeHTTP(testWriter, testRequest)
headers1 := testWriter.Header()
assert.NotEqual(t, headers1.Get(headerKey), "")
}

{
testWriter := httptest.NewRecorder()
engine.ServeHTTP(testWriter, testRequest)
headers2 := testWriter.Header()
assert.Equal(t, headers2.Get(headerKey), "")
}
}

func TestCustomCacheStrategy(t *testing.T) {
memoryStore := persist.NewMemoryStore(1 * time.Minute)
cacheMiddleware := Cache(
memoryStore,
24*time.Hour,
WithCacheStrategyByRequest(func(c *gin.Context) (bool, Strategy) {
return true, Strategy{
CacheKey: "custom_cache_key_" + c.Query("uid"),
}
}),
)

_ = mockHttpRequest(cacheMiddleware, "/cache?uid=1", false)

var val interface{}
err := memoryStore.Get("custom_cache_key_1", &val)
assert.Nil(t, err)
}

func TestCacheByRequestURICustomCacheStrategy(t *testing.T) {
const customKey = "CustomKey"
memoryStore := persist.NewMemoryStore(1 * time.Minute)
cacheURIMiddleware := CacheByRequestURI(memoryStore, 1*time.Second, WithCacheStrategyByRequest(func(c *gin.Context) (bool, Strategy) {
return true, Strategy{
CacheKey: customKey,
CacheDuration: 2 * time.Second,
}
}))

w1 := mockHttpRequest(cacheURIMiddleware, "/cache?uid=u1", true)
var val interface{}
err := memoryStore.Get(customKey, &val)
assert.Nil(t, err)
time.Sleep(1 * time.Second)

w2 := mockHttpRequest(cacheURIMiddleware, "/cache?uid=u1", true)
assert.Equal(t, w1.Body, w2.Body)
assert.Equal(t, w1.Code, w2.Code)
time.Sleep(3 * time.Second)

w3 := mockHttpRequest(cacheURIMiddleware, "/cache?uid=u1", true)
assert.NotEqual(t, w1.Body, w3.Body)
}
Loading