Truyền một luồng đến s3.upload ()


89

Tôi hiện đang sử dụng plugin node.js có tên s3-upload-stream để truyền các tệp rất lớn lên Amazon S3. Nó sử dụng API nhiều phần và về phần lớn, nó hoạt động rất tốt.

Tuy nhiên, mô-đun này đang hiển thị tuổi của nó và tôi đã phải sửa đổi nó (tác giả cũng đã không dùng nó nữa). Hôm nay tôi gặp phải một vấn đề khác với Amazon và tôi thực sự muốn nghe theo đề xuất của tác giả và bắt đầu sử dụng aws-sdk chính thức để hoàn thành tải lên của mình.

NHƯNG.

SDK chính thức dường như không hỗ trợ đường ống đến s3.upload(). Bản chất của s3.upload là bạn phải truyền luồng có thể đọc được làm đối số cho hàm tạo S3.

Tôi có khoảng 120+ mô-đun mã người dùng thực hiện nhiều quá trình xử lý tệp khác nhau và chúng không xác định được đích cuối cùng của đầu ra. Động cơ cung cấp cho chúng một luồng đầu ra có thể ghi được có thể ghi được và chúng chuyển tới đó. Tôi không thể đưa cho họ một AWS.S3đối tượng và yêu cầu họ gọi upload()nó mà không thêm mã vào tất cả các mô-đun. Lý do tôi sử dụng s3-upload-streamlà vì nó hỗ trợ đường ống.

Có cách nào để tạo aws-sdk s3.upload()thứ gì đó mà tôi có thể truyền trực tuyến đến không?

Câu trả lời:


132

Kết hợp upload()chức năng S3 với stream.PassThrough()luồng node.js.

Đây là một ví dụ:

inputStream
  .pipe(uploadFromStream(s3));

function uploadFromStream(s3) {
  var pass = new stream.PassThrough();

  var params = {Bucket: BUCKET, Key: KEY, Body: pass};
  s3.upload(params, function(err, data) {
    console.log(err, data);
  });

  return pass;
}

2
Tuyệt vời, điều này đã giải quyết được vụ hack rất xấu xí của tôi = -) Bạn có thể giải thích những gì stream.PassThrough () thực sự làm được không?
mraxus

6
Luồng PassThrough của bạn có đóng khi bạn làm điều này không? Tôi đang có một khoảng thời gian dài khi kết thúc phần tải lên của s3. để đạt được luồng PassThrough của tôi.
443

7
kích thước của tệp được tải lên là 0 byte. Nếu tôi chuyển cùng một dữ liệu từ luồng nguồn sang hệ thống tệp thì tất cả đều hoạt động tốt. Bất kỳ ý tưởng?
Radar155,

3
Một luồng chuyển qua sẽ lấy các byte được ghi vào nó và xuất chúng. Điều này cho phép bạn trả về một luồng có thể ghi mà aws-sdk sẽ đọc từ đó khi bạn ghi vào nó. Tôi cũng sẽ trả lại đối tượng phản hồi từ s3.upload () vì nếu không, bạn không thể đảm bảo quá trình tải lên hoàn tất.
Rebot

1
từ nơi s3param bên trong đường ống và streamđang đến từ đâu?
Blackjack

94

Câu trả lời hơi muộn, hy vọng có thể giúp ích cho người khác. Bạn có thể trả lại cả luồng có thể ghi và lời hứa, vì vậy bạn có thể nhận dữ liệu phản hồi khi quá trình tải lên kết thúc.

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

const uploadStream = ({ Bucket, Key }) => {
  const s3 = new AWS.S3();
  const pass = new stream.PassThrough();
  return {
    writeStream: pass,
    promise: s3.upload({ Bucket, Key, Body: pass }).promise(),
  };
}

Và bạn có thể sử dụng chức năng như sau:

const { writeStream, promise } = uploadStream({Bucket: 'yourbucket', Key: 'yourfile.mp4'});
const readStream = fs.createReadStream('/path/to/yourfile.mp4');

const pipeline = readStream.pipe(writeStream);

Bây giờ bạn có thể kiểm tra lời hứa:

promise.then(() => {
  console.log('upload completed successfully');
}).catch((err) => {
  console.log('upload failed.', err.message);
});

Hoặc như stream.pipe()trả về stream.Writes, đích (biến writeStream ở trên), cho phép một chuỗi các đường ống, chúng ta cũng có thể sử dụng các sự kiện của nó:

 pipeline.on('close', () => {
   console.log('upload successful');
 });
 pipeline.on('error', (err) => {
   console.log('upload failed', err.message)
 });

Nó trông tuyệt vời, nhưng về phía tôi, tôi gặp lỗi này stackoverflow.com/questions/62330721/…
Arco Voltaico

vừa trả lời câu hỏi của bạn. hy vọng nó giúp.
Ahmet Cetin

48

Trong câu trả lời được chấp nhận, chức năng kết thúc trước khi quá trình tải lên hoàn tất và do đó, nó không chính xác. Đoạn mã dưới đây dẫn chính xác từ một luồng có thể đọc được.

Tải lên tài liệu tham khảo

async function uploadReadableStream(stream) {
  const params = {Bucket: bucket, Key: key, Body: stream};
  return s3.upload(params).promise();
}

async function upload() {
  const readable = getSomeReadableStream();
  const results = await uploadReadableStream(readable);
  console.log('upload complete', results);
}

Bạn cũng có thể tiến thêm một bước và xuất thông tin tiến trình bằng cách sử dụng ManagedUploadnhư sau:

const manager = s3.upload(params);
manager.on('httpUploadProgress', (progress) => {
  console.log('progress', progress) // { loaded: 4915, total: 192915, part: 1, key: 'foo.jpg' }
});

Tham chiếu ManagedUpload

Danh sách các sự kiện có sẵn


1
aws-sdk hiện cung cấp các hứa hẹn được tích hợp trong 2.3.0+, vì vậy bạn không cần phải nâng chúng lên nữa. s3.upload (params) .promise (). then (data => data) .catch (error => error);
DBrown

1
@DBrown Cảm ơn vì con trỏ! Tôi đã cập nhật câu trả lời, theo đó.
tsuz

1
@tsuz, cố gắng triển khai giải pháp của bạn, hãy cho tôi biết lỗi:, TypeError: dest.on is not a functionbất kỳ ý kiến ​​nào tại sao?
FireBrand

dest.ongì? Bạn có thể chỉ ra một ví dụ? @FireBrand
tsuz

9
Điều này cho biết câu trả lời được chấp nhận là chưa đầy đủ nhưng nó không hoạt động với đường ống đến s3.upload như được chỉ ra trong bài đăng cập nhật của @ Womp. Sẽ rất hữu ích nếu câu trả lời này được cập nhật để lấy kết quả đầu ra của một thứ khác!
MattW

6

Không có câu trả lời nào phù hợp với tôi vì tôi muốn:

  • Ống vào s3.upload()
  • Đưa kết quả của s3.upload()vào một luồng khác

Câu trả lời được chấp nhận không làm được điều sau. Những người khác dựa vào api hứa hẹn, nó rất cồng kềnh khi làm việc với các đường ống dòng.

Đây là sửa đổi của tôi về câu trả lời được chấp nhận.

const s3 = new S3();

function writeToS3({Key, Bucket}) {
  const Body = new stream.PassThrough();

  s3.upload({
    Body,
    Key,
    Bucket: process.env.adpBucket
  })
   .on('httpUploadProgress', progress => {
       console.log('progress', progress);
   })
   .send((err, data) => {
     if (err) {
       Body.destroy(err);
     } else {
       console.log(`File uploaded and available at ${data.Location}`);
       Body.destroy();
     }
  });

  return Body;
}

const pipeline = myReadableStream.pipe(writeToS3({Key, Bucket});

pipeline.on('close', () => {
  // upload finished, do something else
})
pipeline.on('error', () => {
  // upload wasn't successful. Handle it
})


Có vẻ tuyệt vời, nhưng về phía tôi, tôi gặp lỗi này stackoverflow.com/questions/62330721/…
Arco Voltaico

5

Loại giải pháp Script:
Ví dụ này sử dụng:

import * as AWS from "aws-sdk";
import * as fsExtra from "fs-extra";
import * as zlib from "zlib";
import * as stream from "stream";

Và chức năng async:

public async saveFile(filePath: string, s3Bucket: AWS.S3, key: string, bucketName: string): Promise<boolean> { 

         const uploadStream = (S3: AWS.S3, Bucket: string, Key: string) => {
            const passT = new stream.PassThrough();
            return {
              writeStream: passT,
              promise: S3.upload({ Bucket, Key, Body: passT }).promise(),
            };
          };
        const { writeStream, promise } = uploadStream(s3Bucket, bucketName, key);
        fsExtra.createReadStream(filePath).pipe(writeStream);     //  NOTE: Addition You can compress to zip by  .pipe(zlib.createGzip()).pipe(writeStream)
        let output = true;
        await promise.catch((reason)=> { output = false; console.log(reason);});
        return output;
}

Gọi phương thức này ở đâu đó như:

let result = await saveFileToS3(testFilePath, someS3Bucket, someKey, someBucketName);

4

Điều cần lưu ý ở đây trong câu trả lời được chấp nhận nhiều nhất ở trên là: Bạn cần trả lại pass trong hàm nếu bạn đang sử dụng đường ống như,

fs.createReadStream(<filePath>).pipe(anyUploadFunction())

function anyUploadFunction () { 
 let pass = new stream.PassThrough();
 return pass // <- Returning this pass is important for the stream to understand where it needs to write to.
}

Nếu không, nó sẽ âm thầm chuyển sang bước tiếp theo mà không gặp lỗi hoặc sẽ xuất hiện lỗi TypeError: dest.on is not a functiontùy thuộc vào cách bạn viết hàm


3

Nếu nó giúp được bất kỳ ai, tôi đã có thể phát trực tuyến từ máy khách sang s3 thành công:

https://gist.github.com/mattlockyer/532291b6194f6d9ca40cb82564db9d2a

Mã phía máy chủ giả định reqlà một đối tượng luồng, trong trường hợp của tôi, nó được gửi từ máy khách với thông tin tệp được đặt trong tiêu đề.

const fileUploadStream = (req, res) => {
  //get "body" args from header
  const { id, fn } = JSON.parse(req.get('body'));
  const Key = id + '/' + fn; //upload to s3 folder "id" with filename === fn
  const params = {
    Key,
    Bucket: bucketName, //set somewhere
    Body: req, //req is a stream
  };
  s3.upload(params, (err, data) => {
    if (err) {
      res.send('Error Uploading Data: ' + JSON.stringify(err) + '\n' + JSON.stringify(err.stack));
    } else {
      res.send(Key);
    }
  });
};

Vâng, nó phá vỡ quy ước nhưng nếu bạn nhìn vào ý chính, nó sạch hơn nhiều so với bất kỳ thứ gì khác mà tôi thấy khi sử dụng multer, busboy, v.v.

+1 cho chủ nghĩa thực dụng và cảm ơn @SalehenRahman vì sự giúp đỡ của anh ấy.


multer, busboy xử lý tải lên nhiều phần / biểu mẫu-dữ liệu. yêu cầu như một luồng hoạt động khi máy khách gửi một bộ đệm dưới dạng nội dung từ XMLHttpRequest.
André Werlang

Để làm rõ, tải lên đang được thực hiện từ phía sau không phải khách hàng phải không?
numX

Có, đó là "đường ống" luồng, BẬT chương trình phụ trợ, nhưng nó đến từ giao diện người dùng
mattdlockyer

3

Đối với những người phàn nàn rằng khi họ sử dụng chức năng tải lên api s3 và tệp byte 0 kết thúc trên s3 (@ Radar155 và @gabo) - tôi cũng gặp sự cố này.

Tạo luồng PassThrough thứ hai và chỉ cần chuyển tất cả dữ liệu từ luồng đầu tiên sang luồng thứ hai và chuyển tham chiếu đến luồng thứ hai đó thành s3. Bạn có thể thực hiện việc này theo một số cách khác nhau - có thể một cách sai lầm là lắng nghe sự kiện "dữ liệu" trên luồng đầu tiên và sau đó ghi cùng dữ liệu đó vào luồng thứ hai - tương tự đối với sự kiện "kết thúc" - chỉ cần gọi hàm kết thúc trên luồng thứ hai. Tôi không biết liệu đây có phải là lỗi trong api aws, phiên bản của nút hay một số vấn đề khác - nhưng nó đã giải quyết được vấn đề cho tôi.

Đây là cách nó có thể trông:

var PassThroughStream = require('stream').PassThrough;
var srcStream = new PassThroughStream();

var rstream = fs.createReadStream('Learning/stocktest.json');
var sameStream = rstream.pipe(srcStream);
// interesting note: (srcStream == sameStream) at this point
var destStream = new PassThroughStream();
// call your s3.upload function here - passing in the destStream as the Body parameter
srcStream.on('data', function (chunk) {
    destStream.write(chunk);
});

srcStream.on('end', function () {
    dataStream.end();
});

Điều này thực sự làm việc cho tôi. Chức năng tải lên của S3 đã im lặng "chết" bất cứ khi nào sử dụng tải lên nhiều phần, nhưng khi sử dụng giải pháp của bạn, nó hoạt động tốt (!). Cảm ơn! :)
jhdrn

Bạn có thể cung cấp một số thông tin về lý do tại sao luồng thứ hai lại cần thiết không?
noob7

1

Làm theo các câu trả lời khác và sử dụng AWS SDK mới nhất cho Node.js, có một giải pháp đơn giản và gọn gàng hơn nhiều vì hàm upload () s3 chấp nhận một luồng, sử dụng cú pháp await và lời hứa của S3:

var model = await s3Client.upload({
    Bucket : bucket,
    Key : key,
    ContentType : yourContentType,
    Body : fs.createReadStream(path-to-file)
}).promise();

0

Tôi đang sử dụng KnexJS và gặp sự cố khi sử dụng API phát trực tuyến của họ. Cuối cùng thì mình cũng đã sửa được, hy vọng những điều sau đây sẽ giúp ích được cho ai đó.

const knexStream = knex.select('*').from('my_table').stream();
const passThroughStream = new stream.PassThrough();

knexStream.on('data', (chunk) => passThroughStream.write(JSON.stringify(chunk) + '\n'));
knexStream.on('end', () => passThroughStream.end());

const uploadResult = await s3
  .upload({
    Bucket: 'my-bucket',
    Key: 'stream-test.txt',
    Body: passThroughStream
  })
  .promise();

-3

Nếu bạn biết kích thước của luồng, bạn có thể sử dụng minio-j để tải luồng như sau:

  s3Client.putObject('my-bucketname', 'my-objectname.ogg', stream, size, 'audio/ogg', function(e) {
    if (e) {
      return console.log(e)
    }
    console.log("Successfully uploaded the stream")
  })
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.