Skip to content

Commit

Permalink
beatlabs#128 expose cache functionality as a middleware
Browse files Browse the repository at this point in the history
Signed-off-by: Vangelis Katikaridis <[email protected]>
  • Loading branch information
drakos74 committed May 2, 2020
1 parent 039a3b4 commit e518a9b
Show file tree
Hide file tree
Showing 10 changed files with 501 additions and 550 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,20 +200,22 @@ routeWithAuth := NewAuthRoute("/index", "GET" ProcessorFunc, true, Authendicator
The caching layer for HTTP routes is specified per Route.

```go
// routeCache is the builder needed to build a cache for the corresponding route
type routeCache struct {
// RouteCache is the builder needed to build a cache for the corresponding route
type RouteCache struct {
// cache is the ttl cache implementation to be used
cache cache.TTLCache
// age specifies the minimum and maximum amount for max-age and min-fresh header values respectively
// regarding the client cache-control requests in seconds
age age
}

func NewRouteCache(ttlCache cache.TTLCache, age Age) *RouteCache
```

#### server cache
- The **cache key** is based on the route path and the url request parameters.
- The server caches only **GET requests**.
- The server implementation must specify **Age** parameters upon construction.
- The server implementation must specify an **Age** parameters upon construction.
- Age with **Min=0** and **Max=0** effectively disables caching
- The route should return always the most fresh object instance.
- An **ETag header** must be always in responses that are part of the cache, representing the hash of the response.
Expand All @@ -229,6 +231,25 @@ That implies that all generic handler functionalities MUST be delegated to a cus
i.e. counting number of server client requests etc ...
```

### Usage

- provide the cache in the route builder
```go
NewRouteBuilder("/", handler).
WithRouteCache(cache, http.Age{
Min: 30 * time.Minute,
Max: 1 * time.Hour,
}).
MethodGet()
```

- use the cache as a middleware
```go
NewRouteBuilder("/", handler).
WithMiddlewares(NewCachingMiddleware(NewRouteCache(cc, Age{Max: 10 * time.Second}))).
MethodGet()
```

#### client cache-control
The client can control the cache with the appropriate Headers
- `max-age=?`
Expand Down Expand Up @@ -297,6 +318,7 @@ improvement for big response objects.
- we could extend the metrics to use the key of the object as a label as well for more fine-grained tuning.
But this has been left out for now, due to the potentially huge number of metric objects.
We can review according to usage or make this optional in the future.
- improve the serialization performance for the cache response objects

### Asynchronous

Expand Down
6 changes: 3 additions & 3 deletions component/http/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type executor func(now int64, key string) *CachedResponse
// cacheHandler wraps the an execution logic with a cache layer
// exec is the processor func that the cache will wrap
// rc is the route cache implementation to be used
func cacheHandler(exec executor, rc *routeCache) func(request *cacheHandlerRequest) (response *CacheHandlerResponse, e error) {
func cacheHandler(exec executor, rc *RouteCache) func(request *cacheHandlerRequest) (response *CacheHandlerResponse, e error) {

return func(request *cacheHandlerRequest) (response *CacheHandlerResponse, e error) {

Expand Down Expand Up @@ -114,7 +114,7 @@ func cacheHandler(exec executor, rc *routeCache) func(request *cacheHandlerReque

// getResponse will get the appropriate Response either using the cache or the executor,
// depending on the
func getResponse(cfg *cacheControl, path, key string, now int64, rc *routeCache, exec executor) *CachedResponse {
func getResponse(cfg *cacheControl, path, key string, now int64, rc *RouteCache, exec executor) *CachedResponse {

if cfg.noCache {
return exec(now, key)
Expand Down Expand Up @@ -166,7 +166,7 @@ func isValid(age, maxAge int64, validators ...validator) (bool, validationContex

// getFromCache is the implementation that will provide a CachedResponse instance from the cache,
// if it exists
func getFromCache(key string, rc *routeCache) *CachedResponse {
func getFromCache(key string, rc *RouteCache) *CachedResponse {
if resp, ok, err := rc.cache.Get(key); ok && err == nil {
if b, ok := resp.([]byte); ok {
r := &CachedResponse{}
Expand Down
57 changes: 19 additions & 38 deletions component/http/cache_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ type cacheHandlerRequest struct {
query string
}

// toCacheHandlerRequest transforms the http Request object to the cache handler request
func toCacheHandlerRequest(req *http.Request) *cacheHandlerRequest {
var header string
if req.Header != nil {
header = req.Header.Get(cacheControlHeader)
}
var path string
var query string
if req.URL != nil {
path = req.URL.Path
query = req.URL.RawQuery
}
return &cacheHandlerRequest{
header: header,
path: path,
query: query,
}
}

// getKey generates a unique cache key based on the route path and the query parameters
func (c *cacheHandlerRequest) getKey() string {
return fmt.Sprintf("%s:%s", c.path, c.query)
Expand Down Expand Up @@ -45,41 +64,3 @@ func (c *CachedResponse) encode() ([]byte, error) {
func (c *CachedResponse) decode(data []byte) error {
return json.Unmarshal(data, c)
}

// fromRequest transforms the Request object to the cache handler request
func fromRequest(path string, req *Request) *cacheHandlerRequest {
var header string
if req.Headers != nil {
header = req.Headers[cacheControlHeader]
}
var query string
if req.Fields != nil {
if fields, err := json.Marshal(req.Fields); err == nil {
query = string(fields)
}
}
return &cacheHandlerRequest{
header: header,
path: path,
query: query,
}
}

// fromHTTPRequest transforms the http Request object to the cache handler request
func fromHTTPRequest(req *http.Request) *cacheHandlerRequest {
var header string
if req.Header != nil {
header = req.Header.Get(cacheControlHeader)
}
var path string
var query string
if req.URL != nil {
path = req.URL.Path
query = req.URL.RawQuery
}
return &cacheHandlerRequest{
header: header,
path: path,
query: query,
}
}
Loading

0 comments on commit e518a9b

Please sign in to comment.