Skip to content

Commit

Permalink
Add Ability to provide non-cacheable headers
Browse files Browse the repository at this point in the history
  • Loading branch information
fr05t1k committed Dec 12, 2019
1 parent fe78e97 commit 5f67ae8
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 35 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func main() {
cache.ClientWithAdapter(memcached),
cache.ClientWithTTL(10 * time.Minute),
cache.ClientWithRefreshKey("opn"),
cache.ClientWithNonCachedHeaders([]string{"geo-country"}),
)
if err != nil {
fmt.Println(err)
Expand Down Expand Up @@ -137,4 +138,4 @@ http-cache memory adapter takes way less GC pause time, that means smaller GC ov
- [Redis adapter](https://godoc.org/github.com/victorspringer/http-cache/adapter/redis)

## License
http-cache is released under the [MIT License](https://github.com/victorspringer/http-cache/blob/master/LICENSE).
http-cache is released under the [MIT License](https://github.com/victorspringer/http-cache/blob/master/LICENSE).
64 changes: 52 additions & 12 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ type Response struct {

// Client data structure for HTTP cache middleware.
type Client struct {
adapter Adapter
ttl time.Duration
refreshKey string
methods []string
adapter Adapter
ttl time.Duration
refreshKey string
methods []string
nonCacheableHeaders []string
}

// ClientOption is used to set Client settings.
Expand All @@ -89,7 +90,9 @@ func (c *Client) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if c.cacheableMethod(r.Method) {
sortURLParams(r.URL)
key := generateKey(r.URL.String())
headerValues := extractHeaders(c.nonCacheableHeaders, r.Header)

key := generateKey(r.URL.String(), headerValues)
if r.Method == http.MethodPost && r.Body != nil {
body, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
Expand All @@ -98,7 +101,7 @@ func (c *Client) Middleware(next http.Handler) http.Handler {
return
}
reader := ioutil.NopCloser(bytes.NewBuffer(body))
key = generateKeyWithBody(r.URL.String(), body)
key = generateKeyWithBody(r.URL.String(), headerValues, body)
r.Body = reader
}

Expand All @@ -107,7 +110,7 @@ func (c *Client) Middleware(next http.Handler) http.Handler {
delete(params, c.refreshKey)

r.URL.RawQuery = params.Encode()
key = generateKey(r.URL.String())
key = generateKey(r.URL.String(), headerValues)

c.adapter.Release(key)
} else {
Expand Down Expand Up @@ -202,21 +205,49 @@ func KeyAsString(key uint64) string {
return strconv.FormatUint(key, 36)
}

func generateKey(URL string) uint64 {
func generateKey(URL string, headerValues []string) uint64 {
builder := strings.Builder{}
builder.WriteString(URL)

for _, value := range headerValues {
builder.WriteString(value)
}

hash := fnv.New64a()
hash.Write([]byte(URL))
hash.Write([]byte(builder.String()))

return hash.Sum64()
}

func generateKeyWithBody(URL string, body []byte) uint64 {
func generateKeyWithBody(URL string, headerValues []string, body []byte) uint64 {
builder := strings.Builder{}
builder.WriteString(URL)

for _, value := range headerValues {
builder.WriteString(value)
}

builder.Write(body)

hash := fnv.New64a()
body = append([]byte(URL), body...)
hash.Write(body)
hash.Write([]byte(builder.String()))

return hash.Sum64()
}

func extractHeaders(nonCachedHeaders []string, headers http.Header) []string {
var headerValues []string

for _, nonCachedHeader := range nonCachedHeaders {
headerValue, ok := headers[nonCachedHeader]
if ok {
headerValues = append(headerValues, headerValue...)
}
}

return headerValues
}

// NewClient initializes the cache HTTP middleware client with the given
// options.
func NewClient(opts ...ClientOption) (*Client, error) {
Expand Down Expand Up @@ -285,3 +316,12 @@ func ClientWithMethods(methods []string) ClientOption {
return nil
}
}

// ClientWithNonCacheableHeaders sets the un-cacheable headers.
// If you provide []string{"geo-country"} cache will be missed with different "geo-country" header but same URL.
func ClientWithNonCacheableHeaders(headers []string) ClientOption {
return func(c *Client) error {
c.nonCacheableHeaders = headers
return nil
}
}
Loading

0 comments on commit 5f67ae8

Please sign in to comment.