Class: ShafClient::Middleware::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/shaf_client/middleware/cache.rb

Defined Under Namespace

Modules: Control Classes: Response

Constant Summary collapse

DEFAULT_THRESHOLD =
10_000

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, **options) ⇒ Cache

Returns a new instance of Cache.



119
120
121
122
# File 'lib/shaf_client/middleware/cache.rb', line 119

def initialize(app, **options)
  @app = app
  @options = options
end

Class Attribute Details

.thresholdObject



78
79
80
# File 'lib/shaf_client/middleware/cache.rb', line 78

def threshold
  @threshold ||= DEFAULT_THRESHOLD
end

Class Method Details

.clearObject



39
40
41
42
43
# File 'lib/shaf_client/middleware/cache.rb', line 39

def clear
  mutex.synchronize do
    @cache = {}
  end
end

.clear_staleObject



45
46
47
48
49
# File 'lib/shaf_client/middleware/cache.rb', line 45

def clear_stale
  mutex.synchronize do
    cache.delete_if { |_key, entry| expired? entry }
  end
end

.get(key:, check_expiration: true) ⇒ Object



51
52
53
54
55
56
57
58
# File 'lib/shaf_client/middleware/cache.rb', line 51

def get(key:, check_expiration: true)
  entry = nil
  mutex.synchronize do
    entry = cache[key.to_sym].dup
  end
  return entry[:payload] if valid?(entry, check_expiration)
  yield if block_given?
end

.get_etag(key:) ⇒ Object



60
61
62
63
64
# File 'lib/shaf_client/middleware/cache.rb', line 60

def get_etag(key:)
  mutex.synchronize do
    cache.dig(key.to_sym, :etag)
  end
end

.inc_request_countObject



27
28
29
30
31
32
33
# File 'lib/shaf_client/middleware/cache.rb', line 27

def inc_request_count
  @request_count ||= 0
  @request_count += 1
  return if (@request_count % 500 != 0)
  clear_stale
  check_threshold
end

.sizeObject



35
36
37
# File 'lib/shaf_client/middleware/cache.rb', line 35

def size
  cache.size
end

.store(key:, payload:, etag: nil, expire_at: nil) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/shaf_client/middleware/cache.rb', line 66

def store(key:, payload:, etag: nil, expire_at: nil)
  return unless payload && key && (etag || expire_at)

  mutex.synchronize do
    cache[key.to_sym] = {
      payload: payload,
      etag: etag,
      expire_at: expire_at
    }
  end
end

Instance Method Details

#add_cached_payload(response_env, key) ⇒ Object



152
153
154
155
156
# File 'lib/shaf_client/middleware/cache.rb', line 152

def add_cached_payload(response_env, key)
  return if response_env[:status] != 304
  cached = self.class.get(key: key, check_expiration: false)
  response_env[:body] = cached if cached
end

#add_etag(env, key = nil) ⇒ Object



145
146
147
148
149
150
# File 'lib/shaf_client/middleware/cache.rb', line 145

def add_etag(env, key = nil)
  return unless i[get head].include? env[:method]
  key ||= cache_key(env[:url], env[:request_headers])
  etag = self.class.get_etag(key: key)
  env[:request_headers]['If-None-Match'] = etag if etag
end

#auth_headerObject



174
175
176
# File 'lib/shaf_client/middleware/cache.rb', line 174

def auth_header
  @options.fetch(:auth_header, 'X-AUTH-TOKEN')
end

#cache_key(url, request_headers) ⇒ Object



170
171
172
# File 'lib/shaf_client/middleware/cache.rb', line 170

def cache_key(url, request_headers)
  :"#{url}.#{request_headers&.dig(auth_header)}"
end

#cache_response(response_env, key) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/shaf_client/middleware/cache.rb', line 158

def cache_response(response_env, key)
  etag = response_env[:response_headers]['etag']
  cache_control = response_env[:response_headers]['cache-control']
  expire_at = expiration(cache_control)
  self.class.store(
    key: key,
    payload: response_env[:body],
    etag: etag,
    expire_at: expire_at
  )
end

#call(request_env) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/shaf_client/middleware/cache.rb', line 124

def call(request_env)
  key = cache_key(request_env[:url], request_env[:request_headers])

  skip_cache = request_env[:request_headers].delete :skip_cache

  if !skip_cache && request_env[:method] == :get
    cached = self.class.get(key: key)
    return Response.new(body: cached, headers: {}) if cached
  end

  add_etag(request_env, key)

  @app.call(request_env).on_complete do |response_env|
    # key might have changed in other middleware
    key = cache_key(response_env[:url], request_env[:request_headers])
    add_cached_payload(response_env, key)
    cache_response(response_env, key)
    self.class.inc_request_count
  end
end

#expiration(cache_control) ⇒ Object



178
179
180
181
182
183
# File 'lib/shaf_client/middleware/cache.rb', line 178

def expiration(cache_control)
  return unless cache_control

  max_age = cache_control[/max-age=\s?(\d+)/, 1]
  Time.now + max_age.to_i if max_age
end