1
0
mirror of https://github.com/docker/docs.git synced 2026-11-05 00:14:17 +03:00

Modify server handler to set cache headers based upon a cache configuration object

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li
2016-02-26 16:45:12 -08:00
parent 802673fc9d
commit 9b022a9cda
2 changed files with 133 additions and 34 deletions

View File

@@ -25,13 +25,68 @@ import (
"github.com/docker/notary/tuf/validation"
)
const (
// ConsistentCacheMaxAge is the Cache-Control header's max age for consistent downloads
ConsistentCacheMaxAge int = 30 * 24 * 60 * 60 // 30 days
// NewCacheControlConfig creates a new configuration for Cache-Control headers,
// which by default, sets cache max-age values for consistent (by checksum)
// downloads 30 days and non-consistent (current) downloads to 5 minutes
func NewCacheControlConfig() *CacheControlConfig {
return &CacheControlConfig{
headerVals: map[string]int{
"consistent": 30 * 24 * 60 * 60, // 30 days
"current": 5 * 60, // 5 minutes
},
}
}
// NonConsistentCacheMaxAge is the Cache-Control header's max age for current (non-consistent) downloads
NonConsistentCacheMaxAge int = 5 * 60 // five minutes
)
// CacheControlConfig is the configuration for the max cache age for
// cache control headers.
type CacheControlConfig struct {
headerVals map[string]int
}
// SetConsistentCacheMaxAge sets the Cache-Control header value for consistent
// downloads
func (c *CacheControlConfig) SetConsistentCacheMaxAge(value int) {
c.headerVals["consistent"] = value
}
// SetCurrentCacheMaxAge sets the Cache-Control header value for current
// (non-consistent) downloads
func (c *CacheControlConfig) SetCurrentCacheMaxAge(value int) {
c.headerVals["current"] = value
}
// UpdateConsistentHeaders updates the given Headers object with the Cache-Control
// headers for consistent downloads
func (c *CacheControlConfig) UpdateConsistentHeaders(headers http.Header, lastModified time.Time) {
c.updateHeaders(headers, lastModified, true)
}
// UpdateCurrentHeaders updates the given Headers object with th eCache-Control
// headers for current (non-consistent) downloads
func (c *CacheControlConfig) UpdateCurrentHeaders(headers http.Header, lastModified time.Time) {
c.updateHeaders(headers, lastModified, false)
}
func (c *CacheControlConfig) updateHeaders(headers http.Header, lastModified time.Time, consistent bool) {
var seconds int
var cacheHeader string
if consistent {
seconds = c.headerVals["consistent"]
cacheHeader = fmt.Sprintf("public, max-age=%v, s-maxage=%v, must-revalidate", seconds, seconds)
} else {
seconds = c.headerVals["current"]
cacheHeader = fmt.Sprintf("public, max-age=%v, s-maxage=%v", seconds, seconds)
}
if seconds > 0 {
headers.Set("Cache-Control", cacheHeader)
headers.Set("Last-Modified", lastModified.Format(time.RFC1123))
} else {
headers.Set("Cache-Control", "max-age=0, no-cache, no-store")
headers.Set("Pragma", "no-cache")
}
}
// MainHandler is the default handler for the server
func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
@@ -130,32 +185,42 @@ func getHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, var
checksum := vars["checksum"]
tufRole := vars["tufRole"]
s := ctx.Value("metaStore")
c := ctx.Value("cacheConfig")
store, ok := s.(storage.MetaStore)
if !ok {
return errors.ErrNoStorage.WithDetail(nil)
}
creation, output, err := getRole(ctx, store, gun, tufRole, checksum)
// If cache control headers were not provided, just use the default values
cacheConfig, ok := c.(*CacheControlConfig)
if !ok {
cacheConfig = NewCacheControlConfig()
}
lastModified, output, err := getRole(ctx, store, gun, tufRole, checksum)
if err != nil {
return err
}
if creation == nil {
if lastModified == nil {
// This shouldn't ever happen, but if it does, it just messes up the cache headers, so
// proceed anyway
logrus.Warnf("Got bytes out for %s's %s (checksum: %s), but missing creation date",
logrus.Warnf("Got bytes out for %s's %s (checksum: %s), but missing lastModified date",
gun, tufRole, checksum)
creation = &time.Time{} // set the last modification date to the beginning of time
lastModified = &time.Time{} // set the last modification date to the beginning of time
}
maxAge := ConsistentCacheMaxAge
if checksum == "" { // maxAge should be much shorter for metadata without checksums
maxAge = NonConsistentCacheMaxAge
switch checksum {
case "":
cacheConfig.UpdateCurrentHeaders(w.Header(), *lastModified)
shasum := sha256.Sum256(output)
checksum = hex.EncodeToString(shasum[:])
default:
cacheConfig.UpdateConsistentHeaders(w.Header(), *lastModified)
}
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%v", maxAge))
w.Header().Set("Last-Modified", creation.Format(time.RFC1123))
w.Header().Set("ETag", checksum)
w.Write(output)
return nil

View File

@@ -174,29 +174,51 @@ func TestGetKeyHandlerCreatesOnce(t *testing.T) {
}
}
// Verifies that the body is as expected, the ETag is as expected,
type expectedCacheSetting int
const (
checksumCaching expectedCacheSetting = iota
currentCaching
noCaching
)
// Verifies that the body is as expected, the ETag is as expected, and cache control headers
// are as expected
func verifyGetResponse(t *testing.T, rw *httptest.ResponseRecorder, expectedBytes []byte,
getWithChecksum bool, checksumHex string) {
checksumHex string, cacheType expectedCacheSetting, cacheConfig *CacheControlConfig) {
body, err := ioutil.ReadAll(rw.Body)
assert.NoError(t, err)
assert.True(t, bytes.Equal(expectedBytes, body))
lastModified, err := time.Parse(time.RFC1123, rw.HeaderMap.Get("Last-Modified"))
assert.NoError(t, err)
assert.True(t, lastModified.After(time.Now().Add(-5*time.Minute)))
assert.Equal(t, rw.HeaderMap.Get("ETag"), checksumHex)
cacheControl := rw.HeaderMap.Get("Cache-Control")
maxAge := NonConsistentCacheMaxAge
if getWithChecksum {
maxAge = ConsistentCacheMaxAge
switch cacheType {
case checksumCaching:
maxAge := cacheConfig.headerVals["consistent"]
assert.Equal(t, fmt.Sprintf("public, max-age=%v, s-maxage=%v, must-revalidate", maxAge, maxAge), cacheControl)
case currentCaching:
maxAge := cacheConfig.headerVals["current"]
assert.Equal(t, fmt.Sprintf("public, max-age=%v, s-maxage=%v", maxAge, maxAge), cacheControl)
default:
assert.Equal(t, "max-age=0, no-cache, no-store", cacheControl)
}
assert.Equal(t, fmt.Sprintf("public, max-age=%v", maxAge), cacheControl)
assert.Equal(t, rw.HeaderMap.Get("ETag"), checksumHex)
switch cacheType {
case checksumCaching, currentCaching:
lastModified, err := time.Parse(time.RFC1123, rw.HeaderMap.Get("Last-Modified"))
assert.NoError(t, err)
assert.True(t, lastModified.After(time.Now().Add(-5*time.Minute)))
assert.Equal(t, "", rw.HeaderMap.Get("Pragma"))
default:
assert.Equal(t, "", rw.HeaderMap.Get("Last-Modified"))
assert.Equal(t, "no-cache", rw.HeaderMap.Get("Pragma"))
}
}
func TestGetHandlerRoot(t *testing.T) {
func TestGetHandlerRootAndNoCacheConfigProvided(t *testing.T) {
metaStore := storage.NewMemStorage()
repo, _, err := testutils.EmptyRepo("gun")
assert.NoError(t, err)
@@ -221,17 +243,19 @@ func TestGetHandlerRoot(t *testing.T) {
checksumBytes := sha256.Sum256(rootJSON)
checksumHex := hex.EncodeToString(checksumBytes[:])
cacheConfig := NewCacheControlConfig()
rw := httptest.NewRecorder()
assert.NoError(t, getHandler(ctx, rw, req, vars))
verifyGetResponse(t, rw, rootJSON, false, checksumHex)
verifyGetResponse(t, rw, rootJSON, checksumHex, currentCaching, cacheConfig)
vars["checksum"] = checksumHex
rw = httptest.NewRecorder()
assert.NoError(t, getHandler(ctx, rw, req, vars))
verifyGetResponse(t, rw, rootJSON, true, checksumHex)
verifyGetResponse(t, rw, rootJSON, checksumHex, checksumCaching, cacheConfig)
}
func TestGetHandlerTimestamp(t *testing.T) {
func TestGetHandlerTimestampWithCacheValues(t *testing.T) {
metaStore := storage.NewMemStorage()
repo, crypto, err := testutils.EmptyRepo("gun")
assert.NoError(t, err)
@@ -259,20 +283,25 @@ func TestGetHandlerTimestamp(t *testing.T) {
"tufRole": "timestamp",
}
cacheConfig := NewCacheControlConfig()
cacheConfig.SetConsistentCacheMaxAge(365 * 24 * 60 * 60)
cacheConfig.SetCurrentCacheMaxAge(1)
ctx = context.WithValue(ctx, "cacheConfig", cacheConfig)
checksumBytes := sha256.Sum256(tsJSON)
checksumHex := hex.EncodeToString(checksumBytes[:])
rw := httptest.NewRecorder()
assert.NoError(t, getHandler(ctx, rw, req, vars))
verifyGetResponse(t, rw, tsJSON, false, checksumHex)
verifyGetResponse(t, rw, tsJSON, checksumHex, currentCaching, cacheConfig)
vars["checksum"] = checksumHex
rw = httptest.NewRecorder()
assert.NoError(t, getHandler(ctx, rw, req, vars))
verifyGetResponse(t, rw, tsJSON, true, checksumHex)
verifyGetResponse(t, rw, tsJSON, checksumHex, checksumCaching, cacheConfig)
}
func TestGetHandlerSnapshot(t *testing.T) {
func TestGetHandlerSnapshotWithNoCaching(t *testing.T) {
metaStore := storage.NewMemStorage()
repo, crypto, err := testutils.EmptyRepo("gun")
assert.NoError(t, err)
@@ -294,17 +323,22 @@ func TestGetHandlerSnapshot(t *testing.T) {
"tufRole": "snapshot",
}
cacheConfig := NewCacheControlConfig()
cacheConfig.SetConsistentCacheMaxAge(0)
cacheConfig.SetCurrentCacheMaxAge(-1)
ctx = context.WithValue(ctx, "cacheConfig", cacheConfig)
checksumBytes := sha256.Sum256(snJSON)
checksumHex := hex.EncodeToString(checksumBytes[:])
rw := httptest.NewRecorder()
assert.NoError(t, getHandler(ctx, rw, req, vars))
verifyGetResponse(t, rw, snJSON, false, checksumHex)
verifyGetResponse(t, rw, snJSON, checksumHex, noCaching, cacheConfig)
vars["checksum"] = checksumHex
rw = httptest.NewRecorder()
assert.NoError(t, getHandler(ctx, rw, req, vars))
verifyGetResponse(t, rw, snJSON, true, checksumHex)
verifyGetResponse(t, rw, snJSON, checksumHex, noCaching, cacheConfig)
}
func TestGetHandler404(t *testing.T) {