Include headers when serving blob through proxy

In commit 17952924f3 we updated ServeBlob() to use an io.MultiWriter to
write simultaneously to the local store and the HTTP response.

However, copyContent was using a type assertion to only add headers if
the io.Writer was a http.ResponseWriter. Therefore, this change caused
us to stop sending the expected headers (i.e. Content-Length, Etag,
etc.) on the first request for a blob.

Resolve the issue by explicitly passing in http.Header and setting it
unconditionally.

Signed-off-by: Mikel Rychliski <mikel@mikelr.com>
This commit is contained in:
Mikel Rychliski 2024-01-26 18:56:45 -05:00
parent 9b3eac8f08
commit 041824555c
2 changed files with 20 additions and 12 deletions

View file

@ -33,22 +33,20 @@ var inflight = make(map[digest.Digest]struct{})
// mu protects inflight // mu protects inflight
var mu sync.Mutex var mu sync.Mutex
func setResponseHeaders(w http.ResponseWriter, length int64, mediaType string, digest digest.Digest) { func setResponseHeaders(h http.Header, length int64, mediaType string, digest digest.Digest) {
w.Header().Set("Content-Length", strconv.FormatInt(length, 10)) h.Set("Content-Length", strconv.FormatInt(length, 10))
w.Header().Set("Content-Type", mediaType) h.Set("Content-Type", mediaType)
w.Header().Set("Docker-Content-Digest", digest.String()) h.Set("Docker-Content-Digest", digest.String())
w.Header().Set("Etag", digest.String()) h.Set("Etag", digest.String())
} }
func (pbs *proxyBlobStore) copyContent(ctx context.Context, dgst digest.Digest, writer io.Writer) (distribution.Descriptor, error) { func (pbs *proxyBlobStore) copyContent(ctx context.Context, dgst digest.Digest, writer io.Writer, h http.Header) (distribution.Descriptor, error) {
desc, err := pbs.remoteStore.Stat(ctx, dgst) desc, err := pbs.remoteStore.Stat(ctx, dgst)
if err != nil { if err != nil {
return distribution.Descriptor{}, err return distribution.Descriptor{}, err
} }
if w, ok := writer.(http.ResponseWriter); ok { setResponseHeaders(h, desc.Size, desc.MediaType, dgst)
setResponseHeaders(w, desc.Size, desc.MediaType, dgst)
}
remoteReader, err := pbs.remoteStore.Open(ctx, dgst) remoteReader, err := pbs.remoteStore.Open(ctx, dgst)
if err != nil { if err != nil {
@ -102,7 +100,7 @@ func (pbs *proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter,
// Will return the blob from the remote store directly. // Will return the blob from the remote store directly.
// TODO Maybe we could reuse the these blobs are serving remotely and caching locally. // TODO Maybe we could reuse the these blobs are serving remotely and caching locally.
mu.Unlock() mu.Unlock()
_, err := pbs.copyContent(ctx, dgst, w) _, err := pbs.copyContent(ctx, dgst, w, w.Header())
return err return err
} }
inflight[dgst] = struct{}{} inflight[dgst] = struct{}{}
@ -122,7 +120,7 @@ func (pbs *proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter,
// Serving client and storing locally over same fetching request. // Serving client and storing locally over same fetching request.
// This can prevent a redundant blob fetching. // This can prevent a redundant blob fetching.
multiWriter := io.MultiWriter(w, bw) multiWriter := io.MultiWriter(w, bw)
desc, err := pbs.copyContent(ctx, dgst, multiWriter) desc, err := pbs.copyContent(ctx, dgst, multiWriter, w.Header())
if err != nil { if err != nil {
return err return err
} }

View file

@ -448,12 +448,22 @@ func testProxyStoreServe(t *testing.T, te *testEnv, numClients int) {
return return
} }
bodyBytes := w.Body.Bytes() resp := w.Result()
bodyBytes, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Errorf(err.Error())
return
}
localDigest := digest.FromBytes(bodyBytes) localDigest := digest.FromBytes(bodyBytes)
if localDigest != remoteBlob.Digest { if localDigest != remoteBlob.Digest {
t.Errorf("Mismatching blob fetch from proxy") t.Errorf("Mismatching blob fetch from proxy")
return return
} }
if resp.Header.Get("Docker-Content-Digest") != localDigest.String() {
t.Errorf("Mismatching digest in response header")
return
}
desc, err := te.store.localStore.Stat(te.ctx, remoteBlob.Digest) desc, err := te.store.localStore.Stat(te.ctx, remoteBlob.Digest)
if err != nil { if err != nil {