httpcache

package module
v0.0.0-...-5a2aa12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2023 License: MIT Imports: 16 Imported by: 0

README

httpcache, inject-able HTTP cache in Golang

Howdy there!!!

Usually when we want to integrate with cache (let's say Redis), we usually have to do many changes in our code. What if, we just inject the cache to the HTTP client. So we don't have to create many changes in every line of our code to support the cache features? With only less than 10 line of code, you can got a complete implementations of HTTP Cache based on RFC 7234

Build Status License GoDoc Go.Dev

This package is used for caching your http request results from the server. Example how to use can be seen below.

Index

Support

You can file an Issue. See documentation in Godoc or in go.dev

Getting Started

Download
go get -u github.com/bxcodec/httpcache

Example with Inmemory Storage


Example how to use more details can be seen in the example file: ./example_inmemory_storage_test.go

Short example:


// Inject the HTTP Client with httpcache
client := &http.Client{}
_, err := httpcache.NewWithInmemoryCache(client, true, time.Second*60)
if err != nil {
  log.Fatal(err)
}

// And your HTTP Client already supported for HTTP Cache
// To verify you can run a request in a loop

for i:=0; i< 10; i++ {
  startTime := time.Now()
  req, err := http.NewRequest("GET", "https://imantumorang.com", nil)
  if err != nil {
    log.Fatal((err))
  }

  res, err := client.Do(req)
  if err != nil {
    log.Fatal(err)
  }

  fmt.Printf("Response time: %vms\n", time.Since(startTime).Microseconds())
  fmt.Println("Status Code", res.StatusCode)
}
// See the response time, it will different on each request and will go smaller.

Example with Custom Storage

You also can use your own custom storage, what you need to do is implement the cache.ICacheInteractor interface. Example how to use more details can be seen in the example file: ./example_custom_storage_test.go

Example:

client := &http.Client{}
_, err := httpcache.NewWithCustomStorageCache(client,true, mystorage.NewCustomInMemStorage())
if err != nil {
	log.Fatal(err)
}
About RFC 7234 Compliance

You can disable/enable the RFC Compliance as you want. If RFC 7234 is too complex for you, you can just disable it by set the RFCCompliance parameter to false

_, err := httpcache.NewWithInmemoryCache(client, false, time.Second*60)
// or
_, err := httpcache.NewWithCustomStorageCache(client,false, mystorage.NewCustomInMemStorage())

The downside of disabling the RFC Compliance, All the response/request will be cached automatically. Do with caution.

TODOs

Inspirations and Thanks

  • pquerna/cachecontrol for the Cache-Header Extraction
  • bxcodec/gothca for in-memory cache. *Notes: if you find another library that has a better way for inmemm cache, please raise an issue or submit a PR

Contribution


To contrib to this project, you can open a PR or an issue.

Documentation

Overview

Example (InMemoryStorageDefault)
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/aibarbetta/httpcache"
)

func main() {
	client := &http.Client{}
	handler, err := httpcache.NewWithInmemoryCache(client, true, false, time.Second*15)
	if err != nil {
		log.Fatal(err)
	}

	processCachedRequest(client, handler)
	// Example Output:
	/*
		2020/06/21 13:14:51 Cache item's missing failed to retrieve from cache, trying with a live version
		Response time: 940086 micro-second
		Status Code 200
		Sequence >>>  0
		2020/06/21 13:14:53 Cache item's missing failed to retrieve from cache, trying with a live version
		Response time: 73679 micro-second
		Status Code 200
		Sequence >>>  1
		Response time: 126 micro-second
		Status Code 200
		Sequence >>>  2
		Response time: 96 micro-second
		Status Code 200
		Sequence >>>  3
		Response time: 102 micro-second
		Status Code 200
		Sequence >>>  4
		Response time: 94 micro-second
		Status Code 200
		Sequence >>>  5
	*/
}

func processCachedRequest(client *http.Client, handler *httpcache.CacheHandler) {
	for i := 0; i < 100; i++ {
		startTime := time.Now()
		req, err := http.NewRequestWithContext(context.TODO(), "GET", "https://imantumorang.com", http.NoBody)
		if err != nil {
			log.Fatal((err))
		}
		res, err := client.Do(req)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("Response time: %v micro-second\n", time.Since(startTime).Microseconds())
		fmt.Println("Status Code", res.StatusCode)
		time.Sleep(time.Second * 1)
		fmt.Println("Sequence >>> ", i)
		if i%5 == 0 {
			err := handler.CacheInteractor.Flush()
			if err != nil {
				log.Fatal(err)
			}
		}
		res.Body.Close()
	}
}
Output:

Example (RedisStorage)
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/aibarbetta/httpcache"
	"github.com/aibarbetta/httpcache/cache/redis"
)

func main() {
	client := &http.Client{}
	handler, err := httpcache.NewWithRedisCache(client, true, false, &redis.CacheOptions{
		Addr: "localhost:6379",
	}, time.Second*15)
	if err != nil {
		log.Fatal(err)
	}

	processCachedRequest(client, handler)
	// Example Output:
	/*
		2020/06/21 13:14:51 Cache item's missing failed to retrieve from cache, trying with a live version
		Response time: 940086 micro-second
		Status Code 200
		Sequence >>>  0
		2020/06/21 13:14:53 Cache item's missing failed to retrieve from cache, trying with a live version
		Response time: 73679 micro-second
		Status Code 200
		Sequence >>>  1
		Response time: 126 micro-second
		Status Code 200
		Sequence >>>  2
		Response time: 96 micro-second
		Status Code 200
		Sequence >>>  3
		Response time: 102 micro-second
		Status Code 200
		Sequence >>>  4
		Response time: 94 micro-second
		Status Code 200
		Sequence >>>  5
	*/
}

func processCachedRequest(client *http.Client, handler *httpcache.CacheHandler) {
	for i := 0; i < 100; i++ {
		startTime := time.Now()
		req, err := http.NewRequestWithContext(context.TODO(), "GET", "https://imantumorang.com", http.NoBody)
		if err != nil {
			log.Fatal((err))
		}
		res, err := client.Do(req)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("Response time: %v micro-second\n", time.Since(startTime).Microseconds())
		fmt.Println("Status Code", res.StatusCode)
		time.Sleep(time.Second * 1)
		fmt.Println("Sequence >>> ", i)
		if i%5 == 0 {
			err := handler.CacheInteractor.Flush()
			if err != nil {
				log.Fatal(err)
			}
		}
		res.Body.Close()
	}
}
Output:

Example (WithCustomStorage)
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/aibarbetta/httpcache"
	"github.com/aibarbetta/httpcache/cache"
	patrickCache "github.com/patrickmn/go-cache"
)

type customInMemStorage struct {
	cacheHandler *patrickCache.Cache
}

// NewCustomInMemStorage will return a custom in memory cache
func NewCustomInMemStorage() cache.ICacheInteractor {
	return &customInMemStorage{
		cacheHandler: patrickCache.New(patrickCache.DefaultExpiration, time.Second*10),
	}
}

func (c customInMemStorage) Set(key string, value cache.CachedResponse) error { //nolint
	c.cacheHandler.Set(key, value, patrickCache.DefaultExpiration)
	return nil
}

func (c customInMemStorage) Get(key string) (res cache.CachedResponse, err error) {
	cachedRes, ok := c.cacheHandler.Get(key)
	if !ok {
		err = cache.ErrCacheMissed
		return
	}
	res, ok = cachedRes.(cache.CachedResponse)
	if !ok {
		err = cache.ErrInvalidCachedResponse
		return
	}
	return
}
func (c customInMemStorage) Delete(key string) error {
	c.cacheHandler.Delete(key)
	return nil
}
func (c customInMemStorage) Flush() error {
	c.cacheHandler.Flush()
	return nil
}
func (c customInMemStorage) Origin() string {
	return "MY-OWN-CUSTOM-INMEMORY-CACHED"
}

func main() {
	client := &http.Client{}
	handler, err := httpcache.NewWithCustomStorageCache(client, true, false, NewCustomInMemStorage())
	if err != nil {
		log.Fatal(err)
	}

	for i := 0; i < 100; i++ {
		startTime := time.Now()
		req, err := http.NewRequestWithContext(context.TODO(), "GET", "https://imantumorang.com", http.NoBody)
		if err != nil {
			log.Fatal((err))
		}
		res, err := client.Do(req)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("Response time: %v micro-second\n", time.Since(startTime).Microseconds())
		fmt.Println("Status Code", res.StatusCode)
		time.Sleep(time.Second * 1)
		fmt.Println("Sequence >>> ", i)
		if i%5 == 0 {
			err := handler.CacheInteractor.Flush()
			if err != nil {
				log.Fatal(err)
			}
		}
		res.Body.Close()
	}
	// Example Output:
	/*
		2020/06/21 13:14:51 Cache item's missing failed to retrieve from cache, trying with a live version
		Response time: 940086 micro-second
		Status Code 200
		Sequence >>>  0
		2020/06/21 13:14:53 Cache item's missing failed to retrieve from cache, trying with a live version
		Response time: 73679 micro-second
		Status Code 200
		Sequence >>>  1
		Response time: 126 micro-second
		Status Code 200
		Sequence >>>  2
		Response time: 96 micro-second
		Status Code 200
		Sequence >>>  3
		Response time: 102 micro-second
		Status Code 200
		Sequence >>>  4
		Response time: 94 micro-second
		Status Code 200
		Sequence >>>  5
	*/
}
Output:

Index

Examples

Constants

View Source
const (
	HeaderAuthorization = "Authorization"
	HeaderCacheControl  = "Cache-Control"
	// To indicate that the response is got from this httpcache library
	XFromHache   = "X-HTTPCache"
	XHacheOrigin = "X-HTTPCache-Origin"
)

Headers

View Source
const (
	MaxSizeCacheItem = 100
)

Variables

This section is empty.

Functions

This section is empty.

Types

type CacheHandler

type CacheHandler struct {
	DefaultRoundTripper http.RoundTripper
	CacheInteractor     cache.ICacheInteractor
	ComplyRFC           bool
	IsPrivateCache      bool
}

CacheHandler custom plugable' struct of implementation of the http.RoundTripper

func NewCacheHandlerRoundtrip

func NewCacheHandlerRoundtrip(defaultRoundTripper http.RoundTripper, rfcCompliance, isPrivateCache bool, cacheActor cache.ICacheInteractor) *CacheHandler

NewCacheHandlerRoundtrip will create an implementations of cache http roundtripper

func NewWithCustomStorageCache

func NewWithCustomStorageCache(client *http.Client, rfcCompliance, isPrivateCache bool,
	cacheInteractor cache.ICacheInteractor) (cacheHandler *CacheHandler, err error)

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 NewWithInmemoryCache

func NewWithInmemoryCache(client *http.Client, rfcCompliance, isPrivateCache bool, duration ...time.Duration) (cachedHandler *CacheHandler, err error)

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 NewWithRedisCache

func NewWithRedisCache(client *http.Client, rfcCompliance, isPrivateCache bool, options *rediscache.CacheOptions,
	duration ...time.Duration) (cachedHandler *CacheHandler, err error)

NewWithRedisCache will create a complete cache-support of HTTP client with using redis cache. If the duration not set, the cache will use LFU algorithm

func (*CacheHandler) RFC7234Compliance

func (r *CacheHandler) RFC7234Compliance(val bool) *CacheHandler

RFC7234Compliance used for enable/disable the RFC 7234 compliance

func (*CacheHandler) RoundTrip

func (r *CacheHandler) RoundTrip(req *http.Request) (resp *http.Response, err error)

RoundTrip the implementation of http.RoundTripper

Directories

Path Synopsis
helper

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL