rest-client 0.8.2 → 0.9

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.

@@ -12,6 +12,10 @@ module RestClient
12
12
  # resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
13
13
  # resource.delete
14
14
  #
15
+ # With a timeout (seconds):
16
+ #
17
+ # RestClient::Resource.new('http://slow', :timeout => 10)
18
+ #
15
19
  # You can also use resources to share common headers. For headers keys,
16
20
  # symbols are converted to strings. Example:
17
21
  #
@@ -38,37 +42,37 @@ module RestClient
38
42
  end
39
43
 
40
44
  def get(additional_headers={})
41
- Request.execute(:method => :get,
45
+ Request.execute(options.merge(
46
+ :method => :get,
42
47
  :url => url,
43
- :user => user,
44
- :password => password,
45
- :headers => headers.merge(additional_headers))
48
+ :headers => headers.merge(additional_headers)
49
+ ))
46
50
  end
47
51
 
48
52
  def post(payload, additional_headers={})
49
- Request.execute(:method => :post,
53
+ Request.execute(options.merge(
54
+ :method => :post,
50
55
  :url => url,
51
56
  :payload => payload,
52
- :user => user,
53
- :password => password,
54
- :headers => headers.merge(additional_headers))
57
+ :headers => headers.merge(additional_headers)
58
+ ))
55
59
  end
56
60
 
57
61
  def put(payload, additional_headers={})
58
- Request.execute(:method => :put,
62
+ Request.execute(options.merge(
63
+ :method => :put,
59
64
  :url => url,
60
65
  :payload => payload,
61
- :user => user,
62
- :password => password,
63
- :headers => headers.merge(additional_headers))
66
+ :headers => headers.merge(additional_headers)
67
+ ))
64
68
  end
65
69
 
66
70
  def delete(additional_headers={})
67
- Request.execute(:method => :delete,
71
+ Request.execute(options.merge(
72
+ :method => :delete,
68
73
  :url => url,
69
- :user => user,
70
- :password => password,
71
- :headers => headers.merge(additional_headers))
74
+ :headers => headers.merge(additional_headers)
75
+ ))
72
76
  end
73
77
 
74
78
  def to_s
@@ -87,6 +91,10 @@ module RestClient
87
91
  options[:headers] || {}
88
92
  end
89
93
 
94
+ def timeout
95
+ options[:timeout]
96
+ end
97
+
90
98
  # Construct a subresource, preserving authentication.
91
99
  #
92
100
  # Example:
@@ -0,0 +1,35 @@
1
+ module RestClient
2
+ # The response from RestClient looks like a string, but is actually one of
3
+ # these. 99% of the time you're making a rest call all you care about is
4
+ # the body, but on the occassion you want to fetch the headers you can:
5
+ #
6
+ # RestClient.get('http://example.com').headers[:content_type]
7
+ #
8
+ class Response < String
9
+ attr_reader :net_http_res
10
+
11
+ def initialize(string, net_http_res)
12
+ @net_http_res = net_http_res
13
+ super string
14
+ end
15
+
16
+ # HTTP status code, always 200 since RestClient throws exceptions for
17
+ # other codes.
18
+ def code
19
+ @code ||= @net_http_res.code.to_i
20
+ end
21
+
22
+ # A hash of the headers, beautified with symbols and underscores.
23
+ # e.g. "Content-type" will become :content_type.
24
+ def headers
25
+ @headers ||= self.class.beautify_headers(@net_http_res.to_hash)
26
+ end
27
+
28
+ def self.beautify_headers(headers)
29
+ headers.inject({}) do |out, (key, value)|
30
+ out[key.gsub(/-/, '_').to_sym] = value.first
31
+ out
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,4 +1,4 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
3
 
4
- require File.dirname(__FILE__) + '/../lib/rest_client'
4
+ require File.dirname(__FILE__) + '/../lib/restclient'
@@ -0,0 +1,296 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe RestClient::Request do
4
+ before do
5
+ @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload')
6
+
7
+ @uri = mock("uri")
8
+ @uri.stub!(:request_uri).and_return('/resource')
9
+ @uri.stub!(:host).and_return('some')
10
+ @uri.stub!(:port).and_return(80)
11
+
12
+ @net = mock("net::http base")
13
+ @http = mock("net::http connection")
14
+ Net::HTTP.stub!(:new).and_return(@net)
15
+ @net.stub!(:start).and_yield(@http)
16
+ @net.stub!(:use_ssl=)
17
+ @net.stub!(:verify_mode=)
18
+ end
19
+
20
+ it "requests xml mimetype" do
21
+ @request.default_headers[:accept].should == 'application/xml'
22
+ end
23
+
24
+ it "decodes an uncompressed result body by passing it straight through" do
25
+ @request.decode(nil, 'xyz').should == 'xyz'
26
+ end
27
+
28
+ it "decodes a gzip body" do
29
+ @request.decode('gzip', "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000").should == "i'm gziped\n"
30
+ end
31
+
32
+ it "decodes a deflated body" do
33
+ @request.decode('deflate', "x\234+\316\317MUHIM\313I,IMQ(I\255(\001\000A\223\006\363").should == "some deflated text"
34
+ end
35
+
36
+ it "processes a successful result" do
37
+ res = mock("result")
38
+ res.stub!(:code).and_return("200")
39
+ res.stub!(:body).and_return('body')
40
+ res.stub!(:[]).with('content-encoding').and_return(nil)
41
+ @request.process_result(res).should == 'body'
42
+ end
43
+
44
+ it "parses a url into a URI object" do
45
+ URI.should_receive(:parse).with('http://example.com/resource')
46
+ @request.parse_url('http://example.com/resource')
47
+ end
48
+
49
+ it "adds http:// to the front of resources specified in the syntax example.com/resource" do
50
+ URI.should_receive(:parse).with('http://example.com/resource')
51
+ @request.parse_url('example.com/resource')
52
+ end
53
+
54
+ it "extracts the username and password when parsing http://user:password@example.com/" do
55
+ URI.stub!(:parse).and_return(mock('uri', :user => 'joe', :password => 'pass1'))
56
+ @request.parse_url_with_auth('http://joe:pass1@example.com/resource')
57
+ @request.user.should == 'joe'
58
+ @request.password.should == 'pass1'
59
+ end
60
+
61
+ it "doesn't overwrite user and password (which may have already been set by the Resource constructor) if there is no user/password in the url" do
62
+ URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
63
+ @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :user => 'beth', :password => 'pass2')
64
+ @request.parse_url_with_auth('http://example.com/resource')
65
+ @request.user.should == 'beth'
66
+ @request.password.should == 'pass2'
67
+ end
68
+
69
+ it "determines the Net::HTTP class to instantiate by the method name" do
70
+ @request.net_http_request_class(:put).should == Net::HTTP::Put
71
+ end
72
+
73
+ it "merges user headers with the default headers" do
74
+ @request.should_receive(:default_headers).and_return({ '1' => '2' })
75
+ @request.make_headers('3' => '4').should == { '1' => '2', '3' => '4' }
76
+ end
77
+
78
+ it "prefers the user header when the same header exists in the defaults" do
79
+ @request.should_receive(:default_headers).and_return({ '1' => '2' })
80
+ @request.make_headers('1' => '3').should == { '1' => '3' }
81
+ end
82
+
83
+ it "converts header symbols from :content_type to 'Content-type'" do
84
+ @request.should_receive(:default_headers).and_return({})
85
+ @request.make_headers(:content_type => 'abc').should == { 'Content-type' => 'abc' }
86
+ end
87
+
88
+ it "converts header values to strings" do
89
+ @request.make_headers('A' => 1)['A'].should == '1'
90
+ end
91
+
92
+ it "executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do
93
+ @request.should_receive(:parse_url_with_auth).with('http://some/resource').and_return(@uri)
94
+ klass = mock("net:http class")
95
+ @request.should_receive(:net_http_request_class).with(:put).and_return(klass)
96
+ klass.should_receive(:new).and_return('result')
97
+ @request.should_receive(:transmit).with(@uri, 'result', 'payload')
98
+ @request.execute_inner
99
+ end
100
+
101
+ it "transmits the request with Net::HTTP" do
102
+ @http.should_receive(:request).with('req', 'payload')
103
+ @request.should_receive(:process_result)
104
+ @request.should_receive(:response_log)
105
+ @request.transmit(@uri, 'req', 'payload')
106
+ end
107
+
108
+ it "uses SSL when the URI refers to a https address" do
109
+ @uri.stub!(:is_a?).with(URI::HTTPS).and_return(true)
110
+ @net.should_receive(:use_ssl=).with(true)
111
+ @http.stub!(:request)
112
+ @request.stub!(:process_result)
113
+ @request.stub!(:response_log)
114
+ @request.transmit(@uri, 'req', 'payload')
115
+ end
116
+
117
+ it "sends nil payloads" do
118
+ @http.should_receive(:request).with('req', nil)
119
+ @request.should_receive(:process_result)
120
+ @request.stub!(:response_log)
121
+ @request.transmit(@uri, 'req', nil)
122
+ end
123
+
124
+ it "passes non-hash payloads straight through" do
125
+ @request.process_payload("x").should == "x"
126
+ end
127
+
128
+ it "converts a hash payload to urlencoded data" do
129
+ @request.process_payload(:a => 'b c+d').should == "a=b%20c%2Bd"
130
+ end
131
+
132
+ it "accepts nested hashes in payload" do
133
+ payload = @request.process_payload(:user => { :name => 'joe', :location => { :country => 'USA', :state => 'CA' }})
134
+ payload.should include('user[name]=joe')
135
+ payload.should include('user[location][country]=USA')
136
+ payload.should include('user[location][state]=CA')
137
+ end
138
+
139
+ it "set urlencoded content_type header on hash payloads" do
140
+ @request.process_payload(:a => 1)
141
+ @request.headers[:content_type].should == 'application/x-www-form-urlencoded'
142
+ end
143
+
144
+ it "sets up the credentials prior to the request" do
145
+ @http.stub!(:request)
146
+ @request.stub!(:process_result)
147
+ @request.stub!(:response_log)
148
+
149
+ @request.stub!(:user).and_return('joe')
150
+ @request.stub!(:password).and_return('mypass')
151
+ @request.should_receive(:setup_credentials).with('req')
152
+
153
+ @request.transmit(@uri, 'req', nil)
154
+ end
155
+
156
+ it "does not attempt to send any credentials if user is nil" do
157
+ @request.stub!(:user).and_return(nil)
158
+ req = mock("request")
159
+ req.should_not_receive(:basic_auth)
160
+ @request.setup_credentials(req)
161
+ end
162
+
163
+ it "setup credentials when there's a user" do
164
+ @request.stub!(:user).and_return('joe')
165
+ @request.stub!(:password).and_return('mypass')
166
+ req = mock("request")
167
+ req.should_receive(:basic_auth).with('joe', 'mypass')
168
+ @request.setup_credentials(req)
169
+ end
170
+
171
+ it "catches EOFError and shows the more informative ServerBrokeConnection" do
172
+ @http.stub!(:request).and_raise(EOFError)
173
+ lambda { @request.transmit(@uri, 'req', nil) }.should raise_error(RestClient::ServerBrokeConnection)
174
+ end
175
+
176
+ it "execute calls execute_inner" do
177
+ @request.should_receive(:execute_inner)
178
+ @request.execute
179
+ end
180
+
181
+ it "class method execute wraps constructor" do
182
+ req = mock("rest request")
183
+ RestClient::Request.should_receive(:new).with(1 => 2).and_return(req)
184
+ req.should_receive(:execute)
185
+ RestClient::Request.execute(1 => 2)
186
+ end
187
+
188
+ it "raises a Redirect with the new location when the response is in the 30x range" do
189
+ res = mock('response', :code => '301', :header => { 'Location' => 'http://new/resource' })
190
+ lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://new/resource'}
191
+ end
192
+
193
+ it "handles redirects with relative paths" do
194
+ res = mock('response', :code => '301', :header => { 'Location' => 'index' })
195
+ lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://some/index' }
196
+ end
197
+
198
+ it "handles redirects with absolute paths" do
199
+ @request.instance_variable_set('@url', 'http://some/place/else')
200
+ res = mock('response', :code => '301', :header => { 'Location' => '/index' })
201
+ lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://some/index' }
202
+ end
203
+
204
+ it "raises Unauthorized when the response is 401" do
205
+ res = mock('response', :code => '401')
206
+ lambda { @request.process_result(res) }.should raise_error(RestClient::Unauthorized)
207
+ end
208
+
209
+ it "raises ResourceNotFound when the response is 404" do
210
+ res = mock('response', :code => '404')
211
+ lambda { @request.process_result(res) }.should raise_error(RestClient::ResourceNotFound)
212
+ end
213
+
214
+ it "raises RequestFailed otherwise" do
215
+ res = mock('response', :code => '500')
216
+ lambda { @request.process_result(res) }.should raise_error(RestClient::RequestFailed)
217
+ end
218
+
219
+ it "creates a proxy class if a proxy url is given" do
220
+ RestClient.stub!(:proxy).and_return("http://example.com/")
221
+ @request.net_http_class.should include(Net::HTTP::ProxyDelta)
222
+ end
223
+
224
+ it "creates a non-proxy class if a proxy url is not given" do
225
+ @request.net_http_class.should_not include(Net::HTTP::ProxyDelta)
226
+ end
227
+
228
+ it "logs a get request" do
229
+ RestClient::Request.new(:method => :get, :url => 'http://url').request_log.should ==
230
+ 'RestClient.get "http://url"'
231
+ end
232
+
233
+ it "logs a post request with a small payload" do
234
+ RestClient::Request.new(:method => :post, :url => 'http://url', :payload => 'foo').request_log.should ==
235
+ 'RestClient.post "http://url", "foo"'
236
+ end
237
+
238
+ it "logs a post request with a large payload" do
239
+ RestClient::Request.new(:method => :post, :url => 'http://url', :payload => ('x' * 1000)).request_log.should ==
240
+ 'RestClient.post "http://url", "(1000 byte payload)"'
241
+ end
242
+
243
+ it "logs input headers as a hash" do
244
+ RestClient::Request.new(:method => :get, :url => 'http://url', :headers => { :accept => 'text/plain' }).request_log.should ==
245
+ 'RestClient.get "http://url", :accept=>"text/plain"'
246
+ end
247
+
248
+ it "logs a response including the status code, content type, and result body size in bytes" do
249
+ res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
250
+ res.stub!(:[]).with('Content-type').and_return('text/html')
251
+ @request.response_log(res).should == "# => 200 OK | text/html 4 bytes"
252
+ end
253
+
254
+ it "logs a response with a nil Content-type" do
255
+ res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
256
+ res.stub!(:[]).with('Content-type').and_return(nil)
257
+ @request.response_log(res).should == "# => 200 OK | 4 bytes"
258
+ end
259
+
260
+ it "strips the charset from the response content type" do
261
+ res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
262
+ res.stub!(:[]).with('Content-type').and_return('text/html; charset=utf-8')
263
+ @request.response_log(res).should == "# => 200 OK | text/html 4 bytes"
264
+ end
265
+
266
+ it "displays the log to stdout" do
267
+ RestClient.stub!(:log).and_return('stdout')
268
+ STDOUT.should_receive(:puts).with('xyz')
269
+ @request.display_log('xyz')
270
+ end
271
+
272
+ it "displays the log to stderr" do
273
+ RestClient.stub!(:log).and_return('stderr')
274
+ STDERR.should_receive(:puts).with('xyz')
275
+ @request.display_log('xyz')
276
+ end
277
+
278
+ it "append the log to the requested filename" do
279
+ RestClient.stub!(:log).and_return('/tmp/restclient.log')
280
+ f = mock('file handle')
281
+ File.should_receive(:open).with('/tmp/restclient.log', 'a').and_yield(f)
282
+ f.should_receive(:puts).with('xyz')
283
+ @request.display_log('xyz')
284
+ end
285
+
286
+ it "set read_timeout" do
287
+ @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => 123)
288
+ @http.stub!(:request)
289
+ @request.stub!(:process_result)
290
+ @request.stub!(:response_log)
291
+
292
+ @http.should_receive(:read_timeout=).with(123)
293
+
294
+ @request.transmit(@uri, 'req', nil)
295
+ end
296
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe RestClient::Response do
4
+ before do
5
+ @net_http_res = mock('net http response')
6
+ @response = RestClient::Response.new('abc', @net_http_res)
7
+ end
8
+
9
+ it "behaves like string" do
10
+ @response.should == 'abc'
11
+ end
12
+
13
+ it "fetches the numeric response code" do
14
+ @net_http_res.should_receive(:code).and_return('200')
15
+ @response.code.should == 200
16
+ end
17
+
18
+ it "beautifies the headers by turning the keys to symbols" do
19
+ h = RestClient::Response.beautify_headers('content-type' => [ 'x' ])
20
+ h.keys.first.should == :content_type
21
+ end
22
+
23
+ it "beautifies the headers by turning the values to strings instead of one-element arrays" do
24
+ h = RestClient::Response.beautify_headers('x' => [ 'text/html' ] )
25
+ h.values.first.should == 'text/html'
26
+ end
27
+
28
+ it "fetches the headers" do
29
+ @net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
30
+ @response.headers.should == { :content_type => 'text/html' }
31
+ end
32
+
33
+ it "can access the net http result directly" do
34
+ @response.net_http_res.should == @net_http_res
35
+ end
36
+ end