스트림을 s3.upload ()로 파이프


89

저는 현재 s3-upload-stream 이라는 node.js 플러그인을 사용 하여 매우 큰 파일을 Amazon S3 로 스트리밍 하고 있습니다. 멀티 파트 API를 사용하며 대부분 잘 작동합니다.

그러나이 모듈은 그 나이를 보여주고 있으며 이미 수정해야했습니다 (저자도이 모듈을 사용하지 않습니다). 오늘 저는 Amazon에서 또 다른 문제를 만났고 작성자의 추천을 받아 공식 aws-sdk를 사용하여 업로드를 완료하고 싶습니다.

그러나.

공식 SDK는 s3.upload(). s3.upload의 특성은 읽을 수있는 스트림을 S3 생성자에 인수로 전달해야한다는 것입니다.

다양한 파일 처리를 수행하는 약 120 개 이상의 사용자 코드 모듈이 있으며 출력의 최종 대상에 대해 알 수 없습니다. 엔진은 파이프 가능한 쓰기 가능한 출력 스트림을 전달하고 여기에 파이프합니다. 나는 그들에게 AWS.S3객체를 건네 upload()주고 모든 모듈에 코드를 추가하지 않고는 그것을 호출하도록 요청할 수 없습니다 . 내가 사용한 이유 s3-upload-stream는 배관을 지원했기 때문입니다.

s3.upload()스트림을 파이프 할 수 있는 대상으로 aws-sdk를 만드는 방법 이 있습니까?

답변:


131

S3 upload()함수를 node.js stream.PassThrough()스트림으로 래핑합니다 .

예를 들면 다음과 같습니다.

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
좋아, 이것은 내 매우 추악한 해킹을 해결했습니다 =-) stream.PassThrough ()가 실제로 무엇을하는지 설명 할 수 있습니까?
mraxus

6
이렇게하면 PassThrough 스트림이 닫히나요? PassThrough 스트림에 도달하기 위해 s3.upload에서 닫기를 전파하는 데 시간이 많이 걸립니다.
four43

7
업로드 된 파일의 크기는 0 바이트입니다. 소스 스트림에서 파일 시스템으로 동일한 데이터를 파이프하면 모두 잘 작동합니다. 어떤 생각?
Radar155

3
통과 스트림은 쓰여진 바이트를 가져와 출력합니다. 이렇게하면 aws-sdk가 쓸 때 읽을 쓰기 가능한 스트림을 반환 할 수 있습니다. 또한 s3.upload ()에서 응답 객체를 반환 할 것입니다. 그렇지 않으면 업로드가 완료되었는지 확인할 수 없기 때문입니다.
reconbot

1
s3파이프 내부 의 매개 변수 는 어디 stream에서 왔습니까?
Blackjack

94

조금 늦게 대답하면 다른 사람에게 도움이 될 수 있습니다. 쓰기 가능한 스트림과 프라 미스를 모두 반환 할 수 있으므로 업로드가 완료되면 응답 데이터를 얻을 수 있습니다.

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

그리고 다음과 같이 기능을 사용할 수 있습니다.

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

const pipeline = readStream.pipe(writeStream);

이제 약속을 확인할 수 있습니다.

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

또는 stream.pipe()파이프 체인을 허용하는 대상 (위의 writeStream 변수) 인 stream.Writable 을 반환하므로 해당 이벤트를 사용할 수도 있습니다.

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

멋져 보이지만 제 쪽에서는이 오류가 발생합니다. stackoverflow.com/questions/62330721/…
Arco Voltaico

귀하의 질문에 답장했습니다. 도움이되기를 바랍니다.
Ahmet Cetin

48

수락 된 답변에서 업로드가 완료되기 전에 기능이 종료되므로 잘못된 것입니다. 아래 코드는 읽을 수있는 스트림에서 올바르게 파이프됩니다.

참조 업로드

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

다음과 같이 한 단계 더 나아가 진행 정보를 출력 할 수도 있습니다 ManagedUpload.

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

ManagedUpload 참조

사용 가능한 이벤트 목록


1
aws-sdk는 이제 2.3.0+에 내장 된 promise를 제공하므로 더 이상 해제 할 필요가 없습니다. s3.upload (params) .promise (). then (data => data) .catch (error => error);
DBrown

1
@DBrown 포인터 주셔서 감사합니다! 그에 따라 답변을 업데이트했습니다.
tsuz

1
@tsuz, 솔루션을 구현하려고하면 오류가 발생합니다. TypeError: dest.on is not a function, 그 이유는 무엇입니까?
FireBrand

무엇입니까 dest.on? 예를 보여줄 수 있습니까? @FireBrand
tsuz

9
이것은 수락 된 답변이 불완전하지만 @Womp의 업데이트 된 게시물에 표시된 것처럼 s3.upload에 대한 배관에서는 작동하지 않는다고 말합니다. 이 답변이 다른 것의 파이프 출력을 취하도록 업데이트되면 매우 도움이 될 것입니다!
MattW

6

내가 원했기 때문에 어떤 답변도 나를 위해 일하지 않았습니다.

  • 파이프 s3.upload()
  • 결과 s3.upload()를 다른 스트림으로 파이프

받아 들여진 대답은 후자를하지 않습니다. 다른 것들은 promise api에 의존하는데, 이는 하천 파이프로 작업 할 때 번거 롭습니다.

이것은 받아 들여진 대답의 수정입니다.

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


멋져 보이지만 제 쪽에서는이 오류가 발생합니다. stackoverflow.com/questions/62330721/…
Arco Voltaico

5

유형 스크립트 솔루션 :
이 예제에서는 다음을 사용합니다.

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

그리고 비동기 기능 :

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

이 메서드를 다음과 같이 호출하십시오.

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

4

위의 가장 많이 받아 들여진 답변에서 주목할 점은 다음과 같습니다. 파이프를 사용하는 경우 함수에서 패스를 반환해야합니다.

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.
}

그렇지 않으면 오류를 발생시키지 않고 조용히 다음으로 이동하거나 TypeError: dest.on is not a function함수를 작성한 방법 에 따라 오류가 발생 합니다.


3

그것이 누구에게나 도움이된다면 클라이언트에서 s3로 성공적으로 스트리밍 할 수있었습니다.

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

서버 측 코드는 req스트림 객체 라고 가정합니다 . 제 경우에는 헤더에 파일 정보가 설정된 클라이언트에서 전송되었습니다.

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

예, 관습을 어기지만 요점을 보면 multer, busboy 등을 사용하여 찾은 다른 것보다 훨씬 깨끗합니다.

실용주의에 +1하고 도움을 주신 @SalehenRahman에게 감사드립니다.


multer, busboy는 멀티 파트 / 양식 데이터 업로드를 처리합니다. req as a stream은 클라이언트가 XMLHttpRequest에서 본문으로 버퍼를 보낼 때 작동합니다.
André Werlang

명확히하기 위해 업로드는 클라이언트가 아닌 백엔드에서 수행되고 있습니까?
numX

예, 백엔드에서 스트림을 "파이핑"하고 있지만 프론트 엔드에서 나왔습니다
mattdlockyer

3

s3 api 업로드 기능과 0 바이트 파일을 사용할 때 s3 (@ Radar155 및 @gabo)에서 종료된다는 불평을하는 사람들을 위해이 문제도 발생했습니다.

두 번째 PassThrough 스트림을 만들고 첫 번째에서 두 번째로 모든 데이터를 파이프하고 두 번째에 대한 참조를 s3에 전달합니다. 몇 가지 다른 방법으로이 작업을 수행 할 수 있습니다. 아마도 더러운 방법은 첫 번째 스트림에서 "data"이벤트를 수신 한 다음 두 번째 스트림에 동일한 데이터를 쓰는 것입니다. "end"이벤트와 유사하게 두 번째 스트림의 종료 기능. 이것이 aws api의 버그인지, 노드 버전인지 또는 다른 문제인지는 모르겠지만 문제를 해결했습니다.

다음과 같이 보일 수 있습니다.

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

이것은 실제로 저에게도 효과적이었습니다. S3 업로드 기능은 멀티 파트 업로드가 사용될 때마다 조용히 "죽어"있었지만 솔루션을 사용할 때는 제대로 작동했습니다 (!). 감사! :)
jhdrn

두 번째 스트림이 필요한 이유에 대한 정보를 제공 할 수 있습니까?
noob7 19

1

다른 답변에 따라 Node.js 용 최신 AWS SDK를 사용하면 s3 upload () 함수가 await 구문과 S3의 약속을 사용하여 스트림을 수락하므로 훨씬 더 깔끔하고 간단한 솔루션이 있습니다.

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

0

KnexJS를 사용하고 있으며 스트리밍 API를 사용하는 데 문제가 있습니다. 나는 마침내 그것을 고쳤고, 다음이 누군가를 도울 것입니다.

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

스트림의 크기를 알고 있다면 minio-js 를 사용 하여 다음과 같이 스트림을 업로드 할 수 있습니다 .

  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")
  })
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.