rest-client 0.9 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.

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: