【Mark】服务端签名直传并设置上传回调


公司让我使用阿里云OSS服务器,上传其实很快就搞定了,但是一直搞不懂我上传上去的文件如何下载,翻阅了好多好多资料和文档都没法解决,直到我打了客服电话,虽然并没有解决,但是在解决的路上看到了答案,给我气的。阿里云服务器的文档写的太不友好了,对于我这样的新手使用阿里云服务器的人来说。


背景

请参考Web端直传实践里的背景介绍。

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

用户的请求逻辑

.用户向应用服务器取到上传policy和回调设置。

.应用服务器返回上传policy和回调。

.用户直接向OSS发送文件上传请求。

.等文件数据上传完,OSS给用户Response前,OSS会根据用户的回调设置,请求用户的服务器。

.如果应用服务器返回成功,那么就返回用户成功,如果应用服务器返回失败,那么OSS也返回给用户失败。这样确保了用户上传成功的照片,应用服务器都已经收到通知了。

.应用服务器给OSS返回。

.OSS将应用服务器返回的内容返回给用户。

简单讲,就是用户要上载一个文件到OSS服务器,而且希望上载完毕的时候自己的应用服务能够知道这件事,这时就需要设置一个回调函数,把这件事告知用户的应用服务器。这样当OSS收到用户的上传请求之后,开始上传,传完之后不会直接给用户返回结果,而是先通知用户的应用服务器:“我上传完毕了”,然后应用服务器告诉OSS:“我知道啦,你帮我转达给我的主人吧”,于是OSS就把结果转达给用户了。

快速使用

只要以下三步,就能实现文件快速通过网页上传到OSS,并且OSS会回调通知到用户设置的应用服务器。

.设置成自己的id、key、bucket。

设置方法:修改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"}

内容的解析如下:

CallbackUrl: OSS往这个机器发送的url请求。

callbackHost: OSS发送这个请求时,请求头部所带的Host头。

callbackBody: OSS请求时,发送给应用服务器的内容,可以包括文件的名字、大小、类型,如果是图片可以是图片的高度、宽度。

callbackBodyType:请求发送的Content-Type。

回调应用服务器

在用户的请求逻辑中,很重要的地方就是第4步和第5步,OSS与应用服务器交互的时候,用户可能会有以下疑问:

问题1:如果我是开发者,那么我要怎么样确认请求是从OSS发送过来的呢?

答案:OSS发送请求时,会跟应用服务器构造签名。两者通过签名保证。

问题2:这个签名是怎么做的?或者有示例代码吗?

答案:有的。上面的例子里面是Callback应用服务器的例子:http://oss-demo.aliyuncs.com:23450(目前只支持Linux)。

运行方案:在Linux下面直接执行里面的文件python callback_app_server.py即可,程序自实现了一个简单的http server,运行该程序可能需要安装rsa的依赖。

问题3:为何我的应用服务器收到的回调请求没有Authotization头?

答案:有些Web server会将Authorization头自行解析掉,比如apache2,因此需要设置成不解析这个头部。以apache2为例,具体设置方法为:

i.打开rewrite模块,执行命令:a2enmod rewrite

ii.修改配置文件/etc/apache2/apache2.conf(apache2的安装路径不同会有不一样)。将Allow Override设置成All,然后添加下面两条配置:

RewriteEngine on

RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]

示例程序只是完成了如何检查应用服务器收到的签名,用户要自行增加对应用服务器收到回调的内容的格式解析。


JS的源代码

亲测实用有效

accessid = ''

accesskey = ''

host = ''

policyBase64 = ''

signature = ''

callbackbody = ''

filename = ''

key = ''

expire = 0

g_object_name = ''

g_object_name_type = ''

now = timestamp = Date.parse(new Date()) / 1000;

function send_request()

{

var xmlhttp = null;

if (window.XMLHttpRequest)

{

xmlhttp=new XMLHttpRequest();

}

else if (window.ActiveXObject)

{

xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");

}

if (xmlhttp!=null)

{

serverUrl = './php/get.php'

xmlhttp.open( "GET", serverUrl, false );

xmlhttp.send( null );

return xmlhttp.responseText

}

else

{

alert("Your browser does not support XMLHTTP.");

}

};

function check_object_radio() {

var tt = document.getElementsByName('myradio');

for (var i = 0; i < tt.length ; i++ )

{

if(tt[i].checked)

{

g_object_name_type = tt[i].value;

break;

}

}

}

function get_signature()

{

now = timestamp = Date.parse(new Date()) / 1000;

if (expire < now + 3)

{

body = send_request()

var obj = eval ("(" + body + ")");

host = obj['host']

policyBase64 = obj['policy']

accessid = obj['accessid']

signature = obj['signature']

expire = parseInt(obj['expire'])

callbackbody = obj['callback']

key = obj['dir']

return true;

}

return false;

};

function random_string(len) {

len = len || 32;

var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';

var maxPos = chars.length;

var pwd = '';

for (i = 0; i < len; i++) {

pwd += chars.charAt(Math.floor(Math.random() * maxPos));

}

return pwd;

}

function get_suffix(filename) {

pos = filename.lastIndexOf('.')

suffix = ''

if (pos != -1) {

suffix = filename.substring(pos)

}

return suffix;

}

function calculate_object_name(filename)

{

if (g_object_name_type == 'local_name')

{

g_object_name += "${filename}"

}

else if (g_object_name_type == 'random_name')

{

suffix = get_suffix(filename)

g_object_name = key + random_string(10) + suffix

}

return ''

}

function get_uploaded_object_name(filename)

{

if (g_object_name_type == 'local_name')

{

tmp_name = g_object_name

tmp_name = tmp_name.replace("${filename}", filename);

return tmp_name

}

else if(g_object_name_type == 'random_name')

{

return g_object_name

}

}

function set_upload_param(up, filename, ret)

{

if (ret == false)

{

ret = get_signature()

}

g_object_name = key;

if (filename != '') { suffix = get_suffix(filename)

calculate_object_name(filename)

}

new_multipart_params = {

'key' : g_object_name,

'policy': policyBase64,

'OSSAccessKeyId': accessid,

'success_action_status' : '200',

'callback' : callbackbody,

'signature': signature,

};

up.setOption({

'url': host,

'multipart_params': new_multipart_params

});

up.start();

}

var uploader = new plupload.Uploader({

runtimes : 'html5,flash,silverlight,html4',

browse_button : 'selectfiles',

//multi_selection: false,

container: document.getElementById('container'),

flash_swf_url : 'lib/plupload-2.1.2/js/Moxie.swf',

silverlight_xap_url : 'lib/plupload-2.1.2/js/Moxie.xap',

url : 'http://oss.aliyuncs.com',

filters: {

mime_types : [ 

{ title : "Image files", extensions : "jpg,gif,png,bmp" },

{ title : "Zip files", extensions : "zip,rar" }

],

max_file_size : '10mb',

prevent_duplicates : true 

},

init: {

PostInit: function() {

document.getElementById('ossfile').innerHTML = '';

document.getElementById('postfiles').onclick = function() {

set_upload_param(uploader, '', false);

return false;

};

},

FilesAdded: function(up, files) {

plupload.each(files, function(file) {

document.getElementById('ossfile').innerHTML += '

' + file.name + ' (' + plupload.formatSize(file.size) + ')'

+'

'

+'';

});

},

BeforeUpload: function(up, file) {

check_object_radio();

set_upload_param(up, file.name, true);

},

UploadProgress: function(up, file) {

var d = document.getElementById(file.id);

d.getElementsByTagName('b')[0].innerHTML = '' + file.percent + "%";

var prog = d.getElementsByTagName('div')[0];

var progBar = prog.getElementsByTagName('div')[0]

progBar.style.width= 2*file.percent+'px';

progBar.setAttribute('aria-valuenow', file.percent);

},

FileUploaded: function(up, file, info) {

if (info.status == 200)

{

document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = 'upload to oss success, object name:' + get_uploaded_object_name(file.name) + ':' + info.response;

}

else if (info.status == 203)

{

document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = ':' + info.response;

}

else

{

document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;

}

},

Error: function(up, err) {

if (err.code == -600) {

document.getElementById('console').appendChild(document.createTextNode("\n"));

}

else if (err.code == -601) {

document.getElementById('console').appendChild(document.createTextNode("\n"));

}

else if (err.code == -602) {

document.getElementById('console').appendChild(document.createTextNode("\n"));

}

else

{

document.getElementById('console').appendChild(document.createTextNode("\nError xml:" + err.response));

}

}

}

});

uploader.init();

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,015评论 1 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,292评论 18 399
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,619评论 2 17
  • 《ijs》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 10...
    叶染柒丶阅读 4,697评论 0 7