1+ # frozen_string_literal: true
2+
13require "fileutils"
24require "uri"
35require "active_support/core_ext/class/attribute_accessors"
46require "active_support/core_ext/string/strip"
7+ require 'brotli'
58
69module ActionController
710 module Caching
11+ COMPRESSIONS_DEFAULTS = { gzip : 9 , brotli : 9 } # ::Zlib::BEST_COMPRESSION
12+
813 # Page caching is an approach to caching where the entire action output of is
914 # stored as a HTML file that the web server can serve without going through
1015 # Action Pack. This is the fastest way to cache your content as opposed to going
@@ -60,6 +65,9 @@ module Pages
6065 # or <tt>:best_speed</tt> or an integer configuring the compression level.
6166 class_attribute :page_cache_compression
6267 self . page_cache_compression ||= false
68+
69+ class_attribute :page_cache_compressions
70+ self . page_cache_compressions ||= COMPRESSIONS_DEFAULTS
6371 end
6472
6573 class PageCache #:nodoc:
@@ -75,9 +83,9 @@ def expire(path)
7583 end
7684 end
7785
78- def cache ( content , path , extension = nil , gzip = Zlib :: BEST_COMPRESSION )
86+ def cache ( content , path , extension = nil , compressions : COMPRESSIONS_DEFAULTS )
7987 instrument :write_page , path do
80- write ( content , cache_path ( path , extension ) , gzip )
88+ write ( content , cache_path ( path , extension ) , compressions : compressions )
8189 end
8290 end
8391
@@ -161,14 +169,20 @@ def cache_path(path, extension = nil)
161169 def delete ( path )
162170 File . delete ( path ) if File . exist? ( path )
163171 File . delete ( path + ".gz" ) if File . exist? ( path + ".gz" )
172+ File . delete ( path + ".br" ) if File . exist? ( path + ".br" )
164173 end
165174
166- def write ( content , path , gzip )
175+ def write ( content , path , compressions : )
167176 FileUtils . makedirs ( File . dirname ( path ) )
168177 File . open ( path , "wb+" ) { |f | f . write ( content ) }
169178
170- if gzip
171- Zlib ::GzipWriter . open ( path + ".gz" , gzip ) { |f | f . write ( content ) }
179+ if compressions [ :gzip ]
180+ Zlib ::GzipWriter . open ( path + ".gz" , compressions [ :gzip ] ) { |f | f . write ( content ) }
181+ end
182+
183+ if compressions [ :brotli ]
184+ brotli = ::Brotli . deflate ( content , mode : :text , quality : compressions [ :brotli ] )
185+ File . atomic_write ( path + ".br" ) { |f | f . write ( brotli ) }
172186 end
173187 end
174188
@@ -190,9 +204,9 @@ def expire_page(path)
190204 # Manually cache the +content+ in the key determined by +path+.
191205 #
192206 # cache_page "I'm the cached content", "/lists/show"
193- def cache_page ( content , path , extension = nil , gzip = Zlib :: BEST_COMPRESSION )
207+ def cache_page ( content , path , extension = nil , compressions : COMPRESSIONS_DEFAULTS )
194208 if perform_caching
195- page_cache . cache ( content , path , extension , gzip )
209+ page_cache . cache ( content , path , extension , compressions : compressions )
196210 end
197211 end
198212
@@ -214,21 +228,33 @@ def caches_page(*actions)
214228 if perform_caching
215229 options = actions . extract_options!
216230
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
231+ compressions = options . fetch ( :compressions , page_cache_compressions )
232+
233+ if options . key? ( :gzip )
234+ ActiveSupport ::Deprecation . warn (
235+ "actionpack-page-caching now support brotli compression.\n
236+ Using gzip directly is deprecated. instead of\n caches_page :index, gzip: Zlib::BEST_COMPRESSION \n
237+ please use\n caches_page :index, compressions: {gzip: Zlib::BEST_COMPRESSION, brotli: 9}"
238+ )
239+
240+ gzip_level = options . fetch ( :gzip , page_cache_compression )
241+ gzip_level = \
242+ case gzip_level
243+ when Symbol
244+ Zlib . const_get ( gzip_level . upcase )
245+ when Integer
246+ gzip_level
247+ when false
248+ nil
249+ else
250+ Zlib ::BEST_COMPRESSION
251+ end
252+
253+ compressions [ :gzip ] = gzip_level
254+ end
229255
230256 after_action ( { only : actions } . merge ( options ) ) do |c |
231- c . cache_page ( nil , nil , gzip_level )
257+ c . cache_page ( nil , nil , compressions : compressions )
232258 end
233259 end
234260 end
@@ -263,7 +289,7 @@ def expire_page(options = {})
263289 # request being handled is used.
264290 #
265291 # cache_page "I'm the cached content", controller: "lists", action: "show"
266- def cache_page ( content = nil , options = nil , gzip = Zlib :: BEST_COMPRESSION )
292+ def cache_page ( content = nil , options = nil , compressions : COMPRESSIONS_DEFAULTS )
267293 if perform_caching? && caching_allowed?
268294 path = \
269295 case options
@@ -285,7 +311,7 @@ def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
285311 extension = ".#{ type_symbol } "
286312 end
287313
288- page_cache . cache ( content || response . body , path , extension , gzip )
314+ page_cache . cache ( content || response . body , path , extension , compressions : compressions )
289315 end
290316 end
291317
0 commit comments