rest-client 0.9 → 0.9.2

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 ADDED
@@ -0,0 +1,138 @@
1
+ = REST Client -- simple DSL for accessing REST resources
2
+
3
+ A simple REST client for Ruby, inspired by the Sinatra's microframework style
4
+ of specifying actions: get, put, post, delete.
5
+
6
+ == Usage: Raw URL
7
+
8
+ require 'rest_client'
9
+
10
+ RestClient.get 'http://example.com/resource'
11
+ RestClient.get 'https://user:password@example.com/private/resource'
12
+
13
+ RestClient.post 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' }
14
+
15
+ RestClient.delete 'http://example.com/resource'
16
+
17
+ See RestClient module docs for details.
18
+
19
+ == Usage: ActiveResource-Style
20
+
21
+ resource = RestClient::Resource.new 'http://example.com/resource'
22
+ resource.get
23
+
24
+ private_resource = RestClient::Resource.new 'https://example.com/private/resource', :user => 'adam', :password => 'secret', :timeout => 20, :open_timeout => 5
25
+ private_resource.put File.read('pic.jpg'), :content_type => 'image/jpg'
26
+
27
+ See RestClient::Resource module docs for details.
28
+
29
+ == Usage: Resource Nesting
30
+
31
+ site = RestClient::Resource.new('http://example.com')
32
+ site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
33
+
34
+ See RestClient::Resource docs for details.
35
+
36
+ == Shell
37
+
38
+ The restclient shell command gives an IRB session with RestClient already loaded:
39
+
40
+ $ restclient
41
+ >> RestClient.get 'http://example.com'
42
+
43
+ Specify a URL argument for get/post/put/delete on that resource:
44
+
45
+ $ restclient http://example.com
46
+ >> put '/resource', 'data'
47
+
48
+ Add a user and password for authenticated resources:
49
+
50
+ $ restclient https://example.com user pass
51
+ >> delete '/private/resource'
52
+
53
+ Create ~/.restclient for named sessions:
54
+
55
+ sinatra:
56
+ url: http://localhost:4567
57
+ rack:
58
+ url: http://localhost:9292
59
+ private_site:
60
+ url: http://example.com
61
+ username: user
62
+ password: pass
63
+
64
+ Then invoke:
65
+
66
+ $ restclient private_site
67
+
68
+ Use as a one-off, curl-style:
69
+
70
+ $ restclient get http://example.com/resource > output_body
71
+
72
+ $ restclient put http://example.com/resource < input_body
73
+
74
+ == Logging
75
+
76
+ Write calls to a log filename (can also be "stdout" or "stderr"):
77
+
78
+ RestClient.log = '/tmp/restclient.log'
79
+
80
+ Or set an environment variable to avoid modifying the code:
81
+
82
+ $ RESTCLIENT_LOG=stdout path/to/my/program
83
+
84
+ Either produces logs like this:
85
+
86
+ RestClient.get "http://some/resource"
87
+ # => 200 OK | text/html 250 bytes
88
+ RestClient.put "http://some/resource", "payload"
89
+ # => 401 Unauthorized | application/xml 340 bytes
90
+
91
+ Note that these logs are valid Ruby, so you can paste them into the restclient
92
+ shell or a script to replay your sequence of rest calls.
93
+
94
+ == Proxy
95
+
96
+ All calls to RestClient, including Resources, will use the proxy specified by
97
+ RestClient.proxy:
98
+
99
+ RestClient.proxy = "http://proxy.example.com/"
100
+ RestClient.get "http://some/resource"
101
+ # => response from some/resource as proxied through proxy.example.com
102
+
103
+ Often the proxy url is set in an environment variable, so you can do this to
104
+ use whatever proxy the system is configured to use:
105
+
106
+ RestClient.proxy = ENV['http_proxy']
107
+
108
+ == Cookies
109
+
110
+ Request and Response objects know about HTTP cookies, and will automatically
111
+ extract and set headers for them as needed:
112
+
113
+ response = RestClient.get 'http://example.com/action_which_sets_session_id'
114
+ response.cookies
115
+ # => {"_applicatioN_session_id" => "1234"}
116
+
117
+ response2 = RestClient.post(
118
+ 'http://localhost:3000/',
119
+ {:param1 => "foo"},
120
+ {:cookies => {:session_id => "1234"}}
121
+ )
122
+ # ...response body
123
+
124
+ == Meta
125
+
126
+ Written by Adam Wiggins (adam at heroku dot com)
127
+
128
+ Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro
129
+ Belo, Rafael Souza, Rick Olson, Aman Gupta, Blake Mizerany, Brian Donovan, Ivan
130
+ Makfinsky, Marc-André Cournoyer, Coda Hale, Tetsuo Watanabe, Dusty Doris,
131
+ Lennon Day-Reynolds, James Edward Gray II, Cyril Rohr, and Juan Alvarez
132
+
133
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
134
+
135
+ http://rest-client.heroku.com
136
+
137
+ http://github.com/adamwiggins/rest-client
138
+
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"
34
+ version = "0.9.2"
35
35
  name = "rest-client"
36
36
 
37
37
  spec = Gem::Specification.new do |s|
@@ -47,7 +47,7 @@ spec = Gem::Specification.new do |s|
47
47
  s.platform = Gem::Platform::RUBY
48
48
  s.has_rdoc = true
49
49
 
50
- s.files = %w(Rakefile) + Dir.glob("{lib,spec}/**/*")
50
+ s.files = %w(Rakefile README.rdoc) + Dir.glob("{lib,spec}/**/*")
51
51
  s.executables = ['restclient']
52
52
 
53
53
  s.require_path = "lib"
data/lib/restclient.rb CHANGED
@@ -31,10 +31,14 @@ require File.dirname(__FILE__) + '/restclient/exceptions'
31
31
  # # DELETE
32
32
  # RestClient.delete 'http://example.com/resource'
33
33
  #
34
- # # retreive the response headers
34
+ # # retreive the response http code and headers
35
35
  # res = RestClient.get 'http://example.com/some.jpg'
36
+ # res.code # => 200
36
37
  # res.headers[:content_type] # => 'image/jpg'
37
38
  #
39
+ # # HEAD
40
+ # RestClient.head('http://example.com').headers
41
+ #
38
42
  # To use with a proxy, just set RestClient.proxy to the proper http proxy:
39
43
  #
40
44
  # RestClient.proxy = "http://proxy.example.com/"
@@ -65,6 +69,10 @@ module RestClient
65
69
  Request.execute(:method => :delete, :url => url, :headers => headers)
66
70
  end
67
71
 
72
+ def self.head(url, headers={})
73
+ Request.execute(:method => :head, :url => url, :headers => headers)
74
+ end
75
+
68
76
  class << self
69
77
  attr_accessor :proxy
70
78
  end
@@ -30,7 +30,7 @@ module RestClient
30
30
  end
31
31
  end
32
32
 
33
- class NotModified < Exception
33
+ class NotModified < ExceptionWithResponse
34
34
  ErrorMessage = 'NotModified'
35
35
  end
36
36
 
@@ -6,7 +6,7 @@ module RestClient
6
6
  # RestClient::Request.execute(:method => :head, :url => 'http://example.com')
7
7
  #
8
8
  class Request
9
- attr_reader :method, :url, :payload, :headers, :user, :password, :timeout
9
+ attr_reader :method, :url, :payload, :headers, :cookies, :user, :password, :timeout, :open_timeout
10
10
 
11
11
  def self.execute(args)
12
12
  new(args).execute
@@ -16,10 +16,12 @@ module RestClient
16
16
  @method = args[:method] or raise ArgumentError, "must pass :method"
17
17
  @url = args[:url] or raise ArgumentError, "must pass :url"
18
18
  @headers = args[:headers] || {}
19
+ @cookies = @headers.delete(:cookies) || args[:cookies] || {}
19
20
  @payload = process_payload(args[:payload])
20
21
  @user = args[:user]
21
22
  @password = args[:password]
22
23
  @timeout = args[:timeout]
24
+ @open_timeout = args[:open_timeout]
23
25
  end
24
26
 
25
27
  def execute
@@ -35,6 +37,10 @@ module RestClient
35
37
  end
36
38
 
37
39
  def make_headers(user_headers)
40
+ unless @cookies.empty?
41
+ user_headers[:cookie] = @cookies.map {|key, val| "#{key.to_s}=#{val}" }.join('; ')
42
+ end
43
+
38
44
  default_headers.merge(user_headers).inject({}) do |final, (key, value)|
39
45
  final[key.to_s.gsub(/_/, '-').capitalize] = value.to_s
40
46
  final
@@ -89,15 +95,17 @@ module RestClient
89
95
  net = net_http_class.new(uri.host, uri.port)
90
96
  net.use_ssl = uri.is_a?(URI::HTTPS)
91
97
  net.verify_mode = OpenSSL::SSL::VERIFY_NONE
98
+ net.read_timeout = @timeout if @timeout
99
+ net.open_timeout = @open_timeout if @open_timeout
92
100
 
93
101
  display_log request_log
94
102
 
95
103
  net.start do |http|
96
- http.read_timeout = @timeout if @timeout
97
104
  res = http.request(req, payload)
98
105
  display_log response_log(res)
99
106
  string = process_result(res)
100
- if string
107
+
108
+ if string or @method == :head
101
109
  Response.new(string, res)
102
110
  else
103
111
  nil
@@ -114,8 +122,8 @@ module RestClient
114
122
  end
115
123
 
116
124
  def process_result(res)
117
- if %w(200 201 202).include? res.code
118
- decode res['content-encoding'], res.body
125
+ if res.code =~ /\A2\d{2}\z/
126
+ decode res['content-encoding'], res.body if res.body
119
127
  elsif %w(301 302 303).include? res.code
120
128
  url = res.header['Location']
121
129
 
@@ -127,7 +135,7 @@ module RestClient
127
135
 
128
136
  raise Redirect, url
129
137
  elsif res.code == "304"
130
- raise NotModified
138
+ raise NotModified, res
131
139
  elsif res.code == "401"
132
140
  raise Unauthorized, res
133
141
  elsif res.code == "404"
@@ -138,7 +146,7 @@ module RestClient
138
146
  end
139
147
 
140
148
  def decode(content_encoding, body)
141
- if content_encoding == 'gzip'
149
+ if content_encoding == 'gzip' and not body.empty?
142
150
  Zlib::GzipReader.new(StringIO.new(body)).read
143
151
  elsif content_encoding == 'deflate'
144
152
  Zlib::Inflate.new.inflate(body)
@@ -16,6 +16,10 @@ module RestClient
16
16
  #
17
17
  # RestClient::Resource.new('http://slow', :timeout => 10)
18
18
  #
19
+ # With an open timeout (seconds):
20
+ #
21
+ # RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
22
+ #
19
23
  # You can also use resources to share common headers. For headers keys,
20
24
  # symbols are converted to strings. Example:
21
25
  #
@@ -94,6 +98,10 @@ module RestClient
94
98
  def timeout
95
99
  options[:timeout]
96
100
  end
101
+
102
+ def open_timeout
103
+ options[:open_timeout]
104
+ end
97
105
 
98
106
  # Construct a subresource, preserving authentication.
99
107
  #
@@ -10,7 +10,7 @@ module RestClient
10
10
 
11
11
  def initialize(string, net_http_res)
12
12
  @net_http_res = net_http_res
13
- super string
13
+ super(string || "")
14
14
  end
15
15
 
16
16
  # HTTP status code, always 200 since RestClient throws exceptions for
@@ -25,6 +25,17 @@ module RestClient
25
25
  @headers ||= self.class.beautify_headers(@net_http_res.to_hash)
26
26
  end
27
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
+
28
39
  def self.beautify_headers(headers)
29
40
  headers.inject({}) do |out, (key, value)|
30
41
  out[key.gsub(/-/, '_').to_sym] = value.first
data/spec/request_spec.rb CHANGED
@@ -28,18 +28,32 @@ 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
+
32
+ it "ingores gzip for empty bodies" do
33
+ @request.decode('gzip', '').should be_empty
34
+ end
31
35
 
32
36
  it "decodes a deflated body" do
33
37
  @request.decode('deflate', "x\234+\316\317MUHIM\313I,IMQ(I\255(\001\000A\223\006\363").should == "some deflated text"
34
38
  end
35
39
 
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
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
+
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
43
57
 
44
58
  it "parses a url into a URI object" do
45
59
  URI.should_receive(:parse).with('http://example.com/resource')
@@ -66,6 +80,13 @@ describe RestClient::Request do
66
80
  @request.password.should == 'pass2'
67
81
  end
68
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
89
+
69
90
  it "determines the Net::HTTP class to instantiate by the method name" do
70
91
  @request.net_http_request_class(:put).should == Net::HTTP::Put
71
92
  end
@@ -289,7 +310,18 @@ describe RestClient::Request do
289
310
  @request.stub!(:process_result)
290
311
  @request.stub!(:response_log)
291
312
 
292
- @http.should_receive(:read_timeout=).with(123)
313
+ @net.should_receive(:read_timeout=).with(123)
314
+
315
+ @request.transmit(@uri, 'req', nil)
316
+ end
317
+
318
+ it "set open_timeout" do
319
+ @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :open_timeout => 123)
320
+ @http.stub!(:request)
321
+ @request.stub!(:process_result)
322
+ @request.stub!(:response_log)
323
+
324
+ @net.should_receive(:open_timeout=).with(123)
293
325
 
294
326
  @request.transmit(@uri, 'req', nil)
295
327
  end
@@ -30,7 +30,16 @@ describe RestClient::Response do
30
30
  @response.headers.should == { :content_type => 'text/html' }
31
31
  end
32
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
+
33
38
  it "can access the net http result directly" do
34
39
  @response.net_http_res.should == @net_http_res
35
40
  end
41
+
42
+ it "accepts nil strings and sets it to empty for the case of HEAD" do
43
+ RestClient::Response.new(nil, @net_http_res).should == ""
44
+ end
36
45
  end
@@ -21,6 +21,11 @@ describe RestClient do
21
21
  RestClient::Request.should_receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {})
22
22
  RestClient.delete('http://some/resource')
23
23
  end
24
+
25
+ it "HEAD" do
26
+ RestClient::Request.should_receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {})
27
+ RestClient.head('http://some/resource')
28
+ end
24
29
  end
25
30
 
26
31
  describe "logging" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-client
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.9"
4
+ version: 0.9.2
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-01-24 00:00:00 -08:00
12
+ date: 2009-03-12 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -23,19 +23,20 @@ extra_rdoc_files: []
23
23
 
24
24
  files:
25
25
  - Rakefile
26
+ - README.rdoc
27
+ - lib/restclient.rb
26
28
  - lib/rest_client.rb
27
29
  - lib/restclient
28
- - lib/restclient/exceptions.rb
29
- - lib/restclient/request.rb
30
- - lib/restclient/resource.rb
31
30
  - lib/restclient/response.rb
32
- - lib/restclient.rb
31
+ - lib/restclient/resource.rb
32
+ - lib/restclient/request.rb
33
+ - lib/restclient/exceptions.rb
34
+ - spec/resource_spec.rb
35
+ - spec/restclient_spec.rb
36
+ - spec/response_spec.rb
33
37
  - spec/base.rb
34
38
  - spec/exceptions_spec.rb
35
39
  - spec/request_spec.rb
36
- - spec/resource_spec.rb
37
- - spec/response_spec.rb
38
- - spec/restclient_spec.rb
39
40
  has_rdoc: true
40
41
  homepage: http://rest-client.heroku.com/
41
42
  post_install_message: