frostfs-s3-gw/api/handler/object_list.go
Angira Kekteeva b1cda2a714 [#154] api: refactor EncodeV1 and EncodeV2
Move common parts of Encode to separate functions.

Signed-off-by: Angira Kekteeva <kira@nspcc.ru>
2021-07-19 16:38:51 +03:00

269 lines
7 KiB
Go

package handler
import (
"net/http"
"strconv"
"time"
"github.com/nspcc-dev/neofs-s3-gw/api"
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
)
// ListObjectsV1Handler handles objects listing requests for API version 1.
func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
params, err := parseListObjectsArgsV1(r)
if err != nil {
h.registerAndSendError(w, r, err, "failed to parse arguments")
return
}
list, err := h.obj.ListObjectsV1(r.Context(), params)
if err != nil {
h.registerAndSendError(w, r, err, "something went wrong")
return
}
err = api.EncodeToResponse(w, encodeV1(params, list))
if err != nil {
h.registerAndSendError(w, r, err, "something went wrong")
}
}
func encodeV1(p *layer.ListObjectsParamsV1, list *layer.ListObjectsInfoV1) *ListObjectsV1Response {
res := &ListObjectsV1Response{
Name: p.Bucket,
EncodingType: p.Encode,
Marker: p.Marker,
Prefix: p.Prefix,
MaxKeys: p.MaxKeys,
Delimiter: p.Delimiter,
IsTruncated: list.IsTruncated,
NextMarker: list.NextMarker,
}
res.CommonPrefixes = fillPrefixes(list.Prefixes, p.Encode)
res.Contents = fillContents(list.Objects, p.Encode)
return res
}
// ListObjectsV2Handler handles objects listing requests for API version 2.
func (h *handler) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
params, err := parseListObjectsArgsV2(r)
if err != nil {
h.registerAndSendError(w, r, err, "failed to parse arguments")
return
}
list, err := h.obj.ListObjectsV2(r.Context(), params)
if err != nil {
h.registerAndSendError(w, r, err, "something went wrong")
return
}
err = api.EncodeToResponse(w, encodeV2(params, list))
if err != nil {
h.registerAndSendError(w, r, err, "something went wrong")
}
}
func encodeV2(p *layer.ListObjectsParamsV2, list *layer.ListObjectsInfoV2) *ListObjectsV2Response {
res := &ListObjectsV2Response{
Name: p.Bucket,
EncodingType: p.Encode,
Prefix: s3PathEncode(p.Prefix, p.Encode),
KeyCount: len(list.Objects) + len(list.Prefixes),
MaxKeys: p.MaxKeys,
Delimiter: s3PathEncode(p.Delimiter, p.Encode),
StartAfter: s3PathEncode(p.StartAfter, p.Encode),
IsTruncated: list.IsTruncated,
ContinuationToken: p.ContinuationToken,
NextContinuationToken: list.NextContinuationToken,
}
res.CommonPrefixes = fillPrefixes(list.Prefixes, p.Encode)
res.Contents = fillContents(list.Objects, p.Encode)
return res
}
func parseListObjectsArgsV1(r *http.Request) (*layer.ListObjectsParamsV1, error) {
var (
err error
res layer.ListObjectsParamsV1
)
common, err := parseListObjectArgs(r)
if err != nil {
return nil, err
}
res.ListObjectsParamsCommon = *common
res.Marker = r.URL.Query().Get("marker")
return &res, nil
}
func parseListObjectsArgsV2(r *http.Request) (*layer.ListObjectsParamsV2, error) {
var (
err error
res layer.ListObjectsParamsV2
)
common, err := parseListObjectArgs(r)
if err != nil {
return nil, err
}
res.ListObjectsParamsCommon = *common
res.ContinuationToken = r.URL.Query().Get("continuation-token")
res.StartAfter = r.URL.Query().Get("start-after")
return &res, nil
}
func parseListObjectArgs(r *http.Request) (*layer.ListObjectsParamsCommon, error) {
var (
err error
res layer.ListObjectsParamsCommon
)
if info := api.GetReqInfo(r.Context()); info != nil {
res.Bucket = info.BucketName
}
res.Delimiter = r.URL.Query().Get("delimiter")
res.Encode = r.URL.Query().Get("encoding-type")
if r.URL.Query().Get("max-keys") == "" {
res.MaxKeys = maxObjectList
} else if res.MaxKeys, err = strconv.Atoi(r.URL.Query().Get("max-keys")); err != nil || res.MaxKeys < 0 {
return nil, api.GetAPIError(api.ErrInvalidMaxKeys)
}
res.Prefix = r.URL.Query().Get("prefix")
return &res, nil
}
func fillPrefixes(src []string, encode string) []CommonPrefix {
var dst []CommonPrefix
for _, obj := range src {
dst = append(dst, CommonPrefix{
Prefix: s3PathEncode(obj, encode),
})
}
return dst
}
func fillContents(src []*layer.ObjectInfo, encode string) []Object {
var dst []Object
for _, obj := range src {
dst = append(dst, Object{
Key: s3PathEncode(obj.Name, encode),
Size: obj.Size,
LastModified: obj.Created.Format(time.RFC3339),
Owner: Owner{
ID: obj.Owner.String(),
DisplayName: obj.Owner.String(),
},
ETag: obj.HashSum,
})
}
return dst
}
func (h *handler) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
p, err := parseListObjectVersionsRequest(r)
if err != nil {
h.registerAndSendError(w, r, err, "failed to parse request ")
return
}
info, err := h.obj.ListObjectVersions(r.Context(), p)
if err != nil {
h.registerAndSendError(w, r, err, "something went wrong")
return
}
response := encodeListObjectVersionsToResponse(info, p.Bucket)
if err := api.EncodeToResponse(w, response); err != nil {
h.registerAndSendError(w, r, err, "something went wrong")
}
}
func parseListObjectVersionsRequest(r *http.Request) (*layer.ListObjectVersionsParams, error) {
var (
err error
res layer.ListObjectVersionsParams
)
if r.URL.Query().Get("max-keys") == "" {
res.MaxKeys = maxObjectList
} else if res.MaxKeys, err = strconv.Atoi(r.URL.Query().Get("max-keys")); err != nil || res.MaxKeys <= 0 {
return nil, api.GetAPIError(api.ErrInvalidMaxKeys)
}
res.Prefix = r.URL.Query().Get("prefix")
res.KeyMarker = r.URL.Query().Get("marker")
res.Delimiter = r.URL.Query().Get("delimiter")
res.Encode = r.URL.Query().Get("encoding-type")
res.VersionIDMarker = r.URL.Query().Get("version-id-marker")
if info := api.GetReqInfo(r.Context()); info != nil {
res.Bucket = info.BucketName
}
return &res, nil
}
func encodeListObjectVersionsToResponse(info *layer.ListObjectVersionsInfo, bucketName string) *ListObjectsVersionsResponse {
res := ListObjectsVersionsResponse{
Name: bucketName,
IsTruncated: info.IsTruncated,
KeyMarker: info.KeyMarker,
NextKeyMarker: info.NextKeyMarker,
NextVersionIDMarker: info.NextVersionIDMarker,
VersionIDMarker: info.VersionIDMarker,
}
for _, prefix := range info.CommonPrefixes {
res.CommonPrefixes = append(res.CommonPrefixes, CommonPrefix{Prefix: *prefix})
}
for _, ver := range info.Version {
res.Version = append(res.Version, ObjectVersionResponse{
IsLatest: ver.IsLatest,
Key: ver.Object.Name,
LastModified: ver.Object.Created.Format(time.RFC3339),
Owner: Owner{
ID: ver.Object.Owner.String(),
DisplayName: ver.Object.Owner.String(),
},
Size: ver.Object.Size,
VersionID: ver.VersionID,
ETag: ver.Object.HashSum,
})
}
// this loop is not starting till versioning is not implemented
for _, del := range info.DeleteMarker {
res.DeleteMarker = append(res.DeleteMarker, DeleteMarkerEntry{
IsLatest: del.IsLatest,
Key: del.Key,
LastModified: del.LastModified,
Owner: Owner{
ID: del.Owner.String(),
DisplayName: del.Owner.String(),
},
VersionID: del.VersionID,
})
}
return &res
}