private async downloadPageBlob()

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