本 SDK 使用符合 C89 标准的 C 语言实现。由于 C 语言的普适性,原则上此 SDK 可以跨所有主流平台,不仅可以直接在 C 和 C++ 的工程中使用,也可以用于与 C 语言交互性较好的语言中,例如 C#(使用 P/Invoke 交互)、Java(使用 JNI 交互)、Lua 等。
本开发指南假设开发者使用的开发语言是 C/C++,C-SDK 以开源方式提供。开发者可以从本文档提供的下载地址查看和下载 SDK 的源代码,并按自己的工程现状进行合理使用,例如编译为静态库或者动态库后进行链接,或者直接将 SDK 的源代码加入到自己的工程中一起编译,以保持工程设置的简单性。
从 v5.0.0 版本开始,我们对 SDK 的内容进行了精简。所有管理操作,
例如创建/删除 bucket,为 bucket 绑定域名 (publish),设置数据处理的样式分隔符,新增数据处理样式等都去除了,建议统一到
从内容上来说,C-SDK 主要包含如下几方面的内容:
公共部分,所有情况下都用到:qiniu/base.c、qiniu/conf.c、qiniu/http.c
客户端上传文件:qiniu/base_io.c、qiniu/io.c
客户端断点续上传:qiniu/base_io.c、qiniu/io.c、qiniu/resumable_io.c
数据处理:qiniu/fop.c
服务端操作:qiniu/auth_mac.c(授权),qiniu/rs.c(资源管理,上传凭证 uptoken/下载凭证 dntoken 生成)
cdn相关功能:qiniu/cdn.c
安装
C-SDK 使用cURL进行网络相关操作。无论是作为客户端还是服务端,都需要依赖 cURL。如果作为服务端,C-SDK 因为需要用 HMAC 进行数字签名做授权(简称签名授权),所以依赖了OpenSSL库。C-SDK 并没有带上这 2 个外部库,因此在使用 C-SDK 之前,首先确认您的当前开发环境中是否已经安装了这 2 个外部库,然后检查这 2 个外部库的头文件目录和库文件目录是否都已经加入到了项目工程的设置。
在主流的 *nix 环境下,通常cURL和OpenSSL都已经随系统默认安装到了 /usr/include 和 /usr/lib 目录下。如果您的系统还没有这些库,请自行安装。
如果你使用 gcc 进行编译,服务端典型的链接选项是:-lcurl -lssl -lcrypto -lm,客户端则是:-lcurl -lm。
如果在项目构建过程中出现环境相关的编译错误和链接错误,请确认这些选项是否都已经正确配置,以及所依赖的库是否都已经正确安装。
ACCESS_KEY 和 SECRET_KEY
如果你的服务端采用 C-SDK,那么使用 C-SDK 前,您需要拥有一对有效的 AccessKey 和 SecretKey 用来进行签名授权。可以通过如下步骤获得:
七牛开发者帐号
登录七牛开发者平台,
C-SDK 的 conf.h 文件中声明了对应的两个变量:QINIU_ACCESS_KEY 和 QINIU_SECRET_KEY。您需要在启动程序之初初始化这两个变量为七牛颁发的 AccessKey 和 SecretKey。
初始化
对于服务端而言,常规程序流程是:
Qiniu_Client client;
QINIU_ACCESS_KEY = "";
QINIU_SECRET_KEY = "";
Qiniu_Servend_Init(-1); /* 全局初始化函数,整个进程只需要调用一次 */
Qiniu_Client_InitMacAuth(&client, 1024, NULL); /* HTTP客户端初始化。HTTP客户端是线程不安全的,不要在多个线程间共用 */
...
Qiniu_Client_Cleanup(&client); /* 每个HTTP客户端使用完后释放 */
Qiniu_Servend_Cleanup(); /* 全局清理函数,只需要在进程退出时调用一次 */
对于客户端而言,常规程序流程是:
Qiniu_Client client;
Qiniu_Global_Init(-1); /* 全局初始化函数,整个进程只需要调用一次 */
Qiniu_Client_InitNoAuth(&client, 1024); /* HTTP客户端初始化。HTTP客户端是线程不安全的,不要在多个线程间共用 */
...
Qiniu_Client_Cleanup(&client); /* 每个HTTP客户端使用完后释放 */
Qiniu_Global_Cleanup(); /* 全局清理函数,只需要在进程退出时调用一次 */
两者主要的区别在于:
客户端没有 QINIU_ACCESS_KEY、QINIU_SECRET_KEY 变量(不需要初始化)。
客户端没有签名授权,所以初始化 Qiniu_Client 对象应该用 Qiniu_Client_InitNoAuth 而不是 Qiniu_Client_InitMacAuth。
客户端初始化/清理用 Qiniu_Global_Init/Cleanup,而服务端用 Qiniu_Servend_Init/Cleanup 这对函数。
内存管理
在 C-SDK 中,有一些函数会涉及到内存的动态分配。这些函数的一惯处理方式是在函数内部申请内存,并以指针的形式直接返回。这就要求函数调用者在得到指针后,需要在恰当的时机去释放这些内存。对于特殊的结构体,C-SDK 都会提供特定的函数来释放内存,例如 Qiniu_Buffer 提供了 Qiniu_Buffer_Cleanup 函数。而对于其他基本数据类型的指针,则由 Qiniu_Free 函数来负责释放不再使用的内存。
HTTP 客户端
在 C-SDK 中,HTTP 客户端叫 Qiniu_Client。在某些语言环境中,这个类是线程安全的,多个线程可以共享同一份实例,但在 C-SDK 中它被设计为线程不安全的。一个重要的原因是我们试图简化内存管理的负担。HTTP 请求结果的生命周期被设计成由 Qiniu_Client 负责,在下一次请求时会自动释放上一次 HTTP 请求的结果。如果某个 HTTP 请求结果的数据需要长期使用,建议您复制一份,如:
void stat(Qiniu_Client* client, const char* bucket, const char* key) {
Qiniu_RS_StatRet ret;
Qiniu_Error err = Qiniu_RS_Stat(client, &ret, bucket, key);
if (err.code != 200) {
debug(client, err);
return;
}
printf("hash: %s, fsize: %lld, mimeType: %s\n", ret.hash, ret.fsize, ret.mimeType);
}
这个例子中,Qiniu_RS_Stat 请求返回了 Qiniu_Error 和 Qiniu_RS_StatRet 两个结构体。其中的 Qiniu_Error 类型如下:
typedef struct _Qiniu_Error {
int code;
const char* message;
} Qiniu_Error;
Qiniu_RS_StatRet 类型如下:
typedef struct _Qiniu_RS_StatRet {
const char* hash;
const char* mimeType;
Qiniu_Int64 fsize;
Qiniu_Int64 putTime;
Qiniu_Int64 type;
} Qiniu_RS_StatRet;
**注意:*Qiniu_Error.message、Qiniu_RS_StatRet.hash、Qiniu_RS_StatRet.mimeType 都声明为 const char 类型,是个只读字符串,并不管理字符串内容的生命周期。
这些字符串什么时候失效?下次 Qiniu_Client 发生网络 API 请求时失效。如果您需要长久使用,建议您复制一份,如:
hash = strdup(ret.hash);
错误处理与调试
在 HTTP 请求出错的时候,C-SDK 统一返回了一个 Qiniu_Error 结构体:
typedef struct _Qiniu_Error {
int code;
const char* message;
} Qiniu_Error;
即一个错误码和对应的描述消息。这个错误码有可能是 cURL 的错误码,表示请求发送环节发生了意外,或者是一个 HTTP 错误码,表示请求发送正常,服务器端处理请求后返回了 HTTP 错误码。
如果一切正常,code 应该是 200,即 HTTP 的 OK 状态码。如果不是 200,则需要对 code 值进行相应分析。对于低于 200 的值,可以查看cURL错误码,否则应查看七牛HTTP状态码。
如果 message 指示的信息还不够友好,也可以尝试把整个 HTTP 返回包打印出来看看:
void debug(Qiniu_Client* client, Qiniu_Error err)
{
printf("\nerror code: %d, message: %s\n", err.code, err.message);
printf("respose header:\n%s", Qiniu_Buffer_CStr(&client->respHeader));
printf("respose body:\n%s", Qiniu_Buffer_CStr(&client->b));
}
文件上传
上传流程
在七牛云存储中,整个上传流程大致分为以下几步:
业务服务器颁发上传凭证给客户端(终端用户)
客户端凭借上传凭证上传文件到七牛
在七牛获得完整数据后,发起一个 HTTP 请求回调到业务服务器
业务服务器保存相关信息,并返回一些信息给七牛
七牛原封不动地将这些信息转发给客户端(终端用户)
**注意:**回调到业务服务器的过程是可选的,它取决于业务服务器颁发的上传凭证。如果没有回调,七牛会返回一些标准的信息(例如文件的 hash)给客户端。
如果上传发生在业务服务器,以上流程简化为:
业务服务器生成上传凭证
凭借上传凭证上传文件到七牛
后续工作,例如保存一些相关信息
上传凭证
上传凭证实际上是用 AccessKey/SecretKey 进行数字签名的上传策略 Qiniu_RS_PutPolicy,它控制着整个上传流程的行为。具体上传策略参数的解释可见 上传凭证
typedef struct _Qiniu_RS_PutPolicy {
const char *scope;
const char *saveKey;
Qiniu_Uint32 isPrefixalScope;
const char *callbackUrl;
const char *callbackHost;
const char *callbackBody;
const char *callbackBodyType;
const char *callbackFetchKey;
const char *returnUrl;
const char *returnBody;
const char *endUser;
const char *persistentOps;
const char *persistentNotifyUrl;
const char *persistentPipeline;
const char *mimeLimit;
Qiniu_Uint64 fsizeLimit;
Qiniu_Uint64 fsizeMin;
Qiniu_Uint32 detectMime;
Qiniu_Uint32 insertOnly;
Qiniu_Uint32 expires;
Qiniu_Uint32 deleteAfterDays;
Qiniu_Uint32 fileType;
} Qiniu_RS_PutPolicy;
在构建上传凭证之前,我们首先要定义鉴权对象Qiniu_mac
Qiniu_Mac mac;
mac.accessKey = "your access key";
mac.secretKey = "your secret key";
简单上传的凭证
最简单的上传凭证只需要AccessKey,SecretKey和Bucket就可以。
//简单上传凭证
char *bucket="your bucket name";
Qiniu_RS_PutPolicy putPolicy;
Qiniu_Zero(putPolicy);
putPolicy.scope = bucket;
char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac);
printf("simple:\t%s\n\n", uptoken);
Qiniu_Free(uptoken);
默认情况下,在不指定上传凭证的有效时间情况下,默认有效期为1个小时。也可以自行指定上传凭证的有效期,例如:
//自定义凭证有效期(示例2小时,expires单位为秒,为上传凭证的有效时间)
Qiniu_RS_PutPolicy putPolicy;
Qiniu_Zero(putPolicy);
putPolicy.scope = bucket;
putPolicy.expires = 7200; //单位秒
char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac);
printf("returnBody:\t%s\n\n", uptoken);
覆盖上传的凭证
覆盖上传除了需要简单上传所需要的信息之外,还需要想进行覆盖的文件名称,这个文件名称同时可是客户端上传代码中指定的文件名,两者必须一致。
//覆盖上传
Qiniu_RS_PutPolicy putPolicy;
Qiniu_Zero(putPolicy);
char *keyToOverwrite = "qiniu.png";
putPolicy.scope = Qiniu_String_Concat3(bucket, ":",keyToOverwrite);
char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac);
printf("overwrite:\t%s\n\n", uptoken);
自定义上传回复的凭证
默认情况下,文件上传到七牛之后,在没有设置returnBody或者回调相关的参数情况下,七牛返回给上传端的回复格式为hash和key,例如:
{"hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","key":"qiniu.jpg"}
有时候我们希望能自定义这个返回的JSON格式的内容,可以通过设置returnBody参数来实现,在returnBody中,我们可以使用七牛支持的魔法变量和自定义变量。
//自定义返回值
Qiniu_RS_PutPolicy putPolicy;
Qiniu_Zero(putPolicy);
putPolicy.scope = bucket;
putPolicy.returnBody = "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"fsize\":$(fsize),\"bucket\":\"$(bucket)\",\"name\":\"$(x:name)\"}";
char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac);
printf("returnBody:\t%s\n\n", uptoken);
则文件上传到七牛之后,收到的回复内容如下:
{"key":"qiniu.jpg","hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","bucket":"if-bc","fsize":39335,"name":"qiniu"}
带回调业务服务器的凭证
上面生成的自定义上传回复的上传凭证适用于上传端(无论是客户端还是服务端)和七牛服务器之间进行直接交互的情况下。在客户端上传的场景之下,有时候客户端需要在文件上传到七牛之后,从业务服务器获取相关的信息,这个时候就要用到七牛的上传回调及相关回调参数的设置。
Qiniu_RS_PutPolicy putPolicy;
Qiniu_Zero(putPolicy);
putPolicy.scope = bucket;
putPolicy.callbackUrl = "http://api.example.com/upload/callback";
putPolicy.callbackBodyType = "application/json";
putPolicy.callbackBody = "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"fsize\":$(fsize),\"bucket\":\"$(bucket)\",\"name\":\"$(x:name)\"}";
char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac);
printf("callback(json):\t%s\n\n", uptoken);
在使用了上传回调的情况下,客户端收到的回复就是业务服务器响应七牛的JSON格式内容。
通常情况下,我们建议使用application/json格式来设置callbackBody,保持数据格式的统一性。实际情况下,callbackBody也支持application/x-www-form-urlencoded格式来组织内容,这个主要看业务服务器在接收到callbackBody的内容时如何解析。例如:
Qiniu_RS_PutPolicy putPolicy;
Qiniu_Zero(putPolicy);
putPolicy.scope = bucket;
putPolicy.callbackUrl = "http://api.example.com/upload/callback";
//putPolicy.callbackBodyType = "application/json";
putPolicy.callbackBody ="key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)" ;
char *uptoken = Qiniu_RS_PutPolicy_Token(&putPolicy, &mac);
printf("callback(