您只需在發送給OSS的請求中攜帶相應的Callback參數即可實現回調。本文介紹Callback的實現原理。
注意事項
華東1(杭州)、華東2(上海)、華北1(青島)、華北2(北京)、華北 3(張家口)、華北5(呼和浩特)、華北6(烏蘭察布)、華南1(深圳)、華南2(河源)、華南3(廣州)、西南1(成都)、中國香港、美國(硅谷)、美國(弗吉尼亞)、日本(東京)、新加坡、馬來西亞(吉隆坡)、印度尼西亞(雅加達)、菲律賓(馬尼拉)、德國(法蘭克福)、英國(倫敦)、阿聯酋(迪拜)地域支持設置Callback。
僅PutObject、PostObject和CompleteMultipartUpload接口支持設置Callback。
Callback請求默認超時時間為5秒。
Callback請求失敗,不支持自動重試。
步驟1:構造回調參數
Callback參數
Callback參數是由一段經過Base64編碼的JSON字符串(字段)構成的。構建callback參數的關鍵是指定請求回調的服務器URL(callbackUrl)以及回調的內容(callbackBody)。
JSON字段的詳細信息請參見下表。
字段
是否必選
描述
callbackUrl
是
文件上傳成功后,OSS向此URL發送回調請求。
請求方法為POST,Body為callbackBody指定的內容。正常情況下,該URL需要響應
HTTP/1.1 200 OK
,Body必須為JSON格式,響應頭Content-Length必須為合法的值,且大小不超過3 MB。支持同時配置最多5個URL,多個URL間以分號(;)分隔。OSS會依次發送請求,直到第一個回調請求成功返回。
支持HTTPS協議地址。
不支持填寫IPV6地址,也不支持填寫指向IPV6地址的域名。
為了保證正確處理中文等情況,callbackUrl需做URL編碼處理,例如
https://example.com/中文.php?key=value&中文名稱=中文值
需要編碼為https://example.com/%E4%B8%AD%E6%96%87.php?key=value&%E4%B8%AD%E6%96%87%E5%90%8D%E7%A7%B0=%E4%B8%AD%E6%96%87%E5%80%BC
。
callbackHost
否
發起回調請求時Host頭的值,格式為域名或IP地址。
callbackHost僅在設置了callbackUrl時有效。
如果沒有配置callbackHost,則解析callbackUrl中的URL,并將解析的Host填充到callbackHost中。
callbackBody
是
發起回調時請求Body的值,例如
key=${object}&etag=${etag}&my_var=${x:my_var}
。callbackBody支持OSS系統參數、自定義變量和常量。
在PutObject和CompleteMultipart中,自定義變量通過callback-var傳遞。
在PostObject中,各個變量通過表單域傳遞。
callbackSNI
否
客戶端發起回調請求時,OSS是否向通過callbackUrl指定的回源地址發送服務器名稱指示SNI(Server Name Indication)。是否發送SNI取決于服務器的配置和需求。對于使用同一個IP地址來托管多個TLS/SSL證書的服務器的情況,建議選擇發送SNI。callbackSNI取值如下:
true:發送SNI。
false(默認值):不發送SNI。
說明OSS通過英國(倫敦)地域發起回調請求時,無論callbackSNI取值為true或者false,均會發送SNI。除英國(倫敦)地域以外,OSS支持的其他地域遵守callbackSNI取值約定的行為。
callbackBodyType
否
發起回調請求的Content-Type。Content-Type支持以下兩種類型:
application/x-www-form-urlencoded(默認值)
將經過URL編碼的值替換callbackBody中的變量。
application/json
按照JSON格式替換callbackBody中的變量。
JSON字段示例如下:
示例一(包含必選參數和可選參數)
{ "callbackUrl":"172.16.XX.XX/test.php", "callbackHost":"oss-cn-hangzhou.aliyuncs.com", "callbackBody":"{\"mimeType\":${mimeType},\"size\":${size}}", "callbackBodyType":"application/json", "callbackSNI":false }
示例二(僅包含必選參數)
{ "callbackUrl":"172.16.XX.XX: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}&x:my_var=${x:my_var}" }
callbackBody中可以設置的系統參數請參見下表。
系統參數
含義
bucket
存儲空間名稱。
object
對象(文件)的完整路徑。
etag
文件的ETag,即返回給用戶的ETag字段。
size
Object大小。調用CompleteMultipartUpload時,size為整個Object的大小。
mimeType
資源類型,例如jpeg圖片的資源類型為image/jpeg。
imageInfo.height
圖片高度。該變量僅適用于圖片格式,對于非圖片格式,該變量的值為空。
imageInfo.width
圖片寬度。該變量僅適用于圖片格式,對于非圖片格式,該變量的值為空。
imageInfo.format
圖片格式,例如JPG、PNG等。該變量僅適用于圖片格式,對于非圖片格式,該變量的值為空。
crc64
與上傳文件后返回的x-oss-hash-crc64ecma頭內容一致。
contentMd5
與上傳文件后返回的Content-MD5頭內容一致。
重要僅在調用PutObject和PostObject接口上傳文件時,該變量的值不為空。
vpcId
發起請求的客戶端所在的VpcId。如果不是通過VPC發起請求,則該變量的值為空。
clientIp
發起請求的客戶端IP地址。
reqId
發起請求的RequestId。
operation
發起請求的接口名稱,例如PutObject、PostObject等。
callback-var自定義參數
您可以通過callback-var參數來配置自定義參數。自定義參數是一個Key-Value對(例如:
my_var=${x:my_var}
)。在OSS發起POST回調請求的時候,會將callback-var自定義參數以及上述系統參數放在POST請求的body中,方便用戶接收回調信息。構造自定義參數的方法和callBack參數的方法相同,也是以JSON格式來傳遞。該JSON字符串是一個包含所有自定義參數的Key-Value對。
重要自定義參數的Key必須以
x:
開頭且為小寫,否則即使OSS返回200,自定義參數也不能被正確賦值。
步驟2:構造回調請求
將Callback和callback-var參數附加到OSS請求時,需要對上述構造的JSON字符串使用base64編碼,然后選用以下任意一種方式將參數附加到OSS的請求中。
構造callback(x-oss-callback)
構造回調參數。
{ "callbackUrl": "http://xxx.xxx.22.143/test", "callbackHost": "your.callback.com", "callbackBody": "bucket=${bucket}&object=${object}&my_var=${x:my_var}", "callbackBodyType": "application/x-www-form-urlencoded", "callbackSNI": false }
將上面的JSON字符串使用Base64進行編碼后得到編碼后的結果callback(x-oss-callback)。
ewogICAg****bGxiYWNrVXJsIjogImh0dHA6Ly94eHgueHh4LjIyLjE0My90ZXN0IiwKICAgICJjYWxsYmFja0hvc3QiOiAieW91ci5jYWxsYmFjay5jb20iLAogICAgImNhbGxiYWNrQm9keSI6ICJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyPSR7eDpteV92YXJ9IiwKICAgICJjYWxsYmFja0JvZHlUeXBlIjogImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIsCiAgICAiY2FsbGJhY2tTTkkiOiBmYWxzZQp9
構造callback-var(x-oss-callback-var)
構造回調參數。
{"x:my_var": "var"}
將自定義變量使用Base64進行編碼后得到編碼后的結果callback-var(x-oss-callback-var)。
eyJ4Om15X3ZhciI6ICJ2YX****==
在URL中攜帶參數
在URL中攜帶參數時,callback
為必選參數,callback-var
為可選參數。如果請求中出現了callback
或callback-var
,則在計算簽名時,需要將callback或者callback-var參數作為CanonicalizedResource的子資源。更多信息,請參見構建CanonicalizedResource的方法。
PUT /your_object?OSSAccessKeyId=LTAI5t7h6SgiLSga****&Signature=vjbyPxybdZaNmGa%2ByT272YEAiv****&Expires=1682484377&callback-var=eyJ4Om15X3ZhciI6ICJ2YX****==&callback=bGxiYWNrVXJsIjogImh0dHA6Ly94eHgueHh4LjIyLjE0My90ZXN0IiwKICAgICJjYWxsYmFja0hvc3QiOiAieW91ci5jYWxsYmFjay5jb20iLAogICAgImNhbGxiYWNrQm9keSI6ICJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyPSR7eDpteV92YXJ9IiwKICAgICJjYWxsYmFja0JvZHlUeXBlIjogImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIsCiAgICAiY2FsbGJhY2tTTkkiOiBmYWxzZQp9 HTTP/1.1
Host: callback-test.oss-cn-hangzhou.aliyuncs.com
Date: Wed, 26 Apr 2023 03:46:17 GMT
Content-Length: 5
Content-Type: text/plain
在Header中攜帶參數
在Header中攜帶參數時,需要將x-oss-callback
或者x-oss-callback-var
作為Header帶入請求發送。在計算簽名CanonicalizedOSSHeaders時,將x-oss-callback-var和x-oss-callback計算在內。
完整示例如下:
PUT /your_object HTTP/1.1
Host: callback-test.oss-test.aliyun-inc.com
Accept-Encoding: identity
Content-Length: 5
x-oss-callback-var: eyJ4Om15X3ZhciI6ICJ2YX****==
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: ewogICAg****bGxiYWNrVXJsIjogImh0dHA6Ly94eHgueHh4LjIyLjE0My90ZXN0IiwKICAgICJjYWxsYmFja0hvc3QiOiAieW91ci5jYWxsYmFjay5jb20iLAogICAgImNhbGxiYWNrQm9keSI6ICJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyPSR7eDpteV92YXJ9IiwKICAgICJjYWxsYmFja0JvZHlUeXBlIjogImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIsCiAgICAiY2FsbGJhY2tTTkkiOiBmYWxzZQp9
Host: callback-test.oss-test.aliyun-inc.com
Expect: 100-Continue
Date: Wed, 26 Apr 2023 03:46:17 GMT
Content-Type: text/plain
Authorization: OSS qn6q**************:77Dv****************
Test
在POST請求的Body中使用表單域來攜帶參數
使用PostObject接口上傳Object時,僅支持通過POST請求的Body中使用表單域來攜帶參數的方式指定回調參數。
如果需要在POST上傳Object時附帶回調參數,callback參數要使用獨立的表單域來附加,示例如下:
--9431149156168 Content-Disposition: form-data; name="callback" eyJjYWxsYmFja1VybCI6IjEwLj****4xN
如果是自定義參數,不能直接將callback-var參數直接附加到表單域中。每個自定義的參數都需要使用獨立的表單域來添加。例如,自定義參數JSON字段示例如下:
{ "x:var1":"value1", "x:var2":"value2" }
則POST請求的表單域為:
--9431149156168 Content-Disposition: form-data; name="callback" eyJjYWxsYmFja1VybCI6IjEwLj****4xN --9431149156168 Content-Disposition: form-data; name="x:var1" value1 --9431149156168 Content-Disposition: form-data; name="x:var2" value2
同時,您還可以在policy中添加callback條件。如果不添加callback條件,則不對該參數做上傳驗證,如下所示。
{ "expiration": "2021-12-01T12:00:00.000Z", "conditions": [ {"bucket": "johnsmith" }, {"callback": "eyJjYWxsYmFja1V****4My"}, ["starts-with", "$key", "user/eric/"], ] }
步驟3:發起回調請求
如果文件上傳成功,OSS會根據請求中的callback參數和callback-var自定義參數,將特定內容以POST方式發送給應用服務器。
POST /test HTTP/1.1
Host: your.callback.com
Connection: close
Authorization: GevnM3**********3j7AKluzWnubHSVWI4dY3VsIfUHYWnyw==
Content-MD5: iKU/O/JB***ZMd8Ftg==
Content-Type: application/x-www-form-urlencoded
Date: Tue, 07 May 2024 03:06:13 GMT
User-Agent: aliyun-oss-callback
x-oss-bucket: your_bucket
x-oss-pub-key-url: aHR0cHM6Ly9nb3NzcHVi**********vY2FsbGJeV92MS5wZW0=
x-oss-request-id: 66399AA50*****3334673EC2
x-oss-requester: 23313******948342006
x-oss-signature-version: 1.0
x-oss-tag: CALLBACK
bucket=your_bucket&object=your_object&my_var=var
(可選)步驟4:回調簽名
設置Callback參數后,OSS將按照您設置的callbackUrl發送POST回調請求給用戶的應用服務器。應用服務器收到回調請求后,如果希望驗證回調請求是否由OSS發起,可以通過在回調中攜帶簽名來驗證OSS身份。
驗證簽名為可選步驟,您可以根據實際情況決定是否驗證簽名。
步驟一:生成簽名
生成簽名的方式
采用RSA非對稱方式生成簽名。
authorization = base64_encode(rsa_sign(private_key, url_decode(path) + query_string + '\n' + body, md5))
說明其中,private_key為私鑰,path為回調請求的資源路徑,query_string為查詢字符串,body為回調的消息體。
生成簽名的步驟
獲取待簽名字符串:資源路徑經過URL解碼后,會附加原始的查詢字符串、一個回車符以及回調消息體。
RSA簽名:使用密鑰對待簽名字符串進行簽名,簽名的哈希函數為md5。
將簽名后的結果做Base64編碼,獲取最終的簽名,然后將簽名放在回調請求的authorization頭中。
生成簽名的示例
POST /index.php?id=1&index=2 HTTP/1.0 Host: 172.16.XX.XX Connection: close Content-Length: 18 authorization: kKQeGTRccDKyHB3H9vF+xYMSrmhMZj****/kdD1ktNVgbWEfYTQG0G2SU/RaHBovRCE8OkQDjC3uG33esH2t**** Content-Type: application/x-www-form-urlencoded User-Agent: http-client/0.0.1 x-oss-pub-key-url: aHR0cDovL2dvc3NwdWJsaWMuYWxpY2RuLmNvbS9jYWxsYmFja19wdWJfa2V5X3YxLnsr**** bucket=examplebucket
path為
/index.php
,query_string為?id=1&index=2
,body為bucket=examplebucket
,最終簽名結果為kKQeGTRccDKyHB3H9vF+xYMSrmhMZjzzl2/kdD1ktNVgbWEfYTQG0G2SU/RaHBovRCE8OkQDjC3uG33esH2t****
。
步驟二:驗證簽名
驗證簽名的方式
通過應用服務器驗證簽名示例如下:
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編碼,需要對x-oss-pub-key-url
執行base64解碼后獲取公鑰。public_key = urlopen(base64_decode(x-oss-pub-key-url頭的值))
例如,獲取到公鑰的URL地址為
aHR0cDovL2dvc3NwdWJsaWMuYWxpY2RuLmNvbS9jYWxsYmFja19wdWJfa2V5X3YxLnBlbQ==
,經過base64解碼后得到http://gosspublic.alicdn.com/callback_pub_key_v1.pem
。說明OSS頒發的public_key中
x-oss-pub-key-url
頭的值必須以http://gosspublic.alicdn.com/
或者https://gosspublic.alicdn.com/
開頭。由于公鑰地址的內容不變,因此推薦根據公鑰地址緩存公鑰內容,以避免因為網絡或者公鑰地址所在服務問題造成的業務異常。獲取base64解碼后的簽名。
signature = base64_decode(authorization頭的值)
獲取待簽名字符串,方法與簽名一致。
sign_str = url_decode(path) + query_string + ‘\n’ + body
驗證簽名。
result = rsa_verify(public_key, md5(sign_str), signature)
驗證簽名示例
以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,recommend caching public key content based on the public key address 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 according 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()
其他語言的服務端代碼請參見下表。
SDK語言
描述
Java
下載地址:Java
運行方法:解壓包運行
java -jar oss-callback-server-demo.jar 9000
(9000指運行的端口,可以自行指定)。
Python
下載地址:Python
運行方法:解壓包直接運行
python callback_app_server.py
,運行該程序需要安裝RSA的依賴。
Go
下載地址:Go
運行方法:解壓后參看README.md。
PHP
下載地址:PHP
運行方法:部署到Apache環境下,因為PHP本身語言的特點,取一些數據頭部會依賴于環境。所以可以參考例子根據所在環境修改。
.NET
下載地址:.NET
運行方法:解壓后參看
README.md
。
Node.js
下載地址:Node.js
運行方法:解壓包直接運行
node example.js
。
Ruby
下載地址:Ruby
運行方法:ruby aliyun_oss_callback_server.rb
步驟5:返回回調結果
應用服務器返回響應給OSS。返回的回調請求為:
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"}
應用服務器返回OSS的響應必須帶有Content-Length的Header,Body大小不能超過1 MB。
步驟6:返回上傳結果
OSS將應用服務器返回的內容返回給用戶。
返回的內容響應為:
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中存在內容(例如JSON格式的信息),使用上傳回調功能后會覆蓋原有的body中的內容,例如{"a":"b"}
。
常見問題
OSS上傳文件失敗后是否會發送回調通知給應用服務器?
不會。OSS上傳文件成功后會執行回調,如果上傳失敗,則不執行回調,直接返回錯誤信息。
報錯Response body is not valid json format.如何處理?
應用服務器處理過程中拋出異常,導致返回給OSS的Body不是JSON格式,如下圖所示:
解決方法:
通過以下命令確認內容。
curl -d "<Content>" <CallbackServerURL> -v
通過抓包確認內容。
Windows下推薦使用工具Wireshark抓包,Linux下使用命令tcpdump抓包。
應用服務器返回給OSS的Body中帶有BOM頭。
這類錯誤常見于使用PHP SDK編寫的應用服務器中。由于PHP SDK返回了BOM頭,導致OSS收到的Body中多了三個字節,出現不符合JSON格式的Body。如下圖所示,ef bb bf這三個字節為BOM頭。
解決方法:刪除應用服務器返回OSS Body中的BOM頭。
EC錯誤碼
使用Callback過程出現錯誤時,OSS返回的錯誤信息中包含EC,EC與錯誤原因一一對應。與Callback相關的EC錯誤碼,請參見07-CALLBACK。