package handler import ( "context" "errors" "io" "net/url" "sync/atomic" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/response" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/valyala/fasthttp" "go.uber.org/zap" ) type Handler struct { log *zap.Logger pool *pool.Pool ownerID *user.ID settings *Settings containerResolver *resolver.ContainerResolver tree *tree.Tree } // Settings stores reloading parameters, so it has to provide atomic getters and setters. type Settings struct { defaultTimestamp atomic.Bool zipCompression atomic.Bool } func (s *Settings) DefaultTimestamp() bool { return s.defaultTimestamp.Load() } func (s *Settings) SetDefaultTimestamp(val bool) { s.defaultTimestamp.Store(val) } func (s *Settings) ZipCompression() bool { return s.zipCompression.Load() } func (s *Settings) SetZipCompression(val bool) { s.zipCompression.Store(val) } func New(params *utils.AppParams, settings *Settings, tree *tree.Tree) *Handler { return &Handler{ log: params.Logger, pool: params.Pool, ownerID: params.Owner, settings: settings, containerResolver: params.Resolver, tree: tree, } } // getContainerID decode container id, if it's not a valid container id // then trey to resolve name using provided resolver. func (h *Handler) getContainerID(ctx context.Context, containerID string) (*cid.ID, error) { cnrID := new(cid.ID) err := cnrID.DecodeString(containerID) if err != nil { cnrID, err = h.containerResolver.Resolve(ctx, containerID) } return cnrID, err } // byAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that // prepares request and object address to it. func (h *Handler) byAddress(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) { var ( idCnr, _ = c.UserValue("cid").(string) idObj, _ = c.UserValue("oid").(string) log = h.log.With(zap.String("cid", idCnr), zap.String("oid", idObj)) ) ctx := utils.GetContextFromRequest(c) cnrID, err := h.getContainerID(ctx, idCnr) if err != nil { log.Error(logs.WrongContainerID, zap.Error(err)) response.Error(c, "wrong container id", fasthttp.StatusBadRequest) return } objID := new(oid.ID) if err = objID.DecodeString(idObj); err != nil { log.Error(logs.WrongObjectID, zap.Error(err)) response.Error(c, "wrong object id", fasthttp.StatusBadRequest) return } var addr oid.Address addr.SetContainer(*cnrID) addr.SetObject(*objID) f(ctx, *h.newRequest(c, log), addr) } // byBucketname is a wrapper for function (e.g. request.headObject, request.receiveFile) that // prepares request and object address to it. func (h *Handler) byBucketname(req *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) { var ( bucketname = req.UserValue("cid").(string) key = req.UserValue("oid").(string) log = h.log.With(zap.String("bucketname", bucketname), zap.String("key", key)) ) ctx := utils.GetContextFromRequest(req) cnrID, err := h.getContainerID(ctx, bucketname) if err != nil { log.Error(logs.WrongContainerID, zap.Error(err)) response.Error(req, "wrong container id", fasthttp.StatusBadRequest) return } foundOid, err := h.tree.GetLatestVersion(ctx, cnrID, key) if err != nil { log.Error(logs.ObjectWasntFound, zap.Error(err)) response.Error(req, "object wasn't found", fasthttp.StatusNotFound) return } if foundOid.DeleteMarker { log.Error(logs.ObjectWasDeleted) response.Error(req, "object deleted", fasthttp.StatusNotFound) return } var addr oid.Address addr.SetContainer(*cnrID) addr.SetObject(foundOid.OID) f(ctx, *h.newRequest(req, log), addr) } // byAttribute is a wrapper similar to byAddress. func (h *Handler) byAttribute(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) { var ( scid, _ = c.UserValue("cid").(string) key, _ = url.QueryUnescape(c.UserValue("attr_key").(string)) val, _ = url.QueryUnescape(c.UserValue("attr_val").(string)) log = h.log.With(zap.String("cid", scid), zap.String("attr_key", key), zap.String("attr_val", val)) ) ctx := utils.GetContextFromRequest(c) containerID, err := h.getContainerID(ctx, scid) if err != nil { log.Error(logs.WrongContainerID, zap.Error(err)) response.Error(c, "wrong container id", fasthttp.StatusBadRequest) return } res, err := h.search(ctx, containerID, key, val, object.MatchStringEqual) if err != nil { log.Error(logs.CouldNotSearchForObjects, zap.Error(err)) response.Error(c, "could not search for objects: "+err.Error(), fasthttp.StatusBadRequest) return } defer res.Close() buf := make([]oid.ID, 1) n, err := res.Read(buf) if n == 0 { if errors.Is(err, io.EOF) { log.Error(logs.ObjectNotFound, zap.Error(err)) response.Error(c, "object not found", fasthttp.StatusNotFound) return } log.Error(logs.ReadObjectListFailed, zap.Error(err)) response.Error(c, "read object list failed: "+err.Error(), fasthttp.StatusBadRequest) return } var addrObj oid.Address addrObj.SetContainer(*containerID) addrObj.SetObject(buf[0]) f(ctx, *h.newRequest(c, log), addrObj) }