faraday-http-cache 1.0.1 → 1.1.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: 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