October 31, 2015
This blog is part of our Rails 5 series.
Let's look at by default what kind of response headers we get when we start with a brand new Rails 4.2.4 application.
Now let's say that I want to set a custom response header. That's easy. All I need to do is add following line of code in the controller.
response.headers['X-Tracking-ID'] = '123456'
Now I see this custom response header.
Let's say that I need that custom response header not only for standard web
requests but also for my assets. For example application.js
file is served by
Rails in localhost. How would I set custom header to the asset being served by
Rails here.
Actually that's not possible in Rails 4. In Rails 4 we can set only one response
header for assets that are served by Rails and that response header is
Cache-Control
.
Here is how I can configure Cache-Control
for assets.
# open config/environments/production.rb and add following line
config.static_cache_control = 'public, max-age=1000'
Now we have modified 'Cache-Control` header for the assets.
Besides Cache-Control
no other response header can be set for assets served by
Rails. That's a limitation.
Rails is not the best server to serve static assets. Apace and NGINX are much better at this job. Hence ,in reality, in production almost everyone puts either Apache or NGINX in front of a Rails server and in this way Rails does not have to serve static files.
Havig said that, Rails applications hosted at Heroku is an exception. Assets for Rails applications running at Heroku are served by Rails application itself.
Our website is hosted at heroku. When we ran
Google page speed insights
for our website we were warned that we were not using Expires
header for the
assets.
Here are how the header looked for application.js
.
Now you see the problem we are running into.
Expires
header to the assets.One easy solution is to host our website at Digital Ocean and then use Apache or NGINX.
Recently Rails merged basic support for access control headers and added ability to define custom HTTP Headers on assets served by Rails.
Behind the scenes, Rails uses ActionDispatch::Static
middleware to take care
of serving assets. For example, a request to fetch an image, goes through
ActionDispatch::Static
in the request cycle. ActionDispatch::Static
takes
care of serving Rack::File
object from server with appropriate headers set in
the response. The served image can have headers like Content-Type
,
Cache-Control
.
To fix this, we first pointed the App to use Rails master.
gem 'rails', github: 'rails/rails'
gem 'rack', github: 'rack/rack' # Rails depends on Rack 2
gem 'arel', github: 'rails/arel' # Rails master works alongside of arel master.
Next, we changed asset configuration to start providing and using missing header
for Expires
.
# production.rb
config.public_file_server.headers = {
'Cache-Control' => 'public, s-maxage=31536000, max-age=15552000',
'Expires' => "#{1.year.from_now.to_formatted_s(:rfc822)}"
}
Here, we are first setting the Cache-Control
header to use public
(intermediate) caching, for a year (31536000 seconds) with max-age
and
s-maxage
. Here s-maxage
stands for Surrogate
cache, which is used to cache
internally by Fastly.
We then provide the missing Expires
value with some future date in Internet
formatted time value.
With this setup, we can see PageSpeed pickups the new headers on assets and does not warn us for the missing header.
Here is the changed response header for asset.
For better use and more details about different headers to use for the assets, please refer to RFC 2616.
If this blog was helpful, check out our full blog archive.