直接(简单!)AJAX 从(AngularJS)单页应用程序上传到 AWS S3
Direct (and simple!) AJAX upload to AWS S3 from (AngularJS) Single Page App
我知道有很多关于上传到 AWS S3 的报道。但是,我已经为此苦苦挣扎了大约 24 小时,但我还没有找到适合我情况的答案。
我想做什么
将文件直接从我的客户端上传到 AWS S3 到我的 S3 存储桶。情况是:
- 这是一个单页应用程序,所以上传请求必须在 AJAX
- 我的服务器和我的客户端不在同一个域
- S3 存储桶是最新类型的(法兰克福),一些签名生成库对此不起作用(见下文)
- 客户在AngularJS
- 服务器在 ExpressJS 中
我试过的
- Heroku's article 直接上传到 S3。不适合我的 client/server 配置(加上它真的不适合 Angular)
- 现成的指令,如 ng-s3upload。不起作用,因为最近的 s3 存储桶不接受他们的签名生成算法。
- 像 this article 一样在客户端手动创建文件上传指令和逻辑(使用
FormData
和 Angular 的 $http
)。它包括从服务器上的 AWS 获得签名的 URL(并且该部分有效),然后 AJAX-上传到那个 URL。它因一些神秘的 CORS 相关消息而失败(尽管我确实在 Heroku 上设置了 CORS 配置)
我似乎面临 2 个困难:在我的单页应用程序中使用文件输入,以及正确使用 AWS 的工作流程。
我正在寻找的解决方案
如果可能,我想避免 'all included' 管理整个过程同时隐藏所有复杂性的解决方案,这使得很难适应特殊情况。我宁愿有一个简单的解释来分解所涉及的各个组件之间的数据流,即使它需要我做更多的工作。
我终于成功了。关键点是:
- 放弃Angular的
$http
,改用原生的XMLHttpRequest
。
- 使用 AWS SDK 的
getSignedUrl
功能,而不是像许多库那样实现我自己的签名生成工作流程。
- 设置 AWS 配置以使用正确的签名版本(撰写本文时为 v4)和区域(
'eu-central-1'
在法兰克福的情况下)。
以下是我所做工作的分步指南;它在服务器上使用 AngularJS 和 NodeJS,但应该很容易适应其他堆栈,特别是因为它处理最病态的情况(SPA 在与服务器不同的域上,最近有一个桶- 在撰写本文时 - 地区)。
工作流程摘要
- 用户在浏览器中选择一个文件;您的 JavaScript 保留了对它的引用。
- 客户端向您的服务器发送请求以获取签名上传URL。
- 您的服务器为要放入存储桶中的对象选择一个名称(确保避免名称冲突!)。
- 服务器使用 AWS SDK 为您的对象获取签名 URL,并将其发送回客户端。这涉及对象的名称和 AWS 凭证。
- 给定文件和签名 URL,客户端直接向您的 S3 存储桶发送 PUT 请求。
开始之前
确保:
- 您的服务器有 AWS SDK
- 您的服务器拥有 AWS 凭证,对您的存储桶具有适当的访问权限
- 您的 S3 存储桶具有适用于您的客户端的正确 CORS 配置。
第 1 步:设置 SPA 友好的文件上传表单/小部件。
重要的是拥有一个最终让您以编程方式访问 File
对象的工作流程 - 而无需上传它。
在我的例子中,我使用了优秀 angular-file-upload library. But there are other ways of doing it (see this post 的 ng-file-select
和 ng-file-drop
指令。)。
请注意,您可以在文件对象中访问有用的信息,例如 file.name
、file.type
等
第 2 步:为服务器上的文件URL签名
在您的服务器上,您可以使用 AWS SDK 从其他地方(例如您的前端)获取 URL 到 PUT
您的文件的安全临时文件。
在 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);
第三步:客户端发送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 and Will Webberley,他们的出版物对我来说对这个问题非常有价值。
您可以结合使用 ng-file-upload $upload.http 方法和 aws-sdk getSignedUrl 来完成此操作。从服务器获取 signedUrl 后,这是客户端代码:
var fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = function(e) {
$upload.http({
method: 'PUT',
headers: {'Content-Type': file.type != '' ? file.type : 'application/octet-stream'},
url: signedUrl,
data: e.target.result
}).progress(function (evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
console.log('progress: ' + progressPercentage + '% ' + file.name);
}).success(function (data, status, headers, config) {
console.log('file ' + file.name + 'uploaded. Response: ' + data);
});
要进行分段上传或大于 5 GB 的上传,此过程会稍微复杂一些,因为每个部分都需要自己的签名。方便的是,有一个 JS 库:
使用具有动态数据绑定和自动回调功能的 s3-file-upload 开源指令 - https://github.com/vinayvnvv/s3FileUpload
我知道有很多关于上传到 AWS S3 的报道。但是,我已经为此苦苦挣扎了大约 24 小时,但我还没有找到适合我情况的答案。
我想做什么
将文件直接从我的客户端上传到 AWS S3 到我的 S3 存储桶。情况是:
- 这是一个单页应用程序,所以上传请求必须在 AJAX
- 我的服务器和我的客户端不在同一个域
- S3 存储桶是最新类型的(法兰克福),一些签名生成库对此不起作用(见下文)
- 客户在AngularJS
- 服务器在 ExpressJS 中
我试过的
- Heroku's article 直接上传到 S3。不适合我的 client/server 配置(加上它真的不适合 Angular)
- 现成的指令,如 ng-s3upload。不起作用,因为最近的 s3 存储桶不接受他们的签名生成算法。
- 像 this article 一样在客户端手动创建文件上传指令和逻辑(使用
FormData
和 Angular 的$http
)。它包括从服务器上的 AWS 获得签名的 URL(并且该部分有效),然后 AJAX-上传到那个 URL。它因一些神秘的 CORS 相关消息而失败(尽管我确实在 Heroku 上设置了 CORS 配置)
我似乎面临 2 个困难:在我的单页应用程序中使用文件输入,以及正确使用 AWS 的工作流程。
我正在寻找的解决方案
如果可能,我想避免 'all included' 管理整个过程同时隐藏所有复杂性的解决方案,这使得很难适应特殊情况。我宁愿有一个简单的解释来分解所涉及的各个组件之间的数据流,即使它需要我做更多的工作。
我终于成功了。关键点是:
- 放弃Angular的
$http
,改用原生的XMLHttpRequest
。 - 使用 AWS SDK 的
getSignedUrl
功能,而不是像许多库那样实现我自己的签名生成工作流程。 - 设置 AWS 配置以使用正确的签名版本(撰写本文时为 v4)和区域(
'eu-central-1'
在法兰克福的情况下)。
以下是我所做工作的分步指南;它在服务器上使用 AngularJS 和 NodeJS,但应该很容易适应其他堆栈,特别是因为它处理最病态的情况(SPA 在与服务器不同的域上,最近有一个桶- 在撰写本文时 - 地区)。
工作流程摘要
- 用户在浏览器中选择一个文件;您的 JavaScript 保留了对它的引用。
- 客户端向您的服务器发送请求以获取签名上传URL。
- 您的服务器为要放入存储桶中的对象选择一个名称(确保避免名称冲突!)。
- 服务器使用 AWS SDK 为您的对象获取签名 URL,并将其发送回客户端。这涉及对象的名称和 AWS 凭证。
- 给定文件和签名 URL,客户端直接向您的 S3 存储桶发送 PUT 请求。
开始之前
确保:
- 您的服务器有 AWS SDK
- 您的服务器拥有 AWS 凭证,对您的存储桶具有适当的访问权限
- 您的 S3 存储桶具有适用于您的客户端的正确 CORS 配置。
第 1 步:设置 SPA 友好的文件上传表单/小部件。
重要的是拥有一个最终让您以编程方式访问 File
对象的工作流程 - 而无需上传它。
在我的例子中,我使用了优秀 angular-file-upload library. But there are other ways of doing it (see this post 的 ng-file-select
和 ng-file-drop
指令。)。
请注意,您可以在文件对象中访问有用的信息,例如 file.name
、file.type
等
第 2 步:为服务器上的文件URL签名
在您的服务器上,您可以使用 AWS SDK 从其他地方(例如您的前端)获取 URL 到 PUT
您的文件的安全临时文件。
在 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);
第三步:客户端发送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 and Will Webberley,他们的出版物对我来说对这个问题非常有价值。
您可以结合使用 ng-file-upload $upload.http 方法和 aws-sdk getSignedUrl 来完成此操作。从服务器获取 signedUrl 后,这是客户端代码:
var fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = function(e) {
$upload.http({
method: 'PUT',
headers: {'Content-Type': file.type != '' ? file.type : 'application/octet-stream'},
url: signedUrl,
data: e.target.result
}).progress(function (evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
console.log('progress: ' + progressPercentage + '% ' + file.name);
}).success(function (data, status, headers, config) {
console.log('file ' + file.name + 'uploaded. Response: ' + data);
});
要进行分段上传或大于 5 GB 的上传,此过程会稍微复杂一些,因为每个部分都需要自己的签名。方便的是,有一个 JS 库:
使用具有动态数据绑定和自动回调功能的 s3-file-upload 开源指令 - https://github.com/vinayvnvv/s3FileUpload