adamwiggins-rest-client 0.9.2 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
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