private Callback<String> routerPutBlobCallback(BlobInfo blobInfo) {
      return buildCallback(frontendMetrics.putRouterPutBlobMetrics, blobId -> {
        restResponseChannel.setHeader(RestUtils.Headers.BLOB_SIZE, restRequest.getBlobBytesReceived());
        // TODO [S3] Make changes to sign ETags. Currently they are sent as shown below.
        //  ETag: {"chunks":[{"blob":"AAYQAQBlAAgAAQAAAAAAAAAAw6UGCoNgS8KGgV-SGXAMdQ","size":4194304},
        //  {"blob":"AAYQAQBlAAgAAQAAAAAAAAAA6Jaga4MiRFiBXi_jjaiQhg","size":1052262}]}
        PutBlobMetaInfo putBlobMetaInfo = PutBlobMetaInfo.deserialize(blobId);
        S3MultipartETag etag = new S3MultipartETag(putBlobMetaInfo.getOrderedChunkIdSizeList());
        String eTagStr = S3MultipartETag.serialize(etag);
        restResponseChannel.setHeader(RestUtils.Headers.LOCATION, eTagStr);
        securityService.processResponse(restRequest, restResponseChannel, blobInfo, securityProcessResponseCallback());
      }, uri, logger, finalCallback);
    }
After {@link Router#putBlob} finishes, call {@link IdConverter#convert} to store the mapping between blobName and
blobId.
@param blobInfo the {@link BlobInfo} to use for security checks.
@return a {@link Callback} to be used with {@link Router#putBlob}.
ambry-frontend/src/main/java/com/github/ambry/frontend/s3/S3MultipartUploadPartHandler.java