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