cache.rb 3.2 KB
Newer Older
1 2
require 'active_support/core_ext/object/blank'

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
module ActionDispatch
  module Http
    module Cache
      module Request
        def if_modified_since
          if since = env['HTTP_IF_MODIFIED_SINCE']
            Time.rfc2822(since) rescue nil
          end
        end

        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

        # 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)
          last_modified = if_modified_since
          etag          = if_none_match

          return false unless last_modified || etag

          success = true
          success &&= not_modified?(response.last_modified) if last_modified
          success &&= etag_matches?(response.etag) if etag
          success
        end
      end

      module Response
42 43 44 45 46 47 48 49 50 51 52
        attr_reader :cache_control

        def initialize(*)
          status, header, body = super

          @cache_control = {}
          @etag = self["ETag"]

          if cache_control = self["Cache-Control"]
            cache_control.split(/,\s*/).each do |segment|
              first, last = segment.split("=")
53
              @cache_control[first.to_sym] = last || true
54 55
            end
          end
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
        end

        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
          @etag
        end

        def etag?
          @etag
        end

        def etag=(etag)
          key = ActiveSupport::Cache.expand_cache_key(etag)
82
          @etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}")
83 84 85 86 87 88 89 90 91 92 93 94 95
        end

      private

        def handle_conditional_get!
          if etag? || last_modified? || !@cache_control.empty?
            set_conditional_cache_control!
          end
        end

        DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"

        def set_conditional_cache_control!
96 97
          return if self["Cache-Control"].present?

98 99
          control = @cache_control

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
          if control.empty?
            headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
          elsif @cache_control[:no_cache]
            headers["Cache-Control"] = "no-cache"
          else
            extras  = control[:extras]
            max_age = control[:max_age]

            options = []
            options << "max-age=#{max_age.to_i}" if max_age
            options << (control[:public] ? "public" : "private")
            options << "must-revalidate" if control[:must_revalidate]
            options.concat(extras) if extras

            headers["Cache-Control"] = options.join(", ")
          end
        end
      end
    end
  end
120
end