ストリームをs3.upload()にパイプします


94

私は現在、s3-upload-streamと呼ばれるnode.jsプラグインを使用して、非常に大きなファイルをAmazonS3にストリーミングしています。マルチパートAPIを使用し、ほとんどの場合、非常にうまく機能します。

ただし、このモジュールはその年齢を示しており、私はすでにモジュールに変更を加える必要がありました(作成者も非推奨にしています)。今日、私はAmazonで別の問題に遭遇しました。著者の推奨を受け入れ、公式のaws-sdkを使用してアップロードを実行したいと思います。

だが。

公式SDKはへのパイプをサポートしていないようですs3.upload()。s3.uploadの性質は、読み取り可能なストリームを引数としてS3コンストラクターに渡す必要があることです。

さまざまなファイル処理を行う約120以上のユーザーコードモジュールがあり、それらは出力の最終的な宛先に依存しません。エンジンはそれらにパイプ可能な書き込み可能な出力ストリームを渡し、それらはそれにパイプします。すべてのモジュールにコードを追加せずに、AWS.S3オブジェクトを渡して呼び出すように依頼することはできませんupload()s3-upload-stream配管に対応しているので使用しました。

aws-sdkをs3.upload()ストリームをパイプできるものにする方法はありますか?

回答:


136

S3upload()関数をnode.jsstream.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 2016年

6
これを行うと、PassThroughストリームは閉じますか?PassThroughストリームをヒットするためにs3.uploadのクローズを伝播するのにかなりの時間があります。
four43 2016

7
アップロードされたファイルのサイズは0バイトです。同じデータをソースストリームからファイルシステムにパイプすると、すべて正常に機能します。何か案が?
radar155 2017年

3
パススルーストリームは、それに書き込まれたバイトを受け取り、それらを出力します。これにより、書き込み時にaws-sdkが読み取る書き込み可能なストリームを返すことができます。また、s3.upload()から応答オブジェクトを返します。そうしないと、アップロードが確実に完了することができないためです。
reconbot 2017

1
これは、読み取り可能なストリームをBodyに渡すのと同じではありませんが、より多くのコードが含まれていますか?AWS SDKは引き続きPassThroughストリームでread()を呼び出すため、S3までの真のパイピングはありません。唯一の違いは、途中に余分なストリームがあることです。
ShadowChaser

96

少し遅い答えですが、うまくいけば他の誰かを助けるかもしれません。書き込み可能なストリームとpromiseの両方を返すことができるため、アップロードが完了したときに応答データを取得できます。

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を確認できます。

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/...
アルコVoltaico

あなたの質問に答えただけです。それが役に立てば幸い。
AhmetCetin20年

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 2017

1
@DBrownポインタをありがとう!それに応じて、回答を更新しました。
tsuz 2017

1
@tsuz、あなたのソリューションを実装しようとすると、私にエラーが発生します:TypeError: dest.on is not a function、なぜですか?
炬火

dest.onですか?例を挙げていただけますか?@FireBrand
tsuz

9
これは、受け入れられた回答が不完全であることを示していますが、@ Wompの更新された投稿に示されているようにs3.uploadへのパイピングでは機能しません。この回答が更新されて、他の何かのパイプ出力を取得できると非常に役立ちます。
MattW 2018年

6

私がしたかったので、答えのどれも私のために働きませんでした:

  • パイプで s3.upload()
  • 結果s3.upload()を別のストリームにパイプします

受け入れられた答えは後者をしません。他のものは、ストリームパイプを操作するときに作業するのが面倒なpromiseapiに依存しています。

これは、受け入れられた答えの私の修正です。

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/...取得しています
アルコ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はmultipart / form-dataのアップロードを処理します。ストリームとしてのreqは、クライアントがXMLHttpRequestから本体としてバッファーを送信するときに機能します。
アンドレ・Werlang

明確にするために、アップロードはクライアントではなくバックエンドから実行されていますか?
numX

はい、それは、バックエンドで「配管」の流れ、ですが、それはフロントエンドから来た
mattdlockyer

3

s3 apiアップロード関数を使用し、ゼロバイトファイルがs3(@ Radar155および@gabo)で終わると不満を言う人のために、私もこの問題を抱えていました。

2番目のPassThroughストリームを作成し、すべてのデータを最初のストリームから2番目のストリームにパイプして、その2番目のストリームへの参照をs3に渡します。これはいくつかの異なる方法で行うことができます-おそらく汚い方法は、最初のストリームで「データ」イベントをリッスンしてから、同じデータを2番目のストリームに書き込むことです-「終了」イベントの場合も同様です-ただ呼び出すだけです2番目のストリームの終了関数。これがawsapiのバグなのか、ノードのバージョンなのか、その他の問題なのかはわかりませんが、問題は回避できました。

外観は次のとおりです。

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

2番目のストリームが必要な理由について教えてください。
noob7

2

他の回答に従い、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.