4. 文件上传

在NOS中用户的基本操作单元是对象,亦可以理解为文件,NOS CPP SDK提供了丰富的上传接口,可以通过以下的方式上传文件:

  • 流式上传(包含文件上传)
  • 分块上传

流式上传、单块上传最大为100M;分块上传除最后一块外,分块不能小于16k,每一个分块不能大于100M,最多能上传10000个分块,即分块上传的文件最大支持1T。

4.1. 流式上传(包含文件上传)

您可以使用S3Client.PutObject上传一个一个Stream中的内容,具体实现如下:

//input_data为想要上传的内容的Stream,可以是Aws::StringStream,Aws::FStream,通过使用Aws::MakeShared<<#typename T#>>(<#const char *allocationTag#>, <#ArgTypes &&args...#>)来构建
void putObjectFromContent(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName,const std::shared_ptr<Aws::IOStream>& input_data){
    Aws::S3::Model::PutObjectRequest object_request;
    object_request.WithBucket(bucketName).WithKey(objectName);

    auto input_data = Aws::MakeShared<Aws::StringStream>("PutObjectInputStream",content);

    object_request.SetBody(input_data);

    auto put_object_outcome = s3Client.PutObject(object_request);

    if (put_object_outcome.IsSuccess())
    {
        std::cout << "Done!" << std::endl;
    }
    else
    {
        std::cout << "PutObject error: " <<
        put_object_outcome.GetError().GetExceptionName() << " " <<
        put_object_outcome.GetError().GetMessage() << std::endl;
    }
}
//上传本地文件input_data的构造方式
auto input_file = Aws::MakeShared<Aws::FStream>("test",file_name.c_str(), std::ios_base::in | std::ios_base::binary);
//上传内存对象
auto input_content = Aws::MakeShared<Aws::StringStream>("test","hello world");

Attention

  1. 上传的流内容不超过100M

4.2. 分块上传

除了通过putObject接口上传文件到NOS之外,NOS还提供了另外一种上传模式-分块上传,用户可以在如下应用场景内(但不限于此),使用分块上传模式,如:

  • 需支持断点上传
  • 上传超过100M的文件
  • 网络条件较差,经常和NOS服务器断开连接
  • 上传文件之前无法确定文件的大小

分块上传一般流程如下所示:

  • 初始化一个分块上传任务(createMultipartRequest)
  • 上传分块(UploadPart)
  • 完成分块上传(CompleteMultipartUpload)或者取消分块上传(AbortMultipartUpload)

4.2.1. 初始化分块上传

您可以使用S3Client.InitiateMultipartUpload初始化分块上传,具体实现如下:

Aws::String initMultipart(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName){
    Aws::S3::Model::CreateMultipartUploadRequest createMultipartRequest;
    createMultipartRequest.WithBucket(bucketName).WithKey(objectName);

    //1. init
    auto createMultiPartResult = s3Client.CreateMultipartUpload(createMultipartRequest);
    if(createMultiPartResult.IsSuccess()){
        std::cout << "createMultipartUpload ok, uploadId = " << createMultiPartResult.GetResult().GetUploadId() << std::endl;
        return createMultiPartResult.GetResult().GetUploadId();
    } else {
        std::cout << "Create MultipartUpload failed" << createMultiPartResult.GetError().GetMessage() <<  std::endl;
        return "";
    }
}

4.2.2. 上传分块

您可以使用S3Client.UploadPart上传分块,具体实现如下:

Aws::Vector<Aws::S3::Model::CompletedPart> uploadParts(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName,const Aws::String& uploadId,const Aws::String& uploadFilePath){

    long partSize =  5 * 1024 * 1024;
    std::fstream file(uploadFilePath.c_str(),std::ios::in | std::ios::binary);
    file.seekg(0,std::ios::end);
    long   fileSize = file.tellg();
    std::cout << file.tellg() << std::endl;
    file.seekg(0, std::ios::beg);


    char* buffer = new char[partSize];

    long filePosition = 0;
    Aws::Vector<Aws::S3::Model::CompletedPart> completeParts;

    int partNumber = 1;
    while(filePosition < fileSize){
        partSize = std::min(partSize,(fileSize - filePosition));
        file.read(buffer,partSize);
        std::cout << "readSize : " << partSize << std::endl;
        Aws::S3::Model::UploadPartRequest uploadPartRequest;
        uploadPartRequest.WithBucket(bucketName).WithKey(objectName).WithUploadId(uploadId).WithPartNumber(partNumber).WithContentLength(partSize);

        Aws::String str(buffer,partSize);
        auto input_data = Aws::MakeShared<Aws::StringStream>("UploadPartStream",str);
        uploadPartRequest.SetBody(input_data);
        filePosition += partSize;

        auto uploadPartResult = s3Client.UploadPart(uploadPartRequest);
        std::cout << uploadPartResult.GetResult().GetETag() << std::endl;
        completeParts.push_back(Aws::S3::Model::CompletedPart().WithETag(uploadPartResult.GetResult().GetETag()).WithPartNumber(partNumber));
        memset(buffer, 0, partSize);
        ++partNumber;
    }
    return completeParts;
}

4.2.3. 完成分块上传

您可以使用S3Client.CompleteMultipartUpload完成上传分块,具体实现如下:

void completeMultipart(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName,const Aws::String& uploadId,const Aws::Vector<Aws::S3::Model::CompletedPart>& completedParts){
    Aws::S3::Model::CompleteMultipartUploadRequest complete;

    complete.WithBucket(bucketName).WithKey(objectName).
    WithUploadId(uploadId).
    WithMultipartUpload(Aws::S3::Model::CompletedMultipartUpload().WithParts(completedParts));
    auto completeMultipartUploadResult = s3Client.CompleteMultipartUpload(complete);
    if( completeMultipartUploadResult.IsSuccess()){
        std::cout << "done" << std::endl;
    } else {
        Aws::S3::Model::AbortMultipartUploadRequest abortRequest;
        abortRequest.WithBucket(bucketName).WithKey(objectName).WithUploadId(uploadId);
        s3Client.AbortMultipartUpload(abortRequest);
        std::cout << "multipartUpload failed" << std::endl;
    }
}

4.2.4. 分块上传示例

下面通过一个完整的示例说明了如何进行分片上传操作,可以参考下面代码:

void multipartUpload(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName,const Aws::String& uploadFilePath){
    //1. init
    const Aws::String uploadId = initMultipart(s3Client, bucketName, objectName);

    //2. uploadPart
    Aws::Vector<Aws::S3::Model::CompletedPart> completeParts = uploadParts(s3Client, bucketName, objectName, uploadId, uploadFilePath);

    //3. complete multipart upload

    completeMultipart(s3Client,bucketName,objectName,uploadId,completeParts);
}

Attention

  1. 上面程序一共分为三个步骤:1. initiate 2. uploadPart 3. complete
  2. UploadPart 方法要求除最后一个Part以外,其他的Part大小都要大于或等于16K。但是Upload Part接口并不会立即校验上传Part的大小(因为不知道是否为最后一块);只有当Complete Multipart Upload的时候才会校验。
  3. Part号码的范围是1~10000。如果超出这个范围,NOS将返回InvalidArgument的错误码。
  4. 分块上传除最后一块外,分块不能小于16k,每一个分块不能大于100M。
  5. 每次上传Part时都要把流定位到此次上传块开头所对应的位置。
  6. 分片上传任务初始化或上传部分分片后,可以使用abortMultipartUpload接口中止分片上传事件。当分片上传事件被中止后,就不能再使用这个Upload ID做任何操作,已经上传的分片数据也会被删除。
  7. 每次上传Part之后,NOS的返回结果会包含一个 PartETag 对象,它是上传块的ETag与块编号(PartNumber)的组合。在后续完成分片上传的步骤中会用到它,因此我们需要将其保存起来,然后在第三步complete的时候使用,具体操作参考上面代码。

4.2.5. 取消分块上传

您可以使用S3Client.AbortMultipartUpload取消上传事件,具体实现如下:

void abortMultipartUpload(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName,const Aws::String& uploadId){
    Aws::S3::Model::AbortMultipartUploadRequest abortRequest;
    abortRequest.WithBucket(bucketName).WithKey(objectName).WithUploadId(uploadId);
    auto result = s3Client.AbortMultipartUpload(abortRequest);
    if (result.IsSuccess()) {
        std::cout << "abort succ" << std::endl;
    } else {
        std::cout << "abort failed" << std::endl;
    }
}

4.2.6. 查看已经上传的分片

查看已经上传的分片可以罗列出指定Upload ID(InitiateMultipartUpload时获取)所属的所有已经上传成功的分片,您可以通过S3Client.ListParts接口获取已经上传的分片,可以参考以下代码:

void ListParts(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName,const Aws::String& uploadId){
    Aws::S3::Model::ListPartsRequest listPartRequest;
    listPartRequest.WithBucket(bucketName).WithKey(objectName).WithUploadId(uploadId).WithMaxParts(10).WithPartNumberMarker(10);
    auto result = s3Client.ListParts(listPartRequest);
    if (result.IsSuccess()) {
        for(auto part : result.GetResult().GetParts()){
            std::cout << part.GetPartNumber() << "-->" << part.GetETag() << std::endl;
        }
    } else {
        std::cout << "list part request faield" << std::endl;
    }
}

查看已经上传的分片可以指定以下参数:

4.2.7. 查看当前正在进行的分片上传任务

查看正在进行的分片上传任务可以罗列出正在进行,还未完成的分片上传任务,您可以通过S3Client.ListMultipartUploads接口当前的上传任务,可以参考以下代码:

void listMultipartUpload(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName){
    Aws::S3::Model::ListMultipartUploadsRequest listMultipartUploadRequest;
    listMultipartUploadRequest.WithBucket(bucketName);
    auto result = s3Client.ListMultipartUploads(listMultipartUploadRequest);
    if (result.IsSuccess()) {
        for(auto upload : result.GetResult().GetUploads()){
            std::cout << upload.GetKey() << std::endl;
        }
    } else {
        std::cout << "failed" << std::endl;
    }
}

上述代码中使用的options参数如下:

参数值 描述
KeyMarker 指定某一uploads key,只有大于该KeyMarker的才会被列出
MaxUploads 最多返回MaxUploads条记录,默认1000

4.3. 设置文件元信息

文件元数据(object meta),是上传到NOS的文件属性描述信息,CPP SDK只支持设置用户自定义元数据。文件元信息可以在各种上传方式(流式上传、单块上传、分块上传)时进行设置。

4.3.1. 设定自定义http header

下面的源代码实现了上传文件时设置Http header:

void putObjectFromContentWithMeta(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName,const Aws::String& content,const Aws::Map<Aws::String, Aws::String>& metas){
    Aws::S3::Model::PutObjectRequest object_request;
    object_request.WithBucket(bucketName).WithKey(objectName);

    // Binary files must also have the std::ios_base::bin flag or'ed in
    auto input_data = Aws::MakeShared<Aws::StringStream>("PutObjectInputStream",content);

    object_request.SetBody(input_data);

    object_request.SetMetadata(metas);

    auto put_object_outcome = s3Client.PutObject(object_request);

    if (put_object_outcome.IsSuccess())
    {
        std::cout << "Done!" << std::endl;
    }
    else
    {
        std::cout << "PutObject error: " <<
        put_object_outcome.GetError().GetExceptionName() << " " <<
        put_object_outcome.GetError().GetMessage() << std::endl;
    }
}

Attention

  1. 各种上传方式(包括流式上传、单块上传、分块上传)都可以设置元数据信息。流式上传设置元数据的方式与单块上传相同
  2. 文件的元信息可以通过S3Client.GetObjectMetadata获取;
  3. user meta的名称以”x-amz-meta-“开头,例如设置为:’x-amz-meta-name’,读取名字为x-amz-meta-name的参数即可

4.4. 获取文件元信息

下面的源代码实现了获取对象的自定义原信息:

void getObjectMeta(const Aws::S3::S3Client& s3Client,const Aws::String& bucketName,const Aws::String& objectName){
    Aws::S3::Model::HeadObjectRequest headObjectRequest;
    headObjectRequest.WithBucket(bucketName).WithKey(objectName);
    auto result = s3Client.HeadObject(headObjectRequest);
    if (result.IsSuccess()) {
        for(auto entry : result.GetResult().GetMetadata()){
            std::cout << entry.first << " -- > " << entry.second << std::endl;
        }
    } else {
        std::cout << "failed" << std::endl;
    }
}