private async downloadBlockBlobOrAppendBlob()

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;
  }