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

Sar*_*tra 19 nginx amazon-s3 fileserver node.js express

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

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

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

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

Jon*_*Ong 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)
  })
})
Run Code Online (Sandbox Code Playgroud)

  • 很高兴我们都可以在SO @SaranshMohapatra和JonathanOng上获得我们的语法课程.感谢您的宝贵贡献. (8认同)
  • "然而,knox维护者更多地参与了节点社区." (2认同)

Leo*_*tny 7

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

为防止浏览器缓存已签名的URL本身,您应该发送正确的Cache-Control标头:

Cache-Control: private, no-cache, no-store, must-revalidate
Run Code Online (Sandbox Code Playgroud)

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

您可以knox使用using signedUrl方法生成签名的URL .

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

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

但速度怎么样?使用签名的URL有两个优点,可能会影响页面加载速度.

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

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

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

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

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

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

如果您在每个页面上只有很少的图像并直接从S3提供它们,您可能根本不会看到任何差异.

重要更新

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

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

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

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