adamwiggins-rest-client 0.9.2 → 1.0

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/README.rdoc CHANGED
@@ -121,6 +121,18 @@ extract and set headers for them as needed:
121
121
  )
122
122
  # ...response body
123
123
 
124
+ == SSL Client Certificates
125
+
126
+ RestClient::Resource.new(
127
+ 'https://example.com',
128
+ :ssl_client_cert => OpenSSL::X509::Certificate.new(File.read("cert.pem")),
129
+ :ssl_client_key => OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"),
130
+ :ssl_ca_file => "ca_certificate.pem",
131
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER
132
+ ).get
133
+
134
+ Self-signed certificates can be generated with the openssl command-line tool.
135
+
124
136
  == Meta
125
137
 
126
138
  Written by Adam Wiggins (adam at heroku dot com)
@@ -128,7 +140,8 @@ Written by Adam Wiggins (adam at heroku dot com)
128
140
  Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro
129
141
  Belo, Rafael Souza, Rick Olson, Aman Gupta, Blake Mizerany, Brian Donovan, Ivan
130
142
  Makfinsky, Marc-André Cournoyer, Coda Hale, Tetsuo Watanabe, Dusty Doris,
131
- Lennon Day-Reynolds, James Edward Gray II, Cyril Rohr, and Juan Alvarez
143
+ Lennon Day-Reynolds, James Edward Gray II, Cyril Rohr, Juan Alvarez, and Adam
144
+ Jacob
132
145
 
133
146
  Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
134
147
 
data/Rakefile CHANGED
@@ -31,7 +31,7 @@ require 'rake/gempackagetask'
31
31
  require 'rake/rdoctask'
32
32
  require 'fileutils'
33
33
 
34
- version = "0.9.2"
34
+ version = "1.0"
35
35
  name = "rest-client"
36
36
 
37
37
  spec = Gem::Specification.new do |s|
@@ -1,12 +1,17 @@
1
+ require 'tempfile'
2
+
1
3
  module RestClient
2
4
  # This class is used internally by RestClient to send the request, but you can also
3
5
  # access it internally if you'd like to use a method not directly supported by the
4
6
  # main API. For example:
5
7
  #
6
8
  # RestClient::Request.execute(:method => :head, :url => 'http://example.com')
7
- #
9
+ #
8
10
  class Request
9
- attr_reader :method, :url, :payload, :headers, :cookies, :user, :password, :timeout, :open_timeout
11
+ attr_reader :method, :url, :payload, :headers,
12
+ :cookies, :user, :password, :timeout, :open_timeout,
13
+ :verify_ssl, :ssl_client_cert, :ssl_client_key, :ssl_ca_file,
14
+ :raw_response
10
15
 
11
16
  def self.execute(args)
12
17
  new(args).execute
@@ -16,12 +21,18 @@ module RestClient
16
21
  @method = args[:method] or raise ArgumentError, "must pass :method"
17
22
  @url = args[:url] or raise ArgumentError, "must pass :url"
18
23
  @headers = args[:headers] || {}
19
- @cookies = @headers.delete(:cookies) || args[:cookies] || {}
24
+ @cookies = @headers.delete(:cookies) || args[:cookies] || {}
20
25
  @payload = process_payload(args[:payload])
21
26
  @user = args[:user]
22
27
  @password = args[:password]
23
28
  @timeout = args[:timeout]
24
29
  @open_timeout = args[:open_timeout]
30
+ @raw_response = args[:raw_response] || false
31
+ @verify_ssl = args[:verify_ssl] || false
32
+ @ssl_client_cert = args[:ssl_client_cert] || nil
33
+ @ssl_client_key = args[:ssl_client_key] || nil
34
+ @ssl_ca_file = args[:ssl_ca_file] || nil
35
+ @tf = nil # If you are a raw request, this is your tempfile
25
36
  end
26
37
 
27
38
  def execute
@@ -37,13 +48,13 @@ module RestClient
37
48
  end
38
49
 
39
50
  def make_headers(user_headers)
40
- unless @cookies.empty?
41
- user_headers[:cookie] = @cookies.map {|key, val| "#{key.to_s}=#{val}" }.join('; ')
42
- end
51
+ unless @cookies.empty?
52
+ user_headers[:cookie] = @cookies.map {|key, val| "#{key.to_s}=#{val}" }.join('; ')
53
+ end
43
54
 
44
55
  default_headers.merge(user_headers).inject({}) do |final, (key, value)|
45
56
  final[key.to_s.gsub(/_/, '-').capitalize] = value.to_s
46
- final
57
+ final
47
58
  end
48
59
  end
49
60
 
@@ -94,19 +105,28 @@ module RestClient
94
105
 
95
106
  net = net_http_class.new(uri.host, uri.port)
96
107
  net.use_ssl = uri.is_a?(URI::HTTPS)
97
- net.verify_mode = OpenSSL::SSL::VERIFY_NONE
108
+ if @verify_ssl == false
109
+ net.verify_mode = OpenSSL::SSL::VERIFY_NONE
110
+ elsif @verify_ssl.is_a? Integer
111
+ net.verify_mode = @verify_ssl
112
+ end
113
+ net.cert = @ssl_client_cert if @ssl_client_cert
114
+ net.key = @ssl_client_key if @ssl_client_key
115
+ net.ca_file = @ssl_ca_file if @ssl_ca_file
98
116
  net.read_timeout = @timeout if @timeout
99
117
  net.open_timeout = @open_timeout if @open_timeout
100
118
 
101
119
  display_log request_log
102
120
 
103
121
  net.start do |http|
104
- res = http.request(req, payload)
122
+ res = http.request(req, payload) { |http_response| fetch_body(http_response) }
123
+ result = process_result(res)
105
124
  display_log response_log(res)
106
- string = process_result(res)
107
125
 
108
- if string or @method == :head
109
- Response.new(string, res)
126
+ if result.kind_of?(String) or @method == :head
127
+ Response.new(result, res)
128
+ elsif @raw_response
129
+ RawResponse.new(@tf, res)
110
130
  else
111
131
  nil
112
132
  end
@@ -121,9 +141,38 @@ module RestClient
121
141
  req.basic_auth(user, password) if user
122
142
  end
123
143
 
144
+ def fetch_body(http_response)
145
+ if @raw_response
146
+ # Taken from Chef, which as in turn...
147
+ # Stolen from http://www.ruby-forum.com/topic/166423
148
+ # Kudos to _why!
149
+ @tf = Tempfile.new("rest-client")
150
+ size, total = 0, http_response.header['Content-Length'].to_i
151
+ http_response.read_body do |chunk|
152
+ @tf.write(chunk)
153
+ size += chunk.size
154
+ if size == 0
155
+ display_log("#{@method} #{@url} done (0 length file)")
156
+ elsif total == 0
157
+ display_log("#{@method} #{@url} (zero content length)")
158
+ else
159
+ display_log("#{@method} #{@url} %d%% done (%d of %d)" % [(size * 100) / total, size, total])
160
+ end
161
+ end
162
+ @tf.close
163
+ @tf
164
+ else
165
+ http_response.read_body
166
+ end
167
+ http_response
168
+ end
169
+
124
170
  def process_result(res)
125
- if res.code =~ /\A2\d{2}\z/
126
- decode res['content-encoding'], res.body if res.body
171
+ if res.code =~ /\A2\d{2}\z/
172
+ # We don't decode raw requests
173
+ unless @raw_response
174
+ decode res['content-encoding'], res.body if res.body
175
+ end
127
176
  elsif %w(301 302 303).include? res.code
128
177
  url = res.header['Location']
129
178
 
@@ -164,7 +213,8 @@ module RestClient
164
213
  end
165
214
 
166
215
  def response_log(res)
167
- "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{(res.body) ? res.body.size : nil} bytes"
216
+ size = @raw_response ? File.size(@tf.path) : res.body.size
217
+ "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes"
168
218
  end
169
219
 
170
220
  def display_log(msg)
@@ -100,8 +100,8 @@ module RestClient
100
100
  end
101
101
 
102
102
  def open_timeout
103
- options[:open_timeout]
104
- end
103
+ options[:open_timeout]
104
+ end
105
105
 
106
106
  # Construct a subresource, preserving authentication.
107
107
  #
@@ -1,3 +1,5 @@
1
+ require File.dirname(__FILE__) + '/mixin/response'
2
+
1
3
  module RestClient
2
4
  # The response from RestClient looks like a string, but is actually one of
3
5
  # these. 99% of the time you're making a rest call all you care about is
@@ -6,41 +8,13 @@ module RestClient
6
8
  # RestClient.get('http://example.com').headers[:content_type]
7
9
  #
8
10
  class Response < String
9
- attr_reader :net_http_res
11
+
12
+ include RestClient::Mixin::Response
10
13
 
11
14
  def initialize(string, net_http_res)
12
15
  @net_http_res = net_http_res
13
16
  super(string || "")
14
17
  end
15
18
 
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
- # Hash of cookies extracted from response headers
29
- def cookies
30
- @cookies ||= (self.headers[:set_cookie] || "").split('; ').inject({}) do |out, raw_c|
31
- key, val = raw_c.split('=')
32
- unless %w(expires domain path secure).member?(key)
33
- out[key] = val
34
- end
35
- out
36
- end
37
- end
38
-
39
- def self.beautify_headers(headers)
40
- headers.inject({}) do |out, (key, value)|
41
- out[key.gsub(/-/, '_').to_sym] = value.first
42
- out
43
- end
44
- end
45
19
  end
46
20
  end
data/lib/restclient.rb CHANGED
@@ -4,7 +4,9 @@ require 'zlib'
4
4
  require 'stringio'
5
5
 
6
6
  require File.dirname(__FILE__) + '/restclient/request'
7
+ require File.dirname(__FILE__) + '/restclient/mixin/response'
7
8
  require File.dirname(__FILE__) + '/restclient/response'
9
+ require File.dirname(__FILE__) + '/restclient/raw_response'
8
10
  require File.dirname(__FILE__) + '/restclient/resource'
9
11
  require File.dirname(__FILE__) + '/restclient/exceptions'
10
12
 
data/rest-client.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rest-client"
3
- s.version = "0.9.2"
3
+ s.version = "1.0"
4
4
  s.summary = "Simple REST client for Ruby, inspired by microframework syntax for specifying actions."
5
5
  s.description = "A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
6
6
  s.author = "Adam Wiggins"
data/spec/request_spec.rb CHANGED
@@ -28,7 +28,7 @@ describe RestClient::Request do
28
28
  it "decodes a gzip body" do
29
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
30
  end
31
-
31
+
32
32
  it "ingores gzip for empty bodies" do
33
33
  @request.decode('gzip', '').should be_empty
34
34
  end
@@ -37,23 +37,23 @@ describe RestClient::Request do
37
37
  @request.decode('deflate', "x\234+\316\317MUHIM\313I,IMQ(I\255(\001\000A\223\006\363").should == "some deflated text"
38
38
  end
39
39
 
40
- it "processes a successful result" do
41
- res = mock("result")
42
- res.stub!(:code).and_return("200")
43
- res.stub!(:body).and_return('body')
44
- res.stub!(:[]).with('content-encoding').and_return(nil)
45
- @request.process_result(res).should == 'body'
46
- end
40
+ it "processes a successful result" do
41
+ res = mock("result")
42
+ res.stub!(:code).and_return("200")
43
+ res.stub!(:body).and_return('body')
44
+ res.stub!(:[]).with('content-encoding').and_return(nil)
45
+ @request.process_result(res).should == 'body'
46
+ end
47
47
 
48
- it "doesn't classify successful requests as failed" do
49
- 203.upto(206) do |code|
50
- res = mock("result")
51
- res.stub!(:code).and_return(code.to_s)
52
- res.stub!(:body).and_return("")
53
- res.stub!(:[]).with('content-encoding').and_return(nil)
54
- @request.process_result(res).should be_empty
55
- end
56
- end
48
+ it "doesn't classify successful requests as failed" do
49
+ 203.upto(206) do |code|
50
+ res = mock("result")
51
+ res.stub!(:code).and_return(code.to_s)
52
+ res.stub!(:body).and_return("")
53
+ res.stub!(:[]).with('content-encoding').and_return(nil)
54
+ @request.process_result(res).should be_empty
55
+ end
56
+ end
57
57
 
58
58
  it "parses a url into a URI object" do
59
59
  URI.should_receive(:parse).with('http://example.com/resource')
@@ -80,12 +80,12 @@ describe RestClient::Request do
80
80
  @request.password.should == 'pass2'
81
81
  end
82
82
 
83
- it "correctly formats cookies provided to the constructor" do
84
- URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
85
- @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {:session_id => '1' })
86
- @request.should_receive(:default_headers).and_return({'foo' => 'bar'})
87
- headers = @request.make_headers({}).should == { 'Foo' => 'bar', 'Cookie' => 'session_id=1'}
88
- end
83
+ it "correctly formats cookies provided to the constructor" do
84
+ URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
85
+ @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {:session_id => '1' })
86
+ @request.should_receive(:default_headers).and_return({'foo' => 'bar'})
87
+ headers = @request.make_headers({}).should == { 'Foo' => 'bar', 'Cookie' => 'session_id=1'}
88
+ end
89
89
 
90
90
  it "determines the Net::HTTP class to instantiate by the method name" do
91
91
  @request.net_http_request_class(:put).should == Net::HTTP::Put
@@ -243,7 +243,7 @@ describe RestClient::Request do
243
243
  end
244
244
 
245
245
  it "creates a non-proxy class if a proxy url is not given" do
246
- @request.net_http_class.should_not include(Net::HTTP::ProxyDelta)
246
+ @request.net_http_class.should_not include(Net::HTTP::ProxyDelta)
247
247
  end
248
248
 
249
249
  it "logs a get request" do
@@ -303,26 +303,154 @@ describe RestClient::Request do
303
303
  f.should_receive(:puts).with('xyz')
304
304
  @request.display_log('xyz')
305
305
  end
306
-
306
+
307
307
  it "set read_timeout" do
308
308
  @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => 123)
309
309
  @http.stub!(:request)
310
310
  @request.stub!(:process_result)
311
311
  @request.stub!(:response_log)
312
-
312
+
313
313
  @net.should_receive(:read_timeout=).with(123)
314
-
314
+
315
315
  @request.transmit(@uri, 'req', nil)
316
316
  end
317
-
317
+
318
318
  it "set open_timeout" do
319
319
  @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :open_timeout => 123)
320
320
  @http.stub!(:request)
321
321
  @request.stub!(:process_result)
322
322
  @request.stub!(:response_log)
323
-
323
+
324
324
  @net.should_receive(:open_timeout=).with(123)
325
-
325
+
326
326
  @request.transmit(@uri, 'req', nil)
327
327
  end
328
+
329
+ it "should default to not verifying ssl certificates" do
330
+ @request.verify_ssl.should == false
331
+ end
332
+
333
+ it "should set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is false" do
334
+ @net.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
335
+ @http.stub!(:request)
336
+ @request.stub!(:process_result)
337
+ @request.stub!(:response_log)
338
+ @request.transmit(@uri, 'req', 'payload')
339
+ end
340
+
341
+ it "should not set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is true" do
342
+ @request = RestClient::Request.new(:method => :put, :url => 'https://some/resource', :payload => 'payload', :verify_ssl => true)
343
+ @net.should_not_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
344
+ @http.stub!(:request)
345
+ @request.stub!(:process_result)
346
+ @request.stub!(:response_log)
347
+ @request.transmit(@uri, 'req', 'payload')
348
+ end
349
+
350
+ it "should set net.verify_mode to the passed value if verify_ssl is an OpenSSL constant" do
351
+ mode = OpenSSL::SSL::VERIFY_PEER |
352
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
353
+ @request = RestClient::Request.new( :method => :put,
354
+ :url => 'https://some/resource',
355
+ :payload => 'payload',
356
+ :verify_ssl => mode )
357
+ @net.should_receive(:verify_mode=).with(mode)
358
+ @http.stub!(:request)
359
+ @request.stub!(:process_result)
360
+ @request.stub!(:response_log)
361
+ @request.transmit(@uri, 'req', 'payload')
362
+ end
363
+
364
+ it "should default to not having an ssl_client_cert" do
365
+ @request.ssl_client_cert.should be(nil)
366
+ end
367
+
368
+ it "should set the ssl_client_cert if provided" do
369
+ @request = RestClient::Request.new(
370
+ :method => :put,
371
+ :url => 'https://some/resource',
372
+ :payload => 'payload',
373
+ :ssl_client_cert => "whatsupdoc!"
374
+ )
375
+ @net.should_receive(:cert=).with("whatsupdoc!")
376
+ @http.stub!(:request)
377
+ @request.stub!(:process_result)
378
+ @request.stub!(:response_log)
379
+ @request.transmit(@uri, 'req', 'payload')
380
+ end
381
+
382
+ it "should not set the ssl_client_cert if it is not provided" do
383
+ @request = RestClient::Request.new(
384
+ :method => :put,
385
+ :url => 'https://some/resource',
386
+ :payload => 'payload'
387
+ )
388
+ @net.should_not_receive(:cert=).with("whatsupdoc!")
389
+ @http.stub!(:request)
390
+ @request.stub!(:process_result)
391
+ @request.stub!(:response_log)
392
+ @request.transmit(@uri, 'req', 'payload')
393
+ end
394
+
395
+ it "should default to not having an ssl_client_key" do
396
+ @request.ssl_client_key.should be(nil)
397
+ end
398
+
399
+ it "should set the ssl_client_key if provided" do
400
+ @request = RestClient::Request.new(
401
+ :method => :put,
402
+ :url => 'https://some/resource',
403
+ :payload => 'payload',
404
+ :ssl_client_key => "whatsupdoc!"
405
+ )
406
+ @net.should_receive(:key=).with("whatsupdoc!")
407
+ @http.stub!(:request)
408
+ @request.stub!(:process_result)
409
+ @request.stub!(:response_log)
410
+ @request.transmit(@uri, 'req', 'payload')
411
+ end
412
+
413
+ it "should not set the ssl_client_key if it is not provided" do
414
+ @request = RestClient::Request.new(
415
+ :method => :put,
416
+ :url => 'https://some/resource',
417
+ :payload => 'payload'
418
+ )
419
+ @net.should_not_receive(:key=).with("whatsupdoc!")
420
+ @http.stub!(:request)
421
+ @request.stub!(:process_result)
422
+ @request.stub!(:response_log)
423
+ @request.transmit(@uri, 'req', 'payload')
424
+ end
425
+
426
+ it "should default to not having an ssl_ca_file" do
427
+ @request.ssl_ca_file.should be(nil)
428
+ end
429
+
430
+ it "should set the ssl_ca_file if provided" do
431
+ @request = RestClient::Request.new(
432
+ :method => :put,
433
+ :url => 'https://some/resource',
434
+ :payload => 'payload',
435
+ :ssl_ca_file => "Certificate Authority File"
436
+ )
437
+ @net.should_receive(:ca_file=).with("Certificate Authority File")
438
+ @http.stub!(:request)
439
+ @request.stub!(:process_result)
440
+ @request.stub!(:response_log)
441
+ @request.transmit(@uri, 'req', 'payload')
442
+ end
443
+
444
+ it "should not set the ssl_ca_file if it is not provided" do
445
+ @request = RestClient::Request.new(
446
+ :method => :put,
447
+ :url => 'https://some/resource',
448
+ :payload => 'payload'
449
+ )
450
+ @net.should_not_receive(:ca_file=).with("Certificate Authority File")
451
+ @http.stub!(:request)
452
+ @request.stub!(:process_result)
453
+ @request.stub!(:response_log)
454
+ @request.transmit(@uri, 'req', 'payload')
455
+ end
328
456
  end
@@ -10,35 +10,6 @@ describe RestClient::Response do
10
10
  @response.should == 'abc'
11
11
  end
12
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 "extracts cookies from response headers" do
34
- @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
35
- @response.cookies.should == { 'session_id' => '1' }
36
- end
37
-
38
- it "can access the net http result directly" do
39
- @response.net_http_res.should == @net_http_res
40
- end
41
-
42
13
  it "accepts nil strings and sets it to empty for the case of HEAD" do
43
14
  RestClient::Response.new(nil, @net_http_res).should == ""
44
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adamwiggins-rest-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: "1.0"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Wiggins
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-25 00:00:00 -08:00
12
+ date: 2009-05-16 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15