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 +4 -4
- data/README.md +39 -0
- data/lib/faraday/http_cache.rb +60 -2
- data/spec/http_cache_spec.rb +5 -0
- data/spec/instrumentation_spec.rb +78 -0
- data/spec/validation_spec.rb +36 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbe28eb1ed1ea0c338b45aad4f4c85002031a3d7
|
4
|
+
data.tar.gz: a98f5ecdd009cabb2a535ebc9b505c168fdac1f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/faraday/http_cache.rb
CHANGED
@@ -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(
|
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.
|
data/spec/http_cache_spec.rb
CHANGED
@@ -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
|
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-
|
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
|