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:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user