项目
版本

Uppy AWS S3

@uppy/aws-s3 插件可用于直接将文件上传到 S3 存储桶或与 S3 兼容的提供商,如 Google Cloud Storage 或 DigitalOcean Spaces。上传可以通过 Companion、临时凭证或自定义签名函数进行签名。

何时应该使用它?

提示

不确定哪个上传器最适合您?阅读 “选择你需要的上传器” 。

当您更倾向于使用 客户端到存储 而不是 客户端到服务器再到存储( 例如 TransloaditTus )设置时,可以使用此插件。在某些情况下,这可能是首选,例如,为了减少成本或运行具有 Tus 的服务器和负载均衡器的复杂性 。

多部分上传对于大文件(100 MiB+)开始变得有价值,因为它将单个对象作为一系列部分上传。这有一定的好处,比如提高了吞吐量(并行上传部分)、快速从网络问题恢复(只需重试失败的部分)。缺点是请求开销,因为它除了上传请求外,还需要创建、签名( 除非你在 客户端签名 )和完成请求。例如,如果你上传的文件只有几千字节,并且有 100 毫秒的往返延迟,你就在开销上花费了 400 毫秒,而只用了几毫秒来上传 。

简而言之

  • 我们建议设置 shouldUseMultipart 仅对大型文件启用多部分上传。
  • 如果你希望减少开销(+20% 上传速度),可以使用临时 S3 凭证与 getTemporarySecurityCredentials. 这意味着用户获得一个单一的令牌,允许他们长时间执行存储桶操作,而不是每个资源的短时签名 URL。这是一个安全性的权衡。

安装

  • npm

    npm install @uppy/aws-s3
    
  • yarn

    yarn add @uppy/aws-s3
    
  • CDN

    <!-- 1. Add CSS to `<head>` -->
    <link href="https://releases.transloadit.com/uppy/v3.27.3/uppy.min.css" rel="stylesheet">
    
    <!-- 2. Initialize -->
    <div id="uppy"></div>
    
    <script type="module">
        import { Uppy, AwsS3 } from "https://releases.transloadit.com/uppy/v3.27.3/uppy.min.mjs"
        new Uppy().use(AwsS3, { /* see options */ })
    </script>
    

使用

设置你的 S3 存储桶

要与 S3 一起使用此插件,我们需要设置一个具有正确权限和 CORS 设置的存储桶。

出于安全原因,S3 存储桶不允许公开上传。为了让 Uppy 和浏览器直接上传到存储桶,需要配置其 CORS 权限。

CORS 权限可以在 S3 管理控制台 中找到。点击接收上传的存储桶,然后进入 Permissions 选项卡并选择 CORS configuration 按钮。将会显示一个 JSON 文档,定义了 CORS 配置。(AWS 曾经使用 XML,但现在只允许 JSON)。更多关于 S3 CORS 格式的信息在这里

Uppy 和 Companion 所需的配置是这样的:

[
  {
    "AllowedOrigins": ["https://my-app.com"],
    "AllowedMethods": ["GET", "PUT"],
    "MaxAgeSeconds": 3000,
    "AllowedHeaders": [
      "Authorization",
      "x-amz-date",
      "x-amz-content-sha256",
      "content-type"
    ],
    "ExposeHeaders": ["ETag", "Location"]
  },
  {
    "AllowedOrigins": ["*"],
    "AllowedMethods": ["GET"],
    "MaxAgeSeconds": 3000
  }
]

一个好的做法是使用两条 CORS 规则:一条用于查看上传的文件,另一条用于上传文件。上面的例子使文件公开可查看。你可以根据需要进行更改。

如果你正在使用 IAM 策略来允许访问 S3 存储桶,该策略必须至少包含针对所讨论的存储桶的 s3:PutObjects3:PutObjectAcl 权限。有关 CORS 规则的详细文档可在 AWS 文档站点 上找到。

与你自己的服务器一起使用

推荐的做法是将 @uppy/aws-s3 与你自己的服务器集成。你需要做以下事情:

  1. 设置一个 S3 存储桶
  2. 设置你的服务器
  3. 设置 Uppy 客户端

与 Companion 一起使用

Companion 内置了 S3 路由,为 Uppy 提供即插即用体验。

import Uppy from "@uppy/core";
import Dashboard from "@uppy/dashboard";
import AwsS3 from "@uppy/aws-s3";

import "@uppy/core/dist/style.min.css";
import "@uppy/dashboard/dist/style.min.css";

const uppy = new Uppy()
  .use(Dashboard, { inline: true, target: "body" })
  .use(AwsS3, {
    shouldUseMultipart: (file) => file.size > 100 * 2 ** 20,
    companionUrl: "https://companion.uppy.io",
  });

API

选项

shouldUseMultipart(file)

这是一个布尔值,或是一个函数,该函数接收每个上传文件的对应 UppyFile 实例作为参数并返回一个布尔值 。

默认情况下,所有文件都以多部分形式上传。在未来的版本中,所有 file.size ≤ 100 MiB 的文件都将单个分块上传,大于此大小的文件则使用多部分上传。

使用方法如下:

uppy.use(AwsS3, {
  shouldUseMultipart(file) {
    // 仅对大于100MiB的文件使用多部分上传。
    return file.size > 100 * 2 ** 20;
  },
});

limit

同时上传文件的最大数量( number,默认:6 )。

注意,文件数量并不等同于并发连接的数量。多部分上传可以为每个文件使用多个请求。例如,对于 100 MiB 的文件,如果分块大小为 5 MiB:

  • 1 个 createMultipartUpload 请求
  • 100/5 = 20 个签名请求( 除非你在 客户端签名
  • 100/5 = 20 个上传请求
  • 1 个 completeMultipartUpload 请求

companionUrl

指向 Companion 实例的 URL( string,默认:null )。

companionHeaders

Companion 发送的每个请求上应附加的自定义头部( Object,默认:{} )。

companionCookiesRule

此选项对应于 RequestCredentials 值string,默认:'same-origin' )。

这告诉插件是否应向 Companion 发送 cookie。

retryDelays

retryDelays是用于重试失败分块的间隔时间(以毫秒为单位)( array,默认:[0, 1000, 3000, 5000] )。

这也用于 signPart()。设置为 null 可禁用自动重试,在任何分块上传失败时立即失败 。

getChunkSize(file)

一个函数,根据给定的文件返回上传该文件时使用的最小分块大小。

S3 Multipart 插件以分块的形式上传文件。分块以批次的形式发送出去以便生成预签名的 URL,使用 signPart() 。为了减少大文件的请求量,你可以选择更大的分块大小,但这样如果某个分块上传失败,则需要重新上传更多的数据。

S3 要求最小分块大小为 5 MiB,并且每个多部分上传支持最多 10,000 个分块。如果 getChunkSize() 返回的大小太小,Uppy 将会增加到 S3 的最低要求。

getUploadParameters(file, options)

提示

当使用 Companion 来签名 S3 上传时,你不应该定义这个选项。

一个函数,将针对每个非多部分上传调用。

  • file: UppyFile 待上传的文件
  • options: object
    • signal: AbortSignal
  • 返回: object | Promise<object>
    • method: string,用于上传的 HTTP 方法。应为 PUTPOST 之一,具体取决于使用的上传类型。
    • url: string,上传请求将发送到的 URL。当使用预签名的 PUT 上传时,这应该是包含签名参数的查询字符串的 S3 对象的 URL。当使用带有策略文档的 POST 上传时,这应该是桶的根 URL。
    • fields object,随上传请求一起发送的表单字段对象。对于默认的预签名 PUT 上传,应留空。
    • headers: object,随上传请求一起发送的请求头对象。当使用预签名 PUT 上传时,提供 headers['content-type'] 是个好主意。这将确保请求使用与生成签名时相同的 content-type。没有它,浏览器可能会决定使用不同的 content-type,导致 S3 拒绝上传。

createMultipartUpload(file)

一个调用 S3 Multipart API 以创建新上传的函数。

file 是来自 Uppy 状态的文件对象。最相关的键是 file.namefile.type

返回一个 Promise,其中包含以下键的对象:

  • uploadId - S3 返回的 UploadID。
  • key - 文件的对象键。需要返回它,以便它可以与 file.name 不同。

默认实现会调用到 Companion 的 S3 签名端点。

listParts(file, { uploadId, key })

一个调用 S3 Multipart API 以列出文件已上传的部分的函数。

接收来自 Uppy 状态的 file 对象,以及一个对象,其键包括:

  • uploadId - 此多部分上传的 UploadID。
  • key - 此多部分上传的对象键。

返回一个 Promise,其内容为 S3 Part 对象数组,由 S3 Multipart API 返回。每个对象具有以下键:

  • PartNumber - 文件中已上传部分的索引。
  • Size - 部分的大小,以字节为单位。
  • ETag - 部分的 ETag,用于在完成多部分上传并组合所有部分成单个文件时识别它。

默认实现会调用到 Companion 的 S3 签名端点。

signPart(file, partData)

一个函数,用于为指定的分片编号生成签名 URL 。partData 参数是一个对象,包含以下键:

  • uploadId - 此分段上传的UploadID。
  • key - S3存储桶中的对象键。
  • partNumber - 不能为零。
  • body – 将被签名的数据。
  • signal – 可用于中止正在进行请求的 AbortSignal

此函数应返回一个对象,或一个解析为对象的承诺,包含以下键:

  • url – 预签名的URL,作为 string
  • headers(可选) 发送至S3端点请求时的自定义头部。

返回值示例:

{
	"url": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=1&...",
	"headers": { "Content-MD5": "foo" }
}

abortMultipartUpload(file, { uploadId, key })

调用 S3 Multipart API 以中止分段上传,并删除迄今为止已上传的所有部分。

接收来自 Uppy 状态的 file 对象和一个包含以下键的对象:

  • uploadId - 此分段上传的 UploadID。
  • key - 此分段上传的 S3 对象键。

通常在用户取消上传时调用。在 Uppy 中,取消操作不能失败,因此此函数的结果将被忽略。

默认实现会调用 Companion 的 S3 签名端点。

completeMultipartUpload(file, { uploadId, key, parts })

调用 S3 Multipart API 以完成分段上传,将所有部分合并到 S3 存储桶中的单个对象中。

接收来自 Uppy 状态的 file 对象和一个包含以下键的对象:

  • uploadId - 此分段上传的 UploadID。
  • key - 此分段上传的 S3 对象键。
  • parts - S3 风格的部分列表,是一个具有 ETagPartNumber 属性的对象数组。这可以直接传递给 S3 的 Multipart API。

返回一个 Promise,其中包含以下属性:

  • location - (可选) 对象在 S3 存储桶中的公开访问 URL。

默认实现会调用 Companion 的 S3 签名端点。

allowedMetaFields: null

传递一个字段名称数组以限制将作为查询参数添加到上传中的元数据字段。

  • 设置为 ['name'] 仅发送 name 字段 。
  • 设置为 null(默认)发送所有元数据字段 。
  • 设置为空数组 [] 不发送任何字段 。

已废弃的选项

prepareUploadParts(file, partData)

生成指定分片编号的一批签名 URL 的函数。

接收来自 Uppy 状态的 file 对象。partData 参数是一个对象,包含以下键:

  • uploadId - 此分段上传的 UploadID。
  • key - S3 存储桶中的对象键。
  • parts - 具有分片号和块的部分数组( Array<{ number: number, chunk: blob }> ) 。number 不能为零 。

prepareUploadParts 应返回一个包含以下键的 PromiseObject

  • presignedUrls - 一个 JavaScript 对象,其键为分片编号,值为每个分片的预签名 URL。
  • headers - (可选) 每个请求每部分要一起发送的自定义头部(如:{ 1: { 'Content-MD5': 'hash' }})。这些也是基于分片编号(从 1 开始)索引的,因此你可以例如为每个分片向 S3 发送 MD5 哈希验证。

返回值示例:

{
  "presignedUrls": {
    "1": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=1&...",
    "2": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=2&...",
    "3": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=3&..."
  },
  "headers": {
    "1": { "Content-MD5": "foo" },
    "2": { "Content-MD5": "bar" },
    "3": { "Content-MD5": "baz" }
  }
}

如果发生错误,以包含以下键的 Object 拒绝 Promise

{ "source": { "status": 500 } }

status 是 HTTP 代码,对于确定是否重试请求是必需的。如果代码为 0409423,或介于 500600 之间,则将重试 prepareUploadParts

getTemporarySecurityCredentials(options)

当使用 Companion 作为后端时,你可以传递 true 而不是一个函数。设置 Companion 并不会简化客户端签名的过程。

当使用 Companion 时为布尔值,或异步函数,用于检索用于所有上传的临时安全凭证,而不是对每个部分进行签名。这会导致较少的请求开销,从而可能导致上传速度提高约 20%。这是一个安全权衡。我们建议除非你熟悉临时凭证的安全影响以及如何配置你的存储桶使其工作,否则不要使用此选项。有关更多信息,请参阅 AWS 指南:请求临时安全凭证

强烈建议有一个缓存机制来避免不必要的请求临时令牌。

  • options: object
    • signal: AbortSignal
  • 返回值: object | Promise<object>
    • credentials: object
      • AccessKeyId: string
      • SecretAccessKey: string
      • SessionToken: string
      • Expiration: string
    • bucket: string
    • region: string

如果你正在使用 Companion(例如,因为你希望支持远程上传源),你可以传递一个布尔值:

uppy.use(AwsS3, {
  // 这是使用Companion的一个示例:
  companionUrl: "http://companion.uppy.io",
  getTemporarySecurityCredentials: true,
  shouldUseMultipart: (file) => file.size > 100 * 2 ** 20,
});

在最常见的情况下,您可能使用了不同的后端,这时您需要指定一个函数:

uppy.use(AwsS3, {
  // 这是一个没有使用Companion的示例:
  async getTemporarySecurityCredentials({ signal }) {
    const response = await fetch("/sts-token", { signal });
    if (!response.ok) {
      throw new Error("获取STS失败", { cause: response });
    }
    return response.json();
  },
  shouldUseMultipart: (file) => file.size > 100 * 2 ** 20, // 文件大小超过100MB时使用分片上传
});
在本文档中