What is ETag and how to use it in Rails for caching?

Every now and then, in the realm of web development, we come across terms that are crucial to the operation but may seem obscure until we delve into them. An ETag, or entity tag, is one such term. In this article, we will explore what ETags are, why they are important, and how to use them.

Defining ETags

An ETag is, simply put, a part of the HTTP protocol designed for optimistic concurrency control. They act as a mechanism for detecting content changes at a given URL. Whenever a URL is accessed, the server returns an ETag, a unique identifier. If the content changes, the ETag changes as well, signaling to clients that a new version of the resource is available.

# curl -i 'https://jsonplaceholder.typicode.com/todos/1'                    
HTTP/2 200 
content-type: application/json; charset=utf-8
content-length: 83
etag: W/"53-hfEnumeNh6YirfjyjaujcOPPT+s"
via: 1.1 vegur
cf-cache-status: HIT
age: 14038
accept-ranges: bytes
server: cloudflare
cf-ray: 81fba87a9f0d35d2-WAW
alt-svc: h3=":443"; ma=86400

Why are ETags important?

ETags are important because they enable efficient updating of cached resources. They act as validators, checking whether or not the cache needs to be updated. If the ETag hasn't changed, the client can use the cached resource version instead, optimizing network usage and improving speed.

ETags result

Most web servers and client libraries support ETags out of the box, but the implementation of the ETag logic is left up to the developer. ETags can be used in conjunction with cache control headers to manage caching policies. For example, when Rails receives the same ETag in the header and compares it to the current stored version, it will return 304 Not Modified to avoid re-calculating and re-rendering things.

Using expires_in

The expires_in method is used to set the maximum age for the resource. It helps to cache a time-invariant response (one that does not change often). Here's how to use it:

def show
  @post = Post.find(params[:id])
  expires_in 10.minutes, public: true
end

In this example, the expires_in method sets the max-age value of the cache control header to 10 minutes.

Using expires_now

If you want to prevent a resource from being cached, you can use the expires_now function. It sets the cache-control header to no-cache, ensuring that the browser and intermediate caches do not cache the page.

def show
  @profile = Profile.find(params[:id])
  expires_now if @profile.private?
end

Here, the expires_now method is used to force the expiration of a resource when the profile is private.

Using stale?

The stale? method helps set the ETag and Last-Modified headers in the response and checks whether the client's copy of the resource is stale or fresh.

def show
  @post = Post.find(params[:id])
  
  if stale?(etag: @post, last_modified: @post.updated_at)
    render json: @post
  end
end

In this example, if the client request is stale, Rails will continue to render the @post in JSON format.

Using fresh_when

The fresh_when method helps take advantage of HTTP's conditional GETs. By comparing the current request's ETag and Last-Modified headers to those previously stored, it helps you determine if the request is fresh.

def show
  @posts = Post.all
  fresh_when last_modified: @posts.maximum(:updated_at)
end

Here, fresh_when checks if the @posts is fresh based on its etag value or updated_at timestamp.

Declaring public requests

Public responses can be stored by Internet-based caching mechanisms and don't contain sensitive data. You can use public: true to declare this type of response.

def show
  @post = Post.find(params[:id])
  fresh_when @post, public: true
end

Using http_cache_forever

The http_cache_forever method is used when a page will never change. It has the same effect as setting expires_in to a future value and ignoring all incoming invalidation requests.

def show
  @post = Post.find(params[:id])
  http_cache_forever(public: true) do
    render json: @post
  end
end

In this code, http_cache_forever helps to cache this page forever because the response does not change. This means that every time the same request is made, the cached JSON representation is returned instead of recomputed, improving performance.

Conclusion

ETags play a critical role in improving Web performance by efficiently utilizing caching resources. As developers, understanding ETags can allow us to optimize our applications and APIs to significantly improve the end-user experience. Please remember to add caching only when it's necessary - not before building the stuff to save your time.

Happy coding!