Module: ResponseBank

Defined in:
lib/response_bank.rb,
lib/response_bank/railtie.rb,
lib/response_bank/version.rb,
lib/response_bank/controller.rb,
lib/response_bank/middleware.rb,
lib/response_bank/cache_policy.rb,
lib/response_bank/cache_writer.rb,
lib/response_bank/deferred_store.rb,
lib/response_bank/model_extensions.rb,
lib/response_bank/brotli_splice_slot.rb,
lib/response_bank/brotli_splice_injector.rb,
lib/response_bank/response_cache_handler.rb

Defined Under Namespace

Modules: BrotliSpliceInjector, BrotliSpliceSlot, Controller, ModelExtensions Classes: DeferredStore, Middleware, Railtie, ResponseCacheHandler

Constant Summary collapse

VERSION =
"1.4.0"
CACHEABLE_HEADERS =
["Location", "Content-Type", "ETag", "Content-Encoding", "Last-Modified", "Cache-Control", "Expires", "Link", "Surrogate-Keys", "Cache-Tags", "Speculation-Rules"].freeze
CACHEABLE_STATUSES =
[200, 404, 301].freeze
DEFAULT_BROTLI_COMPRESSION_LEVEL =
7
DEFAULT_COMPRESSION_LEVEL =
-> (env, _headers) {
  case env['response_bank.server_cache_encoding']
  when 'br'
    DEFAULT_BROTLI_COMPRESSION_LEVEL
  when 'gzip'
    Zlib::BEST_COMPRESSION
  end
}

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.cache_storeObject

Returns the value of attribute cache_store.



18
19
20
# File 'lib/response_bank.rb', line 18

def cache_store
  @cache_store
end

.compression_level=(value) ⇒ Object (writeonly)

Sets the attribute compression_level

Parameters:

  • value

    the value to set the attribute compression_level to.



19
20
21
# File 'lib/response_bank.rb', line 19

def compression_level=(value)
  @compression_level = value
end

.logger=(value) ⇒ Object (writeonly)

Sets the attribute logger

Parameters:

  • value

    the value to set the attribute logger to.



19
20
21
# File 'lib/response_bank.rb', line 19

def logger=(value)
  @logger = value
end

Class Method Details

.acquire_lock(_cache_key) ⇒ Object

Raises:

  • (NotImplementedError)


44
45
46
# File 'lib/response_bank.rb', line 44

def acquire_lock(_cache_key)
  raise NotImplementedError, "Override ResponseBank.acquire_lock in an initializer."
end

.cache_key_for(data) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/response_bank.rb', line 112

def cache_key_for(data)
  case data
  when Hash
    return data.inspect unless data.key?(:key)

    key = hash_value_str(data[:key])

    key = %{#{data[:key_schema_version]}:#{key}} if data[:key_schema_version]

    key = %{#{key}:#{hash_value_str(data[:version])}} if data[:version]

    # add the encoding to only the cache key but don't expose this detail in the entity_tag
    key = %{#{key}:#{hash_value_str(data[:encoding])}} if data[:encoding]

    key
  when Array
    data.inspect
  when Time, DateTime
    data.to_i
  when Date
    data.to_s # Date#to_i does not support timezones, using iso8601 instead
  when true, false, Integer, Symbol, String
    data.inspect
  else
    data.to_s.inspect
  end
end

.check_encoding(env, default_encoding = 'br') ⇒ Object



140
141
142
143
144
145
146
147
148
149
# File 'lib/response_bank.rb', line 140

def check_encoding(env, default_encoding = 'br')
  if env['HTTP_ACCEPT_ENCODING'].to_s.include?('br')
    'br'
  elsif env['HTTP_ACCEPT_ENCODING'].to_s.include?('gzip')
    'gzip'
  else
    # No encoding requested from client, but we still need to cache the page in server cache
    default_encoding
  end
end

.compress(content, encoding = "br", compression_level: nil) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/response_bank.rb', line 77

def compress(content, encoding = "br", compression_level: nil)
  case encoding
  when 'gzip'
    attempts = 0

    begin
      Zlib.gzip(content, level: compression_level || Zlib::BEST_COMPRESSION)
    rescue Zlib::BufError
      # We get sporadic Zlib::BufError, so we retry once (https://github.com/ruby/zlib/issues/49)
      attempts += 1

      if attempts <= 1
        retry
      else
        raise
      end
    end
  when 'br'
    Brotli.deflate(content, mode: :text, quality: compression_level || DEFAULT_BROTLI_COMPRESSION_LEVEL)
  else
    raise ArgumentError, "Unsupported encoding: #{encoding}"
  end
end

.compression_level_for_request(env, headers) ⇒ Object



32
33
34
35
36
37
38
# File 'lib/response_bank.rb', line 32

def compression_level_for_request(env, headers)
  if @compression_level
    return @compression_level.respond_to?(:call) ? @compression_level.call(env, headers) : @compression_level
  end

  DEFAULT_COMPRESSION_LEVEL.call(env, headers)
end

.decompress(content, encoding = "br") ⇒ Object



101
102
103
104
105
106
107
108
109
110
# File 'lib/response_bank.rb', line 101

def decompress(content, encoding = "br")
  case encoding
  when 'gzip'
    Zlib.gunzip(content)
  when 'br'
    Brotli.inflate(content)
  else
    raise ArgumentError, "Unsupported encoding: #{encoding}"
  end
end

.defer_store(env, timestamp: Time.now.to_i) ⇒ Object

Starts a one-shot deferred cache fill for the current ResponseBank miss. Complete it only after the intended shared response was generated and written successfully. Abort failed, disconnected or truncated responses.



51
52
53
# File 'lib/response_bank.rb', line 51

def defer_store(env, timestamp: Time.now.to_i)
  DeferredStore.create(env, timestamp: timestamp)
end

.log(message) ⇒ Object



40
41
42
# File 'lib/response_bank.rb', line 40

def log(message)
  @logger.info("[ResponseBank] #{message}")
end

.measureObject



71
72
73
74
75
# File 'lib/response_bank.rb', line 71

def measure
  Benchmark.realtime do
    yield
  end * 1000 # milliseconds
end

.read_from_backing_cache_store(_env, cache_key, backing_cache_store: cache_store) ⇒ Object



67
68
69
# File 'lib/response_bank.rb', line 67

def read_from_backing_cache_store(_env, cache_key, backing_cache_store: cache_store)
  backing_cache_store.read(cache_key, raw: true)
end

.release_lock(_cache_key) ⇒ Object

Override when deferred fills must release an application-managed lock.



56
57
# File 'lib/response_bank.rb', line 56

def release_lock(_cache_key)
end

.write_to_backing_cache_store(_env, key, payload, expires_in: nil) ⇒ Object



63
64
65
# File 'lib/response_bank.rb', line 63

def write_to_backing_cache_store(_env, key, payload, expires_in: nil)
  cache_store.write(key, payload, raw: true, expires_in: expires_in)
end

.write_to_cache(_key) ⇒ Object



59
60
61
# File 'lib/response_bank.rb', line 59

def write_to_cache(_key)
  yield
end