web-dev-qa-db-ja.com

長さとファイルタイプに関する情報を含むExpressを使用したS3からのストリーミングファイル

aws-sdkモジュールとExpress 4.13を使用すると、S3からさまざまな方法でファイルをプロキシできます。

このコールバックバージョンは、ファイル本体をバッファーとして、さらにContent-Lengthなどの他の関連ヘッダーを返します。

function(req,res){

  var s3 = new AWS.S3();

  s3.getObject({Bucket: myBucket, Key: myFile},function(err,data){

    if (err) {
      return res.status(500).send("Error!");
    }

    // Headers
    res.set("Content-Length",data.ContentLength)
       .set("Content-Type",data.ContentType);

    res.send(data.Body); // data.Body is a buffer

  });

}

このバージョンの問題は、ファイルを送信する前にファイル全体を取得する必要があることです。これは、特にビデオのような大きなファイルの場合、あまり良くありません。

このバージョンはファイルを直接ストリーミングします:

function(req,res){

  var s3 = new AWS.S3();

  s3.getObject({Bucket: myBucket, Key: myFile})
    .createReadStream()
    .pipe(res);

}

ただし、最初のヘッダーとは異なり、ブラウザーはファイルを適切に処理する必要があるかもしれないヘッダーについては何もしません。

S3から正しいヘッダーを通過させるが、ファイルをストリームとして送信するという、両方の長所を最大限に活用する方法はありますか?最初にHEADリクエストをS3に送信してメタデータを取得することで実行できますが、1回のAPI呼び出しで実行できますか?

26
NChase

1つのアプローチは、httpHeadersイベントをリッスンし、その中にストリームを作成することです。

s3.getObject(params)
    .on('httpHeaders', function (statusCode, headers) {
        res.set('Content-Length', headers['content-length']);
        res.set('Content-Type', headers['content-type']);
        this.response.httpResponse.createUnbufferedStream()
            .pipe(res);
    })
    .send();
29
André Werlang

私のプロジェクトでは、オブジェクトメタデータのみを取得するためにheadObjectを実行します(非常に高速で、オブジェクトのダウンロードを回避します)。次に、パイピングのために伝播する必要があるすべてのヘッダーを応答に追加します。

    var s3 = new AWS.S3();

    var params = {
        Bucket: bucket,
        Key: key
    };
    s3.headObject(params, function (err, data) {
        if (err) {
            // an error occurred
            console.error(err);
            return next();
        }
        var stream = s3.getObject(params).createReadStream();

        // forward errors
        stream.on('error', function error(err) {
            //continue to the next middlewares
            return next();
        });

        //Add the content type to the response (it's not propagated from the S3 SDK)
        res.set('Content-Type', mime.lookup(key));
        res.set('Content-Length', data.ContentLength);
        res.set('Last-Modified', data.LastModified);
        res.set('ETag', data.ETag);

        stream.on('end', () => {
            console.log('Served by Amazon S3: ' + key);
        });
        //Pipe the s3 object to the response
        stream.pipe(res);
    });
15
Mathieu Seiler

AndréWerlangの答えに基づいて、AWS RequestオブジェクトをforwardToExpressメソッドで拡張するために次のことを行いました。

const _ = require('lodash');
const AWS = require('aws-sdk');

AWS.Request.prototype.forwardToExpress = function forwardToExpress(res, next) {
    this
    .on('httpHeaders', function (code, headers) {
        if (code < 300) {
            res.set(_.pick(headers, 'content-type', 'content-length', 'last-modified'));
        }                            
    })
    .createReadStream()
    .on('error', next)
    .pipe(res);
};    

次に、ルートハンドラーで、次のようなことができます。

s3.getObject({Bucket: myBucket, Key: myFile}).forwardToExpress(res, next);
12
cdhowie