公司讓我使用阿里云OSS服務(wù)器,上傳其實(shí)很快就搞定了,但是一直搞不懂我上傳上去的文件如何下載,翻閱了好多好多資料和文檔都沒法解決,直到我打了客服電話,雖然并沒有解決,但是在解決的路上看到了答案,給我氣的。阿里云服務(wù)器的文檔寫的太不友好了,對(duì)于我這樣的新手使用阿里云服務(wù)器的人來說。
背景
請(qǐng)參考Web端直傳實(shí)踐里的背景介紹。
當(dāng)采用服務(wù)端簽名后直傳方案后,問題來了,用戶上傳數(shù)據(jù)后,很多場(chǎng)景下,應(yīng)用服務(wù)器都要知道用戶上傳了哪些文件,文件名字,甚至如果是圖片的話,圖片的大小等。為此OSS開發(fā)了上傳回調(diào)功能。
用戶的請(qǐng)求邏輯
.用戶向應(yīng)用服務(wù)器取到上傳policy和回調(diào)設(shè)置。
.應(yīng)用服務(wù)器返回上傳policy和回調(diào)。
.用戶直接向OSS發(fā)送文件上傳請(qǐng)求。
.等文件數(shù)據(jù)上傳完,OSS給用戶Response前,OSS會(huì)根據(jù)用戶的回調(diào)設(shè)置,請(qǐng)求用戶的服務(wù)器。
.如果應(yīng)用服務(wù)器返回成功,那么就返回用戶成功,如果應(yīng)用服務(wù)器返回失敗,那么OSS也返回給用戶失敗。這樣確保了用戶上傳成功的照片,應(yīng)用服務(wù)器都已經(jīng)收到通知了。
.應(yīng)用服務(wù)器給OSS返回。
.OSS將應(yīng)用服務(wù)器返回的內(nèi)容返回給用戶。
簡(jiǎn)單講,就是用戶要上載一個(gè)文件到OSS服務(wù)器,而且希望上載完畢的時(shí)候自己的應(yīng)用服務(wù)能夠知道這件事,這時(shí)就需要設(shè)置一個(gè)回調(diào)函數(shù),把這件事告知用戶的應(yīng)用服務(wù)器。這樣當(dāng)OSS收到用戶的上傳請(qǐng)求之后,開始上傳,傳完之后不會(huì)直接給用戶返回結(jié)果,而是先通知用戶的應(yīng)用服務(wù)器:“我上傳完畢了”,然后應(yīng)用服務(wù)器告訴OSS:“我知道啦,你幫我轉(zhuǎn)達(dá)給我的主人吧”,于是OSS就把結(jié)果轉(zhuǎn)達(dá)給用戶了。
快速使用
只要以下三步,就能實(shí)現(xiàn)文件快速通過網(wǎng)頁上傳到OSS,并且OSS會(huì)回調(diào)通知到用戶設(shè)置的應(yīng)用服務(wù)器。
.設(shè)置成自己的id、key、bucket。
設(shè)置方法:修改php/get.php,將變量$id設(shè)成AccessKeyId,$key設(shè)置成AccessKeySecret,$host設(shè)置成bucket+endpoint。
.$id='xxxxxx';
.$key='xxxxx';
.$host='http://post-test.oss-cn-hangzhou.aliyuncs.com
.
.為了瀏覽安全,必須為bucket設(shè)置Cors,參照下文。
.設(shè)置自己的回調(diào)URL,如http://abc.com/test.html(必須公網(wǎng)訪問得通),即自己的回調(diào)服務(wù)器地址,OSS會(huì)在文件上傳完成后,把文件上傳信息,通過自己設(shè)置的回調(diào)URL(http://abc.com/test.html)發(fā)送給應(yīng)用服務(wù)器。
設(shè)置方法:修改php/get.php,(這個(gè)回調(diào)服務(wù)端代碼實(shí)例參考下文)
.$callbackUrl="http://abc.com/test.html";
下面講解一下核心邏輯。
核心代碼解析
代碼要添加的內(nèi)容如下:
.new_multipart_params={
.'key':key+'${filename}',
.'policy':policyBase64,
.'OSSAccessKeyId':accessid,
.'success_action_status':'200',//讓服務(wù)端返回200,不然,默認(rèn)會(huì)返回204
.'callback':callbackbody,
.'signature':signature,
.};
上述的callbackbody是php服務(wù)端返回的。在本例中,從后端php取到的內(nèi)容如下:
.{"accessid":"6MKOqxGiGU4AUk44",
."host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
."policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDo1MjoyOVoiLCJjdb25kaXRpb25zIjpbWyJjdb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8iXV19",
."signature":"VsxOcOudxDbtNSvz93CLaXPz+4s=",
."expire":1446727949,
."callback":"eyJjYWxsYmFja1VybCI6Imh0dHA6Ly9vc3MtZGVtby5hbGl5dW5jcy5jdb206MjM0NTAiLCJjYWxsYmFja0hvc3QiOiJvc3MtZGVtby5hbGl5dW5jcy5jdb20iLCJjYWxsYmFja0JvZHkiOiJmaWxlbmFtZT0ke29iamVjdH0mc2l6ZT0ke3NpemV9Jm1pbWVUeXBlPSR7bWltZVR5cGV9JmhlaWdodD0ke2ltYWdlSW5mby5oZWlnaHR9JndpZHRoPSR7aW1hZ2VJdbmZvLndpZHRofSIsImNhbGxiYWNrQm9keVR5cGUiOiJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQifQ==","dir":"user-dirs/"}
上面提到callbackbody,就是上述返回結(jié)果里面的callback內(nèi)容,經(jīng)過base64編碼后生成的。
解碼后的內(nèi)容如下:
.{"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"}
內(nèi)容的解析如下:
CallbackUrl: OSS往這個(gè)機(jī)器發(fā)送的url請(qǐng)求。
callbackHost: OSS發(fā)送這個(gè)請(qǐng)求時(shí),請(qǐng)求頭部所帶的Host頭。
callbackBody: OSS請(qǐng)求時(shí),發(fā)送給應(yīng)用服務(wù)器的內(nèi)容,可以包括文件的名字、大小、類型,如果是圖片可以是圖片的高度、寬度。
callbackBodyType:請(qǐng)求發(fā)送的Content-Type。
回調(diào)應(yīng)用服務(wù)器
在用戶的請(qǐng)求邏輯中,很重要的地方就是第4步和第5步,OSS與應(yīng)用服務(wù)器交互的時(shí)候,用戶可能會(huì)有以下疑問:
問題1:如果我是開發(fā)者,那么我要怎么樣確認(rèn)請(qǐng)求是從OSS發(fā)送過來的呢?
答案:OSS發(fā)送請(qǐng)求時(shí),會(huì)跟應(yīng)用服務(wù)器構(gòu)造簽名。兩者通過簽名保證。
問題2:這個(gè)簽名是怎么做的?或者有示例代碼嗎?
答案:有的。上面的例子里面是Callback應(yīng)用服務(wù)器的例子:http://oss-demo.aliyuncs.com:23450(目前只支持Linux)。
運(yùn)行方案:在Linux下面直接執(zhí)行里面的文件python callback_app_server.py即可,程序自實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的http server,運(yùn)行該程序可能需要安裝rsa的依賴。
問題3:為何我的應(yīng)用服務(wù)器收到的回調(diào)請(qǐng)求沒有Authotization頭?
答案:有些Web server會(huì)將Authorization頭自行解析掉,比如apache2,因此需要設(shè)置成不解析這個(gè)頭部。以apache2為例,具體設(shè)置方法為:
i.打開rewrite模塊,執(zhí)行命令:a2enmod rewrite;
ii.修改配置文件/etc/apache2/apache2.conf(apache2的安裝路徑不同會(huì)有不一樣)。將Allow Override設(shè)置成All,然后添加下面兩條配置:
RewriteEngine on
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]
示例程序只是完成了如何檢查應(yīng)用服務(wù)器收到的簽名,用戶要自行增加對(duì)應(yīng)用服務(wù)器收到回調(diào)的內(nèi)容的格式解析。
JS的源代碼
親測(cè)實(shí)用有效
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();