forked from TrueCloudLab/frostfs-s3-gw
[#538] Return headers with 304 Not Modified
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
fb00dff83b
commit
eff0de43d5
6 changed files with 116 additions and 26 deletions
|
@ -578,6 +578,18 @@ func checkFound(t *testing.T, hc *handlerContext, bktName, objName, version stri
|
||||||
assertStatus(t, w, http.StatusOK)
|
assertStatus(t, w, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func headObjectWithHeaders(hc *handlerContext, bktName, objName, version string, headers map[string]string) *httptest.ResponseRecorder {
|
||||||
|
query := make(url.Values)
|
||||||
|
query.Add(api.QueryVersionID, version)
|
||||||
|
|
||||||
|
w, r := prepareTestFullRequest(hc, bktName, objName, query, nil)
|
||||||
|
for k, v := range headers {
|
||||||
|
r.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
hc.Handler().HeadObjectHandler(w, r)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
func headObjectBase(hc *handlerContext, bktName, objName, version string) *httptest.ResponseRecorder {
|
func headObjectBase(hc *handlerContext, bktName, objName, version string) *httptest.ResponseRecorder {
|
||||||
query := make(url.Values)
|
query := make(url.Values)
|
||||||
query.Add(api.QueryVersionID, version)
|
query.Add(api.QueryVersionID, version)
|
||||||
|
|
|
@ -399,6 +399,15 @@ func getObject(hc *handlerContext, bktName, objName string) ([]byte, http.Header
|
||||||
return getObjectBase(hc, w, r)
|
return getObjectBase(hc, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getObjectWithHeaders(hc *handlerContext, bktName, objName string, headers map[string]string) *httptest.ResponseRecorder {
|
||||||
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
||||||
|
for k, v := range headers {
|
||||||
|
r.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
hc.Handler().GetObjectHandler(w, r)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
func getObjectBase(hc *handlerContext, w *httptest.ResponseRecorder, r *http.Request) ([]byte, http.Header) {
|
func getObjectBase(hc *handlerContext, w *httptest.ResponseRecorder, r *http.Request) ([]byte, http.Header) {
|
||||||
hc.Handler().GetObjectHandler(w, r)
|
hc.Handler().GetObjectHandler(w, r)
|
||||||
assertStatus(hc.t, w, http.StatusOK)
|
assertStatus(hc.t, w, http.StatusOK)
|
||||||
|
|
|
@ -78,6 +78,27 @@ func addSSECHeaders(responseHeader http.Header, requestHeader http.Header) {
|
||||||
responseHeader.Set(api.AmzServerSideEncryptionCustomerKeyMD5, requestHeader.Get(api.AmzServerSideEncryptionCustomerKeyMD5))
|
responseHeader.Set(api.AmzServerSideEncryptionCustomerKeyMD5, requestHeader.Get(api.AmzServerSideEncryptionCustomerKeyMD5))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeNotModifiedHeaders(h http.Header, extendedInfo *data.ExtendedObjectInfo, tagSetLength int, isBucketUnversioned, md5Enabled bool) {
|
||||||
|
h.Set(api.ETag, data.Quote(extendedInfo.ObjectInfo.ETag(md5Enabled)))
|
||||||
|
h.Set(api.LastModified, extendedInfo.ObjectInfo.Created.UTC().Format(http.TimeFormat))
|
||||||
|
h.Set(api.AmzTaggingCount, strconv.Itoa(tagSetLength))
|
||||||
|
|
||||||
|
if !isBucketUnversioned {
|
||||||
|
h.Set(api.AmzVersionID, extendedInfo.Version())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cacheControl := extendedInfo.ObjectInfo.Headers[api.CacheControl]; cacheControl != "" {
|
||||||
|
h.Set(api.CacheControl, cacheControl)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range extendedInfo.ObjectInfo.Headers {
|
||||||
|
if layer.IsSystemHeader(key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h[api.MetadataPrefix+key] = []string{val}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func writeHeaders(h http.Header, requestHeader http.Header, extendedInfo *data.ExtendedObjectInfo, tagSetLength int,
|
func writeHeaders(h http.Header, requestHeader http.Header, extendedInfo *data.ExtendedObjectInfo, tagSetLength int,
|
||||||
isBucketUnversioned, md5Enabled bool) {
|
isBucketUnversioned, md5Enabled bool) {
|
||||||
info := extendedInfo.ObjectInfo
|
info := extendedInfo.ObjectInfo
|
||||||
|
@ -158,7 +179,28 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
info := extendedInfo.ObjectInfo
|
info := extendedInfo.ObjectInfo
|
||||||
|
|
||||||
|
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &data.ObjectVersion{
|
||||||
|
BktInfo: bktInfo,
|
||||||
|
ObjectName: info.Name,
|
||||||
|
VersionID: info.VersionID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(ctx, t, extendedInfo.NodeVersion)
|
||||||
|
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
|
h.logAndSendError(ctx, w, "could not get object meta data", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
|
if errors.IsS3Error(err, errors.ErrNotModified) {
|
||||||
|
writeNotModifiedHeaders(w.Header(), extendedInfo, len(tagSet), bktSettings.Unversioned(), h.cfg.MD5Enabled())
|
||||||
|
}
|
||||||
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -185,18 +227,6 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &data.ObjectVersion{
|
|
||||||
BktInfo: bktInfo,
|
|
||||||
ObjectName: info.Name,
|
|
||||||
VersionID: info.VersionID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(ctx, t, extendedInfo.NodeVersion)
|
|
||||||
if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
|
||||||
h.logAndSendError(ctx, w, "could not get object meta data", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if layer.IsAuthenticatedRequest(ctx) {
|
if layer.IsAuthenticatedRequest(ctx) {
|
||||||
overrideResponseHeaders(w.Header(), reqInfo.URL.Query())
|
overrideResponseHeaders(w.Header(), reqInfo.URL.Query())
|
||||||
}
|
}
|
||||||
|
@ -206,12 +236,6 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
getPayloadParams := &layer.GetObjectParams{
|
getPayloadParams := &layer.GetObjectParams{
|
||||||
ObjectInfo: info,
|
ObjectInfo: info,
|
||||||
Versioned: p.Versioned(),
|
Versioned: p.Versioned(),
|
||||||
|
|
|
@ -210,6 +210,27 @@ func TestGetObjectEnabledMD5(t *testing.T) {
|
||||||
require.Equal(t, data.Quote(objInfo.MD5Sum), headers.Get(api.ETag))
|
require.Equal(t, data.Quote(objInfo.MD5Sum), headers.Get(api.ETag))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetObjectNotModifiedHeaders(t *testing.T) {
|
||||||
|
hc := prepareHandlerContextWithMinCache(t)
|
||||||
|
bktName, objName, metadataHeader := "bucket", "obj", api.MetadataPrefix+"header"
|
||||||
|
createVersionedBucket(hc, bktName)
|
||||||
|
header := putObjectWithHeaders(hc, bktName, objName, map[string]string{api.CacheControl: "value", metadataHeader: "value"})
|
||||||
|
etag, versionID := header.Get(api.ETag), header.Get(api.AmzVersionID)
|
||||||
|
require.NotEmpty(t, etag)
|
||||||
|
require.NotEmpty(t, versionID)
|
||||||
|
|
||||||
|
putObjectTagging(t, hc, bktName, objName, map[string]string{"key": "value"})
|
||||||
|
|
||||||
|
w := getObjectWithHeaders(hc, bktName, objName, map[string]string{api.IfNoneMatch: etag})
|
||||||
|
require.Equal(t, http.StatusNotModified, w.Code)
|
||||||
|
require.Equal(t, "1", w.Header().Get(api.AmzTaggingCount))
|
||||||
|
require.Equal(t, etag, w.Header().Get(api.ETag))
|
||||||
|
require.NotEmpty(t, w.Header().Get(api.LastModified))
|
||||||
|
require.Equal(t, versionID, w.Header().Get(api.AmzVersionID))
|
||||||
|
require.Equal(t, "value", w.Header().Get(api.CacheControl))
|
||||||
|
require.Equal(t, []string{"value"}, w.Header()[metadataHeader])
|
||||||
|
}
|
||||||
|
|
||||||
func putObjectContent(hc *handlerContext, bktName, objName, content string) http.Header {
|
func putObjectContent(hc *handlerContext, bktName, objName, content string) http.Header {
|
||||||
body := bytes.NewReader([]byte(content))
|
body := bytes.NewReader([]byte(content))
|
||||||
w, r := prepareTestPayloadRequest(hc, bktName, objName, body)
|
w, r := prepareTestPayloadRequest(hc, bktName, objName, body)
|
||||||
|
|
|
@ -66,8 +66,9 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
||||||
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +84,14 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = checkPreconditions(info, conditional, h.cfg.MD5Enabled()); err != nil {
|
||||||
|
if errors.IsS3Error(err, errors.ErrNotModified) {
|
||||||
|
writeNotModifiedHeaders(w.Header(), extendedInfo, len(tagSet), bktSettings.Unversioned(), h.cfg.MD5Enabled())
|
||||||
|
}
|
||||||
|
h.logAndSendError(ctx, w, "precondition failed", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(info.ContentType) == 0 {
|
if len(info.ContentType) == 0 {
|
||||||
if info.ContentType = layer.MimeByFilePath(info.Name); len(info.ContentType) == 0 {
|
if info.ContentType = layer.MimeByFilePath(info.Name); len(info.ContentType) == 0 {
|
||||||
getParams := &layer.GetObjectParams{
|
getParams := &layer.GetObjectParams{
|
||||||
|
@ -113,12 +122,6 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bktSettings, err := h.obj.GetBucketSettings(ctx, bktInfo)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(ctx, w, "could not get bucket settings", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeHeaders(w.Header(), r.Header, extendedInfo, len(tagSet), bktSettings.Unversioned(), h.cfg.MD5Enabled())
|
writeHeaders(w.Header(), r.Header, extendedInfo, len(tagSet), bktSettings.Unversioned(), h.cfg.MD5Enabled())
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,27 @@ func TestHeadObject(t *testing.T) {
|
||||||
headObjectAssertS3Error(hc, bktName, objName, emptyVersion, apierr.ErrNoSuchKey)
|
headObjectAssertS3Error(hc, bktName, objName, emptyVersion, apierr.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHeadObjectNotModifiedHeaders(t *testing.T) {
|
||||||
|
hc := prepareHandlerContextWithMinCache(t)
|
||||||
|
bktName, objName, metadataHeader := "bucket", "obj", api.MetadataPrefix+"header"
|
||||||
|
createVersionedBucket(hc, bktName)
|
||||||
|
header := putObjectWithHeaders(hc, bktName, objName, map[string]string{api.CacheControl: "value", metadataHeader: "value"})
|
||||||
|
etag, versionID := header.Get(api.ETag), header.Get(api.AmzVersionID)
|
||||||
|
require.NotEmpty(t, etag)
|
||||||
|
require.NotEmpty(t, versionID)
|
||||||
|
|
||||||
|
putObjectTagging(t, hc, bktName, objName, map[string]string{"key": "value"})
|
||||||
|
|
||||||
|
w := headObjectWithHeaders(hc, bktName, objName, emptyVersion, map[string]string{api.IfNoneMatch: etag})
|
||||||
|
require.Equal(t, http.StatusNotModified, w.Code)
|
||||||
|
require.Equal(t, "1", w.Header().Get(api.AmzTaggingCount))
|
||||||
|
require.Equal(t, etag, w.Header().Get(api.ETag))
|
||||||
|
require.NotEmpty(t, w.Header().Get(api.LastModified))
|
||||||
|
require.Equal(t, versionID, w.Header().Get(api.AmzVersionID))
|
||||||
|
require.Equal(t, "value", w.Header().Get(api.CacheControl))
|
||||||
|
require.Equal(t, []string{"value"}, w.Header()[metadataHeader])
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsAvailableToResolve(t *testing.T) {
|
func TestIsAvailableToResolve(t *testing.T) {
|
||||||
list := []string{"container", "s3"}
|
list := []string{"container", "s3"}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue