adamwiggins-rest-client 0.9 → 0.9.1

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
@@ -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