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 +19 -2
- data/lib/rest_client.rb +2 -0
- data/lib/restclient.rb +91 -0
- data/lib/restclient/exceptions.rb +1 -1
- data/lib/restclient/request.rb +14 -6
- data/lib/restclient/resource.rb +8 -0
- data/lib/restclient/response.rb +12 -1
- data/rest-client.gemspec +2 -1
- data/spec/request_spec.rb +40 -8
- data/spec/response_spec.rb +9 -0
- data/spec/restclient_spec.rb +5 -0
- metadata +4 -2
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,
|
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
|
|
data/lib/rest_client.rb
ADDED
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
|
data/lib/restclient/request.rb
CHANGED
@@ -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
|
-
|
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
|
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)
|
data/lib/restclient/resource.rb
CHANGED
@@ -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
|
#
|
data/lib/restclient/response.rb
CHANGED
@@ -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
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
@
|
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
|
data/spec/response_spec.rb
CHANGED
@@ -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
|
data/spec/restclient_spec.rb
CHANGED
@@ -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:
|
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-
|
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
|