faraday-http-cache 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d851a7d541055e460f971cf80a0e06ac22e532e5
4
- data.tar.gz: 519218770fbff5e98c7fbb9ba6561b6f8af3a75e
3
+ metadata.gz: fbe28eb1ed1ea0c338b45aad4f4c85002031a3d7
4
+ data.tar.gz: a98f5ecdd009cabb2a535ebc9b505c168fdac1f7
5
5
  SHA512:
6
- metadata.gz: b13880d63d13760de6e4759b1315311412ca445f706b80e9dcb4a4fc7a23bdb22f976f5fbd77e6782ed62b0fa2e5c9e48c0806cbf4e5e137ab038ad0ea2739d2
7
- data.tar.gz: 8945bb6f2bf29e4f9e6578eb40c4fcaa2d1c0502ad1c22963ae0bfab6c7f848d0fdac4df1f9010225c67e9461d23bedecfeaf4f61782fab28c6bfc3063ac7866
6
+ metadata.gz: d92bdd0d0ccc0e30da2aa4288c7b33be98c82ef2c8e200f8bdd65bb79433dfc64ba1314c0174dd4e5add8992499816b3e0385b38c7e8d50b3ac665612f25d77d
7
+ data.tar.gz: d9f5c220e6179f176640c7d97c5c6ff7235e5d774882dbbff9352dcbecf73ce788b6d303655a6cafba2ae53332fddd499fdc06caf41432611aaf4af6a3b4d3c2
data/README.md CHANGED
@@ -79,6 +79,45 @@ client.get('http://site/api/users')
79
79
  # logs "HTTP Cache: [GET users] miss, store"
80
80
  ```
81
81
 
82
+ ### Instrumentation
83
+
84
+ In addition to logging you can instrument the middleware by passing in an `:instrumenter` option
85
+ such as ActiveSupport::Notifications (compatible objects are also allowed).
86
+
87
+ The event `process_request.http_cache.faraday` will be published every time the middleware
88
+ processes a request. In the event payload, `:env` contains the response Faraday env and
89
+ `:cache_status` contains a Symbol indicating the status of the cache processing for that request:
90
+
91
+ - `:unacceptable` means that the request did not go through the cache at all.
92
+ - `:miss` means that no cached response could be found.
93
+ - `:invalid` means that the cached response could not be validated against the server.
94
+ - `:valid` means that the cached response *could* be validated against the server.
95
+ - `:fresh` means that the cached response was still fresh and could be returned without even
96
+ calling the server.
97
+
98
+ ```ruby
99
+ client = Faraday.new do |builder|
100
+ builder.use :http_cache, store: Rails.cache, instrumenter: ActiveSupport::Notifications
101
+ builder.adapter Faraday.default_adapter
102
+ end
103
+
104
+ # Subscribes to all events from Faraday::HttpCache.
105
+ ActiveSupport::Notifications.subscribe "process_request.http_cache.faraday" do |*args|
106
+ event = ActiveSupport::Notifications::Event.new(*args)
107
+ cache_status = event.payload.fetch(:cache_status)
108
+ statsd = Statsd.new
109
+
110
+ case cache_status
111
+ when :fresh, :valid
112
+ statsd.increment("api-calls.cache_hits")
113
+ when :invalid, :miss
114
+ statsd.increment("api-calls.cache_misses")
115
+ when :unacceptable
116
+ statsd.increment("api-calls.cache_bypass")
117
+ end
118
+ end
119
+ ```
120
+
82
121
  ## See it live
83
122
 
84
123
  You can clone this repository, install it's dependencies with Bundler (run `bundle install`) and
@@ -37,14 +37,39 @@ module Faraday
37
37
  # client = Faraday.new do |builder|
38
38
  # builder.use :http_cache, store: Rails.cache, serializer: Marshal
39
39
  # end
40
+ #
41
+ # # Instrument events using ActiveSupport::Notifications
42
+ # client = Faraday.new do |builder|
43
+ # builder.use :http_cache, store: Rails.cache, instrumenter: ActiveSupport::Notifications
44
+ # end
40
45
  class HttpCache < Faraday::Middleware
41
46
  # Internal: valid options for the 'initialize' configuration Hash.
42
- VALID_OPTIONS = [:store, :serializer, :logger, :shared_cache]
47
+ VALID_OPTIONS = [:store, :serializer, :logger, :shared_cache, :instrumenter]
43
48
 
44
49
  UNSAFE_METHODS = [:post, :put, :delete, :patch]
45
50
 
46
51
  ERROR_STATUSES = 400..499
47
52
 
53
+ # The name of the instrumentation event.
54
+ EVENT_NAME = 'process_request.http_cache.faraday'
55
+
56
+ CACHE_STATUSES = [
57
+ # The request was not cacheable:
58
+ :unacceptable,
59
+
60
+ # The response was cached and can still be used:
61
+ :fresh,
62
+
63
+ # The response was cached and the server has validated it with a 304 response:
64
+ :valid,
65
+
66
+ # The response was cached but the server could not validate it.
67
+ :invalid,
68
+
69
+ # No response was found in the cache:
70
+ :miss
71
+ ]
72
+
48
73
  # Public: Initializes a new HttpCache middleware.
49
74
  #
50
75
  # app - the next endpoint on the 'Faraday' stack.
@@ -53,6 +78,7 @@ module Faraday
53
78
  # :serializer - A serializer that should respond to 'dump' and 'load'.
54
79
  # :shared_cache - A flag to mark the middleware as a shared cache or not.
55
80
  # :store - A cache store that should respond to 'read' and 'write'.
81
+ # :instrumenter - An instrumentation object that should respond to 'instrument'.
56
82
  #
57
83
  # Examples:
58
84
  #
@@ -75,6 +101,7 @@ module Faraday
75
101
 
76
102
  @logger = options[:logger]
77
103
  @shared_cache = options.fetch(:shared_cache, true)
104
+ @instrumenter = options[:instrumenter]
78
105
  @storage = create_storage(options)
79
106
  end
80
107
 
@@ -118,6 +145,8 @@ module Faraday
118
145
  response.on_complete do
119
146
  delete(@request, response) if should_delete?(response.status, @request.method)
120
147
  log_request
148
+ response.env[:http_cache_trace] = @trace
149
+ instrument(response.env)
121
150
  end
122
151
  end
123
152
 
@@ -200,8 +229,16 @@ module Faraday
200
229
  response = Response.new(requested_env)
201
230
  if response.not_modified?
202
231
  trace :valid
232
+ updated_response_headers = response.payload[:response_headers]
233
+
234
+ # These headers are not allowed in 304 responses, yet some proxy
235
+ # servers add them in. Don't override the values from the original
236
+ # response.
237
+ updated_response_headers.delete('Content-Type')
238
+ updated_response_headers.delete('Content-Length')
239
+
203
240
  updated_payload = entry.payload
204
- updated_payload[:response_headers].update(response.payload[:response_headers])
241
+ updated_payload[:response_headers].update(updated_response_headers)
205
242
  requested_env.update(updated_payload)
206
243
  response = Response.new(updated_payload)
207
244
  end
@@ -293,6 +330,27 @@ module Faraday
293
330
  @logger.debug(line)
294
331
  end
295
332
 
333
+ # Internal: instruments the request processing.
334
+ #
335
+ # Returns nothing.
336
+ def instrument(env)
337
+ return unless @instrumenter
338
+
339
+ payload = {
340
+ env: env,
341
+ cache_status: extract_status(env[:http_cache_trace])
342
+ }
343
+
344
+ @instrumenter.instrument(EVENT_NAME, payload)
345
+ end
346
+
347
+ # Internal: Extracts the cache status from a trace.
348
+ #
349
+ # Returns the Symbol status or nil if none was available.
350
+ def extract_status(trace)
351
+ CACHE_STATUSES.detect {|status| trace.include?(status) }
352
+ end
353
+
296
354
  # Internal: Checks if the given 'options' Hash contains only
297
355
  # valid keys. Please see the 'VALID_OPTIONS' constant for the
298
356
  # acceptable keys.
@@ -32,6 +32,11 @@ describe Faraday::HttpCache do
32
32
  expect(client.get('broken').body).to eq('2')
33
33
  end
34
34
 
35
+ it 'adds a trace of the actions performed to the env' do
36
+ response = client.post('post')
37
+ expect(response.env[:http_cache_trace]).to eq([:unacceptable, :delete])
38
+ end
39
+
35
40
  describe 'cache invalidation' do
36
41
  it 'expires POST requests' do
37
42
  client.get('counter')
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Instrumentation' do
4
+ let(:backend) { Faraday::Adapter::Test::Stubs.new }
5
+
6
+ let(:client) do
7
+ Faraday.new do |stack|
8
+ stack.use Faraday::HttpCache, instrumenter: ActiveSupport::Notifications
9
+ stack.adapter :test, backend
10
+ end
11
+ end
12
+
13
+ let(:events) { [] }
14
+ let(:subscriber) { lambda {|*args| events << ActiveSupport::Notifications::Event.new(*args) } }
15
+
16
+ around do |example|
17
+ ActiveSupport::Notifications.subscribed(subscriber, 'process_request.http_cache.faraday') do
18
+ example.run
19
+ end
20
+ end
21
+
22
+ describe 'the :cache_status payload entry' do
23
+ it 'is :miss if there is no cache entry for the URL' do
24
+ backend.get('/hello') do
25
+ [200, { 'Cache-Control' => 'public, max-age=999' }, '']
26
+ end
27
+
28
+ client.get('/hello')
29
+ expect(events.last.payload.fetch(:cache_status)).to eq(:miss)
30
+ end
31
+
32
+ it 'is :fresh if the cache entry has not expired' do
33
+ backend.get('/hello') do
34
+ [200, { 'Cache-Control' => 'public, max-age=999' }, '']
35
+ end
36
+
37
+ client.get('/hello') # miss
38
+ client.get('/hello') # fresh!
39
+ expect(events.last.payload.fetch(:cache_status)).to eq(:fresh)
40
+ end
41
+
42
+ it 'is :valid if the cache entry can be validated against the upstream' do
43
+ backend.get('/hello') do
44
+ headers = {
45
+ 'Cache-Control' => 'public, must-revalidate, max-age=0',
46
+ 'Etag' => '123ABCD'
47
+ }
48
+
49
+ [200, headers, '']
50
+ end
51
+
52
+ client.get('/hello') # miss
53
+
54
+ backend.get('/hello') { [304, {}, ''] }
55
+
56
+ client.get('/hello') # valid!
57
+ expect(events.last.payload.fetch(:cache_status)).to eq(:valid)
58
+ end
59
+
60
+ it 'is :invalid if the cache entry could not be validated against the upstream' do
61
+ backend.get('/hello') do
62
+ headers = {
63
+ 'Cache-Control' => 'public, must-revalidate, max-age=0',
64
+ 'Etag' => '123ABCD'
65
+ }
66
+
67
+ [200, headers, '']
68
+ end
69
+
70
+ client.get('/hello') # miss
71
+
72
+ backend.get('/hello') { [200, {}, ''] }
73
+
74
+ client.get('/hello') # invalid!
75
+ expect(events.last.payload.fetch(:cache_status)).to eq(:invalid)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Faraday::HttpCache do
4
+ let(:backend) { Faraday::Adapter::Test::Stubs.new }
5
+
6
+ let(:client) do
7
+ Faraday.new(url: ENV['FARADAY_SERVER']) do |stack|
8
+ stack.use Faraday::HttpCache
9
+ stack.adapter :test, backend
10
+ end
11
+ end
12
+
13
+ it 'maintains the "Content-Type" header for cached responses' do
14
+ backend.get('/test') { [200, { 'ETag' => '123ABC', 'Content-Type' => 'x' }, ""] }
15
+ first_content_type = client.get('/test').headers['Content-Type']
16
+
17
+ # The Content-Type header of the validation response should be ignored.
18
+ backend.get('/test') { [304, { 'Content-Type' => 'y' }, ""] }
19
+ second_content_type = client.get('/test').headers['Content-Type']
20
+
21
+ expect(first_content_type).to eq('x')
22
+ expect(second_content_type).to eq('x')
23
+ end
24
+
25
+ it 'maintains the "Content-Length" header for cached responses' do
26
+ backend.get('/test') { [200, { 'ETag' => '123ABC', 'Content-Length' => 1 }, ""] }
27
+ first_content_length = client.get('/test').headers['Content-Length']
28
+
29
+ # The Content-Length header of the validation response should be ignored.
30
+ backend.get('/test') { [304, { 'Content-Length' => 2 }, ""] }
31
+ second_content_length = client.get('/test').headers['Content-Length']
32
+
33
+ expect(first_content_length).to eq(1)
34
+ expect(second_content_length).to eq(1)
35
+ end
36
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faraday-http-cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.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: 2015-01-30 00:00:00.000000000 Z
11
+ date: 2015-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -42,6 +42,7 @@ files:
42
42
  - spec/binary_spec.rb
43
43
  - spec/cache_control_spec.rb
44
44
  - spec/http_cache_spec.rb
45
+ - spec/instrumentation_spec.rb
45
46
  - spec/json_spec.rb
46
47
  - spec/request_spec.rb
47
48
  - spec/response_spec.rb
@@ -50,6 +51,7 @@ files:
50
51
  - spec/support/empty.png
51
52
  - spec/support/test_app.rb
52
53
  - spec/support/test_server.rb
54
+ - spec/validation_spec.rb
53
55
  homepage: https://github.com/plataformatec/faraday-http-cache
54
56
  licenses:
55
57
  - Apache 2.0
@@ -78,6 +80,7 @@ test_files:
78
80
  - spec/binary_spec.rb
79
81
  - spec/cache_control_spec.rb
80
82
  - spec/http_cache_spec.rb
83
+ - spec/instrumentation_spec.rb
81
84
  - spec/json_spec.rb
82
85
  - spec/request_spec.rb
83
86
  - spec/response_spec.rb
@@ -86,3 +89,4 @@ test_files:
86
89
  - spec/support/empty.png
87
90
  - spec/support/test_app.rb
88
91
  - spec/support/test_server.rb
92
+ - spec/validation_spec.rb