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 +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
|