七牛云存储使用中一些常见问题的解决经验
599错误处理
如果在与七牛的交互中出现http状态码为599的错误,一句话,不要犹豫,直接联系七牛技术支持。七牛的文档也在很多地方提到这个错误,都是指导大家去联系技术支持的。笔者是在分块上传后的mkfile调用时出现的,联系技术支持后,说是调整了一下,让我重试。后来就好了…
分块上传无法从回调中获得文件的原始名
简单上传采用的是multipart/form-data方式上传,七牛服务端能够从请求中获得文件的原始名,并支持使用魔法变量$(fname)回调业务服务器。不过当使用分片上传的时候情况有所不同。分片上传需要在最后调用mkfile,来将分片拼接起来。但是,mkfile接口支持普通的请求,并没有附带文件名,所以七牛也就无法获得文件名,此时从$(fname)中是取不到文件名的。这个问题我也向七牛技术支持提交了问题,得到的结果是使用自定义变量mkfile支持将自定义变量放在url中,回调的时候自定义变量可以传递给业务服务器。
慎用图片预处理
七牛云支持很多对文件的预处理,其中最常用的应该就是图片预处理了,可以对图片的大小做变换等。七牛推荐使用get的方式直接指定图片处理结果的url,像这样:
http://qiniuphotos.qiniudn.com/gogopher.jpg?imageview2/1/w/200/h/200
处理后的图片会自动缓存,用户不用关心,只要每次访问都用这个url就行了。然而,笔者在开始的时候,为了保持与其他文件形式统一的处理方法,对图片使用了预处理(因为视频什么的只能预处理),即在token中指定了预处理。此时问题出现了,从后台的日志看到,图片的预处理通知回调竟然比正常的上传成功回调还要快!这就导致预处理结果到来之前,我的业务服务器的数据库中还没有这个图片,无法保存预处理结果了。所以推荐还是使用url直接处理,对图片要慎用预处理
视频文件无法快进播放
通常用户在观看视频的时候都会根据自己的喜好,快速将视频定位到指定的时间播放。实现这个功能,需要视频本身有关键帧信息、服务端需要支持关键帧播放请求。
但是笔者发现,在使用七牛云转化后的视频,这样做是无效的。于是咨询技术支持,得到的答案是:转化的文件是具有关键帧的,但七牛使用cdn加速,所以关键帧请求需要cdn的支持,如果想要用这个功能的话,需要单独联系销售或技术支持在cdn上配置,而且时间比较长。笔者联系了销售和技术支持,说是帮我配置,但到现在还没有搞定,因为最近这个也不是特别重要,所以也没有跟下去。
callback校验
这是可选的一个步骤。由于七牛云会在上传完成之后回调业务服务器,所以理论上说业务服务器需要校验这个回调的合理性。原理在七牛的文档中有,需要用到hmac-sha1签名函数。但是七牛的sdk中没有提供直接的方式来做校验,在研读文档、多次失败和查看sdk源码后,笔者终于校验成功了。关键的分歧在于,文档中的这句话:
获取明文:data = request.url.path +”\n” +request.body
这里的request.url.path是否包含querystring?答案是包含的!下面是笔者c#服务端的校验代码,使用的是asp.net web api:
- byte[] key = system.text.encoding.utf8.getbytes(qiniu.conf.config.secret_key);
- using (hmacsha1 hmac = new hmacsha1(key))
- {
- var t = filtercontext.request.content.readasstringasync();
- t.wait();
- string rawbody = t.result;
- log.debugformat("request's rawbody : {0}", rawbody);
- string text = filtercontext.request.requesturi.pathandquery + "\n" + rawbody;
- log.debugformat("pathandquery + \\n + rawbody : {0}", text);
- byte[] digest = hmac.computehash(system.text.encoding.utf8.getbytes(text));
- string computed = qiniu.util.base64urlsafe.encode(digest);
- log.debugformat("computed hash after base64 : {0}", computed);
- ienumerable<string> auths;
- if (filtercontext.request.headers.trygetvalues("authorization", out auths) && auths.count() == 1)
- {
- string auth = auths.first();
- log.debugformat("authorization in header : {0}", auth);
- if (auth.startswith("qbox "))
- {
- var arr = auth.substring(5).split(':');
- if (arr.length == 2)
- {
- if (arr[1] != computed)
- {
- log.errorformat("authorization failed. since auth from header {0} not equals computed {1}", arr[1], computed);
- }
- else
- {
- log.debug("authorization success.");
- //only pass can be return
- return;
- }
- }
- else
- {
- log.error("callback authorization's format is invalid, can not find two part after split by ':'.");
- }
- }
- else
- {
- log.error("callback authorization's format is invalid, missing leading 'qbox '.");
- }
- }
- else
- {
- log.error("the request from qiniu callback is missing 'authorization'");
- }
- filtercontext.response = filtercontext.request.createresponse(system.net.httpstatuscode.forbidden);
- }
如下几个注意点:
明文应当是请求的path+querystring部分和rawbody
对于.net而言,明文和key都需要用utf-8编码变换成字节才能进行签名。而php中的hash_hmac函数完全不用这么复杂…
签名的结果再用base64的url安全的方式编码,再与请求的http头部的authorization比较
建议官方在文档中加入一些相对底层一些的编程语言的实现,php太高端了…
js-sdk实现略显粗糙
在使用过程中,我发现有几个我觉得不好的地方:
不能为每个文件获取uptoken
试想,在文件上传过程中有获取uptoken是必须的,而且uptoken又需要包含预处理指令,不同的文件显然需要不同的uptoken,而在js-sdk的实现中,只在初始化这个上传组件对象的时候请求一次上传凭证,后面所有的上传都需要使用这个预先得到的uptoken:
- uploader.bind('init', function(up, params) {
- getuptoken();
- });
只能实现分片上传,无法断点续传
js-sdk的实现在分片上传的实现上,是很简单的,不仅没有使用分片,而是分块(一块4m,调用mkblk),而且没有实现持久化ctx,或者类似的回调或接口。4m分块这个问题还可以不追究,没有实现持久化ctx就说不过去了,不持久化怎么实现断点续传撒?!就算不实现,也应该给出回调的入口,让调用者来实现持久化,而我实在无法找到这个’空子’可钻,只能直接在源码上改动了。
没有复用流行类库的东西
这个其实算不上问题,因为作为一个不依赖jquery的sdk,当然不能使用jquery现成的东西,比如ajax。不依赖jquery就算了,依赖plupload是几个意思嘛,还依赖全局对象…于是最后,我干脆自己将sdk改成了backbone的类,将不要的东西统统去掉,使用jquery和underscore简化代码了…