faraday-http-cache 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80c7adcab041a8cfdcc1bfa6ac4a8ddd30661d9b
4
- data.tar.gz: 688398ca6e4d77ac6769f66925c3108d5257ecdd
3
+ metadata.gz: f18d30f7f544974ede44d28d9bba250e746a9b2e
4
+ data.tar.gz: 29cfe8e36786c6a0a47d3d9f1b3cbdb2388f1a19
5
5
  SHA512:
6
- metadata.gz: 789666e8549d1cb08978ae0870d97333e333541f0a774ec7bc10563dce42d3b4f9cf4ec425d16d03d13ae81d5420f5f5ecb8e71962525370b0aa9b39edae0f7b
7
- data.tar.gz: 936fd149b8b358248bdb868fd347c7a53bc5115dae097a9ef6137680afc8d2584bf2f6066f328664132cc0191a9e199a813d31ee803ee1b5bdd8af02f9f8c514
6
+ metadata.gz: 14809735e62b3f547d9a8a9b8e35ce1be0f44a962650a2c196d4dece1c372829184766cc979ec1bf8cad3cfd4011dd086aebefd74e1f11847b684e277853bf8e
7
+ data.tar.gz: a1cdb3e8286c86b03f61dfb6516affbe2b743707b4b7c7d68a8c45e34df55bfd9dcd9e3c7e8290d8f965f3ee36af762a8be2fd751730a6842244a00534c05eac
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012-2013 Plataformatec.
1
+ Copyright 2012-2014 Plataformatec.
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -15,26 +15,30 @@ gem 'faraday-http-cache'
15
15
 
16
16
  ## Usage and configuration
17
17
 
18
- You have to use the middleware in the Faraday instance that you want to. You can use the new
18
+ You have to use the middleware in the Faraday instance that you want to,
19
+ along with a suitable `store` to cache the responses. You can use the new
19
20
  shortcut using a symbol or passing the middleware class
20
21
 
21
22
  ```ruby
22
23
  client = Faraday.new do |builder|
23
- builder.use :http_cache
24
+ builder.use :http_cache, store: Rails.cache
24
25
  # or
25
- builder.use Faraday::HttpCache
26
+ builder.use Faraday::HttpCache, store: Rails.cache
26
27
 
27
28
  builder.adapter Faraday.default_adapter
28
29
  end
29
30
  ```
30
31
 
31
- The middleware uses the `ActiveSupport::Cache` API to record the responses from the targeted
32
- endpoints, and any extra configuration option will be used to setup the cache store.
32
+ The middleware accepts a `store` option for the cache backend responsible for recording
33
+ the API responses that should be stored. Stores should respond to `write` and `read`,
34
+ just like an object from the `ActiveSupport::Cache` API.
33
35
 
34
36
  ```ruby
35
37
  # Connect the middleware to a Memcache instance.
38
+ store = ActiveSupport::Cache.lookup_store(:mem_cache_store, ['localhost:11211'])
39
+
36
40
  client = Faraday.new do |builder|
37
- builder.use :http_cache, store: :mem_cache_store, store_options: ['localhost:11211']
41
+ builder.use :http_cache, store: store
38
42
  builder.adapter Faraday.default_adapter
39
43
  end
40
44
 
@@ -44,16 +48,16 @@ client = Faraday.new do |builder|
44
48
  builder.adapter Faraday.default_adapter
45
49
  end
46
50
  ```
51
+ The default store provided is a simple in memory cache that lives on the client
52
+ instance, so it's important to configure a proper one for your production environment.
47
53
 
48
- The default store provided by ActiveSupport is the `MemoryStore` one, so it's important to
49
- configure a proper one for your production environment.
50
-
51
- MultiJson is used for serialization by default. If you expect to be dealing
52
- with images, you can use [Marshal][marshal] instead.
54
+ the stdlib `JSON` module is used for serialization by default.
55
+ If you expect to be dealing with images, you can use [Marshal][marshal] instead, or
56
+ if you want to use another json library like `oj` or `yajl-ruby`.
53
57
 
54
58
  ```ruby
55
59
  client = Faraday.new do |builder|
56
- builder.use :http_cache, serializer: Marshal
60
+ builder.use :http_cache, store: Rails.cache, serializer: Marshal
57
61
  builder.adapter Faraday.default_adapter
58
62
  end
59
63
  ```
@@ -65,7 +69,7 @@ operations:
65
69
 
66
70
  ```ruby
67
71
  client = Faraday.new do |builder|
68
- builder.use :http_cache, logger: Rails.logger
72
+ builder.use :http_cache, store: Rails.cache, logger: Rails.logger
69
73
  builder.adapter Faraday.default_adapter
70
74
  end
71
75
 
@@ -91,10 +95,23 @@ The middleware will use the following headers to make caching decisions:
91
95
 
92
96
  The `max-age`, `must-revalidate`, `proxy-revalidate` and `s-maxage` directives are checked.
93
97
 
94
- Note: private caches are ignored.
98
+ ### Shared vs. non-shared caches
99
+
100
+ By default, the middleware acts as a "shared cache" per RFC 2616. This means it does not cache
101
+ responses with `Cache-Control: private`. This behavior can be changed by passing in the
102
+ `:shared_cache` configuration option:
103
+
104
+ ```ruby
105
+ client = Faraday.new do |builder|
106
+ builder.use :http_cache, shared_cache: false
107
+ builder.adapter Faraday.default_adapter
108
+ end
109
+
110
+ client.get('http://site/api/some-private-resource') # => will be cached
111
+ ```
95
112
 
96
113
  ## License
97
114
 
98
- Copyright (c) 2012-2013 Plataformatec. See LICENSE file.
115
+ Copyright (c) 2012-2014 Plataformatec. See LICENSE file.
99
116
 
100
117
  [marshal]: http://www.ruby-doc.org/core-2.0/Marshal.html
@@ -1,8 +1,4 @@
1
1
  require 'faraday'
2
- require 'multi_json'
3
-
4
- require 'active_support/core_ext/hash/slice'
5
- require 'active_support/deprecation'
6
2
 
7
3
  require 'faraday/http_cache/storage'
8
4
  require 'faraday/http_cache/response'
@@ -21,13 +17,13 @@ module Faraday
21
17
  #
22
18
  # # Using the middleware with a simple client:
23
19
  # client = Faraday.new do |builder|
24
- # builder.user :http_cache
20
+ # builder.user :http_cache, store: my_store_backend
25
21
  # builder.adapter Faraday.default_adapter
26
22
  # end
27
23
  #
28
24
  # # Attach a Logger to the middleware.
29
25
  # client = Faraday.new do |builder|
30
- # builder.use :http_cache, logger: my_logger_instance
26
+ # builder.use :http_cache, logger: my_logger_instance, store: my_store_backend
31
27
  # builder.adapter Faraday.default_adapter
32
28
  # end
33
29
  #
@@ -38,14 +34,21 @@ module Faraday
38
34
  #
39
35
  # # Use Marshal for serialization
40
36
  # client = Faraday.new do |builder|
41
- # builder.use :http_cache, serializer: Marshal
37
+ # builder.use :http_cache, store: Rails.cache, serializer: Marshal
42
38
  # end
43
39
  class HttpCache < Faraday::Middleware
40
+ # Internal: valid options for the 'initialize' configuration Hash.
41
+ VALID_OPTIONS = [:store, :serializer, :logger, :store_options, :shared_cache]
44
42
 
45
43
  # Public: Initializes a new HttpCache middleware.
46
44
  #
47
- # app - the next endpoint on the 'Faraday' stack.
48
- # arguments - aditional options to setup the logger and the storage.
45
+ # app - the next endpoint on the 'Faraday' stack.
46
+ # args - aditional options to setup the logger and the storage.
47
+ # :logger - A logger object.
48
+ # :serializer - A serializer that should respond to 'dump' and 'load'.
49
+ # :shared_cache - A flag to mark the middleware as a shared cache or not.
50
+ # :store - A cache store that should respond to 'read' and 'write'.
51
+ # :store_options - Deprecated: additional options to setup the cache store.
49
52
  #
50
53
  # Examples:
51
54
  #
@@ -56,20 +59,25 @@ module Faraday
56
59
  # Faraday:HttpCache.new(app, logger: my_logger, serializer: Marshal)
57
60
  #
58
61
  # # Initialize the middleware with a FileStore at the 'tmp' dir.
59
- # Faraday::HttpCache.new(app, store: :file_store, store_options: ['tmp'])
62
+ # store = ActiveSupport::Cache.lookup_store(:file_store, ['tmp'])
63
+ # Faraday::HttpCache.new(app, store: store)
60
64
  #
61
65
  # # Initialize the middleware with a MemoryStore and logger
62
- # Faraday::HttpCache.new(app, store: :memory_store, logger: my_logger, store_options: [size: 1024])
66
+ # store = ActiveSupport::Cache.lookup_store
67
+ # Faraday::HttpCache.new(app, store: store, logger: my_logger)
63
68
  def initialize(app, *args)
64
69
  super(app)
65
70
  @logger = nil
71
+ @shared_cache = true
66
72
  if args.first.is_a? Hash
67
73
  options = args.first
68
74
  @logger = options[:logger]
75
+ @shared_cache = options.fetch(:shared_cache, true)
69
76
  else
70
77
  options = parse_deprecated_options(*args)
71
78
  end
72
79
 
80
+ assert_valid_options!(options)
73
81
  @storage = Storage.new(options)
74
82
  end
75
83
 
@@ -107,6 +115,12 @@ module Faraday
107
115
  end
108
116
  end
109
117
 
118
+ # Internal: Should this cache instance act like a "shared cache" according
119
+ # to the the definition in RFC 2616?
120
+ def shared_cache?
121
+ @shared_cache
122
+ end
123
+
110
124
  private
111
125
  # Internal: Receive the deprecated arguments to initialize the old API
112
126
  # and returns a Hash compatible with the new API
@@ -147,7 +161,7 @@ module Faraday
147
161
  def parse_deprecated_options(*args)
148
162
  options = {}
149
163
  if args.length > 0
150
- ActiveSupport::Deprecation.warn('This api is deprecated, refer to the documentation for the new one', caller)
164
+ Kernel.warn('DEPRECATION WARNING: This API is deprecated, refer to the documentation for the new one', caller)
151
165
  end
152
166
 
153
167
  options[:store] = args.shift
@@ -156,7 +170,8 @@ module Faraday
156
170
  hash_params = args.first
157
171
  options[:serializer] = hash_params.delete(:serializer)
158
172
 
159
- @logger = hash_params.delete(:logger)
173
+ @logger = hash_params[:logger]
174
+ @shared_cache = hash_params.fetch(:shared_cache, true)
160
175
  end
161
176
 
162
177
  options[:store_options] = args
@@ -243,7 +258,7 @@ module Faraday
243
258
  #
244
259
  # Returns nothing.
245
260
  def store(response)
246
- if response.cacheable?
261
+ if shared_cache? ? response.cacheable_in_shared_cache? : response.cacheable_in_private_cache?
247
262
  trace :store
248
263
  @storage.write(@request, response)
249
264
  else
@@ -271,7 +286,13 @@ module Faraday
271
286
  # Returns a 'Hash' containing the ':status', ':body' and 'response_headers'
272
287
  # entries.
273
288
  def create_response(env)
274
- env.to_hash.slice(:status, :body, :response_headers)
289
+ hash = env.to_hash
290
+
291
+ {
292
+ status: hash[:status],
293
+ body: hash[:body],
294
+ response_headers: hash[:response_headers]
295
+ }
275
296
  end
276
297
 
277
298
  # Internal: Creates a new 'Hash' containing the request information.
@@ -281,9 +302,13 @@ module Faraday
281
302
  # Returns a 'Hash' containing the ':method', ':url' and 'request_headers'
282
303
  # entries.
283
304
  def create_request(env)
284
- request = env.to_hash.slice(:method, :url, :request_headers)
285
- request[:request_headers] = request[:request_headers].dup
286
- request
305
+ hash = env.to_hash
306
+
307
+ {
308
+ method: hash[:method],
309
+ url: hash[:url],
310
+ request_headers: hash[:request_headers].dup
311
+ }
287
312
  end
288
313
 
289
314
  # Internal: Logs the trace info about the incoming request
@@ -299,6 +324,21 @@ module Faraday
299
324
  line = "HTTP Cache: [#{method} #{path}] #{@trace.join(', ')}"
300
325
  @logger.debug(line)
301
326
  end
327
+
328
+ # Internal: Checks if the given 'options' Hash contains only
329
+ # valid keys. Please see the 'VALID_OPTIONS' constant for the
330
+ # acceptable keys.
331
+ #
332
+ # Raises an 'ArgumentError'.
333
+ #
334
+ # Returns nothing.
335
+ def assert_valid_options!(options)
336
+ options.each_key do |key|
337
+ unless VALID_OPTIONS.include?(key)
338
+ raise ArgumentError.new("Unknown option: #{key}. Valid options are :#{VALID_OPTIONS.join(', ')}")
339
+ end
340
+ end
341
+ end
302
342
  end
303
343
  end
304
344
 
@@ -59,17 +59,29 @@ module Faraday
59
59
  @payload[:status] == 304
60
60
  end
61
61
 
62
- # Internal: Checks if the response can be cached by the client.
63
- # This is validated by the 'Cache-Control' directives, the response
64
- # status code and it's freshness or validation status.
62
+ # Internal: Checks if the response can be cached by the client when the
63
+ # client is acting as a shared cache per RFC 2616. This is validated by
64
+ # the 'Cache-Control' directives, the response status code and it's
65
+ # freshness or validation status.
66
+ #
67
+ # Returns false if the 'Cache-Control' says that we can't store the
68
+ # response, or it can be stored in private caches only, or if isn't fresh
69
+ # or it can't be revalidated with the origin server. Otherwise, returns
70
+ # true.
71
+ def cacheable_in_shared_cache?
72
+ cacheable?(true)
73
+ end
74
+
75
+ # Internal: Checks if the response can be cached by the client when the
76
+ # client is acting as a private cache per RFC 2616. This is validated by
77
+ # the 'Cache-Control' directives, the response status code and it's
78
+ # freshness or validation status.
65
79
  #
66
80
  # Returns false if the 'Cache-Control' says that we can't store the
67
81
  # response, or if isn't fresh or it can't be revalidated with the origin
68
82
  # server. Otherwise, returns true.
69
- def cacheable?
70
- return false if cache_control.private? || cache_control.no_store?
71
-
72
- cacheable_status_code? && (validateable? || fresh?)
83
+ def cacheable_in_private_cache?
84
+ cacheable?(false)
73
85
  end
74
86
 
75
87
  # Internal: Gets the response age in seconds.
@@ -124,7 +136,12 @@ module Faraday
124
136
  # Returns a 'Hash'.
125
137
  def serializable_hash
126
138
  prepare_to_cache
127
- @payload.slice(:status, :body, :response_headers)
139
+
140
+ {
141
+ status: @payload[:status],
142
+ body: @payload[:body],
143
+ response_headers: @payload[:response_headers]
144
+ }
128
145
  end
129
146
 
130
147
  private
@@ -137,6 +154,15 @@ module Faraday
137
154
  headers.key?('Last-Modified') || headers.key?('ETag')
138
155
  end
139
156
 
157
+ # Internal: The logic behind cacheable_in_private_cache? and
158
+ # cacheable_in_shared_cache? The logic is the same except for the
159
+ # treatment of the private Cache-Control directive.
160
+ def cacheable?(shared_cache)
161
+ return false if (cache_control.private? && shared_cache) || cache_control.no_store?
162
+
163
+ cacheable_status_code? && (validateable? || fresh?)
164
+ end
165
+
140
166
  # Internal: Validates the response status against the
141
167
  # `CACHEABLE_STATUS_CODES' constant.
142
168
  #
@@ -177,12 +203,14 @@ module Faraday
177
203
  @payload[:response_headers]
178
204
  end
179
205
 
180
- # Internal: Prepares the response headers ready to be cached.
206
+ # Internal: Prepares the response headers to be cached.
181
207
  #
182
- # It removes the age header if present to allow cached responses
208
+ # It removes the 'Age' header if present to allow cached responses
183
209
  # to continue aging while cached. It also normalizes the 'max-age'
184
210
  # related headers if the 'Age' header is provided to ensure accuracy
185
211
  # once the 'Age' header is removed.
212
+ #
213
+ # Returns nothing.
186
214
  def prepare_to_cache
187
215
  if headers.key? 'Age'
188
216
  cache_control.normalize_max_ages(headers['Age'].to_i)
@@ -1,10 +1,9 @@
1
+ require 'json'
1
2
  require 'digest/sha1'
2
- require 'active_support/cache'
3
- require 'active_support/core_ext/hash/keys'
4
3
 
5
4
  module Faraday
6
5
  class HttpCache < Faraday::Middleware
7
- # Internal: A Wrapper around a ActiveSupport::CacheStore to store responses.
6
+ # Internal: A Wrapper around an ActiveSupport::CacheStore to store responses.
8
7
  #
9
8
  # Examples
10
9
  # # Creates a new Storage using a MemCached backend from ActiveSupport.
@@ -16,20 +15,25 @@ module Faraday
16
15
  # # Creates a new Storage using Marshal for serialization.
17
16
  # Faraday::HttpCache::Storage.new(:memory_store, serializer: Marshal)
18
17
  class Storage
19
- attr_reader :cache
20
-
21
18
  # Internal: Initialize a new Storage object with a cache backend.
22
19
  #
23
- # options - Storage options (default: {}).
24
- # :store - An ActiveSupport::CacheStore identifier.
25
- # :serializer - A serializer class for the body.
26
- # Should respond to #dump and #load.
27
- # :store_options - An array containg the options for
28
- # the cache store
20
+ # options - Storage options (default: {}).
21
+ # :logger - A Logger object to be used to emit warnings.
22
+ # :store - An cache store object that should
23
+ # respond to 'dump' and 'load'.
24
+ # :serializer - A serializer object that should
25
+ # respond to 'dump' and 'load'.
26
+ # :store_options - An array containg the options for
27
+ # the cache store.
29
28
  def initialize(options = {})
30
- store = options[:store]
31
- @serializer = options[:serializer] || MultiJson
32
- @cache = ActiveSupport::Cache.lookup_store(store, options[:store_options])
29
+ @cache = options[:store] || MemoryStore.new
30
+ @serializer = options[:serializer] || JSON
31
+ @logger = options[:logger]
32
+ if @cache.is_a? Symbol
33
+ @cache = lookup_store(@cache, options[:store_options])
34
+ end
35
+ assert_valid_store!
36
+ notify_memory_store_usage
33
37
  end
34
38
 
35
39
  # Internal: Writes a response with a key based on the given request.
@@ -42,7 +46,7 @@ module Faraday
42
46
  def write(request, response)
43
47
  key = cache_key_for(request)
44
48
  value = @serializer.dump(response.serializable_hash)
45
- cache.write(key, value)
49
+ @cache.write(key, value)
46
50
  end
47
51
 
48
52
  # Internal: Reads a key based on the given request from the underlying cache.
@@ -53,11 +57,14 @@ module Faraday
53
57
  # :request_headers - The custom headers for the request.
54
58
  # klass - The Class to be instantiated with the recovered informations.
55
59
  def read(request, klass = Faraday::HttpCache::Response)
56
- key = cache_key_for(request)
57
- value = cache.read(key)
60
+ cache_key = cache_key_for(request)
61
+ found = @cache.read(cache_key)
62
+
63
+ if found
64
+ payload = @serializer.load(found).each_with_object({}) do |(key,value), hash|
65
+ hash[key.to_sym] = value
66
+ end
58
67
 
59
- if value
60
- payload = @serializer.load(value).symbolize_keys
61
68
  klass.new(payload)
62
69
  end
63
70
  end
@@ -71,8 +78,72 @@ module Faraday
71
78
  #
72
79
  # Returns the encoded String.
73
80
  def cache_key_for(request)
74
- array = request.stringify_keys.to_a.sort
75
- Digest::SHA1.hexdigest(@serializer.dump(array))
81
+ cache_keys = request.each_with_object([]) do |(key, value), parts|
82
+ parts << [key.to_s, value]
83
+ end
84
+
85
+ Digest::SHA1.hexdigest(@serializer.dump(cache_keys.sort))
86
+ end
87
+
88
+ # Internal: Logs a warning when the 'cache' implementation
89
+ # isn't suitable for production use.
90
+ #
91
+ # Returns nothing.
92
+ def notify_memory_store_usage
93
+ return if @logger.nil?
94
+
95
+ kind = @cache.class.name.split('::').last.sub('Store', '').downcase
96
+ if kind == 'memory'
97
+ @logger.warn 'HTTP Cache: using a MemoryStore is not advised as the cache might not be persisted across multiple processes or connection instances.'
98
+ end
99
+ end
100
+
101
+ # Internal: Creates a cache store from 'ActiveSupport' with a set of options.
102
+ #
103
+ # store - A 'Symbol' with the store name.
104
+ # options - Additional options for the cache store.
105
+ #
106
+ # Returns an 'ActiveSupport::Cache' store.
107
+ def lookup_store(store, options)
108
+ if @logger
109
+ @logger.warn "Passing a Symbol as the 'store' is deprecated, please pass the cache store instead."
110
+ end
111
+
112
+ begin
113
+ require 'active_support/cache'
114
+ ActiveSupport::Cache.lookup_store(store, options)
115
+ rescue LoadError => e
116
+ puts "You're missing the 'activesupport' gem. Add it to your Gemfile, bundle it and try again"
117
+ raise e
118
+ end
119
+ end
120
+
121
+ # Internal: Checks if the given cache object supports the
122
+ # expect API ('read' and 'write').
123
+ #
124
+ # Raises an 'ArgumentError'.
125
+ #
126
+ # Returns nothing.
127
+ def assert_valid_store!
128
+ unless @cache.respond_to?(:read) && @cache.respond_to?(:write)
129
+ raise ArgumentError.new("#{@cache.inspect} is not a valid cache store as it does not responds to 'read' and 'write'.")
130
+ end
131
+ end
132
+ end
133
+
134
+ # Internal: A Hash based store to be used by the 'Storage' class
135
+ # when a 'store' is not provided for the middleware setup.
136
+ class MemoryStore
137
+ def initialize
138
+ @cache = {}
139
+ end
140
+
141
+ def read(key)
142
+ @cache[key]
143
+ end
144
+
145
+ def write(key, value)
146
+ @cache[key] = value
76
147
  end
77
148
  end
78
149
  end
data/spec/json_spec.rb CHANGED
@@ -12,6 +12,9 @@ describe Faraday::HttpCache do
12
12
  end
13
13
 
14
14
  it 'works fine with other middlewares' do
15
+ if Faraday::VERSION == '0.9.0'
16
+ pending 'faraday_middleware is not compatible with faraday 0.9'
17
+ end
15
18
  client.get('clear')
16
19
  expect(client.get('json').body['count']).to eq(1)
17
20
  expect(client.get('json').body['count']).to eq(1)
@@ -1,11 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Faraday::HttpCache do
4
- let(:logger) { double('a Logger object', debug: nil) }
4
+ let(:logger) { double('a Logger object', debug: nil, warn: nil) }
5
+ let(:options) { { logger: logger } }
5
6
 
6
7
  let(:client) do
7
8
  Faraday.new(url: ENV['FARADAY_SERVER']) do |stack|
8
- stack.use Faraday::HttpCache, logger: logger
9
+ stack.use Faraday::HttpCache, options
9
10
  adapter = ENV['FARADAY_ADAPTER']
10
11
  stack.headers['X-Faraday-Adapter'] = adapter
11
12
  stack.adapter adapter.to_sym
@@ -36,14 +37,32 @@ describe Faraday::HttpCache do
36
37
  client.get('broken')
37
38
  end
38
39
 
39
- it 'does not cache requests with a private cache control' do
40
- client.get('private')
41
- expect(client.get('private').body).to eq('2')
40
+ describe 'when acting as a shared cache' do
41
+ let(:options) { { logger: logger, shared_cache: true } }
42
+
43
+ it 'does not cache requests with a private cache control' do
44
+ client.get('private')
45
+ expect(client.get('private').body).to eq('2')
46
+ end
47
+
48
+ it 'logs that a private response is invalid' do
49
+ expect(logger).to receive(:debug).with('HTTP Cache: [GET /private] miss, invalid')
50
+ client.get('private')
51
+ end
42
52
  end
43
53
 
44
- it 'logs that a private response is invalid' do
45
- expect(logger).to receive(:debug).with('HTTP Cache: [GET /private] miss, invalid')
46
- client.get('private')
54
+ describe 'when acting as a private cache' do
55
+ let(:options) { { logger: logger, shared_cache: false } }
56
+
57
+ it 'does cache requests with a private cache control' do
58
+ client.get('private')
59
+ expect(client.get('private').body).to eq('1')
60
+ end
61
+
62
+ it 'logs that a private response is stored' do
63
+ expect(logger).to receive(:debug).with('HTTP Cache: [GET /private] miss, store')
64
+ client.get('private')
65
+ end
47
66
  end
48
67
 
49
68
  it 'does not cache requests with a explicit no-store directive' do
@@ -152,42 +171,67 @@ describe Faraday::HttpCache do
152
171
  expect(first_vary).not_to eql(second_vary)
153
172
  end
154
173
 
174
+ it 'raises an error when misconfigured' do
175
+ expect {
176
+ client = Faraday.new(url: ENV['FARADAY_SERVER']) do |stack|
177
+ stack.use Faraday::HttpCache, i_have_no_idea: true
178
+ end
179
+
180
+ client.get('get')
181
+ }.to raise_error(ArgumentError)
182
+ end
183
+
155
184
  describe 'Configuration options' do
156
185
  let(:app) { double('it is an app!') }
157
186
 
158
187
  it 'uses the options to create a Cache Store' do
159
- expect(ActiveSupport::Cache).to receive(:lookup_store).with(:file_store, ['tmp'])
160
- Faraday::HttpCache.new(app, store: :file_store, store_options: ['tmp'])
188
+ store = double(read: nil, write: nil)
189
+
190
+ expect(Faraday::HttpCache::Storage).to receive(:new).with(store: store)
191
+ Faraday::HttpCache.new(app, store: store)
161
192
  end
162
193
 
163
194
  it 'accepts a Hash option' do
164
- expect(ActiveSupport::Cache).to receive(:lookup_store).with(:memory_store, [{ size: 1024 }])
195
+ expect(ActiveSupport::Cache).to receive(:lookup_store).with(:memory_store, [{ size: 1024 }]).and_call_original
165
196
  Faraday::HttpCache.new(app, store: :memory_store, store_options: [size: 1024])
166
197
  end
167
198
 
168
199
  it 'consumes the "logger" key' do
169
- expect(ActiveSupport::Cache).to receive(:lookup_store).with(:memory_store, nil)
200
+ expect(ActiveSupport::Cache).to receive(:lookup_store).with(:memory_store, nil).and_call_original
170
201
  Faraday::HttpCache.new(app, store: :memory_store, logger: logger)
171
202
  end
172
203
 
204
+ describe '#shared_cache?' do
205
+ it 'is true by default' do
206
+ expect(Faraday::HttpCache.new(app).shared_cache?).to eq(true)
207
+ end
208
+
209
+ it 'is true when configured to true' do
210
+ expect(Faraday::HttpCache.new(app, shared_cache: true).shared_cache?).to eq(true)
211
+ end
212
+
213
+ it 'is false when configured to be false' do
214
+ expect(Faraday::HttpCache.new(app, shared_cache: false).shared_cache?).to eq(false)
215
+ end
216
+ end
217
+
173
218
  context 'with deprecated options format' do
219
+ before do
220
+ allow(Kernel).to receive(:warn)
221
+ end
222
+
174
223
  it 'uses the options to create a Cache Store' do
175
- expect(ActiveSupport::Cache).to receive(:lookup_store).with(:file_store, ['tmp'])
224
+ expect(ActiveSupport::Cache).to receive(:lookup_store).with(:file_store, ['tmp']).and_call_original
176
225
  Faraday::HttpCache.new(app, :file_store, 'tmp')
177
226
  end
178
227
 
179
228
  it 'accepts a Hash option' do
180
- expect(ActiveSupport::Cache).to receive(:lookup_store).with(:memory_store, [{ size: 1024 }])
229
+ expect(ActiveSupport::Cache).to receive(:lookup_store).with(:memory_store, [{ size: 1024 }]).and_call_original
181
230
  Faraday::HttpCache.new(app, :memory_store, size: 1024)
182
231
  end
183
232
 
184
- it 'consumes the "logger" key' do
185
- expect(ActiveSupport::Cache).to receive(:lookup_store).with(:memory_store, [{}])
186
- Faraday::HttpCache.new(app, :memory_store, logger: logger)
187
- end
188
-
189
233
  it 'warns the user about the deprecated options' do
190
- expect(ActiveSupport::Deprecation).to receive(:warn)
234
+ expect(Kernel).to receive(:warn)
191
235
 
192
236
  Faraday::HttpCache.new(app, :memory_store, logger: logger)
193
237
  end
@@ -1,25 +1,56 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Faraday::HttpCache::Response do
4
- describe 'cacheable?' do
4
+ describe 'cacheable_in_shared_cache?' do
5
5
  it 'the response is not cacheable if the response is marked as private' do
6
- headers = { 'Cache-Control' => 'private' }
7
- response = Faraday::HttpCache::Response.new(response_headers: headers)
6
+ headers = { 'Cache-Control' => 'private, max-age=400' }
7
+ response = Faraday::HttpCache::Response.new(status: 200, response_headers: headers)
8
8
 
9
- expect(response).not_to be_cacheable
9
+ expect(response).not_to be_cacheable_in_shared_cache
10
10
  end
11
11
 
12
12
  it 'the response is not cacheable if it should not be stored' do
13
- headers = { 'Cache-Control' => 'no-store' }
14
- response = Faraday::HttpCache::Response.new(response_headers: headers)
13
+ headers = { 'Cache-Control' => 'no-store, max-age=400' }
14
+ response = Faraday::HttpCache::Response.new(status: 200, response_headers: headers)
15
+
16
+ expect(response).not_to be_cacheable_in_shared_cache
17
+ end
18
+
19
+ it 'the response is not cacheable when the status code is not acceptable' do
20
+ headers = { 'Cache-Control' => 'max-age=400' }
21
+ response = Faraday::HttpCache::Response.new(status: 503, response_headers: headers)
22
+ expect(response).not_to be_cacheable_in_shared_cache
23
+ end
24
+
25
+ [200, 203, 300, 301, 302, 404, 410].each do |status|
26
+ it "the response is cacheable if the status code is #{status} and the response is fresh" do
27
+ headers = { 'Cache-Control' => 'max-age=400' }
28
+ response = Faraday::HttpCache::Response.new(status: status, response_headers: headers)
29
+
30
+ expect(response).to be_cacheable_in_shared_cache
31
+ end
32
+ end
33
+ end
34
+
35
+ describe 'cacheable_in_private_cache?' do
36
+ it 'the response is cacheable if the response is marked as private' do
37
+ headers = { 'Cache-Control' => 'private, max-age=400' }
38
+ response = Faraday::HttpCache::Response.new(status: 200, response_headers: headers)
39
+
40
+ expect(response).to be_cacheable_in_private_cache
41
+ end
42
+
43
+ it 'the response is not cacheable if it should not be stored' do
44
+ headers = { 'Cache-Control' => 'no-store, max-age=400' }
45
+ response = Faraday::HttpCache::Response.new(status: 200, response_headers: headers)
15
46
 
16
- expect(response).not_to be_cacheable
47
+ expect(response).not_to be_cacheable_in_private_cache
17
48
  end
18
49
 
19
50
  it 'the response is not cacheable when the status code is not acceptable' do
20
51
  headers = { 'Cache-Control' => 'max-age=400' }
21
52
  response = Faraday::HttpCache::Response.new(status: 503, response_headers: headers)
22
- expect(response).not_to be_cacheable
53
+ expect(response).not_to be_cacheable_in_private_cache
23
54
  end
24
55
 
25
56
  [200, 203, 300, 301, 302, 404, 410].each do |status|
@@ -27,14 +58,14 @@ describe Faraday::HttpCache::Response do
27
58
  headers = { 'Cache-Control' => 'max-age=400' }
28
59
  response = Faraday::HttpCache::Response.new(status: status, response_headers: headers)
29
60
 
30
- expect(response).to be_cacheable
61
+ expect(response).to be_cacheable_in_private_cache
31
62
  end
32
63
  end
33
64
  end
34
65
 
35
66
  describe 'freshness' do
36
67
  it 'is fresh if the response still has some time to live' do
37
- date = 200.seconds.ago.httpdate
68
+ date = (Time.now - 200).httpdate
38
69
  headers = { 'Cache-Control' => 'max-age=400', 'Date' => date }
39
70
  response = Faraday::HttpCache::Response.new(response_headers: headers)
40
71
 
@@ -42,7 +73,7 @@ describe Faraday::HttpCache::Response do
42
73
  end
43
74
 
44
75
  it 'is not fresh when the ttl has expired' do
45
- date = 500.seconds.ago.httpdate
76
+ date = (Time.now - 500).httpdate
46
77
  headers = { 'Cache-Control' => 'max-age=400', 'Date' => date }
47
78
  response = Faraday::HttpCache::Response.new(response_headers: headers)
48
79
 
@@ -108,7 +139,7 @@ describe Faraday::HttpCache::Response do
108
139
  end
109
140
 
110
141
  it 'calculates the time from the "Date" header' do
111
- date = 3.seconds.ago.httpdate
142
+ date = (Time.now - 3).httpdate
112
143
  response = Faraday::HttpCache::Response.new(response_headers: { 'Date' => date })
113
144
  expect(response.age).to eq(3)
114
145
  end
@@ -121,7 +152,7 @@ describe Faraday::HttpCache::Response do
121
152
 
122
153
  describe 'time to live calculation' do
123
154
  it 'returns the time to live based on the max age limit' do
124
- date = 200.seconds.ago.httpdate
155
+ date = (Time.now - 200).httpdate
125
156
  headers = { 'Cache-Control' => 'max-age=400', 'Date' => date }
126
157
  response = Faraday::HttpCache::Response.new(response_headers: headers)
127
158
  expect(response.ttl).to eq(200)
@@ -160,9 +191,9 @@ describe Faraday::HttpCache::Response do
160
191
  headers = {
161
192
  'Age' => 6,
162
193
  'Cache-Control' => 'public, max-age=40',
163
- 'Date' => 38.seconds.ago.httpdate,
164
- 'Expires' => 37.seconds.from_now.httpdate,
165
- 'Last-Modified' => 300.seconds.ago.httpdate
194
+ 'Date' => (Time.now - 38).httpdate,
195
+ 'Expires' => (Time.now - 37).httpdate,
196
+ 'Last-Modified' => (Time.now - 300).httpdate
166
197
  }
167
198
  response = Faraday::HttpCache::Response.new(response_headers: headers)
168
199
  expect(response).to be_fresh
data/spec/spec_helper.rb CHANGED
@@ -3,9 +3,7 @@ require 'socket'
3
3
 
4
4
  require 'faraday-http-cache'
5
5
  require 'faraday_middleware'
6
- require 'active_support/core_ext/date/calculations'
7
- require 'active_support/core_ext/numeric/time'
8
- require 'json'
6
+ require 'active_support/cache'
9
7
 
10
8
  require 'support/test_app'
11
9
  require 'support/test_server'
@@ -18,14 +16,10 @@ ENV['FARADAY_ADAPTER'] ||= 'net_http'
18
16
  server.start
19
17
 
20
18
  RSpec.configure do |config|
21
- config.treat_symbols_as_metadata_keys_with_true_values = true
22
19
  config.run_all_when_everything_filtered = true
23
- config.filter_run :focus
24
20
  config.order = 'random'
25
21
 
26
22
  config.after(:suite) do
27
23
  server.stop
28
24
  end
29
25
  end
30
-
31
- ActiveSupport::Deprecation.silenced = true
data/spec/storage_spec.rb CHANGED
@@ -13,9 +13,34 @@ describe Faraday::HttpCache::Storage do
13
13
  subject { storage }
14
14
 
15
15
  describe 'Cache configuration' do
16
- it 'lookups a ActiveSupport cache store' do
17
- expect(ActiveSupport::Cache).to receive(:lookup_store).with(:file_store, ['/tmp'])
18
- Faraday::HttpCache::Storage.new({ store: :file_store, store_options: ['/tmp'] })
16
+ it 'uses a MemoryStore by default' do
17
+ expect(Faraday::HttpCache::MemoryStore).to receive(:new).and_call_original
18
+ Faraday::HttpCache::Storage.new
19
+ end
20
+
21
+ it 'emits a warning when using a MemoryStore' do
22
+ logger = double
23
+ expect(logger).to receive(:warn).with(/using a MemoryStore is not advised/)
24
+ Faraday::HttpCache::Storage.new(logger: logger)
25
+ end
26
+
27
+ it 'lookups an ActiveSupport cache store if a Symbol is given' do
28
+ expect(ActiveSupport::Cache).to receive(:lookup_store).with(:file_store, ['/tmp']).and_call_original
29
+ Faraday::HttpCache::Storage.new(store: :file_store, store_options: ['/tmp'])
30
+ end
31
+
32
+ it 'emits a warning when doing the lookup of an ActiveSupport cache store' do
33
+ logger = double
34
+ expect(logger).to receive(:warn).with(/Passing a Symbol as the 'store' is deprecated/)
35
+ Faraday::HttpCache::Storage.new(store: :file_store, logger: logger)
36
+ end
37
+
38
+ it 'raises an error when the given store is not valid' do
39
+ wrong = double
40
+
41
+ expect {
42
+ Faraday::HttpCache::Storage.new(store: wrong)
43
+ }.to raise_error(ArgumentError)
19
44
  end
20
45
  end
21
46
 
@@ -29,7 +54,7 @@ describe Faraday::HttpCache::Storage do
29
54
  end
30
55
 
31
56
  context 'with default serializer' do
32
- let(:serialized) { MultiJson.dump(response.serializable_hash) }
57
+ let(:serialized) { JSON.dump(response.serializable_hash) }
33
58
  let(:cache_key) { '503ac9f7180ca1cdec49e8eb73a9cc0b47c27325' }
34
59
  it_behaves_like 'serialization'
35
60
  end
@@ -63,9 +88,9 @@ describe Faraday::HttpCache::Storage do
63
88
  headers = {
64
89
  'Age' => 6,
65
90
  'Cache-Control' => 'public, max-age=40',
66
- 'Date' => 38.seconds.ago.httpdate,
67
- 'Expires' => 37.seconds.from_now.httpdate,
68
- 'Last-Modified' => 300.seconds.ago.httpdate
91
+ 'Date' => (Time.now - 38).httpdate,
92
+ 'Expires' => (Time.now - 37).httpdate,
93
+ 'Last-Modified' => (Time.now - 300).httpdate
69
94
  }
70
95
  response = Faraday::HttpCache::Response.new(response_headers: headers)
71
96
  expect(response).to be_fresh
@@ -78,9 +103,10 @@ describe Faraday::HttpCache::Storage do
78
103
 
79
104
  it 'is fresh until cached and that 1 second elapses then the response is no longer fresh' do
80
105
  headers = {
81
- 'Date' => 39.seconds.ago.httpdate,
82
- 'Expires' => 40.seconds.from_now.httpdate,
106
+ 'Date' => (Time.now - 39).httpdate,
107
+ 'Expires' => (Time.now + 40).httpdate,
83
108
  }
109
+
84
110
  response = Faraday::HttpCache::Response.new(response_headers: headers)
85
111
  expect(response).to be_fresh
86
112
  subject.write(request, response)
@@ -1,4 +1,5 @@
1
1
  require 'sinatra/base'
2
+ require 'json'
2
3
 
3
4
  class TestApp < Sinatra::Base
4
5
 
@@ -8,7 +9,7 @@ class TestApp < Sinatra::Base
8
9
 
9
10
  set :counter, 0
10
11
  set :requests, 0
11
- set :yesterday, 1.day.ago.httpdate
12
+ set :yesterday, (Date.today - 1).httpdate
12
13
 
13
14
  get '/ping' do
14
15
  'PONG'
@@ -21,7 +22,7 @@ class TestApp < Sinatra::Base
21
22
  end
22
23
 
23
24
  get '/json' do
24
- json = MultiJson.encode(count: increment_counter.to_i)
25
+ json = JSON.dump(count: increment_counter.to_i)
25
26
  [200, { 'Cache-Control' => 'max-age=400', 'Content-Type' => 'application/json' }, json]
26
27
  end
27
28
 
@@ -44,7 +45,7 @@ class TestApp < Sinatra::Base
44
45
  end
45
46
 
46
47
  get '/private' do
47
- [200, { 'Cache-Control' => 'private' }, increment_counter]
48
+ [200, { 'Cache-Control' => 'private, max-age=100' }, increment_counter]
48
49
  end
49
50
 
50
51
  get '/dontstore' do
metadata CHANGED
@@ -1,71 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faraday-http-cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Mazza
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-09 00:00:00.000000000 Z
11
+ date: 2014-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - '>='
18
- - !ruby/object:Gem::Version
19
- version: '3.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - '>='
25
- - !ruby/object:Gem::Version
26
- version: '3.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: faraday
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
- - - ~>
17
+ - - "~>"
32
18
  - !ruby/object:Gem::Version
33
19
  version: '0.8'
34
20
  type: :runtime
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - ~>
24
+ - - "~>"
39
25
  - !ruby/object:Gem::Version
40
26
  version: '0.8'
41
- - !ruby/object:Gem::Dependency
42
- name: multi_json
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ~>
46
- - !ruby/object:Gem::Version
47
- version: '1.3'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ~>
53
- - !ruby/object:Gem::Version
54
- version: '1.3'
55
27
  description: Middleware to handle HTTP caching
56
28
  email:
57
- - contact@plataformatec.com.br
29
+ - opensource@plataformatec.com.br
58
30
  executables: []
59
31
  extensions: []
60
32
  extra_rdoc_files: []
61
33
  files:
62
34
  - LICENSE
63
35
  - README.md
36
+ - lib/faraday-http-cache.rb
37
+ - lib/faraday/http_cache.rb
64
38
  - lib/faraday/http_cache/cache_control.rb
65
39
  - lib/faraday/http_cache/response.rb
66
40
  - lib/faraday/http_cache/storage.rb
67
- - lib/faraday/http_cache.rb
68
- - lib/faraday-http-cache.rb
69
41
  - spec/binary_spec.rb
70
42
  - spec/cache_control_spec.rb
71
43
  - spec/json_spec.rb
@@ -86,17 +58,17 @@ require_paths:
86
58
  - lib
87
59
  required_ruby_version: !ruby/object:Gem::Requirement
88
60
  requirements:
89
- - - '>='
61
+ - - ">="
90
62
  - !ruby/object:Gem::Version
91
63
  version: '0'
92
64
  required_rubygems_version: !ruby/object:Gem::Requirement
93
65
  requirements:
94
- - - '>='
66
+ - - ">="
95
67
  - !ruby/object:Gem::Version
96
68
  version: '0'
97
69
  requirements: []
98
70
  rubyforge_project:
99
- rubygems_version: 2.0.3
71
+ rubygems_version: 2.2.0
100
72
  signing_key:
101
73
  specification_version: 4
102
74
  summary: A Faraday middleware that stores and validates cache expiration.