rest-client 1.1.0 → 1.6.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.
Potentially problematic release.
This version of rest-client might be problematic. Click here for more details.
- data/README.rdoc +190 -13
- data/Rakefile +35 -26
- data/VERSION +1 -1
- data/bin/restclient +43 -38
- data/history.md +112 -0
- data/lib/rest-client.rb +2 -0
- data/lib/restclient.rb +107 -40
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +187 -82
- data/lib/restclient/net_http_ext.rb +11 -11
- data/lib/restclient/payload.rb +217 -168
- data/lib/restclient/raw_response.rb +28 -24
- data/lib/restclient/request.rb +310 -248
- data/lib/restclient/resource.rb +155 -132
- data/lib/restclient/response.rb +19 -15
- data/spec/abstract_response_spec.rb +85 -0
- data/spec/base.rb +6 -0
- data/spec/exceptions_spec.rb +79 -46
- data/spec/integration/certs/equifax.crt +19 -0
- data/spec/integration/certs/verisign.crt +14 -0
- data/spec/integration/request_spec.rb +25 -0
- data/spec/integration_spec.rb +38 -0
- data/spec/payload_spec.rb +187 -99
- data/spec/raw_response_spec.rb +12 -12
- data/spec/request2_spec.rb +27 -0
- data/spec/request_spec.rb +526 -494
- data/spec/resource_spec.rb +131 -72
- data/spec/response_spec.rb +166 -18
- data/spec/restclient_spec.rb +70 -50
- metadata +79 -20
- data/lib/restclient/mixin/response.rb +0 -48
- data/spec/mixin/response_spec.rb +0 -46
data/lib/restclient/resource.rb
CHANGED
@@ -1,146 +1,169 @@
|
|
1
1
|
module RestClient
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
2
|
+
# A class that can be instantiated for access to a RESTful resource,
|
3
|
+
# including authentication.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
#
|
7
|
+
# resource = RestClient::Resource.new('http://some/resource')
|
8
|
+
# jpg = resource.get(:accept => 'image/jpg')
|
9
|
+
#
|
10
|
+
# With HTTP basic authentication:
|
11
|
+
#
|
12
|
+
# resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
|
13
|
+
# resource.delete
|
14
|
+
#
|
15
|
+
# With a timeout (seconds):
|
16
|
+
#
|
17
|
+
# RestClient::Resource.new('http://slow', :timeout => 10)
|
18
|
+
#
|
19
|
+
# With an open timeout (seconds):
|
20
|
+
#
|
21
|
+
# RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
|
22
|
+
#
|
23
|
+
# You can also use resources to share common headers. For headers keys,
|
24
|
+
# symbols are converted to strings. Example:
|
25
|
+
#
|
26
|
+
# resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
|
27
|
+
#
|
28
|
+
# This header will be transported as X-Client-Version (notice the X prefix,
|
29
|
+
# capitalization and hyphens)
|
30
|
+
#
|
31
|
+
# Use the [] syntax to allocate subresources:
|
32
|
+
#
|
33
|
+
# site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
|
34
|
+
# site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
|
35
|
+
#
|
36
|
+
class Resource
|
37
|
+
attr_reader :url, :options, :block
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
def initialize(url, options={}, backwards_compatibility=nil, &block)
|
40
|
+
@url = url
|
41
|
+
@block = block
|
42
|
+
if options.class == Hash
|
43
|
+
@options = options
|
44
|
+
else # compatibility with previous versions
|
45
|
+
@options = { :user => options, :password => backwards_compatibility }
|
46
|
+
end
|
47
|
+
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
49
|
+
def get(additional_headers={}, &block)
|
50
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
51
|
+
Request.execute(options.merge(
|
52
|
+
:method => :get,
|
53
|
+
:url => url,
|
54
|
+
:headers => headers), &(block || @block))
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
57
|
+
def head(additional_headers={}, &block)
|
58
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
59
|
+
Request.execute(options.merge(
|
60
|
+
:method => :head,
|
61
|
+
:url => url,
|
62
|
+
:headers => headers), &(block || @block))
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
65
|
+
def post(payload, additional_headers={}, &block)
|
66
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
67
|
+
Request.execute(options.merge(
|
68
|
+
:method => :post,
|
69
|
+
:url => url,
|
70
|
+
:payload => payload,
|
71
|
+
:headers => headers), &(block || @block))
|
72
|
+
end
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
def put(payload, additional_headers={}, &block)
|
75
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
76
|
+
Request.execute(options.merge(
|
77
|
+
:method => :put,
|
78
|
+
:url => url,
|
79
|
+
:payload => payload,
|
80
|
+
:headers => headers), &(block || @block))
|
81
|
+
end
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
83
|
+
def patch(payload, additional_headers={}, &block)
|
84
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
85
|
+
Request.execute(options.merge(
|
86
|
+
:method => :patch,
|
87
|
+
:url => url,
|
88
|
+
:payload => payload,
|
89
|
+
:headers => headers), &(block || @block))
|
90
|
+
end
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
|
92
|
+
def delete(additional_headers={}, &block)
|
93
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
94
|
+
Request.execute(options.merge(
|
95
|
+
:method => :delete,
|
96
|
+
:url => url,
|
97
|
+
:headers => headers), &(block || @block))
|
98
|
+
end
|
89
99
|
|
90
|
-
|
91
|
-
|
92
|
-
|
100
|
+
def to_s
|
101
|
+
url
|
102
|
+
end
|
93
103
|
|
94
|
-
|
95
|
-
|
96
|
-
|
104
|
+
def user
|
105
|
+
options[:user]
|
106
|
+
end
|
97
107
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
def open_timeout
|
103
|
-
options[:open_timeout]
|
104
|
-
end
|
108
|
+
def password
|
109
|
+
options[:password]
|
110
|
+
end
|
105
111
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
#
|
110
|
-
# site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
|
111
|
-
# site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
|
112
|
-
#
|
113
|
-
# This is especially useful if you wish to define your site in one place and
|
114
|
-
# call it in multiple locations:
|
115
|
-
#
|
116
|
-
# def orders
|
117
|
-
# RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
|
118
|
-
# end
|
119
|
-
#
|
120
|
-
# orders.get # GET http://example.com/orders
|
121
|
-
# orders['1'].get # GET http://example.com/orders/1
|
122
|
-
# orders['1/items'].delete # DELETE http://example.com/orders/1/items
|
123
|
-
#
|
124
|
-
# Nest resources as far as you want:
|
125
|
-
#
|
126
|
-
# site = RestClient::Resource.new('http://example.com')
|
127
|
-
# posts = site['posts']
|
128
|
-
# first_post = posts['1']
|
129
|
-
# comments = first_post['comments']
|
130
|
-
# comments.post 'Hello', :content_type => 'text/plain'
|
131
|
-
#
|
132
|
-
def [](suburl)
|
133
|
-
self.class.new(concat_urls(url, suburl), options)
|
134
|
-
end
|
112
|
+
def headers
|
113
|
+
options[:headers] || {}
|
114
|
+
end
|
135
115
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
116
|
+
def timeout
|
117
|
+
options[:timeout]
|
118
|
+
end
|
119
|
+
|
120
|
+
def open_timeout
|
121
|
+
options[:open_timeout]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Construct a subresource, preserving authentication.
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
#
|
128
|
+
# site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
|
129
|
+
# site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
|
130
|
+
#
|
131
|
+
# This is especially useful if you wish to define your site in one place and
|
132
|
+
# call it in multiple locations:
|
133
|
+
#
|
134
|
+
# def orders
|
135
|
+
# RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# orders.get # GET http://example.com/orders
|
139
|
+
# orders['1'].get # GET http://example.com/orders/1
|
140
|
+
# orders['1/items'].delete # DELETE http://example.com/orders/1/items
|
141
|
+
#
|
142
|
+
# Nest resources as far as you want:
|
143
|
+
#
|
144
|
+
# site = RestClient::Resource.new('http://example.com')
|
145
|
+
# posts = site['posts']
|
146
|
+
# first_post = posts['1']
|
147
|
+
# comments = first_post['comments']
|
148
|
+
# comments.post 'Hello', :content_type => 'text/plain'
|
149
|
+
#
|
150
|
+
def [](suburl, &new_block)
|
151
|
+
case
|
152
|
+
when block_given? then self.class.new(concat_urls(url, suburl), options, &new_block)
|
153
|
+
when block then self.class.new(concat_urls(url, suburl), options, &block)
|
154
|
+
else
|
155
|
+
self.class.new(concat_urls(url, suburl), options)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def concat_urls(url, suburl) # :nodoc:
|
160
|
+
url = url.to_s
|
161
|
+
suburl = suburl.to_s
|
162
|
+
if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
|
163
|
+
url + suburl
|
164
|
+
else
|
165
|
+
"#{url}/#{suburl}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
146
169
|
end
|
data/lib/restclient/response.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/mixin/response'
|
2
|
-
|
3
1
|
module RestClient
|
4
|
-
# The response from RestClient looks like a string, but is actually one of
|
5
|
-
# these. 99% of the time you're making a rest call all you care about is
|
6
|
-
# the body, but on the occassion you want to fetch the headers you can:
|
7
|
-
#
|
8
|
-
# RestClient.get('http://example.com').headers[:content_type]
|
9
|
-
#
|
10
|
-
class Response < String
|
11
2
|
|
12
|
-
|
3
|
+
# A Response from RestClient, you can access the response body, the code or the headers.
|
4
|
+
#
|
5
|
+
module Response
|
6
|
+
|
7
|
+
include AbstractResponse
|
8
|
+
|
9
|
+
attr_accessor :args, :body, :net_http_res
|
10
|
+
|
11
|
+
def body
|
12
|
+
self
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def Response.create body, net_http_res, args
|
16
|
+
result = body || ''
|
17
|
+
result.extend Response
|
18
|
+
result.net_http_res = net_http_res
|
19
|
+
result.args = args
|
20
|
+
result
|
21
|
+
end
|
18
22
|
|
19
|
-
|
23
|
+
end
|
20
24
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.join( File.dirname(File.expand_path(__FILE__)), 'base')
|
2
|
+
|
3
|
+
describe RestClient::AbstractResponse do
|
4
|
+
|
5
|
+
class MyAbstractResponse
|
6
|
+
|
7
|
+
include RestClient::AbstractResponse
|
8
|
+
|
9
|
+
attr_accessor :size
|
10
|
+
|
11
|
+
def initialize net_http_res, args
|
12
|
+
@net_http_res = net_http_res
|
13
|
+
@args = args
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
@net_http_res = mock('net http response')
|
20
|
+
@response = MyAbstractResponse.new(@net_http_res, {})
|
21
|
+
end
|
22
|
+
|
23
|
+
it "fetches the numeric response code" do
|
24
|
+
@net_http_res.should_receive(:code).and_return('200')
|
25
|
+
@response.code.should == 200
|
26
|
+
end
|
27
|
+
|
28
|
+
it "has a nice description" do
|
29
|
+
@net_http_res.should_receive(:to_hash).and_return({'Content-Type' => ['application/pdf']})
|
30
|
+
@net_http_res.should_receive(:code).and_return('200')
|
31
|
+
@response.description == '200 OK | application/pdf bytes\n'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "beautifies the headers by turning the keys to symbols" do
|
35
|
+
h = RestClient::AbstractResponse.beautify_headers('content-type' => [ 'x' ])
|
36
|
+
h.keys.first.should == :content_type
|
37
|
+
end
|
38
|
+
|
39
|
+
it "beautifies the headers by turning the values to strings instead of one-element arrays" do
|
40
|
+
h = RestClient::AbstractResponse.beautify_headers('x' => [ 'text/html' ] )
|
41
|
+
h.values.first.should == 'text/html'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "fetches the headers" do
|
45
|
+
@net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
|
46
|
+
@response.headers.should == { :content_type => 'text/html' }
|
47
|
+
end
|
48
|
+
|
49
|
+
it "extracts cookies from response headers" do
|
50
|
+
@net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
|
51
|
+
@response.cookies.should == { 'session_id' => '1' }
|
52
|
+
end
|
53
|
+
|
54
|
+
it "extract strange cookies" do
|
55
|
+
@net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/'])
|
56
|
+
@response.cookies.should == { 'session_id' => 'ZJ%2FHQVH6YE+rVkTpn0zvTQ%3D%3D' }
|
57
|
+
end
|
58
|
+
|
59
|
+
it "doesn't escape cookies" do
|
60
|
+
@net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca; path=/'])
|
61
|
+
@response.cookies.should == { 'session_id' => 'BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca' }
|
62
|
+
end
|
63
|
+
|
64
|
+
it "can access the net http result directly" do
|
65
|
+
@response.net_http_res.should == @net_http_res
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#return!" do
|
69
|
+
it "should return the response itself on 200-codes" do
|
70
|
+
@net_http_res.should_receive(:code).and_return('200')
|
71
|
+
@response.return!.should be_equal(@response)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should raise RequestFailed on unknown codes" do
|
75
|
+
@net_http_res.should_receive(:code).and_return('1000')
|
76
|
+
lambda { @response.return! }.should raise_error RestClient::RequestFailed
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise an error on a redirection after non-GET/HEAD requests" do
|
80
|
+
@net_http_res.should_receive(:code).and_return('301')
|
81
|
+
@response.args.merge(:method => :put)
|
82
|
+
lambda { @response.return! }.should raise_error RestClient::RequestFailed
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/spec/base.rb
CHANGED
data/spec/exceptions_spec.rb
CHANGED
@@ -1,65 +1,98 @@
|
|
1
|
-
require File.dirname(__FILE__)
|
1
|
+
require File.join( File.dirname(File.expand_path(__FILE__)), 'base')
|
2
|
+
|
3
|
+
require 'webmock/rspec'
|
4
|
+
include WebMock
|
2
5
|
|
3
6
|
describe RestClient::Exception do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
+
it "returns a 'message' equal to the class name if the message is not set, because 'message' should not be nil" do
|
8
|
+
e = RestClient::Exception.new
|
9
|
+
e.message.should == "RestClient::Exception"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns the 'message' that was set" do
|
13
|
+
e = RestClient::Exception.new
|
14
|
+
message = "An explicitly set message"
|
15
|
+
e.message = message
|
16
|
+
e.message.should == message
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sets the exception message to ErrorMessage" do
|
20
|
+
RestClient::ResourceNotFound.new.message.should == 'Resource Not Found'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "contains exceptions in RestClient" do
|
24
|
+
RestClient::Unauthorized.new.should be_a_kind_of(RestClient::Exception)
|
25
|
+
RestClient::ServerBrokeConnection.new.should be_a_kind_of(RestClient::Exception)
|
26
|
+
end
|
27
|
+
end
|
7
28
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
29
|
+
describe RestClient::ServerBrokeConnection do
|
30
|
+
it "should have a default message of 'Server broke connection'" do
|
31
|
+
e = RestClient::ServerBrokeConnection.new
|
32
|
+
e.message.should == 'Server broke connection'
|
33
|
+
end
|
12
34
|
end
|
13
35
|
|
14
36
|
describe RestClient::RequestFailed do
|
15
|
-
|
16
|
-
|
17
|
-
|
37
|
+
before do
|
38
|
+
@response = mock('HTTP Response', :code => '502')
|
39
|
+
end
|
18
40
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
41
|
+
it "stores the http response on the exception" do
|
42
|
+
response = "response"
|
43
|
+
begin
|
44
|
+
raise RestClient::RequestFailed, response
|
45
|
+
rescue RestClient::RequestFailed => e
|
46
|
+
e.response.should == response
|
47
|
+
end
|
48
|
+
end
|
26
49
|
|
27
|
-
|
28
|
-
|
29
|
-
|
50
|
+
it "http_code convenience method for fetching the code as an integer" do
|
51
|
+
RestClient::RequestFailed.new(@response).http_code.should == 502
|
52
|
+
end
|
30
53
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
RestClient::RequestFailed.new(@response).http_body.should == 'plain body'
|
36
|
-
end
|
54
|
+
it "http_body convenience method for fetching the body (decoding when necessary)" do
|
55
|
+
RestClient::RequestFailed.new(@response).http_code.should == 502
|
56
|
+
RestClient::RequestFailed.new(@response).message.should == 'HTTP status code 502'
|
57
|
+
end
|
37
58
|
|
38
|
-
|
39
|
-
|
40
|
-
|
59
|
+
it "shows the status code in the message" do
|
60
|
+
RestClient::RequestFailed.new(@response).to_s.should match(/502/)
|
61
|
+
end
|
41
62
|
end
|
42
63
|
|
43
64
|
describe RestClient::ResourceNotFound do
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
65
|
+
it "also has the http response attached" do
|
66
|
+
response = "response"
|
67
|
+
begin
|
68
|
+
raise RestClient::ResourceNotFound, response
|
69
|
+
rescue RestClient::ResourceNotFound => e
|
70
|
+
e.response.should == response
|
71
|
+
end
|
72
|
+
end
|
51
73
|
end
|
52
74
|
|
53
75
|
describe "backwards compatibility" do
|
54
|
-
|
55
|
-
|
56
|
-
|
76
|
+
it "alias RestClient::Request::Redirect to RestClient::Redirect" do
|
77
|
+
RestClient::Request::Redirect.should == RestClient::Redirect
|
78
|
+
end
|
79
|
+
|
80
|
+
it "alias RestClient::Request::Unauthorized to RestClient::Unauthorized" do
|
81
|
+
RestClient::Request::Unauthorized.should == RestClient::Unauthorized
|
82
|
+
end
|
57
83
|
|
58
|
-
|
59
|
-
|
60
|
-
|
84
|
+
it "alias RestClient::Request::RequestFailed to RestClient::RequestFailed" do
|
85
|
+
RestClient::Request::RequestFailed.should == RestClient::RequestFailed
|
86
|
+
end
|
61
87
|
|
62
|
-
|
63
|
-
|
64
|
-
|
88
|
+
it "make the exception's response act like an Net::HTTPResponse" do
|
89
|
+
body = "body"
|
90
|
+
stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
|
91
|
+
begin
|
92
|
+
RestClient.get "www.example.com"
|
93
|
+
raise
|
94
|
+
rescue RestClient::ResourceNotFound => e
|
95
|
+
e.response.body.should == body
|
96
|
+
end
|
97
|
+
end
|
65
98
|
end
|