Skip to content
This repository has been archived by the owner on Mar 8, 2024. It is now read-only.

Commit

Permalink
feat: add option enable/disable rfc (#22)
Browse files Browse the repository at this point in the history
* feat: add option to disable RFC Compliance

* chore: fix error assignation

* chore: add logger for the failure case

* chore: resolve the comment
  • Loading branch information
bxcodec authored Jun 22, 2020
1 parent 9d8c739 commit f1716d8
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 17 deletions.
2 changes: 1 addition & 1 deletion example_custom_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (c customInMemStorage) Origin() string {

func Example_withCustomStorage() {
client := &http.Client{}
handler, err := httpcache.NewWithCustomStorageCache(client, NewCustomInMemStorage())
handler, err := httpcache.NewWithCustomStorageCache(client, true, NewCustomInMemStorage())
if err != nil {
log.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion example_inmemory_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func Example_inMemoryStorageDefault() {
client := &http.Client{}
handler, err := httpcache.NewWithInmemoryCache(client, time.Second*15)
handler, err := httpcache.NewWithInmemoryCache(client, true, time.Second*15)
if err != nil {
log.Fatal(err)
}
Expand Down
12 changes: 6 additions & 6 deletions httpcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ import (
// NewWithCustomStorageCache will initiate the httpcache with your defined cache storage
// To use your own cache storage handler, you need to implement the cache.Interactor interface
// And pass it to httpcache.
func NewWithCustomStorageCache(client *http.Client, cacheInteractor cache.ICacheInteractor) (cacheHandler *CacheHandler, err error) {
return newClient(client, cacheInteractor)
func NewWithCustomStorageCache(client *http.Client, rfcCompliance bool, cacheInteractor cache.ICacheInteractor) (cacheHandler *CacheHandler, err error) {
return newClient(client, rfcCompliance, cacheInteractor)
}

func newClient(client *http.Client, cacheInteractor cache.ICacheInteractor) (cachedHandler *CacheHandler, err error) {
func newClient(client *http.Client, rfcCompliance bool, cacheInteractor cache.ICacheInteractor) (cachedHandler *CacheHandler, err error) {
if client.Transport == nil {
client.Transport = http.DefaultTransport
}
cachedHandler = NewRoundtrip(client.Transport, cacheInteractor)
cachedHandler = NewRoundtrip(client.Transport, cacheInteractor, rfcCompliance)
client.Transport = cachedHandler
return
}

// NewWithInmemoryCache will create a complete cache-support of HTTP client with using inmemory cache.
// If the duration not set, the cache will use LFU algorithm
func NewWithInmemoryCache(client *http.Client, duration ...time.Duration) (cachedHandler *CacheHandler, err error) {
func NewWithInmemoryCache(client *http.Client, rfcCompliance bool, duration ...time.Duration) (cachedHandler *CacheHandler, err error) {
var expiryTime time.Duration
if len(duration) > 0 {
expiryTime = duration[0]
Expand All @@ -38,5 +38,5 @@ func NewWithInmemoryCache(client *http.Client, duration ...time.Duration) (cache
SetExpiryTime(expiryTime).SetMaxSizeItem(100),
)

return newClient(client, inmem.NewCache(c))
return newClient(client, rfcCompliance, inmem.NewCache(c))
}
54 changes: 46 additions & 8 deletions roundtriper.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ const (
type CacheHandler struct {
DefaultRoundTripper http.RoundTripper
CacheInteractor cache.ICacheInteractor
ComplyRFC bool
}

// NewRoundtrip will create an implementations of cache http roundtripper
func NewRoundtrip(defaultRoundTripper http.RoundTripper, cacheActor cache.ICacheInteractor) *CacheHandler {
func NewRoundtrip(defaultRoundTripper http.RoundTripper, cacheActor cache.ICacheInteractor, rfcCompliance bool) *CacheHandler {
if cacheActor == nil {
log.Fatal("cache interactor is nil")
}
return &CacheHandler{
DefaultRoundTripper: defaultRoundTripper,
CacheInteractor: cacheActor,
ComplyRFC: rfcCompliance,
}
}

Expand Down Expand Up @@ -91,8 +93,7 @@ func validateTheCacheControl(req *http.Request, resp *http.Response) (validation
return validationResult, nil
}

// RoundTrip the implementation of http.RoundTripper
func (r *CacheHandler) RoundTrip(req *http.Request) (resp *http.Response, err error) {
func (r *CacheHandler) roundTripRFCCompliance(req *http.Request) (resp *http.Response, err error) {
allowCache := allowedFromCache(req.Header)
if allowCache {
cachedResp, cachedItem, cachedErr := getCachedResponse(r.CacheInteractor, req)
Expand All @@ -111,18 +112,21 @@ func (r *CacheHandler) RoundTrip(req *http.Request) (resp *http.Response, err er
return
}

validationResult, err := validateTheCacheControl(req, resp)
if err != nil {
return
validationResult, errValidation := validateTheCacheControl(req, resp)
if errValidation != nil {
log.Printf("Can't validate the response to RFC 7234, plase check. Err: %v\n", errValidation)
return // return directly, not sure can be stored or not
}

if validationResult.OutErr != nil {
return
log.Printf("Can't validate the response to RFC 7234, plase check. Err: %v\n", validationResult.OutErr)
return // return directly, not sure can be stored or not
}

// reasons to not to cache
if len(validationResult.OutReasons) > 0 {
return
log.Printf("Can't validate the response to RFC 7234, plase check. Err: %v\n", validationResult.OutReasons)
return // return directly, not sure can be stored or not.
}

err = storeRespToCache(r.CacheInteractor, req, resp)
Expand All @@ -134,6 +138,40 @@ func (r *CacheHandler) RoundTrip(req *http.Request) (resp *http.Response, err er
return
}

// RoundTrip the implementation of http.RoundTripper
func (r *CacheHandler) RoundTrip(req *http.Request) (resp *http.Response, err error) {
if r.ComplyRFC {
return r.roundTripRFCCompliance(req)
}
cachedResp, cachedItem, cachedErr := getCachedResponse(r.CacheInteractor, req)
if cachedResp != nil && cachedErr == nil {
buildTheCachedResponseHeader(cachedResp, cachedItem, r.CacheInteractor.Origin())
return cachedResp, cachedErr
}
// if error when getting from cachce, ignore it, re-try a live version
if cachedErr != nil {
log.Println(cachedErr, "failed to retrieve from cache, trying with a live version")
}

resp, err = r.DefaultRoundTripper.RoundTrip(req)
if err != nil {
return
}

err = storeRespToCache(r.CacheInteractor, req, resp)
if err != nil {
log.Printf("Can't store the response to database, plase check. Err: %v\n", err)
err = nil // set err back to nil to make the call still success.
}
return
}

// RFC7234Compliance used for enable/disable the RFC 7234 compliance
func (r *CacheHandler) RFC7234Compliance(val bool) *CacheHandler {
r.ComplyRFC = val
return r
}

func storeRespToCache(cacheInteractor cache.ICacheInteractor, req *http.Request, resp *http.Response) (err error) {
cachedResp := cache.CachedResponse{
RequestMethod: req.Method,
Expand Down
2 changes: 1 addition & 1 deletion roundtripper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestSetToCacheRoundtrip(t *testing.T) {
mockCacheInteractor.On("Get", mock.AnythingOfType("string")).Once().Return(cachedResponse, errors.New("uknown error"))
mockCacheInteractor.On("Set", mock.AnythingOfType("string"), mock.Anything).Once().Return(nil)
client := &http.Client{}
client.Transport = httpcache.NewRoundtrip(http.DefaultTransport, mockCacheInteractor)
client.Transport = httpcache.NewRoundtrip(http.DefaultTransport, mockCacheInteractor, true)
// HTTP GET 200
jsonResp := []byte(`{"message": "Hello World!"}`)
handler := func() (res http.Handler) {
Expand Down

0 comments on commit f1716d8

Please sign in to comment.