Merge pull request #130 from KirillovDenis/feature/95-etag_match_headers
[#95] Supported get match headers
This commit is contained in:
commit
d5af797371
4 changed files with 147 additions and 19 deletions
|
@ -13,8 +13,7 @@ import (
|
|||
)
|
||||
|
||||
type copyObjectArgs struct {
|
||||
IfModifiedSince *time.Time
|
||||
IfUnmodifiedSince *time.Time
|
||||
Conditional *conditionalArgs
|
||||
}
|
||||
|
||||
// path2BucketObject returns bucket and object.
|
||||
|
@ -74,12 +73,9 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if args.IfModifiedSince != nil && inf.Created.Before(*args.IfModifiedSince) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
if args.IfUnmodifiedSince != nil && inf.Created.After(*args.IfUnmodifiedSince) {
|
||||
w.WriteHeader(http.StatusPreconditionFailed)
|
||||
status := checkPreconditions(inf, args.Conditional)
|
||||
if status != http.StatusOK {
|
||||
w.WriteHeader(status)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -124,7 +120,10 @@ func writeErrorCopy(w http.ResponseWriter, r *http.Request, log *zap.Logger, msg
|
|||
|
||||
func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) {
|
||||
var err error
|
||||
args := ©ObjectArgs{}
|
||||
args := &conditionalArgs{
|
||||
IfMatch: headers.Get(api.AmzCopyIfMatch),
|
||||
IfNoneMatch: headers.Get(api.AmzCopyIfNoneMatch),
|
||||
}
|
||||
|
||||
if args.IfModifiedSince, err = parseHTTPTime(headers.Get(api.AmzCopyIfModifiedSince)); err != nil {
|
||||
return nil, err
|
||||
|
@ -133,5 +132,5 @@ func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return args, nil
|
||||
return ©ObjectArgs{Conditional: args}, nil
|
||||
}
|
||||
|
|
|
@ -13,9 +13,15 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type getObjectArgs struct {
|
||||
type conditionalArgs struct {
|
||||
IfModifiedSince *time.Time
|
||||
IfUnmodifiedSince *time.Time
|
||||
IfMatch string
|
||||
IfNoneMatch string
|
||||
}
|
||||
|
||||
type getObjectArgs struct {
|
||||
Conditional *conditionalArgs
|
||||
}
|
||||
|
||||
func fetchRangeHeader(headers http.Header, fullSize uint64) (*layer.RangeParams, error) {
|
||||
|
@ -90,12 +96,9 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if args.IfModifiedSince != nil && inf.Created.Before(*args.IfModifiedSince) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
if args.IfUnmodifiedSince != nil && inf.Created.After(*args.IfUnmodifiedSince) {
|
||||
w.WriteHeader(http.StatusPreconditionFailed)
|
||||
status := checkPreconditions(inf, args.Conditional)
|
||||
if status != http.StatusOK {
|
||||
w.WriteHeader(status)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -119,9 +122,31 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func checkPreconditions(inf *layer.ObjectInfo, args *conditionalArgs) int {
|
||||
if len(args.IfMatch) > 0 && args.IfMatch != inf.HashSum {
|
||||
return http.StatusPreconditionFailed
|
||||
}
|
||||
if len(args.IfNoneMatch) > 0 && args.IfNoneMatch == inf.HashSum {
|
||||
return http.StatusNotModified
|
||||
}
|
||||
if args.IfModifiedSince != nil && inf.Created.Before(*args.IfModifiedSince) {
|
||||
return http.StatusNotModified
|
||||
}
|
||||
if args.IfUnmodifiedSince != nil && inf.Created.After(*args.IfUnmodifiedSince) {
|
||||
if len(args.IfMatch) == 0 {
|
||||
return http.StatusPreconditionFailed
|
||||
}
|
||||
}
|
||||
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) {
|
||||
var err error
|
||||
args := &getObjectArgs{}
|
||||
args := &conditionalArgs{
|
||||
IfMatch: headers.Get(api.IfMatch),
|
||||
IfNoneMatch: headers.Get(api.IfNoneMatch),
|
||||
}
|
||||
|
||||
if args.IfModifiedSince, err = parseHTTPTime(headers.Get(api.IfModifiedSince)); err != nil {
|
||||
return nil, err
|
||||
|
@ -130,7 +155,7 @@ func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return args, nil
|
||||
return &getObjectArgs{Conditional: args}, nil
|
||||
}
|
||||
|
||||
func parseHTTPTime(data string) (*time.Time, error) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package handler
|
|||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -39,3 +40,102 @@ func TestFetchRangeHeader(t *testing.T) {
|
|||
require.Equal(t, tc.expected, params)
|
||||
}
|
||||
}
|
||||
|
||||
func newInfo(etag string, created time.Time) *layer.ObjectInfo {
|
||||
return &layer.ObjectInfo{
|
||||
HashSum: etag,
|
||||
Created: created,
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreconditions(t *testing.T) {
|
||||
today := time.Now()
|
||||
yesterday := today.Add(-24 * time.Hour)
|
||||
etag := "etag"
|
||||
etag2 := "etag2"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
info *layer.ObjectInfo
|
||||
args *conditionalArgs
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "no conditions",
|
||||
info: new(layer.ObjectInfo),
|
||||
args: new(conditionalArgs),
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "IfMatch true",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfMatch: etag},
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "IfMatch false",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfMatch: etag2},
|
||||
expected: http.StatusPreconditionFailed},
|
||||
{
|
||||
name: "IfNoneMatch true",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfNoneMatch: etag2},
|
||||
expected: http.StatusOK},
|
||||
{
|
||||
name: "IfNoneMatch false",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfNoneMatch: etag},
|
||||
expected: http.StatusNotModified},
|
||||
{
|
||||
name: "IfModifiedSince true",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfModifiedSince: &yesterday},
|
||||
expected: http.StatusOK},
|
||||
{
|
||||
name: "IfModifiedSince false",
|
||||
info: newInfo(etag, yesterday),
|
||||
args: &conditionalArgs{IfModifiedSince: &today},
|
||||
expected: http.StatusNotModified},
|
||||
{
|
||||
name: "IfUnmodifiedSince true",
|
||||
info: newInfo(etag, yesterday),
|
||||
args: &conditionalArgs{IfUnmodifiedSince: &today},
|
||||
expected: http.StatusOK},
|
||||
{
|
||||
name: "IfUnmodifiedSince false",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfUnmodifiedSince: &yesterday},
|
||||
expected: http.StatusPreconditionFailed},
|
||||
|
||||
{
|
||||
name: "IfMatch true, IfUnmodifiedSince false",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfMatch: etag, IfUnmodifiedSince: &yesterday},
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "IfMatch false, IfUnmodifiedSince true",
|
||||
info: newInfo(etag, yesterday),
|
||||
args: &conditionalArgs{IfMatch: etag2, IfUnmodifiedSince: &today},
|
||||
expected: http.StatusPreconditionFailed,
|
||||
},
|
||||
{
|
||||
name: "IfNoneMatch false, IfModifiedSince true",
|
||||
info: newInfo(etag, today),
|
||||
args: &conditionalArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday},
|
||||
expected: http.StatusNotModified,
|
||||
},
|
||||
{
|
||||
name: "IfNoneMatch true, IfModifiedSince false",
|
||||
info: newInfo(etag, yesterday),
|
||||
args: &conditionalArgs{IfNoneMatch: etag2, IfModifiedSince: &today},
|
||||
expected: http.StatusNotModified,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := checkPreconditions(tc.info, tc.args)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,11 @@ const (
|
|||
Action = "Action"
|
||||
IfModifiedSince = "If-Modified-Since"
|
||||
IfUnmodifiedSince = "If-Unmodified-Since"
|
||||
IfMatch = "If-Match"
|
||||
IfNoneMatch = "If-None-Match"
|
||||
|
||||
AmzCopyIfModifiedSince = "X-Amz-Copy-Source-If-Modified-Since"
|
||||
AmzCopyIfUnmodifiedSince = "X-Amz-Copy-Source-If-Unmodified-Since"
|
||||
AmzCopyIfMatch = "X-Amz-Copy-Source-If-Match"
|
||||
AmzCopyIfNoneMatch = "X-Amz-Copy-Source-If-None-Match"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue