actionpack-page_caching_multithread 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +25 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/Rakefile +11 -0
- data/actionpack-page_caching_multithread.gemspec +21 -0
- data/gemfiles/Gemfile-4-0-stable +5 -0
- data/gemfiles/Gemfile-edge +5 -0
- data/lib/action_controller/caching/pages.rb +213 -0
- data/lib/action_controller/page_caching_multithread.rb +13 -0
- data/lib/actionpack/page_caching_multithread.rb +1 -0
- data/lib/actionpack/page_caching_multithread/railtie.rb +17 -0
- data/test/abstract_unit.rb +22 -0
- data/test/caching_test.rb +265 -0
- data/test/log_subscriber_test.rb +54 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a147a8417128e01b369621434196fd328aa1c29a
|
4
|
+
data.tar.gz: ce604c979d8946929e535e7ef1c0ce697a1d6e50
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ce048215299a130b477b9ea7974d04b8966c0d014c7cd3d9fe60d5c5c39854678b54427549a9f1f95d6780a137b45792de3a0f039e56180672eb2bf0372ec6ce
|
7
|
+
data.tar.gz: 409c1e2c4cea069c07448693a10b327f6eb073dfd6bb2cb3bca88c274cf314519bfe47230462c7595345a507e3b261442ba8a61ac76edbf0d4bab733bb8eb821
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
language: ruby
|
2
|
+
before_install:
|
3
|
+
- gem install bundler
|
4
|
+
rvm:
|
5
|
+
- 1.9.3
|
6
|
+
- 2.0.0
|
7
|
+
gemfile:
|
8
|
+
- Gemfile
|
9
|
+
- gemfiles/Gemfile-4-0-stable
|
10
|
+
- gemfiles/Gemfile-edge
|
11
|
+
matrix:
|
12
|
+
allow_failures:
|
13
|
+
- gemfile: gemfiles/Gemfile-edge
|
14
|
+
notifications:
|
15
|
+
email: false
|
16
|
+
irc:
|
17
|
+
on_success: change
|
18
|
+
on_failure: always
|
19
|
+
channels:
|
20
|
+
- "irc.freenode.org#rails-contrib"
|
21
|
+
campfire:
|
22
|
+
on_success: change
|
23
|
+
on_failure: always
|
24
|
+
rooms:
|
25
|
+
- secure: "eRCx+FMvH50pmLu0GZTF7NN+2X+CesgodYUlHvCr5EXQ0ZO/YUmeW8vAh/N8\njSrLWYpk/4P/JA63JGWsvFor/zpkTnfwzX3LWgw04GV0V3T9jsn9CD2Coiu6\nFll5u4fUCUwpfbB4RlCkjvFdQmW+F9mmbRGMCDO5CmuPHOyyPH0="
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Dimelo
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
actionpack-page_caching_multithread
|
2
|
+
=======================
|
3
|
+
|
4
|
+
Static page caching for Action Pack (removed from core in Rails 4.0).
|
5
|
+
|
6
|
+
**NOTE:** It will continue to be officially maintained until Rails 4.1.
|
7
|
+
|
8
|
+
Installation
|
9
|
+
------------
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'actionpack-page_caching_multithread'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install actionpack-page_caching_multithread
|
22
|
+
|
23
|
+
Usage
|
24
|
+
-----
|
25
|
+
|
26
|
+
Page caching is an approach to caching where the entire action output of is
|
27
|
+
stored as a HTML file that the web server can serve without going through
|
28
|
+
Action Pack. This is the fastest way to cache your content as opposed to going
|
29
|
+
dynamically through the process of generating the content. Unfortunately, this
|
30
|
+
incredible speed-up is only available to stateless pages where all visitors are
|
31
|
+
treated the same. Content management systems -- including weblogs and wikis --
|
32
|
+
have many pages that are a great fit for this approach, but account-based systems
|
33
|
+
where people log in and manipulate their own data are often less likely candidates.
|
34
|
+
|
35
|
+
First you need to set `page_cache_directory` in your configuration file:
|
36
|
+
|
37
|
+
config.action_controller.page_cache_directory = "#{Rails.root.to_s}/public/deploy"
|
38
|
+
|
39
|
+
Specifying which actions to cache is done through the `caches_page` class method:
|
40
|
+
|
41
|
+
class WeblogController < ActionController::Base
|
42
|
+
caches_page :show, :new
|
43
|
+
end
|
44
|
+
|
45
|
+
This will generate cache files such as `weblog/show/5.html` and
|
46
|
+
`weblog/new.html`, which match the URLs used that would normally trigger
|
47
|
+
dynamic page generation. Page caching works by configuring a web server to first
|
48
|
+
check for the existence of files on disk, and to serve them directly when found,
|
49
|
+
without passing the request through to Action Pack. This is much faster than
|
50
|
+
handling the full dynamic request in the usual way.
|
51
|
+
|
52
|
+
Expiration of the cache is handled by deleting the cached file, which results
|
53
|
+
in a lazy regeneration approach where the cache is not restored before another
|
54
|
+
hit is made against it. The API for doing so mimics the options from `url_for`
|
55
|
+
and friends:
|
56
|
+
|
57
|
+
class WeblogController < ActionController::Base
|
58
|
+
def update
|
59
|
+
List.update(params[:list][:id], params[:list])
|
60
|
+
expire_page action: 'show', id: params[:list][:id]
|
61
|
+
redirect_to action: 'show', id: params[:list][:id]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Additionally, you can expire caches using [Sweepers](https://github.com/rails/rails-observers#action-controller-sweeper)
|
66
|
+
that act on changes in the model to determine when a cache is supposed to be expired.
|
67
|
+
|
68
|
+
Contributing
|
69
|
+
------------
|
70
|
+
|
71
|
+
1. Fork it.
|
72
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
73
|
+
3. Commit your changes (`git commit -am 'Add some feature'`).
|
74
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
75
|
+
5. Create a new Pull Request.
|
76
|
+
|
77
|
+
Code Status
|
78
|
+
-----------
|
79
|
+
|
80
|
+
* [![Build Status](https://travis-ci.org/dimelo/actionpack-page_caching_multithread.png?branch=master)](https://travis-ci.org/dimelo/page_caching_multithread)
|
81
|
+
* [![Dependency Status](https://gemnasium.com/dimelo/actionpack-page_caching_multithread.png)](https://gemnasium.com/dimelo/actionpack-page_caching_multithread)
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'actionpack-page_caching_multithread'
|
5
|
+
gem.version = '1.2'
|
6
|
+
gem.author = 'Dimelo'
|
7
|
+
gem.email = 'contact@dimelo.com'
|
8
|
+
gem.description = 'Threadsafe Static page caching for Action Pack 4.x'
|
9
|
+
gem.summary = 'Threadsafe Static page caching for Action Pack 4.x'
|
10
|
+
gem.homepage = 'https://github.com/dimelo/actionpack-page_caching_multithread'
|
11
|
+
gem.license = 'MIT'
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($/)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.require_paths = ['lib']
|
17
|
+
|
18
|
+
gem.add_dependency 'actionpack', '>= 4.0.0', '< 5'
|
19
|
+
|
20
|
+
gem.add_development_dependency 'mocha'
|
21
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
3
|
+
|
4
|
+
module ActionController
|
5
|
+
module Caching
|
6
|
+
# Page caching is an approach to caching where the entire action output of is
|
7
|
+
# stored as a HTML file that the web server can serve without going through
|
8
|
+
# Action Pack. This is the fastest way to cache your content as opposed to going
|
9
|
+
# dynamically through the process of generating the content. Unfortunately, this
|
10
|
+
# incredible speed-up is only available to stateless pages where all visitors are
|
11
|
+
# treated the same. Content management systems -- including weblogs and wikis --
|
12
|
+
# have many pages that are a great fit for this approach, but account-based systems
|
13
|
+
# where people log in and manipulate their own data are often less likely candidates.
|
14
|
+
#
|
15
|
+
# Specifying which actions to cache is done through the +caches_page+ class method:
|
16
|
+
#
|
17
|
+
# class WeblogController < ActionController::Base
|
18
|
+
# caches_page :show, :new
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# This will generate cache files such as <tt>weblog/show/5.html</tt> and
|
22
|
+
# <tt>weblog/new.html</tt>, which match the URLs used that would normally trigger
|
23
|
+
# dynamic page generation. Page caching works by configuring a web server to first
|
24
|
+
# check for the existence of files on disk, and to serve them directly when found,
|
25
|
+
# without passing the request through to Action Pack. This is much faster than
|
26
|
+
# handling the full dynamic request in the usual way.
|
27
|
+
#
|
28
|
+
# Expiration of the cache is handled by deleting the cached file, which results
|
29
|
+
# in a lazy regeneration approach where the cache is not restored before another
|
30
|
+
# hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
|
31
|
+
#
|
32
|
+
# class WeblogController < ActionController::Base
|
33
|
+
# def update
|
34
|
+
# List.update(params[:list][:id], params[:list])
|
35
|
+
# expire_page action: 'show', id: params[:list][:id]
|
36
|
+
# redirect_to action: 'show', id: params[:list][:id]
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Additionally, you can expire caches using Sweepers that act on changes in
|
41
|
+
# the model to determine when a cache is supposed to be expired.
|
42
|
+
module Pages
|
43
|
+
extend ActiveSupport::Concern
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
# The cache directory should be the document root for the web server and is
|
47
|
+
# set using <tt>Base.page_cache_directory = "/document/root"</tt>. For Rails,
|
48
|
+
# this directory has already been set to Rails.public_path (which is usually
|
49
|
+
# set to <tt>Rails.root + "/public"</tt>). Changing this setting can be useful
|
50
|
+
# to avoid naming conflicts with files in <tt>public/</tt>, but doing so will
|
51
|
+
# likely require configuring your web server to look in the new location for
|
52
|
+
# cached files.
|
53
|
+
def page_cache_directory
|
54
|
+
thread_storage[:page_cache_directory]
|
55
|
+
end
|
56
|
+
|
57
|
+
# The compression used for gzip. If +false+ (default), the page is not compressed.
|
58
|
+
# If can be a symbol showing the ZLib compression method, for example, <tt>:best_compression</tt>
|
59
|
+
# or <tt>:best_speed</tt> or an integer configuring the compression level.
|
60
|
+
def page_cache_compression
|
61
|
+
thread_storage[:page_cache_compression]
|
62
|
+
end
|
63
|
+
|
64
|
+
def page_cache_directory=(value)
|
65
|
+
thread_storage[:page_cache_directory] = value
|
66
|
+
end
|
67
|
+
|
68
|
+
def page_cache_compression=(value)
|
69
|
+
thread_storage[:page_cache_compression] = value
|
70
|
+
end
|
71
|
+
|
72
|
+
def thread_storage
|
73
|
+
Thread.current[:multiple_page_caching] ||= {}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Expires the page that was cached with the +path+ as a key.
|
77
|
+
#
|
78
|
+
# expire_page '/lists/show'
|
79
|
+
def expire_page(path)
|
80
|
+
return unless perform_caching
|
81
|
+
path = page_cache_path(path)
|
82
|
+
|
83
|
+
instrument_page_cache :expire_page, path do
|
84
|
+
File.delete(path) if File.exist?(path)
|
85
|
+
File.delete(path + '.gz') if File.exist?(path + '.gz')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Manually cache the +content+ in the key determined by +path+.
|
90
|
+
#
|
91
|
+
# cache_page "I'm the cached content", '/lists/show'
|
92
|
+
def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
|
93
|
+
return unless perform_caching
|
94
|
+
path = page_cache_path(path, extension)
|
95
|
+
|
96
|
+
instrument_page_cache :write_page, path do
|
97
|
+
tmpfile = Tempfile.new([File.basename(path), File.extname(path)])
|
98
|
+
tmpfile.write(content)
|
99
|
+
tmpfile.close
|
100
|
+
|
101
|
+
dirname = File.dirname(path)
|
102
|
+
FileUtils.makedirs(dirname) unless File.directory?(dirname)
|
103
|
+
FileUtils.mv(tmpfile.path, path)
|
104
|
+
|
105
|
+
if gzip
|
106
|
+
Zlib::GzipWriter.open(tmpfile.path + '.gz', gzip) { |gz| gz.write(content) }
|
107
|
+
FileUtils.mv(tmpfile.path + '.gz', path + '.gz')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Caches the +actions+ using the page-caching approach that'll store
|
113
|
+
# the cache in a path within the +page_cache_directory+ that
|
114
|
+
# matches the triggering url.
|
115
|
+
#
|
116
|
+
# You can also pass a <tt>:gzip</tt> option to override the class configuration one.
|
117
|
+
#
|
118
|
+
# # cache the index action
|
119
|
+
# caches_page :index
|
120
|
+
#
|
121
|
+
# # cache the index action except for JSON requests
|
122
|
+
# caches_page :index, if: Proc.new { !request.format.json? }
|
123
|
+
#
|
124
|
+
# # don't gzip images
|
125
|
+
# caches_page :image, gzip: false
|
126
|
+
def caches_page(*actions)
|
127
|
+
return unless perform_caching
|
128
|
+
options = actions.extract_options!
|
129
|
+
|
130
|
+
gzip_level = options.fetch(:gzip, page_cache_compression)
|
131
|
+
gzip_level = case gzip_level
|
132
|
+
when Symbol
|
133
|
+
Zlib.const_get(gzip_level.upcase)
|
134
|
+
when Fixnum
|
135
|
+
gzip_level
|
136
|
+
when false
|
137
|
+
nil
|
138
|
+
else
|
139
|
+
Zlib::BEST_COMPRESSION
|
140
|
+
end
|
141
|
+
|
142
|
+
after_filter({only: actions}.merge(options)) do |c|
|
143
|
+
c.cache_page(nil, nil, gzip_level)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def page_cache_file(path, extension)
|
149
|
+
name = (path.empty? || path == '/') ? '/index' : URI.parser.unescape(path.chomp('/'))
|
150
|
+
unless (name.split('/').last || name).include? '.'
|
151
|
+
name << (extension || self.default_static_extension)
|
152
|
+
end
|
153
|
+
return name
|
154
|
+
end
|
155
|
+
|
156
|
+
def page_cache_path(path, extension = nil)
|
157
|
+
page_cache_directory.to_s + page_cache_file(path, extension)
|
158
|
+
end
|
159
|
+
|
160
|
+
def instrument_page_cache(name, path)
|
161
|
+
ActiveSupport::Notifications.instrument("#{name}.action_controller", path: path){ yield }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Expires the page that was cached with the +options+ as a key.
|
166
|
+
#
|
167
|
+
# expire_page controller: 'lists', action: 'show'
|
168
|
+
def expire_page(options = {})
|
169
|
+
return unless self.class.perform_caching
|
170
|
+
|
171
|
+
if options.is_a?(Hash)
|
172
|
+
if options[:action].is_a?(Array)
|
173
|
+
options[:action].each do |action|
|
174
|
+
self.class.expire_page(url_for(options.merge(only_path: true, action: action)))
|
175
|
+
end
|
176
|
+
else
|
177
|
+
self.class.expire_page(url_for(options.merge(only_path: true)))
|
178
|
+
end
|
179
|
+
else
|
180
|
+
self.class.expire_page(options)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Manually cache the +content+ in the key determined by +options+. If no content is provided,
|
185
|
+
# the contents of response.body is used. If no options are provided, the url of the current
|
186
|
+
# request being handled is used.
|
187
|
+
#
|
188
|
+
# cache_page "I'm the cached content", controller: 'lists', action: 'show'
|
189
|
+
def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
|
190
|
+
return unless self.class.perform_caching && caching_allowed?
|
191
|
+
|
192
|
+
path = case options
|
193
|
+
when Hash
|
194
|
+
url_for(options.merge(only_path: true, format: params[:format]))
|
195
|
+
when String
|
196
|
+
options
|
197
|
+
else
|
198
|
+
request.path
|
199
|
+
end
|
200
|
+
|
201
|
+
if (type = Mime::LOOKUP[self.content_type]) && (type_symbol = type.symbol).present?
|
202
|
+
extension = ".#{type_symbol}"
|
203
|
+
end
|
204
|
+
|
205
|
+
self.class.cache_page(content || response.body, path, extension, gzip)
|
206
|
+
end
|
207
|
+
|
208
|
+
def caching_allowed?
|
209
|
+
(request.get? || request.head?) && response.status == 200
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'actionpack/page_caching_multithread/railtie'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rails/railtie'
|
2
|
+
|
3
|
+
module ActionPack
|
4
|
+
module PageCachingMultithread
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer 'action_pack.page_caching_multithread' do
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
require 'action_controller/page_caching_multithread'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer 'action_pack.page_caching_multithread.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,22 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'action_controller'
|
4
|
+
require 'action_controller/page_caching_multithread'
|
5
|
+
|
6
|
+
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
|
7
|
+
|
8
|
+
module ActionController
|
9
|
+
class Base
|
10
|
+
include SharedTestRoutes.url_helpers
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestCase
|
14
|
+
def setup
|
15
|
+
@routes = SharedTestRoutes
|
16
|
+
|
17
|
+
@routes.draw do
|
18
|
+
get ':controller(/:action)'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
require 'abstract_unit'
|
2
|
+
|
3
|
+
CACHE_DIR = 'test_cache'
|
4
|
+
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
|
5
|
+
FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
|
6
|
+
|
7
|
+
class CachingMetalController < ActionController::Metal
|
8
|
+
abstract!
|
9
|
+
|
10
|
+
include ActionController::Caching
|
11
|
+
|
12
|
+
self.page_cache_directory = FILE_STORE_PATH
|
13
|
+
self.cache_store = :file_store, FILE_STORE_PATH
|
14
|
+
end
|
15
|
+
|
16
|
+
class PageCachingMetalTestController < CachingMetalController
|
17
|
+
caches_page :ok
|
18
|
+
|
19
|
+
def ok
|
20
|
+
self.response_body = 'ok'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PageCachingMetalTest < ActionController::TestCase
|
25
|
+
tests PageCachingMetalTestController
|
26
|
+
|
27
|
+
def setup
|
28
|
+
super
|
29
|
+
|
30
|
+
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
31
|
+
FileUtils.mkdir_p(FILE_STORE_PATH)
|
32
|
+
end
|
33
|
+
|
34
|
+
def teardown
|
35
|
+
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_should_cache_get_with_ok_status
|
39
|
+
get :ok
|
40
|
+
assert_response :ok
|
41
|
+
assert File.exist?("#{FILE_STORE_PATH}/page_caching_metal_test/ok.html"), 'get with ok status should have been cached'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
ActionController::Base.page_cache_directory = FILE_STORE_PATH
|
46
|
+
|
47
|
+
class CachingController < ActionController::Base
|
48
|
+
abstract!
|
49
|
+
|
50
|
+
self.cache_store = :file_store, FILE_STORE_PATH
|
51
|
+
end
|
52
|
+
|
53
|
+
class PageCachingTestController < CachingController
|
54
|
+
self.page_cache_compression = :best_compression
|
55
|
+
|
56
|
+
caches_page :ok, :no_content, if: Proc.new { |c| !c.request.format.json? }
|
57
|
+
caches_page :found, :not_found
|
58
|
+
caches_page :about_me
|
59
|
+
caches_page :default_gzip
|
60
|
+
caches_page :no_gzip, gzip: false
|
61
|
+
caches_page :gzip_level, gzip: :best_speed
|
62
|
+
|
63
|
+
def ok
|
64
|
+
head :ok
|
65
|
+
end
|
66
|
+
|
67
|
+
def no_content
|
68
|
+
head :no_content
|
69
|
+
end
|
70
|
+
|
71
|
+
def found
|
72
|
+
redirect_to action: 'ok'
|
73
|
+
end
|
74
|
+
|
75
|
+
def not_found
|
76
|
+
head :not_found
|
77
|
+
end
|
78
|
+
|
79
|
+
def custom_path
|
80
|
+
render text: 'Super soaker'
|
81
|
+
cache_page('Super soaker', '/index.html')
|
82
|
+
end
|
83
|
+
|
84
|
+
def default_gzip
|
85
|
+
render text: 'Text'
|
86
|
+
end
|
87
|
+
|
88
|
+
def no_gzip
|
89
|
+
render text: 'PNG'
|
90
|
+
end
|
91
|
+
|
92
|
+
def gzip_level
|
93
|
+
render text: 'Big text'
|
94
|
+
end
|
95
|
+
|
96
|
+
def expire_custom_path
|
97
|
+
expire_page('/index.html')
|
98
|
+
head :ok
|
99
|
+
end
|
100
|
+
|
101
|
+
def trailing_slash
|
102
|
+
render text: 'Sneak attack'
|
103
|
+
end
|
104
|
+
|
105
|
+
def about_me
|
106
|
+
respond_to do |format|
|
107
|
+
format.html { render text: 'I am html' }
|
108
|
+
format.xml { render text: 'I am xml' }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class PageCachingTest < ActionController::TestCase
|
114
|
+
def setup
|
115
|
+
super
|
116
|
+
|
117
|
+
@request = ActionController::TestRequest.new
|
118
|
+
@request.host = 'hostname.com'
|
119
|
+
@request.env.delete('PATH_INFO')
|
120
|
+
|
121
|
+
@controller = PageCachingTestController.new
|
122
|
+
@controller.perform_caching = true
|
123
|
+
@controller.cache_store = :file_store, FILE_STORE_PATH
|
124
|
+
|
125
|
+
@response = ActionController::TestResponse.new
|
126
|
+
|
127
|
+
@params = { controller: 'posts', action: 'index', only_path: true }
|
128
|
+
|
129
|
+
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
130
|
+
FileUtils.mkdir_p(FILE_STORE_PATH)
|
131
|
+
end
|
132
|
+
|
133
|
+
def teardown
|
134
|
+
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
135
|
+
@controller.perform_caching = false
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
|
139
|
+
with_routing do |set|
|
140
|
+
set.draw do
|
141
|
+
get 'posts.:format', to: 'posts#index', as: :formatted_posts
|
142
|
+
get '/', to: 'posts#index', as: :main
|
143
|
+
end
|
144
|
+
@params[:format] = 'rss'
|
145
|
+
assert_equal '/posts.rss', @routes.url_for(@params)
|
146
|
+
@params[:format] = nil
|
147
|
+
assert_equal '/', @routes.url_for(@params)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_should_cache_head_with_ok_status
|
152
|
+
head :ok
|
153
|
+
assert_response :ok
|
154
|
+
assert_page_cached :ok, 'head with ok status should have been cached'
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_should_cache_get_with_ok_status
|
158
|
+
get :ok
|
159
|
+
assert_response :ok
|
160
|
+
assert_page_cached :ok, 'get with ok status should have been cached'
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_should_cache_with_custom_path
|
164
|
+
get :custom_path
|
165
|
+
assert File.exist?("#{FILE_STORE_PATH}/index.html")
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_should_expire_cache_with_custom_path
|
169
|
+
get :custom_path
|
170
|
+
assert File.exist?("#{FILE_STORE_PATH}/index.html")
|
171
|
+
|
172
|
+
get :expire_custom_path
|
173
|
+
assert !File.exist?("#{FILE_STORE_PATH}/index.html")
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_should_gzip_cache
|
177
|
+
get :custom_path
|
178
|
+
assert File.exist?("#{FILE_STORE_PATH}/index.html.gz")
|
179
|
+
|
180
|
+
get :expire_custom_path
|
181
|
+
assert !File.exist?("#{FILE_STORE_PATH}/index.html.gz")
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_should_allow_to_disable_gzip
|
185
|
+
get :no_gzip
|
186
|
+
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html")
|
187
|
+
assert !File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html.gz")
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_should_use_config_gzip_by_default
|
191
|
+
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION)
|
192
|
+
get :default_gzip
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_should_set_gzip_level
|
196
|
+
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED)
|
197
|
+
get :gzip_level
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_should_cache_without_trailing_slash_on_url
|
201
|
+
@controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash'
|
202
|
+
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_should_obey_http_accept_attribute
|
206
|
+
@request.env['HTTP_ACCEPT'] = 'text/xml'
|
207
|
+
get :about_me
|
208
|
+
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/about_me.xml")
|
209
|
+
assert_equal 'I am xml', @response.body
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash
|
213
|
+
@controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/'
|
214
|
+
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_should_cache_ok_at_custom_path
|
218
|
+
@request.env['PATH_INFO'] = '/index.html'
|
219
|
+
get :ok
|
220
|
+
assert_response :ok
|
221
|
+
assert File.exist?("#{FILE_STORE_PATH}/index.html")
|
222
|
+
end
|
223
|
+
|
224
|
+
[:ok, :no_content, :found, :not_found].each do |status|
|
225
|
+
[:get, :post, :patch, :put, :delete].each do |method|
|
226
|
+
unless method == :get && status == :ok
|
227
|
+
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
|
228
|
+
send(method, status)
|
229
|
+
assert_response status
|
230
|
+
assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_page_caching_conditional_options
|
237
|
+
get :ok, format: 'json'
|
238
|
+
assert_page_not_cached :ok
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_page_caching_directory_set_as_pathname
|
242
|
+
begin
|
243
|
+
ActionController::Base.page_cache_directory = Pathname.new(FILE_STORE_PATH)
|
244
|
+
get :ok
|
245
|
+
assert_response :ok
|
246
|
+
assert_page_cached :ok
|
247
|
+
ensure
|
248
|
+
ActionController::Base.page_cache_directory = FILE_STORE_PATH
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
def assert_page_cached(action, message = "#{action} should have been cached")
|
255
|
+
assert page_cached?(action), message
|
256
|
+
end
|
257
|
+
|
258
|
+
def assert_page_not_cached(action, message = "#{action} shouldn't have been cached")
|
259
|
+
assert !page_cached?(action), message
|
260
|
+
end
|
261
|
+
|
262
|
+
def page_cached?(action)
|
263
|
+
File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html"
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'abstract_unit'
|
2
|
+
require 'active_support/log_subscriber/test_helper'
|
3
|
+
require 'action_controller/log_subscriber'
|
4
|
+
|
5
|
+
module Another
|
6
|
+
class LogSubscribersController < ActionController::Base
|
7
|
+
abstract!
|
8
|
+
|
9
|
+
self.perform_caching = true
|
10
|
+
|
11
|
+
def with_page_cache
|
12
|
+
cache_page('Super soaker', '/index.html')
|
13
|
+
render nothing: true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ACLogSubscriberTest < ActionController::TestCase
|
19
|
+
tests Another::LogSubscribersController
|
20
|
+
include ActiveSupport::LogSubscriber::TestHelper
|
21
|
+
|
22
|
+
def setup
|
23
|
+
super
|
24
|
+
|
25
|
+
@routes = SharedTestRoutes
|
26
|
+
@routes.draw do
|
27
|
+
get ':controller(/:action)'
|
28
|
+
end
|
29
|
+
|
30
|
+
@cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__))
|
31
|
+
ActionController::Base.page_cache_directory = @cache_path
|
32
|
+
@controller.cache_store = :file_store, @cache_path
|
33
|
+
ActionController::LogSubscriber.attach_to :action_controller
|
34
|
+
end
|
35
|
+
|
36
|
+
def teardown
|
37
|
+
ActiveSupport::LogSubscriber.log_subscribers.clear
|
38
|
+
FileUtils.rm_rf(@cache_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_logger(logger)
|
42
|
+
ActionController::Base.logger = logger
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_with_page_cache
|
46
|
+
get :with_page_cache
|
47
|
+
wait
|
48
|
+
|
49
|
+
logs = @logger.logged(:info)
|
50
|
+
assert_equal 3, logs.size
|
51
|
+
assert_match(/Write page/, logs[1])
|
52
|
+
assert_match(/\/index\.html/, logs[1])
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: actionpack-page_caching_multithread
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dimelo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: actionpack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 4.0.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: mocha
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
description: Threadsafe Static page caching for Action Pack 4.x
|
48
|
+
email: contact@dimelo.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- ".gitignore"
|
54
|
+
- ".travis.yml"
|
55
|
+
- CHANGELOG.md
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE.txt
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- actionpack-page_caching_multithread.gemspec
|
61
|
+
- gemfiles/Gemfile-4-0-stable
|
62
|
+
- gemfiles/Gemfile-edge
|
63
|
+
- lib/action_controller/caching/pages.rb
|
64
|
+
- lib/action_controller/page_caching_multithread.rb
|
65
|
+
- lib/actionpack/page_caching_multithread.rb
|
66
|
+
- lib/actionpack/page_caching_multithread/railtie.rb
|
67
|
+
- test/abstract_unit.rb
|
68
|
+
- test/caching_test.rb
|
69
|
+
- test/log_subscriber_test.rb
|
70
|
+
homepage: https://github.com/dimelo/actionpack-page_caching_multithread
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.2.2
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: Threadsafe Static page caching for Action Pack 4.x
|
94
|
+
test_files:
|
95
|
+
- test/abstract_unit.rb
|
96
|
+
- test/caching_test.rb
|
97
|
+
- test/log_subscriber_test.rb
|