Skip to content

Commit e035109

Browse files
committed
Enable brotli compression in the caches_page
1 parent 42a2d81 commit e035109

File tree

9 files changed

+146
-31
lines changed

9 files changed

+146
-31
lines changed

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ cache:
55
bundler: true
66

77
before_install:
8-
- gem install bundler
8+
- "travis_retry gem update --system 2.7.9"
9+
- "travis_retry gem install bundler -v '1.17.3'"
10+
- "travis_retry gem install brotli"
911

1012
rvm:
1113
- 2.4.6

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.2.4 Unreleased
2+
3+
* Allow to use brotli compression
4+
5+
*Anton Kolodii*
6+
17
## 1.1.1 (September 25, 2018)
28

39
* Fixes handling of several forward slashes as root path.

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,37 @@ Additionally, you can expire caches using
200200
that act on changes in the model to determine when a cache is supposed to be
201201
expired.
202202

203+
#### Compression Algorithm
204+
205+
To use brotli manually install gem 'brotli' inside Gemfile
206+
``` ruby
207+
gem 'brotli'
208+
```
209+
210+
To use both gzip and brotli, or just skip compressions, because this is default value
211+
212+
``` ruby
213+
class WeblogController < ActionController::Base
214+
caches_page :show, :new, compressions: {gzip: Zlib::BEST_COMPRESSION, brotli: 9}
215+
end
216+
```
217+
218+
To use only gzip
219+
220+
``` ruby
221+
class WeblogController < ActionController::Base
222+
caches_page :show, :new, compressions: {gzip: Zlib::BEST_COMPRESSION}
223+
end
224+
```
225+
226+
To use only brotli
227+
228+
``` ruby
229+
class WeblogController < ActionController::Base
230+
caches_page :show, :new, compressions: {brotli: 9}
231+
end
232+
```
233+
203234
Contributing
204235
------------
205236

actionpack-page_caching.gemspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@ Gem::Specification.new do |gem|
1717

1818
gem.add_dependency "actionpack", ">= 5.0.0"
1919

20+
gem.add_development_dependency 'brotli', '>= 0.2.0'
2021
gem.add_development_dependency "mocha"
22+
23+
gem.post_install_message = "To use brotli compression you have to manually add gem 'brotli' to Gemfile"
2124
end

lib/action_controller/caching/pages.rb

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
# frozen_string_literal: true
2+
13
require "fileutils"
24
require "uri"
35
require "active_support/core_ext/class/attribute_accessors"
46
require "active_support/core_ext/string/strip"
57

68
module ActionController
79
module Caching
10+
COMPRESSIONS_DEFAULTS = {gzip: 9, brotli: 9} # ::Zlib::BEST_COMPRESSION
11+
812
# Page caching is an approach to caching where the entire action output of is
913
# stored as a HTML file that the web server can serve without going through
1014
# Action Pack. This is the fastest way to cache your content as opposed to going
@@ -60,6 +64,9 @@ module Pages
6064
# or <tt>:best_speed</tt> or an integer configuring the compression level.
6165
class_attribute :page_cache_compression
6266
self.page_cache_compression ||= false
67+
68+
class_attribute :page_cache_compressions
69+
self.page_cache_compressions ||= COMPRESSIONS_DEFAULTS
6370
end
6471

6572
class PageCache #:nodoc:
@@ -75,9 +82,9 @@ def expire(path)
7582
end
7683
end
7784

78-
def cache(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
85+
def cache(content, path, extension = nil, compressions: COMPRESSIONS_DEFAULTS)
7986
instrument :write_page, path do
80-
write(content, cache_path(path, extension), gzip)
87+
write(content, cache_path(path, extension), compressions: compressions)
8188
end
8289
end
8390

@@ -168,16 +175,22 @@ def delete(path)
168175

169176
File.delete(path) if File.exist?(path)
170177
File.delete(path + ".gz") if File.exist?(path + ".gz")
178+
File.delete(path + ".br") if File.exist?(path + ".br")
171179
end
172180

173-
def write(content, path, gzip)
181+
def write(content, path, compressions:)
174182
return unless path
175183

176184
FileUtils.makedirs(File.dirname(path))
177185
File.open(path, "wb+") { |f| f.write(content) }
178186

179-
if gzip
180-
Zlib::GzipWriter.open(path + ".gz", gzip) { |f| f.write(content) }
187+
if compressions[:gzip]
188+
Zlib::GzipWriter.open(path + ".gz", compressions[:gzip]) { |f| f.write(content) }
189+
end
190+
191+
if compressions[:brotli]
192+
brotli = ::Brotli.deflate(content, mode: :text, quality: compressions[:brotli])
193+
File.atomic_write(path + ".br") { |f| f.write(brotli) }
181194
end
182195
end
183196

@@ -199,9 +212,9 @@ def expire_page(path)
199212
# Manually cache the +content+ in the key determined by +path+.
200213
#
201214
# cache_page "I'm the cached content", "/lists/show"
202-
def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
215+
def cache_page(content, path, extension = nil, compressions: COMPRESSIONS_DEFAULTS)
203216
if perform_caching
204-
page_cache.cache(content, path, extension, gzip)
217+
page_cache.cache(content, path, extension, compressions: compressions)
205218
end
206219
end
207220

@@ -218,26 +231,50 @@ def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
218231
# caches_page :index, if: Proc.new { !request.format.json? }
219232
#
220233
# # don't gzip images
221-
# caches_page :image, gzip: false
234+
# caches_page :image, compressions: false
222235
def caches_page(*actions)
223236
if perform_caching
224237
options = actions.extract_options!
225238

226-
gzip_level = options.fetch(:gzip, page_cache_compression)
227-
gzip_level = \
228-
case gzip_level
229-
when Symbol
230-
Zlib.const_get(gzip_level.upcase)
231-
when Integer
232-
gzip_level
239+
compressions = options.fetch(:compressions, page_cache_compressions)
240+
241+
compressions =
242+
case compressions
233243
when false
234-
nil
244+
{}
235245
else
236-
Zlib::BEST_COMPRESSION
246+
compressions
237247
end
238248

249+
if options.key?(:gzip)
250+
ActiveSupport::Deprecation.warn(
251+
"actionpack-page-caching now support brotli compression.\n
252+
Using gzip directly is deprecated. instead of\n caches_page :index, gzip: Zlib::BEST_COMPRESSION \n
253+
please use\n caches_page :index, compressions: {gzip: Zlib::BEST_COMPRESSION, brotli: 9}"
254+
)
255+
256+
gzip_level = options.fetch(:gzip, page_cache_compression)
257+
gzip_level = \
258+
case gzip_level
259+
when Symbol
260+
Zlib.const_get(gzip_level.upcase)
261+
when Integer
262+
gzip_level
263+
when false
264+
nil
265+
else
266+
Zlib::BEST_COMPRESSION
267+
end
268+
269+
compressions[:gzip] = gzip_level
270+
end
271+
272+
if compressions.key?(:brotli)
273+
require 'brotli'
274+
end
275+
239276
after_action({ only: actions }.merge(options)) do |c|
240-
c.cache_page(nil, nil, gzip_level)
277+
c.cache_page(nil, nil, compressions: compressions)
241278
end
242279
end
243280
end
@@ -272,7 +309,7 @@ def expire_page(options = {})
272309
# request being handled is used.
273310
#
274311
# cache_page "I'm the cached content", controller: "lists", action: "show"
275-
def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
312+
def cache_page(content = nil, options = nil, compressions: COMPRESSIONS_DEFAULTS)
276313
if perform_caching? && caching_allowed?
277314
path = \
278315
case options
@@ -294,7 +331,7 @@ def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
294331
extension = ".#{type_symbol}"
295332
end
296333

297-
page_cache.cache(content || response.body, path, extension, gzip)
334+
page_cache.cache(content || response.body, path, extension, compressions: compressions)
298335
end
299336
end
300337

lib/action_controller/page_caching.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
require "action_controller/caching/pages"
24

35
module ActionController

lib/actionpack/page_caching.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
# frozen_string_literal: true
2+
13
require "actionpack/page_caching/railtie"

lib/actionpack/page_caching/railtie.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
require "rails/railtie"
24

35
module ActionPack

test/caching_test.rb

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,11 @@ class PageCachingTestController < CachingController
119119
caches_page :ok, :no_content, if: Proc.new { |c| !c.request.format.json? }
120120
caches_page :found, :not_found
121121
caches_page :about_me
122-
caches_page :default_gzip
123-
caches_page :no_gzip, gzip: false
124-
caches_page :gzip_level, gzip: :best_speed
122+
caches_page :default_compressions
123+
caches_page :no_gzip, compressions: {gzip: false}
124+
caches_page :gzip_level, compressions: {gzip: 1}
125+
caches_page :no_brotli, compressions: {brotli: false}
126+
caches_page :brotli_level, compressions: {brotli: 1}
125127

126128
def ok
127129
render html: "ok"
@@ -144,8 +146,8 @@ def custom_path
144146
cache_page(nil, "/index.html")
145147
end
146148

147-
def default_gzip
148-
render html: "default_gzip"
149+
def default_compressions
150+
render html: "default_compressions"
149151
end
150152

151153
def no_gzip
@@ -156,6 +158,14 @@ def gzip_level
156158
render html: "gzip_level"
157159
end
158160

161+
def no_brotli
162+
render html: "no_brotli"
163+
end
164+
165+
def brotli_level
166+
render html: "brotli_level"
167+
end
168+
159169
def expire_custom_path
160170
expire_page("/index.html")
161171
head :ok
@@ -261,6 +271,7 @@ def test_should_gzip_cache
261271

262272
get :expire_custom_path
263273
assert_page_not_cached :index, controller: ".", format: "html.gz"
274+
assert_page_not_cached :index, controller: ".", format: "html.br"
264275
end
265276

266277
def test_should_allow_to_disable_gzip
@@ -273,24 +284,43 @@ def test_should_allow_to_disable_gzip
273284
assert_page_not_cached :no_gzip, format: "html.gz"
274285
end
275286

276-
def test_should_use_config_gzip_by_default
287+
def test_should_allow_to_disable_brotli
288+
draw do
289+
get "/page_caching_test/no_brotli", to: "page_caching_test#no_brotli"
290+
end
291+
292+
get :no_brotli
293+
assert_page_cached :no_brotli, format: "html"
294+
assert_page_not_cached :no_brotli, format: "html.br"
295+
end
296+
297+
def test_should_use_config_compressions_by_default
277298
draw do
278-
get "/page_caching_test/default_gzip", to: "page_caching_test#default_gzip"
299+
get "/page_caching_test/default_compressions", to: "page_caching_test#default_compressions"
279300
end
280301

281-
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION)
282-
get :default_gzip
302+
@controller.expects(:cache_page).with(nil, nil, compressions: {gzip: Zlib::BEST_COMPRESSION, brotli: 9})
303+
get :default_compressions
283304
end
284305

285306
def test_should_set_gzip_level
286307
draw do
287308
get "/page_caching_test/gzip_level", to: "page_caching_test#gzip_level"
288309
end
289310

290-
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED)
311+
@controller.expects(:cache_page).with(nil, nil, compressions: {gzip: Zlib::BEST_SPEED})
291312
get :gzip_level
292313
end
293314

315+
def test_should_set_brotli_level
316+
draw do
317+
get "/page_caching_test/brotli_level", to: "page_caching_test#brotli_level"
318+
end
319+
320+
@controller.expects(:cache_page).with(nil, nil, compressions: {brotli: 1})
321+
get :brotli_level
322+
end
323+
294324
def test_should_cache_without_trailing_slash_on_url
295325
@controller.class.cache_page "cached content", "/page_caching_test/trailing_slash"
296326
assert_page_cached :trailing_slash, content: "cached content"

0 commit comments

Comments
 (0)