adamwiggins-rest-client 0.9 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -21,7 +21,7 @@ See RestClient module docs for details.
21
21
  resource = RestClient::Resource.new 'http://example.com/resource'
22
22
  resource.get
23
23
 
24
- private_resource = RestClient::Resource.new 'https://example.com/private/resource', :user => 'adam', :password => 'secret', :timeout => 20
24
+ private_resource = RestClient::Resource.new 'https://example.com/private/resource', :user => 'adam', :password => 'secret', :timeout => 20, :open_timeout => 5
25
25
  private_resource.put File.read('pic.jpg'), :content_type => 'image/jpg'
26
26
 
27
27
  See RestClient::Resource module docs for details.
@@ -105,13 +105,30 @@ use whatever proxy the system is configured to use:
105
105
 
106
106
  RestClient.proxy = ENV['http_proxy']
107
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
+
108
124
  == Meta
109
125
 
110
126
  Written by Adam Wiggins (adam at heroku dot com)
111
127
 
112
128
  Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro
113
129
  Belo, Rafael Souza, Rick Olson, Aman Gupta, Blake Mizerany, Brian Donovan, Ivan
114
- Makfinsky, Marc-André Cournoyer, Coda Hale, and Tetsuo Watanabe
130
+ Makfinsky, Marc-André Cournoyer, Coda Hale, Tetsuo Watanabe, Dusty Doris,
131
+ Lennon Day-Reynolds, James Edward Gray II, and Cyril Rohr
115
132
 
116
133
  Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
117
134
 
@@ -0,0 +1,2 @@
1
+ # This file exists for backward compatbility with require 'rest_client'
2
+ require File.dirname(__FILE__) + '/restclient'
data/lib/restclient.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+ require 'zlib'
4
+ require 'stringio'
5
+
6
+ require File.dirname(__FILE__) + '/restclient/request'
7
+ require File.dirname(__FILE__) + '/restclient/response'
8
+ require File.dirname(__FILE__) + '/restclient/resource'
9
+ require File.dirname(__FILE__) + '/restclient/exceptions'
10
+
11
+ # This module's static methods are the entry point for using the REST client.
12
+ #
13
+ # # GET
14
+ # xml = RestClient.get 'http://example.com/resource'
15
+ # jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg'
16
+ #
17
+ # # authentication and SSL
18
+ # RestClient.get 'https://user:password@example.com/private/resource'
19
+ #
20
+ # # POST or PUT with a hash sends parameters as a urlencoded form body
21
+ # RestClient.post 'http://example.com/resource', :param1 => 'one'
22
+ #
23
+ # # nest hash parameters
24
+ # RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
25
+ #
26
+ # # POST and PUT with raw payloads
27
+ # RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
28
+ # RestClient.post 'http://example.com/resource.xml', xml_doc
29
+ # RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
30
+ #
31
+ # # DELETE
32
+ # RestClient.delete 'http://example.com/resource'
33
+ #
34
+ # # retreive the response http code and headers
35
+ # res = RestClient.get 'http://example.com/some.jpg'
36
+ # res.code # => 200
37
+ # res.headers[:content_type] # => 'image/jpg'
38
+ #
39
+ # # HEAD
40
+ # RestClient.head('http://example.com').headers
41
+ #
42
+ # To use with a proxy, just set RestClient.proxy to the proper http proxy:
43
+ #
44
+ # RestClient.proxy = "http://proxy.example.com/"
45
+ #
46
+ # Or inherit the proxy from the environment:
47
+ #
48
+ # RestClient.proxy = ENV['http_proxy']
49
+ #
50
+ # For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
51
+ #
52
+ # >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
53
+ # => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
54
+ #
55
+ module RestClient
56
+ def self.get(url, headers={})
57
+ Request.execute(:method => :get, :url => url, :headers => headers)
58
+ end
59
+
60
+ def self.post(url, payload, headers={})
61
+ Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers)
62
+ end
63
+
64
+ def self.put(url, payload, headers={})
65
+ Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers)
66
+ end
67
+
68
+ def self.delete(url, headers={})
69
+ Request.execute(:method => :delete, :url => url, :headers => headers)
70
+ end
71
+
72
+ def self.head(url, headers={})
73
+ Request.execute(:method => :head, :url => url, :headers => headers)
74
+ end
75
+
76
+ class << self
77
+ attr_accessor :proxy
78
+ end
79
+
80
+ # Print log of RestClient calls. Value can be stdout, stderr, or a filename.
81
+ # You can also configure logging by the environment variable RESTCLIENT_LOG.
82
+ def self.log=(log)
83
+ @@log = log
84
+ end
85
+
86
+ def self.log # :nodoc:
87
+ return ENV['RESTCLIENT_LOG'] if ENV['RESTCLIENT_LOG']
88
+ return @@log if defined? @@log
89
+ nil
90
+ end
91
+ 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,7 +122,7 @@ module RestClient
114
122
  end
115
123
 
116
124
  def process_result(res)
117
- if %w(200 201 202).include? res.code
125
+ if res.code =~ /\A2\d{2}\z/
118
126
  decode res['content-encoding'], res.body
119
127
  elsif %w(301 302 303).include? res.code
120
128
  url = res.header['Location']
@@ -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/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"
3
+ s.version = "0.9.1"
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"
@@ -10,6 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.has_rdoc = true
11
11
  s.platform = Gem::Platform::RUBY
12
12
  s.files = %w(Rakefile README.rdoc rest-client.gemspec
13
+ lib/rest_client.rb lib/restclient.rb
13
14
  lib/restclient/request.rb lib/restclient/response.rb
14
15
  lib/restclient/exceptions.rb lib/restclient/resource.rb
15
16
  spec/base.rb spec/request_spec.rb spec/response_spec.rb
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: adamwiggins-rest-client
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.9"
4
+ version: 0.9.1
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-11 00:00:00 -08:00
12
+ date: 2009-02-13 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,6 +25,8 @@ files:
25
25
  - Rakefile
26
26
  - README.rdoc
27
27
  - rest-client.gemspec
28
+ - lib/rest_client.rb
29
+ - lib/restclient.rb
28
30
  - lib/restclient/request.rb
29
31
  - lib/restclient/response.rb
30
32
  - lib/restclient/exceptions.rb