服务端签名直传并设置上传回调
请参考 Web端直传实践 里的背景介绍。
当采用服务端签名后直传方案后,问题来了,用户上传数据后,很多场景下,应用服务器都要知道用户上传了哪些文件,文件名字,甚至如果是图片的话,图片的大小等。为此OSS开发了上传回调功能。

简单讲,就是用户要上载一个文件到OSS服务器,而且希望上载完毕的时候自己的应用服务能够知道这件事,这时就需要设置一个回调函数,把这件事告知用户的应用服务器。这样当OSS收到用户的上传请求之后,开始上传,传完之后不会直接给用户返回结果,而是先通知用户的应用服务器:“我上传完毕了”,然后应用服务器告诉OSS:“我知道啦,你帮我转达给我的主人吧”,于是OSS就把结果转达给用户了。
用户电脑浏览器测试样例:点击这里体验上传回调示例
用手机测试该上传是否有效。可以用手机(微信、QQ、手机浏览器等)扫一扫二维码试试(这个不是广告,只是上述网址的二维码,为了让大家看一下这个实现能在手机端完美运行)。
点击这里:oss-h5-upload-js-php-callback.zip
例子是采用后端签名,语言是用PHP。
其他语言的用法:
oss-h5-upload-js-php-callback.zip里面的upload.js, 将里面的变量severUrl改成第2步部署的地址。如severUrl = http://1.2.3.4:8080 或者serverUrl= http://abc.com/post/只要以下三步,就能实现文件快速通过网页上传到OSS,并且OSS会回调通知到用户设置的应用服务器。
php/get.php,将变量$id设成AccessKeyId,$key设置成AccessKeySecret,$host设置成 bucket+endpoint。 $id= ‘xxxxxx‘; $key= ‘xxxxx‘; $host = ‘http://post-test.oss-cn-hangzhou.aliyuncs.com为了浏览安全,必须为bucket设置Cors, 参照下文。
设置自己的回调URL,如http://abc.com/test.html (必须公网访问得通),即自己的回调服务器地址,OSS会在文件上传完成后,把文件上传信息,通过自己设置的回调URL(http://abc.com/test.html)发送给应用服务器。
设置方法:修改php/get.php,(这个回调服务端代码实例参考下文)
    $callbackUrl = "http://abc.com/test.html";这个例子里面更多细节 ,如上传签名,设置随机文件名等更多细节可以参照:点击这里,了解上传更多细节。
下面讲解一下核心逻辑。
代码要添加的内容如下:
new_multipart_params = {     ‘key‘ : key + ‘${filename}‘,     ‘policy‘: policyBase64,     ‘OSSAccessKeyId‘: accessid,     ‘success_action_status‘ : ‘200‘, //让服务端返回200,不然,默认会返回204     ‘callback‘:  callbackbody,     ‘signature‘: signature, };上述的callbackbody 是php服务端返回的。在本例中,从后端php取到的内容如下:
{"accessid":"6MKOqxGiGU4AUk44","host":"http://post-test.oss-cn-hangzhou.aliyuncs.com","policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDo1MjoyOVoiLCJjdb25kaXRpb25zIjpbWyJjdb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8iXV19","signature":"VsxOcOudxDbtNSvz93CLaXPz+4s=","expire":1446727949,"callback":"eyJjYWxsYmFja1VybCI6Imh0dHA6Ly9vc3MtZGVtby5hbGl5dW5jcy5jdb206MjM0NTAiLCJjYWxsYmFja0hvc3QiOiJvc3MtZGVtby5hbGl5dW5jcy5jdb20iLCJjYWxsYmFja0JvZHkiOiJmaWxlbmFtZT0ke29iamVjdH0mc2l6ZT0ke3NpemV9Jm1pbWVUeXBlPSR7bWltZVR5cGV9JmhlaWdodD0ke2ltYWdlSW5mby5oZWlnaHR9JndpZHRoPSR7aW1hZ2VJdbmZvLndpZHRofSIsImNhbGxiYWNrQm9keVR5cGUiOiJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQifQ==","dir":"user-dirs/"}上面提到callbackbody,就是上述返回结果里面的callback内容,经过base64编码后生成的。
解码后的内容如下:
{"callbackUrl":"http://oss-demo.aliyuncs.com:23450","callbackHost":"oss-demo.aliyuncs.com","callbackBody":"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}","callbackBodyType":"application/x-www-form-urlencoded"}内容的解析如下:
在用户的请求逻辑中,很重要的地方就是第4步和第5步,OSS与应用服务器交互的时候,用户可能会有以下疑问:
问题1:如果我是开发者,那么我要怎么样确认请求是从OSS发送过来的呢?
答案:OSS发送请求时,会跟应用服务器构造签名。两者通过签名保证。
问题2:这个签名是怎么做的?或者有示例代码吗?
答案:有的。上面的例子里面是Callback应用服务器的例子:
http://oss-demo.aliyuncs.com:23450(目前只支持Linux)。
上面运行的代码是:callback_app_server.py.zip
运行方案:在Linux下面直接执行里面的文件python callback_app_server.py即可,程序自实现了一个简单的http server,运行该程序可能需要安装rsa的依赖。
问题3: 为何我的应用服务器收到的回调请求没有Authotization头?
答案:有些 Web server会将Authorization头自行解析掉,比如apache2,因此需要设置成不解析这个头部。以apache2为例,具体设置方法为:
- 打开rewrite模块,执行命令:a2enmod rewrite;
- 修改配置文件
/etc/apache2/apache2.conf(apache2的安装路径不同会有不一样)。将Allow Override设置成All,然后添加下面两条配置:
RewriteEngine on
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
示例程序只是完成了如何检查应用服务器收到的签名, 用户要自行增加对应用服务器收到回调的内容的格式解析 。
Java版本:
java -jar oss-callback-server-demo.jar 9000(9000就运行的端口,可以自己指定)
注意这个jar例子在java 1.7运行通过,如果有问题可以自己依据提供的代码进行修改。这是一个maven项目。
PHP版本:
Python版本:
Ruby版本:
https://help.aliyun.com/document_detail/31927.html
授权给第三方上传
在典型的C/S系统架构中,服务器端负责接收并处理客户端的请求。那么考虑一个使用OSS作为后端的存储服务,客户端将要上传的文件发送给服务器端,然后服务器端再将数据转发上传到OSS。在这个过程中,一份数据需要在网络上传输两次,一次从客户端到服务器端,一次从服务器端到OSS。当访问量很大的时候,服务器端需要有足够的带宽资源来满足多个客户端的同时上传的需求,这对架构的伸缩性提出了挑战。
为了解决这种场景带来的挑战,OSS提供了授权给第三方上传的功能。使用这个功能,每个客户端可以直接将文件上传到OSS而不是通过服务器端转发,节省了自建服务器的成本,并且充分利用了OSS的海量数据处理能力,无需考虑带宽和并发限制等,可以让客户专心于业务处理。
目前授权上传可以由两种实现方式:
URL签名是授权访问的一种方式,即在请求的URL中带OSS AccessKeyId和Signature字段,这样用户就可以直接使用该URL来进行上传。每个URL签名中携带有过期时间以保证安全。具体的做法可以参考在URL中包含签名。
临时访问凭证是通过阿里云Security Token Service(STS)来实现授权的一种方式。其实现请参见STS Java SDK。临时访问凭证的流程如下:
https://help.aliyun.com/document_detail/31852.html?spm=5176.doc50092.6.593.oLFTdy
OSS在文件上传完成的时,可以提供回调(Callback)给用户的回调服务器(Callback Server)。在上传请求中携带相应的回调参数,即能实现上传回调。支持上传回调的 API 接口有:PutObject、PostObject、CompleteMultipartUpload。
提示:回调服务器(Callback Server),有时也叫业务服务器。
OSS上传回调流程,如下图:
 -----------1---------> -----------2--------->客户端(SDK/PostObject) OSS 回调服务器(Callback Server) <----------4---------- <----------3----------上图中的数据流含义,见下表:
| 数据流 | 含义 | 说明 | 
|---|---|---|
| 1 | 上传文件并携带回调参数[1] | 通过SDK(PutObject、CompleteMultipartUpload)/PostObject完成 | 
| 2 | OSS存储文件后发起回调 | OSS向上传请求中指定的 CallbackUrl发起POST[2] 请求,回调超时时间是 5秒 [3] | 
| 3 | 回调服务器返回处理结果 | 回调服务器返回的消息体一定要是 Json 格式;OSS认为非 200 请求为回调失败[4] | 
| 4 | OSS返回上传、回调结果 | 上传、回调都成功,返回 200;上传成功、回调失败返回203, ErrorCode为CallbackFailed, ErrorMessage描述错误原因 | 
注:
- [1] 格式请参看
SDK/PostObject;- [2] 回调请求POST的格式详见 发起回调请求;
- [3] 超时时间为固定值,不支持配置;
- [4] 参数无效、认真失败返回
40x,超时、无法连接返回50x。
上传时可以通过设置回调参数,指定回调服务器URL、发送给回调服务器的数据、格式等。回调服务器处理回调时,需要一些上下文信息,如bucket、object等,通过系统变量指定;系统变量以外的上下文信息,通过自定义变量指定。
上传回调包括以下参数:
| 字段 | 含义 | 说明 | 
|---|---|---|
| callbackUrl | 回调服务器地址 | 必选参数 | 
| callbackHost | 回调请求消息头中 Host的值 | 可选参数,默认为 callbackUrl | 
| callbackBody | 回调请求的消息体 | 必选参数,内容可以包括系统变量和自定义变量 | 
| callbackBodyType | 回调请求消息头中 Content-Type的值,即callbackBody的数据格式 | 可选参数,支持 application/x-www-form-urlencoded和application/json,默认为前者 | 
上传回调参数通过上传请求携带,有两种方式:
x-oss-callback,携带回调参数。这种方式比较常用,推荐该方式;callback,携带回调参数。x-oss-callback 或 callback 的值生成规则如下:
Callback := Base64(CallbackJson)CallbackJson := ‘{‘ CallbackUrlItem, CallbackBodyItem [, CallbackHostItem, CallbackBodyTypeItem] ‘}‘CallbackUrlItem := ‘"‘callbackUrl‘"‘ ‘:‘ ‘"‘CallbackUrlValue‘"‘CallbackBodyItem := ‘"‘callbackBody‘"‘ ‘:‘ ‘"‘CallbackBodyValue‘"‘CallbackHostItem := ‘"‘callbackHost‘"‘ ‘:‘ ‘"‘CallbackHostValue‘"‘CallbackBodyTypeItem := ‘"‘callbackBodyType‘"‘ : ‘"‘CallbackBodyType‘"‘CallbackBodyType := application/x-www-form-urlencoded | application/jsonCallbackJson的值示例如下:
{ "callbackUrl" : "http://abc.com/test.php", "callbackHost" : "oss-cn-hangzhou.aliyuncs.com", "callbackBody" : "{\"bucket\":${mimeType}, \"object\":${object},\"size\":${size},\"mimeType\":${mimeType},\"my_var\":${x:my_var}}", "callbackBodyType" : "application/json"}或
{ "callbackUrl" : "http://abc.com/test.php", "callbackBody" : "bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&my_var=${x:my_var}"}/*callback: new Buffer(JSON.stringify({ ‘callbackUrl‘:‘http://abc.com/test.php‘, ‘callbackBody‘:‘bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}‘})).toString(‘base64‘),*/ JSONObject call = new JSONObject(); call.put("callbackUrl", "http://abc.com/test.php"); call.put("callbackBody", "bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}"); byte[] bytes = Base64.encodeBase64(call.toString().getBytes()); String callback = new String(bytes);
CallbackJson示例中的 callbackBody 包括如 ${bucket}、${object}、${size} 的变量,即为OSS定义的系统变量,OSS回调时会用实际值替换掉系统变量。OSS定义的系统变量如下表:
| 变量 | 含义 | 
|---|---|
| ${bucket} | 存储空间名称 | 
| ${object} | 文件名称 | 
| ${etag} | 文件的ETag | 
| ${size} | 文件大小 | 
| ${mimeType} | 文件类型,如image/jpeg等 | 
| ${imageInfo.height} | 图片高度 | 
| ${imageInfo.width} | 图片宽度 | 
| ${imageInfo.format} | 图片格式,如jpg、png等 | 
注意:
- 系统变量大小写敏感;
- 系统的变量的格式一定是
${bucket};- imageInfo针对于图片格式,为非图片格式值为空。
CallbackJson示例中的 callbackBody 包括如 ${x:my_var} 的变量,即自定义变量,OSS回调时会用自定义的值替换掉自定义变量。自定义变量的值可以在上传请求中定义并携带,有以下两种方式:
x-oss-callback-var,携带自定义变量。这种方式比较常用,也是推荐方式;callback-var,携带自定义变量。x-oss-callback-var 或 callback-var 的生成规则如下:
CallbackVar := Base64(CallbackVarJson)CallbackVarJson := ‘{‘ CallbackVarItem [, CallbackVarItem]* ‘}‘CallbackVarItem := ‘"‘‘x:‘VarName‘"‘ : ‘"‘VarValue‘"‘CallbackVarJson 的值示例如下:
{ "x:my_var1" : "value1", "x:my_var2" : "value2"}注意:
- 自定义变量必须以 x: 开头,大小写敏感,格式是
${x:my_var};- 自定义变量的长度受消息头、URL的长度限制,建议自定义变量不超过10个,总长度不超过512Byte。
部分SDK对上述步骤进行了封装,如Java、JS;部分SDK需要使用上面的规则生成上传回调参数和自定义变量,如Python、PHP、C。SDK的使用示例如下:
| SDK | 上传回调示例 | 说明 | 
|---|---|---|
| Java | CallbackSample.java | 注意 CallbackBody中的转义字符 | 
| Python | object_callback.py | |
| PHP | Callback.php | 注意上传的$options中 OSS_CALLBACK、OSS_CALLBACK_VAR不需要base64,SDK会做 | 
| JS | object.test.js | |
| C | oss_callback_sample.c | |
| Ruby | callback.rb | |
| iOS | 上传后回调通知 | 注意 <var1>的格式应该是x:var1 | 
| Andriod | 上传后回调通知 | 注意 CallbackBody中的转义字符 | 
注意:C# SDK、Go SDK尚不支持上传回调。
PostObject支持上传回调,回调参数通过表单域callback携带,自定义变量通过独立的表单域携带,详细请参看 PostObjet。PostObject的使用示例如下:
| SDK | 上传回调示例 | 
|---|---|
| Java | PostObjectSample.java | 
| Python | object_post.py | 
| JS | JavaScript客户端签名直传 | 
| C# | PostPolicySample.cs | 
回调服务器(Callback Server),是一个HTTP服务器,处理OSS发送的回调请求,POST消息。回调服务器的URL即上传回调参数中的 callbackUrl。回调服务器是用户自己实现的处理逻辑,实现上传数据的记录、审查、处理、统计等。
回调服务器为了确认收到的POST请求,来自于OSS的上传回调,需要验证该POST消息的签名。当然,回调服务器也可以不验证签名,直接处理该消息。为了提高回调服务器的安全性,建议验证消息签名。回调签名规则请参看回调签名。
提示:OSS的回调服务器示例中提供了签名校验的实现,推荐直接使用该部分代码。
回调服务器的主要逻辑,对OSS的回调请求进行处理。以下几点请注意:
回调服务器的实现示例如下:
| 语言 | 示例 | 运行方法 | 
|---|---|---|
| Java | AppCallbackServer.zip | 解压后执行 java -jar oss-callback-server-demo.jar 9000 | 
| PHP | callback-php-demo.zip | Apache环境下部署运行 | 
| Python | callback_app_server.py.zip | 解压后执行 python callback_app_server.py | 
| Ruby | oss-callback-server | 执行 ruby aliyun_oss_callback_server.rb | 
上传回调的调试分为两部分,上传的客户端、处理回调的回调服务器。建议先调试客户端上传部分,再调试回调服务器部分。两部分单独调试完成后,再运行完整的上传回调。
客户端调试时,可使用OSS提供的回调服务器 http://callback.oss-demo.com:23450,即回调参数 callbackUrl。该回调服务器只验证回调请求的签名,对回调请求不做处理。对于签名验证成功的回调请求,返回 {"Status":"OK"};签名验证失败的回调请求,返回 400 Bad Request;非POST请求返回501 Unsupported method。示例回调服务器的代码请参看callback_app_server.py.zip
回调服务器是一个支持处理 POST 请求的 HTTP 服务器,可以在OSS提供的示例基础上修改,也可以自己独立实现。OSS提供的回调服务器示例:
| 语言 | 示例 | 运行方法 | 
|---|---|---|
| Java | AppCallbackServer.zip | 解压后执行 java -jar oss-callback-server-demo.jar 9000 | 
| PHP | callback-php-demo.zip | Apache环境下部署运行 | 
| Python | callback_app_server.py.zip | 解压后执行 python callback_app_server.py | 
| Ruby | oss-callback-server | 执行 ruby aliyun_oss_callback_server.rb | 
回调服务器可以通过 cRUL 命令调试,下面几个命令可能会用到:
# 向回调服务器发送消息体为 `object=test_obj` 的 `POST` 请求,可以使用如下命令 curl -d "object=test_obj" http://callback.oss-demo.com:23450 -v# 向回调服务器发送消息体为文件 `post.txt` 内容 的 `POST` 请求,可以使用如下命令 curl -d @post.txt http://callback.oss-demo.com:23450 -v#向回调服务器发送消息体为文件 `post.txt` 内容的 `POST` 请求,并携带指定的消息头 `Content-Type`curl -d @post.txt -H "Content-Type: application/json" http://callback.oss-demo.com:23450 -v注意:
- 调试回调服务器时,可以先忽略签名验证部分,因为
cURL模拟签名功能比较困难;- 签名验证功能OSS示例中已经提供,建议直接使用;
- 回调服务器建议有日志功能,记录收到的所有消息,方便调试、跟踪;
- 回调服务器正确处理回调请求后,一定要返回 200,而不是其它的
20x;- 回调服务器返回给OSS的消息体,一定要是 JSON 格式,
Content-Type设置为application/json。
<Error> <Code>InvalidArgument</Code> <Message>The callback configuration is not json format.</Message> <RequestId>587C79A3DD373E2676F73ECE</RequestId> <HostId>bucket.oss-cn-hangzhou.aliyuncs.com</HostId> <ArgumentName>callback</ArgumentName> <ArgumentValue>{"callbackUrl":"8.8.8.8:9090","callbackBody":"{"bucket":${bucket},"object":${object}}","callbackBodyType":"application/json"}</ArgumentValue></Error>原因:回调参数设置错误,或参数格式错误。常见的错误是ArgumentValue之间的回调参数,不是有效 JSON 格式。在 JSON 中 \、" 是转移字符,如 "callbackBody":"{"bucket":${bucket},"object":${object}}"应该为 "callbackBody":"{\"bucket\":${bucket},\"object\":${object}}"。针对具体的SDK,请参看对应的上传回调示例,详细请参考 SDK使用示例 部分。
| 转义后的字符 | 转义前的字符 | 
|---|---|
| \\ | \\\\ | 
| “ | \\\” | 
| \b | \\b | 
| \f | \\f | 
| \n | \\n | 
| \r | \\r | 
| \t | \\t | 
<Error> <Code>CallbackFailed</Code> <Message>Response body is not valid json format.</Message> <RequestId>587C81A125F797621829923D</RequestId> <HostId>bucket.oss-cn-hangzhou.aliyuncs.com</HostId></Error>原因:回调服务器返回给OSS的消息体非 JSON 格式。您可以通过 curl -d "<Content>" <CallbackServerURL> -v或抓包确认内容。Windows下推荐使用工具 Wireshark 抓包,Linux下使用命令 tcpdump 抓包。一些非法返回消息体如下:OK,\357\273\277{"Status":"OK"}(即含有ef bb bf三个字节的BOM头)等
<Error> <Code>CallbackFailed</Code> <Message>Error status : -1.OSS can not connect to your callbackUrl, please check it.</Message> <RequestId>587C8735355BE8694A8E9100</RequestId> <HostId>bucket.oss-cn-hangzhou.aliyuncs.com</HostId></Error>原因:回调服务器处理时间超过 5秒,OSS认为超时。建议回调服务器的处理逻辑修改为异步,保证在5秒内处理完毕并返回结果OSS。
<Error> <Code>CallbackFailed</Code> <Message>Error status : -1 8.8.8.8:9090 reply timeout, cost:5000ms, timeout:5000ms (err -4, errno115)</Message> <RequestId>587C8D382AE0B92FA3EEF62C</RequestId> <HostId>bucket.oss-cn-hangzhou.aliyuncs.com</HostId></Error>原因:回调服务器处理时间超过5秒,OSS认为超时。
<Error> <Code>CallbackFailed</Code> <Message>Error status : 400.</Message> <RequestId>587C89A02AE0B92FA3C7981D</RequestId> <HostId>bucket.oss-cn-hangzhou.aliyuncs.com</HostId></Error>原因:回调服务器返回给OSS的消息的状态码是400,请检查回调服务器的处理逻辑。
<Error> <Code>CallbackFailed</Code> <Message>Error status : 502.</Message> <RequestId>587C8D382AE0B92FA3EEF62C</RequestId> <HostId>bucket.oss-cn-hangzhou.aliyuncs.com</HostId></Error>原因:回调服务器未启动,或者上传回调参数中的CallbackUrl,或者OSS与回调服务器的网络不通。推荐在ECS上部署回调服务器,与OSS同属内网可以节省流量费用,同时保证网络质量。
https://help.aliyun.com/document_detail/50092.html?spm=5176.doc27781.6.1050.pxQLlA
原文:http://www.cnblogs.com/softidea/p/7217201.html