git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/client/object_get.go (about) 1 package client 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "errors" 7 "fmt" 8 "io" 9 10 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" 11 v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" 12 v2refs "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" 13 rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" 14 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" 15 v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" 16 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" 17 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" 18 apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" 19 cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" 20 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" 21 oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" 22 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" 23 ) 24 25 // PrmObjectGet groups parameters of ObjectGetInit operation. 26 type PrmObjectGet struct { 27 XHeaders []string 28 29 BearerToken *bearer.Token 30 31 Session *session.Object 32 33 Raw bool 34 35 Local bool 36 37 ContainerID *cid.ID 38 39 ObjectID *oid.ID 40 41 Key *ecdsa.PrivateKey 42 } 43 44 func (prm *PrmObjectGet) buildRequest(c *Client) (*v2object.GetRequest, error) { 45 if prm.ContainerID == nil { 46 return nil, errorMissingContainer 47 } 48 49 if prm.ObjectID == nil { 50 return nil, errorMissingObject 51 } 52 53 if len(prm.XHeaders)%2 != 0 { 54 return nil, errorInvalidXHeaders 55 } 56 57 meta := new(v2session.RequestMetaHeader) 58 writeXHeadersToMeta(prm.XHeaders, meta) 59 60 if prm.BearerToken != nil { 61 v2BearerToken := new(acl.BearerToken) 62 prm.BearerToken.WriteToV2(v2BearerToken) 63 meta.SetBearerToken(v2BearerToken) 64 } 65 66 if prm.Session != nil { 67 v2SessionToken := new(v2session.Token) 68 prm.Session.WriteToV2(v2SessionToken) 69 meta.SetSessionToken(v2SessionToken) 70 } 71 72 if prm.Local { 73 meta.SetTTL(1) 74 } 75 76 addr := new(v2refs.Address) 77 78 cnrV2 := new(v2refs.ContainerID) 79 prm.ContainerID.WriteToV2(cnrV2) 80 addr.SetContainerID(cnrV2) 81 82 objV2 := new(v2refs.ObjectID) 83 prm.ObjectID.WriteToV2(objV2) 84 addr.SetObjectID(objV2) 85 86 body := new(v2object.GetRequestBody) 87 body.SetRaw(prm.Raw) 88 body.SetAddress(addr) 89 90 req := new(v2object.GetRequest) 91 req.SetBody(body) 92 c.prepareRequest(req, meta) 93 94 return req, nil 95 } 96 97 // ResObjectGet groups the final result values of ObjectGetInit operation. 98 type ResObjectGet struct { 99 statusRes 100 } 101 102 // ObjectReader is designed to read one object from FrostFS system. 103 // 104 // Must be initialized using Client.ObjectGetInit, any other 105 // usage is unsafe. 106 type ObjectReader struct { 107 cancelCtxStream context.CancelFunc 108 109 client *Client 110 stream interface { 111 Read(resp *v2object.GetResponse) error 112 } 113 114 res ResObjectGet 115 err error 116 117 tailPayload []byte 118 119 remainingPayloadLen int 120 } 121 122 // UseKey specifies private key to sign the requests. 123 // If key is not provided, then Client default key is used. 124 // 125 // Deprecated: Use PrmObjectGet.Key instead. 126 func (prm *PrmObjectGet) UseKey(key ecdsa.PrivateKey) { 127 prm.Key = &key 128 } 129 130 // ReadHeader reads header of the object. Result means success. 131 // Failure reason can be received via Close. 132 func (x *ObjectReader) ReadHeader(dst *object.Object) bool { 133 var resp v2object.GetResponse 134 x.err = x.stream.Read(&resp) 135 if x.err != nil { 136 return false 137 } 138 139 x.res.st, x.err = x.client.processResponse(&resp) 140 if x.err != nil || !apistatus.IsSuccessful(x.res.st) { 141 return false 142 } 143 144 var partInit *v2object.GetObjectPartInit 145 146 switch v := resp.GetBody().GetObjectPart().(type) { 147 default: 148 x.err = fmt.Errorf("unexpected message instead of heading part: %T", v) 149 return false 150 case *v2object.SplitInfo: 151 x.err = object.NewSplitInfoError(object.NewSplitInfoFromV2(v)) 152 return false 153 case *v2object.ECInfo: 154 x.err = object.NewECInfoError(object.NewECInfoFromV2(v)) 155 return false 156 case *v2object.GetObjectPartInit: 157 partInit = v 158 } 159 160 var objv2 v2object.Object 161 162 objv2.SetObjectID(partInit.GetObjectID()) 163 objv2.SetHeader(partInit.GetHeader()) 164 objv2.SetSignature(partInit.GetSignature()) 165 166 x.remainingPayloadLen = int(objv2.GetHeader().GetPayloadLength()) 167 168 *dst = *object.NewFromV2(&objv2) // need smth better 169 170 return true 171 } 172 173 func (x *ObjectReader) readChunk(buf []byte) (int, bool) { 174 var read int 175 176 // read remaining tail 177 read = copy(buf, x.tailPayload) 178 179 x.tailPayload = x.tailPayload[read:] 180 181 if len(buf) == read { 182 return read, true 183 } 184 185 var chunk []byte 186 var lastRead int 187 188 for { 189 var resp v2object.GetResponse 190 x.err = x.stream.Read(&resp) 191 if x.err != nil { 192 return read, false 193 } 194 195 x.res.st, x.err = x.client.processResponse(&resp) 196 if x.err != nil || !apistatus.IsSuccessful(x.res.st) { 197 return read, false 198 } 199 200 part := resp.GetBody().GetObjectPart() 201 partChunk, ok := part.(*v2object.GetObjectPartChunk) 202 if !ok { 203 x.err = fmt.Errorf("unexpected message instead of chunk part: %T", part) 204 return read, false 205 } 206 207 // read new chunk 208 chunk = partChunk.GetChunk() 209 if len(chunk) == 0 { 210 // just skip empty chunks since they are not prohibited by protocol 211 continue 212 } 213 214 lastRead = copy(buf[read:], chunk) 215 216 read += lastRead 217 218 if read == len(buf) { 219 // save the tail 220 x.tailPayload = append(x.tailPayload, chunk[lastRead:]...) 221 222 return read, true 223 } 224 } 225 } 226 227 // ReadChunk reads another chunk of the object payload. Works similar to 228 // io.Reader.Read but returns success flag instead of error. 229 // 230 // Failure reason can be received via Close. 231 func (x *ObjectReader) ReadChunk(buf []byte) (int, bool) { 232 return x.readChunk(buf) 233 } 234 235 func (x *ObjectReader) close(ignoreEOF bool) (*ResObjectGet, error) { 236 defer x.cancelCtxStream() 237 238 if x.err != nil { 239 if !errors.Is(x.err, io.EOF) { 240 return nil, x.err 241 } else if !ignoreEOF { 242 if x.remainingPayloadLen > 0 { 243 return nil, io.ErrUnexpectedEOF 244 } 245 246 return nil, io.EOF 247 } 248 } 249 250 return &x.res, nil 251 } 252 253 // Close ends reading the object and returns the result of the operation 254 // along with the final results. Must be called after using the ObjectReader. 255 // 256 // Exactly one return value is non-nil. By default, server status is returned in res structure. 257 // Any client's internal or transport errors are returned as Go built-in error. 258 // If Client is tuned to resolve FrostFS API statuses, then FrostFS failures 259 // codes are returned as error. 260 // 261 // Return errors: 262 // 263 // *object.SplitInfoError (returned on virtual objects with PrmObjectGet.MakeRaw). 264 // *object.ECInfoError (returned on erasure-coded objects with PrmObjectGet.MakeRaw). 265 // 266 // Return statuses: 267 // - global (see Client docs); 268 // - *apistatus.ContainerNotFound; 269 // - *apistatus.ObjectNotFound; 270 // - *apistatus.ObjectAccessDenied; 271 // - *apistatus.ObjectAlreadyRemoved; 272 // - *apistatus.SessionTokenExpired. 273 func (x *ObjectReader) Close() (*ResObjectGet, error) { 274 return x.close(true) 275 } 276 277 // Read implements io.Reader of the object payload. 278 func (x *ObjectReader) Read(p []byte) (int, error) { 279 n, ok := x.readChunk(p) 280 281 x.remainingPayloadLen -= n 282 283 if !ok { 284 res, err := x.close(false) 285 if err != nil { 286 return n, err 287 } 288 289 return n, apistatus.ErrFromStatus(res.Status()) 290 } 291 292 if x.remainingPayloadLen < 0 { 293 return n, errors.New("payload size overflow") 294 } 295 296 return n, nil 297 } 298 299 // ObjectGetInit initiates reading an object through a remote server using FrostFS API protocol. 300 // 301 // The call only opens the transmission channel, explicit fetching is done using the ObjectReader. 302 // Exactly one return value is non-nil. Resulting reader must be finally closed. 303 // 304 // Returns an error if parameters are set incorrectly (see PrmObjectGet docs). 305 // Context is required and must not be nil. It is used for network communication. 306 func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectReader, error) { 307 req, err := prm.buildRequest(c) 308 if err != nil { 309 return nil, err 310 } 311 312 key := prm.Key 313 if key == nil { 314 key = &c.prm.Key 315 } 316 317 err = signature.SignServiceMessage(key, req) 318 if err != nil { 319 return nil, fmt.Errorf("sign request: %w", err) 320 } 321 322 ctx, cancel := context.WithCancel(ctx) 323 324 stream, err := rpcapi.GetObject(&c.c, req, client.WithContext(ctx)) 325 if err != nil { 326 cancel() 327 return nil, fmt.Errorf("open stream: %w", err) 328 } 329 330 var r ObjectReader 331 r.cancelCtxStream = cancel 332 r.stream = stream 333 r.client = c 334 335 return &r, nil 336 } 337 338 // PrmObjectHead groups parameters of ObjectHead operation. 339 type PrmObjectHead struct { 340 XHeaders []string 341 342 BearerToken *bearer.Token 343 344 Session *session.Object 345 346 Raw bool 347 348 Local bool 349 350 ContainerID *cid.ID 351 352 ObjectID *oid.ID 353 354 Key *ecdsa.PrivateKey 355 } 356 357 // UseKey specifies private key to sign the requests. 358 // If key is not provided, then Client default key is used. 359 // 360 // Deprecated: Use PrmObjectHead.Key instead. 361 func (prm *PrmObjectHead) UseKey(key ecdsa.PrivateKey) { 362 prm.Key = &key 363 } 364 365 // ResObjectHead groups resulting values of ObjectHead operation. 366 type ResObjectHead struct { 367 statusRes 368 369 // requested object (response doesn't carry the ID) 370 idObj oid.ID 371 372 hdr *v2object.HeaderWithSignature 373 } 374 375 // ReadHeader reads header of the requested object. 376 // Returns false if header is missing in the response (not read). 377 func (x *ResObjectHead) ReadHeader(dst *object.Object) bool { 378 if x.hdr == nil { 379 return false 380 } 381 382 var objv2 v2object.Object 383 384 objv2.SetHeader(x.hdr.GetHeader()) 385 objv2.SetSignature(x.hdr.GetSignature()) 386 387 obj := object.NewFromV2(&objv2) 388 obj.SetID(x.idObj) 389 390 *dst = *obj 391 392 return true 393 } 394 395 func (prm *PrmObjectHead) buildRequest(c *Client) (*v2object.HeadRequest, error) { 396 if prm.ContainerID == nil { 397 return nil, errorMissingContainer 398 } 399 400 if prm.ObjectID == nil { 401 return nil, errorMissingObject 402 } 403 404 if len(prm.XHeaders)%2 != 0 { 405 return nil, errorInvalidXHeaders 406 } 407 408 meta := new(v2session.RequestMetaHeader) 409 writeXHeadersToMeta(prm.XHeaders, meta) 410 411 if prm.BearerToken != nil { 412 v2BearerToken := new(acl.BearerToken) 413 prm.BearerToken.WriteToV2(v2BearerToken) 414 meta.SetBearerToken(v2BearerToken) 415 } 416 417 if prm.Session != nil { 418 v2SessionToken := new(v2session.Token) 419 prm.Session.WriteToV2(v2SessionToken) 420 meta.SetSessionToken(v2SessionToken) 421 } 422 423 if prm.Local { 424 meta.SetTTL(1) 425 } 426 427 addr := new(v2refs.Address) 428 429 cnrV2 := new(v2refs.ContainerID) 430 prm.ContainerID.WriteToV2(cnrV2) 431 addr.SetContainerID(cnrV2) 432 433 objV2 := new(v2refs.ObjectID) 434 prm.ObjectID.WriteToV2(objV2) 435 addr.SetObjectID(objV2) 436 body := new(v2object.HeadRequestBody) 437 body.SetRaw(prm.Raw) 438 body.SetAddress(addr) 439 440 req := new(v2object.HeadRequest) 441 req.SetBody(body) 442 c.prepareRequest(req, meta) 443 444 return req, nil 445 } 446 447 // ObjectHead reads object header through a remote server using FrostFS API protocol. 448 // 449 // Exactly one return value is non-nil. By default, server status is returned in res structure. 450 // Any client's internal or transport errors are returned as `error`, 451 // If PrmInit.DisableFrostFSFailuresResolution has been called, unsuccessful 452 // FrostFS status codes are included in the returned result structure, 453 // otherwise, are also returned as `error`. 454 // 455 // Returns an error if parameters are set incorrectly (see PrmObjectHead docs). 456 // Context is required and must not be nil. It is used for network communication. 457 // 458 // Return errors: 459 // 460 // *object.SplitInfoError (returned on virtual objects with PrmObjectHead.MakeRaw). 461 // *object.ECInfoError (returned on erasure-coded objects with PrmObjectHead.MakeRaw). 462 // 463 // Return statuses: 464 // - global (see Client docs); 465 // - *apistatus.ContainerNotFound; 466 // - *apistatus.ObjectNotFound; 467 // - *apistatus.ObjectAccessDenied; 468 // - *apistatus.ObjectAlreadyRemoved; 469 // - *apistatus.SessionTokenExpired. 470 func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectHead, error) { 471 req, err := prm.buildRequest(c) 472 if err != nil { 473 return nil, err 474 } 475 476 key := c.prm.Key 477 if prm.Key != nil { 478 key = *prm.Key 479 } 480 481 // sign the request 482 483 err = signature.SignServiceMessage(&key, req) 484 if err != nil { 485 return nil, fmt.Errorf("sign request: %w", err) 486 } 487 488 resp, err := rpcapi.HeadObject(&c.c, req, client.WithContext(ctx)) 489 if err != nil { 490 return nil, fmt.Errorf("write request: %w", err) 491 } 492 493 var res ResObjectHead 494 res.st, err = c.processResponse(resp) 495 if err != nil || !apistatus.IsSuccessful(res.st) { 496 return &res, err 497 } 498 499 res.idObj = *prm.ObjectID 500 501 switch v := resp.GetBody().GetHeaderPart().(type) { 502 default: 503 return nil, fmt.Errorf("unexpected header type %T", v) 504 case *v2object.SplitInfo: 505 return nil, object.NewSplitInfoError(object.NewSplitInfoFromV2(v)) 506 case *v2object.ECInfo: 507 return nil, object.NewECInfoError(object.NewECInfoFromV2(v)) 508 case *v2object.HeaderWithSignature: 509 res.hdr = v 510 } 511 512 return &res, nil 513 } 514 515 // PrmObjectRange groups parameters of ObjectRange operation. 516 type PrmObjectRange struct { 517 XHeaders []string 518 519 BearerToken *bearer.Token 520 521 Session *session.Object 522 523 Raw bool 524 525 Local bool 526 527 ContainerID *cid.ID 528 529 ObjectID *oid.ID 530 531 Key *ecdsa.PrivateKey 532 533 Offset uint64 534 535 Length uint64 536 } 537 538 func (prm *PrmObjectRange) buildRequest(c *Client) (*v2object.GetRangeRequest, error) { 539 if prm.Length == 0 { 540 return nil, errorZeroRangeLength 541 } 542 543 if prm.ContainerID == nil { 544 return nil, errorMissingContainer 545 } 546 547 if prm.ObjectID == nil { 548 return nil, errorMissingObject 549 } 550 551 if len(prm.XHeaders)%2 != 0 { 552 return nil, errorInvalidXHeaders 553 } 554 555 meta := new(v2session.RequestMetaHeader) 556 writeXHeadersToMeta(prm.XHeaders, meta) 557 558 if prm.BearerToken != nil { 559 v2BearerToken := new(acl.BearerToken) 560 prm.BearerToken.WriteToV2(v2BearerToken) 561 meta.SetBearerToken(v2BearerToken) 562 } 563 564 if prm.Session != nil { 565 v2SessionToken := new(v2session.Token) 566 prm.Session.WriteToV2(v2SessionToken) 567 meta.SetSessionToken(v2SessionToken) 568 } 569 570 if prm.Local { 571 meta.SetTTL(1) 572 } 573 574 addr := new(v2refs.Address) 575 576 cnrV2 := new(v2refs.ContainerID) 577 prm.ContainerID.WriteToV2(cnrV2) 578 addr.SetContainerID(cnrV2) 579 580 objV2 := new(v2refs.ObjectID) 581 prm.ObjectID.WriteToV2(objV2) 582 addr.SetObjectID(objV2) 583 584 rng := new(v2object.Range) 585 rng.SetLength(prm.Length) 586 rng.SetOffset(prm.Offset) 587 588 body := new(v2object.GetRangeRequestBody) 589 body.SetRaw(prm.Raw) 590 body.SetAddress(addr) 591 body.SetRange(rng) 592 593 req := new(v2object.GetRangeRequest) 594 req.SetBody(body) 595 c.prepareRequest(req, meta) 596 597 return req, nil 598 } 599 600 // UseKey specifies private key to sign the requests. 601 // If key is not provided, then Client default key is used. 602 // 603 // Deprecated: Use PrmObjectRange.Key instead. 604 func (prm *PrmObjectRange) UseKey(key ecdsa.PrivateKey) { 605 prm.Key = &key 606 } 607 608 // ResObjectRange groups the final result values of ObjectRange operation. 609 type ResObjectRange struct { 610 statusRes 611 } 612 613 // ObjectRangeReader is designed to read payload range of one object 614 // from FrostFS system. 615 // 616 // Must be initialized using Client.ObjectRangeInit, any other 617 // usage is unsafe. 618 type ObjectRangeReader struct { 619 cancelCtxStream context.CancelFunc 620 621 client *Client 622 623 res ResObjectRange 624 err error 625 626 stream interface { 627 Read(resp *v2object.GetRangeResponse) error 628 } 629 630 tailPayload []byte 631 632 remainingPayloadLen int 633 } 634 635 func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) { 636 var read int 637 638 // read remaining tail 639 read = copy(buf, x.tailPayload) 640 641 x.tailPayload = x.tailPayload[read:] 642 643 if len(buf) == read { 644 return read, true 645 } 646 647 var partChunk *v2object.GetRangePartChunk 648 var chunk []byte 649 var lastRead int 650 651 for { 652 var resp v2object.GetRangeResponse 653 x.err = x.stream.Read(&resp) 654 if x.err != nil { 655 return read, false 656 } 657 658 x.res.st, x.err = x.client.processResponse(&resp) 659 if x.err != nil || !apistatus.IsSuccessful(x.res.st) { 660 return read, false 661 } 662 663 // get chunk message 664 switch v := resp.GetBody().GetRangePart().(type) { 665 default: 666 x.err = fmt.Errorf("unexpected message received: %T", v) 667 return read, false 668 case *v2object.SplitInfo: 669 x.err = object.NewSplitInfoError(object.NewSplitInfoFromV2(v)) 670 return read, false 671 case *v2object.ECInfo: 672 x.err = object.NewECInfoError(object.NewECInfoFromV2(v)) 673 return read, false 674 case *v2object.GetRangePartChunk: 675 partChunk = v 676 } 677 678 chunk = partChunk.GetChunk() 679 if len(chunk) == 0 { 680 // just skip empty chunks since they are not prohibited by protocol 681 continue 682 } 683 684 lastRead = copy(buf[read:], chunk) 685 686 read += lastRead 687 688 if read == len(buf) { 689 // save the tail 690 x.tailPayload = append(x.tailPayload, chunk[lastRead:]...) 691 692 return read, true 693 } 694 } 695 } 696 697 // ReadChunk reads another chunk of the object payload range. 698 // Works similar to io.Reader.Read but returns success flag instead of error. 699 // 700 // Failure reason can be received via Close. 701 func (x *ObjectRangeReader) ReadChunk(buf []byte) (int, bool) { 702 return x.readChunk(buf) 703 } 704 705 func (x *ObjectRangeReader) close(ignoreEOF bool) (*ResObjectRange, error) { 706 defer x.cancelCtxStream() 707 708 if x.err != nil { 709 if !errors.Is(x.err, io.EOF) { 710 return nil, x.err 711 } else if !ignoreEOF { 712 if x.remainingPayloadLen > 0 { 713 return nil, io.ErrUnexpectedEOF 714 } 715 716 return nil, io.EOF 717 } 718 } 719 720 return &x.res, nil 721 } 722 723 // Close ends reading the payload range and returns the result of the operation 724 // along with the final results. Must be called after using the ObjectRangeReader. 725 // 726 // Exactly one return value is non-nil. By default, server status is returned in res structure. 727 // Any client's internal or transport errors are returned as Go built-in error. 728 // If Client is tuned to resolve FrostFS API statuses, then FrostFS failures 729 // codes are returned as error. 730 // 731 // Return errors: 732 // 733 // *object.SplitInfoError (returned on virtual objects with PrmObjectRange.MakeRaw). 734 // *object.ECInfoError (returned on erasure-coded objects with PrmObjectRange.MakeRaw). 735 // 736 // Return statuses: 737 // - global (see Client docs); 738 // - *apistatus.ContainerNotFound; 739 // - *apistatus.ObjectNotFound; 740 // - *apistatus.ObjectAccessDenied; 741 // - *apistatus.ObjectAlreadyRemoved; 742 // - *apistatus.ObjectOutOfRange; 743 // - *apistatus.SessionTokenExpired. 744 func (x *ObjectRangeReader) Close() (*ResObjectRange, error) { 745 return x.close(true) 746 } 747 748 // Read implements io.Reader of the object payload. 749 func (x *ObjectRangeReader) Read(p []byte) (int, error) { 750 n, ok := x.readChunk(p) 751 752 x.remainingPayloadLen -= n 753 754 if !ok { 755 res, err := x.close(false) 756 if err != nil { 757 return n, err 758 } 759 760 return n, apistatus.ErrFromStatus(res.Status()) 761 } 762 763 if x.remainingPayloadLen < 0 { 764 return n, errors.New("payload range size overflow") 765 } 766 767 return n, nil 768 } 769 770 // ObjectRangeInit initiates reading an object's payload range through a remote 771 // server using FrostFS API protocol. 772 // 773 // The call only opens the transmission channel, explicit fetching is done using the ObjectRangeReader. 774 // Exactly one return value is non-nil. Resulting reader must be finally closed. 775 // 776 // Returns an error if parameters are set incorrectly (see PrmObjectRange docs). 777 // Context is required and must not be nil. It is used for network communication. 778 func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*ObjectRangeReader, error) { 779 req, err := prm.buildRequest(c) 780 if err != nil { 781 return nil, err 782 } 783 784 key := prm.Key 785 if key == nil { 786 key = &c.prm.Key 787 } 788 789 err = signature.SignServiceMessage(key, req) 790 if err != nil { 791 return nil, fmt.Errorf("sign request: %w", err) 792 } 793 794 ctx, cancel := context.WithCancel(ctx) 795 796 stream, err := rpcapi.GetObjectRange(&c.c, req, client.WithContext(ctx)) 797 if err != nil { 798 cancel() 799 return nil, fmt.Errorf("open stream: %w", err) 800 } 801 802 var r ObjectRangeReader 803 r.remainingPayloadLen = int(prm.Length) 804 r.cancelCtxStream = cancel 805 r.stream = stream 806 r.client = c 807 808 return &r, nil 809 }