@Override
  public Future<String> putBlob(RestRequest restRequest, BlobProperties blobProperties, byte[] userMetadata,
      ReadableStreamChannel channel, PutBlobOptions options, Callback<String> callback,
      QuotaChargeCallback quotaChargeCallback) {
    if (blobProperties == null || channel == null || options == null) {
      throw new IllegalArgumentException("blobProperties, channel, or options must not be null");
    }
    if (userMetadata == null) {
      userMetadata = new byte[0];
    }
    currentOperationsCount.incrementAndGet();
    if (blobProperties.isEncrypted()) {
      routerMetrics.putEncryptedBlobOperationRate.mark();
    } else {
      routerMetrics.putBlobOperationRate.mark();
    }
    routerMetrics.operationQueuingRate.mark();
    FutureResult<String> futureResult = new FutureResult<>();
    Callback<String> wrappedCallback =
        restRequest != null ? createIdConverterCallbackForPut(restRequest, blobProperties, futureResult, callback)
            : callback;
    if (isOpen.get()) {
      getOperationController().putBlob(blobProperties, userMetadata, channel, options, futureResult, wrappedCallback,
          quotaChargeCallback);
    } else {
      RouterException routerException =
          new RouterException("Cannot accept operation because Router is closed", RouterErrorCode.RouterClosed);
      routerMetrics.operationDequeuingRate.mark();
      routerMetrics.onPutBlobError(routerException, blobProperties.isEncrypted(), false);
      completeOperation(futureResult, wrappedCallback, null, routerException);
    }
    return futureResult;
  }
Requests for a new blob to be put asynchronously and invokes the {@link Callback} when the request completes.

@param restRequest    The {@link RestRequest} to put the blob.
@param blobProperties The properties of the blob. Note that the size specified in the properties is ignored. The
                      channel is consumed fully, and the size of the blob is the number of bytes read from it.
@param userMetadata   Optional user metadata about the blob. This can be null.
@param channel        The {@link ReadableStreamChannel} that contains the content of the blob.
@param options        The {@link PutBlobOptions} associated with the request. This cannot be null.
@param callback       The {@link Callback} which will be invoked on the completion of the request .
@return A future that would contain the BlobId eventually.
ambry-router/src/main/java/com/github/ambry/router/NonBlockingRouter.java