paul-resourceful 0.2.3
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 +21 -0
- data/Manifest.txt +34 -0
- data/README.markdown +86 -0
- data/Rakefile +14 -0
- data/lib/resourceful.rb +29 -0
- data/lib/resourceful/authentication_manager.rb +107 -0
- data/lib/resourceful/cache_manager.rb +174 -0
- data/lib/resourceful/header.rb +31 -0
- data/lib/resourceful/http_accessor.rb +85 -0
- data/lib/resourceful/net_http_adapter.rb +60 -0
- data/lib/resourceful/options_interpreter.rb +78 -0
- data/lib/resourceful/request.rb +63 -0
- data/lib/resourceful/resource.rb +266 -0
- data/lib/resourceful/response.rb +175 -0
- data/lib/resourceful/stubbed_resource_proxy.rb +47 -0
- data/lib/resourceful/util.rb +6 -0
- data/lib/resourceful/version.rb +1 -0
- data/resourceful.gemspec +30 -0
- data/spec/acceptance_shared_specs.rb +49 -0
- data/spec/acceptance_spec.rb +408 -0
- data/spec/resourceful/authentication_manager_spec.rb +249 -0
- data/spec/resourceful/cache_manager_spec.rb +211 -0
- data/spec/resourceful/header_spec.rb +38 -0
- data/spec/resourceful/http_accessor_spec.rb +125 -0
- data/spec/resourceful/net_http_adapter_spec.rb +96 -0
- data/spec/resourceful/options_interpreter_spec.rb +94 -0
- data/spec/resourceful/request_spec.rb +186 -0
- data/spec/resourceful/resource_spec.rb +600 -0
- data/spec/resourceful/response_spec.rb +238 -0
- data/spec/resourceful/stubbed_resource_proxy_spec.rb +58 -0
- data/spec/simple_http_server_shared_spec.rb +160 -0
- data/spec/simple_http_server_shared_spec_spec.rb +212 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +14 -0
- metadata +98 -0
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'time'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'facets/kernel/ergo'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
module Resourceful
|
8
|
+
# Exception indicating that the server used a content coding scheme
|
9
|
+
# that Resourceful is unable to handle.
|
10
|
+
class UnsupportedContentCoding < Exception
|
11
|
+
end
|
12
|
+
|
13
|
+
class Response
|
14
|
+
REDIRECT_RESPONSE_CODES = [301,302,303,307]
|
15
|
+
|
16
|
+
attr_reader :uri, :code, :header, :body, :response_time
|
17
|
+
alias headers header
|
18
|
+
|
19
|
+
attr_accessor :authoritative, :request_time
|
20
|
+
alias authoritative? authoritative
|
21
|
+
|
22
|
+
def initialize(uri, code, header, body)
|
23
|
+
@uri, @code, @header, @body = uri, code, header, body
|
24
|
+
@response_time = Time.now
|
25
|
+
end
|
26
|
+
|
27
|
+
# Is the response code sucessful? True for only 2xx series
|
28
|
+
# response codes.
|
29
|
+
#
|
30
|
+
# @return true|false
|
31
|
+
def is_success?
|
32
|
+
@code.in? 200..299
|
33
|
+
end
|
34
|
+
alias was_successful? is_success?
|
35
|
+
|
36
|
+
# Is the response the result of a server error? True for
|
37
|
+
# 5xx series response codes
|
38
|
+
#
|
39
|
+
# @return true|false
|
40
|
+
def is_server_error?
|
41
|
+
@code.in? 500..599
|
42
|
+
end
|
43
|
+
alias was_server_error? is_server_error?
|
44
|
+
|
45
|
+
# Is the response the result of a client error? True for
|
46
|
+
# 4xx series response codes
|
47
|
+
#
|
48
|
+
# @return true|false
|
49
|
+
def is_client_error?
|
50
|
+
@code.in? 400..499
|
51
|
+
end
|
52
|
+
alias was_client_error? is_client_error?
|
53
|
+
|
54
|
+
# Is the response the result of any kind of error? True for
|
55
|
+
# 4xx and 5xx series response codes
|
56
|
+
#
|
57
|
+
# @return true|false
|
58
|
+
def is_error?
|
59
|
+
is_server_error? || is_client_error?
|
60
|
+
end
|
61
|
+
alias was_error? is_error?
|
62
|
+
|
63
|
+
# Is the response not a success? True for
|
64
|
+
# 3xx, 4xx and 5xx series response codes
|
65
|
+
#
|
66
|
+
# @return true|false
|
67
|
+
def is_unsuccesful?
|
68
|
+
is_error? || is_redirect?
|
69
|
+
end
|
70
|
+
alias was_unsuccessful? is_unsuccesful?
|
71
|
+
|
72
|
+
# Is the response a redirect response code? True for
|
73
|
+
# 3xx codes that are redirects (301, 302, 303, 307)
|
74
|
+
#
|
75
|
+
# @return true|false
|
76
|
+
def is_redirect?
|
77
|
+
@code.in? REDIRECT_RESPONSE_CODES
|
78
|
+
end
|
79
|
+
alias was_redirect? is_redirect?
|
80
|
+
|
81
|
+
# Is the response a Permanent Redirect (301) ?
|
82
|
+
#
|
83
|
+
# @return true|false
|
84
|
+
def is_permanent_redirect?
|
85
|
+
@code == 301
|
86
|
+
end
|
87
|
+
|
88
|
+
# Is the response a Temporary Redirect (anything but 301) ?
|
89
|
+
#
|
90
|
+
# @return true|false
|
91
|
+
def is_temporary_redirect?
|
92
|
+
is_redirect? and not is_permanent_redirect?
|
93
|
+
end
|
94
|
+
|
95
|
+
# Is the response a client error of Not Authorized (401) ?
|
96
|
+
#
|
97
|
+
# @return true|false
|
98
|
+
def is_not_authorized?
|
99
|
+
@code == 401
|
100
|
+
end
|
101
|
+
|
102
|
+
# Is the response not modified (304) ?
|
103
|
+
#
|
104
|
+
# @return true|false
|
105
|
+
def is_not_modified?
|
106
|
+
@code == 304
|
107
|
+
end
|
108
|
+
|
109
|
+
# Is this a cached response that has expired?
|
110
|
+
#
|
111
|
+
# @return true|false
|
112
|
+
def expired?
|
113
|
+
if header['Expire']
|
114
|
+
return true if Time.httpdate(header['Expire'].first) < Time.now
|
115
|
+
end
|
116
|
+
if header['Cache-Control'] and header['Cache-Control'].first.include?('max-age')
|
117
|
+
max_age = header['Cache-Control'].first.split(',').grep(/max-age/).first.split('=').last.to_i
|
118
|
+
return true if current_age > max_age
|
119
|
+
end
|
120
|
+
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
# Is this a cached response that is stale?
|
125
|
+
#
|
126
|
+
# @return true|false
|
127
|
+
def stale?
|
128
|
+
return true if expired?
|
129
|
+
if header['Cache-Control']
|
130
|
+
return true if header['Cache-Control'].include?('must-revalidate')
|
131
|
+
return true if header['Cache-Control'].include?('no-cache')
|
132
|
+
end
|
133
|
+
|
134
|
+
false
|
135
|
+
end
|
136
|
+
|
137
|
+
# Is this response cachable?
|
138
|
+
#
|
139
|
+
# @return true|false
|
140
|
+
def cachable?
|
141
|
+
return false if header['Vary'] and header['Vary'].include?('*')
|
142
|
+
return false if header['Cache-Control'] and header['Cache-Control'].include?('no-store')
|
143
|
+
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
# Algorithm taken from RCF2616#13.2.3
|
148
|
+
def current_age
|
149
|
+
age_value = Time.httpdate(header['Age'].first) if header['Age']
|
150
|
+
date_value = Time.httpdate(header['Date'].first)
|
151
|
+
now = Time.now
|
152
|
+
|
153
|
+
apparent_age = [0, response_time - date_value].max
|
154
|
+
corrected_received_age = [apparent_age, age_value || 0].max
|
155
|
+
current_age = corrected_received_age + (response_time - request_time) + (now - response_time)
|
156
|
+
end
|
157
|
+
|
158
|
+
def body
|
159
|
+
case header['Content-Encoding'].ergo.first
|
160
|
+
when nil
|
161
|
+
# body is identity encoded; just return it
|
162
|
+
@body
|
163
|
+
when /^\s*gzip\s*$/i
|
164
|
+
gz_in = ::Zlib::GzipReader.new(StringIO.new(@body, 'r'))
|
165
|
+
@body = gz_in.read
|
166
|
+
gz_in.close
|
167
|
+
header.delete('Content-Encoding')
|
168
|
+
@body
|
169
|
+
else
|
170
|
+
raise UnsupportedContentCoding, "Resourceful does not support #{header['Content-Encoding'].ergo.first} content coding"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
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
|
@@ -0,0 +1 @@
|
|
1
|
+
RESOURCEFUL_VERSION = '0.2'
|
data/resourceful.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{resourceful}
|
3
|
+
s.version = "0.2.3"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Paul Sadauskas", "Peter Williams"]
|
7
|
+
s.date = %q{2008-11-22}
|
8
|
+
s.email = ["psadauskas@gmail.com", "pezra@barelyenough.org"]
|
9
|
+
s.extra_rdoc_files = ["Manifest.txt"]
|
10
|
+
s.files = ["MIT-LICENSE", "Manifest.txt", "README.markdown", "Rakefile", "lib/resourceful.rb", "lib/resourceful/authentication_manager.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/header.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpreter.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/version.rb", "resourceful.gemspec", "spec/acceptance_shared_specs.rb", "spec/acceptance_spec.rb", "spec/resourceful/authentication_manager_spec.rb", "spec/resourceful/cache_manager_spec.rb", "spec/resourceful/header_spec.rb", "spec/resourceful/http_accessor_spec.rb", "spec/resourceful/net_http_adapter_spec.rb", "spec/resourceful/options_interpreter_spec.rb", "spec/resourceful/request_spec.rb", "spec/resourceful/resource_spec.rb", "spec/resourceful/response_spec.rb", "spec/resourceful/stubbed_resource_proxy_spec.rb", "spec/simple_http_server_shared_spec.rb", "spec/simple_http_server_shared_spec_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
|
11
|
+
s.has_rdoc = true
|
12
|
+
s.rdoc_options = ["--main", "README.txt"]
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
s.rubyforge_project = %q{resourceful}
|
15
|
+
s.rubygems_version = %q{1.3.1}
|
16
|
+
s.summary = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
|
17
|
+
|
18
|
+
if s.respond_to? :specification_version then
|
19
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
20
|
+
s.specification_version = 2
|
21
|
+
|
22
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
23
|
+
s.add_development_dependency(%q<hoe>, [">= 1.8.2"])
|
24
|
+
else
|
25
|
+
s.add_dependency(%q<hoe>, [">= 1.8.2"])
|
26
|
+
end
|
27
|
+
else
|
28
|
+
s.add_dependency(%q<hoe>, [">= 1.8.2"])
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
describe 'redirect', :shared => true do
|
2
|
+
it 'should be followed by default on GET' do
|
3
|
+
resp = @resource.get
|
4
|
+
resp.should be_instance_of(Resourceful::Response)
|
5
|
+
resp.code.should == 200
|
6
|
+
resp.header['Content-Type'].should == ['text/plain']
|
7
|
+
end
|
8
|
+
|
9
|
+
%w{PUT POST}.each do |method|
|
10
|
+
it "should not be followed by default on #{method}" do
|
11
|
+
lambda {
|
12
|
+
@resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
|
13
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should redirect on #{method.to_s.upcase} if the redirection callback returns true" do
|
17
|
+
@resource.on_redirect { true }
|
18
|
+
resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
|
19
|
+
resp.code.should == 200
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should not redirect on #{method.to_s.upcase} if the redirection callback returns false" do
|
23
|
+
@resource.on_redirect { false }
|
24
|
+
lambda {
|
25
|
+
@resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
|
26
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not be followed by default on DELETE" do
|
31
|
+
lambda {
|
32
|
+
@resource.delete
|
33
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should redirect on DELETE if vthe redirection callback returns true" do
|
37
|
+
@resource.on_redirect { true }
|
38
|
+
resp = @resource.delete
|
39
|
+
resp.code.should == 200
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should not redirect on DELETE if the redirection callback returns false" do
|
43
|
+
@resource.on_redirect { false }
|
44
|
+
lambda {
|
45
|
+
@resource.delete
|
46
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,408 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname + 'spec_helper'
|
3
|
+
require 'resourceful'
|
4
|
+
|
5
|
+
require Pathname(__FILE__).dirname + 'acceptance_shared_specs'
|
6
|
+
|
7
|
+
|
8
|
+
describe Resourceful do
|
9
|
+
it_should_behave_like 'simple http server'
|
10
|
+
|
11
|
+
describe 'working with a resource' do
|
12
|
+
before do
|
13
|
+
@accessor = Resourceful::HttpAccessor.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should #get a resource, and return a response object' do
|
17
|
+
resource = @accessor.resource('http://localhost:3000/get')
|
18
|
+
resp = resource.get
|
19
|
+
resp.should be_instance_of(Resourceful::Response)
|
20
|
+
resp.code.should == 200
|
21
|
+
resp.body.should == 'Hello, world!'
|
22
|
+
resp.header.should be_instance_of(Resourceful::Header)
|
23
|
+
resp.header['Content-Type'].should == ['text/plain']
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should set additional headers on the #get' do
|
27
|
+
resource = @accessor.resource('http://localhost:3000/echo_header')
|
28
|
+
resp = resource.get(:foo => :bar)
|
29
|
+
resp.should be_instance_of(Resourceful::Response)
|
30
|
+
resp.code.should == 200
|
31
|
+
resp.body.should =~ /"HTTP_FOO"=>"bar"/
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should #post a resource, and return the response' do
|
35
|
+
resource = @accessor.resource('http://localhost:3000/post')
|
36
|
+
resp = resource.post('Hello world from POST', :content_type => 'text/plain')
|
37
|
+
resp.should be_instance_of(Resourceful::Response)
|
38
|
+
resp.code.should == 201
|
39
|
+
resp.body.should == 'Hello world from POST'
|
40
|
+
resp.header.should be_instance_of(Resourceful::Header)
|
41
|
+
resp.header['Content-Type'].should == ['text/plain']
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should #put a resource, and return the response' do
|
45
|
+
resource = @accessor.resource('http://localhost:3000/put')
|
46
|
+
resp = resource.put('Hello world from PUT', :content_type => 'text/plain')
|
47
|
+
resp.should be_instance_of(Resourceful::Response)
|
48
|
+
resp.code.should == 200
|
49
|
+
resp.body.should == 'Hello world from PUT'
|
50
|
+
resp.header.should be_instance_of(Resourceful::Header)
|
51
|
+
resp.header['Content-Type'].should == ['text/plain']
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should #delete a resource, and return a response' do
|
55
|
+
resource = @accessor.resource('http://localhost:3000/delete')
|
56
|
+
resp = resource.delete
|
57
|
+
resp.should be_instance_of(Resourceful::Response)
|
58
|
+
resp.code.should == 200
|
59
|
+
resp.body.should == 'KABOOM!'
|
60
|
+
resp.header.should be_instance_of(Resourceful::Header)
|
61
|
+
resp.header['Content-Type'].should == ['text/plain']
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should take an optional default header for reads' do
|
65
|
+
resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
|
66
|
+
resp = resource.get
|
67
|
+
resp.should be_instance_of(Resourceful::Response)
|
68
|
+
resp.code.should == 200
|
69
|
+
resp.body.should =~ /"HTTP_FOO"=>"bar"/
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should take an optional default header for writes' do
|
73
|
+
resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
|
74
|
+
resp = resource.post("data", :content_type => 'text/plain')
|
75
|
+
resp.should be_instance_of(Resourceful::Response)
|
76
|
+
resp.code.should == 200
|
77
|
+
resp.body.should =~ /"HTTP_FOO"=>"bar"/
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should override the default header with any set on a read action' do
|
81
|
+
resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
|
82
|
+
resp = resource.get(:foo => :baz)
|
83
|
+
resp.should be_instance_of(Resourceful::Response)
|
84
|
+
resp.code.should == 200
|
85
|
+
resp.body.should =~ /"HTTP_FOO"=>"baz"/
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should override the default header with any set on a write action' do
|
89
|
+
resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
|
90
|
+
resp = resource.post("data", :foo => :baz, :content_type => 'text/plain')
|
91
|
+
resp.should be_instance_of(Resourceful::Response)
|
92
|
+
resp.code.should == 200
|
93
|
+
resp.body.should =~ /"HTTP_FOO"=>"baz"/
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'redirecting' do
|
97
|
+
|
98
|
+
describe 'registering callback' do
|
99
|
+
before do
|
100
|
+
@resource = @accessor.resource('http://localhost:3000/redirect/301?http://localhost:3000/get')
|
101
|
+
@callback = mock('callback')
|
102
|
+
@callback.stub!(:call).and_return(true)
|
103
|
+
|
104
|
+
@resource.on_redirect { @callback.call }
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should allow a callback to be registered' do
|
108
|
+
@resource.should respond_to(:on_redirect)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should perform a registered callback on redirect' do
|
112
|
+
@callback.should_receive(:call).and_return(true)
|
113
|
+
@resource.get
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should not perform the redirect if the callback returns false' do
|
117
|
+
@callback.should_receive(:call).and_return(false)
|
118
|
+
lambda {
|
119
|
+
@resource.get
|
120
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'permanent redirect' do
|
125
|
+
before do
|
126
|
+
@redirect_code = 301
|
127
|
+
@resource = @accessor.resource('http://localhost:3000/redirect/301?http://localhost:3000/get')
|
128
|
+
end
|
129
|
+
|
130
|
+
it_should_behave_like 'redirect'
|
131
|
+
|
132
|
+
it 'should change the effective uri of the resource' do
|
133
|
+
@resource.get
|
134
|
+
@resource.effective_uri.should == 'http://localhost:3000/get'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'temporary redirect' do
|
139
|
+
before do
|
140
|
+
@redirect_code = 302
|
141
|
+
@resource = @accessor.resource('http://localhost:3000/redirect/302?http://localhost:3000/get')
|
142
|
+
end
|
143
|
+
|
144
|
+
it_should_behave_like 'redirect'
|
145
|
+
|
146
|
+
it 'should not change the effective uri of the resource' do
|
147
|
+
@resource.get
|
148
|
+
@resource.effective_uri.should == 'http://localhost:3000/redirect/302?http://localhost:3000/get'
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '303 See Other' do
|
152
|
+
before do
|
153
|
+
@redirect_code = 303
|
154
|
+
@resource = @accessor.resource('http://localhost:3000/redirect/303?http://localhost:3000/method')
|
155
|
+
@resource.on_redirect { true }
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should GET the redirected resource, regardless of the initial method' do
|
159
|
+
resp = @resource.delete
|
160
|
+
resp.code.should == 200
|
161
|
+
resp.body.should == 'GET'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
describe 'caching' do
|
169
|
+
before do
|
170
|
+
@accessor = Resourceful::HttpAccessor.new(:cache_manager => Resourceful::InMemoryCacheManager.new)
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should use the cached response' do
|
174
|
+
resource = @accessor.resource('http://localhost:3000/get')
|
175
|
+
resp = resource.get
|
176
|
+
resp.authoritative?.should be_true
|
177
|
+
|
178
|
+
resp2 = resource.get
|
179
|
+
resp2.authoritative?.should be_false
|
180
|
+
|
181
|
+
resp2.should == resp
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should not store the representation if the server says not to' do
|
185
|
+
resource = @accessor.resource('http://localhost:3000/header?{Vary:%20*}')
|
186
|
+
resp = resource.get
|
187
|
+
resp.authoritative?.should be_true
|
188
|
+
resp.should_not be_cachable
|
189
|
+
|
190
|
+
resp2 = resource.get
|
191
|
+
resp2.should_not == resp
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should use the cached version of the representation if it has not expired' do
|
195
|
+
in_an_hour = (Time.now + (60*60)).httpdate
|
196
|
+
uri = URI.escape("http://localhost:3000/header?{Expire: \"#{in_an_hour}\"}")
|
197
|
+
|
198
|
+
resource = @accessor.resource(uri)
|
199
|
+
resp = resource.get
|
200
|
+
resp.should be_authoritative
|
201
|
+
|
202
|
+
resp2 = resource.get
|
203
|
+
resp2.should_not be_authoritative
|
204
|
+
resp2.should == resp
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should revalidate the cached response if it has expired' do
|
208
|
+
an_hour_ago = (Time.now - (60*60)).httpdate
|
209
|
+
uri = URI.escape("http://localhost:3000/header?{Expire: \"#{an_hour_ago}\"}")
|
210
|
+
|
211
|
+
resource = @accessor.resource(uri)
|
212
|
+
resp = resource.get
|
213
|
+
resp.should be_authoritative
|
214
|
+
resp.should be_expired
|
215
|
+
|
216
|
+
resp2 = resource.get
|
217
|
+
resp2.should be_authoritative
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'should provide the cached version if the server responds with a 304 not modified' do
|
221
|
+
in_an_hour = (Time.now + (60*60)).httpdate
|
222
|
+
uri = URI.escape("http://localhost:3000/modified?#{in_an_hour}")
|
223
|
+
|
224
|
+
resource = @accessor.resource(uri)
|
225
|
+
resp = resource.get
|
226
|
+
resp.should be_authoritative
|
227
|
+
resp.header['Cache-Control'].should include('must-revalidate')
|
228
|
+
|
229
|
+
resp2 = resource.get
|
230
|
+
resp2.should be_authoritative
|
231
|
+
resp2.should == resp
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'should not use a cached document for a resource that has been posted to' do
|
235
|
+
resource = @accessor.resource('http://localhost:3000/get')
|
236
|
+
resp = resource.get
|
237
|
+
resp.authoritative?.should be_true
|
238
|
+
|
239
|
+
resource.post("foo", :content_type => 'text/plain')
|
240
|
+
|
241
|
+
resp2 = resource.get
|
242
|
+
resp2.should_not == resp
|
243
|
+
end
|
244
|
+
|
245
|
+
describe 'Cache-Control' do
|
246
|
+
|
247
|
+
it 'should cache anything with "Cache-Control: public"' do
|
248
|
+
uri = URI.escape('http://localhost:3000/header?{Cache-Control: public}')
|
249
|
+
resource = @accessor.resource(uri)
|
250
|
+
resp = resource.get
|
251
|
+
resp.authoritative?.should be_true
|
252
|
+
|
253
|
+
resp2 = resource.get
|
254
|
+
resp2.authoritative?.should be_false
|
255
|
+
|
256
|
+
resp2.should == resp
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'should cache anything with "Cache-Control: private"' do
|
260
|
+
uri = URI.escape('http://localhost:3000/header?{Cache-Control: private}')
|
261
|
+
resource = @accessor.resource(uri)
|
262
|
+
resp = resource.get
|
263
|
+
resp.authoritative?.should be_true
|
264
|
+
|
265
|
+
resp2 = resource.get
|
266
|
+
resp2.authoritative?.should be_false
|
267
|
+
|
268
|
+
resp2.should == resp
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'should cache but revalidate anything with "Cache-Control: no-cache"' do
|
272
|
+
uri = URI.escape('http://localhost:3000/header?{Cache-Control: no-cache}')
|
273
|
+
resource = @accessor.resource(uri)
|
274
|
+
resp = resource.get
|
275
|
+
resp.authoritative?.should be_true
|
276
|
+
|
277
|
+
resp2 = resource.get
|
278
|
+
resp2.authoritative?.should be_true
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'should revalidate anything that is older than "Cache-Control: max-age" value' do
|
282
|
+
|
283
|
+
uri = URI.escape('http://localhost:3000/header?{Cache-Control: max-age=1, Date: "Mon, 04 Aug 2008 18:00:00 GMT"}')
|
284
|
+
resource = @accessor.resource(uri)
|
285
|
+
resp = resource.get
|
286
|
+
resp.authoritative?.should be_true
|
287
|
+
|
288
|
+
resp.expired?.should be_true
|
289
|
+
|
290
|
+
resp2 = resource.get
|
291
|
+
resp2.authoritative?.should be_true
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'should cache but revalidate anything with "Cache-Control: must-revalidate"' do
|
295
|
+
uri = URI.escape('http://localhost:3000/header?{Cache-Control: must-revalidate}')
|
296
|
+
resource = @accessor.resource(uri)
|
297
|
+
resp = resource.get
|
298
|
+
resp.authoritative?.should be_true
|
299
|
+
|
300
|
+
resp2 = resource.get
|
301
|
+
resp2.authoritative?.should be_true
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'should not cache anything with "Cache-Control: no-store"' do
|
305
|
+
uri = URI.escape('http://localhost:3000/header?{Cache-Control: no-store}')
|
306
|
+
resource = @accessor.resource(uri)
|
307
|
+
resp = resource.get
|
308
|
+
resp.authoritative?.should be_true
|
309
|
+
|
310
|
+
resp2 = resource.get
|
311
|
+
resp2.authoritative?.should be_true
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
describe 'authorization' do
|
320
|
+
before do
|
321
|
+
@uri = 'http://localhost:3000/auth?basic'
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'should automatically add authorization info to the request if its available'
|
325
|
+
|
326
|
+
it 'should not authenticate if no auth handlers are set' do
|
327
|
+
resource = @accessor.resource(@uri)
|
328
|
+
lambda {
|
329
|
+
resource.get
|
330
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'should not authenticate if no valid auth handlers are available' do
|
334
|
+
basic_handler = Resourceful::BasicAuthenticator.new('Not Test Auth', 'admin', 'secret')
|
335
|
+
@accessor.auth_manager.add_auth_handler(basic_handler)
|
336
|
+
resource = @accessor.resource(@uri)
|
337
|
+
lambda {
|
338
|
+
resource.get
|
339
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
340
|
+
end
|
341
|
+
|
342
|
+
describe 'basic' do
|
343
|
+
before do
|
344
|
+
@uri = 'http://localhost:3000/auth?basic'
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'should be able to authenticate basic auth' do
|
348
|
+
basic_handler = Resourceful::BasicAuthenticator.new('Test Auth', 'admin', 'secret')
|
349
|
+
@accessor.auth_manager.add_auth_handler(basic_handler)
|
350
|
+
resource = @accessor.resource(@uri)
|
351
|
+
resp = resource.get
|
352
|
+
|
353
|
+
resp.code.should == 200
|
354
|
+
end
|
355
|
+
|
356
|
+
it 'should not keep trying to authenticate with incorrect credentials' do
|
357
|
+
basic_handler = Resourceful::BasicAuthenticator.new('Test Auth', 'admin', 'well-known')
|
358
|
+
@accessor.auth_manager.add_auth_handler(basic_handler)
|
359
|
+
resource = @accessor.resource(@uri)
|
360
|
+
|
361
|
+
lambda {
|
362
|
+
resource.get
|
363
|
+
}.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
|
364
|
+
end
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
describe 'digest' do
|
369
|
+
before do
|
370
|
+
@uri = 'http://localhost:3000/auth/digest'
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'should be able to authenticate digest auth' do
|
374
|
+
pending
|
375
|
+
digest_handler = Resourceful::DigestAuthenticator.new('Test Auth', 'admin', 'secret')
|
376
|
+
@accessor.auth_manager.add_auth_handler(digest_handler)
|
377
|
+
resource = @accessor.resource(@uri)
|
378
|
+
resp = resource.get
|
379
|
+
|
380
|
+
resp.code.should == 200
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
describe 'error checking' do
|
388
|
+
|
389
|
+
it 'should raise InvalidResponse when response code is invalid'
|
390
|
+
|
391
|
+
describe 'client errors' do
|
392
|
+
|
393
|
+
it 'should raise when there is one'
|
394
|
+
|
395
|
+
end
|
396
|
+
|
397
|
+
describe 'server errors' do
|
398
|
+
|
399
|
+
it 'should raise when there is one'
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
end
|
404
|
+
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|
408
|
+
|