
# 使用自定义头域（Custom Headers）(C SDK)
![](https://support.huaweicloud.com/sdk-c-devg-obs/public_sys-resources/notice_3.0-zh-cn.png)
开发过程中，您有任何问题可以在GitHub上[提交issue](https://github.com/huaweicloud/huaweicloud-sdk-c-obs/issues)，或者在[华为云对象存储服务论坛](https://bbs.huaweicloud.com/forum/forum-620-1.html)中发帖求助。
#### 功能说明
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 设置，不要通过自定义头域传入
 
#### 方法定义
表1obs_http_request_option 
| **参数名称**            | **参数类型**                                                 | **是否必选** | **描述**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
|:---|:---|:---|:---|
| ustom_headers_count | int                                                      | 否        | **参数解释** **：** 自定义头域的数量。 **约束限制**： 自定义头域与用户元数据（meta_data）共享内部数组。 **取值范围**： ≥ 0 的整数。为 0 时不发送自定义头域；为负数时等同于 0。 **默认取值**： 0                                                                                                                                                                                                                                                                                                                      |
| custom_headers      | [表2 obs_name_value]\* | 否        | **参数解释** **：** 自定义头域数组，每个元素为一个[表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 |
   
 表2obs_name_value 
| 参数名称  | 参数类型    | 是否必选 | 描述                                                                                                                                                                                                                                                                                                                                                    |
|:---|:---|:---|:---|
| name  | char \* | 是    | **参数解释** **：** 自定义头域的名称。 **约束限制**： 不能为 NULL，否则该条目被跳过。 **取值范围**： 合法的 HTTP 头域名称字符串。以 x-obs- 开头的名称会自动转小写并参与签名；其他名称保持原样不参与签名。 **默认取值**： 无 |
| value | char \* | 是    | **参数解释** **：** 自定义头域的值。 **约束限制**： 不能为 NULL，否则该条目被跳过。指针在请求完成前必须保持有效。 **取值范围**： 合法的 HTTP 头域值字符串。 **默认取值**： 无                           |
   
#### 代码示例一：上传对象时携带自定义头域
以下示例展示如何上传对象时携带自定义头域：
```
#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);
        }
    }
}
```
