
# Web端直传OBS并设置上传回调
#### 应用场景
常见的Web端上传方式是用户通过浏览器上传文件至应用服务器，再由应用服务器上传至OBS，如[图1]所示，数据需要在应用服务器中转，传输效率较低，且多任务同时上传时应用服务器压力很大。
图1常见的Web端上传文件方式   
![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002355535970.png "点击放大")
本文介绍一种在Web端利用PostObject接口直传文件至OBS并设置上传回调的方法，即使用表单上传方式上传文件至OBS，上传成功后会回调指定的地址。如[方案架构]所示，该方案省去了在应用服务器中转文件这一步骤，由Web端直传文件至OBS，提高了传输效率，且不会对服务器产生压力，同时服务端签名后直传可以保证传输的安全性。
 #### 方案架构
图2Web端PostObject直传并设置上传回调架构图   
![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002389056033.png "点击放大")
整个Web端直传文件至OBS并设置上传回调的请求流程如下：
1. 用户通过Web客户端向服务端发送上传安全策略请求；
2. 服务端使用访问凭证和上传安全策略计算签名，然后向Web端返回上传安全策略和签名；
3. 用户使用Web端构建的HTML表单上传文件至OBS并设置上传回调；
4. OBS向服务端发送回调请求；
5. 服务端返回响应至OBS；
6. OBS返回上传结果至Web端。
![](https://support.huaweicloud.com/bestpractice-obs/public_sys-resources/note_3.0-zh-cn.png)
安全策略（policy）的作用是限制表单上传的内容，例如规定表单上传对象的对象名前缀必须以"prefix01"开头，使用policy能够帮助您更好的管控桶中的文件。
#### 资源和成本规划
表1资源规划 
| 区域     | 资源    | 资源名称               | 资源说明                                                                                                                                                      | 数量 | 费用                                                                                     |
|:---|:---|:---|:---|:---|:---|
| 华北-北京四 | OBS桶  | post-callback-demo | 用于存储用户上传文件的桶。 您需要在OBS控制台创建桶。                                                                                                | 1  | 根据文件占用的存储空间收费，具体请参见[存储费用](https://support.huaweicloud.com/price-obs/obs_42_0003.html)。 |
| -      | 文件    | demo-object.txt    | 用于上传的文件。 您需要提前准备好上传的文件。                                                                                                     | 1  | -                                                                                      |
| -      | AK/SK | AK/SK              | AK/SK为访问OBS的凭证，服务端使用AK/SK生成签名，进行用户鉴权。 AK/SK相关内容，详情请参见[访问密钥](https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html)。 | 1  | -                                                                                      |
   
#### 操作流程
要实现Web端直传OBS并设置上传回调，需要以下三步：
1. **创建桶并配置CORS规则：**在OBS控制台创建一个桶，用于存储用户上传的文件；同时为桶配置跨域资源共享（CORS），以允许来自Web端的跨域名访问。
2. **服务端生成签名：**服务端使用访问凭证和上传策略（如：桶名称、对象名前缀、过期时间等）生成签名，授权用户在指定时间内完成文件上传。
3. **Web端通过表单上传对象并设置上传回调：**Web端在HTML表单中构建请求并设置上传回调，此请求使用POST表单上传来直接调用OBS的服务端，实现文件上传。
 
#### 实施步骤
#### 步骤一：创建桶并配置CORS规则
在OBS控制台创建一个桶，用于存储用户上传的文件；同时为桶配置跨域资源共享（CORS），以允许来自Web端的跨域名访问。
**1. 创建桶**
如果您已有桶，可跳过本步骤直接[配置CORS规则]。
1. 在OBS[管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/obs/manager/buckets)左侧导航栏选择"桶列表"。
2. 在页面右上角单击"创建桶"。
3. 设置相关参数。 
   表2创建桶参数说明 
   | 参数名称 | 示例                     | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
   |:---|:---|:---|
   | 区域   | **华北-北京四**             | 桶所属的区域。 - **桶创建成功后，不支持变更区域，请谨慎选择。**  - 请选择靠近您业务的区域创建桶，以降低网络时延，提高访问速度。                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
   | 桶名称  | **post-callback-demo** | 创建桶时，需要设置合适的桶名称。 **桶创建成功后，不支持修改桶名称。** OBS中桶按照DNS规范进行命名，DNS规范为全球通用规则，其具体命名规则如下： - 需全局唯一，不能与已有的任何桶或并行文件系统（包含其他用户创建的）名称重复。**删除桶后，立即创建同名桶或并行文件系统会创建失败，需要等待30分钟才能创建。**  - 长度范围为3到63个字符。  - 支持小写字母、数字、中划线（-）、英文句号（.），且不能以中划线（-）或英文句号（.）开头及结尾。  - 禁止两个英文句号（.）相邻，禁止英文句号（.）和中划线（-）相邻。  - 禁止使用IP地址。   |
   | 企业项目 | **default**            | 将桶加入到企业项目中统一管理。如无特殊的企业项目划分和管理需求，此处可直接选择默认企业项目"default"。 如果您想要了解更多关于如何通过企业项目管理OBS桶，具体请参见[创建桶](https://support.huaweicloud.com/usermanual-obs/zh-cn_topic_0045829088.html#section3)中的"企业项目"参数说明。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
      
   
   
4. 单击右下角的"立即创建"，确认提示信息，并单击"确定"。
5. 在"创建成功"弹窗，单击"确定"。在桶列表页可以看到新创建的桶，即表示创建成功。
**2. 配置CORS规则**
在通常的网页请求中，由于同源安全策略（Same-Origin Policy，SOP）的存在，不同域之间的网站脚本和内容是无法进行交互的。
跨域资源共享CORS是一种网络浏览器的规范机制，定义了一个域中加载的客户端Web应用程序与另一个域中的资源交互的方式。OBS支持CORS规范，允许跨域请求访问OBS中的资源。在OBS控制台配置CORS规则步骤如下：
1. 在OBS[管理控制台](https://console.huaweicloud.com/console/?locale=zh-cn#/obs/manager/buckets)左侧导航栏选择"桶列表"。
2. 在桶列表单击待操作的桶，进入对象页面。
3. 在左侧导航栏，单击"数据安全 \> CORS规则"。
4. 单击"创建"系统弹出"创建CORS规则"弹窗。
5. 设置相关参数。 
   图3创建CORS规则   
   ![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002416453257.png "点击放大")
   表3CORS规则参数说明 
   | 参数名称  | 示例                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | 说明                                                                                                                                                                                                                                               |
   |:---|:---|:---|
   | 允许的来源 | **\***                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 指定允许的跨域请求的来源，即允许来自该域名下的请求访问该桶。 允许填写的字符范围：1\~1024。 允许多条匹配规则，以回车换行为间隔。每个匹配规则允许使用最多一个"\*"通配符。例如： ``` http://rds.example.com https://*.vbs.example.com ``` |
   | 允许的方法 | **Get、Post、Put、Delete、Head**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 指定允许的跨域请求方法，即桶和对象的几种操作类型。包括：Get、Post、Put、Delete、Head。                                                                                                                                                                                            |
   | 允许的头域 | **\***                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 可选参数，指定允许的跨域请求的头域。只有匹配上允许的头域中的配置，才被视为是合法的CORS请求。 允许填写的字符范围：1\~1024。 允许的头域可设置多个，多个头域之间换行隔开，每行最多可填写一个\*符号，不支持\&、:、\<、空格以及中文字符。                                                          |
   | 补充头域  | - **ETag**  - **x-obs-request-id**  - **x-obs-api**  - **Content-Type**  - **Content-Length**  - **Cache-Control**  - **Content-Disposition**  - **Content-Encoding**  - **Content-Language**  - **Expires**  - **x-obs-id-2**  - **x-reserved-indicator**  - **x-obs-version-id**  - **x-obs-copy-source-version-id**  - **x-obs-storage-class**  - **x-obs-delete-marker**  - **x-obs-expiration**  - **x-obs-website-redirect-location**  - **x-obs-restore**  - **x-obs-version**  - **x-obs-object-type**  - **x-obs-next-append-position**  - **x-obs-callback**   | 可选参数，指定CORS响应中带的补充头域，给客户端提供额外的信息。 允许填写的字符范围：1\~1024。 补充头域可设置多个，多个头域之间换行隔开，不支持\*、\&、:、\<、空格以及中文字符。                                                                                 |
   | 缓存时间  | **300**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | 请求来源的客户端可以缓存的CORS响应时间，以秒为单位，默认为100秒。 输入值范围：0\~9999999。                                                                                                                                                            |
      
   
   
6. 单击"确定"。 
   "CORS规则"页签显示"创建CORS规则成功"提示即表示成功创建桶的CORS规则。CORS规则会在两分钟内生效。
   
   
 
#### 步骤二：服务端生成签名
服务端使用访问凭证（AK/SK/securityToken\[可选\]）和上传策略（如：桶名称、对象名前缀、过期时间等）生成签名，授权用户在指定时间内完成文件上传。获取或新建AK/SK，请参见[访问密钥](https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html)。
![](https://support.huaweicloud.com/bestpractice-obs/public_sys-resources/caution_3.0-zh-cn.png)
建议先将访问凭证（AK/SK/securityToken\[可选\]）配置到环境变量，避免在代码中直接显示访问凭证而引起敏感信息泄露。
**1. 配置环境变量**
- **Linux系统**
  1. 在命令行中执行以下命令，将环境变量添加到\~/.bashrc中。
     ```
     echo "export ACCESS_KEY_ID='your access key id'" >> ~/.bashrc
     echo "export SECRET_ACCESS_KEY_ID='your secret access key id'" >> ~/.bashrc
     ```
     
  
  2. 在命令行中执行以下命令，使[1]的设置生效。
     ```
     source  ~/.bashrc
     ```
     
  
  3. 在命令行中执行以下命令，确认环境变量是否配置成功。
     ```
     echo $ACCESS_KEY_ID
     echo $SECRET_ACCESS_KEY_ID
     ```
     - 如果命令行中显示[1]设置的信息，表示设置成功。
     
     - 如果命令行中未显示[1]设置的信息，表示设置失败，请重新设置。
      
   
- **Windows系统**
  1. 在Windows系统的命令行解释器（Command Prompt，CMD）中执行以下命令，设置环境变量。
     ```
     setx ACCESS_KEY_ID "your access key id"
     setx SECRET_ACCESS_KEY_ID "your secret access key id"
     ```
     
  
  2. 打开新的CMD窗口并执行以下命令，确认环境变量是否配置成功。
     ```
     echo %ACCESS_KEY_ID%
     echo %SECRET_ACCESS_KEY_ID%
     ```
     - 如果命令行中显示[1]设置的信息，表示设置成功。
     
     - 如果命令行中未显示[1]设置的信息，表示设置失败，请重新设置。
      
   
- **macOS系统**
  1. 打开bash终端，执行以下命令，将环境变量添加到\~/.bash_profile中。
     ```
     echo "export ACCESS_KEY_ID='your access key id'" >> ~/.bash_profile
     echo "export SECRET_ACCESS_KEY_ID='your secret access key id'" >> ~/.bash_profile
     ```
     
  
  2. 在命令行中执行以下命令，使[1]的设置生效。
     ```
     source  ~/.bashrc
     ```
     
  
  3. 执行以下命令，确认环境变量是否配置成功。
     ```
     echo $ACCESS_KEY_ID
     echo $SECRET_ACCESS_KEY_ID
     ```
     - 如果命令行中显示[1]设置的信息，表示设置成功。
     
     - 如果命令行中未显示[1]设置的信息，表示设置失败，请重新设置。
      
   
**2. 计算签名**
您可以通过使用编程语言的Web框架和OBS的SDK计算POST上传签名。POST表单上传是通过安全策略（policy）来限制表单上传的内容，例如规定表单上传对象的对象名前缀必须以"prefix01"开头，使用policy能够帮助您更好的管控桶中的文件。更多关于安全策略的内容，请参见[基于浏览器上传的表单中携带签名](https://support.huaweicloud.com/api-obs/obs_04_0012.html)。
参考如下**Java**示例代码获取访问凭证并计算签名：
```
@Controller
public class FormUploadCallbackController {
    String bucket = "post-callback-demo";
    // 以北京四的OBS地址为例
    String host = "obs.cn-north-4.myhuaweicloud.com";
    String endpoint = "https://" + host;
    String callbackUrl = "http://obs-demo.huaweicloud.com:12345/callback";
    //限定上传到OBS的文件前缀
    String prefix = "demo";
    @GetMapping("/obs-post-callback-signature")
    public ResponseEntity<Map<String, String>> getSignature() throws Exception {
        // 您可以通过环境变量获取访问密钥AK/SK，也可以使用其他外部引入方式传入。如果使用硬编码可能会存在泄露风险。
        // 您可以登录访问管理控制台获取访问密钥AK/SK
        String ak = System.getenv("ACCESS_KEY_ID");
        String sk = System.getenv("SECRET_ACCESS_KEY_ID");
        // 【可选】如果使用临时AK/SK和SecurityToken访问OBS，同样建议您尽量避免使用硬编码，以降低信息泄露风险。
        // 您可以通过环境变量获取访问密钥AK/SK/SecurityToken，也可以使用其他外部引入方式传入。
        // String securityToken = System.getenv("SECURITY_TOKEN");
        // 创建ObsClient实例
        // 【可选】使用临时AK/SK和SecurityToken初始化客户端
//      try ( ObsClient obsClient = new ObsClient(ak, sk, securityToken, endpoint)){
        // 使用永久AK/SK初始化客户端
        try ( ObsClient obsClient = new ObsClient(ak, sk, endpoint)){
            // 1. 创建策略条件
            // 生成基于表单上传的请求
            PostSignatureRequest request = new PostSignatureRequest();
            // 设置表单参数
            Map<String, Object> formParams = new HashMap<>();
            // 设置对象ACL为公共读，需要其它配置可自行修改
            formParams.put("x-obs-acl", "public-read");
            request.setFormParams(formParams);
            List<String> conditions = new ArrayList<>();
            // 设置Post上传请求的签名条件
            conditions.add("[\"starts-with\", \"$key\", \"demo/\"]");
            conditions.add("{\"bucket\": \"post-callback-demo\"}");
            request.setConditions(conditions);
            // 设置表单上传请求有效期，单位：秒
            request.setExpires(3600);
            PostSignatureResponse response = obsClient.createPostSignature(request);
            System.out.println("createPostSignature successfully");
            // 获取表单上传请求参数
            System.out.println("Policy:" + response.getPolicy());
            System.out.println("Signature:" + response.getSignature());
            Map<String, String> signResponse = new HashMap<>();
            signResponse.put("prefix", prefix);
            signResponse.put("accessKeyId", ak);
            signResponse.put("policy", response.getPolicy());
            signResponse.put("signature", response.getSignature());
            // 【可选】如果使用临时AK/SK和SecurityToken访问OBS,此处返回securityToken
//          signResponse.put("securityToken",securityToken);
            signResponse.put("host", host);
            signResponse.put("bucket", bucket);
            signResponse.put("callbackUrl", callbackUrl);
            signResponse.put("callbackBody", "key=$(key)&hash=$(etag)&fname=$(fname)&fsize=$(size)");
            signResponse.put("callbackBodyType", "application/json");
            return ResponseEntity.ok(signResponse);
        } catch (ObsException e) {
            System.out.println("createPostSignature failed");
            // 请求失败,打印http状态码
            System.out.println("HTTP Code:" + e.getResponseCode());
            // 请求失败,打印服务端错误码
            System.out.println("Error Code:" + e.getErrorCode());
            // 请求失败,打印详细错误信息
            System.out.println("Error Message:" + e.getErrorMessage());
            // 请求失败,打印请求id
            System.out.println("Request ID:" + e.getErrorRequestId());
            System.out.println("Host ID:" + e.getErrorHostId());
            e.printStackTrace();
            throw e;
        }
    }
}
```
#### 步骤三：Web端通过表单上传文件并设置上传回调
Web端接收到服务端返回的上传Policy和签名后，使用HTML表单构建请求并设置上传回调。此请求使用POST表单上传来直接调用OBS的服务端，实现文件上传。
Web端接收到服务端的响应示例如下：
```
{
    "accessKeyId": "****************", // 密钥AK
    "bucket": "post-callback-demo",    // 需要上传的桶名
    "callbackBodyType": "application/json", // 响应体格式
    "signature": "24*******************u8=", // POST上传的签名
    "prefix": "demo",   // POST上传对象名前缀
    "host": "obs.cn-north-4.myhuaweicloud.com", // POST上传host地址
    "callbackUrl": "http://obs-demo.huaweicloud.com:23450/callback",  // POST上传回调的地址
    "policy": "eyJleHBpcmF***************************************ifV19",  // POST上传policy
    "callbackBody": "key=$(key)&hash=$(etag)&fname=$(fname)&fsize=$(size)"   // POST上传回调的请求体
}
```
使用HTML表单构建请求并设置上传回调的示例代码如下：
```
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>华为云OBS表单上传</title>
</head>
<body>
<h1>OBS表单上传回调示例</h1>
<form>
    <div class="form-group">
        <label for="file" class="form-label">选择文件:</label>
        <input type="file" class="form-control" id="file" name="file" required/>
    </div>
    <button name="submit" value="Upload" type="submit">上传到OBS</button>
</form>
<div id="result"></div>
<script type="text/javascript">
    document.addEventListener('DOMContentLoaded', function () {
        const form = document.querySelector("form");
        const fileInput = document.querySelector("#file");
        const resultDiv = document.getElementById("result");
        form.addEventListener("submit", async (event) => {
            event.preventDefault();
            if (!fileInput.files.length) {
                resultDiv.innerHTML = '<p style="color:red">请选择文件</p>';
                return;
            }
            const file = fileInput.files[0];
            if (!file) {
                alert('请选择一个文件再上传。');
                return;
            }
            const filename = file.name;
            try {
                // 1. 获取签名参数
                resultDiv.innerHTML = '获取签名中...';
                const response = await fetch(`/obs-post-callback-signature`); 
                const params = await response.json();
                // 2. 构建表单数据
                const formData = new FormData();
                formData.append('key', params.prefix + "/" + filename);
                formData.append('x-obs-acl', 'public-read');
                formData.append('policy', params.policy);
                formData.append('AccessKeyId', params.accessKeyId);
                formData.append('signature', params.signature);
                formData.append('callbackUrl', params.callbackUrl);
                formData.append('callbackBody', params.callbackBody);
                formData.append('callbackBodyType', params.callbackBodyType);
                // 【可选】如果服务端签名使用了SecurityToken，在Form表单中也需要设置
                // formData.append('x-obs-security-token', params['x-obs-security-token']);
                formData.append('file', file);
                // 3. 提交到OBS
                resultDiv.innerHTML = '上传中...';
                const obsEndpoint = `https://${params.bucket}.${params.host}`;
                const uploadResponse = await fetch(obsEndpoint, {
                    method: 'POST',
                    body: formData
                });
                if (uploadResponse.status === 200) {
                    resultDiv.innerHTML = `<p style="color:green">上传成功! OBS路径: ${params.key}</p>
                                     <p>回调通知将发送到: ${new URLSearchParams(atob(params.callback)).get('callbackUrl')}</p>`;
                } else {
                    const error = await uploadResponse.text(); // 使用await
                    resultDiv.innerHTML = `<p style="color:red">上传失败! 状态码: ${uploadResponse.status}</p>
                                     <p>${error}</p>`;
                }
            } catch (error) {
                resultDiv.innerHTML = `<p style="color:red">发生错误: ${error.message}</p>`;
            }
        });
    });
</script>
</body>
</html>
```
#### 结果验证
HTML表单中包含一个文件选择框和一个上传按钮，用户可以选择想要上传的文件然后提交表单。
当表单提交时，JavaScript代码会请求服务器获取本次上传所需要的签名和回调信息，得到正确响应之后，会构造一个FormData对象， 然后填充所有必要数据，通过fetch方法发送POST请求到OBS的服务端，完成文件上传，上传成功后会回调callbackUrl设置的地址。
图4表单上传回调示例   
![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002389749301.png "点击放大")
通过表单上传一个名为"demo-object"的文件至桶"post-callback-demo"中，上传后在OBS桶列表中的"post-callback-demo"中可以看到"demo-object"文件，即表示成功通过表单上传文件。
图5查看所上传的文件   
![](https://support.huaweicloud.com/bestpractice-obs/zh-cn_image_0000002389760505.png "点击放大")
#### 相关文档
使用其它编程语言的Web框架和OBS的SDK计算POST上传签名信息，具体参考如下：
| [Java](https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0612.html) | [Python](https://support.huaweicloud.com/sdk-python-devg-obs/obs_22_0907.html) | C不支持 | Go不支持 | [BrowserJS](https://support.huaweicloud.com/sdk-browserjs-devg-obs/obs_24_0512.html) | .NET不支持 | [Android](https://support.huaweicloud.com/sdk-android-devg-obs/obs_26_0412.html) | iOS不支持 | [PHP](https://support.huaweicloud.com/sdk-php-devg-obs/obs_28_0409.html) | [Node.js](https://support.huaweicloud.com/sdk-nodejs-devg-obs/obs_29_0412.html) |
|---|---|---|---|---|---|---|---|---|---|
   
