## 简单反馈
如果资源上传成功,服务端会响应HTTP 200返回码,且在响应内容中包含两个字段:
* `hash`:已上传资源的校验码,供用户核对使用。
* `key`:目标资源的最终名字,可由七牛云存储自动命名;
以下是一个典型的上传成功反馈:
~~~
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"hash": "Fh8xVqod2MQ1mocfI4S4KpRL6D98",
"key": "gogopher.jpg",
}
~~~
如果资源上传失败,服务端会反馈相应的错误信息。比如,HTTP 401代表验证失败。此时相应内容中会包含详细错误信息。错误信息同样为JSON格式:{“error”:””}
以下是一个典型的上传失败反馈:
~~~
HTTP/1.1 400 Bad Request
Date: Mon, 05 Aug 2013 13:56:34 GMT
Server: nginx/1.0.14
Content-Type: application/json
Access-Control-Allow-Origin: *
Content-Length: 28
X-Log: MC;SBD:10;RBD:11;BDT:12;FOPD/400;FOPG:63/400;IO:109/400
X-Reqid: -RIAAIAI8UjcgRcT
X-Via: 1.1 jssq179:8080 (Cdn Cache Server V2.0), 1.1 jsyc96:9080 (Cdn Cache Server V2.0)
Connection: close
{
"error":"invalid argument"
}
~~~
这些返回的错误信息可以帮助开发者分析问题原因。完整的返回码信息请参见[返回码](http://developer.qiniu.com/docs/v6/api/reference/codes.html)。
从上面的错误示例中可以看到,响应头中还包含了一些以`X-`为前缀的扩展字段,如`X-Reqid`和`X-Log`等。这些扩展信息非常有助于问题定位。我们建议开发者将所有接收到的错误信息写到日志中,以便于我们的技术支持人员在协助分析问题时有足够详细的线索。
关于这些扩展字段的详细描述,请参见[HTTP扩展字段](http://developer.qiniu.com/docs/v6/api/reference/extended-headers.html)。
## 回调(callback)
开发者可以要求七牛云存储在某文件上传完成后向特定的URL发起一个回调请求。七牛云存储会将该回调的响应内容作为文件上传响应的一部分一并返回给客户端。回调的流程如下图所示:
![带回调的上传流程](http://developer.qiniu.com/docs/v6/api/overview/up/response/img/upload-with-callback.png)
要启用回调功能,业务服务器在签发上传凭证时需要设置[上传策略](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)中的`callbackUrl`字段,并且设置`callbackBody`字段。
### 回调地址
通过设定上传策略中的`callbackUrl`字段为一个有效的地址,即可让七牛云存储在文件上传完成后向该地址发起回调请求。
该地址可以是一个HTTP或者HTTPS的URL,允许公网访问。
如果需要传递自定义的请求内容,开发者可以考虑配合使用上传策略中的`callbackBody`字段。如果只有`callbackUrl`而没有`callbackBody`,回调服务器收到的请求内容将为空。
### 回调内容
同普通客户端直传和重定向上传一样,用户也可以控制回调中传递到客户回调服务器的反馈信息。`callbackBody`的格式如下:
~~~
<item>=(<magicvar>|<xvar>)[&<item>=(<magicvar>|<xvar>)...]
~~~
一个典型的`callbackBody`设置如下:
~~~
put_policy = '{
...
"callbackBody" : "name=$(fname)&hash=$(etag)&location=$(x:location)\
&price=$(x:price)&uid=123"
...
}'
~~~
上面的 `callbackBody` 示例中,混合使用了魔法变量(name,hash)、自定义变量(location,price)及自定义常量(uid)。 假设应用客户端发出了如下的上传请求:
~~~
<form method="post" action="http://upload.qiniu.com/" enctype="multipart/form-data">
<input name="key" type="hidden" value="sunflower.jpg">
<input name="x:location" value="Shanghai">
<input name="x:price" value="1500.00">
<input name="token" type="hidden" value="...">
<input name="file" type="file" />
</form>
~~~
其中,客户端发送了自定义变量的值`x:location = Shanghai`和`x:price = 1500.00`,七牛云存储将根据上传资源的实际情况填写魔法变量`$(fname)`和`$(etag)`的值。
完成上传后,七牛云存储便会构造出如下的回调信息:
~~~
name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm- \
RlQx_4O2&location=Shanghai&price=1500.00&uid=123
~~~
七牛云存储将这组数据作为请求Body发送至用户指定的回调服务器,请求方式为POST。回调服务器将接收到以下格式的请求内容:
~~~
POST /callback HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: qiniu go-sdk v6.0.0
Host: api.examples.com
Authorization: QBox iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV:tDK-3f5xF3SJYEAwsll5g=
name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm- \
RlQx_4O2&location=Shanghai&price=1500.00&uid=123
~~~
回调服务器接收到回调请求后,负责生成七牛返回给客户端的数据(json格式),该数据作为此次回调请求的响应内容。如果回调成功,回调服务应对七牛云存储作出类似如下的响应(注意:回调响应内容由回调服务生成,以下仅作为示例):
~~~
HTTP/1.1 200 OK
Server: nginx/1.1.19
Date: Thu, 19 Dec 2013 06:27:30 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
{"success":true,"name":"sunflowerb.jpg"}
~~~
七牛云存储将上面的回调结果返回给客户端,客户端接收到以下回应:
~~~
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Server: nginx/1.4.4
Date: Thu, 19 Dec 2013 08:04:56 GMT
Pragma: no-cache
X-Log: BDT:4;BDT:2;LBD:13;rs.put:1048;rs-upload.putFile:2514;UP.CB:3088;UP:5603
X-Reqid: iDYAAPBicOGXLUET
{"success":true,"name":"sunflowerb.jpg"}
~~~
如果回调失败,七牛云存储会将返回给应用客户端[HTTP状态码579](http://developer.qiniu.com/docs/v6/api/reference/codes.html)以及详细的失败信息。
### 安全性
由于回调地址是公网可任意访问的,回调服务如何确认一次回调是合法的呢?
七牛云存储在回调时会对请求数据签名,并将结果包含在请求头Authorization字段中,示例如下:
~~~
Authorization:QBox iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV:tDK-3f5xF3SJYEAwsll5g=
~~~
其中`QBox`为固定值,`iN7Ngw...dCV`为用户的Accesskey,`tDK-3f...5g=`为签名结果(encoded_data)
回调服务器可以通过以下方法验证其合法性:
* 获取AUTHORIZATION字段值中的签名结果部分encoded_data
* 根据Accesskey选取正确的SecretKey
* 获取明文:data = Request.URL.Path +”\n” +Request.Body
* 部分语言或框架无法直接获取请求body的原始数据,在自行拼接时应当注意,body中的数据是经过[URL转义](http://zh.wikipedia.org/wiki/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81)的
* 采用HMAC-SHA1签名算法,对明文data签名,秘钥为SecretKey,比较签名结果是否与Authorization中的encoded_data字段相同,如相同则表明这是一个合法的回调请求
以PHP语言为示例,验证代码如下:
~~~
/**
*C('accessKey')取得 AccessKey
*C('secretKey')取得 SecretKey
*callback.php 为回调地址的Path部分
*file_get_contents('php://input')获取RequestBody,其值形如:
*name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2\
*&location=Shanghai&price=1500.00&uid=123
*/
function IsQiniuCallback(){
$authstr = $_SERVER['HTTP_AUTHORIZATION'];
if(strpos($authstr,"QBox ")!=0){
return false;
}
$auth = explode(":",substr($authstr,5));
if(sizeof($auth)!=2||$auth[0]!=C('accessKey')){
return false;
}
$data = "/callback.php\n".file_get_contents('php://input');
return URLSafeBase64Encode(hash_hmac('sha1',$data,C("secretKey"), true)) == $auth[1];
}
~~~
注意:如果回调数据包含用户的敏感数据,建议回调地址使用HTTPS协议
## 303重定向
HTTP 303重定向(参见[RFC 2616 - 10.3.4](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4))是HTTP 1.1规范的组成部分。
服务器可以通过返回303状态码告诉客户端,本次请求的内容可以通过返回的跳转URL取到,因此客户端应该重定向到新的URL。
该技术被广泛用于网页开发领域,如在文件上传完成后让客户端自动重定向到一个上传成功的结果页面。
七牛云存储的资源上传后续动作也支持303重定向功能。
在构造[上传凭证](http://developer.qiniu.com/docs/v6/api/reference/security/upload-token.html)时,开发者可以通过设置[上传策略](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)中的`returnUrl`参数以激活303重定向功能。在成功完成上传后,服务端会向客户端返回HTTP 303状态码,并在`Location`字段中携带上传时指定的重定向地址。如下所示:
~~~
HTTP/1.1 303 See Other
Location: <returnUrl>
~~~
客户端收到这样的反馈后,应按HTTP 1.1标准将当前页面重定向到`Location`字段所指定的URL。
主流浏览器都能正确的支持该跳转操作。
如果上传策略中还设定了自定义返回内容`returnBody`,则到时服务端返回的303响应中的`Location`字段也会包含自定义返回内容。参数值采用[URL安全的Base64编码](http://developer.qiniu.com/docs/v6/api/overview/appendix.html#urlsafe-base64)。此时的响应内容如下所示:
~~~
HTTP/1.1 303 See Other
Location: <returnUrl>?upload_ret=<encoded_return_body>
~~~
如果希望返回的自定义返回内容能得到正确处理,重定向URL所对应的服务器需支持请求参数`upload_ret`。
## 自定义响应内容(returnBody)
[简单反馈](http://developer.qiniu.com/docs/v6/api/overview/up/response/simple-response.html)只会包含资源的最基本信息,但很多情况下用户都希望得到更多的资源信息。
用户可以通过[资源管理](http://developer.qiniu.com/docs/v6/api/overview/rs/index.html)和[数据处理](http://developer.qiniu.com/docs/v6/api/overview/fop/index.html)功能获得资源的扩展信息。但这些都需要用户发起一个新请求。七牛云存储支持在上传请求的响应中直接返回客户端需要的资源扩展信息。
在生成[上传凭证](http://developer.qiniu.com/docs/v6/api/reference/security/upload-token.html)时,开发者可以通过设置[上传策略](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)中的`returnBody`字段指定需要返回的信息,比如资源的大小、类型,图片的尺寸等等。
`returnBody`实际上是一个用户定义的反馈信息模板,**内容必须用JSON格式表达**。下面是一个示例:
~~~
{
"foo": "bar",
"name": $(fname),
"size": $(fsize),
"type": $(mimeType),
"hash": $(etag),
"w": $(imageInfo.width),
"h": $(imageInfo.height),
"color": $(exif.ColorSpace.val)
}
~~~
用户可以在在`returnBody`中使用[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html),包括[魔法变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#magicvar)和[自定义变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar)。七牛云存储会将这些变量替换为对应实际值,然后作为响应内容反馈给用户,如下所示:
~~~
{
"foo": "bar",
"name": "gogopher.jpg",
"size": 214513,
"type": "image/jpg",
"hash": "Fh8xVqod2MQ1mocfI4S4KpRL6D98",
"w": 640,
"h": 480,
"color": "sRGB"
}
~~~
需要注意的是,`returnBody`不能在启用了回调时使用。如果上传策略中通过设置`callbackUrl`字段启用了回调,`returnBody`将直接被忽略。
在回调模式中如果也想自定义响应内容,请在生成上传凭证时设置上传策略中的`callbackBody`字段。更多关于回调模式的解释,请参见[回调(callback)](http://developer.qiniu.com/docs/v6/api/overview/up/response/callback.html)。
## 变量
变量是七牛云存储同用户交换数据的机制,引入变量概念的目的在于更灵活的控制上传后续动作中的内容组织和传递。可以认为变量是一种占位符,七牛云存储会将占位符按约定替换为实际内容。
在构造[上传策略](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)时,可在上传策略的`returnBody`和`callbackBody`字段内容中使用变量。
变量分为两种:[魔法变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#magicvar)和[自定义变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar)。魔法变量是系统提供的一系列预定义变量,可直接使用,而自定义变量则由调用方指定,通常应对应于上传时的表单参数。服务端会将这些上传参数的具体值返回给调用方。
### 魔法变量
魔法变量是一组预先定义的变量,可以使用 `$(var)` 或 `$(var.field_name)` 形式求值。
目前可用的魔法变量如下:
| 变量名 | 包含子项 | 变量说明 | 适用范围 |
| :-- | :-- | --- | --- |
| bucket | 获得上传的目标空间名。 |
| key | 获得文件保存在空间中的资源名。 |
| etag | 文件上传成功后的[Etag](http://en.wikipedia.org/wiki/HTTP_ETag)。若上传时未指定资源ID,Etag将作为资源ID使用。 |
| fname | 上传的原始文件名。 | 不支持用于`分片上传` |
| fsize | 资源尺寸,单位为字节。 |
| mimeType | 资源类型,比如JPG图片的资源类型为`image/jpg`。 |
| endUser | 上传时指定的`endUser`字段,通常用于区分不同终端用户的请求。 |
| persistentId | 音视频转码持久化的进度查询ID。 |
| exif | 是 | 获取所上传图片的[EXIF](http://en.wikipedia.org/wiki/Exchangeable_image_file_format)信息。
该变量包含子字段,比如对`$(exif.ApertureValue.val)`取值将得到该图片拍摄时的光圈值。
| 暂不支持用于`saveKey`中 |
| imageInfo | 是 | 获取所上传图片的基本信息。
该变量包含子字段,比如对`$(imageInfo.width)`取值将得到该图片的宽度。
| 暂不支持用于`saveKey`中 |
| year | 上传时的年份。 | 暂不支持用于’returnBody’、’callbackBody’中 |
| mon | 上传时的月份。 | 暂不支持用于’returnBody’、’callbackBody’中 |
| day | 上传时的日期。 | 暂不支持用于’returnBody’、’callbackBody’中 |
| hour | 上传时的小时。 | 暂不支持用于’returnBody’、’callbackBody’中 |
| min | 上传时的分钟。 | 暂不支持用于’returnBody’、’callbackBody’中 |
| sec | 上传时的秒钟。 | 暂不支持用于’returnBody’、’callbackBody’中 |
| avinfo | 是 | 音视频资源的元信息。 | 暂不支持用于’saveKey’中 |
| imageAve | 图片主色调,算法由[Camera360](http://hr.camera360.com/)友情提供。 |
| ext | 上传资源的后缀名,通过自动检测的 mimeType 或者原文件的后缀来获取。 | 不支持用于`分片上传` |
| uuid | 生成uuid | 暂不支持用于’saveKey’中 |
| bodySha1 | callbackBody的sha1(hex编码) | 只支持用于’callbackUrl’中 |
魔法变量支持`$(.)`形式的访问子项,例如:
* $()
* $(.)
* $(..)
求值举例:
* `$(bucket)` - 获得上传目标bucket名字
* `$(imageInfo)` - 获取当前上传图片的基本属性信息
* `$(imageInfo.height)` - 获取当前上传图片的原始高度
魔法变量不支持访问子项为数组形式
* **不支持**$([0])
* **不支持**$(.[0])
变量`avinfo`在[`returnBody`](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html#put-policy-return-body)中返回的格式不同于url触发返回的`avinfo`格式,`avinfo`在[魔法变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#magicvar)中的类型如下(内容经过格式化以便阅读):
~~~
{
"audio":{
"bit_rate":"64028",
"channels":1,
"codec_name":"mp3",
"codec_type":"audio",
"duration":"30.105556",
"index":1,
"nb_frames":"1153",
"r_frame_rate":"0/0",
"sample_fmt":"s16p",
"sample_rate":"44100",
"start_time":"0.000000",
"tags":{
"creation_time":"2012-10-21 01:13:54"
}
},
"format":{
"bit_rate":"918325",
"duration":"30.106000",
"format_long_name":"QuickTime / MOV",
"format_name":"mov,mp4,m4a,3gp,3g2,mj2",
"nb_streams":2,
"size":"3455888",
"start_time":"0.000000",
"tags":{
"creation_time":"2012-10-21 01:13:54"
}
},
"video":{
"bit_rate":"856559",
"codec_name":"h264",
"codec_type":"video",
"display_aspect_ratio":"4:3",
"duration":"29.791667",
"height":480,
"index":0,
"nb_frames":"715",
"pix_fmt":"yuv420p",
"r_frame_rate":"24/1",
"sample_aspect_ratio":"1:1",
"start_time":"0.000000",
"tags":{
"creation_time":"2012-10-21 01:13:54"
},
"width":640
}
}
~~~
变量`exif`的类型如下(内容经过格式化以便阅读,具体细节请参考[EXIF技术白皮书](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf)):
~~~
{
"DateTime" : {
"type" : 2,
"val" : "2011:11:19 17:09:23"
},
"ExposureBiasValue" : {
"type" : 10,
"val" : "0.33 EV"
},
"ExposureTime" : {
"type" : 5,
"val" : "1/50 sec."
},
"Model" : {
"type" : 2,
"val" : "Canon EOS 600D"
},
"ISOSpeedRatings" : {
"type" : 3,
"val" : "3200"
},
"ResolutionUnit" : {
"type" : 3,
"val" : " 英寸"
},
...后续内容已省略...
}
~~~
变量`imageInfo`对应的类型如下(内容经过格式化以便阅读):
~~~
{
"format": "jpeg",
"width": 640,
"height": 427,
"colorModel": "ycbcr"
}
~~~
如果变量取值失败(比如在上传策略中指定了一个并不存在的魔法变量),响应内容中对应的变量将被赋予空值。
### 自定义变量
应用客户端则在上传请求中设定自定义变量的值。七牛云存储收到这些变量信息后,置换掉`returnBody`和`callbackBody`中的自定义变量设置,形成最终的反馈结果。
自定义变量的行为同魔法变量基本一致,但变量名必须以`x:`开始。下面是一个自定义变量的示例:
用户设置了如下的`callbackBody`:
~~~
put_policy = '{
...
"callbackBody" : "name=$(fname)&hash=$(etag)&location=$(x:location)&price=$(x:price)"
...
}'
~~~
这个例子中的`$(x:location)`和`$(x:price)`就是自定义变量。
之后,用户的客户端构造了如下请求:
~~~
<form method="post" action="http://upload.qiniu.com/"
enctype="multipart/form-data">
<input name="key" type="hidden" value="sunflower.jpg">
<input name="x:location" type="hidden" value="Shanghai">
<input name="x:price" type="hidden" value="1500.00">
<input name="token" type="hidden" value="...">
<input name="file" type="file" />
</form>
~~~
文件上传完成后,服务端会将请求中`x:location`和`x:price`的值,替换`callbackBody`中的自定义变量:
~~~
name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm- \
RlQx_4O2&location=Shanghai&price=1500.00
~~~
然后,七牛云存储将此结果进行[URL安全的Base64编码](http://developer.qiniu.com/docs/v6/api/overview/appendix.html#urlsafe-base64),作为回调请求的Body调用`callbackUrl`指定的回调服务器。
如果变量取值失败(比如在上传策略中指定了一个并不存在的表单变量),响应内容中对应的变量将被赋予空值。
## 数据预处理
在生成上传凭证时,开发者可以通过在[上传策略](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)中指定`persistentOp`和`persistentNotifyUrl`字段来设置异步数据处理动作。当资源上传完成后,设置的数据处理动作就会被以异步方式启动。七牛云存储将立刻将响应内容返回给客户端,并不会等待数据处理动作完成。
关于数据处理结果持久化相关的详细内容,请参见[处理结果持久化(pfop)](http://developer.qiniu.com/docs/v6/api/overview/fop/persistent-fop.html)中的相关描述。