OSS 实践篇-OSS C SDK 深度案例

概述:

客户使用 OSS C SDK 3.5 版本,通过 get_object_to_local_file 方法直接下载 OSS文件到本地,测试过程返回 403 签名不对;

搜集信息

挖取有价值的信息很重要,通过基础信息可以筛选下客户上传日志的详细描述,通过堆栈报错可以找到客户大概原因;

  • OSS response header 中包含 requestID ,记录客户端请求到 OSS 的详细日志;
  • 堆栈完整报错信息;

分析 requestID 对应的服务端错误日志

服务端返回 4xx 2xx 3xx 500 的状态码都会返回 requestID;通过 requestID 我们可以过滤到服务的错误日志如下;

java
Method:HEAD    Host:oss-cn-shanghai.aliyuncs.com    URI:/vod%2Fplat%2Fupdate%2Fhaimu_21_9%2FBasketball.zip

StringToSign:HEAD\n\n\nWed, 06 Nov 2019 07:55:44 GMT\n/vod/plat/update/haimu_21_9/Basketball.zip
UserSignature:LIMmOQ/5TLs2Gk24MuNVRl+Lu0Q=    OssServerSignature:P+YZauMD+fRtZjeMWkauuN6A4eU=

ErrorCode:SignatureDoesNotMatch    ErrorMsg:The request signature we calculated does not match the signature you provided. Check your key and signing method.

OSS 实践篇-OSS C SDK 深度案例

  • 通过日志可以明显看出客户端签名和服务端签名不一致导致校验失败;
  • 根据签名算法可以知道这算计算签名的参数中 /vod/plat/update/haimu_21_9/Basketball.zip 其中 vod 是 bucket 的位置;
  • Host: oss-cn-shanghai.aliyuncs.com 这个位置是错误,因为正常的直接访问 OSS ,Host 正确写法应该是 bucket.oss-cn-region.aliyuncs.com(region 替换成 bucket 所在地区)

问题分析

线索一

已经知道问题出现在签名不对,而且 Host 也不对;问题出现在这里,但是如果 Host ,但 OSS 还能识别出 bucket ,这很奇怪;于是和用户沟通, vod 并不是用户的 bucket,那这个桶是怎么获取到的呢?遂让用户在端上进行 Wireshark 抓包, 得到报文

OSS 实践篇-OSS C SDK 深度案例

  1. 从报文中可以看到 Host 是客户端传的时候就没有 bucket ;
  2. 而 vod 是用户 objectkey 文件前缀而已;
  3. 通过上述几点可以知道问题一定是用户代码上哪里出现的变量写错或者用法不对;

线索二

请用户在本地 debug ,将关键签名的变量信息打印出来看是否完整;

关键信息位置

OSS 实践篇-OSS C SDK 深度案例

用户自己 debug 出来变量的位置

OSS 实践篇-OSS C SDK 深度案例

通过客户 debug 看到变量都正确,为什么还会出现 bucket “丢失” 的情况呢;

线索三

分析源码

int get_object_to_local_file(char *bucketname, char *objectname, char *filename)
{
    aos_pool_t *p = NULL;
    aos_string_t bucket;
    aos_string_t object;
    oss_request_options_t *options = NULL;
    aos_table_t *headers = NULL;
    aos_table_t *params = NULL;
    aos_table_t *resp_headers = NULL;
    aos_status_t *s = NULL;
    aos_string_t file;
    int is_cname = 1;

    if (bucketname == NULL || objectname == NULL || filename == NULL)
        return -1;

    aos_pool_create(&p, NULL);
    options = oss_request_options_create(p);
    init_sample_request_options(options, is_cname);
    aos_str_set(&bucket, bucketname);
    aos_str_set(&object, objectname);
    headers = aos_table_make(p, 0);
    aos_str_set(&file, filename);

    s = oss_get_object_to_file(options, &bucket, &object, headers,
        params, &file, &resp_headers);
    if (aos_status_is_ok(s))
    {
        printf("get object to local file succeeded\n");
    }
    else
    {
        printf("get object to local file failed\n");
        aos_pool_destroy(p);
        return -2;
    }
    aos_pool_destroy(p);
    return 0;
}

— —> 从源码中可以,getobject 下载时形参都是传进来的,既然客户 debug 的变量都是正确的,那么肯定不是传进来变量 ,问题一定是方法内的常量导致;对比官方源码:

void get_object_to_local_file()
{
    aos_pool_t *p = NULL;
    aos_string_t bucket;
    char *download_filename = "get_object_to_local_file.txt";
    aos_string_t object;
    int is_cname = 0;
    oss_request_options_t *options = NULL;
    aos_table_t *headers = NULL;
    aos_table_t *params = NULL;
    aos_table_t *resp_headers = NULL;
    aos_status_t *s = NULL;
    aos_string_t file;

    aos_pool_create(&p, NULL);
    options = oss_request_options_create(p);
    init_sample_request_options(options, is_cname);
    aos_str_set(&bucket, BUCKET_NAME);
    aos_str_set(&object, OBJECT_NAME);
    headers = aos_table_make(p, 0);
    aos_str_set(&file, download_filename);

    s = oss_get_object_to_file(options, &bucket, &object, headers, 
                               params, &file, &resp_headers);
    if (aos_status_is_ok(s)) {
        printf("get object to local file succeeded\n");
    } else {
        printf("get object to local file failed\n");
    }

    aos_pool_destroy(p);
}

— —> 从源码和用户的源码对比发现有个 cname 的参数,这个参数用户端是 1,官网的源码是 0,参数的含义,是否启用 cname 方式上传, cname 简称别名,比如用户给 bucket 绑定一个备案的域名后,那个域名就是 cname;我们追下 cname 的用法。

    if (options->config->is_cname ||
        is_valid_ip(raw_endpoint_str))
    {
        req->host = apr_psprintf(options->pool, "%.*s",
                raw_endpoint.len, raw_endpoint.data);
        req->uri = apr_psprintf(options->pool, "%.*s", bucket->len,
                                bucket->data);
    } else {
        req->host = apr_psprintf(options->pool, "%.*s.%.*s",
                bucket->len, bucket->data,
                raw_endpoint.len, raw_endpoint.data);
        req->uri = apr_psprintf(options->pool, "%s", "");
    }

— —> 从这端代码中可以看到,启用了 cname 后,Host 直接取的 endpoint 值;基本上找到了和用户用了 cname 有关系;

  • 经过后台分析发现用户端并没有绑定 oss+域名 的映射关系,而 SDK 在启动了 cname 上传,把 endpoint 当做完成的域名,没有把用户的 bucket name 拼到 Host 中,认为启用 cname 后, endpoint 就是完成域名;(设计就是这样并不是缺陷)
  • 让用户把 cname 改为 0 ,采用直接的方式,这样 SDK 会把 bucketname 和 endpoint 拼成一个 完成域名再上传;

总结

该问题核心在于

1、要对 OSS SDK 的用法熟悉,知道如何进行本地对比用户测试;

2、要清楚排查思路知道搜集哪些有价值的信息;

3、层级递进,从签名算法着眼,推测到是代码中的使用;

4、善用 tcpdump/wireshark 抓包分析请求报文;

上一篇:使用Python在Windows 10上显示通知信息


下一篇:Android Studio Could not initialize class org.codehaus.groovy.runtime.InvokerHelper