pezra-resourceful 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +21 -0
- data/Manifest +34 -0
- data/README.markdown +84 -0
- data/Rakefile +91 -0
- data/lib/resourceful.rb +18 -0
- data/lib/resourceful/authentication_manager.rb +108 -0
- data/lib/resourceful/cache_manager.rb +240 -0
- data/lib/resourceful/exceptions.rb +34 -0
- data/lib/resourceful/header.rb +126 -0
- data/lib/resourceful/http_accessor.rb +104 -0
- data/lib/resourceful/memcache_cache_manager.rb +75 -0
- data/lib/resourceful/multipart_form_data.rb +51 -0
- data/lib/resourceful/net_http_adapter.rb +78 -0
- data/lib/resourceful/options_interpretation.rb +72 -0
- data/lib/resourceful/request.rb +234 -0
- data/lib/resourceful/resource.rb +178 -0
- data/lib/resourceful/response.rb +222 -0
- data/lib/resourceful/stubbed_resource_proxy.rb +47 -0
- data/lib/resourceful/util.rb +6 -0
- data/resourceful.gemspec +49 -0
- data/spec/acceptance/authorization_spec.rb +16 -0
- data/spec/acceptance/caching_spec.rb +190 -0
- data/spec/acceptance/header_spec.rb +24 -0
- data/spec/acceptance/redirecting_spec.rb +12 -0
- data/spec/acceptance/resource_spec.rb +84 -0
- data/spec/acceptance_shared_specs.rb +44 -0
- data/spec/caching_spec.rb +89 -0
- data/spec/old_acceptance_specs.rb +378 -0
- data/spec/resourceful/multipart_form_data_spec.rb +79 -0
- data/spec/resourceful/resource_spec.rb +20 -0
- data/spec/simple_sinatra_server.rb +74 -0
- data/spec/simple_sinatra_server_spec.rb +98 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +29 -0
- metadata +167 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'time'
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
module Resourceful
|
6
|
+
|
7
|
+
class Response
|
8
|
+
REDIRECT_RESPONSE_CODES = [301,302,303,307]
|
9
|
+
NORMALLY_CACHEABLE_RESPONSE_CODES = [200, 203, 300, 301, 410]
|
10
|
+
|
11
|
+
attr_reader :uri, :code, :header, :body, :response_time
|
12
|
+
alias headers header
|
13
|
+
|
14
|
+
attr_accessor :authoritative, :request_time
|
15
|
+
alias authoritative? authoritative
|
16
|
+
|
17
|
+
def initialize(uri, code, header, body)
|
18
|
+
@uri, @code, @header, @body = uri, code, header, body
|
19
|
+
@response_time = Time.now
|
20
|
+
end
|
21
|
+
|
22
|
+
# Is this a cached response that has expired?
|
23
|
+
#
|
24
|
+
# @return true|false
|
25
|
+
def expired?
|
26
|
+
if header['Cache-Control'] and header['Cache-Control'].first.include?('max-age')
|
27
|
+
max_age = header['Cache-Control'].first.split(',').grep(/max-age/).first.split('=').last.to_i
|
28
|
+
return true if current_age > max_age
|
29
|
+
elsif header['Expire']
|
30
|
+
return true if Time.httpdate(header['Expire'].first) < Time.now
|
31
|
+
end
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Is this a cached response that is stale?
|
37
|
+
#
|
38
|
+
# @return true|false
|
39
|
+
def stale?
|
40
|
+
return true if expired?
|
41
|
+
if header['Cache-Control']
|
42
|
+
return true if header['Cache-Control'].include?('must-revalidate')
|
43
|
+
return true if header['Cache-Control'].include?('no-cache')
|
44
|
+
end
|
45
|
+
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
# Is this response cachable?
|
50
|
+
#
|
51
|
+
# @return true|false
|
52
|
+
def cacheable?
|
53
|
+
@cacheable ||= begin
|
54
|
+
@cacheable = true if NORMALLY_CACHEABLE_RESPONSE_CODES.include?(code.to_i)
|
55
|
+
@cacheable = false if header.vary && header.vary.include?('*')
|
56
|
+
@cacheable = false if header.cache_control && header.cache_control.include?('no-cache')
|
57
|
+
@cacheable = true if header.cache_control && header.cache_control.include?('public')
|
58
|
+
@cacheable = true if header.cache_control && header.cache_control.include?('private')
|
59
|
+
@cacheable || false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Does this response force revalidation?
|
64
|
+
def must_be_revalidated?
|
65
|
+
header.cache_control && header.cache_control.include?('must-revalidate')
|
66
|
+
end
|
67
|
+
|
68
|
+
# Update our headers from a later 304 response
|
69
|
+
def revalidate!(not_modified_response)
|
70
|
+
header.merge!(not_modified_response.header)
|
71
|
+
@request_time = not_modified_response.request_time
|
72
|
+
@response_time = not_modified_response.response_time
|
73
|
+
@authoritative = true
|
74
|
+
end
|
75
|
+
|
76
|
+
# Algorithm taken from RCF2616#13.2.3
|
77
|
+
def current_age
|
78
|
+
age_value = header['Age'] ? header['Age'].first.to_i : 0
|
79
|
+
date_value = Time.httpdate(header['Date'].first)
|
80
|
+
now = Time.now
|
81
|
+
|
82
|
+
apparent_age = [0, response_time - date_value].max
|
83
|
+
corrected_received_age = [apparent_age, age_value].max
|
84
|
+
current_age = corrected_received_age + (response_time - request_time) + (now - response_time)
|
85
|
+
end
|
86
|
+
|
87
|
+
def body
|
88
|
+
encoding = header['Content-Encoding'] && header['Content-Encoding'].first
|
89
|
+
case encoding
|
90
|
+
when nil
|
91
|
+
# body is identity encoded; just return it
|
92
|
+
@body
|
93
|
+
when /^\s*gzip\s*$/i
|
94
|
+
gz_in = ::Zlib::GzipReader.new(StringIO.new(@body, 'r'))
|
95
|
+
@body = gz_in.read
|
96
|
+
gz_in.close
|
97
|
+
header.delete('Content-Encoding')
|
98
|
+
@body
|
99
|
+
else
|
100
|
+
raise UnsupportedContentCoding, "Resourceful does not support #{encoding} content coding"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
CODE_NAMES = {
|
105
|
+
100 => "Continue".freeze,
|
106
|
+
101 => "Switching Protocols".freeze,
|
107
|
+
|
108
|
+
200 => "OK".freeze,
|
109
|
+
201 => "Created".freeze,
|
110
|
+
202 => "Accepted".freeze,
|
111
|
+
203 => "Non-Authoritative Information".freeze,
|
112
|
+
204 => "No Content".freeze,
|
113
|
+
205 => "Reset Content".freeze,
|
114
|
+
206 => "Partial Content".freeze,
|
115
|
+
|
116
|
+
300 => "Multiple Choices".freeze,
|
117
|
+
301 => "Moved Permanently".freeze,
|
118
|
+
302 => "Found".freeze,
|
119
|
+
303 => "See Other".freeze,
|
120
|
+
304 => "Not Modified".freeze,
|
121
|
+
305 => "Use Proxy".freeze,
|
122
|
+
307 => "Temporary Redirect".freeze,
|
123
|
+
|
124
|
+
400 => "Bad Request".freeze,
|
125
|
+
401 => "Unauthorized".freeze,
|
126
|
+
402 => "Payment Required".freeze,
|
127
|
+
403 => "Forbidden".freeze,
|
128
|
+
404 => "Not Found".freeze,
|
129
|
+
405 => "Method Not Allowed".freeze,
|
130
|
+
406 => "Not Acceptable".freeze,
|
131
|
+
407 => "Proxy Authentication Required".freeze,
|
132
|
+
408 => "Request Timeout".freeze,
|
133
|
+
409 => "Conflict".freeze,
|
134
|
+
410 => "Gone".freeze,
|
135
|
+
411 => "Length Required".freeze,
|
136
|
+
412 => "Precondition Failed".freeze,
|
137
|
+
413 => "Request Entity Too Large".freeze,
|
138
|
+
414 => "Request-URI Too Long".freeze,
|
139
|
+
415 => "Unsupported Media Type".freeze,
|
140
|
+
416 => "Requested Range Not Satisfiable".freeze,
|
141
|
+
417 => "Expectation Failed".freeze,
|
142
|
+
|
143
|
+
500 => "Internal Server Error".freeze,
|
144
|
+
501 => "Not Implemented".freeze,
|
145
|
+
502 => "Bad Gateway".freeze,
|
146
|
+
503 => "Service Unavailable".freeze,
|
147
|
+
504 => "Gateway Timeout".freeze,
|
148
|
+
505 => "HTTP Version Not Supported".freeze,
|
149
|
+
}.freeze
|
150
|
+
|
151
|
+
CODES = CODE_NAMES.keys
|
152
|
+
|
153
|
+
CODE_NAMES.each do |code, msg|
|
154
|
+
method_name = msg.downcase.gsub(/[- ]/, "_")
|
155
|
+
|
156
|
+
class_eval <<-RUBY
|
157
|
+
def #{method_name}? # def ok?
|
158
|
+
@code == #{code} # @code == 200
|
159
|
+
end # end
|
160
|
+
RUBY
|
161
|
+
end
|
162
|
+
|
163
|
+
# Is the response informational? True for
|
164
|
+
# 1xx series response codes
|
165
|
+
#
|
166
|
+
# @return true|false
|
167
|
+
def informational?
|
168
|
+
@code.in? 100..199
|
169
|
+
end
|
170
|
+
|
171
|
+
# Is the response code sucessful? True for only 2xx series
|
172
|
+
# response codes.
|
173
|
+
#
|
174
|
+
# @return true|false
|
175
|
+
def successful?
|
176
|
+
@code.in? 200..299
|
177
|
+
end
|
178
|
+
alias success? successful?
|
179
|
+
|
180
|
+
# Is the response a redirect? True for
|
181
|
+
# 3xx series response codes
|
182
|
+
#
|
183
|
+
# @return true|false
|
184
|
+
def redirection?
|
185
|
+
@code.in? 300..399
|
186
|
+
end
|
187
|
+
|
188
|
+
# Is the response a actual redirect? True for
|
189
|
+
# 301, 302, 303, 307 response codes
|
190
|
+
#
|
191
|
+
# @return true|false
|
192
|
+
def redirect?
|
193
|
+
@code.in? REDIRECT_RESPONSE_CODES
|
194
|
+
end
|
195
|
+
|
196
|
+
# Is the response the result of a client error? True for
|
197
|
+
# 4xx series response codes
|
198
|
+
#
|
199
|
+
# @return true|false
|
200
|
+
def client_error?
|
201
|
+
@code.in? 400..499
|
202
|
+
end
|
203
|
+
|
204
|
+
# Is the response the result of a server error? True for
|
205
|
+
# 5xx series response codes
|
206
|
+
#
|
207
|
+
# @return true|false
|
208
|
+
def server_error?
|
209
|
+
@code.in? 500..599
|
210
|
+
end
|
211
|
+
|
212
|
+
# Is the response the result of any kind of error? True for
|
213
|
+
# 4xx and 5xx series response codes
|
214
|
+
#
|
215
|
+
# @return true|false
|
216
|
+
def error?
|
217
|
+
server_error? || client_error?
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'resourceful/resource'
|
2
|
+
|
3
|
+
module Resourceful
|
4
|
+
class StubbedResourceProxy
|
5
|
+
def initialize(resource, canned_responses)
|
6
|
+
@resource = resource
|
7
|
+
|
8
|
+
@canned_responses = {}
|
9
|
+
|
10
|
+
canned_responses.each do |cr|
|
11
|
+
mime_type = cr[:mime_type]
|
12
|
+
@canned_responses[mime_type] = resp = Net::HTTPOK.new('1.1', '200', 'OK')
|
13
|
+
resp['content-type'] = mime_type.to_str
|
14
|
+
resp.instance_variable_set(:@read, true)
|
15
|
+
resp.instance_variable_set(:@body, cr[:body])
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_body(*args)
|
21
|
+
get(*args).body
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(*args)
|
25
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
26
|
+
|
27
|
+
if accept = [(options[:accept] || '*/*')].flatten.compact
|
28
|
+
accept.each do |mt|
|
29
|
+
return canned_response(mt) || next
|
30
|
+
end
|
31
|
+
@resource.get(*args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(method, *args)
|
36
|
+
@resource.send(method, *args)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def canned_response(mime_type)
|
42
|
+
mime_type = @canned_responses.keys.first if mime_type == '*/*'
|
43
|
+
@canned_responses[mime_type]
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/resourceful.gemspec
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{resourceful}
|
5
|
+
s.version = "0.5.4"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Paul Sadauskas"]
|
9
|
+
s.date = %q{2009-08-07}
|
10
|
+
s.description = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
|
11
|
+
s.email = %q{psadauskas@gmail.com}
|
12
|
+
s.extra_rdoc_files = ["lib/resourceful/authentication_manager.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/header.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpretation.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/response.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/util.rb", "lib/resourceful.rb", "README.markdown"]
|
13
|
+
s.files = ["lib/resourceful/authentication_manager.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/header.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpretation.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/response.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/util.rb", "lib/resourceful.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README.markdown", "resourceful.gemspec", "spec/acceptance/authorization_spec.rb", "spec/acceptance/caching_spec.rb", "spec/acceptance/header_spec.rb", "spec/acceptance/redirecting_spec.rb", "spec/acceptance/resource_spec.rb", "spec/acceptance_shared_specs.rb", "spec/caching_spec.rb", "spec/old_acceptance_specs.rb", "spec/resourceful/multipart_form_data_spec.rb", "spec/resourceful/resource_spec.rb", "spec/simple_sinatra_server.rb", "spec/simple_sinatra_server_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/paul/resourceful}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Resourceful", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib", "ext"]
|
18
|
+
s.rubyforge_project = %q{resourceful}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<addressable>, [">= 2.1.0"])
|
28
|
+
s.add_runtime_dependency(%q<httpauth>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<thin>, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
31
|
+
s.add_development_dependency(%q<sinatra>, [">= 0"])
|
32
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<addressable>, [">= 2.1.0"])
|
35
|
+
s.add_dependency(%q<httpauth>, [">= 0"])
|
36
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
37
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
38
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
39
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
40
|
+
end
|
41
|
+
else
|
42
|
+
s.add_dependency(%q<addressable>, [">= 2.1.0"])
|
43
|
+
s.add_dependency(%q<httpauth>, [">= 0"])
|
44
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
45
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
46
|
+
s.add_dependency(%q<sinatra>, [">= 0"])
|
47
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
3
|
+
require 'resourceful'
|
4
|
+
require 'addressable/template'
|
5
|
+
|
6
|
+
describe Resourceful do
|
7
|
+
|
8
|
+
describe "caching" do
|
9
|
+
|
10
|
+
before do
|
11
|
+
@http = Resourceful::HttpAccessor.new(:cache_manager => Resourceful::InMemoryCacheManager.new)
|
12
|
+
if ENV['SPEC_LOGGING']
|
13
|
+
@http.logger = Resourceful::StdOutLogger.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_with_errors(resource)
|
18
|
+
begin
|
19
|
+
resp = resource.get
|
20
|
+
rescue Resourceful::UnsuccessfulHttpRequestError => e
|
21
|
+
resp = e.http_response
|
22
|
+
end
|
23
|
+
resp
|
24
|
+
end
|
25
|
+
|
26
|
+
def uri_plus_params(uri, params = {})
|
27
|
+
uri = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri)
|
28
|
+
uri.query_values = params
|
29
|
+
uri
|
30
|
+
end
|
31
|
+
|
32
|
+
def uri_for_code(code, params = {})
|
33
|
+
uri = Addressable::Template.new("http://localhost:42682/code/{code}").expand("code" => code.to_s)
|
34
|
+
uri_plus_params(uri, params)
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "response cacheability" do
|
38
|
+
Resourceful::Response::NORMALLY_CACHEABLE_RESPONSE_CODES.each do |code|
|
39
|
+
describe "response code #{code}" do
|
40
|
+
it "should normally be cached" do
|
41
|
+
resource = @http.resource(uri_for_code(code))
|
42
|
+
|
43
|
+
resp = get_with_errors(resource)
|
44
|
+
resp.should be_cacheable
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not be cached if Vary: *" do
|
48
|
+
resource = @http.resource(uri_for_code(200, "Vary" => "*"))
|
49
|
+
|
50
|
+
resp = get_with_errors(resource)
|
51
|
+
resp.should_not be_cacheable
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not be cached if Cache-Control: no-cache'" do
|
55
|
+
resource = @http.resource(uri_for_code(200, "Cache-Control" => "no-cache"))
|
56
|
+
|
57
|
+
resp = get_with_errors(resource)
|
58
|
+
resp.should_not be_cacheable
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# I would prefer to do all other codes, but some of them do some magic stuff (100),
|
64
|
+
# so I'll just spot check.
|
65
|
+
[201, 206, 302, 307, 404, 500].each do |code|
|
66
|
+
describe "response code #{code}" do
|
67
|
+
it "should not normally be cached" do
|
68
|
+
resource = @http.resource(uri_for_code(code))
|
69
|
+
|
70
|
+
resp = get_with_errors(resource)
|
71
|
+
resp.should_not be_cacheable
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should be cached if Cache-Control: public" do
|
75
|
+
resource = @http.resource(uri_for_code(code, "Cache-Control" => "public"))
|
76
|
+
|
77
|
+
resp = get_with_errors(resource)
|
78
|
+
resp.should be_cacheable
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be cached if Cache-Control: private" do
|
82
|
+
resource = @http.resource(uri_for_code(code, "Cache-Control" => "private"))
|
83
|
+
|
84
|
+
resp = get_with_errors(resource)
|
85
|
+
resp.should be_cacheable
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "expiration" do
|
93
|
+
it 'should use the cached response if Expire: is in the future' do
|
94
|
+
in_the_future = (Time.now + 60).httpdate
|
95
|
+
resource = @http.resource(uri_for_code(200, "Expire" => in_the_future))
|
96
|
+
|
97
|
+
resp = resource.get
|
98
|
+
resp.should_not be_expired
|
99
|
+
|
100
|
+
resp = resource.get
|
101
|
+
resp.should be_ok
|
102
|
+
resp.should_not be_authoritative
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should revalidate the cached response if the response is expired' do
|
106
|
+
in_the_past = (Time.now - 60).httpdate
|
107
|
+
resource = @http.resource(uri_for_code(200, "Expire" => in_the_past))
|
108
|
+
|
109
|
+
resp = resource.get
|
110
|
+
resp.should be_expired
|
111
|
+
|
112
|
+
resp = resource.get
|
113
|
+
resp.should be_ok
|
114
|
+
resp.should be_authoritative
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'authoritative' do
|
119
|
+
|
120
|
+
it "should be authoritative if the response is directly from the server" do
|
121
|
+
resource = @http.resource(
|
122
|
+
uri_plus_params('http://localhost:42682/', "Cache-Control" => 'max-age=10')
|
123
|
+
)
|
124
|
+
|
125
|
+
response = resource.get
|
126
|
+
response.should be_authoritative
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should be authoritative if a cached response was revalidated with the server" do
|
130
|
+
now = Time.now.httpdate
|
131
|
+
resource = @http.resource(
|
132
|
+
uri_plus_params('http://localhost:42682/cached',
|
133
|
+
"modified" => now,
|
134
|
+
"Cache-Control" => 'max-age=0')
|
135
|
+
)
|
136
|
+
|
137
|
+
resource.get
|
138
|
+
response = resource.get("Cache-Control" => "max-age=0")
|
139
|
+
response.should be_authoritative
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should not be authoritative if the cached response was not revalidated" do
|
143
|
+
now = Time.now.httpdate
|
144
|
+
resource = @http.resource(
|
145
|
+
uri_plus_params('http://localhost:42682/cached',
|
146
|
+
"modified" => now,
|
147
|
+
"Cache-Control" => 'max-age=10')
|
148
|
+
)
|
149
|
+
|
150
|
+
resource.get
|
151
|
+
response = resource.get
|
152
|
+
response.should_not be_authoritative
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "Not Modified responses" do
|
159
|
+
before do
|
160
|
+
now = Time.now.httpdate
|
161
|
+
|
162
|
+
resource = @http.resource(
|
163
|
+
uri_plus_params('http://localhost:42682/cached',
|
164
|
+
"modified" => now,
|
165
|
+
"Cache-Control" => 'max-age=0')
|
166
|
+
)
|
167
|
+
|
168
|
+
@first_response = resource.get
|
169
|
+
@second_response = resource.get("Cache-Control" => "max-age=0") # Force revalidation
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should replace the 304 response with whats in the cache" do
|
173
|
+
@second_response.code.should == @first_response.code
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should provide a body identical to the original response" do
|
177
|
+
@second_response.body.should == @first_response.body
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should override any cached headers with new ones"
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "cache invalidation" do
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|