回调通知是指客户端在上传时指定服务端在处理完上传请求后,应该通知某个特定服务器,在该服务器确认接收了该回调后才将所有结果返回给客户端。
因为加入了回调请求和响应的过程,相比简单上传,使用回调通知机制一般会导致客户端花费更多的等待时间。
开发者可以要求七牛云存储在某文件上传完成后向特定的 URL 发起一个回调请求。七牛云存储会将该回调的响应内容作为文件上传响应的一部分一并返回给客户端。回调的流程如下:
要启用回调功能,业务服务器在签发上传凭证时需要设置上传策略 (PutPolicy)中的 callbackUrl 和 callbackBody 字段。
上边是七牛提供的一个常规做法。向七牛服务器上传文件完成后,七牛主动通知给个人业务服务器。当然,也可以在客户端收到返回消息后,再调用个人业务服务器。
回调地址
通过设定上传策略中的 callbackUrl 字段为一个有效的地址,即可让七牛云存储在文件上传完成后向该地址发起回调请求。该地址可以是一个 HTTP 或者 HTTPS 的 URL,允许公网访问。
如果需要传递自定义的请求内容,开发者可以考虑配合使用上传策略中的 callbackBody 字段。如果只有 callbackUrl 而没有 callbackBody,回调服务器收到的请求内容将为空。
一般,回调地址在业务服务器上设置,是 token 的一部分。如(以CI框架为例):
$this->load->library('Qiniu');
$auth = new Qiniu\Auth($accessKey, $secretKey);
$bucket = 'qiubg';
$uid = 1000000;
$policy = array(
'callbackUrl' => '//www.mlxiu.com/upload/callback',
'callbackBody' => '{"fname":"$(fname)", "fkey":"$(key)", "desc":"$(x:desc)", "uid":' . $uid . '}'
);
$token = $auth->uploadToken($bucket, null, 3600, $policy);
token 字符串分为三段,以 “:” 分割,base64解码第二个 “:”后的字符串,可以查看上传策略。
这里不仅设置了回调地址,还设置了七牛服务器向业务服务器发送的回调内容。混合使用了魔法变量 fname, fkey 以及自定义变量 desc 还有常量 uid 。
使用回调还有第三个变量 callbodytype 。 其默认是 application/x-www-form-urlencoded, 也可在上传策略中设置为 application/json 。
客户端表单
一种方法是直接写在input表单中,如下:
<form method="post" action="//www.mlxiu.com/upload/upload" enctype="multipart/form-data">
<input name="fname" value="xx-xxx-xxxx.jpg">
<input name="key" type="hidden" value="sunflower.jpg">
<input name="x:desc" value="在雨中的山谷,不期与你相遇">
<input name="token" type="hidden" value="xxx">
<input name="file" type="file" />
</form>
还有一种方法,将数据填充到 FormData 中,如下:
var formData = new FormData();
formData.append('token', token);
formData.append('file', file);
formData.append('fname', 'xx-xxx-xxxx.jpg');
formData.append('key', 'sunflower.jpg');
formData.append('x:desc', '在雨中的山谷,不期与你相遇');
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://up.qbox.me', true);
xhr.upload.addEventListener('progress', function(e){
var percent = Math.round( e.loaded * 100 / e.total ) + "%";
console.log('uploaded percent:' + percent);
}, false);
xhr.onreadystatechange = function(response){
if(xhr.readyState == 4 && xhr.status == 200 && xhr.responseText != ""){
console.log('上传完成:' + xhr.responseText);
}
}
xhr.send(formData);
FormData 是 XMLHttpRequest 的二代产物,了解更多,请参考:
http://www.zhangxinxu.com/wordpress/2013/10/understand-domstring-document-formdata-blob-file-arraybuffer/
话说,填充的数据,都会返回。
服务端接受
先不看服务器具体怎么接受。因为回调地址是公网可以任意访问的,那怎么确认回调的合法性呢。
七牛云存储在回调时会对请求数据签名,并将结果包含在请求头 Authorization 字段中,示例如下:
Authorization:QBox iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV:tDK-3f5xF3SJYEAwsll5g=
其中 QBox 为固定值,iN7Ngw…dCV 为用户的 Accesskey,tDK-3f…5g= 为签名结果 encoded_data。
业务服务器先对请求进行验证,再做相关操作。比如存到数据库。
$callbackBody = file_get_contents('php://input');
$contentType = 'application/x-www-form-urlencoded';
if (strstr($_SERVER["SERVER_SOFTWARE"],"Apache")){
$data=apache_request_headers();
$authorization = $data['Authorization'];
}else{
$authorization = $_SERVER['HTTP_AUTHORIZATION'];
}
$url = '//www.mlxiu.com/upload/callback';
$this->load->library('Qiniu');
$auth = new Qiniu\Auth($accessKey, $secretKey);
$isQiniuCallback = $auth->verifyCallback($contentType, $authorization, $url, $callbackBody);
if($isQiniuCallback == TRUE)
{
$data = json_encode($callbackBody);
echo $data;
}
else
{
echo json_encode(array('error' => '校验不通过!'));
}
服务器通知到回调后,会再返回给客户端。到此,整个流程完毕。
参考
上传策略: https://developer.qiniu.com/kodo/manual/1206/put-policy
PHP-SDK: https://developer.qiniu.com/kodo/sdk/1241/php
回调及鉴权:https://developer.qiniu.com/kodo/kb/1409/seven-cattle-callback-and-callback-authentication