現在、 s3-upload-stream というnode.jsプラグインを使用して、非常に大きなファイルをAmazon S3にストリーミングしています。マルチパートAPIを使用し、ほとんどの部分で非常にうまく機能します。
ただし、このモジュールは古くなっており、既に修正する必要があります(作成者も非推奨にしています)。今日、私はAmazonで別の問題にぶつかりました。著者の推薦を受け取り、アップロードを完了するために公式のaws-sdkを使い始めたいと思います。
しかし。
公式SDKはs3.upload()
へのパイピングをサポートしていないようです。 s3.uploadの性質は、読み取り可能なストリームを引数としてS3コンストラクターに渡す必要があることです。
さまざまなファイル処理を行う約120以上のユーザーコードモジュールがあり、それらは最終的な出力先に依存しません。エンジンは、パイプ可能な書き込み可能な出力ストリームをそれらに渡し、パイプします。それらにAWS.S3
オブジェクトを渡し、すべてのモジュールにコードを追加せずに_upload()
を呼び出すように依頼することはできません。 s3-upload-stream
を使用した理由は、パイピングをサポートしていたためです。
Aws-sdk s3.upload()
をストリームをパイプできるものにする方法はありますか?
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;
}
少し遅れて答え、それは誰か他の人を助けるかもしれない。書き込み可能なストリームとプロミスの両方を返すことができるため、アップロードの完了時に応答データを取得できます。
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');
readStream.pipe(writeStream);
promise.then(console.log);
受け入れられた回答では、アップロードが完了する前に関数が終了するため、正しくありません。以下のコードは、読み取り可能なストリームから正しくパイプします。
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' }
});
タイプスクリプトソリューション:
この例では以下を使用します。
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);
S3 apiアップロード機能とゼロバイトファイルを使用するとs3(@ Radar155と@gabo)になってしまうと不満を言う人のために-この問題もありました。
2番目のPassThroughストリームを作成し、1番目から2番目にすべてのデータをパイプして、その2番目への参照をs3に渡します。いくつかの異なる方法でこれを行うことができます-おそらくダーティな方法は、最初のストリームで「データ」イベントをリッスンし、次に同じデータを2番目のストリームに書き込むことです-「end」イベントの場合と同様に2番目のストリームの終了関数。これがaws api、nodeのバージョン、またはその他の問題のバグかどうかはわかりませんが、問題を回避できました。
外観は次のとおりです。
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.upload()
にパイプs3.upload()
の結果を別のストリームにパイプします受け入れられた答えは後者を行いません。その他は、Promise APIに依存しています。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
})
それが私がクライアントから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に感謝します。
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();