lib/http/request/caching.rb



require "http/cache/headers"

module HTTP
  class Request
    # Decorator for requests to provide convenience methods related to caching.
    class Caching < DelegateClass(HTTP::Request)
      INVALIDATING_METHODS = [:post, :put, :delete, :patch].freeze
      CACHEABLE_METHODS    = [:get, :head].freeze

      # When was this request sent to the server
      #
      # @api public
      attr_accessor :sent_at

      # Inits a new instance
      # @api private
      def initialize(obj)
        super
        @requested_at = nil
        @received_at  = nil
      end

      # @return [HTTP::Request::Caching]
      def caching
        self
      end

      # @return [Boolean] true iff request demands the resources cache entry be invalidated
      #
      # @api public
      def invalidates_cache?
        INVALIDATING_METHODS.include?(verb) ||
          cache_headers.no_store?
      end

      # @return [Boolean] true if request is cacheable
      #
      # @api public
      def cacheable?
        CACHEABLE_METHODS.include?(verb) &&
          !cache_headers.no_store?
      end

      # @return [Boolean] true iff the cache control info of this
      # request demands that the response be revalidated by the origin
      # server.
      #
      # @api public
      def skips_cache?
        0 == cache_headers.max_age       ||
          cache_headers.must_revalidate? ||
          cache_headers.no_cache?
      end

      # @return [HTTP::Request::Caching] new request based on this
      # one but conditional on the resource having changed since
      # `cached_response`
      #
      # @api public
      def conditional_on_changes_to(cached_response)
        self.class.new HTTP::Request.new(
          verb, uri, headers.merge(conditional_headers_for(cached_response)),
          proxy, body, version).caching
      end

      # @return [HTTP::Cache::Headers] cache control helper for this request
      # @api public
      def cache_headers
        @cache_headers ||= HTTP::Cache::Headers.new headers
      end

      def env
        {"rack-cache.cache_key" => lambda { |r| r.uri.to_s }}
      end

      private

      # @return [Headers] conditional request headers
      # @api private
      def conditional_headers_for(cached_response)
        headers = HTTP::Headers.new

        cached_response.headers.get("Etag").
          each { |etag| headers.add("If-None-Match", etag) }

        cached_response.headers.get("Last-Modified").
          each { |last_mod| headers.add("If-Modified-Since", last_mod) }

        headers.add("Cache-Control", "max-age=0") if cache_headers.forces_revalidation?

        headers
      end
    end
  end
end