back

Using stale? with rails to return 304 not modified

Here is all the code that deals with stale? in one place, so that you can see how it works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      # Sets the etag and/or last_modified on the response and checks it against
      # the client request. If the request doesn't match the options provided, the
      # request is considered stale and should be generated from scratch. Otherwise,
      # it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
      #
      # Example:
      #
      #   def show
      #     @article = Article.find(params[:id])
      #
      #     if stale?(:etag => @article, :last_modified => @article.created_at.utc)
      #       @statistics = @article.really_expensive_call
      #       respond_to do |format|
      #         # all the supported formats
      #       end
      #     end
      #   end
     def stale?(options)
       fresh_when(options)
       !request.fresh?(response)
     end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

      # Sets the etag, last_modified, or both on the response and renders a
      # "304 Not Modified" response if the request is already fresh.
      #
      # Example:
      #
      #   def show
      #     @article = Article.find(params[:id])
      #     fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
      #   end
      #
      # This will render the show template if the request isn't sending a matching etag or
      # If-Modified-Since header and just a "304 Not Modified" response if there's a match.
      def fresh_when(options)
        options.assert_valid_keys(:etag, :last_modified)

        response.etag          = options[:etag]          if options[:etag]
        response.last_modified = options[:last_modified] if options[:last_modified]

        if request.fresh?(response)
          head :not_modified
        end
      end

--- 




--- RUBY

    # Check response freshness (Last-Modified and ETag) against request
    # If-Modified-Since and If-None-Match conditions. If both headers are
    # supplied, both must match, or the request is not considered fresh.
    def fresh?(response)
      case
      when if_modified_since && if_none_match
        not_modified?(response.last_modified) && etag_matches?(response.etag)
      when if_modified_since
        not_modified?(response.last_modified)
      when if_none_match
        etag_matches?(response.etag)
      else
        false
      end
    end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    def if_modified_since
      if since = env['HTTP_IF_MODIFIED_SINCE']
        Time.rfc2822(since) rescue nil
      end
    end
    memoize :if_modified_since

    def if_none_match
      env['HTTP_IF_NONE_MATCH']
    end

    def not_modified?(modified_at)
      if_modified_since && modified_at && if_modified_since >= modified_at
    end

    def etag_matches?(etag)
      if_none_match && if_none_match == etag
    end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    def last_modified
      if last = headers['Last-Modified']
        Time.httpdate(last)
      end
    end

    def last_modified?
      headers.include?('Last-Modified')
    end

    def last_modified=(utc_time)
      headers['Last-Modified'] = utc_time.httpdate
    end

    def etag
      headers['ETag']
    end

    def etag?
      headers.include?('ETag')
    end

    def etag=(etag)
      if etag.blank?
        headers.delete('ETag')
      else
        headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
      end
    end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    def self.expand_cache_key(key, namespace = nil)
      expanded_cache_key = namespace ? "#{namespace}/" : ""

      if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
        expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/"
      end

      expanded_cache_key << case
        when key.respond_to?(:cache_key)
          key.cache_key
        when key.is_a?(Array)
          key.collect { |element| expand_cache_key(element) }.to_param
        when key
          key.to_param
        end.to_s

      expanded_cache_key
    end

January 07, 2009