web-dev-qa-db-ja.com

Express / nodejsアプリでS3に保存されたファイルを提供する

ユーザーの写真を非公開にするアプリがあります。写真(サムネイルも)をAWSs3に保存します。ユーザーが自分の写真(サムネイルなど)を表示できるページがサイトにあります。今私の問題は、これらのファイルをどのように提供するかです。私が評価したいくつかのオプションは次のとおりです。

  • 署名付きURL生成を使用してCloudFront(またはAWS)からファイルを提供します。しかし、問題は、ユーザーがページを更新するたびに、非常に多くの署名付きURLを再度作成してロードする必要があることです。したがって、ブラウザに画像をキャッシュすることはできません。これは良い選択でした。とにかくjavascriptでまだやることはありますか?セキュリティ上の問題により、これらのURLの有効期間を長くすることはできません。次に、その時間枠内で誰かがそのURLを入手した場合、アプリからの認証を実行せずにファイルを表示できます。
  • 他のオプションは、S3サーバーからストリーミングした後、Expressアプリ自体からファイルを提供することです。これにより、httpキャッシュヘッダーを使用できるため、ブラウザのキャッシュを有効にできます。また、認証されない限り誰もファイルを表示できないようにします。理想的には、ファイルをストリーミングしたいと思います。NGINXプロキシリレーを使用してホストしているので、反対側をNGINXにストリーミングします。しかし、私が見るように、それはファイルが同じシステムのファイルに存在する場合にのみ可能です。しかし、ここで私はそれをストリーミングし、ストリームが完了したときに戻る必要があります。ファイルをローカルに保存したくない。

2つのオプションのどちらがより良い選択であるかを評価することができませんか?できるだけ多くの作業をS3またはクラウドフロントにリダイレクトしたいのですが、シンギングされたURLを使用しても、最初にサーバーにリクエストが送信されます。キャッシュ機能も必要です。

では、理想的な方法は何でしょうか。それらの方法に関連する特定の質問に対する答えがありますか?

19

s3からストリーミングするだけです。それは非常に簡単で、署名されたURLははるかに困難です。画像をS3にアップロードするときは、必ずcontent-typeヘッダーとcontent-lengthヘッダーを設定してください。

var aws = require('knox').createClient({
  key: '',
  secret: '',
  bucket: ''
})

app.get('/image/:id', function (req, res, next) {
  if (!req.user.is.authenticated) {
    var err = new Error()
    err.status = 403
    next(err)
    return
  }

  aws.get('/image/' + req.params.id)
  .on('error', next)
  .on('response', function (resp) {
    if (resp.statusCode !== 200) {
      var err = new Error()
      err.status = 404
      next(err)
      return
    }

    res.setHeader('Content-Length', resp.headers['content-length'])
    res.setHeader('Content-Type', resp.headers['content-type'])

    // cache-control?
    // etag?
    // last-modified?
    // expires?

    if (req.fresh) {
      res.statusCode = 304
      res.end()
      return
    }

    if (req.method === 'HEAD') {
      res.statusCode = 200
      res.end()
      return
    }

    resp.pipe(res)
  })
})
20
Jonathan Ong

302 Foundブラウザを使用してユーザーを署名付きURLにリダイレクトする場合、ブラウザはcache-controlヘッダーに従って結果の画像をキャッシュし、2回目は要求しません。

ブラウザが署名されたURL自体をキャッシュしないようにするには、適切なCache-Controlヘッダーを一緒に送信する必要があります。

Cache-Control: private, no-cache, no-store, must-revalidate

そのため、次回は元のURLにリクエストを送信し、新しい署名付きURLにリダイレクトされます。

knox method を使用して、signedUrlで署名付きURLを生成できます。

ただし、アップロードされたすべての画像に適切なヘッダーを設定することを忘れないでください。一部のブラウザはCache-Controlヘッダーをサポートしておらず、Expiresでは絶対有効期限のみを設定できるため、Cache-ControlヘッダーとExpiresヘッダーの両方を使用することをお勧めします時間。

2番目のオプション(アプリを介して画像をストリーミングする)を使用すると、状況をより適切に制御できます。たとえば、現在の日付と時刻に従って、応答ごとにExpiresヘッダーを生成できます。

しかし、速度はどうですか?署名付きURLを使用すると、ページの読み込み速度に影響を与える可能性のある2つの利点があります。

まず、サーバーに過負荷をかけません。 AWS認証情報をハッシュしているだけなので、高速であれば署名付きURLを生成します。また、サーバーを介して画像をストリーミングするには、ページの読み込み中に多くの追加の接続を維持する必要があります。とにかく、サーバーがハードロードされていない限り、実際の違いはありません。

次に、ブラウザは、ページの読み込み中にホスト名ごとに2つの並列接続のみを保持します。そのため、ブラウザは画像のURLをダウンロードしている間、並行して解決し続けます。また、画像のダウンロードが他のリソースのダウンロードをブロックしないようにします。

とにかく、絶対に確実にいくつかのベンチマークを実行する必要があります。私の答えは、HTTP仕様に関する知識と、Web開発の経験に基づいていましたが、自分でそのように画像を提供しようとしたことはありませんでした。キャッシュの有効期間が長いパブリックイメージをS3から直接提供すると、ページ速度が向上します。リダイレクトを介して提供しても状況は変わらないと思います。

また、サーバーを介して画像をストリーミングすると、AmazonCloudFrontのすべてのメリットが失われることを覚えておく必要があります。ただし、S3から直接コンテンツを提供している限り、どちらのオプションでも問題なく機能します。

したがって、署名付きURLを使用するとページが高速化される場合が2つあります。

  • 1ページにたくさんの画像がある場合。
  • CloudFrontを使用して画像を提供する場合。

各ページに画像がほとんどなく、S3から直接提供している場合は、おそらくまったく違いは見られません。

重要な更新

いくつかのテストを実行したところ、キャッシュについて間違っていることがわかりました。ブラウザがリダイレクト先の画像をキャッシュするのは事実です。ただし、キャッシュされた画像は、元の画像ではなく、リダイレクト先のURLに関連付けられます。そのため、ブラウザが2回目にページを読み込むと、キャッシュから画像を取得するのではなく、サーバーに画像を再度要求します。もちろん、サーバーが最初に応答したのと同じリダイレクトURLで応答した場合、ブラウザーはそのキャッシュを使用しますが、署名されたURLの場合はそうではありません。

署名されたURLと受信したデータをブラウザにキャッシュさせることで、問題が解決することがわかりました。しかし、無効なリダイレクトURLをキャッシュするという考えは好きではありません。つまり、ブラウザが何らかの理由で画像を見逃した場合、キャッシュから無効な署名付きURLを使用して画像を再度要求しようとします。だから、それは選択肢ではないと思います。

また、CloudFrontがイメージをより速く提供するかどうか、またはブラウザーがホスト名ごとの並列ダウンロードの数を制限するかどうかは関係ありません。ブラウザーキャッシュを使用する利点は、サーバーを介してイメージをパイプすることのすべての欠点を上回ります。

また、ほとんどのソーシャルネットワークは、実際のURLを一部のプライベートプロキシの背後に隠すことで、プライベート画像の問題を解決しているようです。そのため、すべてのコンテンツをパブリックサーバーに保存しますが、許可なくプライベートイメージへのURLを取得する方法はありません。もちろん、新しいタブでプライベート画像を開いてURLを友達に送信すると、友達もその画像を見ることができます。したがって、それが選択肢ではない場合は、 Jonathan Ongのソリューション を使用するのが最善です。

7

写真を本当に非公開にする必要がある場合は、CloudFrontオプションの使用に関心があります。独自のセキュリティポリシーを管理する際の柔軟性が大幅に向上するようです。 nginxのセットアップは必要以上に複雑かもしれないと思います。 Expressは、リクエストを使用してS3からアイテムをフェッチし、承認されたユーザーにストリーミングするリモートプロキシとして機能する非常に優れたパフォーマンスを提供するはずです。ハッシュ署名を使用してブラウザで永続的なキャッシュを有効にするAssetRackを確認することを強くお勧めします。各ファイルのMD5を計算する必要があるため(おそらくアップロード時に)、デフォルトのラックを使用することはできません。これは、ストリーミング時には実行できません。ただし、アプリケーションによっては、ブラウザが画像を再フェッチする必要がないため、多くの労力を節約できます。

1
Dan Kohn

2番目のオプションに関しては、 S3に直接キャッシュ制御ヘッダー を設定できるはずです。

あなたの最初のオプションについて。別の方法で画像を保護することを検討しましたか? S3に画像を保存する場合、ハッシュ化およびランダム化されたファイル名を使用できませんでしたか?ファイル名を推測しにくくするのは非常に簡単です+この方法では、画像を表示し直すときにパフォーマンスの問題が発生しません。

これはFacebookが使用するテクニックです。 URLを知っている限り、ログアウトしても画像を表示できます。

0
Rob Squires