instiki/vendor/plugins/action_cache/lib/action_cache.rb

241 lines
8.6 KiB
Ruby

require 'yaml'
require 'time'
require 'md5'
module ActionController
class AbstractResponse #:nodoc:
attr_accessor :time_to_live
end
module Caching
module Actions
# All documentation is keeping DRY in the plugin's README
def expire_all_actions
return unless perform_caching
expire_fragment(/.*\/(META|DATA)\/.*/)
end
def expire_one_action(options)
expire_fragment(Regexp.new(".*/" + Regexp.escape(ActionCachePath.path_for(self, options)) + ".*"))
end
def expire_action(options = {})
return unless perform_caching
if options[:action].is_a?(Array)
options[:action].dup.each do |action|
expire_one_action options.merge({ :action => action })
end
else
expire_one_action options
end
end
def action_fragment_key(options)
url_for(options).split('://').last
end
# Override the 1.2 ActionCachePath class, works in 1.1.x too
class ActionCachePath
attr_reader :controller, :options
class << self
def path_for(*args, &block)
new(*args).path
end
end
def initialize(controller, options = {})
@controller = controller
@options = options
end
# Override this to change behavior
def path
return @path if @path
@path = @controller.send(:action_fragment_key, @options)
add_extension!
clean!
end
def extension
@extension ||= extract_extension(controller.request.path)
end
private
def clean!
@path = @path.gsub(':', '-').gsub('?', '-')
end
def add_extension!
@path << ".#{extension}" if extension
end
def extract_extension(file_path)
# Don't want just what comes after the last '.' to accomodate multi part extensions
# such as tar.gz.
file_path[/^[^.]+\.(.+)$/, 1]
end
end
class ActionCacheFilter #:nodoc:
def self.fragment_key=(key_block)
raise "fragment_key member no longer supported - use action_fragment_key on controller instead"
end
class CacheEntry #:nodoc:
def initialize(headers, time_to_live = nil)
@headers = headers.merge({ 'cookie' => [] }) # Don't send cookies for cached responses
@expire_time = Time.now + time_to_live unless time_to_live.nil?
end
def expired?
!expire_time.nil? && Time.now > expire_time
end
attr_reader :headers, :expire_time
end
def before(controller)
if cache_entry = cached_entry(controller)
if cache_entry.expired?
remove_cache_item(controller) and return
end
if x_sendfile_enabled?(controller)
send_using_x_sendfile(cache_entry, controller)
else if x_accel_redirect_enabled?(controller)
send_using_x_accel_redirect(cache_entry, controller)
else
if client_has_latest?(cache_entry, controller)
send_not_modified(controller)
else
send_cached_response(cache_entry, controller)
end
end
end
controller.rendered_action_cache = true
return false
end
end
def after(controller)
if cache_this_request?(controller)
adjust_headers(controller)
save_to_cache(controller)
end
end
protected
def adjust_headers(controller)
if controller.response.time_to_live &&
controller.response.headers['Cache-Control'] == 'no-cache'
controller.response.headers['Cache-Control'] = "max-age=#{controller.response.time_to_live}"
end
controller.response.headers['ETag'] = %{"#{MD5.new(controller.response.body).to_s}"}
controller.response.headers['Last-Modified'] ||= Time.now.httpdate
end
def send_cached_response(cache_entry, controller)
controller.logger.info "Send #{body_name(controller)} by response.body"
controller.response.headers = cache_entry.headers
controller.set_content_type_header
controller.response.body = fragment_body(controller)
end
def send_not_modified(controller)
controller.logger.info "Send Not Modified"
controller.response.headers['ETag'] = %{"#{MD5.new(fragment_body(controller)).to_s}"}
controller.render(:text => "", :status => 304)
end
def client_has_latest?(cache_entry, controller)
requestEtag = controller.request.env['HTTP_IF_NONE_MATCH'] rescue nil
responseEtag = cache_entry.headers['ETag'] rescue nil
return true if (requestEtag and responseEtag and requestEtag == responseEtag)
requestTime = Time.rfc2822(controller.request.env["HTTP_IF_MODIFIED_SINCE"]) rescue nil
responseTime = Time.rfc2822(cache_entry.headers['Last-Modified']) rescue nil
return (requestTime and responseTime and responseTime <= requestTime)
end
def remove_cache_item(controller)
controller.expire_fragment(meta_name(controller))
controller.expire_fragment(body_name(controller))
end
def send_using_x_sendfile(cache_entry, controller)
filename = fragment_body_filename(controller)
controller.logger.info "Send #{filename} by X-Sendfile"
controller.response.headers = cache_entry.headers
controller.response.headers["X-Sendfile"] = filename
end
def send_using_x_accel_redirect(cache_entry, controller)
filename = "/cache/#{fragment_body_filename(controller)[(controller.fragment_cache_store.cache_path.length + 1)..-1]}"
controller.logger.info "Send #{filename} by X-Accel-Redirect"
controller.response.headers = cache_entry.headers
controller.response.headers["X-Accel-Redirect"] = filename
end
def fragment_body_filename(controller)
controller.fragment_cache_store.send(:real_file_path, body_name(controller))
end
def fragment_body(controller)
controller.read_fragment body_name(controller)
end
def cache_request?(controller)
controller.respond_to?(:cache_action?) ? controller.cache_action?(controller.action_name) : true
end
def cache_this_request?(controller)
@actions.include?(controller.action_name.intern) && cache_request?(controller) &&
!controller.rendered_action_cache && controller.response.headers['Status'] == '200 OK'
end
def cached_entry(controller)
if @actions.include?(controller.action_name.intern) &&
cache_request?(controller) &&
(cache = controller.read_fragment(meta_name(controller)))
return YAML.load(cache)
end
return nil
end
def save_to_cache(controller)
cache = CacheEntry.new(controller.response.headers, controller.response.time_to_live)
controller.write_fragment(body_name(controller), controller.response.body)
controller.write_fragment(meta_name(controller), YAML.dump(cache))
end
def x_sendfile_enabled?(controller)
(controller.request.env["ENABLE_X_SENDFILE"] == "true" ||
controller.request.env["HTTP_X_ENABLE_X_SENDFILE"] == "true") &&
controller.fragment_cache_store.is_a?(ActionController::Caching::Fragments::UnthreadedFileStore)
end
def x_accel_redirect_enabled?(controller)
controller.request.env["HTTP_ENABLE_X_ACCEL_REDIRECT"] == "true" &&
controller.fragment_cache_store.is_a?(ActionController::Caching::Fragments::UnthreadedFileStore)
end
def meta_name(controller)
"META/#{ActionCachePath.path_for(controller)}"
end
def body_name(controller)
"DATA/#{ActionCachePath.path_for(controller)}"
end
end
end
end
end