faraday-http-cache 0.0.1.dev
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +13 -0
- data/README.md +73 -0
- data/lib/faraday/http_cache/cache_control.rb +112 -0
- data/lib/faraday/http_cache/response.rb +170 -0
- data/lib/faraday/http_cache/storage.rb +71 -0
- data/lib/faraday/http_cache.rb +228 -0
- data/lib/faraday-http-cache.rb +1 -0
- data/spec/cache_control_spec.rb +107 -0
- data/spec/middleware_spec.rb +143 -0
- data/spec/response_spec.rb +150 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/storage_spec.rb +41 -0
- data/spec/support/test_app.rb +72 -0
- data/spec/support/test_server.rb +64 -0
- metadata +197 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Faraday::HttpCache::CacheControl do
|
4
|
+
it 'takes a String with multiple name=value pairs' do
|
5
|
+
instance = Faraday::HttpCache::CacheControl.new('max-age=600, max-stale=300, min-fresh=570')
|
6
|
+
instance.max_age.should == 600
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'takes a String with a single flag value' do
|
10
|
+
instance = Faraday::HttpCache::CacheControl.new('no-cache')
|
11
|
+
instance.should be_no_cache
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'takes a String with a bunch of all kinds of stuff' do
|
15
|
+
instance =
|
16
|
+
Faraday::HttpCache::CacheControl.new('max-age=600,must-revalidate,min-fresh=3000,foo=bar,baz')
|
17
|
+
instance.max_age.should == 600
|
18
|
+
instance.should be_must_revalidate
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'strips leading and trailing spaces' do
|
22
|
+
instance = Faraday::HttpCache::CacheControl.new(' public, max-age = 600 ')
|
23
|
+
instance.should be_public
|
24
|
+
instance.max_age.should == 600
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'ignores blank segments' do
|
28
|
+
instance = Faraday::HttpCache::CacheControl.new('max-age=600,,s-maxage=300')
|
29
|
+
instance.max_age.should == 600
|
30
|
+
instance.shared_max_age.should == 300
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'sorts alphabetically with boolean directives before value directives' do
|
34
|
+
instance = Faraday::HttpCache::CacheControl.new('foo=bar, z, x, y, bling=baz, zoom=zib, b, a')
|
35
|
+
instance.to_s.should == 'a, b, x, y, z, bling=baz, foo=bar, zoom=zib'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'responds to #max_age with an integer when max-age directive present' do
|
39
|
+
instance = Faraday::HttpCache::CacheControl.new('public, max-age=600')
|
40
|
+
instance.max_age.should == 600
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'responds to #max_age with nil when no max-age directive present' do
|
44
|
+
instance = Faraday::HttpCache::CacheControl.new('public')
|
45
|
+
instance.max_age.should be_nil
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'responds to #shared_max_age with an integer when s-maxage directive present' do
|
49
|
+
instance = Faraday::HttpCache::CacheControl.new('public, s-maxage=600')
|
50
|
+
instance.shared_max_age.should == 600
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'responds to #shared_max_age with nil when no s-maxage directive present' do
|
54
|
+
instance = Faraday::HttpCache::CacheControl.new('public')
|
55
|
+
instance.shared_max_age.should be_nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'responds to #public? truthfully when public directive present' do
|
59
|
+
instance = Faraday::HttpCache::CacheControl.new('public')
|
60
|
+
instance.should be_public
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'responds to #public? non-truthfully when no public directive present' do
|
64
|
+
instance = Faraday::HttpCache::CacheControl.new('private')
|
65
|
+
instance.should_not be_public
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'responds to #private? truthfully when private directive present' do
|
69
|
+
instance = Faraday::HttpCache::CacheControl.new('private')
|
70
|
+
instance.should be_private
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'responds to #private? non-truthfully when no private directive present' do
|
74
|
+
instance = Faraday::HttpCache::CacheControl.new('public')
|
75
|
+
instance.should_not be_private
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'responds to #no_cache? truthfully when no-cache directive present' do
|
79
|
+
instance = Faraday::HttpCache::CacheControl.new('no-cache')
|
80
|
+
instance.should be_no_cache
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'responds to #no_cache? non-truthfully when no no-cache directive present' do
|
84
|
+
instance = Faraday::HttpCache::CacheControl.new('max-age=600')
|
85
|
+
instance.should_not be_no_cache
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'responds to #must_revalidate? truthfully when must-revalidate directive present' do
|
89
|
+
instance = Faraday::HttpCache::CacheControl.new('must-revalidate')
|
90
|
+
instance.should be_must_revalidate
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'responds to #must_revalidate? non-truthfully when no must-revalidate directive present' do
|
94
|
+
instance = Faraday::HttpCache::CacheControl.new('max-age=600')
|
95
|
+
instance.should_not be_no_cache
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'responds to #proxy_revalidate? truthfully when proxy-revalidate directive present' do
|
99
|
+
instance = Faraday::HttpCache::CacheControl.new('proxy-revalidate')
|
100
|
+
instance.should be_proxy_revalidate
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'responds to #proxy_revalidate? non-truthfully when no proxy-revalidate directive present' do
|
104
|
+
instance = Faraday::HttpCache::CacheControl.new('max-age=600')
|
105
|
+
instance.should_not be_no_cache
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Faraday::HttpCache do
|
4
|
+
let(:logger) { double('a Logger object', :debug => nil) }
|
5
|
+
|
6
|
+
let(:client) do
|
7
|
+
Faraday.new(:url => ENV['FARADAY_SERVER']) do |stack|
|
8
|
+
stack.use Faraday::HttpCache, :logger => logger
|
9
|
+
adapter = ENV['FARADAY_ADAPTER']
|
10
|
+
stack.headers['X-Faraday-Adapter'] = adapter
|
11
|
+
stack.adapter adapter.to_sym
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
before do
|
16
|
+
client.get('clear')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "doesn't cache POST requests" do
|
20
|
+
client.post('post').body
|
21
|
+
client.post('post').body.should == "2"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "logs that a POST request is unacceptable" do
|
25
|
+
logger.should_receive(:debug).with('HTTP Cache: [POST /post] unacceptable')
|
26
|
+
client.post('post').body
|
27
|
+
end
|
28
|
+
|
29
|
+
it "doesn't cache responses with invalid status code" do
|
30
|
+
client.get('broken')
|
31
|
+
client.get('broken').body.should == "2"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "logs that a response with a bad status code is invalid" do
|
35
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /broken] miss, invalid')
|
36
|
+
client.get('broken')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "doesn't cache requests with a private cache control" do
|
40
|
+
client.get('private')
|
41
|
+
client.get('private').body.should == "2"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "logs that a private response is invalid" do
|
45
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /private] miss, invalid')
|
46
|
+
client.get('private')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't cache requests with a explicit no-store directive" do
|
50
|
+
client.get('dontstore')
|
51
|
+
client.get('dontstore').body.should == "2"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "logs that a response with a no-store directive is invalid" do
|
55
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /dontstore] miss, invalid')
|
56
|
+
client.get('dontstore')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "caches multiple responses when the headers differ" do
|
60
|
+
client.get('get', nil, 'HTTP_ACCEPT' => 'text/html')
|
61
|
+
client.get('get', nil, 'HTTP_ACCEPT' => 'text/html').body.should == "1"
|
62
|
+
|
63
|
+
client.get('get', nil, 'HTTP_ACCEPT' => 'application/json').body.should == "2"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "caches requests with the 'Expires' header" do
|
67
|
+
client.get('expires')
|
68
|
+
client.get('expires').body.should == "1"
|
69
|
+
end
|
70
|
+
|
71
|
+
it "logs that a request with the 'Expires' is fresh and stored" do
|
72
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /expires] miss, store')
|
73
|
+
client.get('expires')
|
74
|
+
end
|
75
|
+
|
76
|
+
it "caches GET responses" do
|
77
|
+
client.get('get')
|
78
|
+
client.get('get').body.should == "1"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "logs that a GET response is stored" do
|
82
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /get] miss, store')
|
83
|
+
client.get('get')
|
84
|
+
end
|
85
|
+
|
86
|
+
it "logs that a stored GET response is fresh" do
|
87
|
+
client.get('get')
|
88
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /get] fresh')
|
89
|
+
client.get('get')
|
90
|
+
end
|
91
|
+
|
92
|
+
it "sends the 'Last-Modified' header on response validation" do
|
93
|
+
client.get('timestamped')
|
94
|
+
client.get('timestamped').body.should == "1"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "logs that the request with 'Last-Modified' was revalidated" do
|
98
|
+
client.get('timestamped')
|
99
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /timestamped] valid, store')
|
100
|
+
client.get('timestamped').body.should == "1"
|
101
|
+
end
|
102
|
+
|
103
|
+
it "sends the 'If-None-Match' header on response validation" do
|
104
|
+
client.get('etag')
|
105
|
+
client.get('etag').body.should == "1"
|
106
|
+
end
|
107
|
+
|
108
|
+
it "logs that the request with 'ETag' was revalidated" do
|
109
|
+
client.get('etag')
|
110
|
+
logger.should_receive(:debug).with('HTTP Cache: [GET /etag] valid, store')
|
111
|
+
client.get('etag').body.should == "1"
|
112
|
+
end
|
113
|
+
|
114
|
+
it "maintains the 'Date' header for cached responses" do
|
115
|
+
date = client.get('get').headers['Date']
|
116
|
+
client.get('get').headers['Date'].should == date
|
117
|
+
end
|
118
|
+
|
119
|
+
it "preserves an old 'Date' header if present" do
|
120
|
+
date = client.get('yesterday').headers['Date']
|
121
|
+
date.should =~ /^\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'Configuration options' do
|
125
|
+
let(:app) { double("it's an app!") }
|
126
|
+
|
127
|
+
it 'uses the options to create a Cache Store' do
|
128
|
+
ActiveSupport::Cache.should_receive(:lookup_store).with(:file_store, ['tmp'])
|
129
|
+
Faraday::HttpCache.new(app, :file_store, 'tmp')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'accepts a Hash option' do
|
133
|
+
ActiveSupport::Cache.should_receive(:lookup_store).with(:memory_store, { :size => 1024 })
|
134
|
+
Faraday::HttpCache.new(app, :memory_store, :size => 1024)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "consumes the 'logger' key" do
|
138
|
+
logger = double('a logger object')
|
139
|
+
ActiveSupport::Cache.should_receive(:lookup_store).with(:memory_store, {})
|
140
|
+
Faraday::HttpCache.new(app, :memory_store, :logger => logger)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Faraday::HttpCache::Response do
|
4
|
+
describe 'cacheable?' do
|
5
|
+
it "the response isn't' cacheable if the response is marked as private" do
|
6
|
+
headers = { 'Cache-Control' => 'private' }
|
7
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
8
|
+
|
9
|
+
response.should_not be_cacheable
|
10
|
+
end
|
11
|
+
|
12
|
+
it "the response isn't' cacheable if it shouldn't be stored" do
|
13
|
+
headers = { 'Cache-Control' => 'no-store' }
|
14
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
15
|
+
|
16
|
+
response.should_not be_cacheable
|
17
|
+
end
|
18
|
+
|
19
|
+
it "the response isn't cacheable when the status code isn't acceptable" do
|
20
|
+
headers = { 'Cache-Control' => 'max-age=400' }
|
21
|
+
response = Faraday::HttpCache::Response.new(:status => 503, :response_headers => headers)
|
22
|
+
response.should_not be_cacheable
|
23
|
+
end
|
24
|
+
|
25
|
+
[200, 203, 300, 301, 302, 404, 410].each do |status|
|
26
|
+
it "the response is cacheable if the status code is #{status} and the response is fresh" do
|
27
|
+
headers = { 'Cache-Control' => 'max-age=400' }
|
28
|
+
response = Faraday::HttpCache::Response.new(:status => status, :response_headers => headers)
|
29
|
+
|
30
|
+
response.should be_cacheable
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'freshness' do
|
36
|
+
it "is fresh if the response still has some time to live" do
|
37
|
+
date = 200.seconds.ago.httpdate
|
38
|
+
headers = { 'Cache-Control' => 'max-age=400', 'Date' => date }
|
39
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
40
|
+
|
41
|
+
response.should be_fresh
|
42
|
+
end
|
43
|
+
|
44
|
+
it "isn't fresh when the ttl has expired" do
|
45
|
+
date = 500.seconds.ago.httpdate
|
46
|
+
headers = { 'Cache-Control' => 'max-age=400', 'Date' => date }
|
47
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
48
|
+
|
49
|
+
response.should_not be_fresh
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sets the 'Date' header if isn't present" do
|
54
|
+
headers = { 'Date' => nil }
|
55
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
56
|
+
|
57
|
+
response.date.should be_present
|
58
|
+
end
|
59
|
+
|
60
|
+
it "the response is not modified if the status code is 304" do
|
61
|
+
response = Faraday::HttpCache::Response.new(:status => 304)
|
62
|
+
response.should be_not_modified
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns the 'Last-Modified' header on the #last_modified method" do
|
66
|
+
headers = { 'Last-Modified' => '123'}
|
67
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
68
|
+
response.last_modified.should == '123'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "returns the 'ETag' header on the #etag method" do
|
72
|
+
headers = { 'ETag' => 'tag'}
|
73
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
74
|
+
response.etag.should == 'tag'
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'max age calculation' do
|
78
|
+
|
79
|
+
it 'uses the shared max age directive when present' do
|
80
|
+
headers = { 'Cache-Control' => 's-maxage=200, max-age=0'}
|
81
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
82
|
+
response.max_age.should == 200
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'uses the max age directive when present' do
|
86
|
+
headers = { 'Cache-Control' => 'max-age=200'}
|
87
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
88
|
+
response.max_age.should == 200
|
89
|
+
end
|
90
|
+
|
91
|
+
it "fallsback to the expiration date leftovers" do
|
92
|
+
headers = { 'Expires' => (Time.now + 100).httpdate, 'Date' => Time.now.httpdate }
|
93
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
94
|
+
response.max_age.should == 100
|
95
|
+
end
|
96
|
+
|
97
|
+
it "returns nil when there's no information to calculate the max age" do
|
98
|
+
response = Faraday::HttpCache::Response.new
|
99
|
+
response.max_age.should be_nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'age calculation' do
|
104
|
+
it "uses the 'Age' header if it's present" do
|
105
|
+
response = Faraday::HttpCache::Response.new(:response_headers => { 'Age' => '3' })
|
106
|
+
response.age.should == 3
|
107
|
+
end
|
108
|
+
|
109
|
+
it "calculates the time from the 'Date' header" do
|
110
|
+
date = 3.seconds.ago.httpdate
|
111
|
+
response = Faraday::HttpCache::Response.new(:response_headers => { 'Date' => date })
|
112
|
+
response.age.should == 3
|
113
|
+
end
|
114
|
+
|
115
|
+
it "returns 0 if there's no 'Age' or 'Date' header present" do
|
116
|
+
response = Faraday::HttpCache::Response.new(:response_headers => {})
|
117
|
+
response.age.should == 0
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'time to live calculation' do
|
122
|
+
it "returns the time to live based on the max age limit" do
|
123
|
+
date = 200.seconds.ago.httpdate
|
124
|
+
headers = { 'Cache-Control' => 'max-age=400', 'Date' => date }
|
125
|
+
response = Faraday::HttpCache::Response.new(:response_headers => headers)
|
126
|
+
response.ttl.should == 200
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "response unboxing" do
|
131
|
+
subject { described_class.new(:status => 200, :response_headers => {}, :body => 'Hi!') }
|
132
|
+
let(:response) { subject.to_response }
|
133
|
+
|
134
|
+
it 'returns a Faraday::Response' do
|
135
|
+
response.should be_a Faraday::Response
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'merges the status code' do
|
139
|
+
response.status.should == 200
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'merges the headers' do
|
143
|
+
response.headers.should be_a Faraday::Utils::Headers
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'merges the body' do
|
147
|
+
response.body.should == "Hi!"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
require 'faraday-http-cache'
|
5
|
+
require 'active_support/core_ext/date/calculations'
|
6
|
+
require 'active_support/core_ext/numeric/time'
|
7
|
+
require 'yajl'
|
8
|
+
|
9
|
+
require 'support/test_app'
|
10
|
+
require 'support/test_server'
|
11
|
+
|
12
|
+
server = TestServer.new
|
13
|
+
|
14
|
+
ENV['FARADAY_SERVER'] = server.endpoint
|
15
|
+
ENV['FARADAY_ADAPTER'] ||= 'net_http'
|
16
|
+
|
17
|
+
server.start
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
21
|
+
config.run_all_when_everything_filtered = true
|
22
|
+
config.filter_run :focus
|
23
|
+
|
24
|
+
config.after(:suite) do
|
25
|
+
server.stop
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Faraday::HttpCache::Storage do
|
4
|
+
let(:request) do
|
5
|
+
{ :method => :get, :request_headers => {}, :url => URI.parse("http://foo.bar/") }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:response) { double(:payload => {}) }
|
9
|
+
|
10
|
+
let(:cache) { ActiveSupport::Cache.lookup_store }
|
11
|
+
|
12
|
+
subject { Faraday::HttpCache::Storage.new(cache) }
|
13
|
+
|
14
|
+
describe 'Cache configuration' do
|
15
|
+
it 'lookups a ActiveSupport cache store' do
|
16
|
+
ActiveSupport::Cache.should_receive(:lookup_store).with(:file_store, '/tmp')
|
17
|
+
Faraday::HttpCache::Storage.new(:file_store, '/tmp')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'storing responses' do
|
22
|
+
it 'writes the response json to the underlying cache using a digest as the key' do
|
23
|
+
json = MultiJson.dump(response.payload)
|
24
|
+
|
25
|
+
cache.should_receive(:write).with('503ac9f7180ca1cdec49e8eb73a9cc0b47c27325', json)
|
26
|
+
subject.write(request, response)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'reading responses' do
|
31
|
+
it "returns nil if the response isn't cached" do
|
32
|
+
subject.read(request).should be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'decodes a stored response' do
|
36
|
+
subject.write(request, response)
|
37
|
+
|
38
|
+
subject.read(request).should be_a(Faraday::HttpCache::Response)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
class TestApp < Sinatra::Base
|
4
|
+
|
5
|
+
set :environment, :test
|
6
|
+
set :server, 'webrick'
|
7
|
+
disable :protection
|
8
|
+
|
9
|
+
set :counter, 0
|
10
|
+
set :requests, 0
|
11
|
+
set :yesterday, 1.day.ago.httpdate
|
12
|
+
|
13
|
+
get '/ping' do
|
14
|
+
"PONG"
|
15
|
+
end
|
16
|
+
|
17
|
+
get '/clear' do
|
18
|
+
settings.counter = 0
|
19
|
+
settings.requests = 0
|
20
|
+
status 204
|
21
|
+
end
|
22
|
+
|
23
|
+
post '/post' do
|
24
|
+
[200, { 'Cache-Control' => 'max-age=400' }, "#{settings.requests += 1}"]
|
25
|
+
end
|
26
|
+
|
27
|
+
get '/broken' do
|
28
|
+
[500, { 'Cache-Control' => 'max-age=400' }, "#{settings.requests += 1}"]
|
29
|
+
end
|
30
|
+
|
31
|
+
get '/get' do
|
32
|
+
[200, { 'Cache-Control' => 'max-age=200' }, "#{settings.requests += 1}"]
|
33
|
+
end
|
34
|
+
|
35
|
+
get '/private' do
|
36
|
+
[200, { 'Cache-Control' => 'private' }, "#{settings.requests += 1}"]
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/dontstore' do
|
40
|
+
[200, { 'Cache-Control' => 'no-store' }, "#{settings.requests += 1}"]
|
41
|
+
end
|
42
|
+
|
43
|
+
get '/expires' do
|
44
|
+
[200, { 'Expires' => (Time.now + 10).httpdate }, "#{settings.requests += 1}"]
|
45
|
+
end
|
46
|
+
|
47
|
+
get '/yesterday' do
|
48
|
+
[200, { 'Date' => settings.yesterday, 'Expires' => settings.yesterday }, "#{settings.requests += 1}"]
|
49
|
+
end
|
50
|
+
|
51
|
+
get '/timestamped' do
|
52
|
+
settings.counter += 1
|
53
|
+
header = settings.counter > 2 ? '1' : '2'
|
54
|
+
|
55
|
+
if env['HTTP_IF_MODIFIED_SINCE'] == header
|
56
|
+
[304, {}, ""]
|
57
|
+
else
|
58
|
+
[200, { 'Last-Modified' => header }, "#{settings.requests += 1}"]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
get '/etag' do
|
63
|
+
settings.counter += 1
|
64
|
+
tag = settings.counter > 2 ? '1' : '2'
|
65
|
+
|
66
|
+
if env['HTTP_IF_NONE_MATCH'] == tag
|
67
|
+
[304, { 'ETag' => tag }, ""]
|
68
|
+
else
|
69
|
+
[200, { 'ETag' => tag }, "#{settings.requests += 1}"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
class TestServer
|
4
|
+
attr_reader :endpoint
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@host = 'localhost'
|
8
|
+
@port = find_port
|
9
|
+
@endpoint = "http://#{@host}:#{@port}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
@pid = run!
|
14
|
+
wait
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop
|
18
|
+
`kill -9 #{@pid}`
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def run!
|
24
|
+
fork do
|
25
|
+
require 'webrick'
|
26
|
+
log = File.open('log/test.log', 'w+')
|
27
|
+
log.sync = true
|
28
|
+
webrick_opts = {
|
29
|
+
:Port => @port,
|
30
|
+
:Logger => WEBrick::Log::new(log),
|
31
|
+
:AccessLog => [[log, "[%{X-Faraday-Adapter}i] %m %U -> %s %b"]]
|
32
|
+
}
|
33
|
+
Rack::Handler::WEBrick.run(TestApp, webrick_opts)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def wait
|
38
|
+
conn = Net::HTTP.new @host, @port
|
39
|
+
conn.open_timeout = conn.read_timeout = 0.1
|
40
|
+
|
41
|
+
responsive = lambda { |path|
|
42
|
+
begin
|
43
|
+
res = conn.start { conn.get(path) }
|
44
|
+
res.is_a?(Net::HTTPSuccess)
|
45
|
+
rescue Errno::ECONNREFUSED, Errno::EBADF, Timeout::Error, Net::HTTPBadResponse
|
46
|
+
false
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
server_pings = 0
|
51
|
+
begin
|
52
|
+
server_pings += 1
|
53
|
+
sleep 0.05
|
54
|
+
abort "test server didn't manage to start" if server_pings >= 50
|
55
|
+
end until responsive.call('/ping')
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_port
|
59
|
+
server = TCPServer.new(@host, 0)
|
60
|
+
server.addr[1]
|
61
|
+
ensure
|
62
|
+
server.close if server
|
63
|
+
end
|
64
|
+
end
|