git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/client/object_put_raw.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 rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" 13 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" 14 v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" 15 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" 16 apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" 17 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" 18 ) 19 20 func (c *Client) objectPutInitRaw(ctx context.Context, prm PrmObjectPutInit) (*objectWriterRaw, error) { 21 if len(prm.XHeaders)%2 != 0 { 22 return nil, errorInvalidXHeaders 23 } 24 25 var w objectWriterRaw 26 stream, err := rpcapi.PutObject(&c.c, &w.respV2, client.WithContext(ctx)) 27 if err != nil { 28 return nil, fmt.Errorf("open stream: %w", err) 29 } 30 31 w.key = &c.prm.Key 32 if prm.Key != nil { 33 w.key = prm.Key 34 } 35 w.client = c 36 w.stream = stream 37 w.partInit.SetCopiesNumber(prm.CopiesNumber) 38 w.req.SetBody(new(v2object.PutRequestBody)) 39 if prm.MaxChunkLength > 0 { 40 w.maxChunkLen = prm.MaxChunkLength 41 } else { 42 w.maxChunkLen = defaultGRPCPayloadChunkLen 43 } 44 45 meta := new(v2session.RequestMetaHeader) 46 writeXHeadersToMeta(prm.XHeaders, meta) 47 48 if prm.BearerToken != nil { 49 v2BearerToken := new(acl.BearerToken) 50 prm.BearerToken.WriteToV2(v2BearerToken) 51 meta.SetBearerToken(v2BearerToken) 52 } 53 54 if prm.Session != nil { 55 v2SessionToken := new(v2session.Token) 56 prm.Session.WriteToV2(v2SessionToken) 57 meta.SetSessionToken(v2SessionToken) 58 } 59 60 if prm.Local { 61 meta.SetTTL(1) 62 } 63 64 c.prepareRequest(&w.req, meta) 65 return &w, nil 66 } 67 68 type objectWriterRaw struct { 69 client *Client 70 stream interface { 71 Write(*v2object.PutRequest) error 72 Close() error 73 } 74 75 key *ecdsa.PrivateKey 76 res ResObjectPut 77 err error 78 chunkCalled bool 79 respV2 v2object.PutResponse 80 req v2object.PutRequest 81 partInit v2object.PutObjectPartInit 82 partChunk v2object.PutObjectPartChunk 83 maxChunkLen int 84 } 85 86 func (x *objectWriterRaw) WriteHeader(_ context.Context, hdr object.Object) bool { 87 v2Hdr := hdr.ToV2() 88 89 x.partInit.SetObjectID(v2Hdr.GetObjectID()) 90 x.partInit.SetHeader(v2Hdr.GetHeader()) 91 x.partInit.SetSignature(v2Hdr.GetSignature()) 92 93 x.req.GetBody().SetObjectPart(&x.partInit) 94 x.req.SetVerificationHeader(nil) 95 96 x.err = signature.SignServiceMessage(x.key, &x.req) 97 if x.err != nil { 98 x.err = fmt.Errorf("sign message: %w", x.err) 99 return false 100 } 101 102 x.err = x.stream.Write(&x.req) 103 return x.err == nil 104 } 105 106 func (x *objectWriterRaw) WritePayloadChunk(_ context.Context, chunk []byte) bool { 107 if !x.chunkCalled { 108 x.chunkCalled = true 109 x.req.GetBody().SetObjectPart(&x.partChunk) 110 } 111 112 for ln := len(chunk); ln > 0; ln = len(chunk) { 113 if ln > x.maxChunkLen { 114 ln = x.maxChunkLen 115 } 116 117 // we deal with size limit overflow above, but there is another case: 118 // what if method is called with "small" chunk many times? We write 119 // a message to the stream on each call. Alternatively, we could use buffering. 120 // In most cases, the chunk length does not vary between calls. Given this 121 // assumption, as well as the length of the payload from the header, it is 122 // possible to buffer the data of intermediate chunks, and send a message when 123 // the allocated buffer is filled, or when the last chunk is received. 124 // It is mentally assumed that allocating and filling the buffer is better than 125 // synchronous sending, but this needs to be tested. 126 x.partChunk.SetChunk(chunk[:ln]) 127 x.req.SetVerificationHeader(nil) 128 129 x.err = signature.SignServiceMessage(x.key, &x.req) 130 if x.err != nil { 131 x.err = fmt.Errorf("sign message: %w", x.err) 132 return false 133 } 134 135 x.err = x.stream.Write(&x.req) 136 if x.err != nil { 137 return false 138 } 139 140 chunk = chunk[ln:] 141 } 142 143 return true 144 } 145 146 func (x *objectWriterRaw) Close(_ context.Context) (*ResObjectPut, error) { 147 // Ignore io.EOF error, because it is expected error for client-side 148 // stream termination by the server. E.g. when stream contains invalid 149 // message. Server returns an error in response message (in status). 150 if x.err != nil && !errors.Is(x.err, io.EOF) { 151 return nil, x.err 152 } 153 154 if x.err = x.stream.Close(); x.err != nil { 155 return nil, x.err 156 } 157 158 x.res.st, x.err = x.client.processResponse(&x.respV2) 159 if x.err != nil || !apistatus.IsSuccessful(x.res.st) { 160 return &x.res, x.err 161 } 162 163 const fieldID = "ID" 164 165 idV2 := x.respV2.GetBody().GetObjectID() 166 if idV2 == nil { 167 return nil, newErrMissingResponseField(fieldID) 168 } 169 170 x.err = x.res.obj.ReadFromV2(*idV2) 171 if x.err != nil { 172 x.err = newErrInvalidResponseField(fieldID, x.err) 173 } 174 x.res.epoch = x.respV2.GetMetaHeader().GetEpoch() 175 176 return &x.res, nil 177 }