package handler import ( "context" "io" "net/http" "strconv" "time" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "github.com/valyala/fasthttp" "go.uber.org/zap" ) // max bytes needed to detect content type according to http.DetectContentType docs. const sizeToDetectType = 512 const ( hdrObjectID = "X-Object-Id" hdrOwnerID = "X-Owner-Id" hdrContainerID = "X-Container-Id" ) func (h *Handler) headObject(ctx context.Context, req request, objectAddress oid.Address) { var start = time.Now() btoken := bearerToken(ctx) prm := PrmObjectRead{ PrmAuth: PrmAuth{ BearerToken: btoken, }, Address: objectAddress, } obj, err := h.frostfs.ReadObject(ctx, prm) if err != nil { req.handleFrostFSErr(err, start) return } req.Response.Header.Set(fasthttp.HeaderContentLength, strconv.FormatUint(obj.Head.PayloadSize(), 10)) var contentType string for _, attr := range obj.Head.Attributes() { key := attr.Key() val := attr.Value() if !isValidToken(key) || !isValidValue(val) { continue } key = utils.BackwardTransformIfSystem(key) req.Response.Header.Set(utils.UserAttributeHeaderPrefix+key, val) switch key { case object.AttributeTimestamp: value, err := strconv.ParseInt(val, 10, 64) if err != nil { req.log.Info(logs.CouldntParseCreationDate, zap.String("key", key), zap.String("val", val), zap.Error(err)) continue } req.Response.Header.Set(fasthttp.HeaderLastModified, time.Unix(value, 0).UTC().Format(http.TimeFormat)) case object.AttributeContentType: contentType = val } } idsToResponse(&req.Response, obj.Head) if len(contentType) == 0 { contentType, _, err = readContentType(obj.Head.PayloadSize(), func(sz uint64) (io.Reader, error) { prmRange := PrmObjectRead{ PrmAuth: PrmAuth{ BearerToken: btoken, }, Address: objectAddress, PayloadRange: [2]uint64{0, sz}, } resObj, err := h.frostfs.ReadObject(ctx, prmRange) if err != nil { return nil, err } return resObj.Payload, nil }) if err != nil && err != io.EOF { req.handleFrostFSErr(err, start) return } } req.SetContentType(contentType) } func idsToResponse(resp *fasthttp.Response, obj *object.Object) { objID, _ := obj.ID() cnrID, _ := obj.ContainerID() resp.Header.Set(hdrObjectID, objID.String()) resp.Header.Set(hdrOwnerID, obj.OwnerID().String()) resp.Header.Set(hdrContainerID, cnrID.String()) } // HeadByAddressOrBucketName handles head requests using simple cid/oid or bucketname/key format. func (h *Handler) HeadByAddressOrBucketName(c *fasthttp.RequestCtx) { test, _ := c.UserValue("oid").(string) var id oid.ID err := id.DecodeString(test) if err != nil { h.byObjectName(c, h.headObject) } else { h.byAddress(c, h.headObject) } } // HeadByAttribute handles attribute-based head requests. func (h *Handler) HeadByAttribute(c *fasthttp.RequestCtx) { h.byAttribute(c, h.headObject) }