上传文件
上传文件
在BOS中,用户操作的基本数据单元是Object。Object包含Key、Meta和Data。其中,Key是Object的名字;Meta是用户对该Object的描述,由一系列Name-Value对组成;Data是Object的数据。
BOS C SDK提供了丰富的文件上传接口,可以通过以下方式上传文件:
- 简单上传
- 追加上传
- 分块上传
- 断点续传上传
简单上传
BOS在简单上传的场景中,支持以指定文件形式、以数据流方式、以文件描述符方式、以字符串方式执行Object上传,请参考如下代码:
bos_pool_t *p = NULL;
char *object_name = "bos_test_put_object.ts";
char *str = "test bos c sdk";
bos_status_t *s = NULL;
int is_cname = 0;
bos_string_t bucket;
bos_string_t object;
bos_table_t *headers = NULL;
bos_table_t *head_headers = NULL;
bos_table_t *head_resp_headers = NULL;
char *content_type = NULL;
bos_request_options_t *options = NULL;
/* test put object */
bos_pool_create(&p, NULL);
options = bos_request_options_create(p);
init_test_request_options(options, is_cname);
headers = bos_table_make(p, 1);
apr_table_set(headers, "x-bos-meta-author", "bos");
s = create_test_object(options, TEST_BUCKET_NAME, object_name, str, headers);
Object以文件的形式上传到BOS中,put_object、upload_file函数支持不超过5GB的Object上传。若要支持大文件(大于5G的文件)上传请使用upload_super_file,参考代码如下:
bos_pool_t *p = NULL;
int is_cname = 0;
bos_status_t *s = NULL;
bos_request_options_t *options = NULL;
bos_acl_e bos_acl = BOS_ACL_PRIVATE;
bos_string_t bucket;
bos_table_t *resp_headers;
bos_string_t object;
bos_string_t file_path;
/* create test bucket */
bos_pool_create(&p, NULL);
options = bos_request_options_create(p);
init_test_request_options(options, is_cname);
s = create_test_bucket(options, TEST_BUCKET_NAME, bos_acl);
bos_str_set(&bucket, TEST_BUCKET_NAME);
bos_str_set(&object, "test.mp4");
bos_str_set(&file_path, "../../../test.mp4");
s = bos_put_object_from_file(options, &bucket, &object, &file_path, NULL, &resp_headers);
设置文件元信息
文件元信息(Object Meta),是对用户在向BOS上传文件时,同时对文件进行的属性描述,主要分为分为两种:设置HTTP标准属性(HTTP Headers)和用户自定义的元信息。
- 设定Object的Http Header
BOS C SDK本质上是调用后台的HTTP接口,因此用户可以在上传文件时自定义Object的Http Header。常用的http header说明如下:
名称 | 描述 | 默认值 |
---|---|---|
Content-MD5 | 文件数据校验,设置后BOS会启用文件内容MD5校验,把您提供的MD5与文件的MD5比较,不一致会抛出错误 | 无 |
Content-Type | 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如没有指,BOS则根据文件的扩展名自动生成,如文件没有扩展名则填默认值 | application/octet-stream |
Content-Disposition | 指示MIME用户代理如何显示附加的文件,打开或下载,及文件名称 | 无 |
Content-Length | 上传的文件的长度,超过流/文件的长度会截断,不足为实际值 | 流/文件时间长度 |
Expires | 缓存过期时间 | 无 |
Cache-Control | 指定该Object被下载时的网页的缓存行为 | 无 |
参考代码如下:
...
headers = bos_table_make(p, 1);
apr_table_set(headers, BOS_CONTENT_TYPE, "image/jpeg");
...
- 用户自定义元信息
BOS支持用户自定义元数据来对Object进行描述。如下代码所示:
apr_table_set(headers, "x-bce-meta-author", "bos");
提示:
- 在上面代码中,用户自定义了一个名字为”x-bce-meta-author”,值为”bos”的元数据
- 当用户下载此Object的时候,此元数据也可以一并得到
- 一个Object可以有多个类似的参数,但所有的User Meta总大小不能超过2KB
设置Object的Copy属性
BOS同时会提供CopyObject接口用于将一个已经存在的Object拷贝到另外一个Object,拷贝过程中会对源Object的Etag或修改状态进行判断,根据判断结果决定是否执行拷贝。详细的参数解释如下:
名称 | 类型 | 描述 | 是否必需 |
---|---|---|---|
x-bce-copy-source-if-match | std::string | 如果源Object的ETag值和用户提供的ETag相等,则执行拷贝操作,否则拷贝失败。 | 否 |
x-bce-copy-source-if-none-match | std::string | 如果源Object的ETag和用户提供的ETag不相等,则执行拷贝操作,否则拷贝失败。 | 否 |
x-bce-copy-source-if-unmodified-since | std::string | 如果源object在x-bce-copy-source-if-unmodified-since之后没被修改,则执行拷贝操作,否则拷贝失败。 | 否 |
x-bce-copy-source-if-modified-since | std::string | 如果源object在x-bce-copy-source-if-modified-since之后被修改了,则执行拷贝操作,否则拷贝失败。 | 否 |
对应的示例代码:
bos_pool_t *p = NULL;
int is_cname = 0;
bos_status_t *s = NULL;
bos_request_options_t *options = NULL;
bos_string_t bucket;
bos_string_t object;
bos_string_t src_bucket;
bos_string_t src_object;
bos_string_t src_endpoint;
bos_table_t *resp_headers = NULL;
bos_pool_create(&p, NULL);
options = bos_request_options_create(p);
init_test_request_options(options, is_cname);
bos_str_set(&bucket, TEST_BUCKET_NAME);
bos_str_set(&object, "test_copy.txt");
bos_str_set(&src_bucket, TEST_BUCKET_NAME);
bos_str_set(&src_object, "bos_test_put_object.ts");
bos_str_set(&src_endpoint, options->config->endpoint.data);
bos_copy_object_params_t *params = NULL;
params = bos_create_copy_object_params(p);
bos_table_t *headers = bos_table_make(p, 2);
apr_table_add(headers, "x-bce-metadata-directive", "replace");
apr_table_add(headers, "x-bce-storage-class", "STANDARD_IA");
json_t *root;
s = bos_copy_object(options, &src_bucket, &src_object, &bucket, &object, headers, &root, &resp_headers);
上传Object时设置存储类型
BOS支持标准存储, 低频存储和冷存储,上传Object并存储为低频存储类型通过指定StorageClass实现,三种存储类型对应的参数如下:
存储类型 | 参数 |
---|---|
标准存储 | STANDARD |
低频存储 | STANDARD_IA |
冷存储 | COLD |
归档存储 | ARCHIVE |
追加上传
上文介绍的简单上传方式,创建的Object都是Normal类型,用户不可再进行追加写,这在日志、视频监控、视频直播等数据复写较频繁的场景中使用不方便。
正因如此,百度智能云BOS特别支持了AppendObject,即以追加写的方式上传文件。通过AppendObject操作创建的Object类型为Appendable Object,可以对该Object追加数据。AppendObject大小限制为0~5G。
通过AppendObject方式上传示例代码如下:
bos_pool_t *p = NULL;
char *object_name = "bos_test_append_object_from_file";
bos_string_t bucket;
bos_string_t object;
char *filename = __FILE__;
bos_string_t append_file;
bos_status_t *s = NULL;
int is_cname = 0;
int64_t position = 0;
bos_table_t *headers = NULL;
bos_table_t *resp_headers = NULL;
bos_request_options_t *options = NULL;
/* test append object */
bos_pool_create(&p, NULL);
options = bos_request_options_create(p);
init_test_request_options(options, is_cname);
headers = bos_table_make(p, 0);
bos_str_set(&bucket, TEST_BUCKET_NAME);
bos_str_set(&object, object_name);
bos_str_set(&append_file, filename);
s = bos_append_object_from_file(options, &bucket, &object, position,
&append_file, headers, &resp_headers);
分块上传
除了通过简单上传及追加上传方式将文上传件到BOS以外,BOS还提供了另外一种上传模式 —— Multipart Upload。用户可以在如下的应用场景内(但不仅限于此),使用Multipart Upload上传模式,如:
- 需要支持断点上传。
- 上传超过5GB大小的文件。
- 网络条件较差,和BOS的服务器之间的连接经常断开。
- 需要流式地上传文件。
- 上传文件之前,无法确定上传文件的大小。
下面将一步步介绍Multipart Upload的实现。假设有一个文件,本地路径为 /path/to/file.zip
,由于文件比较大,将其分块传输到BOS中。
初始化Multipart Upload
使用 init_test_multipart_upload
方法来初始化一个分块上传事件:
bos_status_t *s = NULL;
bos_table_t *resp_headers = NULL;
bos_string_t object;
bos_table_t *headers = NULL;
bos_table_t *complete_headers = NULL;
bos_string_t upload_id;
bos_upload_file_t *upload_file = NULL;
bos_list_upload_part_params_t *params = NULL;
bos_list_t complete_part_list;
bos_list_part_content_t *part_content = NULL;
bos_complete_part_content_t *complete_part_content = NULL;
int part_num = 1;
int64_t pos = 0;
int64_t file_length = 0;
bos_str_set(&object, "test1");
s = bos_init_multipart_upload(options, &bucket, &object,
&upload_id, headers, &resp_headers);
if (bos_status_is_ok(s)) {
printf("Init multipart upload succeeded, upload_id:%.*s\n",
upload_id.len, upload_id.data);
} else {
printf("Init multipart upload failed\n");
return;
}
initMultiUploadResponse
的返回结果中含有 UploadId
,它是区分分块上传事件的唯一标识,在后面的操作中,我们将用到它。
上传分块
接着,把文件分块上传。
int res = BOSE_OK;
bos_file_buf_t *fb = bos_create_file_buf(p);
res = bos_open_file_for_all_read(p, TEST_MULTIPART_FILE, fb);
if (res != BOSE_OK) {
bos_error_log("Open read file fail, filename:%s\n", TEST_MULTIPART_FILE);
return;
}
file_length = fb->file_last;
apr_file_close(fb->file);
while(pos < file_length) {
upload_file = bos_create_upload_file(p);
bos_str_set(&upload_file->filename, TEST_MULTIPART_FILE);
upload_file->file_pos = pos;
pos += 2 * 1024 * 1024;
upload_file->file_last = pos < file_length ? pos : file_length; //2MB
s = bos_upload_part_from_file(options, &bucket, &object, &upload_id,
part_num++, upload_file, &resp_headers);
if (bos_status_is_ok(s)) {
printf("Multipart upload part from file succeeded\n");
} else {
printf("Multipart upload part from file failed\n");
}
}
上面代码的核心是调用 bos_upload_part_from_file
方法来并发的上传每一个分块,但是要注意以下几点:
bos_upload_part_from_file
要求除最后一个Part以外,其他的Part大小都要大于等于100KB。但是Upload Part接口并不会立即校验上传Part的大小;只有当Complete Multipart Upload的时候才会校验, 若upload_part流程中的块大小不符合预期, 则complete_multipart_upload
接口会报错。- 为了保证数据在网络传输过程中不出现错误,建议您在
upload_part
后,使用每个分块BOS返回的Content-MD5值分别验证已上传分块数据的正确性。当所有分块数据合成一个Object后,不再含MD5值。 - Part号码的范围是1~10000。如果超出这个范围,BOS将返回InvalidArgument的错误码。
- 每次上传Part时都要把流定位到此次上传块开头所对应的位置。
- 每次上传Part之后,BOS的返回结果会包含一个
ETag
对象,它是上传块的ETag与块编号(PartNumber)的组合,在后续完成分块上传的步骤中会用到它,因此需要将其保存起来。一般来讲这些ETag
对象将被保存到vector中。
完成分块上传
如下代码所示,完成分块上传:
s = bos_list_upload_part(options, &bucket, &object, &upload_id,
params, &list_part_resp_headers);
CuAssertIntEquals(tc, 200, s->code);
CuAssertIntEquals(tc, 1, params->truncated);
CuAssertStrEquals(tc, expect_part_num_marker,
params->next_part_number_marker.data);
CuAssertPtrNotNull(tc, list_part_resp_headers);
bos_list_for_each_entry(bos_list_part_content_t, part_content1, ¶ms->part_list, node) {
complete_content1 = bos_create_complete_part_content(p);
bos_str_set(&complete_content1->part_number, part_content1->part_number.data);
bos_str_set(&complete_content1->etag, part_content1->etag.data);
bos_list_add_tail(&complete_content1->node, &complete_part_list);
}
bos_list_init(¶ms->part_list);
if (params->next_part_number_marker.data) {
bos_str_set(¶ms->part_number_marker, params->next_part_number_marker.data);
}
s = bos_list_upload_part(options, &bucket, &object, &upload_id, params, &list_part_resp_headers);
CuAssertIntEquals(tc, 200, s->code);
CuAssertIntEquals(tc, 0, params->truncated);
CuAssertPtrNotNull(tc, list_part_resp_headers);
bos_list_for_each_entry(bos_list_part_content_t, part_content2, ¶ms->part_list, node) {
complete_content2 = bos_create_complete_part_content(p);
bos_str_set(&complete_content2->part_number, part_content2->part_number.data);
bos_str_set(&complete_content2->etag, part_content2->etag.data);
bos_list_add_tail(&complete_content2->node, &complete_part_list);
}
bos_complete_part_content_t *content_test;
bos_list_for_each_entry(bos_complete_part_content_t, content_test, &complete_part_list, node) {
}
s = bos_complete_multipart_upload(options, &bucket, &object, &upload_id,
&complete_part_list, complete_headers, &resp_headers);
上面代码中的 complete_part_list
是第二部中保存的part_t的列表,BOS收到用户提交的Part列表后,会逐一验证每个数据Part的有效性。当所有的数据Part验证通过后,BOS将把这些数据part组合成一个完整的Object。
取消分块上传事件
用户可以使用abortMultipartUpload方法取消分块上传。
bos_status_t *s = NULL;
bos_table_t *resp_headers = NULL;
bos_string_t object;
bos_table_t *headers = NULL;
bos_string_t upload_id;
bos_str_set(&object, "test1");
s = bos_abort_multipart_upload(options, &bucket, &object, &upload_id,
&resp_headers);
获取未完成的分块上传事件
用户可以使用 bos_list_multipart_upload
方法获取Bucket内未完成的分块上传事件。
bos_pool_t *p = NULL;
bos_string_t bucket;
char *object_name1 = "bos_test_abort_multipart_upload1";
char *object_name2 = "bos_test_abort_multipart_upload2";
int is_cname = 0;
bos_request_options_t *options = NULL;
bos_string_t upload_id1;
bos_string_t upload_id2;
bos_status_t *s = NULL;
bos_table_t *resp_headers;
bos_list_multipart_upload_params_t *params = NULL;
char *expect_next_key_marker = "bos_test_abort_multipart_upload1";
bos_pool_create(&p, NULL);
options = bos_request_options_create(p);
init_test_request_options(options, is_cname);
s = init_test_multipart_upload(options, TEST_BUCKET_NAME, object_name1, &upload_id1);
CuAssertIntEquals(tc, 200, s->code);
s = init_test_multipart_upload(options, TEST_BUCKET_NAME, object_name2, &upload_id2);
CuAssertIntEquals(tc, 200, s->code);
params = bos_create_list_multipart_upload_params(p);
params->max_ret = 1;
bos_str_set(&bucket, TEST_BUCKET_NAME);
s = bos_list_multipart_upload(options, &bucket, params, &resp_headers);
注意:
- 默认情况下,如果Bucket中的分块上传事件的数目大于1000,则只会返回1000个Object,并且返回结果中is_truncated的值为True,同时返回next_marker作为下次读取的起点。
- 若想返回更多分块上传事件的数目,可以使用set_marker函数设置marker分次读取。
获取所有已上传的块信息
用户可以使用 listParts
方法获取某个上传事件中所有已上传的块。
params = bos_create_list_upload_part_params(p);
params->max_ret = 1;
bos_list_init(&complete_part_list);
s = bos_list_upload_part(options, &bucket, &object, &upload_id,
params, &list_part_resp_headers);
断点续传上传
当用户向BOS上传大文件时,如果网络不稳定或者遇到程序崩等情况,则整个上传就失败了,失败前已经上传的部分也作废,用户不得不重头再来。这样做不仅浪费资源,在网络不稳定的情况下,往往重试多次还是无法完成上传。 基于上述场景,BOS提供了断点续传上传的能力:
- 当网络情况一般的情况下,建议使用三步上传方式,将object分为5Mb的块,参考分块上传。
- 当您的网络情况非常差,推荐使用append_object的方式进行断点续传,每次append 较小数据256kb,参考追加上传。
提示
- 断点续传是分片上传的封装和加强,是用分片上传实现的;
- 文件较大或网络环境较差时,推荐使用分片上传;