ETags and http caching in Rails 5

Rails 5 has introduced some changes to the way HTTP caching works.

ETags are by default now set to weak mode in Rails 5. This is because Rails does not do byte-to-byte checking for equality which is comparing changes in both the headers and response body.

Weak Etags only compares the response body for differences, which is also the default behaviour in earlier versions of Rails.

Assuming we have a Users controller and an expensive rendering action on the index action which we want to call render only if the users have been updated.

We can implement a conditional get using stale? like so:

1 if stale?(etag: @users, last_modified: @users.maximum(:updated_at)
2   respond_to do |format|
3     format.html
4   end
5 end

Assuming we made a curl request to the endpoint /users:

1 curl -i http://localhost:3000/users

It will return a 200 response:

1 HTTP/1.1 200 OK
2 X-Frame-Options: SAMEORIGIN
3 X-XSS-Protection: 1; mode=block
4 X-Content-Type-Options: nosniff
5 ETag: W/"8e49d1a448037c58d93ea3a00d6edf87"

Note that the ETag now has a W/ prefix to indicate its a weak etag, which is the default in Rails 5.

If we now use the ETag value and make a second curl request with the header If-None-Match set to the same ETag value we should get a 304 response.

1 curl -i -H 'If-None-Match: "8e49d1a448037c58d93ea3a00d6edf87"' http://localhost:3000/users

However, the above will keep returning 200 as it is not matching the response. You need to specify the entire ETag value like so:

1 curl -i -H 'If-None-Match: W/"8e49d1a448037c58d93ea3a00d6edf87"' http://localhost:3000/users

To use strong etags (without the W/ prefix), you can change the etag parameter in the stale? method:

1 if stale?(strong_etag: @users, last_modified: @users.maximum(:updated_at))
2   respond_to do |format|
3     format.html
4   end
5 end

Stay curious and keep hacking!