【问题标题】:Direct (and simple!) AJAX upload to AWS S3 from (AngularJS) Single Page App从(AngularJS)单页应用程序直接(并且简单!)AJAX 上传到 AWS S3
【发布时间】:2015-04-28 16:23:49
【问题描述】:

我知道有很多关于上传到 AWS S3 的报道。但是,我已经为此苦苦挣扎了大约 24 小时,但我还没有找到任何适合我情况的答案。

我想要做什么

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

  1. 这是一个单页应用,所以上传请求必须是 AJAX 格式
  2. 我的服务器和我的客户端在同一个域中
  3. S3 存储桶属于最新类型 (Frankfurt),某些签名生成库不适用于这些存储桶(见下文)
  4. 客户端在 AngularJS 中
  5. 服务器在 ExpressJS 中

我尝试过的

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

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

我正在寻找的解决方案

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

【问题讨论】:

    标签: ajax angularjs file-upload amazon-s3 singlepage


    【解决方案1】:

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

    1. 放弃 Angular 的$http,改用原生的XMLHttpRequest
    2. 使用 AWS 开发工具包的 getSignedUrl 功能,而不是像许多库那样实现我自己的签名生成工作流程。
    3. 设置 AWS 配置以使用正确的签名版本(在撰写本文时为 v4)和区域(在法兰克福的情况下为'eu-central-1')。

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


    工作流程总结

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

    开始之前

    确保:

    • 您的服务器具有 AWS 开发工具包
    • 您的服务器拥有 AWS 凭证,具有对您的存储桶的适当访问权限
    • 您的 S3 存储桶具有适用于您的客户端的正确 CORS 配置。

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

    重要的是要有一个最终让您以编程方式访问 File 对象的工作流程 - 无需上传。

    就我而言,我使用了出色的 angular-file-upload 库的 ng-file-selectng-file-drop 指令。但是还有其他方法可以做到这一点(例如,请参阅 this post。)。

    请注意,您可以访问文件对象中的有用信息,例如 file.namefile.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);
    

    致谢

    我要感谢emil10001Will Webberley,他们的出版物对我来说非常有价值。

    【讨论】:

    • 谢谢!你为什么决定使用 XMLHttpRequest 而不是 Angular 的 $http
    • 我必须添加 xhr.setRequestHeader('enctype','multipart/form-data'); 并对 onreadystatechange 函数进行验证,以确保我只在状态码为 2xx(成功)时才解决我的承诺
    • 请注意,如果您要发送凭据(使用 PresignedPost),则该文件必须是要发送的 FormData 对象上附加的最后一个对象,因为 S3 会忽略 file 键之后的所有内容。
    • @BrunoPeres:为什么没有$http?我不得不承认我只是无法让它工作:) 这里没有合理的理由。我的猜测是在这种情况下它有一些不好的默认值。
    • 我真的不知道为什么,但我认为应该是 Angular 的$http 上的一些默认配置。 Angular 1.4.7 发布了一个 $xhrFactory 似乎可以完成这项工作,但我还没有测试过。
    【解决方案2】:

    您可以将 ng-file-upload $upload.http 方法与 aws-sdk getSignedUrl 结合使用来完成此操作。从服务器取回签名网址后,这是客户端代码:

    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);
    });
    

    【讨论】:

      【解决方案3】:

      要进行分段上传或大于 5 GB 的上传,此过程会稍微复杂一些,因为每个部分都需要自己的签名。方便的是,有一个 JS 库:

      https://github.com/TTLabs/EvaporateJS

      通过https://github.com/aws/aws-sdk-js/issues/468

      【讨论】:

        【解决方案4】:

        使用具有动态数据绑定和自动回调功能的 s3-file-upload 开源指令 - https://github.com/vinayvnvv/s3FileUpload

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-11-07
          • 2018-07-10
          • 2010-09-12
          • 2014-07-28
          • 1970-01-01
          • 1970-01-01
          • 2020-10-22
          相关资源
          最近更新 更多