root/trunk/actionpack/lib/action_controller/caching/pages.rb
| Revision 8546, 5.9 kB (checked in by david, 1 year ago) |
|---|
| Line | |
|---|---|
| 1 | require 'fileutils' |
| 2 | require 'uri' |
| 3 | |
| 4 | module ActionController #:nodoc: |
| 5 | module Caching |
| 6 | # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server |
| 7 | # can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically |
| 8 | # generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors |
| 9 | # are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit |
| 10 | # for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates. |
| 11 | # |
| 12 | # Specifying which actions to cache is done through the <tt>caches</tt> class method: |
| 13 | # |
| 14 | # class WeblogController < ActionController::Base |
| 15 | # caches_page :show, :new |
| 16 | # end |
| 17 | # |
| 18 | # This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic |
| 19 | # generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to |
| 20 | # the Action Pack to generate it. |
| 21 | # |
| 22 | # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache |
| 23 | # is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends: |
| 24 | # |
| 25 | # class WeblogController < ActionController::Base |
| 26 | # def update |
| 27 | # List.update(params[:list][:id], params[:list]) |
| 28 | # expire_page :action => "show", :id => params[:list][:id] |
| 29 | # redirect_to :action => "show", :id => params[:list][:id] |
| 30 | # end |
| 31 | # end |
| 32 | # |
| 33 | # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be |
| 34 | # expired. |
| 35 | # |
| 36 | # == Setting the cache directory |
| 37 | # |
| 38 | # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root". |
| 39 | # For Rails, this directory has already been set to RAILS_ROOT + "/public". |
| 40 | # |
| 41 | # == Setting the cache extension |
| 42 | # |
| 43 | # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want |
| 44 | # something else, like .php or .shtml, just set Base.page_cache_extension. |
| 45 | module Pages |
| 46 | def self.included(base) #:nodoc: |
| 47 | base.extend(ClassMethods) |
| 48 | base.class_eval do |
| 49 | @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "" |
| 50 | cattr_accessor :page_cache_directory |
| 51 | |
| 52 | @@page_cache_extension = '.html' |
| 53 | cattr_accessor :page_cache_extension |
| 54 | end |
| 55 | end |
| 56 | |
| 57 | module ClassMethods |
| 58 | # Expires the page that was cached with the +path+ as a key. Example: |
| 59 | # expire_page "/lists/show" |
| 60 | def expire_page(path) |
| 61 | return unless perform_caching |
| 62 | |
| 63 | benchmark "Expired page: #{page_cache_file(path)}" do |
| 64 | File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path)) |
| 65 | end |
| 66 | end |
| 67 | |
| 68 | # Manually cache the +content+ in the key determined by +path+. Example: |
| 69 | # cache_page "I'm the cached content", "/lists/show" |
| 70 | def cache_page(content, path) |
| 71 | return unless perform_caching |
| 72 | |
| 73 | benchmark "Cached page: #{page_cache_file(path)}" do |
| 74 | FileUtils.makedirs(File.dirname(page_cache_path(path))) |
| 75 | File.open(page_cache_path(path), "wb+") { |f| f.write(content) } |
| 76 | end |
| 77 | end |
| 78 | |
| 79 | # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that |
| 80 | # matches the triggering url. |
| 81 | def caches_page(*actions) |
| 82 | return unless perform_caching |
| 83 | actions = actions.map(&:to_s) |
| 84 | after_filter { |c| c.cache_page if actions.include?(c.action_name) } |
| 85 | end |
| 86 | |
| 87 | private |
| 88 | def page_cache_file(path) |
| 89 | name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/')) |
| 90 | name << page_cache_extension unless (name.split('/').last || name).include? '.' |
| 91 | return name |
| 92 | end |
| 93 | |
| 94 | def page_cache_path(path) |
| 95 | page_cache_directory + page_cache_file(path) |
| 96 | end |
| 97 | end |
| 98 | |
| 99 | # Expires the page that was cached with the +options+ as a key. Example: |
| 100 | # expire_page :controller => "lists", :action => "show" |
| 101 | def expire_page(options = {}) |
| 102 | return unless perform_caching |
| 103 | |
| 104 | if options.is_a?(Hash) |
| 105 | if options[:action].is_a?(Array) |
| 106 | options[:action].dup.each do |action| |
| 107 | self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action))) |
| 108 | end |
| 109 | else |
| 110 | self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true))) |
| 111 | end |
| 112 | else |
| 113 | self.class.expire_page(options) |
| 114 | end |
| 115 | end |
| 116 | |
| 117 | # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used |
| 118 | # If no options are provided, the requested url is used. Example: |
| 119 | # cache_page "I'm the cached content", :controller => "lists", :action => "show" |
| 120 | def cache_page(content = nil, options = nil) |
| 121 | return unless perform_caching && caching_allowed |
| 122 | |
| 123 | path = case options |
| 124 | when Hash |
| 125 | url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])) |
| 126 | when String |
| 127 | options |
| 128 | else |
| 129 | request.path |
| 130 | end |
| 131 | |
| 132 | self.class.cache_page(content || response.body, path) |
| 133 | end |
| 134 | |
| 135 | private |
| 136 | def caching_allowed |
| 137 | request.get? && response.headers['Status'].to_i == 200 |
| 138 | end |
| 139 | end |
| 140 | end |
| 141 | end |
Note: See TracBrowser for help on using the browser.