[#191] Improve copy object compatibility

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2021-08-06 18:08:09 +03:00 committed by Stanislav Bogatyrev
parent 7eb9713a67
commit f3a6636efd
3 changed files with 36 additions and 20 deletions

View file

@ -12,9 +12,12 @@ import (
) )
type copyObjectArgs struct { type copyObjectArgs struct {
Conditional *conditionalArgs Conditional *conditionalArgs
MetadataDirective string
} }
const replaceMetadataDirective = "REPLACE"
// path2BucketObject returns bucket and object. // path2BucketObject returns bucket and object.
func path2BucketObject(path string) (bucket, prefix string) { func path2BucketObject(path string) (bucket, prefix string) {
path = strings.TrimPrefix(path, api.SlashSeparator) path = strings.TrimPrefix(path, api.SlashSeparator)
@ -27,8 +30,9 @@ func path2BucketObject(path string) (bucket, prefix string) {
func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
var ( var (
err error err error
inf *layer.ObjectInfo inf *layer.ObjectInfo
metadata map[string]string
reqInfo = api.GetReqInfo(r.Context()) reqInfo = api.GetReqInfo(r.Context())
) )
@ -59,6 +63,13 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if args.MetadataDirective == replaceMetadataDirective {
metadata = parseMetadata(r)
} else if srcBucket == reqInfo.BucketName && srcObject == reqInfo.ObjectName {
h.logAndSendError(w, "could not copy to itself", reqInfo, api.GetAPIError(api.ErrInvalidRequest))
return
}
if inf, err = h.obj.GetObjectInfo(r.Context(), srcBucket, srcObject); err != nil { if inf, err = h.obj.GetObjectInfo(r.Context(), srcBucket, srcObject); err != nil {
h.logAndSendError(w, "could not find object", reqInfo, err) h.logAndSendError(w, "could not find object", reqInfo, err)
return return
@ -69,20 +80,30 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if metadata == nil {
if len(inf.ContentType) > 0 {
inf.Headers[api.ContentType] = inf.ContentType
}
metadata = inf.Headers
} else if contentType := r.Header.Get(api.ContentType); len(contentType) > 0 {
metadata[api.ContentType] = contentType
}
params := &layer.CopyObjectParams{ params := &layer.CopyObjectParams{
SrcBucket: srcBucket, SrcBucket: srcBucket,
DstBucket: reqInfo.BucketName, DstBucket: reqInfo.BucketName,
SrcObject: srcObject, SrcObject: srcObject,
DstObject: reqInfo.ObjectName, DstObject: reqInfo.ObjectName,
SrcSize: inf.Size, SrcSize: inf.Size,
Header: inf.Headers, Header: metadata,
} }
additional := []zap.Field{zap.String("src_bucket_name", srcBucket), zap.String("src_object_name", srcObject)}
if inf, err = h.obj.CopyObject(r.Context(), params); err != nil { if inf, err = h.obj.CopyObject(r.Context(), params); err != nil {
writeErrorCopy(w, reqInfo, h.log, "could not copy object", srcBucket, srcObject, err) h.logAndSendError(w, "couldn't copy object", reqInfo, err, additional...)
return return
} else if err = api.EncodeToResponse(w, &CopyObjectResponse{LastModified: inf.Created.Format(time.RFC3339), ETag: inf.HashSum}); err != nil { } else if err = api.EncodeToResponse(w, &CopyObjectResponse{LastModified: inf.Created.Format(time.RFC3339), ETag: inf.HashSum}); err != nil {
writeErrorCopy(w, reqInfo, h.log, "something went wrong", srcBucket, srcObject, err) h.logAndSendError(w, "something went wrong", reqInfo, err, additional...)
return return
} }
@ -92,18 +113,6 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
zap.Stringer("object_id", inf.ID())) zap.Stringer("object_id", inf.ID()))
} }
func writeErrorCopy(w http.ResponseWriter, reqInfo *api.ReqInfo, log *zap.Logger, msg, srcBucket, srcObject string, err error) {
log.Error(msg,
zap.String("request_id", reqInfo.RequestID),
zap.String("dst_bucket_name", reqInfo.BucketName),
zap.String("dst_object_name", reqInfo.ObjectName),
zap.String("src_bucket_name", srcBucket),
zap.String("src_object_name", srcObject),
zap.Error(err))
api.WriteErrorResponse(w, reqInfo, err)
}
func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) { func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) {
var err error var err error
args := &conditionalArgs{ args := &conditionalArgs{
@ -118,5 +127,8 @@ func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) {
return nil, err return nil, err
} }
return &copyObjectArgs{Conditional: args}, nil copyArgs := &copyObjectArgs{Conditional: args}
copyArgs.MetadataDirective = headers.Get(api.AmzMetadataDirective)
return copyArgs, nil
} }

View file

@ -38,6 +38,9 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
) )
metadata := parseMetadata(r) metadata := parseMetadata(r)
if contentType := r.Header.Get(api.ContentType); len(contentType) > 0 {
metadata[api.ContentType] = contentType
}
params := &layer.PutObjectParams{ params := &layer.PutObjectParams{
Bucket: reqInfo.BucketName, Bucket: reqInfo.BucketName,

View file

@ -2,7 +2,8 @@ package api
// Standard S3 HTTP request/response constants. // Standard S3 HTTP request/response constants.
const ( const (
MetadataPrefix = "X-Amz-Meta-" MetadataPrefix = "X-Amz-Meta-"
AmzMetadataDirective = "X-Amz-Metadata-Directive"
LastModified = "Last-Modified" LastModified = "Last-Modified"
Date = "Date" Date = "Date"