net-http-not_modified_cache 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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