关于Heroku上Rails资产前的Cloudfront的说明

301 阅读8分钟

Heroku真的建议在你的Rails应用静态资产前使用CDN--与非heroku的情况不同,在非heroku的情况下,像nginx这样的Web服务器可能会负责处理,否则在heroku上,静态资产将直接由你的Rails应用提供,消耗有限/昂贵的动态资源。

在评估了各种选择(包括一些heroku插件)后,我决定AWS Cloudfront对我们来说是最有意义的--足够简单,便宜,而且我们已经在使用其他直接的AWS服务(包括S3和SES)。

虽然heroku有一篇关于使用Cloudfront的文章,其中甚至包括Rails的具体内容,甚至包括CORS的具体问题,但我发现它有点太模糊了,无法让我一路走下去。虽然你可以找到很多关于这个话题的博客文章,但我发现其中很多都已经过时了(Rails已经引入了新的API;Cloudfront也改变了它的配置选项!),或者是其他方面的不足/不足。

因此,虽然我不是这方面的专家,但我要告诉你我所发现的,以及我是如何将Cloudfront设置为CDN,在heroku上运行的Rails静态资产前运行的--虽然这里没有任何专门针对heroku的东西,如果你有任何其他背景,Rails在生产中直接提供资产。

首先是我如何设置Rails,然后是Cloudfront,然后是一些说明和关注。顺便说一下,你可能不需要关心CORS,但你可能关心的一个原因是,如果你从Rails静态资产中提供任何字体(包括font-awesome或其他图标字体!)。

Rails设置

config/environments/production.rb

# set heroku config var RAILS_ASSET_HOST to your cloudfront
# hostname, will look like `xxxxxxxx.cloudfront.net`
config.asset_host = ENV['RAILS_ASSET_HOST']

config.public_file_server.headers = {
  # CORS:
  'Access-Control-Allow-Origin' => "*", 
  # tell Cloudfront to cache a long time:
  'Cache-Control' => 'public, max-age=31536000' 
}

Cloudfront设置

我改变了一些默认的东西。唯一一个绝对必要的--如果你想让CORS工作--似乎是改变允许的HTTP方法,包括OPTIONS

点击 "Create Distribution"。所有的默认值,除了:

  • 起源域名:你的heroku应用主机,如app-name.herokuapp.com
  • 起源协议策略。 切换到 "仅限HTTPS"。这似乎是一个好主意,以确保云端和起源之间的安全流量,不是吗?
  • 允许的HTTP方法: 切换到GET, HEAD, OPTIONS 。在我的实验中,为了让浏览器的CORS工作,这是必要的,AWS文档也建议这样做。
  • 缓存的HTTP方法:点击 "OPTIONS "也是如此,现在我们允许它,我看不出有什么理由不允许?
  • 自动压缩对象:是
    • Sprockets正在创建你所有资产的.gz版本,但无论如何,在Cloudfront设置中它们将被完全忽略。 ☹ (有什么方法可以让Sprokets停止这样做吗?谁知道呢,我不知道,要想知道如何可靠地与Sprockets对话是很难的)。 但是,我们可以通过让Cloudfront为我们加密来实现它的目的,这似乎是一个好主意,谷歌PageSpeed会喜欢它,等等?
    • 我通过实验注意到,Cloudfront会压缩CSS和JS(有时用brotli有时用gz,即使是同一个浏览器,不知道它是怎么决定的,不关心),但很聪明,不会费力地去压缩一个.jpg或.png(已经有内部压缩)。
  • 评论栏:如果有办法在你创建发行后编辑它,我还没有找到,所以要选一个好的!

关于CORS的说明

AWS文档在这里这里建议,为了支持CORS,你还需要配置Cloudfront分布,以转发额外的头信息 - Origin,可能还有Access-Control-Request-Headers和Access-Control-Request-Method。你可以通过设置一个自定义的 "缓存策略 "来做到这一点。或者也许通过设置 "Origin Request Policy "来代替。也可能是通过使用Use legacy cache settings 选项,以不同的方式设置自定义缓存头。这很令人困惑--而且这些设置对我来说似乎都不是CORS正常工作所必需的,我也看不到这些设置对CloudFront的行为或响应中包含的头信息有任何区别。

如果我试图使用一个更具体的Access-Control-Allow-Origin ,而不仅仅是将其设置为* ,也许它们会更重要?但关于那个....

如果你将Access-Control-Allow-Origin 设置为单一主机,MDN文档说你必须同时返回一个Vary: Origin 头。在你的Railsconfig.public_file_server.headers 中添加这个很容易。 但我无法让Cloudfront转发/返回这个Vary 头和它的响应。我尝试了各种方式的缓存策略设置,参考了AWS关于Cloudfront中Vary头的相当混乱的文档,并试图按照它所说的去做--但都无法实现。

如果你实际上需要一个以上的允许来源呢? 根据MDN解释的规格Access-Control-Allow-Origin ,你不能在头中包含多个来源,头只允许一个:" 如果服务器支持来自多个来源的客户,它必须返回发出请求的特定客户的来源。" 而你不能用Rails静态/全局config.public_file_server.headers ,我们需要使用和设置rack-cors,或者其他东西。

所以我就说,嗯,* 可能就好了。我不认为这样做对Rails静态资产来说会涉及任何安全问题? 我想这可能是其他人都在做的事情?

我需要的唯一设置是将Cloudfront设置为允许OPTIONS HTTP方法,并将Railsconfig.public_file_server.headers 设置为包括'Cache-Control' => 'public, max-age=31536000'

关于Cache-Control max-age的说明

很多现有的指南都没有让你设置config.public_file_server.headers ,以包括'Cache-Control' => 'public, max-age=31536000'

但如果不这样做,Cloudfront是否真的会进行缓存?如果每次向Cloudfront发出请求时,Cloudfront都会向Rails应用发出请求以获取资产,并且只是代理它--那么我们就没有真正得到使用Cloudfront的意义,首先是为了避免我们应用的流量。

嗯,事实证明是的,Cloudfront无论如何都会进行缓存。也许是因为Cloudfront的默认TTL设置?我的默认TTL被保留在Cloudfront的默认值,86400秒(一天)。所以我想,也许Cloudfront会在我没有提供任何Cache-ControlExpires headers的情况下缓存一天的资源?

根据我的观察,实际上它的缓存时间比这还短。也许一个小时?(想知道它是否在缓存吗?看一下Cloudfront返回的头信息。有一个简单的方法可以做到这一点?curl -IXGET https://whatever.cloudfront.net/my/asset.jpg,你会看到一个头,要么是x-cache: Miss from cloudfront ,要么是x-cache: Hit from cloudfront )。

当然,Cloudfront并不承诺在它允许的时间内进行缓存,它可以在那之前因为自己的原因/政策而驱逐东西,所以也许这就是所有的事情了。

不过,Rails的资产是有指纹的,所以它们是可以永远缓存的,那么为什么不告诉Cloudfront呢?也许更重要的是,如果Rails没有返回Cache-Cobntrol头,那么Cloudfront也没有返回给实际的用户代理,这意味着他们不知道他们可以在自己的缓存中缓存响应,他们也会在每次重载时不断请求/检查它,这对你的巨大的CSS和JS应用文件来说不是好事

所以,我认为用config.public_file_server.headers 来设置远期的Cache-Control 头是个不错的主意,就像我上面做的那样。我们告诉Cloudfront它可以缓存一年的最大允许量,这也(我检查过)让Cloudfront把头信息转发给用户代理,他们也会知道他们可以缓存。

关于将Cloudfront分布限制在静态资产上的说明?

上面创建的CloudFront分布实际上将代理/缓存我们的整个Rails应用,你也可以通过它访问动态动作。这不是我们的目的,我们的应用不会以这种方式产生任何URL,但有人可以。

这是个问题吗?

我不知道。

一些博客文章试图建议限制它只愿意代理/缓存静态资产,但这实际上是一个痛苦的做法,有几个原因:

  1. 自从许多博客文章写完后,Cloudfront已经改变了他们对 "路径模式 "的配置(除非你使用 "传统的缓存设置 "选项),因此我对如何做到这一点感到困惑,如果有办法让一个分发器停止缓存/代理/服务任何东西,而只是*给定的路径模式了?
  2. 带有webpacker的现代Rails /assets/packs ,所以你需要两种路径模式,这就更令人困惑了。(为什么是Rails?为什么背包不在public/assets/packs ,所有的静态资产仍然在/assets ?)

我只是放弃了对这个问题的思考,认为Cloudfront愿意代理/缓存/服务我不打算做的事情,这并不是一个真正的问题? 是这样吗?我希望如此。

关于Rails asset_path helper和asset_host的说明

你可能已经意识到,Rails有asset_pathasset_url 两个帮助器,用于链接到资产。(还有类似的帮助器,在sass中用破折号代替下划线,可能实现方式不同,通过sass-rails)

通常情况下,asset_path 返回一个没有主机的相对URL,而asset_url 返回一个带有主机名的URL。由于使用外部的asset_host ,我们需要在所有的URL中包含主机,以便正确地瞄准CDN......你可能认为你必须停止在任何地方使用asset_path ,而只是使用asset_url...你会错的

事实证明,如果设置了config.asset_hostasset_path 也开始包括主机。所以使用asset_path一切都很好。 不确定在这一点上它是否是asset_url的同义词?我认为不完全是,因为我认为事实上一旦我设置了config.asset_host ,我对asset_url 的一些使用就开始出错,测试失败?而我实际上不得不只使用asset_path?以我不太明白发生了什么,无法解释的方式?