我终于成功了。关键点是:
- 放弃 Angular 的
$http,改用原生的XMLHttpRequest。
- 使用 AWS 开发工具包的
getSignedUrl 功能,而不是像许多库那样实现我自己的签名生成工作流程。
- 设置 AWS 配置以使用正确的签名版本(在撰写本文时为 v4)和区域(在法兰克福的情况下为
'eu-central-1')。
下面是我所做的分步指南;它在服务器上使用 AngularJS 和 NodeJS,但应该很容易适应其他堆栈,特别是因为它处理最病态的情况(SPA 在与服务器不同的域上,最近有一个桶 - 在写作 - 地区)。
工作流程总结
- 用户在浏览器中选择一个文件;您的 JavaScript 会保留对它的引用。
- 客户端向您的服务器发送请求以获取签名的上传 URL。
- 您的服务器为要放入存储桶的对象选择一个名称(确保避免名称冲突!)。
- 服务器使用 AWS 开发工具包为您的对象获取签名 URL,并将其发送回客户端。这涉及对象的名称和 AWS 凭证。
- 给定文件和签名 URL,客户端会直接向您的 S3 存储桶发送 PUT 请求。
开始之前
确保:
- 您的服务器具有 AWS 开发工具包
- 您的服务器拥有 AWS 凭证,具有对您的存储桶的适当访问权限
- 您的 S3 存储桶具有适用于您的客户端的正确 CORS 配置。
第 1 步:设置 SPA 友好的文件上传表单/小部件。
重要的是要有一个最终让您以编程方式访问 File 对象的工作流程 - 无需上传。
就我而言,我使用了出色的 angular-file-upload 库的 ng-file-select 和 ng-file-drop 指令。但是还有其他方法可以做到这一点(例如,请参阅 this post。)。
请注意,您可以访问文件对象中的有用信息,例如 file.name、file.type 等。
第 2 步:获取服务器上文件的签名 URL
在您的服务器上,您可以使用 AWS 开发工具包从其他地方(例如您的前端)获取指向 PUT 文件的安全临时 URL。
在 NodeJS 中,我是这样做的:
// ---------------------------------
// some initial configuration
var aws = require('aws-sdk');
aws.config.update({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY,
signatureVersion: 'v4',
region: 'eu-central-1'
});
// ---------------------------------
// now say you want fetch a URL for an object named `objectName`
var s3 = new aws.S3();
var s3_params = {
Bucket: MY_BUCKET_NAME,
Key: objectName,
Expires: 60,
ACL: 'public-read'
};
s3.getSignedUrl('putObject', s3_params, function (err, signedUrl) {
// send signedUrl back to client
// [...]
});
您可能想知道获取对象的 URL(通常是图像)。为此,我只是从 URL 中删除了查询字符串:
var url = require('url');
// ...
var parsedUrl = url.parse(signedUrl);
parsedUrl.search = null;
var objectUrl = url.format(parsedUrl);
第 3 步:从客户端发送 PUT 请求
现在您的客户端拥有您的File 对象和签名的 URL,它可以将 PUT 请求发送到 S3。对于 Angular,我的建议是只使用 XMLHttpRequest 而不是 $http 服务:
var signedUrl, file;
// ...
var d_completed = $q.defer(); // since I'm working with Angular, I use $q for asynchronous control flow, but it's not mandatory
var xhr = new XMLHttpRequest();
xhr.file = file; // not necessary if you create scopes like this
xhr.onreadystatechange = function(e) {
if ( 4 == this.readyState ) {
// done uploading! HURRAY!
d_completed.resolve(true);
}
};
xhr.open('PUT', signedUrl, true);
xhr.setRequestHeader("Content-Type","application/octet-stream");
xhr.send(file);
致谢
我要感谢emil10001 和Will Webberley,他们的出版物对我来说非常有价值。