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

refactor(in-memory): resolve the general function #11

Merged
merged 5 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.vscode
.vscode
sample
bin/
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mockery-prepare:
@echo "Installing mockery"
@go get -u github.com/vektra/mockery
16 changes: 6 additions & 10 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,16 @@ type Interactor interface {

// CachedResponse represent the cacher struct item
type CachedResponse struct {
StatusCode int `json:"statusCode"`
DumpedResponse []byte `json:"response"`
DumpedBody []byte `json:"body"`
RequestURI string `json:"requestUri"`
RequestMethod string `json:"requestMethod"`
CachedTime time.Time `json:"cachedTime"`
// StatusCode int `json:"statusCode"`
DumpedResponse []byte `json:"response"`
// DumpedBody []byte `json:"body"`
RequestURI string `json:"requestUri"`
RequestMethod string `json:"requestMethod"`
CachedTime time.Time `json:"cachedTime"`
}

// Validate will validate the cached response
func (c *CachedResponse) Validate() (err error) {
if c.StatusCode == 0 {
return ErrInvalidCachedResponse
}

if c.RequestMethod == "" {
return ErrInvalidCachedResponse
}
Expand Down
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module github.com/bxcodec/hache

require (
github.com/bxcodec/gotcha v1.0.0-beta.2
github.com/hashicorp/golang-lru v0.5.1
)
go 1.12

require github.com/bxcodec/gotcha v1.0.0-beta.2
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
github.com/bxcodec/gotcha v1.0.0-beta.2 h1:0jY/Mx6O5jzM2fkcz84zzyy67hLu/bKGJEFTtcRmw5I=
github.com/bxcodec/gotcha v1.0.0-beta.2/go.mod h1:MEL9PRYL9Squu1zxreMIzJU6xtMouPmQybWEtXrL1nk=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
10 changes: 4 additions & 6 deletions hache.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ func New(client *http.Client, cacheInteractor cache.Interactor) (err error) {
}

func newClient(client *http.Client, cacheInteractor cache.Interactor) (err error) {
roundtrip := &RoundTrip{
DefaultRoundTripper: client.Transport,
CacheInteractor: cacheInteractor,
if client.Transport == nil {
client.Transport = http.DefaultTransport
}
client.Transport = roundtrip
client.Transport = NewRoundtrip(client.Transport, cacheInteractor)
return
}

Expand All @@ -37,6 +36,5 @@ func NewWithInmemoryCache(client *http.Client, duration ...time.Duration) (err e
SetExpiryTime(expiryTime).SetMaxSizeItem(100),
)

newClient(client, inmem.NewCache(c))
return
return newClient(client, inmem.NewCache(c))
}
101 changes: 31 additions & 70 deletions roundtriper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"strings"
Expand All @@ -15,57 +15,13 @@ import (

// Headers
const (
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderCacheControl = "Cache-Control"
HeaderSetCookie = "Set-Cookie"
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXRealIP = "X-Real-IP"
HeaderXRequestID = "X-Request-ID"
HeaderXRequestedWith = "X-Requested-With"
HeaderServer = "Server"
HeaderOrigin = "Origin"

// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age"

// Security
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderXFrameOptions = "X-Frame-Options"
HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderXCSRFToken = "X-CSRF-Token"
HeaderAuthorization = "Authorization"
HeaderCacheControl = "Cache-Control"
)

var (
// CachedAuthorizedRequest used for determine that a request with Authorization header should be cached or not
CachedAuthorizedRequest = false // TODO(bxcodec): Need to revised about this feature
// CacheAuthorizedRequest used for determine that a request with Authorization header should be cached or not
CacheAuthorizedRequest = false // TODO(bxcodec): Need to revised about this feature
)

// RoundTrip custom plugable' struct of implementation of the http.RoundTripper
Expand All @@ -74,16 +30,24 @@ type RoundTrip struct {
CacheInteractor cache.Interactor
}

// NewRoundtrip will create an implementations of cache http roundtripper
func NewRoundtrip(defaultRoundTripper http.RoundTripper, cacheActor cache.Interactor) http.RoundTripper {
return &RoundTrip{
DefaultRoundTripper: defaultRoundTripper,
CacheInteractor: cacheActor,
}
}

// RoundTrip the implementation of http.RoundTripper
func (r *RoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) {
if allowedFromCache(req) {
resp, err = getCachedResponse(r.CacheInteractor, req)
resp, cachedItem, err := getCachedResponse(r.CacheInteractor, req)
if resp != nil && err == nil {
buildTheCachedResponseHeader(resp)
return
buildTheCachedResponseHeader(resp, cachedItem)
return resp, err
}
}

err = nil
resp, err = r.DefaultRoundTripper.RoundTrip(req)
if err != nil {
return
Expand All @@ -93,25 +57,21 @@ func (r *RoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error
return
}

storeRespToCache(r.CacheInteractor, req, resp)
err = storeRespToCache(r.CacheInteractor, req, resp)
if err != nil {
log.Println(err)
}

return
}

func storeRespToCache(cacheInteractor cache.Interactor, req *http.Request, resp *http.Response) (err error) {
cachedResp := cache.CachedResponse{
StatusCode: resp.StatusCode,
RequestMethod: req.Method,
RequestURI: req.RequestURI,
CachedTime: time.Now(),
}

bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}

resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
cachedResp.DumpedBody = bodyBytes
dumpedResponse, err := httputil.DumpResponse(resp, true)
if err != nil {
return
Expand All @@ -121,8 +81,8 @@ func storeRespToCache(cacheInteractor cache.Interactor, req *http.Request, resp
return
}

func getCachedResponse(cacheInteractor cache.Interactor, req *http.Request) (resp *http.Response, err error) {
cachedResp, err := cacheInteractor.Get(getCacheKey(req))
func getCachedResponse(cacheInteractor cache.Interactor, req *http.Request) (resp *http.Response, cachedResp cache.CachedResponse, err error) {
cachedResp, err = cacheInteractor.Get(getCacheKey(req))
if err != nil {
return
}
Expand All @@ -132,12 +92,13 @@ func getCachedResponse(cacheInteractor cache.Interactor, req *http.Request) (res
if err != nil {
return
}

return
}

func getCacheKey(req *http.Request) (key string) {
key = fmt.Sprintf("%s %s", req.Method, req.RequestURI)
if (CachedAuthorizedRequest ||
if (CacheAuthorizedRequest ||
(strings.ToLower(req.Header.Get(HeaderCacheControl)) == "private")) &&
req.Header.Get(HeaderAuthorization) != "" {
key = fmt.Sprintf("%s %s", key, req.Header.Get(HeaderAuthorization))
Expand All @@ -146,21 +107,22 @@ func getCacheKey(req *http.Request) (key string) {
}

// buildTheCachedResponse will finalize the response header
func buildTheCachedResponseHeader(resp *http.Response) {
panic("TODO: (bxcodec) Add the header based on RFC 7234")
func buildTheCachedResponseHeader(resp *http.Response, cachedResp cache.CachedResponse) {
resp.Header.Add("Expires", cachedResp.CachedTime.String())
// TODO: (bxcodec) add more headers related to cache
}

// check the header if the response will cached or not
func allowedToCache(req *http.Request, resp *http.Response) (ok bool) {
// A request with authorization header must not be cached
// https://tools.ietf.org/html/rfc7234#section-3.2
// Unless configured by user to cache request by authorization
if ok = !CachedAuthorizedRequest && req.Header.Get(HeaderAuthorization) != ""; !ok {
if ok = (!CacheAuthorizedRequest && req.Header.Get(HeaderAuthorization) == ""); !ok {
return
}

// check if the request method allowed to be cached
if ok = !requestMethodValid(req); !ok {
if ok = requestMethodValid(req); !ok {
return
}

Expand All @@ -176,7 +138,6 @@ func allowedToCache(req *http.Request, resp *http.Response) (ok bool) {
if ok = resp.StatusCode == http.StatusOK; !ok {
return
}

return
}

Expand Down
13 changes: 13 additions & 0 deletions roundtripper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package hache_test

import (
"net/http"
"testing"

"github.com/bxcodec/hache"
)

func TestRoundtrip(t *testing.T) {
client := &http.Client{}
client.Transport = hache.NewRoundtrip(client.Transport, nil)
}