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.
1response.headers['X-Tracking-ID'] = '123456'
Now I see this custom response header.
Setting custom response header for assets
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.
1# open config/environments/production.rb and add following line 2config.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.
How Rails Apps lived with this limitation so far
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.
Problem we ran into
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.
- Our application is hosted at Heroku.
- Heroku lets Rails serve the assets.
- Google page speed insights wants us to set Expires header to the assets.
- Rails application allows only one header for the assets and that is 'Cache-Control`.
One easy solution is to host our website at Digital Ocean and then use Apache or NGINX.
Rails 5 saves the day
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.
Start using Rails master
To fix this, we first pointed the App to use Rails master.
1 2gem 'rails', github: 'rails/rails' 3gem 'rack', github: 'rack/rack' # Rails depends on Rack 2 4gem 'arel', github: 'rails/arel' # Rails master works alongside of arel master. 5
Next, we changed asset configuration to start providing and using missing header for Expires.
1# production.rb 2 3config.public_file_server.headers = { 4 'Cache-Control' => 'public, s-maxage=31536000, max-age=15552000', 5 'Expires' => "#{1.year.from_now.to_formatted_s(:rfc822)}" 6} 7
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.
Further Reading
For better use and more details about different headers to use for the assets, please refer to RFC 2616.