forked from TrueCloudLab/frostfs-s3-gw
162 lines
4.5 KiB
Go
162 lines
4.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
|
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
|
frosterrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func (h *handler) reqLogger(ctx context.Context) *zap.Logger {
|
|
reqLogger := middleware.GetReqLog(ctx)
|
|
if reqLogger != nil {
|
|
return reqLogger
|
|
}
|
|
return h.log
|
|
}
|
|
|
|
func (h *handler) logAndSendError(w http.ResponseWriter, logText string, reqInfo *middleware.ReqInfo, err error, additional ...zap.Field) {
|
|
err = handleDeleteMarker(w, err)
|
|
if code, wrErr := middleware.WriteErrorResponse(w, reqInfo, transformToS3Error(err)); wrErr != nil {
|
|
additional = append(additional, zap.NamedError("write_response_error", wrErr))
|
|
} else {
|
|
additional = append(additional, zap.Int("status", code))
|
|
}
|
|
fields := []zap.Field{
|
|
zap.String("request_id", reqInfo.RequestID),
|
|
zap.String("method", reqInfo.API),
|
|
zap.String("bucket", reqInfo.BucketName),
|
|
zap.String("object", reqInfo.ObjectName),
|
|
zap.String("description", logText),
|
|
zap.String("user", reqInfo.User),
|
|
zap.Error(err)}
|
|
fields = append(fields, additional...)
|
|
if traceID, err := trace.TraceIDFromHex(reqInfo.TraceID); err == nil && traceID.IsValid() {
|
|
fields = append(fields, zap.String("trace_id", reqInfo.TraceID))
|
|
}
|
|
h.log.Error(logs.RequestFailed, fields...) // consider using h.reqLogger (it requires accept context.Context or http.Request)
|
|
}
|
|
|
|
func handleDeleteMarker(w http.ResponseWriter, err error) error {
|
|
var target layer.DeleteMarkerError
|
|
if !errors.As(err, &target) {
|
|
return err
|
|
}
|
|
|
|
w.Header().Set(api.AmzDeleteMarker, "true")
|
|
return fmt.Errorf("%w: %s", s3errors.GetAPIError(target.ErrorCode), err)
|
|
}
|
|
|
|
func transformToS3Error(err error) error {
|
|
err = frosterrors.UnwrapErr(err) // this wouldn't work with errors.Join
|
|
if _, ok := err.(s3errors.Error); ok {
|
|
return err
|
|
}
|
|
|
|
if errors.Is(err, frostfs.ErrAccessDenied) ||
|
|
errors.Is(err, tree.ErrNodeAccessDenied) {
|
|
return s3errors.GetAPIError(s3errors.ErrAccessDenied)
|
|
}
|
|
|
|
if errors.Is(err, frostfs.ErrGatewayTimeout) {
|
|
return s3errors.GetAPIError(s3errors.ErrGatewayTimeout)
|
|
}
|
|
|
|
return s3errors.GetAPIError(s3errors.ErrInternalError)
|
|
}
|
|
|
|
func (h *handler) ResolveBucket(ctx context.Context, bucket string) (*data.BucketInfo, error) {
|
|
return h.obj.GetBucketInfo(ctx, bucket)
|
|
}
|
|
|
|
func (h *handler) ResolveCID(ctx context.Context, bucket string) (cid.ID, error) {
|
|
return h.obj.ResolveCID(ctx, bucket)
|
|
}
|
|
|
|
func (h *handler) getBucketAndCheckOwner(r *http.Request, bucket string, header ...string) (*data.BucketInfo, error) {
|
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), bucket)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var expected string
|
|
if len(header) == 0 {
|
|
expected = r.Header.Get(api.AmzExpectedBucketOwner)
|
|
} else {
|
|
expected = r.Header.Get(header[0])
|
|
}
|
|
|
|
if len(expected) == 0 {
|
|
return bktInfo, nil
|
|
}
|
|
|
|
return bktInfo, checkOwner(bktInfo, expected)
|
|
}
|
|
|
|
func (h *handler) getPutPayloadSize(r *http.Request) uint64 {
|
|
decodeContentSize := r.Header.Get(api.AmzDecodedContentLength)
|
|
decodedSize, err := strconv.Atoi(decodeContentSize)
|
|
if err != nil {
|
|
decodedSize = 0
|
|
}
|
|
|
|
var size uint64
|
|
if decodedSize > 0 {
|
|
size = uint64(decodedSize)
|
|
} else if r.ContentLength > 0 {
|
|
size = uint64(r.ContentLength)
|
|
}
|
|
|
|
return size
|
|
}
|
|
|
|
func parseRange(s string) (*layer.RangeParams, error) {
|
|
if s == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
prefix := "bytes="
|
|
|
|
if !strings.HasPrefix(s, prefix) {
|
|
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
|
}
|
|
|
|
s = strings.TrimPrefix(s, prefix)
|
|
|
|
valuesStr := strings.Split(s, "-")
|
|
if len(valuesStr) != 2 {
|
|
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
|
}
|
|
|
|
values := make([]uint64, 0, len(valuesStr))
|
|
for _, v := range valuesStr {
|
|
num, err := strconv.ParseUint(v, 10, 64)
|
|
if err != nil {
|
|
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
|
}
|
|
values = append(values, num)
|
|
}
|
|
if values[0] > values[1] {
|
|
return nil, s3errors.GetAPIError(s3errors.ErrInvalidRange)
|
|
}
|
|
|
|
return &layer.RangeParams{
|
|
Start: values[0],
|
|
End: values[1],
|
|
}, nil
|
|
}
|