Careful when using Time objects for generating ETags

Updated . Posted . Visible to the public. Repeats.

You can use ETags to allow clients to use cached responses, if your application would send the same contents as before.

Besides what "actually" defines your response's contents, your application probably also considers "global" conditions, like which user is signed in:

class ApplicationController < ActionController::Base

  etag { current_user&.id }
  etag { current_user&.updated_at }

end

Under the hood, Rails generates an ETag header value like W/"f14ce3710a2a3187802cadc7e0c8ea99". In doing so, all objects from that etaggers list are serialized and Time objects lose their millisecond resolution.

You likely won't notice for a while (ask me how I know) but occasionally encounter tests which are "randomly failing".

Example

When users change their name and visit a page they visited less than a second ago (yes, that is not an issue with humans), your application will respond with an HTTP 304 and the browser will re-use the response from before which still contained the old username.

To avoid that, convert any Time objects into a string or number before an ETag is generated from them.

class ApplicationController < ActionController::Base

  etag { current_user&.id }
  etag { current_user&.updated_at&.to_f }

end

You might even want to patch Rails' etagging logic so you don't have to remember doing that. Here you go:

class ApplicationController < ActionController::Base
  
  etag { current_user&.id }
  etag { current_user&.updated_at }
  
  private
  
  def combine_etags(...)
    # When using Time objects for etagging, their milliseconds are ignored because of how
    # ActiveSupport::Cache#expand_cache_key works (which is called to build ETag contents).
    # We convert them to floats to avoid that.
    super.map! { |object| object.is_a?(Time) ? object.to_f : object }
  end

end
Arne Hartherz
Last edit
Arne Hartherz
License
Source code in this card is licensed under the MIT License.
Posted by Arne Hartherz to makandra dev (2022-09-30 15:58)