Skip to main content

使用预签名 URL 进行上传

本文为希望允许用户上传视频和其他资源的 Web 应用提供指导。我们建议在服务器端生成一个预签名 URL,允许用户直接将文件上传到您的云存储,而无需通过您的服务器传递文件。

您可以设置诸如最大文件大小和文件类型的约束,应用速率限制,要求身份验证,并预定义存储位置。

为什么使用预签名 URL?

传统的文件上传实现方式是让客户端将文件上传到服务器,然后服务器将文件存储在磁盘上或将上传转发到云存储。虽然这种方法可行,但由于几个原因,它并不理想。

  • 减轻负载:如果许多客户端恰好在同一服务器上上传大文件,该服务器可能会因负载过重而变慢甚至崩溃。通过预签名工作流程,服务器只需创建预签名 URL,这比处理文件传输减轻了服务器负载。
  • 减少垃圾邮件:为了防止用户将您的上传功能用作免费托管空间,您可以拒绝为他们提供预签名 URL,如果他们超出了您的允许范围。
  • 数据安全:由于今天许多托管解决方案是临时的或无服务器的,文件不应存储在它们上面。在服务器重新启动后,不能保证文件仍然存在,您可能会耗尽磁盘空间。

AWS 示例

此示例假定用户上传存储在 S3 中。对于其他前端

首先,在前端接受一个文件,例如使用 <input type="file">。您应该获得一个 File,从中可以确定内容类型和内容长度:

App.tsx
ts
const contentType = file.type || "application/octet-stream";
const arrayBuffer = await file.arrayBuffer();
const contentLength = arrayBuffer.byteLength;
App.tsx
ts
const contentType = file.type || "application/octet-stream";
const arrayBuffer = await file.arrayBuffer();
const contentLength = arrayBuffer.byteLength;

此示例使用 @aws-sdk/s3-request-presigner@remotion/lambda 导入的 AWS SDK。通过调用下面的函数,将生成两个 URL:

  • presignedUrl 是文件可以上传到的 URL
  • readUrl 是可以从中读取文件的 URL。
api/generate-presigned-url.ts
tsx
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { AwsRegion, getAwsClient } from "@remotion/lambda/client";
import { v4 } from "uuid";
 
export const generatePresignedUrl = async (
contentType: string,
contentLength: number,
expiresIn: number,
bucketName: string,
region: AwsRegion,
): Promise<{ presignedUrl: string; readUrl: string }> => {
if (contentLength > 1024 * 1024 * 200) {
throw new Error(
`File may not be over 200MB. Yours is ${contentLength} bytes.`,
);
}
 
const { client, sdk } = getAwsClient({
region: process.env.REMOTION_AWS_REGION as AwsRegion,
service: "s3",
});
 
const key = v4();
 
const command = new sdk.PutObjectCommand({
Bucket: bucketName,
Key: key,
ACL: "public-read",
ContentLength: contentLength,
ContentType: contentType,
});
 
const presignedUrl = await getSignedUrl(client, command, {
expiresIn,
});
 
// The location of the assset after the upload
const readUrl = `https://${bucketName}.s3.${region}.amazonaws.com/${key}`;
 
return { presignedUrl, readUrl };
};
api/generate-presigned-url.ts
tsx
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { AwsRegion, getAwsClient } from "@remotion/lambda/client";
import { v4 } from "uuid";
 
export const generatePresignedUrl = async (
contentType: string,
contentLength: number,
expiresIn: number,
bucketName: string,
region: AwsRegion,
): Promise<{ presignedUrl: string; readUrl: string }> => {
if (contentLength > 1024 * 1024 * 200) {
throw new Error(
`File may not be over 200MB. Yours is ${contentLength} bytes.`,
);
}
 
const { client, sdk } = getAwsClient({
region: process.env.REMOTION_AWS_REGION as AwsRegion,
service: "s3",
});
 
const key = v4();
 
const command = new sdk.PutObjectCommand({
Bucket: bucketName,
Key: key,
ACL: "public-read",
ContentLength: contentLength,
ContentType: contentType,
});
 
const presignedUrl = await getSignedUrl(client, command, {
expiresIn,
});
 
// The location of the assset after the upload
const readUrl = `https://${bucketName}.s3.${region}.amazonaws.com/${key}`;
 
return { presignedUrl, readUrl };
};

Explanation:

  • 首先,上传请求会被检查约束条件。在这个例子中,我们拒绝超过 200MB 的上传。您可以添加更多约束条件或添加速率限制。
  • 使用 getAwsClient() 导入 AWS SDK。如果您不使用 Remotion Lambda,请直接安装 @aws-sdk/client-s3 包。
  • 使用 UUID 作为文件名以避免名称冲突。
  • 最后,计算并返回预签名 URL 和输出 URL。

使用预签名 URL

将预签名 URL 发送回客户端。之后,您现在可以使用内置的 fetch() 函数执行上传:

App.tsx
ts
await fetch(presignedUrl, {
method: "PUT",
body: arrayBuffer,
headers: {
"content-type": contentType,
},
});
App.tsx
ts
await fetch(presignedUrl, {
method: "PUT",
body: arrayBuffer,
headers: {
"content-type": contentType,
},
});

参见