in src/blob/handlers/BlobHandler.ts [998:1126]
private async downloadBlockBlobOrAppendBlob(
options: Models.BlobDownloadOptionalParams,
context: Context,
blob: BlobModel
): Promise<Models.BlobDownloadResponse> {
if (blob.isCommitted === false) {
throw StorageErrorFactory.getBlobNotFound(context.contextId!);
}
let rangesParts = undefined;
try {
// Deserializer doesn't handle range header currently, manually parse range headers here
rangesParts = deserializeRangeHeader(
context.request!.getHeader("range"),
context.request!.getHeader("x-ms-range")
);
} catch (err) {
this.logger.info(
`BlobHandler:downloadBlockBlobOrAppendBlob() Ignoring range request due to invalid content range: ${err.message}`,
context.contextId
);
// Ignoring range request as per RFC 9110, section 14.2
}
const rangeStart = rangesParts ? rangesParts[0] : 0;
let rangeEnd = rangesParts ? rangesParts[1] : Infinity;
// 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:downloadBlockBlobOrAppendBlob() NormalizedDownloadRange=bytes=${rangeStart}-${rangeEnd} RequiredContentLength=${contentLength}`,
context.contextId
);
let bodyGetter: () => Promise<NodeJS.ReadableStream | undefined>;
const blocks = blob.committedBlocksInOrder;
if (blocks === undefined || blocks.length === 0) {
bodyGetter = async () => {
if (blob.persistency === undefined) {
return this.extentStore.readExtent(undefined, context.contextId);
}
return this.extentStore.readExtent(
{
id: blob.persistency.id,
offset: blob.persistency.offset + rangeStart,
count: Math.min(blob.persistency.count, contentLength)
},
context.contextId
);
};
} else {
bodyGetter = async () => {
return this.extentStore.readExtents(
blocks.map((block) => block.persistency),
rangeStart,
rangeEnd + 1 - rangeStart,
context.contextId
);
};
}
let contentRange: string | undefined;
if (rangesParts) {
contentRange = `bytes ${rangeStart}-${rangeEnd}/${blob.properties
.contentLength!}`;
}
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();
}
const response: Models.BlobDownloadResponse = {
statusCode: contentRange ? 206 : 200,
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,
blobContentMD5: blob.properties.contentMD5,
acceptRanges: "bytes",
contentLength,
contentRange,
contentMD5: contentRange ? (context.request!.getHeader("x-ms-range-get-content-md5") ? contentMD5: undefined) : contentMD5,
tagCount: getBlobTagsCount(blob.blobTags),
isServerEncrypted: true,
clientRequestId: options.requestId,
creationTime: blob.properties.creationTime,
blobCommittedBlockCount:
blob.properties.blobType === Models.BlobType.AppendBlob
? (blob.committedBlocksInOrder || []).length
: undefined,
};
return response;
}