faraday-http-cache 0.0.1.dev
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/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
|