在express / nodejs app中提供存储在S3中的文件

时间:2013-07-07 22:00:07

标签: node.js nginx amazon-s3 express fileserver

我有app用户的照片是私密的。我将照片(缩略图也)存储在AWS s3中。站点中有一个页面,用户可以在其中查看他的照片(即缩略图)。现在我的问题是如何提供这些文件。我评估的一些选项是:

  • 使用签名的网址生成从CloudFront(或AWS)提供文件。但问题是,每次用户刷新页面时,我必须再次创建这么多已签名的URL并加载它。因此,我无法在浏览器中缓存图像本来是一个不错的选择。无论如何仍然在javascript中做?由于安全问题,我不能长时间保持这些网址的有效性。其次,在该时间范围内,如果有人抓住该网址,他可以查看该文件,而无需通过应用程序进行身份验证。
  • 其他选项是在从S3服务器流式传输后,从我的快递应用程序本身提供文件。这允许我有http缓存头,因此启用浏览器缓存。它还确保没有人可以在未经过身份验证的情况下查看文件。理想情况下,我想流式传输文件和我使用NGINX代理中继托管另一端流式传输到NGINX。但正如我所见,只有文件存在于同一系统的文件中才能实现。但是在这里我必须流式传输并在我完成流时返回。不想在本地存储文件。

我无法评估这两个选项中哪一个更好?我想尽可能多地将工作重定向到S3或cloudfront,但即使使用了singed url也会首先向我的服务器发出请求。我也想要缓存功能。

那么理想的做法是什么?有关这些方法的特定问题的答案?

4 个答案:

答案 0 :(得分:20)

我会从S3流式传输它。它非常简单,签名的URL要困难得多。只需确保在将图像上传到S3时设置content-typecontent-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)
  })
})

答案 1 :(得分:7)

如果您使用302 Found浏览器将用户重定向到已签名的网址,则会根据其cache-control标头缓存生成的图片,并且不会再次询问它。

为防止浏览器缓存已签名的网址,您应该发送正确的Cache-Control标题:

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

因此,下次它会向原始网址发送请求,并会重定向到新签名的网址。

您可以使用signedUrl method生成带有knox的签名网址。

但不要忘记为每个上传的图片设置正确的标题。我建议您使用Cache-ControlExpires标头,因为某些浏览器不支持Cache-Control标头,Expires允许您只设置绝对过期时间。

使用第二个选项(通过您的应用程序流式传输图像),您可以更好地控制情况。例如,您可以根据当前日期和时间为每个响应生成Expires标头。

但速度怎么样?使用签名网址有两个优点,可能会影响网页加载速度。

首先,您不会使服务器过载。如果快速生成签名的URL,因为您只是哈希您的AWS凭据。要通过服务器流式传输图像,您需要在页面加载期间保持大量额外连接。无论如何,除非您的服务器是硬盘,否则它不会产生任何实际差异。

其次,浏览器在页面加载期间每个主机名仅保留两个并行连接。因此,浏览器将在下载时保持并行解析图像网址。它还可以阻止下载任何其他资源的图像下载。

无论如何,要绝对确定你应该运行一些基准测试。我的回答是基于我对HTTP规范的了解以及我在Web开发方面的经验,但我从未试图以自己的方式提供图像。直接从S3提供具有长缓存生命周期的公共图像可以提高页面速度,我相信如果您通过重定向来实现这种情况,情况不会改变。

您应该记住,通过您的服务器流式传输图像将带来Amazon CloudFront的所有好处。但只要您直接从S3提供内容,两个选项都可以正常工作。

因此,在使用签名网址时,有两种情况应该加速您的网页:

  • 如果您在一个页面上有很多图像。
  • 如果您使用CloudFront提供图像。

如果您在每个页面上只有少量图像并直接从S3提供图像,那么您可能根本不会看到任何差异。

重要更新

我进行了一些测试,发现我对缓存有误。确实,浏览器会缓存重定向到的图像。但它将缓存的图像与重定向到的URL相关联,而不是与原始图像相关联。因此,当浏览器第二次加载页面时,它再次从服务器请求图像,而不是从缓存中获取图像。当然,如果服务器使用相同的重定向URL响应它第一次响应,浏览器将使用其缓存,但签名网址不是这样。

我发现强制浏览器缓存已签名的url以及它收到的数据可以解决问题。但我不喜欢缓存无效重定向URL的想法。我的意思是,如果浏览器以某种方式错过图像,它将尝试使用缓存中的无效签名URL再次请求它。所以,我认为这不是一个选择。

如果CloudFront更快地提供图像或浏览器限制每个主机名的并行下载次数无关紧要,使用浏览器缓存的优势超出了通过服务器管道图像的所有缺点。

看起来大多数社交网络通过将其实际网址隐藏在一些私有代理后面来解决私有图像的问题。因此,他们将所有内容存储在公共服务器上,但未经授权就无法获取私有映像的URL。当然,如果您在新标签页中打开私人图片并将网址发送给您的朋友,他也可以看到图片。所以,如果它不适合您,那么最好使用Jonathan Ong's solution

答案 2 :(得分:1)

如果照片确实需要保密,我会担心使用CloudFront选项。您似乎可以更灵活地管理自己的安全策略。我认为nginx设置可能比必要的更复杂。 Express应该作为远程代理提供非常好的性能,它使用请求从S3获取项目并将它们传递给授权用户。我强烈建议您查看Asset Rack,它使用哈希签名在浏览器中启用永久缓存。您将无法使用默认机架,因为您需要计算每个文件的MD5(可能在上传?),这是您在流式传输时无法做到的。但是根据您的应用程序,它可以为您节省大量的工作量,从而无需重新获取图像。

答案 3 :(得分:0)

关于您的第二个选项,您应该能够设置cache control headers directly in S3

关于你的第一个选择。您是否考虑过以不同方式保护图像? 在S3中存储图像时,是否不能使用散列和随机文件名?使文件名难以猜测是非常直接的+这样你就没有查看图像的性能问题了。

这是facebook使用的技术。只要您知道URL,您仍然可以在注销时查看图像。