使用Dropzone.js问题将文件上传到Amazon S3

时间:2015-12-30 09:41:34

标签: javascript ajax amazon-web-services amazon-s3

我尝试使用Dropzone.js

将文件上传到S3服务

我使用本教程直接从客户端上传文件:

https://devcenter.heroku.com/articles/s3-upload-node - 本教程并未包含dropzone js的实现(这是一场噩梦)

流程非常简单:

  1. 从我的服务器询问亚马逊的签名
  2. 获取已签名的请求网址+来自amazon的预期文件网址
  3. 使用签名请求网址覆盖dropzone.options.url
  4. 调用dropzone.processFile将文件上传到服务器
  5. 文件上传到服务器,直到这里一切正常,当我试图查看文件时(在S3 Bucket界面中),文件似乎没有正确写入,我不能这样做查看它。

    根据源代码,使用FormData对象上传文件。

    Dropzone.prototype.submitRequest = function(xhr, formData, files) {
      return xhr.send(formData);
    }
    

    如果我更改源代码:

    xhr.send(formData)
    

    xhr.send(files[0])
    

    一切都很好但我失去了上传多个文件的能力。

    这是dropzone配置:

    {
       url: 'http://signature_url',
       accept: _dropzoneAcceptCallback,
       method: 'put',
       headers: {
          'x-amz-acl': 'public-read',
          'Accept': '*/*',
          'Content-Type': file.type
       },
       clickable: ['.choose-files'],
       autoProcessQueue: false
    }
    

    Request HTTP Headers

    希望它足够:))

    感谢。

3 个答案:

答案 0 :(得分:8)

这是我在后端的dropzone init参数和节点S3签名上的作用:

使用Dropzone的HTML前端代码:

var myDropzone = new Dropzone(dropArea, { 
    url:"#",
    dictDefaultMessage: "Drag n drop or tap here",
    method: "PUT",
    uploadMultiple: false,
    paramName: "file",
    maxFiles: 10,
    thumbnailWidth: 80,
    thumbnailHeight: 80,
    parallelUploads: 20,
    autoProcessQueue: true,
    previewTemplate: dropPreviewTemplate,
    //autoQueue: false, // Make sure the files aren't queued until manually added
    previewsContainer: dropPreviewContainer, // Define the container to display the previews
    clickable: true, //".fileinput-button" // Define the element that should be used as click trigger to select files.
    accept: function(file, cb) {
        //override the file name, to use the s3 signature
        //console.log(file);
        var params = {
          fileName: file.name,
          fileType: file.type,
        };

        //path to S3 signature 
        $.getJSON('/uploader', params).done(function(data) {
            //console.log(data);

          if (!data.signedRequest) {
            return cb('Failed to receive an upload url');
          }

          file.signedRequest = data.signedRequest;
          file.finalURL = data.downloadURL;
          cb();
        }).fail(function() {
          return cb('Failed to receive an upload url');
        });
    },
    sending: function(file, xhr) {

        console.log('sending')
        var _send = xhr.send;
        xhr.setRequestHeader('x-amz-acl', 'public-read');
        xhr.send = function() {
            _send.call(xhr, file);
        }

    },
    processing:function(file){

        this.options.url = file.signedRequest;

    }
    });

这是我在node.js端使用的库

var Crypto = require("crypto"),
    AWS = require("aws-sdk"),

以下是S3上的CORS配置示例

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>

以下是在node.js上生成S3签名的代码:

        getPolicy:function(req,res)
        {
            var fileId = Crypto.randomBytes(20).toString('hex').toUpperCase();

            var prefix = "bl_";
            var newFileName = prefix+fileId;//req.query.fileName;

            var s3 = new AWS.S3();
            var s3_params = {
                Bucket: BUCKET,
                Key: newFileName,
                Expires: 60,
                ContentType: req.query.fileType,
                ACL: 'public-read'
            };
            s3.getSignedUrl('putObject', s3_params, function(err, data){
                if(err){
                    console.log(err);
                }
                else{
                    var return_data = {
                        signedRequest: data,
                        uploadURL: 'https://'+BUCKET+'.s3.amazonaws.com/'+newFileName,
                        downloadURL: 'http://'+BUCKET+'.s3-website-us-east-1.amazonaws.com/'+newFileName,
                    };
                    res.write(JSON.stringify(return_data));
                    res.end();
                }
            });


        }

希望其中一些有用。

答案 1 :(得分:3)

对于可能也会涉及此问题的人,我也想分享我的工作示例。 注意我通过脱掉自己的后端并使用AWS Lambda(又名无服务器)代替进行签名工作更进一步,但概念是相同的。

架构

demo-pic

所以,基本上,

  1. 您正在签署 PUT 上传网址,因此您必须劫持xhr.send功能,如您所述。
  2. 您可以调用processFile函数中的accept,而不是依赖Dropzone的FormData来上传多个文件。因此,对于每个被接受的文件,上传将立即开始,并且您可以同时上传多个文件。
  3. 最终的客户端代码

    const vm = this
    
    let options = {
      // The URL will be changed for each new file being processing
      url: '/',
    
      // Since we're going to do a `PUT` upload to S3 directly
      method: 'put',
    
      // Hijack the xhr.send since Dropzone always upload file by using formData
      // ref: https://github.com/danialfarid/ng-file-upload/issues/743
      sending (file, xhr) {
        let _send = xhr.send
        xhr.send = () => {
          _send.call(xhr, file)
        }
      },
    
      // Upload one file at a time since we're using the S3 pre-signed URL scenario
      parallelUploads: 1,
      uploadMultiple: false,
    
      // Content-Type should be included, otherwise you'll get a signature
      // mismatch error from S3. We're going to update this for each file.
      header: '',
    
      // We're going to process each file manually (see `accept` below)
      autoProcessQueue: false,
    
      // Here we request a signed upload URL when a file being accepted
      accept (file, done) {
        lambda.getSignedURL(file)
          .then((url) => {
            file.uploadURL = url
            done()
            // Manually process each file
            setTimeout(() => vm.dropzone.processFile(file))
          })
          .catch((err) => {
            done('Failed to get an S3 signed upload URL', err)
          })
      }
    }
    
    // Instantiate Dropzone
    this.dropzone = new Dropzone(this.$el, options)
    
    // Set signed upload URL for each file
    vm.dropzone.on('processing', (file) => {
      vm.dropzone.options.url = file.uploadURL
    })
    

    上面的代码与Vue.js有关,但这个概念实际上是框架无关的,你明白了。有关完整的dropzone组件示例,请查看my GitHub repo

    演示

    demo-gif

答案 2 :(得分:1)

要上传到S3,必须处理两个单独的项目-身份验证和上传。

验证

出于安全性考虑,有些可能性:

  1. 将文件夹设为公共文件夹(通过策略或ACL)。
  2. 在IAM中创建一个具有您首选限制的角色,并使用其密钥。
  3. 使用STS颁发临时凭据,以对自己进行身份验证或使用联合身份验证
  4. 为每个文件生成一个预先签名的上传链接。

Aaron Rau演示了生成预签名链接。

使用STS在概念上更简单(无需对每个链接进行签名),但安全性稍差(相同的临时凭据可以在其他地方使用,直到它们过期)。

如果使用联合身份验证,则可以完全跳过服务器端!
从联合用户那里获取临时IAM凭据的一些很好的教程是here(对于FineUploader,但机制相同)和here

要生成自己的临时IAM凭证,可以使用AWS-SDK。 PHP中的示例:

服务器:

<?php
require 'vendor/autoload.php';
use Aws\Result;
use Aws\Sts\StsClient;
$client = new StsClient(['region' => 'us-east-1', 'version' => 'latest']);
$result = $client->getSessionToken();
header('Content-type: application/json');
echo json_encode($result['Credentials']);

客户:

let dropzonesetup = async () => {
    let creds = await fetch('//example.com/auth.php')
        .catch(console.error);

 // If using aws-sdk.js
 AWS.config.credentials = new AWS.Credentials(creds);

上传

要么直接使用DropZone并根据需要进行修改,要么让Dropzone成为aws-sdk的前端。

要使用aws-sdk

您需要包含

<script src="//sdk.amazonaws.com/js/aws-sdk-2.262.1.min.js"></script>

然后更新Dropzone以与其进行交互(基于this tutorial)。

let canceled = file => { if (file.s3upload) file.s3upload.abort() }
let options =
    { canceled
    , removedfile: canceled
    , accept (file, done) {
        let params = {Bucket: 'mybucket', Key: file.name, Body: file };
        file.s3upload = new AWS.S3.ManagedUpload({params});
        done();
        }
    }

// let aws-sdk send events to dropzone.
function sendEvents(file) {
    let progress = i => dz.emit('uploadprogress', file, i.loaded * 100 / i.total, i.loaded);
    file.s3upload.on('httpUploadProgress', progress);
    file.s3upload.send(err => err ? dz.emit('error', file, err) : dz.emit('complete', file));
    }

Dropzone.prototype.uploadFiles = files => files.map(sendEvents);
var dz = new Dropzone('#dz', options)

要在本地使用DropZone

let options = 
    { method: 'put'

    // Have DZ send raw data instead of formData
    , sending (file, xhr) { 
        let _send = xhr.send
        xhr.send = () => _send.call(xhr, file)
        }

    // For STS, if creds is the result of getSessionToken / getFederatedToken
    , headers: { 'x-amz-security-token': creds.SessionToken }

    // Or, if you are using signed URLs (see other answers)
    processing: function(file){ this.options.url = file.signedRequest; }
    async accept (file, done) {
        let url = await fetch('https://example.com/auth.php')
            .catch(err => done('Failed to get an S3 signed upload URL', err));
        file.uploadURL = url
        done()
        }
    }

以上内容未经测试-仅添加了令牌,但不确定确实需要添加哪些标头。检查hereherehere中的文档,并可能使用FineUploader's implementation作为指南。

希望这会有所帮助,并且如果有人想添加对S3支持的拉取请求(如FineUploader中所示),我相信将不胜感激。