forked from TrueCloudLab/frostfs-http-gw
132 lines
3.6 KiB
Go
132 lines
3.6 KiB
Go
package downloader
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
|
|
"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"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
|
"github.com/valyala/fasthttp"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"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 headObject(ctx context.Context, req request, clnt *pool.Pool, objectAddress oid.Address) {
|
|
ctx, span := utils.StartHTTPServerSpan(ctx, req.RequestCtx, "HEAD Object",
|
|
trace.WithAttributes(
|
|
attribute.String("cid", objectAddress.Container().EncodeToString()),
|
|
attribute.String("oid", objectAddress.Object().EncodeToString()),
|
|
))
|
|
defer func() {
|
|
utils.SetHTTPTraceInfo(ctx, span, req.RequestCtx)
|
|
span.End()
|
|
}()
|
|
|
|
var start = time.Now()
|
|
if err := tokens.StoreBearerToken(req.RequestCtx); err != nil {
|
|
req.log.Error("could not fetch and store bearer token", zap.Error(err))
|
|
response.Error(req.RequestCtx, "could not fetch and store bearer token", fasthttp.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
btoken := bearerToken(req.RequestCtx)
|
|
|
|
var prm pool.PrmObjectHead
|
|
prm.SetAddress(objectAddress)
|
|
if btoken != nil {
|
|
prm.UseBearer(*btoken)
|
|
}
|
|
|
|
obj, err := clnt.HeadObject(ctx, prm)
|
|
if err != nil {
|
|
req.handleFrostFSErr(err, start)
|
|
return
|
|
}
|
|
|
|
req.Response.Header.Set(fasthttp.HeaderContentLength, strconv.FormatUint(obj.PayloadSize(), 10))
|
|
var contentType string
|
|
for _, attr := range obj.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("couldn't parse creation date",
|
|
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)
|
|
|
|
if len(contentType) == 0 {
|
|
contentType, _, err = readContentType(obj.PayloadSize(), func(sz uint64) (io.Reader, error) {
|
|
var prmRange pool.PrmObjectRange
|
|
prmRange.SetAddress(objectAddress)
|
|
prmRange.SetLength(sz)
|
|
if btoken != nil {
|
|
prmRange.UseBearer(*btoken)
|
|
}
|
|
|
|
resObj, err := clnt.ObjectRange(ctx, prmRange)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &resObj, 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())
|
|
}
|
|
|
|
// HeadByAddress handles head requests using simple cid/oid format.
|
|
func (d *Downloader) HeadByAddress(c *fasthttp.RequestCtx) {
|
|
d.byAddress(c, headObject)
|
|
}
|
|
|
|
// HeadByAttribute handles attribute-based head requests.
|
|
func (d *Downloader) HeadByAttribute(c *fasthttp.RequestCtx) {
|
|
d.byAttribute(c, headObject)
|
|
}
|