Skip to content

Commit 4434dd5

Browse files
committed
internal/frontend: add an interface for creating request caches
This change adds a new Cacher interface that is used to create middlewares for caching requests. This abstracts away the use of redis so that the frontend doesn't depend on redis. The tests still depend on redis for the 404 page testing logic, but the 404 page logic will be moved out into a different package so those tests will go too. The Expirer and Middleware interfaces are not present on the Cache function so that the interface can be defined in package internal/frontend without needing the dependency on the Middleware package. For golang/go#61399 Change-Id: I6518b2ed1d772cb4deda3308c4190f0f1b8a35a0 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/514518 kokoro-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Jamal Carvalho <[email protected]> Run-TryBot: Michael Matloob <[email protected]>
1 parent 7ec3a4e commit 4434dd5

File tree

7 files changed

+49
-22
lines changed

7 files changed

+49
-22
lines changed

cmd/frontend/main.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,19 @@ func main() {
132132
}
133133

134134
router := dcensus.NewRouter(frontend.TagRoute)
135-
var cacheClient *redis.Client
135+
var redisClient *redis.Client
136+
var cacher frontend.Cacher
136137
if cfg.RedisCacheHost != "" {
137138
addr := cfg.RedisCacheHost + ":" + cfg.RedisCachePort
138-
cacheClient = redis.NewClient(&redis.Options{Addr: addr})
139-
if err := cacheClient.Ping(ctx).Err(); err != nil {
139+
redisClient := redis.NewClient(&redis.Options{Addr: addr})
140+
if err := redisClient.Ping(ctx).Err(); err != nil {
140141
log.Errorf(ctx, "redis at %s: %v", addr, err)
141142
} else {
142143
log.Infof(ctx, "connected to redis at %s", addr)
143144
}
145+
cacher = middleware.NewCacher(redisClient)
144146
}
145-
server.Install(router.Handle, cacheClient, cfg.AuthValues)
147+
server.Install(router.Handle, cacher, cfg.AuthValues)
146148
views := append(dcensus.ServerViews,
147149
postgres.SearchLatencyDistribution,
148150
postgres.SearchResponseCount,
@@ -184,7 +186,7 @@ func main() {
184186
middleware.AcceptRequests(http.MethodGet, http.MethodPost, http.MethodHead), // accept only GETs, POSTs and HEADs
185187
middleware.BetaPkgGoDevRedirect(),
186188
middleware.GodocOrgRedirect(),
187-
middleware.Quota(cfg.Quota, cacheClient),
189+
middleware.Quota(cfg.Quota, redisClient),
188190
middleware.SecureHeaders(!*disableCSP), // must come before any caching for nonces to work
189191
middleware.Experiment(experimenter),
190192
middleware.Panic(panicHandler),

internal/frontend/frontend_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"testing"
1111
"time"
1212

13-
"github.com/go-redis/redis/v8"
1413
"github.com/google/safehtml/template"
1514
"golang.org/x/pkgsite/internal"
1615
"golang.org/x/pkgsite/internal/middleware"
@@ -46,7 +45,7 @@ type testPackage struct {
4645
docs []*internal.Documentation
4746
}
4847

49-
func newTestServer(t *testing.T, proxyModules []*proxytest.Module, redisClient *redis.Client, experimentNames ...string) (*Server, http.Handler, func()) {
48+
func newTestServer(t *testing.T, proxyModules []*proxytest.Module, cacher Cacher, experimentNames ...string) (*Server, http.Handler, func()) {
5049
t.Helper()
5150
proxyClient, teardown := proxytest.SetupTestClient(t, proxyModules)
5251
sourceClient := source.NewClient(sourceTimeout)
@@ -72,7 +71,7 @@ func newTestServer(t *testing.T, proxyModules []*proxytest.Module, redisClient *
7271
t.Fatal(err)
7372
}
7473
mux := http.NewServeMux()
75-
s.Install(mux.Handle, redisClient, nil)
74+
s.Install(mux.Handle, cacher, nil)
7675

7776
var exps []*internal.Experiment
7877
for _, n := range experimentNames {

internal/frontend/server.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"time"
2323

2424
"cloud.google.com/go/errorreporting"
25-
"github.com/go-redis/redis/v8"
2625
"github.com/google/safehtml"
2726
"github.com/google/safehtml/template"
2827
"golang.org/x/pkgsite/internal"
@@ -127,24 +126,36 @@ func NewServer(scfg ServerConfig) (_ *Server, err error) {
127126
return s, nil
128127
}
129128

129+
// A Cacher is used to create request caches for http handlers.
130+
type Cacher interface {
131+
// Cache returns a new middleware that caches every request.
132+
// The name of the cache is used only for metrics.
133+
// The expirer is a func that is used to map a new request to its TTL.
134+
// authHeader is the header key used by the cache to know that a
135+
// request should bypass the cache.
136+
// authValues is the set of values that could be set on the authHeader in
137+
// order to bypass the cache.
138+
Cache(name string, expirer func(r *http.Request) time.Duration, authValues []string) func(http.Handler) http.Handler
139+
}
140+
130141
// Install registers server routes using the given handler registration func.
131142
// authValues is the set of values that can be set on authHeader to bypass the
132143
// cache.
133-
func (s *Server) Install(handle func(string, http.Handler), redisClient *redis.Client, authValues []string) {
144+
func (s *Server) Install(handle func(string, http.Handler), cacher Cacher, authValues []string) {
134145
var (
135146
detailHandler http.Handler = s.errorHandler(s.serveDetails)
136147
fetchHandler http.Handler = s.errorHandler(s.serveFetch)
137148
searchHandler http.Handler = s.errorHandler(s.serveSearch)
138149
vulnHandler http.Handler = s.errorHandler(s.serveVuln)
139150
)
140-
if redisClient != nil {
151+
if cacher != nil {
141152
// The cache middleware uses the URL string as the key for content served
142153
// by the handlers it wraps. Be careful not to wrap the handler it returns
143154
// with a handler that rewrites the URL in a way that could cause key
144155
// collisions, like http.StripPrefix.
145-
detailHandler = middleware.Cache("details", redisClient, detailsTTL, authValues)(detailHandler)
146-
searchHandler = middleware.Cache("search", redisClient, searchTTL, authValues)(searchHandler)
147-
vulnHandler = middleware.Cache("vuln", redisClient, vulnTTL, authValues)(vulnHandler)
156+
detailHandler = cacher.Cache("details", detailsTTL, authValues)(detailHandler)
157+
searchHandler = cacher.Cache("search", searchTTL, authValues)(searchHandler)
158+
vulnHandler = cacher.Cache("vuln", vulnTTL, authValues)(vulnHandler)
148159
}
149160
// Each AppEngine instance is created in response to a start request, which
150161
// is an empty HTTP GET request to /_ah/start when scaling is set to manual

internal/frontend/server_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"golang.org/x/pkgsite/internal/cookie"
3232
"golang.org/x/pkgsite/internal/derrors"
3333
"golang.org/x/pkgsite/internal/experiment"
34+
"golang.org/x/pkgsite/internal/middleware"
3435
"golang.org/x/pkgsite/internal/postgres"
3536
"golang.org/x/pkgsite/internal/testing/htmlcheck"
3637
"golang.org/x/pkgsite/internal/testing/pagecheck"
@@ -1234,7 +1235,7 @@ func TestServer404Redirect_NoLoop(t *testing.T) {
12341235
}
12351236
defer rs.Close()
12361237

1237-
_, handler, _ := newTestServer(t, nil, redis.NewClient(&redis.Options{Addr: rs.Addr()}))
1238+
_, handler, _ := newTestServer(t, nil, middleware.NewCacher(redis.NewClient(&redis.Options{Addr: rs.Addr()})))
12381239

12391240
for _, test := range []struct {
12401241
name, path string
@@ -1311,7 +1312,7 @@ func TestServer404Redirect(t *testing.T) {
13111312
}
13121313
defer rs.Close()
13131314

1314-
_, handler, _ := newTestServer(t, nil, redis.NewClient(&redis.Options{Addr: rs.Addr()}))
1315+
_, handler, _ := newTestServer(t, nil, middleware.NewCacher(redis.NewClient(&redis.Options{Addr: rs.Addr()})))
13151316

13161317
for _, test := range []struct {
13171318
name, path, flash string

internal/middleware/caching.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,26 +98,36 @@ type cache struct {
9898
// An Expirer computes the TTL that should be used when caching a page.
9999
type Expirer func(r *http.Request) time.Duration
100100

101-
// TTL returns an Expirer that expires all pages after the given TTL.
102-
func TTL(ttl time.Duration) Expirer {
101+
// ttl returns an Expirer that expires all pages after the given TTL.
102+
func ttl(ttl time.Duration) Expirer {
103103
return func(r *http.Request) time.Duration {
104104
return ttl
105105
}
106106
}
107107

108+
// NewCacher returns a new Cacher, used for creating a middleware
109+
// that caches each request.
110+
func NewCacher(client *redis.Client) *cacher {
111+
return &cacher{client: client}
112+
}
113+
114+
type cacher struct {
115+
client *redis.Client
116+
}
117+
108118
// Cache returns a new Middleware that caches every request.
109119
// The name of the cache is used only for metrics.
110120
// The expirer is a func that is used to map a new request to its TTL.
111121
// authHeader is the header key used by the cache to know that a
112122
// request should bypass the cache.
113123
// authValues is the set of values that could be set on the authHeader in
114124
// order to bypass the cache.
115-
func Cache(name string, client *redis.Client, expirer Expirer, authValues []string) Middleware {
125+
func (c *cacher) Cache(name string, expirer func(r *http.Request) time.Duration, authValues []string) func(http.Handler) http.Handler {
116126
return func(h http.Handler) http.Handler {
117127
return &cache{
118128
name: name,
119129
authValues: authValues,
120-
cache: icache.New(client),
130+
cache: icache.New(c.client),
121131
delegate: h,
122132
expirer: expirer,
123133
}

internal/middleware/caching_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestCache(t *testing.T) {
4949

5050
c := redis.NewClient(&redis.Options{Addr: s.Addr()})
5151
mux := http.NewServeMux()
52-
mux.Handle("/A", Cache("A", c, TTL(1*time.Minute), []string{"yes"})(handler))
52+
mux.Handle("/A", NewCacher(c).Cache("A", ttl(1*time.Minute), []string{"yes"})(handler))
5353
mux.Handle("/B", handler)
5454
ts := httptest.NewServer(mux)
5555
view.Register(CacheResultCount)

internal/testing/integration/frontend_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ func setupFrontend(ctx context.Context, t *testing.T, q queue.Queue, rc *redis.C
4646
t.Fatal(err)
4747
}
4848
mux := http.NewServeMux()
49-
s.Install(mux.Handle, rc, nil)
49+
var cacher frontend.Cacher
50+
if rc != nil {
51+
cacher = middleware.NewCacher(rc)
52+
}
53+
s.Install(mux.Handle, cacher, nil)
5054

5155
// Get experiments from the context. Fully roll them out.
5256
expNames := experiment.FromContext(ctx).Active()

0 commit comments

Comments
 (0)