Using stale? with rails to return 304 not modified

Send to friend

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