public async appendBlock()

in src/blob/handlers/AppendBlobHandler.ts [101:218]


  public async appendBlock(
    body: NodeJS.ReadableStream,
    contentLength: number,
    options: Models.AppendBlobAppendBlockOptionalParams,
    context: Context
  ): Promise<Models.AppendBlobAppendBlockResponse> {
    const blobCtx = new BlobStorageContext(context);
    const accountName = blobCtx.account!;
    const containerName = blobCtx.container!;
    const blobName = blobCtx.blob!;
    const date = blobCtx.startTime!;

    if (contentLength > MAX_APPEND_BLOB_BLOCK_SIZE) {
      throw StorageErrorFactory.getRequestEntityTooLarge(blobCtx.contextId);
    }

    if (contentLength === 0) {
      throw StorageErrorFactory.getInvalidHeaderValue(blobCtx.contextId, {
        HeaderName: HeaderConstants.CONTENT_LENGTH,
        HeaderValue: "0"
      });
    }

    // TODO: Optimize with cache
    const blob = await this.metadataStore.downloadBlob(
      blobCtx,
      accountName,
      containerName,
      blobName,
      undefined
    );

    if (blob.properties.blobType !== Models.BlobType.AppendBlob) {
      throw StorageErrorFactory.getBlobInvalidBlobType(blobCtx.contextId);
    }

    const committedBlockCount = (blob.committedBlocksInOrder || []).length;
    if (committedBlockCount >= MAX_APPEND_BLOB_BLOCK_COUNT) {
      throw StorageErrorFactory.getBlockCountExceedsLimit(blobCtx.contextId);
    }

    // Persist content
    const extent = await this.extentStore.appendExtent(body, blobCtx.contextId);
    if (extent.count !== contentLength) {
      throw StorageErrorFactory.getInvalidOperation(
        blobCtx.contextId,
        `The size of the request body ${extent.count} mismatches the content-length ${contentLength}.`
      );
    }

    // MD5
    const contentMD5 = blobCtx.request!.getHeader(HeaderConstants.CONTENT_MD5);
    let contentMD5Buffer;
    let contentMD5String;

    if (contentMD5 !== undefined) {
      contentMD5Buffer =
        typeof contentMD5 === "string"
          ? Buffer.from(contentMD5, "base64")
          : contentMD5;
      contentMD5String =
        typeof contentMD5 === "string"
          ? contentMD5
          : contentMD5Buffer.toString("base64");

      const stream = await this.extentStore.readExtent(
        extent,
        blobCtx.contextId
      );
      const calculatedContentMD5Buffer = await getMD5FromStream(stream);
      const calculatedContentMD5String = Buffer.from(
        calculatedContentMD5Buffer
      ).toString("base64");

      if (contentMD5String !== calculatedContentMD5String) {
        throw StorageErrorFactory.getMd5Mismatch(
          context.contextId,
          contentMD5String,
          calculatedContentMD5String
        );
      }
    }

    const originOffset = blob.properties.contentLength;

    const properties = await this.metadataStore.appendBlock(
      blobCtx,
      {
        accountName,
        containerName,
        blobName,
        isCommitted: true,
        name: "", // No block ID for append block
        size: extent.count,
        persistency: extent
      },
      options.leaseAccessConditions,
      options.modifiedAccessConditions,
      options.appendPositionAccessConditions
    );

    const response: Models.AppendBlobAppendBlockResponse = {
      statusCode: 201,
      requestId: context.contextId,
      eTag: properties.etag,
      lastModified: properties.lastModified,
      contentMD5: contentMD5Buffer,
      xMsContentCrc64: undefined,
      clientRequestId: options.requestId,
      version: BLOB_API_VERSION,
      date,
      blobAppendOffset: `${originOffset}`,
      blobCommittedBlockCount: committedBlockCount + 1,
      isServerEncrypted: true
    };

    return response;
  }