forked from TrueCloudLab/frostfs-s3-gw
parent
ccf5db95a5
commit
7d0bc1e992
12 changed files with 434 additions and 25 deletions
|
@ -8,7 +8,10 @@ import (
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bktVersionSettingsObject = ".s3-versioning-settings"
|
const (
|
||||||
|
bktVersionSettingsObject = ".s3-versioning-settings"
|
||||||
|
bktCORSConfigurationObject = ".s3-cors"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// BucketInfo stores basic bucket data.
|
// BucketInfo stores basic bucket data.
|
||||||
|
@ -41,6 +44,9 @@ type (
|
||||||
// SettingsObjectName is system name for bucket settings file.
|
// SettingsObjectName is system name for bucket settings file.
|
||||||
func (b *BucketInfo) SettingsObjectName() string { return bktVersionSettingsObject }
|
func (b *BucketInfo) SettingsObjectName() string { return bktVersionSettingsObject }
|
||||||
|
|
||||||
|
// CORSObjectName returns system name for bucket CORS configuration file.
|
||||||
|
func (b *BucketInfo) CORSObjectName() string { return bktCORSConfigurationObject }
|
||||||
|
|
||||||
// Version returns object version from ObjectInfo.
|
// Version returns object version from ObjectInfo.
|
||||||
func (o *ObjectInfo) Version() string { return o.ID.String() }
|
func (o *ObjectInfo) Version() string { return o.ID.String() }
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ type (
|
||||||
// Config contains data which handler need to keep.
|
// Config contains data which handler need to keep.
|
||||||
Config struct {
|
Config struct {
|
||||||
DefaultPolicy *netmap.PlacementPolicy
|
DefaultPolicy *netmap.PlacementPolicy
|
||||||
|
DefaultMaxAge int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
298
api/handler/cors.go
Normal file
298
api/handler/cors.go
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
||||||
|
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
||||||
|
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// CORSConfiguration stores CORS configuration of a request.
|
||||||
|
CORSConfiguration struct {
|
||||||
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CORSConfiguration" json:"-"`
|
||||||
|
CORSRules []CORSRule `xml:"CORSRule" json:"CORSRules"`
|
||||||
|
}
|
||||||
|
// CORSRule stores rules for CORS in a bucket.
|
||||||
|
CORSRule struct {
|
||||||
|
ID string `xml:"ID,omitempty" json:"ID,omitempty"`
|
||||||
|
AllowedHeaders []string `xml:"AllowedHeader" json:"AllowedHeaders"`
|
||||||
|
AllowedMethods []string `xml:"AllowedMethod" json:"AllowedMethods"`
|
||||||
|
AllowedOrigins []string `xml:"AllowedOrigin" json:"AllowedOrigins"`
|
||||||
|
ExposeHeaders []string `xml:"ExposeHeader" json:"ExposeHeaders"`
|
||||||
|
MaxAgeSeconds int `xml:"MaxAgeSeconds,omitempty" json:"MaxAgeSeconds,omitempty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultMaxAge -- default value of Access-Control-Max-Age if this value is not set in a rule.
|
||||||
|
DefaultMaxAge = 600
|
||||||
|
wildcard = "*"
|
||||||
|
)
|
||||||
|
|
||||||
|
var supportedMethods = map[string]struct{}{"GET": {}, "HEAD": {}, "POST": {}, "PUT": {}, "DELETE": {}}
|
||||||
|
|
||||||
|
func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||||
|
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := h.obj.GetBucketCORS(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get cors", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.WriteResponse(w, http.StatusOK, info, api.MimeNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||||
|
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cors := &CORSConfiguration{}
|
||||||
|
if err := xml.NewDecoder(r.Body).Decode(cors); err != nil {
|
||||||
|
h.logAndSendError(w, "could not parse cors configuration", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cors.CORSRules == nil {
|
||||||
|
h.logAndSendError(w, "could not parse cors rules", reqInfo, errors.GetAPIError(errors.ErrMalformedXML))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkCORS(cors); err != nil {
|
||||||
|
h.logAndSendError(w, "invalid cors configuration", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xml, err := xml.Marshal(cors)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not encode cors configuration to xml", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &layer.PutCORSParams{
|
||||||
|
BktInfo: bktInfo,
|
||||||
|
CORSConfiguration: xml,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.obj.PutBucketCORS(r.Context(), p); err != nil {
|
||||||
|
h.logAndSendError(w, "could not put cors configuration", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.WriteSuccessResponseHeadersOnly(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) DeleteBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||||
|
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.obj.DeleteBucketCORS(r.Context(), bktInfo); err != nil {
|
||||||
|
h.logAndSendError(w, "could not delete cors", reqInfo, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) AppendCORSHeaders(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
origin := r.Header.Get(api.Origin)
|
||||||
|
if origin == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
if reqInfo.BucketName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := h.obj.GetBucketCORS(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cors := &CORSConfiguration{}
|
||||||
|
if err = xml.Unmarshal(info, cors); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
withCredentials := r.Header.Get(api.Authorization) != ""
|
||||||
|
|
||||||
|
for _, rule := range cors.CORSRules {
|
||||||
|
for _, o := range rule.AllowedOrigins {
|
||||||
|
if o == origin {
|
||||||
|
for _, m := range rule.AllowedMethods {
|
||||||
|
if m == r.Method {
|
||||||
|
w.Header().Set(api.AccessControlAllowOrigin, origin)
|
||||||
|
w.Header().Set(api.AccessControlAllowCredentials, "true")
|
||||||
|
w.Header().Set(api.Vary, api.Origin)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if o == wildcard {
|
||||||
|
for _, m := range rule.AllowedMethods {
|
||||||
|
if m == r.Method {
|
||||||
|
if withCredentials {
|
||||||
|
w.Header().Set(api.AccessControlAllowOrigin, origin)
|
||||||
|
w.Header().Set(api.AccessControlAllowCredentials, "true")
|
||||||
|
w.Header().Set(api.Vary, api.Origin)
|
||||||
|
} else {
|
||||||
|
w.Header().Set(api.AccessControlAllowOrigin, o)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Preflight(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
origin := r.Header.Get(api.Origin)
|
||||||
|
if origin == "" {
|
||||||
|
h.logAndSendError(w, "origin request header needed", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
||||||
|
}
|
||||||
|
|
||||||
|
method := r.Header.Get(api.AccessControlRequestMethod)
|
||||||
|
if method == "" {
|
||||||
|
h.logAndSendError(w, "Access-Control-Request-Method request header needed", reqInfo, errors.GetAPIError(errors.ErrBadRequest))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers []string
|
||||||
|
requestHeaders := r.Header.Get(api.AccessControlRequestHeaders)
|
||||||
|
if requestHeaders != "" {
|
||||||
|
headers = strings.Split(requestHeaders, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := h.obj.GetBucketCORS(r.Context(), bktInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get cors", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cors := &CORSConfiguration{}
|
||||||
|
if err = xml.Unmarshal(info, cors); err != nil {
|
||||||
|
h.logAndSendError(w, "could not parse cors configuration", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range cors.CORSRules {
|
||||||
|
for _, o := range rule.AllowedOrigins {
|
||||||
|
if o == origin || o == wildcard {
|
||||||
|
for _, m := range rule.AllowedMethods {
|
||||||
|
if m == method {
|
||||||
|
if !checkSubslice(rule.AllowedHeaders, headers) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Header().Set(api.AccessControlAllowOrigin, o)
|
||||||
|
w.Header().Set(api.AccessControlAllowMethods, strings.Join(rule.AllowedMethods, ", "))
|
||||||
|
if headers != nil {
|
||||||
|
w.Header().Set(api.AccessControlAllowHeaders, requestHeaders)
|
||||||
|
}
|
||||||
|
if rule.ExposeHeaders != nil {
|
||||||
|
w.Header().Set(api.AccessControlExposeHeaders, strings.Join(rule.ExposeHeaders, ", "))
|
||||||
|
}
|
||||||
|
if rule.MaxAgeSeconds > 0 || rule.MaxAgeSeconds == -1 {
|
||||||
|
w.Header().Set(api.AccessControlMaxAge, strconv.Itoa(rule.MaxAgeSeconds))
|
||||||
|
} else {
|
||||||
|
w.Header().Set(api.AccessControlMaxAge, strconv.Itoa(h.cfg.DefaultMaxAge))
|
||||||
|
}
|
||||||
|
if o != wildcard {
|
||||||
|
w.Header().Set(api.AccessControlAllowCredentials, "true")
|
||||||
|
}
|
||||||
|
api.WriteSuccessResponseHeadersOnly(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.logAndSendError(w, "Forbidden", reqInfo, errors.GetAPIError(errors.ErrAccessDenied))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCORS(cors *CORSConfiguration) error {
|
||||||
|
for _, r := range cors.CORSRules {
|
||||||
|
for _, m := range r.AllowedMethods {
|
||||||
|
if _, ok := supportedMethods[m]; !ok {
|
||||||
|
return fmt.Errorf("unsupported HTTP method in CORS config %s", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, h := range r.ExposeHeaders {
|
||||||
|
if h == wildcard {
|
||||||
|
return fmt.Errorf("ExposeHeader \"*\" contains wildcard. We currently do not support wildcard " +
|
||||||
|
"for ExposeHeader")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSubslice(slice []string, subSlice []string) bool {
|
||||||
|
if sliceContains(slice, wildcard) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(subSlice) > len(slice) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, r := range subSlice {
|
||||||
|
if !sliceContains(slice, r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceContains(slice []string, str string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -59,10 +59,6 @@ func (h *handler) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Requ
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,19 @@ const (
|
||||||
AmzSourceExpectedBucketOwner = "X-Amz-Source-Expected-Bucket-Owner"
|
AmzSourceExpectedBucketOwner = "X-Amz-Source-Expected-Bucket-Owner"
|
||||||
|
|
||||||
ContainerID = "X-Container-Id"
|
ContainerID = "X-Container-Id"
|
||||||
|
|
||||||
|
AccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
||||||
|
AccessControlAllowMethods = "Access-Control-Allow-Methods"
|
||||||
|
AccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
||||||
|
AccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
||||||
|
AccessControlMaxAge = "Access-Control-Max-Age"
|
||||||
|
AccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
||||||
|
|
||||||
|
Origin = "Origin"
|
||||||
|
AccessControlRequestMethod = "Access-Control-Request-Method"
|
||||||
|
AccessControlRequestHeaders = "Access-Control-Request-Headers"
|
||||||
|
|
||||||
|
Vary = "Vary"
|
||||||
)
|
)
|
||||||
|
|
||||||
// S3 request query params.
|
// S3 request query params.
|
||||||
|
|
42
api/layer/cors.go
Normal file
42
api/layer/cors.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package layer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-s3-gw/api/data"
|
||||||
|
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
|
||||||
|
s := &PutSystemObjectParams{
|
||||||
|
BktInfo: p.BktInfo,
|
||||||
|
ObjName: p.BktInfo.CORSObjectName(),
|
||||||
|
Metadata: map[string]string{},
|
||||||
|
Prefix: "",
|
||||||
|
Payload: p.CORSConfiguration,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := n.putSystemObject(ctx, s)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ([]byte, error) {
|
||||||
|
obj, err := n.getSystemObject(ctx, bktInfo, bktInfo.CORSObjectName())
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsS3Error(err, errors.ErrNoSuchKey) {
|
||||||
|
return nil, errors.GetAPIError(errors.ErrNoSuchCORSConfiguration)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Payload() == nil {
|
||||||
|
return nil, errors.GetAPIError(errors.ErrInternalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.Payload(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error {
|
||||||
|
return n.deleteSystemObject(ctx, bktInfo, bktInfo.CORSObjectName())
|
||||||
|
}
|
|
@ -91,6 +91,12 @@ type (
|
||||||
Settings *BucketSettings
|
Settings *BucketSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutCORSParams stores PutCORS request parameters.
|
||||||
|
PutCORSParams struct {
|
||||||
|
BktInfo *data.BucketInfo
|
||||||
|
CORSConfiguration []byte
|
||||||
|
}
|
||||||
|
|
||||||
// BucketSettings stores settings such as versioning.
|
// BucketSettings stores settings such as versioning.
|
||||||
BucketSettings struct {
|
BucketSettings struct {
|
||||||
VersioningEnabled bool
|
VersioningEnabled bool
|
||||||
|
@ -168,6 +174,10 @@ type (
|
||||||
PutBucketVersioning(ctx context.Context, p *PutVersioningParams) (*data.ObjectInfo, error)
|
PutBucketVersioning(ctx context.Context, p *PutVersioningParams) (*data.ObjectInfo, error)
|
||||||
GetBucketVersioning(ctx context.Context, name string) (*BucketSettings, error)
|
GetBucketVersioning(ctx context.Context, name string) (*BucketSettings, error)
|
||||||
|
|
||||||
|
PutBucketCORS(ctx context.Context, p *PutCORSParams) error
|
||||||
|
GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ([]byte, error)
|
||||||
|
DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error
|
||||||
|
|
||||||
ListBuckets(ctx context.Context) ([]*data.BucketInfo, error)
|
ListBuckets(ctx context.Context) ([]*data.BucketInfo, error)
|
||||||
GetBucketInfo(ctx context.Context, name string) (*data.BucketInfo, error)
|
GetBucketInfo(ctx context.Context, name string) (*data.BucketInfo, error)
|
||||||
GetBucketACL(ctx context.Context, name string) (*BucketACL, error)
|
GetBucketACL(ctx context.Context, name string) (*BucketACL, error)
|
||||||
|
@ -346,7 +356,7 @@ func (n *layer) GetObject(ctx context.Context, p *GetObjectParams) error {
|
||||||
params.Range = objRange
|
params.Range = objRange
|
||||||
_, err = n.objectRange(ctx, params)
|
_, err = n.objectRange(ctx, params)
|
||||||
} else {
|
} else {
|
||||||
_, err = n.objectGet(ctx, params)
|
_, err = n.objectGetWithPayloadWriter(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -104,14 +104,20 @@ func (n *layer) objectHead(ctx context.Context, cid *cid.ID, oid *object.ID) (*o
|
||||||
return n.pool.GetObjectHeader(ctx, ops, n.BearerOpt(ctx))
|
return n.pool.GetObjectHeader(ctx, ops, n.BearerOpt(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectGet and write it into provided io.Reader.
|
// objectGetWithPayloadWriter and write it into provided io.Reader.
|
||||||
func (n *layer) objectGet(ctx context.Context, p *getParams) (*object.Object, error) {
|
func (n *layer) objectGetWithPayloadWriter(ctx context.Context, p *getParams) (*object.Object, error) {
|
||||||
// prepare length/offset writer
|
// prepare length/offset writer
|
||||||
w := newWriter(p.Writer, p.offset, p.length)
|
w := newWriter(p.Writer, p.offset, p.length)
|
||||||
ops := new(client.GetObjectParams).WithAddress(newAddress(p.cid, p.oid)).WithPayloadWriter(w)
|
ops := new(client.GetObjectParams).WithAddress(newAddress(p.cid, p.oid)).WithPayloadWriter(w)
|
||||||
return n.pool.GetObject(ctx, ops, n.BearerOpt(ctx))
|
return n.pool.GetObject(ctx, ops, n.BearerOpt(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// objectGet returns an object with payload in the object.
|
||||||
|
func (n *layer) objectGet(ctx context.Context, cid *cid.ID, oid *object.ID) (*object.Object, error) {
|
||||||
|
ops := new(client.GetObjectParams).WithAddress(newAddress(cid, oid))
|
||||||
|
return n.pool.GetObject(ctx, ops, n.BearerOpt(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
// objectRange gets object range and writes it into provided io.Writer.
|
// objectRange gets object range and writes it into provided io.Writer.
|
||||||
func (n *layer) objectRange(ctx context.Context, p *getParams) ([]byte, error) {
|
func (n *layer) objectRange(ctx context.Context, p *getParams) ([]byte, error) {
|
||||||
w := newWriter(p.Writer, p.offset, p.length)
|
w := newWriter(p.Writer, p.offset, p.length)
|
||||||
|
|
|
@ -107,19 +107,17 @@ func (n *layer) getSystemObject(ctx context.Context, bktInfo *data.BucketInfo, o
|
||||||
|
|
||||||
objInfo := versions.getLast()
|
objInfo := versions.getLast()
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
obj, err := n.objectGet(ctx, bktInfo.CID, objInfo.ID)
|
||||||
p := &getParams{
|
|
||||||
Writer: buf,
|
|
||||||
cid: bktInfo.CID,
|
|
||||||
oid: objInfo.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := n.objectGet(ctx, p)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.ToV2().SetPayload(buf.Bytes())
|
if err = n.systemCache.Put(systemObjectKey(bktInfo, objName), obj); err != nil {
|
||||||
|
n.log.Warn("couldn't put system meta to objects cache",
|
||||||
|
zap.Stringer("object id", obj.ID()),
|
||||||
|
zap.Stringer("bucket id", obj.ContainerID()),
|
||||||
|
zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ type (
|
||||||
GetBucketACLHandler(http.ResponseWriter, *http.Request)
|
GetBucketACLHandler(http.ResponseWriter, *http.Request)
|
||||||
PutBucketACLHandler(http.ResponseWriter, *http.Request)
|
PutBucketACLHandler(http.ResponseWriter, *http.Request)
|
||||||
GetBucketCorsHandler(http.ResponseWriter, *http.Request)
|
GetBucketCorsHandler(http.ResponseWriter, *http.Request)
|
||||||
|
PutBucketCorsHandler(http.ResponseWriter, *http.Request)
|
||||||
|
DeleteBucketCorsHandler(http.ResponseWriter, *http.Request)
|
||||||
GetBucketWebsiteHandler(http.ResponseWriter, *http.Request)
|
GetBucketWebsiteHandler(http.ResponseWriter, *http.Request)
|
||||||
GetBucketAccelerateHandler(http.ResponseWriter, *http.Request)
|
GetBucketAccelerateHandler(http.ResponseWriter, *http.Request)
|
||||||
GetBucketRequestPaymentHandler(http.ResponseWriter, *http.Request)
|
GetBucketRequestPaymentHandler(http.ResponseWriter, *http.Request)
|
||||||
|
@ -77,6 +79,8 @@ type (
|
||||||
DeleteBucketEncryptionHandler(http.ResponseWriter, *http.Request)
|
DeleteBucketEncryptionHandler(http.ResponseWriter, *http.Request)
|
||||||
DeleteBucketHandler(http.ResponseWriter, *http.Request)
|
DeleteBucketHandler(http.ResponseWriter, *http.Request)
|
||||||
ListBucketsHandler(http.ResponseWriter, *http.Request)
|
ListBucketsHandler(http.ResponseWriter, *http.Request)
|
||||||
|
Preflight(w http.ResponseWriter, r *http.Request)
|
||||||
|
AppendCORSHeaders(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mimeType represents various MIME type used API responses.
|
// mimeType represents various MIME type used API responses.
|
||||||
|
@ -131,6 +135,15 @@ func setRequestID(h http.Handler) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendCORS(handler Handler) mux.MiddlewareFunc {
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handler.AppendCORSHeaders(w, r)
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func logErrorResponse(l *zap.Logger) mux.MiddlewareFunc {
|
func logErrorResponse(l *zap.Logger) mux.MiddlewareFunc {
|
||||||
return func(h http.Handler) http.Handler {
|
return func(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -197,6 +210,11 @@ func Attach(r *mux.Router, domains []string, m MaxClients, h Handler, center aut
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
// Object operations
|
// Object operations
|
||||||
// HeadObject
|
// HeadObject
|
||||||
|
bucket.Use(
|
||||||
|
// -- append CORS headers to a response for
|
||||||
|
appendCORS(h),
|
||||||
|
)
|
||||||
|
bucket.Methods(http.MethodOptions).HandlerFunc(m.Handle(metrics.APIStats("preflight", h.Preflight))).Name("Options")
|
||||||
bucket.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
bucket.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
||||||
m.Handle(metrics.APIStats("headobject", h.HeadObjectHandler))).Name("HeadObject")
|
m.Handle(metrics.APIStats("headobject", h.HeadObjectHandler))).Name("HeadObject")
|
||||||
// CopyObjectPart
|
// CopyObjectPart
|
||||||
|
@ -296,7 +314,15 @@ func Attach(r *mux.Router, domains []string, m MaxClients, h Handler, center aut
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
m.Handle(metrics.APIStats("getbucketencryption", h.GetBucketEncryptionHandler))).Queries("encryption", "").
|
m.Handle(metrics.APIStats("getbucketencryption", h.GetBucketEncryptionHandler))).Queries("encryption", "").
|
||||||
Name("GetBucketEncryption")
|
Name("GetBucketEncryption")
|
||||||
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
|
m.Handle(metrics.APIStats("getbucketcors", h.GetBucketCorsHandler))).Queries("cors", "").
|
||||||
|
Name("GetBucketCors")
|
||||||
|
bucket.Methods(http.MethodPut).HandlerFunc(
|
||||||
|
m.Handle(metrics.APIStats("putbucketcors", h.PutBucketCorsHandler))).Queries("cors", "").
|
||||||
|
Name("PutBucketCors")
|
||||||
|
bucket.Methods(http.MethodDelete).HandlerFunc(
|
||||||
|
m.Handle(metrics.APIStats("deletebucketcors", h.DeleteBucketCorsHandler))).Queries("cors", "").
|
||||||
|
Name("DeleteBucketCors")
|
||||||
// Dummy Bucket Calls
|
// Dummy Bucket Calls
|
||||||
// GetBucketACL -- this is a dummy call.
|
// GetBucketACL -- this is a dummy call.
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
|
@ -306,10 +332,6 @@ func Attach(r *mux.Router, domains []string, m MaxClients, h Handler, center aut
|
||||||
bucket.Methods(http.MethodPut).HandlerFunc(
|
bucket.Methods(http.MethodPut).HandlerFunc(
|
||||||
m.Handle(metrics.APIStats("putbucketacl", h.PutBucketACLHandler))).Queries("acl", "").
|
m.Handle(metrics.APIStats("putbucketacl", h.PutBucketACLHandler))).Queries("acl", "").
|
||||||
Name("PutBucketACL")
|
Name("PutBucketACL")
|
||||||
// GetBucketCors - this is a dummy call.
|
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
|
||||||
m.Handle(metrics.APIStats("getbucketcors", h.GetBucketCorsHandler))).Queries("cors", "").
|
|
||||||
Name("GetBucketCors")
|
|
||||||
// GetBucketWebsiteHandler - this is a dummy call.
|
// GetBucketWebsiteHandler - this is a dummy call.
|
||||||
bucket.Methods(http.MethodGet).HandlerFunc(
|
bucket.Methods(http.MethodGet).HandlerFunc(
|
||||||
m.Handle(metrics.APIStats("getbucketwebsite", h.GetBucketWebsiteHandler))).Queries("website", "").
|
m.Handle(metrics.APIStats("getbucketwebsite", h.GetBucketWebsiteHandler))).Queries("website", "").
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -277,9 +278,10 @@ func getAccessBoxCacheConfig(v *viper.Viper, l *zap.Logger) *cache.Config {
|
||||||
|
|
||||||
func getHandlerOptions(v *viper.Viper, l *zap.Logger) *handler.Config {
|
func getHandlerOptions(v *viper.Viper, l *zap.Logger) *handler.Config {
|
||||||
var (
|
var (
|
||||||
cfg handler.Config
|
cfg handler.Config
|
||||||
err error
|
err error
|
||||||
policyStr = handler.DefaultPolicy
|
policyStr = handler.DefaultPolicy
|
||||||
|
defaultMaxAge = handler.DefaultMaxAge
|
||||||
)
|
)
|
||||||
|
|
||||||
if v.IsSet(cfgDefaultPolicy) {
|
if v.IsSet(cfgDefaultPolicy) {
|
||||||
|
@ -291,5 +293,17 @@ func getHandlerOptions(v *viper.Viper, l *zap.Logger) *handler.Config {
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.IsSet(cfgDefaultMaxAge) {
|
||||||
|
defaultMaxAge = v.GetInt(cfgDefaultMaxAge)
|
||||||
|
|
||||||
|
if defaultMaxAge <= 0 && defaultMaxAge != -1 {
|
||||||
|
l.Fatal("invalid defaultMaxAge",
|
||||||
|
zap.String("parameter", cfgDefaultMaxAge),
|
||||||
|
zap.String("value in config", strconv.Itoa(defaultMaxAge)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.DefaultMaxAge = defaultMaxAge
|
||||||
|
|
||||||
return &cfg
|
return &cfg
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,9 @@ const ( // Settings.
|
||||||
// Policy.
|
// Policy.
|
||||||
cfgDefaultPolicy = "default_policy"
|
cfgDefaultPolicy = "default_policy"
|
||||||
|
|
||||||
|
// CORS.
|
||||||
|
cfgDefaultMaxAge = "cors.default_max_age"
|
||||||
|
|
||||||
// MaxClients.
|
// MaxClients.
|
||||||
cfgMaxClientsCount = "max_clients_count"
|
cfgMaxClientsCount = "max_clients_count"
|
||||||
cfgMaxClientsDeadline = "max_clients_deadline"
|
cfgMaxClientsDeadline = "max_clients_deadline"
|
||||||
|
|
Loading…
Reference in a new issue