214 const std::string c_lineEnding =
"\r\n";
215 const std::string c_contentTypePrefix =
"multipart/mixed; boundary=";
217 std::string boundary =
"batch_" + Azure::Core::Uuid::CreateUuid().GetUuidString();
219 enum class RequestType
225 std::vector<RequestType> requestTypes;
227 std::string requestBody;
229 auto getBatchBoundary = [&c_lineEnding, &boundary, subRequestCounter = 0]()
mutable {
231 ret +=
"--" + boundary + c_lineEnding;
232 ret +=
"Content-Type: application/http" + c_lineEnding +
"Content-Transfer-Encoding: binary"
233 + c_lineEnding +
"Content-ID: " + std::to_string(subRequestCounter++) + c_lineEnding
237 for (
const auto& subrequest : batch.m_deleteBlobSubRequests)
239 requestTypes.emplace_back(RequestType::DeleteBlob);
241 requestBody += getBatchBoundary();
243 auto blobUrl = m_serviceUrl;
244 blobUrl.AppendPath(subrequest.ContainerName);
245 blobUrl.AppendPath(subrequest.BlobName);
246 BlobRestClient::Blob::DeleteBlobOptions protocolLayerOptions;
247 protocolLayerOptions.DeleteSnapshots = subrequest.Options.DeleteSnapshots;
248 protocolLayerOptions.IfModifiedSince = subrequest.Options.AccessConditions.IfModifiedSince;
249 protocolLayerOptions.IfUnmodifiedSince
250 = subrequest.Options.AccessConditions.IfUnmodifiedSince;
251 protocolLayerOptions.IfMatch = subrequest.Options.AccessConditions.IfMatch;
252 protocolLayerOptions.IfNoneMatch = subrequest.Options.AccessConditions.IfNoneMatch;
253 protocolLayerOptions.LeaseId = subrequest.Options.AccessConditions.LeaseId;
254 auto message = BlobRestClient::Blob::DeleteCreateMessage(blobUrl, protocolLayerOptions);
255 message.RemoveHeader(Details::c_HttpHeaderXMsVersion);
256 m_subRequestPipeline->Send(options.Context, message);
257 requestBody += message.GetHTTPMessagePreBody();
259 for (
const auto& subrequest : batch.m_setBlobAccessTierSubRequests)
261 requestTypes.emplace_back(RequestType::SetBlobAccessTier);
263 requestBody += getBatchBoundary();
265 auto blobUrl = m_serviceUrl;
266 blobUrl.AppendPath(subrequest.ContainerName);
267 blobUrl.AppendPath(subrequest.BlobName);
268 BlobRestClient::Blob::SetBlobAccessTierOptions protocolLayerOptions;
269 protocolLayerOptions.Tier = subrequest.Tier;
270 protocolLayerOptions.RehydratePriority = subrequest.Options.RehydratePriority;
272 = BlobRestClient::Blob::SetAccessTierCreateMessage(blobUrl, protocolLayerOptions);
273 message.RemoveHeader(Details::c_HttpHeaderXMsVersion);
274 m_subRequestPipeline->Send(options.Context, message);
275 requestBody += message.GetHTTPMessagePreBody();
277 requestBody +=
"--" + boundary +
"--" + c_lineEnding;
280 BlobRestClient::BlobBatch::SubmitBlobBatchOptions protocolLayerOptions;
281 protocolLayerOptions.ContentType = c_contentTypePrefix + boundary;
283 Azure::Core::Http::MemoryBodyStream requestBodyStream(
284 reinterpret_cast<const uint8_t*
>(requestBody.data()), requestBody.length());
286 auto rawResponse = BlobRestClient::BlobBatch::SubmitBatch(
287 options.Context, *m_pipeline, m_serviceUrl, &requestBodyStream, protocolLayerOptions);
289 if (rawResponse->ContentType.substr(0, c_contentTypePrefix.length()) == c_contentTypePrefix)
291 boundary = rawResponse->ContentType.substr(c_contentTypePrefix.length());
295 throw std::runtime_error(
"failed to parse Content-Type response header");
298 SubmitBlobBatchResult batchResult;
300 const std::vector<uint8_t>& responseBody = rawResponse.GetRawResponse().GetBody();
302 const char*
const startPos =
reinterpret_cast<const char*
>(responseBody.data());
303 const char* currPos = startPos;
304 const char*
const endPos = currPos + responseBody.size();
306 auto parseLookAhead = [&currPos, endPos](
const std::string& expect) ->
bool {
308 for (std::size_t i = 0; i < expect.length(); ++i)
310 if (currPos + i < endPos && currPos[i] == expect[i])
319 auto parseConsume = [&currPos, startPos, &parseLookAhead](
const std::string& expect) ->
void {
321 if (parseLookAhead(expect))
323 currPos += expect.length();
327 throw std::runtime_error(
328 "failed to parse response body at " + std::to_string(currPos - startPos));
332 auto parseFindNext = [&currPos, endPos](
const std::string& expect) ->
const char* {
334 return std::search(currPos, endPos, expect.begin(), expect.end());
337 auto parseFindNextAfter = [endPos, &parseFindNext](
const std::string& expect) ->
const char* {
339 return std::min(endPos, parseFindNext(expect) + expect.length());
342 auto parseGetUntilAfter
343 = [&currPos, endPos, &parseFindNext](
const std::string& expect) -> std::string {
345 auto ePos = parseFindNext(expect);
346 std::string ret(currPos, ePos);
347 currPos = std::min(endPos, ePos + expect.length());
351 int subRequestCounter = 0;
354 parseConsume(
"--" + boundary);
356 if (parseLookAhead(
"--"))
361 if (currPos == endPos)
366 currPos = parseFindNextAfter(c_lineEnding + c_lineEnding);
367 auto boundaryPos = parseFindNext(
"--" + boundary);
370 parseConsume(
"HTTP/");
371 int32_t httpMajorVersion = std::stoi(parseGetUntilAfter(
"."));
372 int32_t httpMinorVersion = std::stoi(parseGetUntilAfter(
" "));
373 int32_t httpStatusCode = std::stoi(parseGetUntilAfter(
" "));
374 std::string httpReasonPhrase = parseGetUntilAfter(c_lineEnding);
376 auto rawSubresponse = std::make_unique<Azure::Core::Http::RawResponse>(
379 static_cast<Azure::Core::Http::HttpStatusCode
>(httpStatusCode),
382 while (currPos < boundaryPos)
384 if (parseLookAhead(c_lineEnding))
389 std::string headerName = parseGetUntilAfter(
": ");
390 std::string headerValue = parseGetUntilAfter(c_lineEnding);
391 rawSubresponse->AddHeader(headerName, headerValue);
394 parseConsume(c_lineEnding);
396 rawSubresponse->SetBody(std::vector<uint8_t>(currPos, boundaryPos));
397 currPos = boundaryPos;
399 RequestType requestType = requestTypes[subRequestCounter++];
400 if (requestType == RequestType::DeleteBlob)
404 batchResult.DeleteBlobResults.emplace_back(BlobRestClient::Blob::DeleteCreateResponse(
405 options.Context, std::move(rawSubresponse)));
407 catch (StorageError& e)
409 batchResult.DeleteBlobResults.emplace_back(Azure::Core::Response<DeleteBlobResult>(
410 DeleteBlobResult{}, std::move(e.RawResponse)));
413 else if (requestType == RequestType::SetBlobAccessTier)
417 batchResult.SetBlobAccessTierResults.emplace_back(
418 BlobRestClient::Blob::SetAccessTierCreateResponse(
419 options.Context, std::move(rawSubresponse)));
421 catch (StorageError& e)
423 batchResult.SetBlobAccessTierResults.emplace_back(
424 Azure::Core::Response<SetBlobAccessTierResult>(
425 SetBlobAccessTierResult{}, std::move(e.RawResponse)));
431 return Azure::Core::Response<SubmitBlobBatchResult>(
432 std::move(batchResult), rawResponse.ExtractRawResponse());