21

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

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

我无法评估这两个选项中的哪一个是更好的选择??我想将尽可能多的工作重定向到 S3 或云端,但即使使用单点 URL 也会首先将请求发送到我的服务器。我还想要缓存功能。

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

4

4 回答 4

22

我会从 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)
  })
})
于 2013-07-14T10:19:25.230 回答
9

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

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

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

所以下次它会向原始 url 发送请求,并将被重定向到一个新的签名 url。

knox您可以使用signedUrl方法生成签名的 url 。

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

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

但是速度呢?使用签名的 url 有两个优点,可能会影响页面加载速度。

首先,您不会使服务器超载。如果快速生成签名 URL,因为您只是在散列您的 AWS 凭证。要通过服务器流式传输图像,您需要在页面加载期间维护大量额外的连接。无论如何,除非您的服务器是硬加载的,否则它不会产生任何实际影响。

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

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

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

因此,有两种情况下使用签名的 url 可以加速你的页面:

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

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

重要更新

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

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

无论是 CloudFront 更快地提供图像还是浏览器限制每个主机名的并行下载数量,使用浏览器缓存的优势都超过了通过服务器管道传输图像的所有缺点。

看起来大多数社交网络通过将其实际网址隐藏在一些私人代理后面来解决私人图像的问题。因此,他们将所有内容存储在公共服务器上,但没有办法在未经授权的情况下获取私有图像的 URL。当然,如果您在新标签页中打开私人图片并将网址发送给您的朋友,他也将能够看到该图片。因此,如果这不是您的选择,那么您最好使用Jonathan Ong 的解决方案

于 2013-07-16T12:26:38.217 回答
1

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

于 2013-07-07T22:59:07.613 回答
0

关于您的第二个选项,您应该能够直接在 S3 中设置缓存控制标头

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

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

于 2013-07-10T23:27:05.400 回答