2022-02-07 20:27:56 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
2022-03-24 08:05:41 +00:00
|
|
|
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
2022-02-07 20:27:56 +00:00
|
|
|
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
|
|
|
v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs"
|
|
|
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
|
|
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
2022-03-24 08:05:41 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
2022-02-07 20:27:56 +00:00
|
|
|
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
|
|
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
|
|
|
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/session"
|
|
|
|
)
|
|
|
|
|
2022-02-11 16:13:53 +00:00
|
|
|
// shared parameters of GET/HEAD/RANGE.
|
|
|
|
type prmObjectRead struct {
|
2022-03-03 11:04:53 +00:00
|
|
|
prmCommonMeta
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
raw bool
|
|
|
|
|
|
|
|
local bool
|
|
|
|
|
|
|
|
sessionSet bool
|
|
|
|
session session.Token
|
|
|
|
|
|
|
|
bearerSet bool
|
2022-03-24 08:05:41 +00:00
|
|
|
bearer bearer.Token
|
2022-02-07 20:27:56 +00:00
|
|
|
|
|
|
|
cnrSet bool
|
2022-04-11 16:25:14 +00:00
|
|
|
cnrID cid.ID
|
2022-02-07 20:27:56 +00:00
|
|
|
|
|
|
|
objSet bool
|
2022-04-11 16:25:14 +00:00
|
|
|
objID oid.ID
|
2022-02-07 20:27:56 +00:00
|
|
|
}
|
|
|
|
|
2022-03-03 11:04:53 +00:00
|
|
|
func (x prmObjectRead) writeToMetaHeader(h *v2session.RequestMetaHeader) {
|
|
|
|
if x.local {
|
|
|
|
h.SetTTL(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if x.bearerSet {
|
2022-03-24 08:05:41 +00:00
|
|
|
var v2token acl.BearerToken
|
|
|
|
x.bearer.WriteToV2(&v2token)
|
|
|
|
h.SetBearerToken(&v2token)
|
2022-03-03 11:04:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if x.sessionSet {
|
|
|
|
h.SetSessionToken(x.session.ToV2())
|
|
|
|
}
|
|
|
|
|
|
|
|
x.prmCommonMeta.writeToMetaHeader(h)
|
|
|
|
}
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
// MarkRaw marks an intent to read physically stored object.
|
2022-02-11 16:13:53 +00:00
|
|
|
func (x *prmObjectRead) MarkRaw() {
|
2022-02-07 20:27:56 +00:00
|
|
|
x.raw = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarkLocal tells the server to execute the operation locally.
|
2022-02-11 16:13:53 +00:00
|
|
|
func (x *prmObjectRead) MarkLocal() {
|
2022-02-07 20:27:56 +00:00
|
|
|
x.local = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithinSession specifies session within which object should be read.
|
2022-02-11 16:13:53 +00:00
|
|
|
//
|
|
|
|
// Creator of the session acquires the authorship of the request.
|
|
|
|
// This may affect the execution of an operation (e.g. access control).
|
|
|
|
//
|
|
|
|
// Must be signed.
|
|
|
|
func (x *prmObjectRead) WithinSession(t session.Token) {
|
2022-02-07 20:27:56 +00:00
|
|
|
x.session = t
|
|
|
|
x.sessionSet = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithBearerToken attaches bearer token to be used for the operation.
|
2022-02-11 16:13:53 +00:00
|
|
|
//
|
|
|
|
// If set, underlying eACL rules will be used in access control.
|
|
|
|
//
|
|
|
|
// Must be signed.
|
2022-03-24 08:05:41 +00:00
|
|
|
func (x *prmObjectRead) WithBearerToken(t bearer.Token) {
|
2022-02-07 20:27:56 +00:00
|
|
|
x.bearer = t
|
|
|
|
x.bearerSet = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromContainer specifies NeoFS container of the object.
|
|
|
|
// Required parameter.
|
2022-02-11 16:13:53 +00:00
|
|
|
func (x *prmObjectRead) FromContainer(id cid.ID) {
|
2022-04-11 16:25:14 +00:00
|
|
|
x.cnrID = id
|
2022-02-07 20:27:56 +00:00
|
|
|
x.cnrSet = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// ByID specifies identifier of the requested object.
|
|
|
|
// Required parameter.
|
2022-02-11 16:13:53 +00:00
|
|
|
func (x *prmObjectRead) ByID(id oid.ID) {
|
2022-04-11 16:25:14 +00:00
|
|
|
x.objID = id
|
2022-02-07 20:27:56 +00:00
|
|
|
x.objSet = true
|
|
|
|
}
|
|
|
|
|
2022-02-11 16:13:53 +00:00
|
|
|
// PrmObjectGet groups parameters of ObjectGetInit operation.
|
|
|
|
type PrmObjectGet struct {
|
|
|
|
prmObjectRead
|
|
|
|
}
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
// ResObjectGet groups the final result values of ObjectGetInit operation.
|
|
|
|
type ResObjectGet struct {
|
|
|
|
statusRes
|
|
|
|
}
|
|
|
|
|
|
|
|
// ObjectReader is designed to read one object from NeoFS system.
|
|
|
|
//
|
|
|
|
// Must be initialized using Client.ObjectGetInit, any other
|
|
|
|
// usage is unsafe.
|
|
|
|
type ObjectReader struct {
|
|
|
|
cancelCtxStream context.CancelFunc
|
|
|
|
|
|
|
|
ctxCall contextCall
|
|
|
|
|
|
|
|
// initially bound to contextCall
|
|
|
|
bodyResp v2object.GetResponseBody
|
|
|
|
|
|
|
|
tailPayload []byte
|
2022-02-16 17:14:24 +00:00
|
|
|
|
|
|
|
remainingPayloadLen int
|
2022-02-07 20:27:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UseKey specifies private key to sign the requests.
|
|
|
|
// If key is not provided, then Client default key is used.
|
|
|
|
func (x *ObjectReader) UseKey(key ecdsa.PrivateKey) {
|
|
|
|
x.ctxCall.key = key
|
|
|
|
}
|
|
|
|
|
2022-02-11 16:13:53 +00:00
|
|
|
func handleSplitInfo(ctx *contextCall, i *v2object.SplitInfo) {
|
|
|
|
ctx.err = object.NewSplitInfoError(object.NewSplitInfoFromV2(i))
|
|
|
|
}
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
// ReadHeader reads header of the object. Result means success.
|
|
|
|
// Failure reason can be received via Close.
|
|
|
|
func (x *ObjectReader) ReadHeader(dst *object.Object) bool {
|
2022-02-17 15:41:49 +00:00
|
|
|
if !x.ctxCall.writeRequest() || !x.ctxCall.readResponse() {
|
2022-02-07 20:27:56 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
var partInit *v2object.GetObjectPartInit
|
|
|
|
|
|
|
|
switch v := x.bodyResp.GetObjectPart().(type) {
|
|
|
|
default:
|
|
|
|
x.ctxCall.err = fmt.Errorf("unexpected message instead of heading part: %T", v)
|
|
|
|
return false
|
|
|
|
case *v2object.SplitInfo:
|
2022-02-11 16:13:53 +00:00
|
|
|
handleSplitInfo(&x.ctxCall, v)
|
2022-02-07 20:27:56 +00:00
|
|
|
return false
|
|
|
|
case *v2object.GetObjectPartInit:
|
|
|
|
partInit = v
|
|
|
|
}
|
|
|
|
|
|
|
|
var objv2 v2object.Object
|
|
|
|
|
|
|
|
objv2.SetObjectID(partInit.GetObjectID())
|
|
|
|
objv2.SetHeader(partInit.GetHeader())
|
|
|
|
objv2.SetSignature(partInit.GetSignature())
|
|
|
|
|
2022-02-16 17:14:24 +00:00
|
|
|
x.remainingPayloadLen = int(objv2.GetHeader().GetPayloadLength())
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
*dst = *object.NewFromV2(&objv2) // need smth better
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x *ObjectReader) readChunk(buf []byte) (int, bool) {
|
|
|
|
var read int
|
|
|
|
|
|
|
|
// read remaining tail
|
|
|
|
read = copy(buf, x.tailPayload)
|
|
|
|
|
|
|
|
x.tailPayload = x.tailPayload[read:]
|
|
|
|
|
|
|
|
if len(buf) == read {
|
|
|
|
return read, true
|
|
|
|
}
|
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
var ok bool
|
|
|
|
var part v2object.GetObjectPart
|
|
|
|
var chunk []byte
|
|
|
|
var lastRead int
|
|
|
|
|
|
|
|
for {
|
|
|
|
// receive next message
|
|
|
|
ok = x.ctxCall.readResponse()
|
|
|
|
if !ok {
|
|
|
|
return read, false
|
|
|
|
}
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
// get chunk part message
|
|
|
|
part = x.bodyResp.GetObjectPart()
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
var partChunk *v2object.GetObjectPartChunk
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
partChunk, ok = part.(*v2object.GetObjectPartChunk)
|
|
|
|
if !ok {
|
|
|
|
x.ctxCall.err = fmt.Errorf("unexpected message instead of chunk part: %T", part)
|
|
|
|
return read, false
|
|
|
|
}
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
// read new chunk
|
|
|
|
chunk = partChunk.GetChunk()
|
|
|
|
if len(chunk) == 0 {
|
|
|
|
// just skip empty chunks since they are not prohibited by protocol
|
|
|
|
continue
|
|
|
|
}
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
lastRead = copy(buf[read:], chunk)
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
read += lastRead
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
if read == len(buf) {
|
|
|
|
// save the tail
|
|
|
|
x.tailPayload = append(x.tailPayload, chunk[lastRead:]...)
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
return read, true
|
|
|
|
}
|
|
|
|
}
|
2022-02-07 20:27:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadChunk reads another chunk of the object payload. Works similar to
|
|
|
|
// io.Reader.Read but returns success flag instead of error.
|
|
|
|
//
|
|
|
|
// Failure reason can be received via Close.
|
|
|
|
func (x *ObjectReader) ReadChunk(buf []byte) (int, bool) {
|
|
|
|
return x.readChunk(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x *ObjectReader) close(ignoreEOF bool) (*ResObjectGet, error) {
|
|
|
|
defer x.cancelCtxStream()
|
|
|
|
|
|
|
|
if x.ctxCall.err != nil {
|
|
|
|
if !errors.Is(x.ctxCall.err, io.EOF) {
|
|
|
|
return nil, x.ctxCall.err
|
|
|
|
} else if !ignoreEOF {
|
2022-02-24 11:44:49 +00:00
|
|
|
if x.remainingPayloadLen > 0 {
|
|
|
|
return nil, io.ErrUnexpectedEOF
|
|
|
|
}
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
return nil, io.EOF
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return x.ctxCall.statusRes.(*ResObjectGet), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close ends reading the object and returns the result of the operation
|
|
|
|
// along with the final results. Must be called after using the ObjectReader.
|
|
|
|
//
|
|
|
|
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
|
|
|
// Any client's internal or transport errors are returned as Go built-in error.
|
|
|
|
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
|
|
|
|
// codes are returned as error.
|
|
|
|
//
|
|
|
|
// Return errors:
|
|
|
|
// *object.SplitInfoError (returned on virtual objects with PrmObjectGet.MakeRaw).
|
|
|
|
//
|
|
|
|
// Return statuses:
|
2022-02-28 10:56:46 +00:00
|
|
|
// - global (see Client docs);
|
|
|
|
// - *apistatus.ContainerNotFound;
|
|
|
|
// - *apistatus.ObjectNotFound;
|
|
|
|
// - *apistatus.ObjectAccessDenied;
|
|
|
|
// - *apistatus.ObjectAlreadyRemoved;
|
|
|
|
// - *apistatus.SessionTokenExpired.
|
2022-02-07 20:27:56 +00:00
|
|
|
func (x *ObjectReader) Close() (*ResObjectGet, error) {
|
|
|
|
return x.close(true)
|
|
|
|
}
|
|
|
|
|
2022-02-17 15:34:38 +00:00
|
|
|
// Read implements io.Reader of the object payload.
|
2022-02-07 20:27:56 +00:00
|
|
|
func (x *ObjectReader) Read(p []byte) (int, error) {
|
|
|
|
n, ok := x.readChunk(p)
|
2022-02-17 17:16:30 +00:00
|
|
|
|
|
|
|
x.remainingPayloadLen -= n
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
if !ok {
|
|
|
|
res, err := x.close(false)
|
|
|
|
if err != nil {
|
|
|
|
return n, err
|
|
|
|
}
|
2022-02-16 17:14:24 +00:00
|
|
|
|
|
|
|
return n, apistatus.ErrFromStatus(res.Status())
|
2022-02-07 20:27:56 +00:00
|
|
|
}
|
|
|
|
|
2022-02-17 17:16:30 +00:00
|
|
|
if x.remainingPayloadLen < 0 {
|
2022-02-16 17:14:24 +00:00
|
|
|
return n, errors.New("payload size overflow")
|
|
|
|
}
|
|
|
|
|
2022-02-07 20:27:56 +00:00
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ObjectGetInit initiates reading an object through a remote server using NeoFS API protocol.
|
|
|
|
//
|
2022-02-11 16:13:53 +00:00
|
|
|
// The call only opens the transmission channel, explicit fetching is done using the ObjectReader.
|
2022-02-07 20:27:56 +00:00
|
|
|
// Exactly one return value is non-nil. Resulting reader must be finally closed.
|
|
|
|
//
|
|
|
|
// Immediately panics if parameters are set incorrectly (see PrmObjectGet docs).
|
|
|
|
// Context is required and must not be nil. It is used for network communication.
|
|
|
|
func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectReader, error) {
|
|
|
|
// check parameters
|
|
|
|
switch {
|
|
|
|
case ctx == nil:
|
|
|
|
panic(panicMsgMissingContext)
|
|
|
|
case !prm.cnrSet:
|
|
|
|
panic(panicMsgMissingContainer)
|
|
|
|
case !prm.objSet:
|
|
|
|
panic("missing object")
|
|
|
|
}
|
|
|
|
|
2022-04-11 16:25:14 +00:00
|
|
|
var (
|
|
|
|
addr v2refs.Address
|
|
|
|
oidV2 v2refs.ObjectID
|
|
|
|
cidV2 v2refs.ContainerID
|
|
|
|
)
|
|
|
|
|
|
|
|
prm.objID.WriteToV2(&oidV2)
|
|
|
|
prm.cnrID.WriteToV2(&cidV2)
|
2022-02-07 20:27:56 +00:00
|
|
|
|
2022-04-11 16:25:14 +00:00
|
|
|
addr.SetContainerID(&cidV2)
|
|
|
|
addr.SetObjectID(&oidV2)
|
2022-02-07 20:27:56 +00:00
|
|
|
|
|
|
|
// form request body
|
|
|
|
var body v2object.GetRequestBody
|
|
|
|
|
|
|
|
body.SetRaw(prm.raw)
|
|
|
|
body.SetAddress(&addr)
|
|
|
|
|
|
|
|
// form meta header
|
|
|
|
var meta v2session.RequestMetaHeader
|
|
|
|
|
2022-03-03 11:04:53 +00:00
|
|
|
prm.prmObjectRead.writeToMetaHeader(&meta)
|
2022-02-07 20:27:56 +00:00
|
|
|
|
|
|
|
// form request
|
|
|
|
var req v2object.GetRequest
|
|
|
|
|
|
|
|
req.SetBody(&body)
|
|
|
|
req.SetMetaHeader(&meta)
|
|
|
|
|
|
|
|
// init reader
|
|
|
|
var (
|
|
|
|
r ObjectReader
|
|
|
|
resp v2object.GetResponse
|
|
|
|
stream *rpcapi.GetResponseReader
|
|
|
|
)
|
|
|
|
|
|
|
|
ctx, r.cancelCtxStream = context.WithCancel(ctx)
|
|
|
|
|
|
|
|
resp.SetBody(&r.bodyResp)
|
|
|
|
|
|
|
|
// init call context
|
|
|
|
c.initCallContext(&r.ctxCall)
|
|
|
|
r.ctxCall.req = &req
|
|
|
|
r.ctxCall.statusRes = new(ResObjectGet)
|
|
|
|
r.ctxCall.resp = &resp
|
|
|
|
r.ctxCall.wReq = func() error {
|
|
|
|
var err error
|
|
|
|
|
2022-03-07 11:39:49 +00:00
|
|
|
stream, err = rpcapi.GetObject(&c.c, &req, client.WithContext(ctx))
|
2022-02-07 20:27:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("open stream: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
r.ctxCall.rResp = func() error {
|
|
|
|
return stream.Read(&resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &r, nil
|
|
|
|
}
|
2022-02-11 16:13:53 +00:00
|
|
|
|
|
|
|
// PrmObjectHead groups parameters of ObjectHead operation.
|
|
|
|
type PrmObjectHead struct {
|
|
|
|
prmObjectRead
|
2022-02-21 12:02:23 +00:00
|
|
|
|
|
|
|
keySet bool
|
|
|
|
key ecdsa.PrivateKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// UseKey specifies private key to sign the requests.
|
|
|
|
// If key is not provided, then Client default key is used.
|
|
|
|
func (x *PrmObjectHead) UseKey(key ecdsa.PrivateKey) {
|
|
|
|
x.keySet = true
|
|
|
|
x.key = key
|
2022-02-11 16:13:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ResObjectHead groups resulting values of ObjectHead operation.
|
|
|
|
type ResObjectHead struct {
|
|
|
|
statusRes
|
|
|
|
|
|
|
|
// requested object (response doesn't carry the ID)
|
|
|
|
idObj oid.ID
|
|
|
|
|
|
|
|
hdr *v2object.HeaderWithSignature
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadHeader reads header of the requested object.
|
|
|
|
// Returns false if header is missing in the response (not read).
|
|
|
|
func (x *ResObjectHead) ReadHeader(dst *object.Object) bool {
|
|
|
|
if x.hdr == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
var objv2 v2object.Object
|
|
|
|
|
|
|
|
objv2.SetHeader(x.hdr.GetHeader())
|
|
|
|
objv2.SetSignature(x.hdr.GetSignature())
|
|
|
|
|
2022-03-03 11:48:55 +00:00
|
|
|
obj := object.NewFromV2(&objv2)
|
2022-04-11 16:25:14 +00:00
|
|
|
obj.SetID(x.idObj)
|
2022-02-11 16:13:53 +00:00
|
|
|
|
2022-03-03 11:48:55 +00:00
|
|
|
*dst = *obj
|
2022-02-11 16:13:53 +00:00
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// ObjectHead reads object header through a remote server using NeoFS API protocol.
|
|
|
|
//
|
|
|
|
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
|
|
|
// Any client's internal or transport errors are returned as `error`,
|
|
|
|
// If WithNeoFSErrorParsing option has been provided, unsuccessful
|
|
|
|
// NeoFS status codes are returned as `error`, otherwise, are included
|
|
|
|
// in the returned result structure.
|
|
|
|
//
|
|
|
|
// Immediately panics if parameters are set incorrectly (see PrmObjectHead docs).
|
|
|
|
// Context is required and must not be nil. It is used for network communication.
|
|
|
|
//
|
|
|
|
// Return errors:
|
|
|
|
// *object.SplitInfoError (returned on virtual objects with PrmObjectHead.MakeRaw).
|
|
|
|
//
|
|
|
|
// Return statuses:
|
2022-02-28 10:56:46 +00:00
|
|
|
// - global (see Client docs);
|
|
|
|
// - *apistatus.ContainerNotFound;
|
|
|
|
// - *apistatus.ObjectNotFound;
|
|
|
|
// - *apistatus.ObjectAccessDenied;
|
|
|
|
// - *apistatus.ObjectAlreadyRemoved;
|
|
|
|
// - *apistatus.SessionTokenExpired.
|
2022-02-11 16:13:53 +00:00
|
|
|
func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectHead, error) {
|
|
|
|
switch {
|
|
|
|
case ctx == nil:
|
|
|
|
panic(panicMsgMissingContext)
|
|
|
|
case !prm.cnrSet:
|
|
|
|
panic(panicMsgMissingContainer)
|
|
|
|
case !prm.objSet:
|
|
|
|
panic("missing object")
|
|
|
|
}
|
|
|
|
|
2022-04-11 16:25:14 +00:00
|
|
|
var (
|
|
|
|
addrV2 v2refs.Address
|
|
|
|
oidV2 v2refs.ObjectID
|
|
|
|
cidV2 v2refs.ContainerID
|
|
|
|
)
|
|
|
|
|
|
|
|
prm.objID.WriteToV2(&oidV2)
|
|
|
|
prm.cnrID.WriteToV2(&cidV2)
|
2022-02-11 16:13:53 +00:00
|
|
|
|
2022-04-11 16:25:14 +00:00
|
|
|
addrV2.SetContainerID(&cidV2)
|
|
|
|
addrV2.SetObjectID(&oidV2)
|
2022-02-11 16:13:53 +00:00
|
|
|
|
|
|
|
// form request body
|
|
|
|
var body v2object.HeadRequestBody
|
|
|
|
|
|
|
|
body.SetRaw(prm.raw)
|
2022-04-11 16:25:14 +00:00
|
|
|
body.SetAddress(&addrV2)
|
2022-02-11 16:13:53 +00:00
|
|
|
|
|
|
|
// form meta header
|
|
|
|
var meta v2session.RequestMetaHeader
|
|
|
|
|
2022-03-03 11:04:53 +00:00
|
|
|
prm.prmObjectRead.writeToMetaHeader(&meta)
|
2022-02-11 16:13:53 +00:00
|
|
|
|
|
|
|
// form request
|
|
|
|
var req v2object.HeadRequest
|
|
|
|
|
|
|
|
req.SetBody(&body)
|
|
|
|
req.SetMetaHeader(&meta)
|
|
|
|
|
|
|
|
// init call context
|
|
|
|
|
|
|
|
var (
|
|
|
|
cc contextCall
|
|
|
|
res ResObjectHead
|
|
|
|
)
|
|
|
|
|
2022-04-11 16:25:14 +00:00
|
|
|
res.idObj = prm.objID
|
2022-02-11 16:13:53 +00:00
|
|
|
|
2022-02-21 12:02:23 +00:00
|
|
|
if prm.keySet {
|
|
|
|
c.initCallContextWithoutKey(&cc)
|
|
|
|
cc.key = prm.key
|
|
|
|
} else {
|
|
|
|
c.initCallContext(&cc)
|
|
|
|
}
|
|
|
|
|
2022-02-11 16:13:53 +00:00
|
|
|
cc.req = &req
|
|
|
|
cc.statusRes = &res
|
|
|
|
cc.call = func() (responseV2, error) {
|
2022-03-07 11:39:49 +00:00
|
|
|
return rpcapi.HeadObject(&c.c, &req, client.WithContext(ctx))
|
2022-02-11 16:13:53 +00:00
|
|
|
}
|
|
|
|
cc.result = func(r responseV2) {
|
|
|
|
switch v := r.(*v2object.HeadResponse).GetBody().GetHeaderPart().(type) {
|
|
|
|
default:
|
|
|
|
cc.err = fmt.Errorf("unexpected header type %T", v)
|
|
|
|
case *v2object.SplitInfo:
|
|
|
|
handleSplitInfo(&cc, v)
|
|
|
|
case *v2object.HeaderWithSignature:
|
|
|
|
res.hdr = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// process call
|
|
|
|
if !cc.processCall() {
|
|
|
|
return nil, cc.err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &res, nil
|
|
|
|
}
|
2022-02-12 09:26:38 +00:00
|
|
|
|
|
|
|
// PrmObjectRange groups parameters of ObjectRange operation.
|
|
|
|
type PrmObjectRange struct {
|
|
|
|
prmObjectRead
|
|
|
|
|
|
|
|
off, ln uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetOffset sets offset of the payload range to be read.
|
|
|
|
// Zero by default.
|
|
|
|
func (x *PrmObjectRange) SetOffset(off uint64) {
|
|
|
|
x.off = off
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetLength sets length of the payload range to be read.
|
|
|
|
// Must be positive.
|
|
|
|
func (x *PrmObjectRange) SetLength(ln uint64) {
|
|
|
|
x.ln = ln
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResObjectRange groups the final result values of ObjectRange operation.
|
|
|
|
type ResObjectRange struct {
|
|
|
|
statusRes
|
|
|
|
}
|
|
|
|
|
|
|
|
// ObjectRangeReader is designed to read payload range of one object
|
|
|
|
// from NeoFS system.
|
|
|
|
//
|
|
|
|
// Must be initialized using Client.ObjectRangeInit, any other
|
|
|
|
// usage is unsafe.
|
|
|
|
type ObjectRangeReader struct {
|
|
|
|
cancelCtxStream context.CancelFunc
|
|
|
|
|
|
|
|
ctxCall contextCall
|
|
|
|
|
|
|
|
reqWritten bool
|
|
|
|
|
|
|
|
// initially bound to contextCall
|
|
|
|
bodyResp v2object.GetRangeResponseBody
|
|
|
|
|
|
|
|
tailPayload []byte
|
2022-02-16 17:14:24 +00:00
|
|
|
|
|
|
|
remainingPayloadLen int
|
2022-02-12 09:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UseKey specifies private key to sign the requests.
|
|
|
|
// If key is not provided, then Client default key is used.
|
|
|
|
func (x *ObjectRangeReader) UseKey(key ecdsa.PrivateKey) {
|
|
|
|
x.ctxCall.key = key
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) {
|
|
|
|
if !x.reqWritten {
|
|
|
|
if !x.ctxCall.writeRequest() {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
|
|
|
|
x.reqWritten = true
|
|
|
|
}
|
|
|
|
|
|
|
|
var read int
|
|
|
|
|
|
|
|
// read remaining tail
|
|
|
|
read = copy(buf, x.tailPayload)
|
|
|
|
|
|
|
|
x.tailPayload = x.tailPayload[read:]
|
|
|
|
|
|
|
|
if len(buf) == read {
|
|
|
|
return read, true
|
|
|
|
}
|
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
var ok bool
|
2022-02-12 09:26:38 +00:00
|
|
|
var partChunk *v2object.GetRangePartChunk
|
2022-02-24 11:33:43 +00:00
|
|
|
var chunk []byte
|
|
|
|
var lastRead int
|
|
|
|
|
|
|
|
for {
|
|
|
|
// receive next message
|
|
|
|
ok = x.ctxCall.readResponse()
|
|
|
|
if !ok {
|
|
|
|
return read, false
|
|
|
|
}
|
2022-02-12 09:26:38 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
// get chunk message
|
|
|
|
switch v := x.bodyResp.GetRangePart().(type) {
|
|
|
|
default:
|
|
|
|
x.ctxCall.err = fmt.Errorf("unexpected message received: %T", v)
|
|
|
|
return read, false
|
|
|
|
case *v2object.SplitInfo:
|
|
|
|
handleSplitInfo(&x.ctxCall, v)
|
|
|
|
return read, false
|
|
|
|
case *v2object.GetRangePartChunk:
|
|
|
|
partChunk = v
|
|
|
|
}
|
2022-02-12 09:26:38 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
chunk = partChunk.GetChunk()
|
|
|
|
if len(chunk) == 0 {
|
|
|
|
// just skip empty chunks since they are not prohibited by protocol
|
|
|
|
continue
|
|
|
|
}
|
2022-02-12 09:26:38 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
lastRead = copy(buf[read:], chunk)
|
2022-02-12 09:26:38 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
read += lastRead
|
2022-02-12 09:26:38 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
if read == len(buf) {
|
|
|
|
// save the tail
|
|
|
|
x.tailPayload = append(x.tailPayload, chunk[lastRead:]...)
|
2022-02-12 09:26:38 +00:00
|
|
|
|
2022-02-24 11:33:43 +00:00
|
|
|
return read, true
|
|
|
|
}
|
|
|
|
}
|
2022-02-12 09:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadChunk reads another chunk of the object payload range.
|
|
|
|
// Works similar to io.Reader.Read but returns success flag instead of error.
|
|
|
|
//
|
|
|
|
// Failure reason can be received via Close.
|
|
|
|
func (x *ObjectRangeReader) ReadChunk(buf []byte) (int, bool) {
|
|
|
|
return x.readChunk(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x *ObjectRangeReader) close(ignoreEOF bool) (*ResObjectRange, error) {
|
|
|
|
defer x.cancelCtxStream()
|
|
|
|
|
|
|
|
if x.ctxCall.err != nil {
|
|
|
|
if !errors.Is(x.ctxCall.err, io.EOF) {
|
|
|
|
return nil, x.ctxCall.err
|
|
|
|
} else if !ignoreEOF {
|
2022-02-24 11:44:49 +00:00
|
|
|
if x.remainingPayloadLen > 0 {
|
|
|
|
return nil, io.ErrUnexpectedEOF
|
|
|
|
}
|
|
|
|
|
2022-02-12 09:26:38 +00:00
|
|
|
return nil, io.EOF
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return x.ctxCall.statusRes.(*ResObjectRange), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close ends reading the payload range and returns the result of the operation
|
|
|
|
// along with the final results. Must be called after using the ObjectRangeReader.
|
|
|
|
//
|
|
|
|
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
|
|
|
// Any client's internal or transport errors are returned as Go built-in error.
|
|
|
|
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
|
|
|
|
// codes are returned as error.
|
|
|
|
//
|
|
|
|
// Return errors:
|
|
|
|
// *object.SplitInfoError (returned on virtual objects with PrmObjectRange.MakeRaw).
|
|
|
|
//
|
|
|
|
// Return statuses:
|
2022-02-28 10:56:46 +00:00
|
|
|
// - global (see Client docs);
|
|
|
|
// - *apistatus.ContainerNotFound;
|
|
|
|
// - *apistatus.ObjectNotFound;
|
|
|
|
// - *apistatus.ObjectAccessDenied;
|
|
|
|
// - *apistatus.ObjectAlreadyRemoved;
|
|
|
|
// - *apistatus.SessionTokenExpired.
|
2022-02-12 09:26:38 +00:00
|
|
|
func (x *ObjectRangeReader) Close() (*ResObjectRange, error) {
|
|
|
|
return x.close(true)
|
|
|
|
}
|
|
|
|
|
2022-02-17 15:34:38 +00:00
|
|
|
// Read implements io.Reader of the object payload.
|
2022-02-12 09:26:38 +00:00
|
|
|
func (x *ObjectRangeReader) Read(p []byte) (int, error) {
|
|
|
|
n, ok := x.readChunk(p)
|
2022-02-17 17:16:30 +00:00
|
|
|
|
|
|
|
x.remainingPayloadLen -= n
|
|
|
|
|
2022-02-12 09:26:38 +00:00
|
|
|
if !ok {
|
|
|
|
res, err := x.close(false)
|
|
|
|
if err != nil {
|
|
|
|
return n, err
|
|
|
|
}
|
2022-02-16 17:14:24 +00:00
|
|
|
|
|
|
|
return n, apistatus.ErrFromStatus(res.Status())
|
|
|
|
}
|
|
|
|
|
2022-02-17 17:16:30 +00:00
|
|
|
if x.remainingPayloadLen < 0 {
|
2022-02-16 17:14:24 +00:00
|
|
|
return n, errors.New("payload range size overflow")
|
2022-02-12 09:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ObjectRangeInit initiates reading an object's payload range through a remote
|
|
|
|
// server using NeoFS API protocol.
|
|
|
|
//
|
|
|
|
// The call only opens the transmission channel, explicit fetching is done using the ObjectRangeReader.
|
|
|
|
// Exactly one return value is non-nil. Resulting reader must be finally closed.
|
|
|
|
//
|
|
|
|
// Immediately panics if parameters are set incorrectly (see PrmObjectRange docs).
|
|
|
|
// Context is required and must not be nil. It is used for network communication.
|
|
|
|
func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*ObjectRangeReader, error) {
|
|
|
|
// check parameters
|
|
|
|
switch {
|
|
|
|
case ctx == nil:
|
|
|
|
panic(panicMsgMissingContext)
|
|
|
|
case !prm.cnrSet:
|
|
|
|
panic(panicMsgMissingContainer)
|
|
|
|
case !prm.objSet:
|
|
|
|
panic("missing object")
|
|
|
|
case prm.ln == 0:
|
|
|
|
panic("zero range length")
|
|
|
|
}
|
|
|
|
|
2022-04-11 16:25:14 +00:00
|
|
|
var (
|
|
|
|
addrV2 v2refs.Address
|
|
|
|
oidV2 v2refs.ObjectID
|
|
|
|
cidV2 v2refs.ContainerID
|
|
|
|
)
|
2022-02-12 09:26:38 +00:00
|
|
|
|
2022-04-11 16:25:14 +00:00
|
|
|
prm.objID.WriteToV2(&oidV2)
|
|
|
|
prm.cnrID.WriteToV2(&cidV2)
|
|
|
|
|
|
|
|
addrV2.SetContainerID(&cidV2)
|
|
|
|
addrV2.SetObjectID(&oidV2)
|
2022-02-12 09:26:38 +00:00
|
|
|
|
|
|
|
var rng v2object.Range
|
|
|
|
|
|
|
|
rng.SetOffset(prm.off)
|
|
|
|
rng.SetLength(prm.ln)
|
|
|
|
|
|
|
|
// form request body
|
|
|
|
var body v2object.GetRangeRequestBody
|
|
|
|
|
|
|
|
body.SetRaw(prm.raw)
|
2022-04-11 16:25:14 +00:00
|
|
|
body.SetAddress(&addrV2)
|
2022-02-12 09:26:38 +00:00
|
|
|
body.SetRange(&rng)
|
|
|
|
|
|
|
|
// form meta header
|
|
|
|
var meta v2session.RequestMetaHeader
|
|
|
|
|
2022-03-03 11:04:53 +00:00
|
|
|
prm.prmObjectRead.writeToMetaHeader(&meta)
|
2022-02-12 09:26:38 +00:00
|
|
|
|
|
|
|
// form request
|
|
|
|
var req v2object.GetRangeRequest
|
|
|
|
|
|
|
|
req.SetBody(&body)
|
|
|
|
req.SetMetaHeader(&meta)
|
|
|
|
|
|
|
|
// init reader
|
|
|
|
var (
|
|
|
|
r ObjectRangeReader
|
|
|
|
resp v2object.GetRangeResponse
|
|
|
|
stream *rpcapi.ObjectRangeResponseReader
|
|
|
|
)
|
|
|
|
|
2022-02-16 17:14:24 +00:00
|
|
|
r.remainingPayloadLen = int(prm.ln)
|
|
|
|
|
2022-02-12 09:26:38 +00:00
|
|
|
ctx, r.cancelCtxStream = context.WithCancel(ctx)
|
|
|
|
|
|
|
|
resp.SetBody(&r.bodyResp)
|
|
|
|
|
|
|
|
// init call context
|
|
|
|
c.initCallContext(&r.ctxCall)
|
|
|
|
r.ctxCall.req = &req
|
|
|
|
r.ctxCall.statusRes = new(ResObjectRange)
|
|
|
|
r.ctxCall.resp = &resp
|
|
|
|
r.ctxCall.wReq = func() error {
|
|
|
|
var err error
|
|
|
|
|
2022-03-07 11:39:49 +00:00
|
|
|
stream, err = rpcapi.GetObjectRange(&c.c, &req, client.WithContext(ctx))
|
2022-02-12 09:26:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("open stream: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
r.ctxCall.rResp = func() error {
|
|
|
|
return stream.Read(&resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &r, nil
|
|
|
|
}
|