使用 Axios 进行 Amazon S3 远程文件上传

Posted

技术标签:

【中文标题】使用 Axios 进行 Amazon S3 远程文件上传【英文标题】:Amazon S3 Remote File Upload with Axios 【发布时间】:2019-02-26 18:44:45 【问题描述】:

我正在尝试编写一个函数:

    以远程 URL 作为参数, 使用 axios 获取文件 将流上传到 amazon s3 最后,返回上传的网址

我找到了帮助 here on ***。到目前为止,我有这个:

/* 
 * Method to pipe the stream 
 */
const uploadFromStream = (file_name, content_type) => 
  const pass = new stream.PassThrough();

  const obj_key = generateObjKey(file_name);
  const params =  Bucket: config.bucket, ACL: config.acl, Key: obj_key, ContentType: content_type, Body: pass ;

  s3.upload(params, function(err, data) 
    if(!err)
        return data.Location;
     else 
        console.log(err, data);
    
  );

  return pass;



/*
 * Method to upload remote file to s3
 */
const uploadRemoteFileToS3 = async (remoteAddr) => 
    axios(
        method: 'get',
        url: remoteAddr,
        responseType: 'stream'
    ).then( (response) => 
        if(response.status===200)
            const file_name = remoteAddr.substring(remoteAddr.lastIndexOf('/')+1);
            const content_type = response.headers['content-type'];
            response.data.pipe(uploadFromStream(file_name, content_type));
        
    );

但是uploadRemoteFileToS3 没有返回任何东西(因为它是一个异步函数)。如何获取上传的url?

更新

我进一步改进了代码并编写了一个类。这是我现在拥有的:

const config = require('../config.json');

const stream = require('stream');
const axios = require('axios');
const AWS = require('aws-sdk');

class S3RemoteUploader 
    constructor(remoteAddr)
        this.remoteAddr = remoteAddr;
        this.stream = stream;
        this.axios = axios;
        this.config = config;
        this.AWS = AWS;
        this.AWS.config.update(
            accessKeyId: this.config.api_key,
            secretAccessKey: this.config.api_secret
        );
        this.spacesEndpoint = new this.AWS.Endpoint(this.config.endpoint);
        this.s3 = new this.AWS.S3(endpoint: this.spacesEndpoint);

        this.file_name = this.remoteAddr.substring(this.remoteAddr.lastIndexOf('/')+1);
        this.obj_key = this.config.subfolder+'/'+this.file_name;
        this.content_type = 'application/octet-stream';

        this.uploadStream();
    

    uploadStream()
        const pass = new this.stream.PassThrough();
        this.promise = this.s3.upload(
            Bucket: this.config.bucket,
            Key: this.obj_key,
            ACL: this.config.acl,
            Body: pass,
            ContentType: this.content_type
        ).promise();
        return pass;
    

    initiateAxiosCall() 
        axios(
            method: 'get',
            url: this.remoteAddr,
            responseType: 'stream'
        ).then( (response) => 
            if(response.status===200)
                this.content_type = response.headers['content-type'];
                response.data.pipe(this.uploadStream());
            
        );
    

    dispatch() 
        this.initiateAxiosCall();
    

    async finish()
        //console.log(this.promise); /* return Promise  Pending  */
        return this.promise.then( (r) => 
            console.log(r.Location);
            return r.Location;
        ).catch( (e)=>
            console.log(e);
        );
    

    run() 
        this.dispatch();
        this.finish();
    

但仍然不知道如何在 promise 被解决时捕获结果。到目前为止,我尝试了这些:

testUpload = new S3RemoteUploader('https://avatars2.githubusercontent.com/u/41177');
testUpload.run();
//console.log(testUpload.promise); /* Returns Promise  Pending  */
testUpload.promise.then(r => console.log); // does nothing

但以上都不起作用。我有一种感觉,我错过了一些非常微妙的东西。有什么线索吗?

【问题讨论】:

它是https://address-to-your-s3-bucket + path-of-file。如果您的 ACL 是公开读取的,则此 url 可以正常工作。文件路径是Key 这里 这已经很老了,但我看到你的异步/承诺调用方法没有返回任何东西。例如,在您的 run 方法中,您调用 dispatch 但那里没有 await。如此多的事情只是变成了“一劳永逸”。任何人,你在这里拥有的骨头我确实觉得很有帮助。谢谢。 【参考方案1】:

上传后,您可以调用 s3 sdk 中的 getsignedurl 函数来获取 url,您还可以在其中指定 url 的到期时间。您需要传递该功能的密钥。现在旅行将在稍后更新示例。

生成一个简单的预签名 URL,允许任何用户查看 您拥有的存储桶中的私有对象的内容,您可以使用 在调用 getSignedUrl() 之后:

 var s3 = new AWS.S3(); 
 var params = Bucket: 'myBucket', Key: 'myKey'; 
 s3.getSignedUrl('getObject', params, function (err, url)   
   console.log("The URL is", url); 
 );

官方文档链接 http://docs.amazonaws.cn/en_us/AWSjavascriptSDK/guide/node-examples.html

代码必须是这样的

function uploadFileToS3AndGenerateUrl(cb) 
const pass = new stream.PassThrough();//I have generated streams from file. Using this since this is what you have used. Must be a valid one.
var params = 
            Bucket: "your-bucket", // required
            Key: key , // required
            Body: pass,
            ContentType: 'your content type',

        ;
s3.upload(params, function(s3Err, data) 
    if (s3Err) 
        cb(s3Err)
    
    console.log(`File uploaded successfully at $data.Location`)

    const params = 
        Bucket: 'your-bucket',
        Key: data.key,
        Expires: 180
    ;
    s3.getSignedUrl('getObject', params, (urlErr, urlData) => 
        if (urlErr) 

            console.log('There was an error getting your files: ' + urlErr);
            cb(urlErr);

         else 
            console.log(`url: $urlData`);
            cb(null, urlData);

        
    )
)

【讨论】:

感谢您的回复。为了使用 getsignedurl 我需要确认上传已经完成。能否请您查看代码并指导我在哪里可以检查上传是否完成? 另外,getsignedurl 给出了一个在给定时间间隔后过期的 url。我不想那样。我想要一个没有过期的网址 您不能同时拥有错误和数据。获取到数据即可确认上传成功。您需要将数据中的密钥作为 getsignedurl 函数的输入传递。 嗯。仍然没有解决我提到的异步问题。我将永远得到undefined。无法返回promise 设置到期是可选的,我刚才提到如果你想你可以使用它。【参考方案2】:

请检查我是否已更新您的代码可能会对您有所帮助。

    /*
         * Method to upload remote file to s3
         */
        const uploadRemoteFileToS3 = async (remoteAddr) => 
            const response = await axios(
                method: 'get',
                url: remoteAddr,
                responseType: 'stream'
            )
               if(response.status===200)
                    const file_name = remoteAddr.substring(remoteAddr.lastIndexOf('/')+1);
                    const content_type = response.headers['content-type'];
                    response.data.pipe(uploadFromStream(file_name, content_type));
                
                return new Promise((resolve, reject) => 
                    response.data.on('end', (response) => 
                      console.log(response)
                      resolve(response)
                    )

                    response.data.on('error', () => 
                      console.log(response);
                      reject(response)
                    )
              )
        ;

       * 
     * Method to pipe the stream 
     */
    const uploadFromStream = (file_name, content_type) => 
       return new Promise((resolve, reject) => 
          const pass = new stream.PassThrough();
          const obj_key = generateObjKey(file_name);
          const params =  Bucket: config.bucket, ACL: config.acl, Key: obj_key, ContentType: content_type, Body: pass ;
          s3.upload(params, function(err, data) 
            if(!err)
                console.log(data)
                return resolve(data.Location);
             else 
                console.log(err)
                return reject(err);
            
          );
       );
    

//call uploadRemoteFileToS3
    uploadRemoteFileToS3(remoteAddr)
      .then((finalResponse) => 
            console.log(finalResponse)
       )
       .catch((err) => 
         console.log(err);
    );

【讨论】:

以上是关于使用 Axios 进行 Amazon S3 远程文件上传的主要内容,如果未能解决你的问题,请参考以下文章

为啥从远程 AWS S3 服务器下载 json 文件时 axios GET 错误

如何将文件从远程服务器传输到我的 Amazon S3 实例?

如何在不下载文件的情况下搜索amazon S3存储桶中的文件内容

sh 使用Crontab和Amazon EC2上的s3cmd.Red Hat Linux对Amazon S3进行MongoDB自动备份

使用 AWSSDK.S3 使用 C# 对 Amazon S3 存储桶进行身份验证

在 angular2 中使用 http 访问 Amazon s3