forked from TrueCloudLab/frostfs-rest-gw
[#1] Add basic structure and operations
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
eb642eae89
commit
9f752cd756
65 changed files with 11534 additions and 0 deletions
218
handlers/util.go
Normal file
218
handlers/util.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/pool"
|
||||
)
|
||||
|
||||
// PrmAttributes groups parameters to form attributes from request headers.
|
||||
type PrmAttributes struct {
|
||||
DefaultTimestamp bool
|
||||
DefaultFileName string
|
||||
}
|
||||
|
||||
type epochDurations struct {
|
||||
currentEpoch uint64
|
||||
msPerBlock int64
|
||||
blockPerEpoch uint64
|
||||
}
|
||||
|
||||
const (
|
||||
UserAttributeHeaderPrefix = "X-Attribute-"
|
||||
SystemAttributePrefix = "__NEOFS__"
|
||||
|
||||
ExpirationDurationAttr = SystemAttributePrefix + "EXPIRATION_DURATION"
|
||||
ExpirationTimestampAttr = SystemAttributePrefix + "EXPIRATION_TIMESTAMP"
|
||||
ExpirationRFC3339Attr = SystemAttributePrefix + "EXPIRATION_RFC3339"
|
||||
)
|
||||
|
||||
var neofsAttributeHeaderPrefixes = [...]string{"Neofs-", "NEOFS-", "neofs-"}
|
||||
|
||||
func systemTranslator(key, prefix string) string {
|
||||
// replace specified prefix with `__NEOFS__`
|
||||
key = strings.Replace(key, prefix, SystemAttributePrefix, 1)
|
||||
|
||||
// replace `-` with `_`
|
||||
key = strings.ReplaceAll(key, "-", "_")
|
||||
|
||||
// replace with uppercase
|
||||
return strings.ToUpper(key)
|
||||
}
|
||||
|
||||
func filterHeaders(header http.Header) map[string]string {
|
||||
result := make(map[string]string)
|
||||
prefix := UserAttributeHeaderPrefix
|
||||
|
||||
for key, vals := range header {
|
||||
if len(key) == 0 || len(vals) == 0 || len(vals[0]) == 0 {
|
||||
continue
|
||||
}
|
||||
// checks that key has attribute prefix
|
||||
if !strings.HasPrefix(key, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
// removing attribute prefix
|
||||
key = strings.TrimPrefix(key, prefix)
|
||||
|
||||
// checks that it's a system NeoFS header
|
||||
for _, system := range neofsAttributeHeaderPrefixes {
|
||||
if strings.HasPrefix(key, system) {
|
||||
key = systemTranslator(key, system)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// checks that attribute key not empty
|
||||
if len(key) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
result[key] = vals[0]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetObjectAttributes forms object attributes from request headers.
|
||||
func GetObjectAttributes(ctx context.Context, header http.Header, pool *pool.Pool, prm PrmAttributes) ([]object.Attribute, error) {
|
||||
filtered := filterHeaders(header)
|
||||
if needParseExpiration(filtered) {
|
||||
epochDuration, err := getEpochDurations(ctx, pool)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get epoch durations from network info: %w", err)
|
||||
}
|
||||
if err = prepareExpirationHeader(filtered, epochDuration); err != nil {
|
||||
return nil, fmt.Errorf("could not prepare expiration header: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
attributes := make([]object.Attribute, 0, len(filtered))
|
||||
// prepares attributes from filtered headers
|
||||
for key, val := range filtered {
|
||||
attribute := object.NewAttribute()
|
||||
attribute.SetKey(key)
|
||||
attribute.SetValue(val)
|
||||
attributes = append(attributes, *attribute)
|
||||
}
|
||||
// sets FileName attribute if it wasn't set from header
|
||||
if _, ok := filtered[object.AttributeFileName]; !ok && prm.DefaultFileName != "" {
|
||||
filename := object.NewAttribute()
|
||||
filename.SetKey(object.AttributeFileName)
|
||||
filename.SetValue(prm.DefaultFileName)
|
||||
attributes = append(attributes, *filename)
|
||||
}
|
||||
// sets Timestamp attribute if it wasn't set from header and enabled by settings
|
||||
if _, ok := filtered[object.AttributeTimestamp]; !ok && prm.DefaultTimestamp {
|
||||
timestamp := object.NewAttribute()
|
||||
timestamp.SetKey(object.AttributeTimestamp)
|
||||
timestamp.SetValue(strconv.FormatInt(time.Now().Unix(), 10))
|
||||
attributes = append(attributes, *timestamp)
|
||||
}
|
||||
|
||||
return attributes, nil
|
||||
}
|
||||
|
||||
func getEpochDurations(ctx context.Context, p *pool.Pool) (*epochDurations, error) {
|
||||
networkInfo, err := p.NetworkInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &epochDurations{
|
||||
currentEpoch: networkInfo.CurrentEpoch(),
|
||||
msPerBlock: networkInfo.MsPerBlock(),
|
||||
}
|
||||
|
||||
networkInfo.NetworkConfig().IterateParameters(func(parameter *netmap.NetworkParameter) bool {
|
||||
if string(parameter.Key()) == "EpochDuration" {
|
||||
data := make([]byte, 8)
|
||||
copy(data, parameter.Value())
|
||||
res.blockPerEpoch = binary.LittleEndian.Uint64(data)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if res.blockPerEpoch == 0 {
|
||||
return nil, fmt.Errorf("not found param: EpochDuration")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func needParseExpiration(headers map[string]string) bool {
|
||||
_, ok1 := headers[ExpirationDurationAttr]
|
||||
_, ok2 := headers[ExpirationRFC3339Attr]
|
||||
_, ok3 := headers[ExpirationTimestampAttr]
|
||||
return ok1 || ok2 || ok3
|
||||
}
|
||||
|
||||
func prepareExpirationHeader(headers map[string]string, epochDurations *epochDurations) error {
|
||||
expirationInEpoch := headers[objectv2.SysAttributeExpEpoch]
|
||||
|
||||
if timeRFC3339, ok := headers[ExpirationRFC3339Attr]; ok {
|
||||
expTime, err := time.Parse(time.RFC3339, timeRFC3339)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't parse value %s of header %s", timeRFC3339, ExpirationRFC3339Attr)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
if expTime.Before(now) {
|
||||
return fmt.Errorf("value %s of header %s must be in the future", timeRFC3339, ExpirationRFC3339Attr)
|
||||
}
|
||||
updateExpirationHeader(headers, epochDurations, expTime.Sub(now))
|
||||
delete(headers, ExpirationRFC3339Attr)
|
||||
}
|
||||
|
||||
if timestamp, ok := headers[ExpirationTimestampAttr]; ok {
|
||||
value, err := strconv.ParseInt(timestamp, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't parse value %s of header %s", timestamp, ExpirationTimestampAttr)
|
||||
}
|
||||
expTime := time.Unix(value, 0)
|
||||
|
||||
now := time.Now()
|
||||
if expTime.Before(now) {
|
||||
return fmt.Errorf("value %s of header %s must be in the future", timestamp, ExpirationTimestampAttr)
|
||||
}
|
||||
updateExpirationHeader(headers, epochDurations, expTime.Sub(now))
|
||||
delete(headers, ExpirationTimestampAttr)
|
||||
}
|
||||
|
||||
if duration, ok := headers[ExpirationDurationAttr]; ok {
|
||||
expDuration, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't parse value %s of header %s", duration, ExpirationDurationAttr)
|
||||
}
|
||||
if expDuration <= 0 {
|
||||
return fmt.Errorf("value %s of header %s must be positive", expDuration, ExpirationDurationAttr)
|
||||
}
|
||||
updateExpirationHeader(headers, epochDurations, expDuration)
|
||||
delete(headers, ExpirationDurationAttr)
|
||||
}
|
||||
|
||||
if expirationInEpoch != "" {
|
||||
headers[objectv2.SysAttributeExpEpoch] = expirationInEpoch
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateExpirationHeader(headers map[string]string, durations *epochDurations, expDuration time.Duration) {
|
||||
epochDuration := durations.msPerBlock * int64(durations.blockPerEpoch)
|
||||
numEpoch := expDuration.Milliseconds() / epochDuration
|
||||
headers[objectv2.SysAttributeExpEpoch] = strconv.FormatInt(int64(durations.currentEpoch)+numEpoch, 10)
|
||||
}
|
||||
|
||||
func NewString(val string) *string {
|
||||
return &val
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue