in src/blob/handlers/BlobHandler.ts [1138:1259]
private async downloadPageBlob(
options: Models.BlobDownloadOptionalParams,
context: Context,
blob: BlobModel
): Promise<Models.BlobDownloadResponse> {
// Deserializer doesn't handle range header currently, manually parse range headers here
const rangesParts = deserializePageBlobRangeHeader(
context.request!.getHeader("range"),
context.request!.getHeader("x-ms-range"),
false
);
const rangeStart = rangesParts[0];
let rangeEnd = rangesParts[1];
// Start Range is bigger than blob length
if (rangeStart > blob.properties.contentLength!) {
throw StorageErrorFactory.getInvalidPageRange2(context.contextId!,`bytes */${blob.properties.contentLength}`);
}
// Will automatically shift request with longer data end than blob size to blob size
if (rangeEnd + 1 >= blob.properties.contentLength!) {
// report error is blob size is 0, and rangeEnd is specified but not 0
if (blob.properties.contentLength == 0 && rangeEnd !== 0 && rangeEnd !== Infinity) {
throw StorageErrorFactory.getInvalidPageRange2(context.contextId!,`bytes */${blob.properties.contentLength}`);
}
else {
rangeEnd = blob.properties.contentLength! - 1;
}
}
const contentLength = rangeEnd - rangeStart + 1;
const partialRead = contentLength !== blob.properties.contentLength!;
this.logger.info(
// tslint:disable-next-line:max-line-length
`BlobHandler:downloadPageBlob() NormalizedDownloadRange=bytes=${rangeStart}-${rangeEnd} RequiredContentLength=${contentLength}`,
context.contextId
);
// if (contentLength <= 0) {
// return {
// statusCode: 200,
// body: undefined,
// metadata: blob.metadata,
// eTag: blob.properties.etag,
// requestId: context.contextID,
// date: context.startTime!,
// version: BLOB_API_VERSION,
// ...blob.properties,
// contentLength,
// contentMD5: undefined
// };
// }
blob.pageRangesInOrder = blob.pageRangesInOrder || [];
const ranges =
contentLength <= 0
? []
: this.rangesManager.fillZeroRanges(blob.pageRangesInOrder, {
start: rangeStart,
end: rangeEnd
});
const bodyGetter = async () => {
return this.extentStore.readExtents(
ranges.map((value) => value.persistency),
0,
contentLength,
context.contextId
);
};
let body: NodeJS.ReadableStream | undefined = await bodyGetter();
let contentMD5: Uint8Array | undefined;
if (!partialRead) {
contentMD5 = blob.properties.contentMD5;
}
if (
contentLength <= 4 * 1024 * 1024 &&
contentMD5 === undefined &&
body !== undefined
) {
contentMD5 = await getMD5FromStream(body);
body = await bodyGetter();
}
let contentRange: string | undefined;
if (
context.request!.getHeader("range") ||
context.request!.getHeader("x-ms-range")
) {
contentRange = `bytes ${rangeStart}-${rangeEnd}/${blob.properties
.contentLength!}`;
}
const response: Models.BlobDownloadResponse = {
statusCode:
rangesParts[1] === Infinity && rangesParts[0] === 0 ? 200 : 206,
body,
metadata: blob.metadata,
eTag: blob.properties.etag,
requestId: context.contextId,
date: context.startTime!,
version: BLOB_API_VERSION,
...blob.properties,
cacheControl: context.request!.getQuery("rscc") ?? blob.properties.cacheControl,
contentDisposition: context.request!.getQuery("rscd") ?? blob.properties.contentDisposition,
contentEncoding: context.request!.getQuery("rsce") ?? blob.properties.contentEncoding,
contentLanguage: context.request!.getQuery("rscl") ?? blob.properties.contentLanguage,
contentType: context.request!.getQuery("rsct") ?? blob.properties.contentType,
contentLength,
contentRange,
contentMD5: contentRange ? (context.request!.getHeader("x-ms-range-get-content-md5") ? contentMD5: undefined) : contentMD5,
blobContentMD5: blob.properties.contentMD5,
tagCount: getBlobTagsCount(blob.blobTags),
isServerEncrypted: true,
creationTime: blob.properties.creationTime,
clientRequestId: options.requestId
};
return response;
}