全部產品
Search
文件中心

Object Storage Service:Callback

更新時間:Feb 28, 2024

您只需在發送給OSS的請求中攜帶相應的Callback參數即可實現回調。本文介紹Callback的實現原理。

注意事項

  • 華東1(杭州)、華東2(上海)、華北1(青島)、華北2(北京)、華北 3(張家口)、華北5(呼和浩特)、華北6(烏蘭察布)、華南1(深圳)、華南2(河源)、華南3(廣州)、西南1(成都)、中國香港、美國(矽谷)、美國(維吉尼亞)、日本(東京)、新加坡、澳大利亞(雪梨)、馬來西亞(吉隆坡)、印尼(雅加達)、菲律賓(馬尼拉)、印度(孟買)、德國(法蘭克福)、英國(倫敦)、阿聯酋(杜拜)地區支援設定Callback。

  • PutObjectPostObjectCompleteMultipartUpload介面支援設定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地址。

    • 為了保證正確處理中文等情況,callbackUrl需做URL編碼處理,例如http://example.com/中文.php?key=value&中文名稱=中文值需要編碼為http://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"
      }
    • 樣本二(僅包含必選參數)

      {
      "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}&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對。在OSS發起POST回調請求的時候,會將callback-var自訂參數以及上述系統參數放在POST請求的body中,方便使用者接收回調資訊。

    構造自訂參數的方法和callBack參數的方法相同,也是以JSON格式來傳遞。該JSON字串是一個包含所有自訂參數的Key-Value對。

    重要

    自訂參數的Key必須以x:開頭且為小寫,否則即使OSS返回200,自訂參數也不能被正確賦值。

    假設,您需要設定兩個自訂的參數,分別為x:var1x:var2,對應的值分別為value1和value2,則構造的JSON格式如下:

    {
    "x:var1":"value1",
    "x:var2":"value2"
    }

步驟2:構造回調請求

將Callback和callback-var參數附加到OSS請求時,需要對上述構造的JSON字串使用base64編碼,然後選用以下任意一種方式將參數附加到OSS的請求中。

在URL中攜帶參數

在URL中攜帶參數時,callback為必選參數,callback-var為選擇性參數。如果請求中出現了callbackcallback-var,則在計算簽名時,需要將callback或者callback-var參數作為CanonicalizedResource的子資源。更多資訊,請參見構建CanonicalizedResource的方法

PUT /test.txt?OSSAccessKeyId=LTAI5t7h6SgiLSga****&Signature=vjbyPxybdZaNmGa%2ByT272YEAiv****&Expires=1682484377&callback-var=eyJ4Om15X3ZhciI6ImZvci1jYWxsYmFjay10ZXN0****&callback=eyJjYWxsYmFja1VybCI6IjEyMS40My4xMTMuODoyMzQ1Ni9pbmRleC5odG1sIiwgICJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mZXRhZz0ke2V0YWd9JnNpemU9JHtzaXplfSZtaW1lVHlwZT0ke21pbWVUeXBlfSZpbWFnZUluZm8uaGVpZ2h0PSR7aW1hZ2VJbmZvLmhlaWdodH0maW1hZ2VJbmZvLndpZHRoPSR7aW1hZ2VJbmZvLndpZHRofSZpbWFnZUluZm8uZm9ybWF0PSR7aW1hZ2VJbmZvLmZvcm1hdH0mbXlfdmFyPSR7eDpteV92YXJ9**** 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 /test.txt HTTP/1.1
Host: callback-test.oss-test.aliyun-inc.com
Accept-Encoding: identity
Content-Length: 5
x-oss-callback-var: eyJ4Om15X3ZhciI6ImZvci1jYWxsYmFjay10ZXN0****
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: eyJjYWxsYmFja1****4x
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 mlepou3zr4u****:5a74vhd4UXpmyuudV14Kaen5****
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 /index.html HTTP/1.0
Host: 172.16.XX.XX
Connection: close
Content-Length: 181
Content-Type: application/x-www-form-urlencoded
User-Agent: http-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

(可選)步驟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為回調的訊息體。

  • 產生簽名的步驟

    1. 擷取待簽名字串:資源路徑經過URL解碼後,會附加原始的查詢字串、一個斷行符號符以及回調訊息體。

    2. RSA簽名:使用金鑰組待簽名字串進行簽名,簽名的雜湊函數為md5。

    3. 將簽名後的結果做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為回調頭中的簽名。

  • 驗證簽名的過程

    1. 回調請求的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/開頭。

    2. 擷取base64解碼後的簽名。

      signature = base64_decode(authorization頭的值)
    3. 擷取待簽名字串,方法與簽名一致。

      sign_str = url_decode(path) + query_string + ‘\n’ + body
    4. 驗證簽名。

      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
                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中存在內容(例如XML格式的資訊),使用上傳回調功能後會覆蓋原有的body中的內容,例如{"a":"b"}

錯誤碼

錯誤碼

HTTP狀態代碼

描述

InvalidArgument

400

傳入的Callback或者Callback-var參數不合法。不合法的情況的分類如下:

  • 在PutObject和CompleteMultipartUpload介面中URL和Header同時傳入Callback(x-oss-callback)或者Callback-var(x-oss-callback-var)。

  • Callback或者Callback-var參數過長,超過5 KB。

  • Callback或者Callback-var參數沒有經過Base64編碼或經過Base64解碼後不是合法的JSON格式。

  • Callback參數解析後,callbackUrl欄位包含的URL超過限制(5個),或者URL中傳入的連接埠不合法,樣本如下:

    {"callbackUrl":"172.16.XX.XX: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"}

CallbackFailed

203

檔案已經成功上傳到了OSS,但回調失敗。回調失敗表示OSS沒有收到預期的回調響應(例如,應用伺服器返回的內容不是JSON格式),不能說明應用伺服器沒有收到回調請求

常見問題

OSS上傳檔案失敗後是否會發送回調通知給應用伺服器?

不會。OSS上傳檔案成功後會執行回調,如果上傳失敗,則不執行回調,直接返回錯誤資訊。

報錯Response body is not valid json format.如何處理?

  • 應用伺服器處理過程中拋出異常,導致返回給OSS的Body不是JSON格式,如下圖所示:callback

    解決方案:

    • 通過以下命令確認內容。

      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頭。

    callback1

    解決方案:刪除應用伺服器返回OSS Body中的BOM頭。