net-http-not_modified_cache 0.0.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.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Einstein Industries
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,12 @@
1
+ = net-http-not_modified_cache - {<img src="https://secure.travis-ci.org/einstein/net-http-not_modified_cache.png" />}[http://travis-ci.org/einstein/net-http-not_modified_cache]
2
+
3
+ Caches responses for requests that respond with "304 - Not Modified" and adds "if-modified-since" or "if-none-match" headers to future requests
4
+
5
+ == Installation
6
+
7
+ gem install net-http-not_modified_cache
8
+
9
+
10
+ == Usage
11
+
12
+ Instructions
@@ -0,0 +1,127 @@
1
+ require 'active_support/cache'
2
+ require 'net/http'
3
+ require 'rack/utils'
4
+ require 'time'
5
+
6
+ module Net
7
+ class HTTP
8
+ module NotModifiedCache
9
+ def cache_entry(response)
10
+ last_modified_at = Time.parse(response['last-modified'] || response['date']) rescue Time.now
11
+ Entry.new(response.body, response['etag'], last_modified_at)
12
+ end
13
+
14
+ def cache_key(request)
15
+ [address, request.path].join
16
+ end
17
+
18
+ def cache_request(request)
19
+ cache_request!(request) if cacheable_request?(request)
20
+ end
21
+
22
+ def cache_request!(request)
23
+ key = cache_key(request)
24
+ unless request['if-modified-since'] || request['if-none-match']
25
+ if entry = NotModifiedCache.store.read(key)
26
+ request['if-modified-since'] = entry.last_modified_at.httpdate
27
+ request['if-none-match'] = entry.etag
28
+ end
29
+ end
30
+ key
31
+ end
32
+
33
+ def cacheable_request?(request)
34
+ NotModifiedCache.enabled? && request.is_a?(Get)
35
+ end
36
+
37
+ def cache_response(response, key)
38
+ cache_response!(response, key) if cacheable_response?(response)
39
+ end
40
+
41
+ def cache_response!(response, key)
42
+ if response.code == '200'
43
+ NotModifiedCache.store.write(key, cache_entry(response))
44
+ elsif response.code == '304' && entry = NotModifiedCache.store.read(key)
45
+ response.instance_variable_set('@body', entry.body)
46
+ end
47
+ end
48
+
49
+ def cacheable_response?(response)
50
+ NotModifiedCache.enabled? && %w(200 304).include?(response.code)
51
+ end
52
+
53
+ def request_with_not_modified_cache(request, body = nil, &block)
54
+ key = cache_request(request)
55
+ response = request_without_not_modified_cache(request, body, &block)
56
+ cache_response(response, key) if key
57
+ response
58
+ end
59
+
60
+ class << self
61
+ attr_writer :root, :store
62
+
63
+ def disable!
64
+ @enabled = false
65
+ end
66
+
67
+ def enable!
68
+ @enabled = true
69
+ end
70
+
71
+ def enabled?
72
+ @enabled
73
+ end
74
+
75
+ def included(base)
76
+ base.class_eval do
77
+ alias_method :request_without_not_modified_cache, :request
78
+ alias_method :request, :request_with_not_modified_cache
79
+ end
80
+ end
81
+
82
+ def root
83
+ @root ||= '/tmp/net-http-not_modified_cache'
84
+ end
85
+
86
+ def store
87
+ @store ||= ActiveSupport::Cache.lookup_store(:file_store, root, :compress => true)
88
+ end
89
+
90
+ def version
91
+ @version ||= '0.0.0'
92
+ end
93
+
94
+ def while_disabled(&block)
95
+ while_enabled_is(false, &block)
96
+ end
97
+
98
+ def while_enabled(&block)
99
+ while_enabled_is(true, &block)
100
+ end
101
+
102
+ def while_enabled_is(boolean)
103
+ old_enabled = @enabled
104
+ @enabled = boolean
105
+ yield
106
+ ensure
107
+ @enabled = old_enabled
108
+ end
109
+
110
+ def with_store(store)
111
+ old_store = self.store
112
+ self.store = store
113
+ yield
114
+ ensure
115
+ self.store = old_store
116
+ end
117
+ end
118
+
119
+ enable!
120
+
121
+ class Entry < Struct.new(:body, :etag, :last_modified_at)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ Net::HTTP.send(:include, Net::HTTP::NotModifiedCache)
@@ -0,0 +1,253 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Net::HTTP::NotModifiedCache do
4
+ let(:nmc) { Net::HTTP::NotModifiedCache }
5
+
6
+ context 'when included in Net::HTTP' do
7
+ subject { Net::HTTP.new(url.host) }
8
+
9
+ let(:found) do
10
+ instance = response.dup
11
+ instance.instance_variable_set('@body', 'test')
12
+ instance.stub!(:code).and_return('200')
13
+ instance
14
+ end
15
+ let(:not_modified) do
16
+ instance = response.dup
17
+ instance.stub!(:code).and_return('304')
18
+ instance
19
+ end
20
+ let(:response) do
21
+ instance = Net::HTTPResponse.allocate
22
+ instance.instance_variable_set('@body', '')
23
+ instance.instance_variable_set('@header', {})
24
+ instance.instance_variable_set('@read', true)
25
+ instance
26
+ end
27
+
28
+ let(:get) { Net::HTTP::Get.allocate }
29
+ let(:post) { Net::HTTP::Post.allocate }
30
+ let(:request) { Net::HTTP::Get.new(url.path) }
31
+
32
+ let(:key) { subject.cache_key(get) }
33
+ let(:url) { URI.parse('http://fakeweb.test/index.html') }
34
+
35
+ context '#cache_entry' do
36
+ it 'should return an Entry instance' do
37
+ subject.cache_entry(found).should be_an_instance_of(nmc::Entry)
38
+ end
39
+
40
+ it 'should set body to the response body' do
41
+ subject.cache_entry(found).body.should == found.body
42
+ end
43
+
44
+ it 'should set etag header if it exists' do
45
+ subject.cache_entry(found).etag.should be_nil
46
+
47
+ found['etag'] = 'test'
48
+ subject.cache_entry(found).etag.should == 'test'
49
+ end
50
+
51
+ it 'should set last_modified_at to last-modified header if it exists' do
52
+ time = Time.now - 100
53
+ found['last-modified'] = time.httpdate
54
+ found['date'] = (time - 100).httpdate
55
+ subject.cache_entry(found).last_modified_at.httpdate.should == time.httpdate
56
+ end
57
+
58
+ it 'should set last_modified_at to date header if it exists and last-modified header is not specified' do
59
+ time = Time.now - 200
60
+ found['date'] = time.httpdate
61
+ subject.cache_entry(found).last_modified_at.httpdate.should == time.httpdate
62
+ end
63
+
64
+ it 'should set last_modified_at to Time.now if both last-modified and date headers are not specified' do
65
+ Timecop.freeze(Time.now - 500) { subject.cache_entry(found).last_modified_at.httpdate.should == Time.now.httpdate }
66
+ end
67
+ end
68
+
69
+ context '#cache_key' do
70
+ it 'should join the address and request path' do
71
+ subject.cache_key(request).should == [subject.address, request.path].join
72
+ end
73
+ end
74
+
75
+ context '#cache_request' do
76
+ it 'should only call #cache_request! if request is cacheable' do
77
+ subject.should_receive(:cache_request!)
78
+ subject.cache_request(get)
79
+
80
+ subject.should_not_receive(:cache_request!)
81
+ subject.cache_request(post)
82
+ end
83
+ end
84
+
85
+ context '#cache_request!' do
86
+ it 'should not add if-modified-since or if-none-match header if either already exists' do
87
+ nmc.store.should_not_receive(:read)
88
+
89
+ request['if-none-match'] = 'etag'
90
+ subject.cache_request!(request)
91
+
92
+ request['if-none-match'] = nil
93
+ request['if-modified-since'] = Time.now.httpdate
94
+ subject.cache_request!(request)
95
+ end
96
+
97
+ it 'should search for cached entry' do
98
+ nmc.store.should_receive(:read)
99
+ subject.cache_request!(request)
100
+ end
101
+
102
+ it 'should set etag and if-modified-since headers' do
103
+ entry = nmc::Entry.new('testing', 'test', Time.now)
104
+ nmc.store.should_receive(:read).with(subject.cache_key(request)).and_return(entry)
105
+ subject.cache_request!(request)
106
+
107
+ request['if-none-match'].should == entry.etag
108
+ request['if-modified-since'].should == entry.last_modified_at.httpdate
109
+ end
110
+ end
111
+
112
+ context '#cacheable_request?' do
113
+ it 'should only return true if enabled' do
114
+ nmc.disable!
115
+ subject.cacheable_request?(get).should be_false
116
+ nmc.enable!
117
+ subject.cacheable_request?(get).should be_true
118
+ end
119
+
120
+ it 'should only return true if request is a Net::HTTP::Get' do
121
+ subject.cacheable_request?(get).should be_true
122
+ subject.cacheable_request?(post).should be_false
123
+ end
124
+ end
125
+
126
+ context '#cache_response' do
127
+ it 'should only be called if request is cacheable' do
128
+ subject.stub(:request_without_not_modified_cache)
129
+ subject.should_not_receive(:cache_response)
130
+ subject.request(post)
131
+ end
132
+
133
+ it 'should only call #cache_response! if response is cacheable' do
134
+ subject.should_receive(:cache_response!)
135
+ subject.cache_response(found, 'test')
136
+
137
+ subject.should_not_receive(:cache_response!)
138
+ subject.cache_response(response, 'test')
139
+ end
140
+ end
141
+
142
+ context '#cache_response!' do
143
+ it 'should cache entry if response is a 200' do
144
+ nmc.store.should_receive(:write)
145
+ subject.cache_response!(found, key)
146
+ end
147
+
148
+ it 'should set cached body if response is a 304' do
149
+ entry = nmc::Entry.new('testing', nil, Time.now)
150
+ nmc.store.should_receive(:read).with(key).and_return(entry)
151
+ subject.cache_response!(not_modified, key)
152
+ not_modified.body.should == entry.body
153
+ end
154
+ end
155
+
156
+ context '#cacheable_response?' do
157
+ it 'should only return true if enabled' do
158
+ nmc.disable!
159
+ subject.cacheable_response?(found).should be_false
160
+ nmc.enable!
161
+ subject.cacheable_response?(found).should be_true
162
+ end
163
+
164
+ it 'should only return true if response code is a 200 or 304' do
165
+ subject.cacheable_response?(response).should be_false
166
+ subject.cacheable_response?(found).should be_true
167
+ subject.cacheable_response?(not_modified).should be_true
168
+ end
169
+ end
170
+
171
+ context '#request_with_not_modified_cache' do
172
+ it 'should run fakeweb tests'
173
+ end
174
+ end
175
+
176
+ context '.enabled?' do
177
+ it 'should be toggleable and true by default' do
178
+ subject.enabled?.should be_true
179
+ subject.disable!
180
+ subject.enabled?.should be_false
181
+ subject.enable!
182
+ subject.enabled?.should be_true
183
+ end
184
+ end
185
+
186
+ context '.root' do
187
+ it 'should be /tmp/net-http-not_modified_cache by default' do
188
+ subject.root.should == '/tmp/net-http-not_modified_cache'
189
+ end
190
+ end
191
+
192
+ context '.store' do
193
+ it 'should be an ActiveSupport::Cache::FileStore by default' do
194
+ subject.store.should be_an_instance_of(ActiveSupport::Cache::FileStore)
195
+ end
196
+
197
+ it 'should use root as cache root' do
198
+ subject.store.cache_path.should == subject.root
199
+ end
200
+ end
201
+
202
+ context '.version' do
203
+ it 'should return a version string' do
204
+ subject.version.should match(/^\d+\.\d+\.\d+(\.[^\.]+)?$/)
205
+ end
206
+ end
207
+
208
+ context '.while_disabled' do
209
+ it 'should set enabled? to false for the duration of the block' do
210
+ subject.while_disabled { subject.enabled?.should be_false }
211
+ subject.enabled?.should be_true
212
+ end
213
+ end
214
+
215
+ context '.while_enabled' do
216
+ it 'should set enabled? to true for the duration of the block' do
217
+ subject.disable!
218
+ subject.while_enabled { subject.enabled?.should be_true }
219
+ subject.enabled?.should be_false
220
+ end
221
+ end
222
+
223
+ context '.while_enabled_is' do
224
+ it 'should set enabled? and return it back to its previous value after evaluating the block' do
225
+ subject.while_enabled_is(false) { subject.enabled?.should be_false }
226
+ subject.enabled?.should be_true
227
+
228
+ subject.disable!
229
+ subject.while_enabled_is(true) { subject.enabled?.should be_true }
230
+ subject.enabled?.should be_false
231
+ end
232
+ end
233
+
234
+ context '.with_store' do
235
+ let(:store) { ActiveSupport::Cache.lookup_store(:file_store, '/tmp/test') }
236
+
237
+ it 'should switch lookup store when yielding' do
238
+ current_store = subject.store
239
+ subject.with_store(store) { subject.store.should_not == current_store }
240
+ subject.store.should == current_store
241
+ end
242
+ end
243
+
244
+ context '::Entry' do
245
+ subject { nmc::Entry.new }
246
+
247
+ it 'should respond to :body, :etag, and :last_modified_at' do
248
+ subject.should respond_to(:body)
249
+ subject.should respond_to(:etag)
250
+ subject.should respond_to(:last_modified_at)
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ # [TODO] why do specs fail without this?
5
+ # [TODO] why doesn't "gem 'timecop', :require => 'timecop/timecop'" work?
6
+ require 'timecop/timecop'
7
+
8
+ spec_root = File.dirname(__FILE__)
9
+ $:.unshift(File.join(spec_root, '..', 'lib'))
10
+ $:.unshift(spec_root)
11
+ require 'net-http-not_modified_cache'
12
+
13
+ RSpec.configure do |config|
14
+ config.filter_run :focus => true
15
+ config.run_all_when_everything_filtered = true
16
+ config.treat_symbols_as_metadata_keys_with_true_values = true
17
+
18
+ config.before { Net::HTTP::NotModifiedCache.enable! }
19
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: net-http-not_modified_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sean Huber
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-17 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: &70308578689380 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70308578689380
25
+ - !ruby/object:Gem::Dependency
26
+ name: i18n
27
+ requirement: &70308578688740 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70308578688740
36
+ - !ruby/object:Gem::Dependency
37
+ name: rack
38
+ requirement: &70308578688120 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70308578688120
47
+ description: Caches responses for requests that respond with "304 - Not Modified"
48
+ and adds "if-modified-since" or "if-none-match" headers to future requests
49
+ email: shuber@einsteinindustries.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/net-http-not_modified_cache.rb
55
+ - MIT-LICENSE
56
+ - README.rdoc
57
+ - spec/net-http-not_modified_cache_spec.rb
58
+ - spec/spec_helper.rb
59
+ homepage: http://github.com/einstein/net-http-not_modified_cache
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.10
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Caches responses for requests that respond with "304 - Not Modified"
83
+ test_files:
84
+ - spec/net-http-not_modified_cache_spec.rb
85
+ - spec/spec_helper.rb