使用自定义头域(Custom Headers)(C SDK)
开发过程中,您有任何问题可以在GitHub上提交issue,或者在华为云对象存储服务论坛中发帖求助。
功能说明
OBS C SDK 支持在发起请求时携带自定义 HTTP 头域。通过 obs_options.request_options 中的 custom_headers 字段设置,所有操作(PUT/GET/DELETE/LIST 等)均支持。
接口约束
| 头域前缀 | 大小写处理 | 是否参与签名 |
|---|---|---|
x-obs-* | 自动转小写 | 是 |
| 其他 | 保持原样 | 否 |
- 以 x-obs- 开头的头域名称会自动转为小写,并参与请求签名计算
- 其他头域保持原始大小写,仅作为 HTTP 头发送,不参与签名
- Content-Type、Content-MD5 等标准签名头应通过 obs_put_properties 设置,不要通过自定义头域传入
方法定义
| 参数名称 | 参数类型 | 是否必选 | 描述 |
|---|---|---|---|
| ustom_headers_count | int | 否 | 参数解释: 自定义头域的数量。 约束限制: 自定义头域与用户元数据(meta_data)共享内部数组。 取值范围: ≥ 0 的整数。为 0 时不发送自定义头域;为负数时等同于 0。 默认取值: 0 |
| custom_headers | 否 | 参数解释: 自定义头域数组,每个元素为一个表2 obs_name_value 键值对。 约束限制: 当 custom_headers_count > 0 时不能为 NULL;每个元素的 name 和 value 不能为 NULL,否则该条目被跳过。所有头域的原始字节总长度不超过 OBS_MAX_METADATA_SIZE(4096 字节),超出返回 OBS_STATUS_MetadataHeadersTooLong。不要通过此字段设置 Content-Type、Content-MD5 等标准签名头,应通过 obs_put_properties 对应字段设置,否则可能导致签名不一致。 取值范围: 指向 obs_name_value 数组的指针,数组长度须 ≥ custom_headers_count。 默认取值: NULL |
代码示例一:上传对象时携带自定义头域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | #include "eSDKOBS.h" #include <stdio.h> #include <stdlib.h> #include <string.h> // 响应回调函数,可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中 obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data); // 结束回调函数,可以在这个回调中把obs_status和obs_error_details的内容记录到callback_data(用户自定义回调数据)中 void response_complete_callback(obs_status status, const obs_error_details *error, void *callback_data); // 上传对象的数据回调函数 int put_object_data_callback(int buffer_size, char *buffer, void *callback_data); // 自定义回调数据:用于在回调间传递上传内容和结果状态 typedef struct PutObjectData { char *data; uint64_t data_len; uint64_t data_read; obs_status ret_status; } PutObjectData; int main() { // 以下示例展示如何上传对象时携带自定义头域: // 在程序入口调用obs_initialize方法来初始化网络、内存等全局资源。 obs_status ret_status = obs_initialize(OBS_INIT_ALL); if (OBS_STATUS_OK != ret_status) { printf("obs_initialize failed(%s).\n", obs_get_status_name(ret_status)); return -1; } obs_options options; // 创建并初始化options,该参数包括访问域名(host_name)、访问密钥(access_key_id和access_key_secret)、桶名(bucket_name)、桶存储类别(storage_class)等配置信息 init_obs_options(&options); // host_name填写桶所在的endpoint, 此处以华北-北京四为例,其他地区请按实际情况填写。 options.bucket_options.host_name = "obs.cn-north-4.myhuaweicloud.com"; // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; // 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量ACCESS_KEY_ID和SECRET_ACCESS_KEY。 options.bucket_options.access_key = getenv("ACCESS_KEY_ID"); options.bucket_options.secret_access_key = getenv("SECRET_ACCESS_KEY"); if (!options.bucket_options.access_key || !options.bucket_options.secret_access_key) { printf("ERROR: ACCESS_KEY_ID and SECRET_ACCESS_KEY environment variables must be set.\n"); obs_deinitialize(); return -1; } // 填写Bucket名称,例如example-bucket-name。 char * bucket_name = "example-bucket-name"; options.bucket_options.bucket_name = bucket_name; // 设置自定义头域(x-obs-前缀的头会自动参与签名,其余头域仅发送不签名) obs_name_value headers[2]; memset(headers, 0, sizeof(headers)); headers[0].name = "x-obs-request-source"; headers[0].value = "sdk-custom-header"; headers[1].name = "X-Client-Version"; headers[1].value = "1.0.0"; options.request_options.custom_headers = headers; options.request_options.custom_headers_count = 2; // 设置上传属性 char *key = "test-object.txt"; char *data = "Hello OBS!"; obs_put_properties put_properties; init_put_properties(&put_properties); put_properties.content_type = "text/plain"; // 构造回调数据与回调结构体 PutObjectData putData; memset(&putData, 0, sizeof(PutObjectData)); putData.data = data; putData.data_len = strlen(data); putData.ret_status = OBS_STATUS_BUTT; obs_put_object_handler handler = { {&response_properties_callback, &response_complete_callback}, &put_object_data_callback, NULL }; // 上传对象(自定义头域会自动携带) put_object(&options, key, strlen(data), &put_properties, NULL, &handler, &putData); // 判断请求是否成功 if (putData.ret_status == OBS_STATUS_OK) { printf("put object successfully. \n"); } else { printf("put object failed(%s).\n", obs_get_status_name(putData.ret_status)); } // 释放分配的全局资源 obs_deinitialize(); } // 上传对象的数据回调函数,SDK通过它读取待上传的数据 int put_object_data_callback(int buffer_size, char *buffer, void *callback_data) { PutObjectData *putData = (PutObjectData *)callback_data; if (putData == NULL) { return 0; } uint64_t remaining = putData->data_len - putData->data_read; if (remaining <= 0) { return 0; } int toRead = (remaining < (uint64_t)buffer_size) ? (int)remaining : buffer_size; memcpy(buffer, putData->data + putData->data_read, toRead); putData->data_read += toRead; return toRead; } // 响应回调函数,可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中 obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data) { if (properties == NULL) { printf("error! obs_response_properties is null!"); return OBS_STATUS_OK; } // 打印响应信息 #define print_nonnull(name, field) \ do { \ if (properties-> field) { \ printf("%s: %s\n", name, properties->field); \ } \ } while (0) print_nonnull("request_id", request_id); print_nonnull("request_id2", request_id2); print_nonnull("content_type", content_type); if (properties->content_length) { printf("content_length: %llu\n", properties->content_length); } print_nonnull("server", server); print_nonnull("ETag", etag); print_nonnull("expiration", expiration); print_nonnull("website_redirect_location", website_redirect_location); print_nonnull("version_id", version_id); print_nonnull("allow_origin", allow_origin); print_nonnull("allow_headers", allow_headers); print_nonnull("max_age", max_age); print_nonnull("allow_methods", allow_methods); print_nonnull("expose_headers", expose_headers); print_nonnull("storage_class", storage_class); print_nonnull("server_side_encryption", server_side_encryption); print_nonnull("kms_key_id", kms_key_id); print_nonnull("customer_algorithm", customer_algorithm); print_nonnull("customer_key_md5", customer_key_md5); print_nonnull("bucket_location", bucket_location); print_nonnull("obs_version", obs_version); print_nonnull("restore", restore); print_nonnull("obs_object_type", obs_object_type); print_nonnull("obs_next_append_position", obs_next_append_position); print_nonnull("obs_head_epid", obs_head_epid); print_nonnull("reserved_indicator", reserved_indicator); int i; for (i = 0; i < properties->meta_data_count; i++) { printf("x-obs-meta-%s: %s\n", properties->meta_data[i].name, properties->meta_data[i].value); } return OBS_STATUS_OK; } // 结束回调函数,可以在这个回调中把obs_status和obs_error_details的内容记录到callback_data(用户自定义回调数据)中 void response_complete_callback(obs_status status, const obs_error_details *error, void *callback_data) { if (callback_data) { PutObjectData *putData = (PutObjectData *)callback_data; putData->ret_status = status; } else { printf("Callback_data is NULL"); } if (error && error->message) { printf("Error Message: \n %s\n", error->message); } if (error && error->resource) { printf("Error Resource: \n %s\n", error->resource); } if (error && error->further_details) { printf("Error further_details: \n %s\n", error->further_details); } if (error && error->extra_details_count) { int i; for (i = 0; i < error->extra_details_count; i++) { printf("Error Extra Detail(%d):\n %s:%s\n", i, error->extra_details[i].name, error->extra_details[i].value); } } if (error && error->error_headers_count) { int i; for (i = 0; i < error->error_headers_count; i++) { const char *errorHeader = error->error_headers[i]; printf("Error Headers(%d):\n %s\n", i, errorHeader == NULL ? "NULL Header" : errorHeader); } } } |
该请求发出的 HTTP 头将包含:
x-obs-request-source: sdk-custom-header <- 参与签名(名称已转小写) X-Client-Version: 1.0.0 <- 不参与签名(保持原始大小写)
代码示例二:下载对象时携带自定义头域
#include "eSDKOBS.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 响应回调函数,可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中
obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data);
// 结束回调函数,可以在这个回调中把obs_status和obs_error_details的内容记录到callback_data(用户自定义回调数据)中
void response_complete_callback(obs_status status, const obs_error_details *error, void *callback_data);
// 下载对象的数据回调函数,SDK通过它返回下载的对象内容
obs_status get_object_data_callback(int buffer_size, const char *buffer, void *callback_data);
// 自定义回调数据:用于在回调间传递下载内容和结果状态
typedef struct GetObjectData
{
FILE *fp;
obs_status ret_status;
} GetObjectData;
int main()
{
// 以下示例展示如何下载对象时携带自定义头域:
// 在程序入口调用obs_initialize方法来初始化网络、内存等全局资源。
obs_status ret_status = obs_initialize(OBS_INIT_ALL);
if (OBS_STATUS_OK != ret_status)
{
printf("obs_initialize failed(%s).\n", obs_get_status_name(ret_status));
return -1;
}
obs_options options;
// 创建并初始化options,该参数包括访问域名(host_name)、访问密钥(access_key_id和access_key_secret)、桶名(bucket_name)、桶存储类别(storage_class)等配置信息
init_obs_options(&options);
// host_name填写桶所在的endpoint, 此处以华北-北京四为例,其他地区请按实际情况填写。
options.bucket_options.host_name = "obs.cn-north-4.myhuaweicloud.com";
// 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
// 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量ACCESS_KEY_ID和SECRET_ACCESS_KEY。
options.bucket_options.access_key = getenv("ACCESS_KEY_ID");
options.bucket_options.secret_access_key = getenv("SECRET_ACCESS_KEY");
if (!options.bucket_options.access_key || !options.bucket_options.secret_access_key) {
printf("ERROR: ACCESS_KEY_ID and SECRET_ACCESS_KEY environment variables must be set.\n");
obs_deinitialize();
return -1;
}
// 填写Bucket名称,例如example-bucket-name。
char * bucket_name = "example-bucket-name";
options.bucket_options.bucket_name = bucket_name;
// 设置自定义头域(x-obs-前缀的头会自动参与签名,其余头域仅发送不签名)
obs_name_value headers[1];
memset(headers, 0, sizeof(headers));
headers[0].name = "x-obs-request-source";
headers[0].value = "download-with-custom-header";
options.request_options.custom_headers = headers;
options.request_options.custom_headers_count = 1;
// 设置下载对象信息
char *key = "test-object.txt";
obs_object_info object_info;
memset(&object_info, 0, sizeof(obs_object_info));
object_info.key = key;
object_info.version_id = NULL;
// 构造回调数据与回调结构体
GetObjectData getData;
memset(&getData, 0, sizeof(GetObjectData));
getData.fp = fopen("test-object.txt", "wb");
if (getData.fp == NULL) {
printf("ERROR: failed to open local file for writing.\n");
obs_deinitialize();
return -1;
}
getData.ret_status = OBS_STATUS_BUTT;
obs_get_object_handler handler =
{
{&response_properties_callback, &response_complete_callback},
&get_object_data_callback,
NULL
};
// 下载对象(自定义头域会自动携带)
get_object(&options, &object_info, NULL, NULL, &handler, &getData);
fclose(getData.fp);
// 判断请求是否成功
if (getData.ret_status == OBS_STATUS_OK) {
printf("get object successfully. \n");
}
else
{
printf("get object failed(%s).\n", obs_get_status_name(getData.ret_status));
}
// 释放分配的全局资源
obs_deinitialize();
}
// 下载对象的数据回调函数,SDK通过它返回下载的对象内容
obs_status get_object_data_callback(int buffer_size, const char *buffer, void *callback_data)
{
GetObjectData *getData = (GetObjectData *)callback_data;
if (getData == NULL || getData->fp == NULL) {
return OBS_STATUS_OK;
}
if (buffer_size > 0 && buffer) {
fwrite(buffer, 1, buffer_size, getData->fp);
}
return OBS_STATUS_OK;
}
// 响应回调函数,可以在这个回调中把properties的内容记录到callback_data(用户自定义回调数据)中
obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data)
{
if (properties == NULL)
{
printf("error! obs_response_properties is null!");
return OBS_STATUS_OK;
}
// 打印响应信息
#define print_nonnull(name, field) \
do { \
if (properties-> field) { \
printf("%s: %s\n", name, properties->field); \
} \
} while (0)
print_nonnull("request_id", request_id);
print_nonnull("request_id2", request_id2);
print_nonnull("content_type", content_type);
if (properties->content_length) {
printf("content_length: %llu\n", properties->content_length);
}
print_nonnull("server", server);
print_nonnull("ETag", etag);
print_nonnull("expiration", expiration);
print_nonnull("website_redirect_location", website_redirect_location);
print_nonnull("version_id", version_id);
print_nonnull("allow_origin", allow_origin);
print_nonnull("allow_headers", allow_headers);
print_nonnull("max_age", max_age);
print_nonnull("allow_methods", allow_methods);
print_nonnull("expose_headers", expose_headers);
print_nonnull("storage_class", storage_class);
print_nonnull("server_side_encryption", server_side_encryption);
print_nonnull("kms_key_id", kms_key_id);
print_nonnull("customer_algorithm", customer_algorithm);
print_nonnull("customer_key_md5", customer_key_md5);
print_nonnull("bucket_location", bucket_location);
print_nonnull("obs_version", obs_version);
print_nonnull("restore", restore);
print_nonnull("obs_object_type", obs_object_type);
print_nonnull("obs_next_append_position", obs_next_append_position);
print_nonnull("obs_head_epid", obs_head_epid);
print_nonnull("reserved_indicator", reserved_indicator);
int i;
for (i = 0; i < properties->meta_data_count; i++) {
printf("x-obs-meta-%s: %s\n", properties->meta_data[i].name,
properties->meta_data[i].value);
}
return OBS_STATUS_OK;
}
// 结束回调函数,可以在这个回调中把obs_status和obs_error_details的内容记录到callback_data(用户自定义回调数据)中
void response_complete_callback(obs_status status, const obs_error_details *error, void *callback_data)
{
if (callback_data) {
GetObjectData *getData = (GetObjectData *)callback_data;
getData->ret_status = status;
} else {
printf("Callback_data is NULL");
}
if (error && error->message) {
printf("Error Message: \n %s\n", error->message);
}
if (error && error->resource) {
printf("Error Resource: \n %s\n", error->resource);
}
if (error && error->further_details) {
printf("Error further_details: \n %s\n", error->further_details);
}
if (error && error->extra_details_count) {
int i;
for (i = 0; i < error->extra_details_count; i++) {
printf("Error Extra Detail(%d):\n %s:%s\n", i, error->extra_details[i].name,
error->extra_details[i].value);
}
}
if (error && error->error_headers_count) {
int i;
for (i = 0; i < error->error_headers_count; i++) {
const char *errorHeader = error->error_headers[i];
printf("Error Headers(%d):\n %s\n", i, errorHeader == NULL ? "NULL Header" : errorHeader);
}
}
}