1
+ # frozen_string_literal: true
2
+
1
3
require "fileutils"
2
4
require "uri"
3
5
require "active_support/core_ext/class/attribute_accessors"
4
6
require "active_support/core_ext/string/strip"
5
7
6
8
module ActionController
7
9
module Caching
10
+ COMPRESSIONS_DEFAULTS = { gzip : 9 , brotli : 9 } # ::Zlib::BEST_COMPRESSION
11
+
8
12
# Page caching is an approach to caching where the entire action output of is
9
13
# stored as a HTML file that the web server can serve without going through
10
14
# Action Pack. This is the fastest way to cache your content as opposed to going
@@ -60,6 +64,9 @@ module Pages
60
64
# or <tt>:best_speed</tt> or an integer configuring the compression level.
61
65
class_attribute :page_cache_compression
62
66
self . page_cache_compression ||= false
67
+
68
+ class_attribute :page_cache_compressions
69
+ self . page_cache_compressions ||= COMPRESSIONS_DEFAULTS
63
70
end
64
71
65
72
class PageCache #:nodoc:
@@ -75,9 +82,9 @@ def expire(path)
75
82
end
76
83
end
77
84
78
- def cache ( content , path , extension = nil , gzip = Zlib :: BEST_COMPRESSION )
85
+ def cache ( content , path , extension = nil , compressions : COMPRESSIONS_DEFAULTS )
79
86
instrument :write_page , path do
80
- write ( content , cache_path ( path , extension ) , gzip )
87
+ write ( content , cache_path ( path , extension ) , compressions : compressions )
81
88
end
82
89
end
83
90
@@ -168,16 +175,22 @@ def delete(path)
168
175
169
176
File . delete ( path ) if File . exist? ( path )
170
177
File . delete ( path + ".gz" ) if File . exist? ( path + ".gz" )
178
+ File . delete ( path + ".br" ) if File . exist? ( path + ".br" )
171
179
end
172
180
173
- def write ( content , path , gzip )
181
+ def write ( content , path , compressions : )
174
182
return unless path
175
183
176
184
FileUtils . makedirs ( File . dirname ( path ) )
177
185
File . open ( path , "wb+" ) { |f | f . write ( content ) }
178
186
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 ) }
181
194
end
182
195
end
183
196
@@ -199,9 +212,9 @@ def expire_page(path)
199
212
# Manually cache the +content+ in the key determined by +path+.
200
213
#
201
214
# 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 )
203
216
if perform_caching
204
- page_cache . cache ( content , path , extension , gzip )
217
+ page_cache . cache ( content , path , extension , compressions : compressions )
205
218
end
206
219
end
207
220
@@ -218,26 +231,50 @@ def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
218
231
# caches_page :index, if: Proc.new { !request.format.json? }
219
232
#
220
233
# # don't gzip images
221
- # caches_page :image, gzip : false
234
+ # caches_page :image, compressions : false
222
235
def caches_page ( *actions )
223
236
if perform_caching
224
237
options = actions . extract_options!
225
238
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
233
243
when false
234
- nil
244
+ { }
235
245
else
236
- Zlib :: BEST_COMPRESSION
246
+ compressions
237
247
end
238
248
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
+
239
276
after_action ( { only : actions } . merge ( options ) ) do |c |
240
- c . cache_page ( nil , nil , gzip_level )
277
+ c . cache_page ( nil , nil , compressions : compressions )
241
278
end
242
279
end
243
280
end
@@ -272,7 +309,7 @@ def expire_page(options = {})
272
309
# request being handled is used.
273
310
#
274
311
# 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 )
276
313
if perform_caching? && caching_allowed?
277
314
path = \
278
315
case options
@@ -294,7 +331,7 @@ def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
294
331
extension = ".#{ type_symbol } "
295
332
end
296
333
297
- page_cache . cache ( content || response . body , path , extension , gzip )
334
+ page_cache . cache ( content || response . body , path , extension , compressions : compressions )
298
335
end
299
336
end
300
337
0 commit comments