openstreetmap-actionpack-page_caching 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +116 -0
- data/.travis.yml +43 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +215 -0
- data/Rakefile +11 -0
- data/actionpack-page_caching.gemspec +21 -0
- data/gemfiles/Gemfile-5-0-stable +5 -0
- data/gemfiles/Gemfile-5-1-stable +5 -0
- data/gemfiles/Gemfile-5-2-stable +5 -0
- data/gemfiles/Gemfile-6-0-stable +5 -0
- data/gemfiles/Gemfile-edge +6 -0
- data/lib/action_controller/caching/pages.rb +306 -0
- data/lib/action_controller/page_caching.rb +13 -0
- data/lib/actionpack/page_caching.rb +1 -0
- data/lib/actionpack/page_caching/railtie.rb +17 -0
- data/test/abstract_unit.rb +12 -0
- data/test/caching_test.rb +524 -0
- data/test/log_subscriber_test.rb +57 -0
- metadata +95 -0
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "openstreetmap-actionpack-page_caching"
|
3
|
+
gem.version = "1.1.2"
|
4
|
+
gem.author = "David Heinemeier Hansson"
|
5
|
+
gem.email = "tom@compton.nu"
|
6
|
+
gem.description = "Static page caching for Action Pack (removed from core in Rails 4.0)"
|
7
|
+
gem.summary = "Static page caching for Action Pack (removed from core in Rails 4.0)"
|
8
|
+
gem.homepage = "https://github.com/rails/actionpack-page_caching"
|
9
|
+
gem.license = "MIT"
|
10
|
+
|
11
|
+
gem.required_ruby_version = '>= 2.4.6'
|
12
|
+
gem.files = `git ls-files`.split($/)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.license = 'MIT'
|
17
|
+
|
18
|
+
gem.add_dependency "actionpack", ">= 5.0.0"
|
19
|
+
|
20
|
+
gem.add_development_dependency "mocha"
|
21
|
+
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "uri"
|
3
|
+
require "active_support/core_ext/class/attribute_accessors"
|
4
|
+
require "active_support/core_ext/string/strip"
|
5
|
+
|
6
|
+
module ActionController
|
7
|
+
module Caching
|
8
|
+
# Page caching is an approach to caching where the entire action output of is
|
9
|
+
# stored as a HTML file that the web server can serve without going through
|
10
|
+
# Action Pack. This is the fastest way to cache your content as opposed to going
|
11
|
+
# dynamically through the process of generating the content. Unfortunately, this
|
12
|
+
# incredible speed-up is only available to stateless pages where all visitors are
|
13
|
+
# treated the same. Content management systems -- including weblogs and wikis --
|
14
|
+
# have many pages that are a great fit for this approach, but account-based systems
|
15
|
+
# where people log in and manipulate their own data are often less likely candidates.
|
16
|
+
#
|
17
|
+
# Specifying which actions to cache is done through the +caches_page+ class method:
|
18
|
+
#
|
19
|
+
# class WeblogController < ActionController::Base
|
20
|
+
# caches_page :show, :new
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# This will generate cache files such as <tt>weblog/show/5.html</tt> and
|
24
|
+
# <tt>weblog/new.html</tt>, which match the URLs used that would normally trigger
|
25
|
+
# dynamic page generation. Page caching works by configuring a web server to first
|
26
|
+
# check for the existence of files on disk, and to serve them directly when found,
|
27
|
+
# without passing the request through to Action Pack. This is much faster than
|
28
|
+
# handling the full dynamic request in the usual way.
|
29
|
+
#
|
30
|
+
# Expiration of the cache is handled by deleting the cached file, which results
|
31
|
+
# in a lazy regeneration approach where the cache is not restored before another
|
32
|
+
# hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
|
33
|
+
#
|
34
|
+
# class WeblogController < ActionController::Base
|
35
|
+
# def update
|
36
|
+
# List.update(params[:list][:id], params[:list])
|
37
|
+
# expire_page action: "show", id: params[:list][:id]
|
38
|
+
# redirect_to action: "show", id: params[:list][:id]
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# Additionally, you can expire caches using Sweepers that act on changes in
|
43
|
+
# the model to determine when a cache is supposed to be expired.
|
44
|
+
module Pages
|
45
|
+
extend ActiveSupport::Concern
|
46
|
+
|
47
|
+
included do
|
48
|
+
# The cache directory should be the document root for the web server and is
|
49
|
+
# set using <tt>Base.page_cache_directory = "/document/root"</tt>. For Rails,
|
50
|
+
# this directory has already been set to Rails.public_path (which is usually
|
51
|
+
# set to <tt>Rails.root + "/public"</tt>). Changing this setting can be useful
|
52
|
+
# to avoid naming conflicts with files in <tt>public/</tt>, but doing so will
|
53
|
+
# likely require configuring your web server to look in the new location for
|
54
|
+
# cached files.
|
55
|
+
class_attribute :page_cache_directory
|
56
|
+
self.page_cache_directory ||= ""
|
57
|
+
|
58
|
+
# The compression used for gzip. If +false+ (default), the page is not compressed.
|
59
|
+
# If can be a symbol showing the ZLib compression method, for example, <tt>:best_compression</tt>
|
60
|
+
# or <tt>:best_speed</tt> or an integer configuring the compression level.
|
61
|
+
class_attribute :page_cache_compression
|
62
|
+
self.page_cache_compression ||= false
|
63
|
+
end
|
64
|
+
|
65
|
+
class PageCache #:nodoc:
|
66
|
+
def initialize(cache_directory, default_extension, controller = nil)
|
67
|
+
@cache_directory = cache_directory
|
68
|
+
@default_extension = default_extension
|
69
|
+
@controller = controller
|
70
|
+
end
|
71
|
+
|
72
|
+
def expire(path)
|
73
|
+
instrument :expire_page, path do
|
74
|
+
delete(cache_path(path))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def cache(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
|
79
|
+
instrument :write_page, path do
|
80
|
+
write(content, cache_path(path, extension), gzip)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def cache_directory
|
86
|
+
case @cache_directory
|
87
|
+
when Proc
|
88
|
+
handle_proc_cache_directory
|
89
|
+
when Symbol
|
90
|
+
handle_symbol_cache_directory
|
91
|
+
else
|
92
|
+
handle_default_cache_directory
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def handle_proc_cache_directory
|
97
|
+
if @controller
|
98
|
+
@controller.instance_exec(&@cache_directory)
|
99
|
+
else
|
100
|
+
raise_runtime_error
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_symbol_cache_directory
|
105
|
+
if @controller
|
106
|
+
@controller.send(@cache_directory)
|
107
|
+
else
|
108
|
+
raise_runtime_error
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_callable_cache_directory
|
113
|
+
if @controller
|
114
|
+
@cache_directory.call(@controller.request)
|
115
|
+
else
|
116
|
+
raise_runtime_error
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def handle_default_cache_directory
|
121
|
+
if @cache_directory.respond_to?(:call)
|
122
|
+
handle_callable_cache_directory
|
123
|
+
else
|
124
|
+
@cache_directory.to_s
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def raise_runtime_error
|
129
|
+
raise RuntimeError, <<-MSG.strip_heredoc
|
130
|
+
Dynamic page_cache_directory used with class-level cache_page method
|
131
|
+
|
132
|
+
You have specified either a Proc, Symbol or callable object for page_cache_directory
|
133
|
+
which needs to be executed within the context of a request. If you need to call the
|
134
|
+
cache_page method from a class-level context then set the page_cache_directory to a
|
135
|
+
static value and override the setting at the instance-level using before_action.
|
136
|
+
MSG
|
137
|
+
end
|
138
|
+
|
139
|
+
def default_extension
|
140
|
+
@default_extension
|
141
|
+
end
|
142
|
+
|
143
|
+
def cache_file(path, extension)
|
144
|
+
if path.empty? || path =~ %r{\A/+\z}
|
145
|
+
name = "/index"
|
146
|
+
else
|
147
|
+
name = URI.parser.unescape(path.chomp("/"))
|
148
|
+
end
|
149
|
+
|
150
|
+
if File.extname(name).empty?
|
151
|
+
name + (extension || default_extension)
|
152
|
+
else
|
153
|
+
name
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def cache_path(path, extension = nil)
|
158
|
+
File.join(cache_directory, cache_file(path, extension))
|
159
|
+
end
|
160
|
+
|
161
|
+
def delete(path)
|
162
|
+
File.delete(path) if File.exist?(path)
|
163
|
+
File.delete(path + ".gz") if File.exist?(path + ".gz")
|
164
|
+
end
|
165
|
+
|
166
|
+
def write(content, path, gzip)
|
167
|
+
FileUtils.makedirs(File.dirname(path))
|
168
|
+
File.open(path, "wb+") { |f| f.write(content) }
|
169
|
+
|
170
|
+
if gzip
|
171
|
+
Zlib::GzipWriter.open(path + ".gz", gzip) { |f| f.write(content) }
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def instrument(name, path)
|
176
|
+
ActiveSupport::Notifications.instrument("#{name}.action_controller", path: path) { yield }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
module ClassMethods
|
181
|
+
# Expires the page that was cached with the +path+ as a key.
|
182
|
+
#
|
183
|
+
# expire_page "/lists/show"
|
184
|
+
def expire_page(path)
|
185
|
+
if perform_caching
|
186
|
+
page_cache.expire(path)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Manually cache the +content+ in the key determined by +path+.
|
191
|
+
#
|
192
|
+
# cache_page "I'm the cached content", "/lists/show"
|
193
|
+
def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
|
194
|
+
if perform_caching
|
195
|
+
page_cache.cache(content, path, extension, gzip)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Caches the +actions+ using the page-caching approach that'll store
|
200
|
+
# the cache in a path within the +page_cache_directory+ that
|
201
|
+
# matches the triggering url.
|
202
|
+
#
|
203
|
+
# You can also pass a <tt>:gzip</tt> option to override the class configuration one.
|
204
|
+
#
|
205
|
+
# # cache the index action
|
206
|
+
# caches_page :index
|
207
|
+
#
|
208
|
+
# # cache the index action except for JSON requests
|
209
|
+
# caches_page :index, if: Proc.new { !request.format.json? }
|
210
|
+
#
|
211
|
+
# # don't gzip images
|
212
|
+
# caches_page :image, gzip: false
|
213
|
+
def caches_page(*actions)
|
214
|
+
if perform_caching
|
215
|
+
options = actions.extract_options!
|
216
|
+
|
217
|
+
gzip_level = options.fetch(:gzip, page_cache_compression)
|
218
|
+
gzip_level = \
|
219
|
+
case gzip_level
|
220
|
+
when Symbol
|
221
|
+
Zlib.const_get(gzip_level.upcase)
|
222
|
+
when Integer
|
223
|
+
gzip_level
|
224
|
+
when false
|
225
|
+
nil
|
226
|
+
else
|
227
|
+
Zlib::BEST_COMPRESSION
|
228
|
+
end
|
229
|
+
|
230
|
+
after_action({ only: actions }.merge(options)) do |c|
|
231
|
+
c.cache_page(nil, nil, gzip_level)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
def page_cache
|
238
|
+
PageCache.new(page_cache_directory, default_static_extension)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Expires the page that was cached with the +options+ as a key.
|
243
|
+
#
|
244
|
+
# expire_page controller: "lists", action: "show"
|
245
|
+
def expire_page(options = {})
|
246
|
+
if perform_caching?
|
247
|
+
case options
|
248
|
+
when Hash
|
249
|
+
case options[:action]
|
250
|
+
when Array
|
251
|
+
options[:action].each { |action| expire_page(options.merge(action: action)) }
|
252
|
+
else
|
253
|
+
page_cache.expire(url_for(options.merge(only_path: true)))
|
254
|
+
end
|
255
|
+
else
|
256
|
+
page_cache.expire(options)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Manually cache the +content+ in the key determined by +options+. If no content is provided,
|
262
|
+
# the contents of response.body is used. If no options are provided, the url of the current
|
263
|
+
# request being handled is used.
|
264
|
+
#
|
265
|
+
# cache_page "I'm the cached content", controller: "lists", action: "show"
|
266
|
+
def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
|
267
|
+
if perform_caching? && caching_allowed?
|
268
|
+
path = \
|
269
|
+
case options
|
270
|
+
when Hash
|
271
|
+
url_for(options.merge(only_path: true, format: params[:format]))
|
272
|
+
when String
|
273
|
+
options
|
274
|
+
else
|
275
|
+
request.path
|
276
|
+
end
|
277
|
+
|
278
|
+
type = if self.respond_to?(:media_type)
|
279
|
+
Mime::LOOKUP[self.media_type]
|
280
|
+
else
|
281
|
+
Mime::LOOKUP[self.content_type]
|
282
|
+
end
|
283
|
+
|
284
|
+
if type && (type_symbol = type.symbol).present?
|
285
|
+
extension = ".#{type_symbol}"
|
286
|
+
end
|
287
|
+
|
288
|
+
page_cache.cache(content || response.body, path, extension, gzip)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def caching_allowed?
|
293
|
+
(request.get? || request.head?) && response.status == 200
|
294
|
+
end
|
295
|
+
|
296
|
+
def perform_caching?
|
297
|
+
self.class.perform_caching
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
def page_cache
|
302
|
+
PageCache.new(page_cache_directory, default_static_extension, self)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "actionpack/page_caching/railtie"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rails/railtie"
|
2
|
+
|
3
|
+
module ActionPack
|
4
|
+
module PageCaching
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "action_pack.page_caching" do
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
require "action_controller/page_caching"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "action_pack.page_caching.set_config", before: "action_controller.set_configs" do |app|
|
13
|
+
app.config.action_controller.page_cache_directory ||= app.config.paths["public"].first
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "minitest/autorun"
|
3
|
+
require "action_controller"
|
4
|
+
require "action_controller/page_caching"
|
5
|
+
|
6
|
+
if ActiveSupport.respond_to?(:test_order)
|
7
|
+
ActiveSupport.test_order = :random
|
8
|
+
end
|
9
|
+
|
10
|
+
if ActionController::Base.respond_to?(:enable_fragment_cache_logging=)
|
11
|
+
ActionController::Base.enable_fragment_cache_logging = true
|
12
|
+
end
|
@@ -0,0 +1,524 @@
|
|
1
|
+
require "abstract_unit"
|
2
|
+
require "mocha/setup"
|
3
|
+
|
4
|
+
CACHE_DIR = "test_cache"
|
5
|
+
# Don't change "../tmp" cavalierly or you might hose something you don't want hosed
|
6
|
+
TEST_TMP_DIR = File.expand_path("../tmp", __FILE__)
|
7
|
+
FILE_STORE_PATH = File.join(TEST_TMP_DIR, CACHE_DIR)
|
8
|
+
|
9
|
+
module PageCachingTestHelpers
|
10
|
+
def setup
|
11
|
+
super
|
12
|
+
|
13
|
+
@routes = ActionDispatch::Routing::RouteSet.new
|
14
|
+
|
15
|
+
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
16
|
+
FileUtils.mkdir_p(FILE_STORE_PATH)
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
super
|
21
|
+
|
22
|
+
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
23
|
+
@controller.perform_caching = false
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def assert_page_cached(action, options = {})
|
29
|
+
expected = options[:content] || action.to_s
|
30
|
+
path = cache_file(action, options)
|
31
|
+
|
32
|
+
assert File.exist?(path), "The cache file #{path} doesn't exist"
|
33
|
+
|
34
|
+
if File.extname(path) == ".gz"
|
35
|
+
actual = Zlib::GzipReader.open(path) { |f| f.read }
|
36
|
+
else
|
37
|
+
actual = File.read(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
assert_equal expected, actual, "The cached content doesn't match the expected value"
|
41
|
+
end
|
42
|
+
|
43
|
+
def assert_page_not_cached(action, options = {})
|
44
|
+
path = cache_file(action, options)
|
45
|
+
assert !File.exist?(path), "The cache file #{path} still exists"
|
46
|
+
end
|
47
|
+
|
48
|
+
def cache_file(action, options = {})
|
49
|
+
path = options[:path] || FILE_STORE_PATH
|
50
|
+
controller = options[:controller] || self.class.name.underscore
|
51
|
+
format = options[:format] || "html"
|
52
|
+
|
53
|
+
"#{path}/#{controller}/#{action}.#{format}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def draw(&block)
|
57
|
+
@routes = ActionDispatch::Routing::RouteSet.new
|
58
|
+
@routes.draw(&block)
|
59
|
+
@controller.extend(@routes.url_helpers)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class CachingMetalController < ActionController::Metal
|
64
|
+
abstract!
|
65
|
+
|
66
|
+
include AbstractController::Callbacks
|
67
|
+
include ActionController::Caching
|
68
|
+
|
69
|
+
self.page_cache_directory = FILE_STORE_PATH
|
70
|
+
self.cache_store = :file_store, FILE_STORE_PATH
|
71
|
+
end
|
72
|
+
|
73
|
+
class PageCachingMetalTestController < CachingMetalController
|
74
|
+
caches_page :ok
|
75
|
+
|
76
|
+
def ok
|
77
|
+
self.response_body = "ok"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class PageCachingMetalTest < ActionController::TestCase
|
82
|
+
include PageCachingTestHelpers
|
83
|
+
tests PageCachingMetalTestController
|
84
|
+
|
85
|
+
def test_should_cache_get_with_ok_status
|
86
|
+
draw do
|
87
|
+
get "/page_caching_metal_test/ok", to: "page_caching_metal_test#ok"
|
88
|
+
end
|
89
|
+
|
90
|
+
get :ok
|
91
|
+
assert_response :ok
|
92
|
+
assert_page_cached :ok
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
ActionController::Base.page_cache_directory = FILE_STORE_PATH
|
97
|
+
|
98
|
+
class CachingController < ActionController::Base
|
99
|
+
abstract!
|
100
|
+
|
101
|
+
self.cache_store = :file_store, FILE_STORE_PATH
|
102
|
+
|
103
|
+
protected
|
104
|
+
if ActionPack::VERSION::STRING < "4.1"
|
105
|
+
def render(options)
|
106
|
+
if options.key?(:html)
|
107
|
+
super({ text: options.delete(:html) }.merge(options))
|
108
|
+
else
|
109
|
+
super
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class PageCachingTestController < CachingController
|
116
|
+
self.page_cache_compression = :best_compression
|
117
|
+
|
118
|
+
caches_page :ok, :no_content, if: Proc.new { |c| !c.request.format.json? }
|
119
|
+
caches_page :found, :not_found
|
120
|
+
caches_page :about_me
|
121
|
+
caches_page :default_gzip
|
122
|
+
caches_page :no_gzip, gzip: false
|
123
|
+
caches_page :gzip_level, gzip: :best_speed
|
124
|
+
|
125
|
+
def ok
|
126
|
+
render html: "ok"
|
127
|
+
end
|
128
|
+
|
129
|
+
def no_content
|
130
|
+
head :no_content
|
131
|
+
end
|
132
|
+
|
133
|
+
def found
|
134
|
+
redirect_to action: "ok"
|
135
|
+
end
|
136
|
+
|
137
|
+
def not_found
|
138
|
+
head :not_found
|
139
|
+
end
|
140
|
+
|
141
|
+
def custom_path
|
142
|
+
render html: "custom_path"
|
143
|
+
cache_page(nil, "/index.html")
|
144
|
+
end
|
145
|
+
|
146
|
+
def default_gzip
|
147
|
+
render html: "default_gzip"
|
148
|
+
end
|
149
|
+
|
150
|
+
def no_gzip
|
151
|
+
render html: "no_gzip"
|
152
|
+
end
|
153
|
+
|
154
|
+
def gzip_level
|
155
|
+
render html: "gzip_level"
|
156
|
+
end
|
157
|
+
|
158
|
+
def expire_custom_path
|
159
|
+
expire_page("/index.html")
|
160
|
+
head :ok
|
161
|
+
end
|
162
|
+
|
163
|
+
def trailing_slash
|
164
|
+
render html: "trailing_slash"
|
165
|
+
end
|
166
|
+
|
167
|
+
def about_me
|
168
|
+
respond_to do |format|
|
169
|
+
format.html { render html: "I am html" }
|
170
|
+
format.xml { render xml: "I am xml" }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class PageCachingTest < ActionController::TestCase
|
176
|
+
include PageCachingTestHelpers
|
177
|
+
tests PageCachingTestController
|
178
|
+
|
179
|
+
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
|
180
|
+
draw do
|
181
|
+
get "posts.:format", to: "posts#index", as: :formatted_posts
|
182
|
+
get "/", to: "posts#index", as: :main
|
183
|
+
end
|
184
|
+
|
185
|
+
defaults = { controller: "posts", action: "index", only_path: true }
|
186
|
+
|
187
|
+
assert_equal "/posts.rss", @routes.url_for(defaults.merge(format: "rss"))
|
188
|
+
assert_equal "/", @routes.url_for(defaults.merge(format: nil))
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_should_cache_head_with_ok_status
|
192
|
+
draw do
|
193
|
+
get "/page_caching_test/ok", to: "page_caching_test#ok"
|
194
|
+
end
|
195
|
+
|
196
|
+
head :ok
|
197
|
+
assert_response :ok
|
198
|
+
assert_page_cached :ok
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_should_cache_get_with_ok_status
|
202
|
+
draw do
|
203
|
+
get "/page_caching_test/ok", to: "page_caching_test#ok"
|
204
|
+
end
|
205
|
+
|
206
|
+
get :ok
|
207
|
+
assert_response :ok
|
208
|
+
assert_page_cached :ok
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_should_cache_with_custom_path
|
212
|
+
draw do
|
213
|
+
get "/page_caching_test/custom_path", to: "page_caching_test#custom_path"
|
214
|
+
end
|
215
|
+
|
216
|
+
get :custom_path
|
217
|
+
assert_page_cached :index, controller: ".", content: "custom_path"
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_should_expire_cache_with_custom_path
|
221
|
+
draw do
|
222
|
+
get "/page_caching_test/custom_path", to: "page_caching_test#custom_path"
|
223
|
+
get "/page_caching_test/expire_custom_path", to: "page_caching_test#expire_custom_path"
|
224
|
+
end
|
225
|
+
|
226
|
+
get :custom_path
|
227
|
+
assert_page_cached :index, controller: ".", content: "custom_path"
|
228
|
+
|
229
|
+
get :expire_custom_path
|
230
|
+
assert_page_not_cached :index, controller: ".", content: "custom_path"
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_should_gzip_cache
|
234
|
+
draw do
|
235
|
+
get "/page_caching_test/custom_path", to: "page_caching_test#custom_path"
|
236
|
+
get "/page_caching_test/expire_custom_path", to: "page_caching_test#expire_custom_path"
|
237
|
+
end
|
238
|
+
|
239
|
+
get :custom_path
|
240
|
+
assert_page_cached :index, controller: ".", format: "html.gz", content: "custom_path"
|
241
|
+
|
242
|
+
get :expire_custom_path
|
243
|
+
assert_page_not_cached :index, controller: ".", format: "html.gz"
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_should_allow_to_disable_gzip
|
247
|
+
draw do
|
248
|
+
get "/page_caching_test/no_gzip", to: "page_caching_test#no_gzip"
|
249
|
+
end
|
250
|
+
|
251
|
+
get :no_gzip
|
252
|
+
assert_page_cached :no_gzip, format: "html"
|
253
|
+
assert_page_not_cached :no_gzip, format: "html.gz"
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_should_use_config_gzip_by_default
|
257
|
+
draw do
|
258
|
+
get "/page_caching_test/default_gzip", to: "page_caching_test#default_gzip"
|
259
|
+
end
|
260
|
+
|
261
|
+
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION)
|
262
|
+
get :default_gzip
|
263
|
+
end
|
264
|
+
|
265
|
+
def test_should_set_gzip_level
|
266
|
+
draw do
|
267
|
+
get "/page_caching_test/gzip_level", to: "page_caching_test#gzip_level"
|
268
|
+
end
|
269
|
+
|
270
|
+
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED)
|
271
|
+
get :gzip_level
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_should_cache_without_trailing_slash_on_url
|
275
|
+
@controller.class.cache_page "cached content", "/page_caching_test/trailing_slash"
|
276
|
+
assert_page_cached :trailing_slash, content: "cached content"
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_should_obey_http_accept_attribute
|
280
|
+
draw do
|
281
|
+
get "/page_caching_test/about_me", to: "page_caching_test#about_me"
|
282
|
+
end
|
283
|
+
|
284
|
+
@request.env["HTTP_ACCEPT"] = "text/xml"
|
285
|
+
get :about_me
|
286
|
+
assert_equal "I am xml", @response.body
|
287
|
+
assert_page_cached :about_me, format: "xml", content: "I am xml"
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash
|
291
|
+
@controller.class.cache_page "cached content", "/page_caching_test/trailing_slash/"
|
292
|
+
assert_page_cached :trailing_slash, content: "cached content"
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_should_cache_ok_at_custom_path
|
296
|
+
draw do
|
297
|
+
get "/page_caching_test/ok", to: "page_caching_test#ok"
|
298
|
+
end
|
299
|
+
|
300
|
+
@request.env["PATH_INFO"] = "/index.html"
|
301
|
+
get :ok
|
302
|
+
assert_response :ok
|
303
|
+
assert_page_cached :index, controller: ".", content: "ok"
|
304
|
+
end
|
305
|
+
|
306
|
+
[:ok, :no_content, :found, :not_found].each do |status|
|
307
|
+
[:get, :post, :patch, :put, :delete].each do |method|
|
308
|
+
unless method == :get && status == :ok
|
309
|
+
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
|
310
|
+
draw do
|
311
|
+
get "/page_caching_test/ok", to: "page_caching_test#ok"
|
312
|
+
match "/page_caching_test/#{status}", to: "page_caching_test##{status}", via: method
|
313
|
+
end
|
314
|
+
|
315
|
+
send(method, status)
|
316
|
+
assert_response status
|
317
|
+
assert_page_not_cached status
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def test_page_caching_conditional_options
|
324
|
+
draw do
|
325
|
+
get "/page_caching_test/ok", to: "page_caching_test#ok"
|
326
|
+
end
|
327
|
+
|
328
|
+
get :ok, format: "json"
|
329
|
+
assert_page_not_cached :ok
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_page_caching_directory_set_as_pathname
|
333
|
+
begin
|
334
|
+
ActionController::Base.page_cache_directory = Pathname.new(FILE_STORE_PATH)
|
335
|
+
|
336
|
+
draw do
|
337
|
+
get "/page_caching_test/ok", to: "page_caching_test#ok"
|
338
|
+
end
|
339
|
+
|
340
|
+
get :ok
|
341
|
+
assert_response :ok
|
342
|
+
assert_page_cached :ok
|
343
|
+
ensure
|
344
|
+
ActionController::Base.page_cache_directory = FILE_STORE_PATH
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def test_page_caching_directory_set_on_controller_instance
|
349
|
+
draw do
|
350
|
+
get "/page_caching_test/ok", to: "page_caching_test#ok"
|
351
|
+
end
|
352
|
+
|
353
|
+
file_store_path = File.join(TEST_TMP_DIR, "instance_cache")
|
354
|
+
@controller.page_cache_directory = file_store_path
|
355
|
+
|
356
|
+
get :ok
|
357
|
+
assert_response :ok
|
358
|
+
assert_page_cached :ok, path: file_store_path
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
class ProcPageCachingTestController < CachingController
|
363
|
+
self.page_cache_directory = -> { File.join(TEST_TMP_DIR, request.domain) }
|
364
|
+
|
365
|
+
caches_page :ok
|
366
|
+
|
367
|
+
def ok
|
368
|
+
render html: "ok"
|
369
|
+
end
|
370
|
+
|
371
|
+
def expire_ok
|
372
|
+
expire_page action: :ok
|
373
|
+
head :ok
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
class ProcPageCachingTest < ActionController::TestCase
|
378
|
+
include PageCachingTestHelpers
|
379
|
+
tests ProcPageCachingTestController
|
380
|
+
|
381
|
+
def test_page_is_cached_by_domain
|
382
|
+
draw do
|
383
|
+
get "/proc_page_caching_test/ok", to: "proc_page_caching_test#ok"
|
384
|
+
get "/proc_page_caching_test/ok/expire", to: "proc_page_caching_test#expire_ok"
|
385
|
+
end
|
386
|
+
|
387
|
+
@request.env["HTTP_HOST"] = "www.foo.com"
|
388
|
+
get :ok
|
389
|
+
assert_response :ok
|
390
|
+
assert_page_cached :ok, path: TEST_TMP_DIR + "/foo.com"
|
391
|
+
|
392
|
+
get :expire_ok
|
393
|
+
assert_response :ok
|
394
|
+
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/foo.com"
|
395
|
+
|
396
|
+
@request.env["HTTP_HOST"] = "www.bar.com"
|
397
|
+
get :ok
|
398
|
+
assert_response :ok
|
399
|
+
assert_page_cached :ok, path: TEST_TMP_DIR + "/bar.com"
|
400
|
+
|
401
|
+
get :expire_ok
|
402
|
+
assert_response :ok
|
403
|
+
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/bar.com"
|
404
|
+
end
|
405
|
+
|
406
|
+
def test_class_level_cache_page_raise_error
|
407
|
+
assert_raises(RuntimeError, /class-level cache_page method/) do
|
408
|
+
@controller.class.cache_page "cached content", "/proc_page_caching_test/ok"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
class SymbolPageCachingTestController < CachingController
|
414
|
+
self.page_cache_directory = :domain_cache_directory
|
415
|
+
|
416
|
+
caches_page :ok
|
417
|
+
|
418
|
+
def ok
|
419
|
+
render html: "ok"
|
420
|
+
end
|
421
|
+
|
422
|
+
def expire_ok
|
423
|
+
expire_page action: :ok
|
424
|
+
head :ok
|
425
|
+
end
|
426
|
+
|
427
|
+
protected
|
428
|
+
def domain_cache_directory
|
429
|
+
File.join(TEST_TMP_DIR, request.domain)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
class SymbolPageCachingTest < ActionController::TestCase
|
434
|
+
include PageCachingTestHelpers
|
435
|
+
tests SymbolPageCachingTestController
|
436
|
+
|
437
|
+
def test_page_is_cached_by_domain
|
438
|
+
draw do
|
439
|
+
get "/symbol_page_caching_test/ok", to: "symbol_page_caching_test#ok"
|
440
|
+
get "/symbol_page_caching_test/ok/expire", to: "symbol_page_caching_test#expire_ok"
|
441
|
+
end
|
442
|
+
|
443
|
+
@request.env["HTTP_HOST"] = "www.foo.com"
|
444
|
+
get :ok
|
445
|
+
assert_response :ok
|
446
|
+
assert_page_cached :ok, path: TEST_TMP_DIR + "/foo.com"
|
447
|
+
|
448
|
+
get :expire_ok
|
449
|
+
assert_response :ok
|
450
|
+
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/foo.com"
|
451
|
+
|
452
|
+
@request.env["HTTP_HOST"] = "www.bar.com"
|
453
|
+
get :ok
|
454
|
+
assert_response :ok
|
455
|
+
assert_page_cached :ok, path: TEST_TMP_DIR + "/bar.com"
|
456
|
+
|
457
|
+
get :expire_ok
|
458
|
+
assert_response :ok
|
459
|
+
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/bar.com"
|
460
|
+
end
|
461
|
+
|
462
|
+
def test_class_level_cache_page_raise_error
|
463
|
+
assert_raises(RuntimeError, /class-level cache_page method/) do
|
464
|
+
@controller.class.cache_page "cached content", "/symbol_page_caching_test/ok"
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
class CallablePageCachingTestController < CachingController
|
470
|
+
class DomainCacheDirectory
|
471
|
+
def self.call(request)
|
472
|
+
File.join(TEST_TMP_DIR, request.domain)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
self.page_cache_directory = DomainCacheDirectory
|
477
|
+
|
478
|
+
caches_page :ok
|
479
|
+
|
480
|
+
def ok
|
481
|
+
render html: "ok"
|
482
|
+
end
|
483
|
+
|
484
|
+
def expire_ok
|
485
|
+
expire_page action: :ok
|
486
|
+
head :ok
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
class CallablePageCachingTest < ActionController::TestCase
|
491
|
+
include PageCachingTestHelpers
|
492
|
+
tests CallablePageCachingTestController
|
493
|
+
|
494
|
+
def test_page_is_cached_by_domain
|
495
|
+
draw do
|
496
|
+
get "/callable_page_caching_test/ok", to: "callable_page_caching_test#ok"
|
497
|
+
get "/callable_page_caching_test/ok/expire", to: "callable_page_caching_test#expire_ok"
|
498
|
+
end
|
499
|
+
|
500
|
+
@request.env["HTTP_HOST"] = "www.foo.com"
|
501
|
+
get :ok
|
502
|
+
assert_response :ok
|
503
|
+
assert_page_cached :ok, path: TEST_TMP_DIR + "/foo.com"
|
504
|
+
|
505
|
+
get :expire_ok
|
506
|
+
assert_response :ok
|
507
|
+
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/foo.com"
|
508
|
+
|
509
|
+
@request.env["HTTP_HOST"] = "www.bar.com"
|
510
|
+
get :ok
|
511
|
+
assert_response :ok
|
512
|
+
assert_page_cached :ok, path: TEST_TMP_DIR + "/bar.com"
|
513
|
+
|
514
|
+
get :expire_ok
|
515
|
+
assert_response :ok
|
516
|
+
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/bar.com"
|
517
|
+
end
|
518
|
+
|
519
|
+
def test_class_level_cache_page_raise_error
|
520
|
+
assert_raises(RuntimeError, /class-level cache_page method/) do
|
521
|
+
@controller.class.cache_page "cached content", "/callable_page_caching_test/ok"
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|