服务端签名直传并设置上传回调概述

本文主要介绍如何基于Post Policy的使用规则在服务端通过各种语言代码完成签名,并且设置上传回调,然后通过表单直传数据到OSS。

背景信息

大多数情况下,用户上传文件后,应用服务器需要知道用户上传了哪些文件以及文件名;如果上传了图片,还需要知道图片的大小等,为此OSS提供了上传回调方案。

流程介绍

当用户要上传一个文件到OSS,而且希望将上传的结果返回给应用服务器时,需要设置一个回调函数,将请求告知应用服务器。用户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户。

image

操作示例

服务端签名直传并设置上传回调提供了以下语言的操作示例:

流程解析

以下根据流程讲解核心代码和消息内容。

  1. 用户向应用服务器请求上传Policy和回调。

    在客户端源码中的upload.js文件中,如下代码片段的变量serverUrl的值可以用来设置应用服务器的URL。设置好之后,客户端会向该serverUrl发送Get请求来获取需要的信息。

    // serverUrl是用户获取签名和Policy等信息的应用服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
    serverUrl = 'http:/88.88.XX.XXX:8888';
  2. 应用服务器返回上传Policy和回调设置代码。

    应用服务器侧的签名直传服务会处理客户端发过来的Get请求消息,您可以设置对应的代码让应用服务器能够给客户端返回正确的消息。各个语言版本的配置文档中都有明确的说明供您参考。

    以下是签名直传服务返回给客户端消息Body内容的示例,Body的内容将作为客户端上传文件的重要参数。

    {
    "accessid":"LTAI**********",
    "host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
    "policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8i****",
    "signature":"I2u5**********",
    "expire":1446727949,
    "callback":"eyJjYWxsYmFja1VybCI6Imh0dHA6Ly9vc3MtZGVtby5hbGl5dW5jcy5jb206MjM0NTAiLAoiY2FsbGJhY2tCb2R5IjoiZmlsZW5hbWU9JHtvYmplY3R9JnNpemU9JHtzaXplfSZtaW1lVHlwZT0ke21pbWVUeXBlfSZoZWlnaHQ9JHtpbWFnZUluZm8uaGVpZ2h0fSZ3aWR0aD0ke2ltYWdlSW5mby53aWR0aH0iLAoiY2FsbGJhY2tCb2R5VHlwZSI6ImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCJ9",
    "dir":"user-dirs/"
    }

    上述示例的callback内容采用的是Base64编码。经过Base64解码后的内容如下:

    {"callbackUrl":"http://oss-demo.aliyuncs.com:23450",
    "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。

    取值:

    • application/x-www-form-urlencoded(默认值)

    • application/json

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

    在客户端源码upload.js文件中,callbackbody的值是步骤2中应用服务器返回给客户端消息Body中Callback的内容。

    new_multipart_params = {
         'key' : key + '${filename}',
         'policy': policyBase64,
         'OSSAccessKeyId': accessid,
         // 设置服务端返回状态码为200,不设置则默认返回状态码204。
         'success_action_status' : '200', 
         'callback':  callbackbody,
         'signature': signature,
     };
  4. OSS根据用户的回调设置,发送回调请求给应用服务器。

    客户端上传文件到OSS结束后,OSS解析客户端的上传回调设置,发送Post回调请求给应用服务器。消息内容示例如下:

    说明

    此示例使用的是V1版本签名,如需使用V4签名发送请求,可参考V1签名升级为V4签名指引

    Hypertext Transfer Protocol
        POST / HTTP/1.1\r\n
        Host47.97.XXX.XX53\r\n
        Connection: close\r\n
        Content-Length: 76\r\n
        Authorization: fsNx********//a8x6v2lI1********\r\n
        Content-MD5: eiEMyp7lbL8KStPBzMdr9w==\r\n
        Content-Type: application/x-www-form-urlencoded\r\n
        Date: Sat, 15 Sep 2018 10:24:12 GMT\r\n
        User-Agent: aliyun-oss-callback\r\n
        x-oss-additional-headers: \r\n
        x-oss-bucket: signedcallback\r\n
        x-oss-owner: 137918634953****\r\n
        x-oss-pub-key-url: aHR0cDovL2dvc3NwdWJsaWMuYWxpY2RuLmNvbS9jYWxsYmFja19wdWJfa2V5X3YxLnaH****\r\n
        x-oss-request-id: 534B371674E88A4D8906****\r\n
        x-oss-requester: 137918634953****\r\n
        x-oss-signature-version: 1.0\r\n
        x-oss-tag: CALLBACK\r\n
        eagleeye-rpcid: 0.1\r\n
        \r\n
        [Full request URI: http://47.xx.xx.53/]
        [HTTP request 1/1]
        [Response in frame: 39]
        File Data: 76 bytes
    HTML Form URL Encoded: application/x-www-form-urlencoded
        Form item: "filename" = ".snappython.png"
        Form item: "size" = "6014"
        Form item: "mimeType" = "image/png"
        Form item: "height" = "221"
  5. 应用服务器返回响应给OSS。

    应用服务器根据OSS发送消息中的authorization来进行验证,如果验证通过,则向OSS返回如下JSON格式的成功消息。

    {
    "String value": "ok",
    "Key": "Status"
    }
  6. OSS将应用服务器返回的消息返回给用户。

客户端源码解析

客户端源码下载地址:aliyun-oss-appserver-js-master.zip

说明

客户端JavaScript代码使用的是Plupload组件。Plupload是一款简单易用且功能强大的文件上传工具, 支持多种上传方式,包括HTML5、Flash、SilverLight、HTML4。它会智能检测当前环境,选择最适合的上传方式,并且会优先采用HTML5方式。详情请参见Plupload官网

以下为常见功能的代码示例:

  • 设置成随机文件名

    若上传时采用固定格式的随机文件名,且后缀跟客户端文件名保持一致,可以将函数改为:

    function check_object_radio() {
        g_object_name_type = 'random_name';
    }
  • 设置成用户的文件名

    如果想在上传时设置成用户的文件名,可以将函数改为:

    function check_object_radio() {
        g_object_name_type = 'local_name';
    }
  • 设置上传目录

    上传的目录由服务端指定, 每个客户端只能上传到指定的目录,实现安全隔离。下面的代码以PHP为例,将上传目录改成abc/,注意目录必须以正斜线(/)结尾。

    $dir ='abc/';
  • 设置上传过滤条件

    您可以利用Plupload的属性filters设置上传的过滤条件,如设置只能上传图片、上传文件的大小、不能有重复上传等。

    var uploader = new plupload.Uploader({
        ……
        filters: {
            mime_types : [ 
            // 只允许上传图片和ZIP文件。
            { title : "Image files", extensions : "jpg,gif,png,bmp" },
            { title : "Zip files", extensions : "zip" }
            ], 
            // 最大只能上传400KB的文件。
            max_file_size : '400kb', 
            // 不允许选取重复文件。
            prevent_duplicates : true 
        },
    • mime_types:限制上传的文件后缀。

    • max_file_size:限制上传的文件大小。

    • prevent_duplicates:限制不能重复上传。

  • 获取上传后的文件名

    如果要知道文件上传成功后的文件名,可以用Plupload调用FileUploaded事件获取,如下所示:

    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);
                }
                else
                {
                    document.getElementById(file.id).getElementsByTagName('b')[0].innerHTML = info.response;
                }
        }

    可以利用get_uploaded_object_name(file.name)函数,得到上传到OSS的文件名,其中file.name记录了本地文件上传的名称。

  • 上传签名

    JavaScript可以从服务端获取policyBase64、accessid、signature这三个变量,核心代码如下:

    function get_signature()
    {
        // 判断expire的值是否超过了当前时间,如果超过了当前时间,则重新获取签名,缓冲时间为3秒。
        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;
    };

    从服务端返回的消息解析如下:

    说明

    以下仅为示例,并不要求相同的格式,但必须包含accessid、policy、signature三个值。

    {"accessid":"LTAI**********",
    "host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
    "policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8i****",
    "signature":"I2u5**********",
    "expire":1446726203,"dir":"user-dir/"}
    • accessid:用户请求的accessid。

    • host:用户要往哪个域名发送上传请求。

    • policy:用户表单上传的策略(Policy),是经过Base64编码过的字符串。详情请参见Post Policy

    • signature:对Policy签名后的字符串。

    • expire:上传策略Policy失效时间,在服务端指定。失效时间之前都可以利用此Policy上传文件,无需每次上传都去服务端获取签名。

    说明

    为了减少服务端的压力,初始化上传时,每上传一个文件,获取一次签名。再次上传文件时,对当前时间与签名时间进行比较,并查看签名时间是否失效。如果签名已失效,则重新获取一次签名;如果签名未失效,则使用之前的签名。

    解析Policy的内容如下:

    {"expiration":"2015-11-05T20:23:23Z",
    "conditions":[["content-length-range",0,1048576000],// 上传文件的大小限制默认为5 GB。您可以自定义上传文件的大小限制。如果超过此限制,文件上传到OSS会报错。
    ["starts-with","$key","user-dir/"]]}

    上面Policy中增加了starts-with,用来指定此次上传的文件名必须以user-dir开头,用户也可以自行指定。增加starts-with的原因是,在众多场景下,一个应用对应一个Bucket。为了防止数据覆盖,用户上传到OSS的每个文件都可以有特定的前缀。但这样存在一个问题,用户获取到这个Policy后,在失效期内都能修改上传前缀,从而上传到其他用户的目录下。为解决该问题,可在应用服务器端指定用户上传文件的前缀。这样即便用户获取了Policy也没有办法上传到其他用户的目录,从而保证了数据的安全性。

  • 设置应用服务器的地址

    在客户端源码upload.js文件中,如下代码片段的变量serverUrl的值可以用来设置应用服务器的URL,设置完成后,客户端会向该serverUrl发送Get请求来获取信息。

    // serverUrl是用户获取签名和Policy等信息的应用服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
    serverUrl = 'http:/88.88.XX.XX8:8888'

常见问题

前端如何实现批量上传文件?

OSS没有开放批量上传接口,如果需要批量上传,您可以使用一个循环去上传所有文件,示例与上传单个文件一致。

使用服务端签名直传并设置上传回调时支持自定义回调请求的header头部信息么?

不支持。您只能自定义回调参数,也就是回调的body,但是OSS回调请求您的callback url时携带的header无法自定义。