从(AngularJS)单页面应用程序直接(简单!)AJAX上传到AWS S3

Val*_*nck 15 ajax file-upload amazon-s3 singlepage angularjs

我知道上传到AWS S3有很多报道.但是,我一直在努力解决这个问题大约24小时,我没有找到适合我情况的任何答案.

我想做什么

将文件直接从我的客户端上传到AWS S3到我的S3存储桶.情况是:

  1. 它是单页应用程序,因此上传请求必须在AJAX中
  2. 我的服务器和我的客户端不在同一个域中
  3. S3存储桶是最新的(法兰克福),一些签名生成库不起作用(见下文)
  4. 客户在AngularJS
  5. 服务器在ExpressJS中

我试过的

  • Heroku关于直接上传到S3 的文章.不适合我的客户端/服务器配置(加上它真的不适合Angular)
  • 现成的指令,如ng-s3upload.不起作用,因为最近的s3存储桶不接受它们的签名生成算法.
  • 在本文中手动创建文件上载指令和逻辑,如本文所述(使用FormData和Angular $http).它包括从服务器上的AWS获取签名的URL(并且该部分工作),然后AJAX上传到该URL.它失败了一些神秘的CORS相关消息(虽然我确实在Heroku上设置了CORS配置)

我似乎面临两个困难:在我的单页应用程序中使用文件输入,并使AWS的工作流程正确.

我正在寻找的那种解决方案

如果可能的话,我想避免"全部包含"的解决方案来管理整个过程,同时隐藏所有的复杂性,使其难以适应特殊情况.我宁愿有一个简单的解释来分解所涉及的各个组件之间的数据流,即使它需要我更多的管道.

Val*_*nck 24

我终于成功了.关键点是:

  1. 放开Angular $http,然后使用原生XMLHttpRequest代替.
  2. 使用getSignedUrlAWS SDK 的功能,而不是像许多库一样实现我自己的签名生成工作流.
  3. 将AWS配置设置为使用正确的签名版本(撰写本文时为v4)和区域('eu-central-1'在法兰克福的情况下).

以下是我所做的一步一步的指导; 它在服务器上使用AngularJS和NodeJS,但应该很容易适应其他堆栈,特别是因为它处理大多数病态情况(服务器在不同域上的SPA,最近有一个桶 - 在写作 - 地区).


工作流程摘要

  1. 用户在浏览器中选择一个文件; 你的JavaScript会保留对它的引用.
  2. 客户端向您的服务器发送请求以获取已签名的上载URL.
  3. 您的服务器选择要放入存储桶的对象的名称(确保避免名称冲突!).
  4. 服务器使用AWS SDK获取对象的签名URL,并将其发送回客户端.这涉及对象的名称和AWS凭证.
  5. 给定文件和签名URL,客户端直接向您的S3 Bucket发送PUT请求.

在你开始之前

确保:

  • 您的服务器具有AWS SDK
  • 您的服务器具有AWS凭据,对您的存储桶具有适当的访问权限
  • 您的S3存储桶具有适合您客户端的CORS配置.

第1步:设置SPA友好的文件上传表单/小部件.

重要的是拥有一个工作流程,最终为您提供对File对象的编程访问- 无需上传.

在我的例子中,我使用了优秀的angular-file-upload库的ng-file-selectng-file-drop指令.但还有其他方法(例如,请参阅此文章).

需要注意的是,你可以在你的文件对象访问有用的信息,例如file.name,file.type等等.

第2步:获取服务器上文件的签名URL

在您的服务器上,您可以使用AWS SDK 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
  // [...]
});
Run Code Online (Sandbox Code Playgroud)

您可能想知道要获取对象的URL(通常是图像).为此,我只是从URL中删除了查询字符串:

var url = require('url');
// ...
var parsedUrl = url.parse(signedUrl);
parsedUrl.search = null;
var objectUrl = url.format(parsedUrl);
Run Code Online (Sandbox Code Playgroud)

第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);
Run Code Online (Sandbox Code Playgroud)

致谢

我要感谢emil10001Will Webberley,他们的出版物对我来说对这个问题非常有价值.