forked from TrueCloudLab/distribution
2db0327dc1
- Set an Etag header - Check If-None-Match and respond appropriately - Set a Cache-Control header with a default of 1 week Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
104 lines
2.6 KiB
Go
104 lines
2.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/docker/distribution"
|
|
"github.com/docker/distribution/digest"
|
|
"github.com/docker/distribution/registry/storage/driver"
|
|
)
|
|
|
|
// layerReader implements Layer and provides facilities for reading and
|
|
// seeking.
|
|
type layerReader struct {
|
|
fileReader
|
|
|
|
digest digest.Digest
|
|
}
|
|
|
|
// newLayerReader returns a new layerReader with the digest, path and length,
|
|
// eliding round trips to the storage backend.
|
|
func newLayerReader(driver driver.StorageDriver, dgst digest.Digest, path string, length int64) (*layerReader, error) {
|
|
fr := &fileReader{
|
|
driver: driver,
|
|
path: path,
|
|
size: length,
|
|
}
|
|
|
|
return &layerReader{
|
|
fileReader: *fr,
|
|
digest: dgst,
|
|
}, nil
|
|
}
|
|
|
|
var _ distribution.Layer = &layerReader{}
|
|
|
|
func (lr *layerReader) Digest() digest.Digest {
|
|
return lr.digest
|
|
}
|
|
|
|
func (lr *layerReader) Length() int64 {
|
|
return lr.size
|
|
}
|
|
|
|
func (lr *layerReader) CreatedAt() time.Time {
|
|
return lr.modtime
|
|
}
|
|
|
|
// Close the layer. Should be called when the resource is no longer needed.
|
|
func (lr *layerReader) Close() error {
|
|
return lr.closeWithErr(distribution.ErrLayerClosed)
|
|
}
|
|
|
|
func (lr *layerReader) Handler(r *http.Request) (h http.Handler, err error) {
|
|
var handlerFunc http.HandlerFunc
|
|
|
|
redirectURL, err := lr.fileReader.driver.URLFor(lr.ctx, lr.path, map[string]interface{}{"method": r.Method})
|
|
|
|
switch err {
|
|
case nil:
|
|
handlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
|
// Redirect to storage URL.
|
|
http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
|
|
}
|
|
case driver.ErrUnsupportedMethod:
|
|
handlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
|
// Fallback to serving the content directly.
|
|
http.ServeContent(w, r, lr.digest.String(), lr.CreatedAt(), lr)
|
|
}
|
|
default:
|
|
// Some unexpected error.
|
|
return nil, err
|
|
}
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// If the registry is serving this content itself, check
|
|
// the If-None-Match header and return 304 on match. Redirected
|
|
// storage implementations do the same.
|
|
|
|
if etagMatch(r, lr.digest.String()) {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
return
|
|
}
|
|
setCacheHeaders(w, 86400, lr.digest.String())
|
|
w.Header().Set("Docker-Content-Digest", lr.digest.String())
|
|
handlerFunc.ServeHTTP(w, r)
|
|
}), nil
|
|
}
|
|
|
|
func etagMatch(r *http.Request, etag string) bool {
|
|
for _, headerVal := range r.Header["If-None-Match"] {
|
|
if headerVal == etag {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func setCacheHeaders(w http.ResponseWriter, cacheAge int, etag string) {
|
|
w.Header().Set("ETag", etag)
|
|
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", cacheAge))
|
|
|
|
}
|