Add Etag header for manifests.
Return 304 (Not Modified) if retrieved with If-None-Match header Signed-off-by: Richard Scothern <richard.scothern@gmail.com>master
							parent
							
								
									d2ca423500
								
							
						
					
					
						commit
						1bc740b0d5
					
				|  | @ -449,6 +449,7 @@ func TestManifestAPI(t *testing.T) { | ||||||
| 	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) | 	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) | ||||||
| 	checkHeaders(t, resp, http.Header{ | 	checkHeaders(t, resp, http.Header{ | ||||||
| 		"Docker-Content-Digest": []string{dgst.String()}, | 		"Docker-Content-Digest": []string{dgst.String()}, | ||||||
|  | 		"ETag":                  []string{dgst.String()}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	var fetchedManifest manifest.SignedManifest | 	var fetchedManifest manifest.SignedManifest | ||||||
|  | @ -470,6 +471,7 @@ func TestManifestAPI(t *testing.T) { | ||||||
| 	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) | 	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) | ||||||
| 	checkHeaders(t, resp, http.Header{ | 	checkHeaders(t, resp, http.Header{ | ||||||
| 		"Docker-Content-Digest": []string{dgst.String()}, | 		"Docker-Content-Digest": []string{dgst.String()}, | ||||||
|  | 		"ETag":                  []string{dgst.String()}, | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	var fetchedManifestByDigest manifest.SignedManifest | 	var fetchedManifestByDigest manifest.SignedManifest | ||||||
|  | @ -482,6 +484,33 @@ func TestManifestAPI(t *testing.T) { | ||||||
| 		t.Fatalf("manifests do not match") | 		t.Fatalf("manifests do not match") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Get by name with etag, gives 304
 | ||||||
|  | 	etag := resp.Header.Get("Etag") | ||||||
|  | 	req, err := http.NewRequest("GET", manifestURL, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Error constructing request: %s", err) | ||||||
|  | 	} | ||||||
|  | 	req.Header.Set("If-None-Match", etag) | ||||||
|  | 	resp, err = http.DefaultClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Error constructing request: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	checkResponse(t, "fetching layer with etag", resp, http.StatusNotModified) | ||||||
|  | 
 | ||||||
|  | 	// Get by digest with etag, gives 304
 | ||||||
|  | 	req, err = http.NewRequest("GET", manifestDigestURL, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Error constructing request: %s", err) | ||||||
|  | 	} | ||||||
|  | 	req.Header.Set("If-None-Match", etag) | ||||||
|  | 	resp, err = http.DefaultClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Error constructing request: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	checkResponse(t, "fetching layer with etag", resp, http.StatusNotModified) | ||||||
|  | 
 | ||||||
| 	// Ensure that the tag is listed.
 | 	// Ensure that the tag is listed.
 | ||||||
| 	resp, err = http.Get(tagsURL) | 	resp, err = http.Get(tagsURL) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -60,6 +60,10 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http | ||||||
| 	if imh.Tag != "" { | 	if imh.Tag != "" { | ||||||
| 		sm, err = manifests.GetByTag(imh.Tag) | 		sm, err = manifests.GetByTag(imh.Tag) | ||||||
| 	} else { | 	} else { | ||||||
|  | 		if etagMatch(r, imh.Digest.String()) { | ||||||
|  | 			w.WriteHeader(http.StatusNotModified) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 		sm, err = manifests.Get(imh.Digest) | 		sm, err = manifests.Get(imh.Digest) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -75,6 +79,10 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http | ||||||
| 			imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid.WithDetail(err)) | 			imh.Errors = append(imh.Errors, v2.ErrorCodeDigestInvalid.WithDetail(err)) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		if etagMatch(r, dgst.String()) { | ||||||
|  | 			w.WriteHeader(http.StatusNotModified) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		imh.Digest = dgst | 		imh.Digest = dgst | ||||||
| 	} | 	} | ||||||
|  | @ -82,9 +90,19 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http | ||||||
| 	w.Header().Set("Content-Type", "application/json; charset=utf-8") | 	w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||||||
| 	w.Header().Set("Content-Length", fmt.Sprint(len(sm.Raw))) | 	w.Header().Set("Content-Length", fmt.Sprint(len(sm.Raw))) | ||||||
| 	w.Header().Set("Docker-Content-Digest", imh.Digest.String()) | 	w.Header().Set("Docker-Content-Digest", imh.Digest.String()) | ||||||
|  | 	w.Header().Set("Etag", imh.Digest.String()) | ||||||
| 	w.Write(sm.Raw) | 	w.Write(sm.Raw) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func etagMatch(r *http.Request, etag string) bool { | ||||||
|  | 	for _, headerVal := range r.Header["If-None-Match"] { | ||||||
|  | 		if headerVal == etag { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // PutImageManifest validates and stores and image in the registry.
 | // PutImageManifest validates and stores and image in the registry.
 | ||||||
| func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) { | func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http.Request) { | ||||||
| 	ctxu.GetLogger(imh).Debug("PutImageManifest") | 	ctxu.GetLogger(imh).Debug("PutImageManifest") | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue