用戶只需要在發送給OSS的請求中攜帶相應的Callback參數,即能實現回調。
構造CallBack參數
CallBack參數是由一段經過base64編碼的Json字串,用戶關鍵需要指定請求回調的伺服器URL(callbackUrl)以及回調的內容(callbackBody)。詳細的Json欄位如下:
欄位 | 含義 | 選項 |
---|---|---|
callbackUrl |
|
必選項 |
callbackHost |
|
可選項 |
callbackBody |
|
必選項 |
callbackBodyType |
|
可選項 |
樣本json串如下
{
"callbackUrl":"121.101.166.30/test.php",
"callbackHost":"oss-cn-hangzhou.aliyuncs.com",
"callbackBody":"{\"mimeType\":${mimeType},\"size\":${size}}",
"callbackBodyType":"application/json"
}
{
"callbackUrl":"121.43.113.8:23456/index.html",
"callbackBody":"bucket=${bucket}&object=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&imageInfo.height=${imageInfo.height}&imageInfo.width=${imageInfo.width}&imageInfo.format=${imageInfo.format}&my_var=${x:my_var}"
}
其中callbackBody中可以設定的系統變數有,其中imageInfo針對於圖片格式,如果為非圖片格式都為空:
系統變數 | 含義 |
---|---|
bucket | bucket |
object | object |
etag | 檔案的etag,即返回給用戶的etag欄位 |
size | object大小,CompleteMultipartUpload時為整個object的大小 |
mimeType | 資源類型,如jpeg圖片的資源類型為image/jpeg |
imageInfo.height | 圖片高度 |
imageInfo.width | 圖片寬度 |
imageInfo.format | 圖片格式,如jpg、png等 |
自訂參數
用戶可以通過callback-var參數來配置自訂參數。
自訂參數是一個Key-Value的Map,用戶可以配置自己需要的參數到這個Map。在OSS發起POST回調請求的時候,會將這些參數和上一節所述的系統參數一起放在POST請求的body中以方便接收回調方獲取。
構造自訂參數的方法和callback參數的方法是一樣的,也是以json格式來傳遞。該json字元串就是一個包含所有自訂參數的Key-Value的Map。
![]() |
说明 |
用戶自訂參數的Key一定要以x:開頭,且必須為小寫。否則OSS會返回錯誤。 |
假定用戶需要設定兩個自訂的參數分別為x:var1和x:var2,對應的值分別為value1和value2,那麼構造出來的json格式如下:
{
"x:var1":"value1",
"x:var2":"value2"
}
構造回調請求
構造完成上述的callback和callback-var兩個參數之後,一共有三種方式傳給OSS。其中callback為必填參數,callback-var為選擇性參數,如果沒有自訂參數的話可以不用添加callback-var欄位。這三種方式如下:
- 在URL中攜帶參數。
- 在Header中攜帶參數。
- 在POST請求的body中使用表單域來攜帶參數。
说明 在使用POST請求上傳Object的時候只能使用這種方式來指定回調參數。
這三種方式只能同時使用其中一種,否則OSS會返回InvalidArgument錯誤。
要將參數附加到OSS的請求中,首先要將上文構造的json字元串使用base64編碼,然後按照如下的方法附加到OSS的請求中:
- 如果在URL中攜帶參數。把
callback=[CallBack]
或者callback-var=[CallBackVar]
作為一個url參數帶入請求發送。計算簽名CanonicalizedResource時 ,將callback或者callback-var當做一個sub-resource計算在內 - 如果在Header中攜帶參數。把
x-oss-callback=[CallBack]
或者x-oss-callback-var=[CallBackVar]
作為一個head帶入請求發送。在計算簽名CanonicalizedOSSHeaders時,將x-oss-callback-var和x-oss-callback計算在內。如下樣本:PUT /test.txt HTTP/1.1 Host: callback-test.oss-test.aliyun-inc.com Accept-ncoding: identity Content-Length: 5 x-oss-callback-var: eyJ4Om15X3ZhciI6ImZvci1jYWxsYmFjay10ZXN0In0= User-Agent: aliyun-sdk-python/0.4.0 (Linux/2.6.32-220.23.2.ali1089.el5.x86_64/x86_64;2.5.4) x-oss-callback: eyJjYWxsYmFja1VybCI6IjEyMS40My4xMTMuODoyMzQ1Ni9pbmRleC5odG1sIiwgICJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mZXRhZz0ke2V0YWd9JnNpemU9JHtzaXplfSZtaW1lVHlwZT0ke21pbWVUeXBlfSZpbWFnZUluZm8uaGVpZ2h0PSR7aW1hZ2VJbmZvLmhlaWdodH0maW1hZ2VJbmZvLndpZHRoPSR7aW1hZ2VJbmZvLndpZHRofSZpbWFnZUluZm8uZm9ybWF0PSR7aW1hZ2VJbmZvLmZvcm1hdH0mbXlfdmFyPSR7eDpteV92YXJ9In0= Host: callback-test.oss-test.aliyun-inc.com Expect: 100-Continue Date: Mon, 14 Sep 2015 12:37:27 GMT Content-Type: text/plain Authorization: OSS mlepou3zr4u7b14:5a74vhd4UXpmyuudV14Kaen5cY4= Test
- 如果需要在POST上傳Object的時候附帶回調參數會稍微複雜一點,callback參數要使用獨立的表單域來附加,如下面的樣本:
--9431149156168 Content-Disposition: form-data; name="callback" eyJjYWxsYmFja1VybCI6IjEwLjEwMS4xNjYuMzA6ODA4My9jYWxsYmFjay5waHAiLCJjYWxsYmFja0hvc3QiOiIxMC4xMDEuMTY2LjMwIiwiY2FsbGJhY2tCb2R5IjoiZmlsZW5hbWU9JChmaWxlbmFtZSkmdGFibGU9JHt4OnRhYmxlfSIsImNhbGxiYWNrQm9keVR5cGUiOiJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQifQ==
如果擁有自訂參數的話,不能直接將callback-var參數直接附加到表單域中,每個自訂的參數都需要使用獨立的表單域來附加,舉個例子,如果用戶的自訂參數的json為
{ "x:var1":"value1", "x:var2":"value2" }
那麼POST請求的表單域應該如下:
--9431149156168 Content-Disposition: form-data; name="callback" eyJjYWxsYmFja1VybCI6IjEwLjEwMS4xNjYuMzA6ODA4My9jYWxsYmFjay5waHAiLCJjYWxsYmFja0hvc3QiOiIxMC4xMDEuMTY2LjMwIiwiY2FsbGJhY2tCb2R5IjoiZmlsZW5hbWU9JChmaWxlbmFtZSkmdGFibGU9JHt4OnRhYmxlfSIsImNhbGxiYWNrQm9keVR5cGUiOiJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQifQ== --9431149156168 Content-Disposition: form-data; name="x:var1" value1 --9431149156168 Content-Disposition: form-data; name="x:var2" value2
同時可以在policy中添加callback條件(如果不添加callback,則不對該參數做上傳驗證)如:
{ "expiration": "2014-12-01T12:00:00.000Z", "conditions": [ {"bucket": "johnsmith" }, {"callback": "eyJjYWxsYmFja1VybCI6IjEwLjEwMS4xNjYuMzA6ODA4My9jYWxsYmFjay5waHAiLCJjYWxsYmFja0hvc3QiOiIxMC4xMDEuMTY2LjMwIiwiY2FsbGJhY2tCb2R5IjoiZmlsZW5hbWU9JChmaWxlbmFtZSkiLCJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkIn0="}, ["starts-with", "$key", "user/eric/"], ] }
發起回調請求
如果檔案上傳成功,OSS會根據用戶的請求中的callback參數和自訂參數(callback-var參數),將特定內容以POST方式發送給應用伺服器。
POST /index.html HTTP/1.0
Host: 121.43.113.8
Connection: close
Content-Length: 181
Content-Type: application/x-www-form-urlencoded
User-Agent: ehttp-client/0.0.1
bucket=callback-test&object=test.txt&etag=D8E8FCA2DC0F896FD7CB4CB0031BA249&size=5&mimeType=text%2Fplain&imageInfo.height=&imageInfo.width=&imageInfo.format=&x:var1=for-callback-test
返回回調結果
比如應用伺服器端返回的回應請求為:
HTTP/1.0 200 OK
Server: BaseHTTP/0.3 Python/2.7.6
Date: Mon, 14 Sep 2015 12:37:27 GMT
Content-Type: application/json
Content-Length: 9
{"a":"b"}
返回上傳結果
再給客戶端的內容為:
HTTP/1.1 200 OK
Date: Mon, 14 Sep 2015 12:37:27 GMT
Content-Type: application/json
Content-Length: 9
Connection: keep-alive
ETag: "D8E8FCA2DC0F896FD7CB4CB0031BA249"
Server: AliyunOSS
x-oss-bucket-version: 1442231779
x-oss-request-id: 55F6BF87207FB30F2640C548
{"a":"b"}
需要注意的是,如果類似CompleteMultipartUpload這樣的請求,在返回請求本身body中存在內容(如XMl格式的資訊),使用上傳回調功能後會覆蓋原有的body的內容如{"a":"b"}
,希望對此處做好判斷處理。
回調簽名
使用者佈建callback參數後,OSS將按照使用者佈建的callbackUrl發送POST回調請求給用戶的應用伺服器。應用伺服器收到回調請求之後,如果希望驗證回調請求確實是由OSS發起的話,那麼可以通過在回調中帶上籤名來驗證OSS的身份。
- 生成簽名
簽名在OSS端發生,採用RSA非對稱方式簽名,私密金鑰加密的過程為:
authorization = base64_encode(rsa_sign(private_key, url_decode(path) + query_string + ‘\n’ + body, md5))
说明 其中private_key為私密金鑰,只有oss知曉,path為回調請求的資源路徑,query_string為查詢字元串,body為回調的消息體,所以簽名過程由以下幾步組成: - 獲取待簽名字元串:資源路徑經過url解碼後,加上原始的查詢字元串,加上一個回車符,加上回調消息體
- RSA簽名:使用秘鑰對待簽名字元串進行簽名,簽名的hash函數為md5
- 將簽名後的結果做base64編碼,得到最終的簽名,簽名放在回調請求的authorization頭中
POST /index.php?id=1&index=2 HTTP/1.0 Host: 121.43.113.8 Connection: close Content-Length: 18 authorization: kKQeGTRccDKyHB3H9vF+xYMSrmhMZjzzl2/kdD1ktNVgbWEfYTQG0G2SU/RaHBovRCE8OkQDjC3uG33esH2txA== Content-Type: application/x-www-form-urlencoded User-Agent: ehttp-client/0.0.1 x-oss-pub-key-url: aHR0cDovL2dvc3NwdWJsaWMuYWxpY2RuLmNvbS9jYWxsYmFja19wdWJfa2V5X3YxLnBlbQ== bucket=yonghu-test
path為
/index.php
,query_string為?id=1&index=2
,body為bucket=yonghu-test
,最終簽名結果為kKQeGTRccDKyHB3H9vF+xYMSrmhMZjzzl2/kdD1ktNVgbWEfYTQG0G2SU/RaHBovRCE8OkQDjC3uG33esH2txA==
- 驗證簽名
驗證簽名的過程即為簽名的逆過程,由應用伺服器驗證,過程如下:
Result = rsa_verify(public_key, md5(url_decode(path) + query_string + ‘\n’ + body), base64_decode(authorization))
欄位的含義與簽名過程中描述相同,其中public_key為公開金鑰, authorization為回調頭中的簽名,整個驗證簽名的過程分為以下幾步:
- 回調請求的x-oss-pub-key-url頭保存的是公開金鑰的url地址的base64編碼,因此需要對其做base64解碼後獲取到公開金鑰,即
public_key = urlopen(base64_decode(x-oss-pub-key-url頭的值))
這裡需要注意,用戶需要校驗
x-oss-pub-key-url
頭的值必須以http://gosspublic.alicdn.com/
或者https://gosspublic.alicdn.com/
開頭,目的是為了保證這個publickey是由OSS頒發的。 - 獲取base64解碼後的簽名
signature = base64_decode(authorization頭的值)
- 獲取待簽名字元串,方法與簽名一致
sign_str = url_decode(path) + query_string + ‘\n’ + body
- 驗證簽名
result = rsa_verify(public_key, md5(sign_str), signature)
以上例為例:
- 獲取到公開金鑰的url地址,即
aHR0cDovL2dvc3NwdWJsaWMuYWxpY2RuLmNvbS9jYWxsYmFja19wdWJfa2V5X3YxLnBlbQ==
經過base64解碼後得到http://gosspublic.alicdn.com/callback_pub_key_v1.pem
- 簽名頭
kKQeGTRccDKyHB3H9vF+xYMSrmhMZjzzl2/kdD1ktNVgbWEfYTQG0G2SU/RaHBovRCE8OkQDjC3uG33esH2txA==
做base64解碼(由於為非列印字元,無法顯示出解碼後的結果) - 獲取待簽名字元串,即url_decode(“index.php”) + “?id=1&index=2” + “\n” + “bucket=yonghu-test”,並做md5
- 驗證簽名
- 回調請求的x-oss-pub-key-url頭保存的是公開金鑰的url地址的base64編碼,因此需要對其做base64解碼後獲取到公開金鑰,即
- 應用伺服器樣本
以下為一段python樣本,示範了一個簡單的應用伺服器,主要是說明驗證簽名的方法,此樣本需要安裝M2Crypto庫
import httplib import base64 import md5 import urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from M2Crypto import RSA from M2Crypto import BIO def get_local_ip(): try: csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) csock.connect(('8.8.8.8', 80)) (addr, port) = csock.getsockname() csock.close() return addr except socket.error: return "" class MyHTTPRequestHandler(BaseHTTPRequestHandler): ''' def log_message(self, format, *args): return ''' def do_POST(self): #get public key pub_key_url = '' try: pub_key_url_base64 = self.headers['x-oss-pub-key-url'] pub_key_url = pub_key_url_base64.decode('base64') if not pub_key_url.startswith("http://gosspublic.alicdn.com/") and not pub_key_url.startswith("https://gosspublic.alicdn.com/"): self.send_response(400) self.end_headers() return url_reader = urllib2.urlopen(pub_key_url) #you can cache it pub_key = url_reader.read() except: print 'pub_key_url : ' + pub_key_url print 'Get pub key failed!' self.send_response(400) self.end_headers() return #get authorization authorization_base64 = self.headers['authorization'] authorization = authorization_base64.decode('base64') #get callback body content_length = self.headers['content-length'] callback_body = self.rfile.read(int(content_length)) #compose authorization string auth_str = '' pos = self.path.find('?') if -1 == pos: auth_str = urllib2.unquote(self.path) + '\n' + callback_body else: auth_str = urllib2.unquote(self.path[0:pos]) + self.path[pos:] + '\n' + callback_body print auth_str #verify authorization auth_md5 = md5.new(auth_str).digest() bio = BIO.MemoryBuffer(pub_key) rsa_pub = RSA.load_pub_key_bio(bio) try: result = rsa_pub.verify(auth_md5, authorization, 'md5') except: result = False if not result: print 'Authorization verify failed!' print 'Public key : %s' % (pub_key) print 'Auth string : %s' % (auth_str) self.send_response(400) self.end_headers() return #do something accoding to callback_body #response to OSS resp_body = '{"Status":"OK"}' self.send_response(200) self.send_header('Content-Type', 'application/json') self.send_header('Content-Length', str(len(resp_body))) self.end_headers() self.wfile.write(resp_body) class MyHTTPServer(HTTPServer): def __init__(self, host, port): HTTPServer.__init__(self, (host, port), MyHTTPRequestHandler) if '__main__' == __name__: server_ip = get_local_ip() server_port = 23451 server = MyHTTPServer(server_ip, server_port) server.serve_forever()
其它語言實現的應用伺服器如下:
Java版本:
- 下載地址:點擊這裡
- 運行方法:解壓包運行
java -jar oss-callback-server-demo.jar 9000
(9000就啟動並執行通信埠,可以自己指定)
PHP版本:
- 下載地址:點擊這裡
- 運行方法:部署到Apache環境下,因為PHP本身語言的特點,取一些數據頭部會依賴於環境。所以可以參考例子根據所在環境修改。
Python版本:
- 下載地址:點擊這裡
- 運行方法:解壓包直接運行
python callback_app_server.py
,運行該程式需要安裝rsa的依賴。
C#版本:
- 下載地址:點擊這裡
- 運行方法:解壓後參看
README.md
。
Go版本:
- 下載地址:點擊這裡
- 運行方法:解壓後參看
README.md
。
Go版本:
- 下載地址:點擊這裡
- 運行方法:解壓後參看
README.md
。
Ruby版本:
- 下載地址:點擊這裡
- 運行方法: ruby aliyun_oss_callback_server.rb
特別須知
- 如果傳入的callback或者callback-var不合法,則會返回400錯誤,錯誤碼為”InvalidArgument”,不合法的情況包括以下幾類:
- PutObject()和CompleteMultipartUpload()介面中url和header同時傳入callback(x-oss-callback)或者callback-var(x-oss-callback-var)參數
- callback或者callback-var(PostObject()由於沒有callback-var參數,因此沒有此限制,下同)參數過長(超過5KB)
- callback或者callback-var沒有經過base64編碼
- callback或者callback-var經過base64解碼後不是合法的json格式
- callback參數解析後callbackUrl欄位包含的url超過限制(5個),或者url中傳入的port不合法,比如
{"callbackUrl":"10.101.166.30:test", "callbackBody":"test"}
- callback參數解析後callbackBody欄位為空
- callback參數解析後callbackBodyType欄位的值不是”application/x-www-form-urlencoded”或者”application/json”
- callback參數解析後callbackBody欄位中變數的格式不合法,合法的格式為${var}
- callback-var參數解析後不是預期的json格式,預期的格式應該為
{"x:var1":"value1","x:var2":"value2"...}
- 如果回調失敗,則返回203,錯誤碼為”CallbackFailed”,回調失敗只是表示OSS沒有收到預期的回調響應,不代表應用伺服器沒有收到回調請求(比如應用伺服器返回的內容不是json格式),另外,此時檔案已經成功上傳到了OSS
- 應用伺服器返回OSS的響應必須帶有Content-Length的Header,Body大小不要超過1MB。
Callback支援的地域
Callback目前支援的地域如下:華北 2(北京)、華東 1(杭州)、華北 1(青島)、華東 2(上海)、上海金融雲、華南 1(深圳)、香港、華北 5(呼和浩特)、華北 3(張家口)、中東東部 1(迪拜)、亞太東北 1(日本)、歐洲中部 1(法蘭克福)、亞太東南 1(新加坡)、美國東部 1(維吉尼亞)、美國西部 1(矽谷)、亞太東南 2 (悉尼)以及亞太東南 3(吉隆坡)。