rest-client 0.9 → 0.9.2
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.
Potentially problematic release.
This version of rest-client might be problematic. Click here for more details.
- data/README.rdoc +138 -0
- data/Rakefile +2 -2
- data/lib/restclient.rb +9 -1
- data/lib/restclient/exceptions.rb +1 -1
- data/lib/restclient/request.rb +15 -7
- data/lib/restclient/resource.rb +8 -0
- data/lib/restclient/response.rb +12 -1
- data/spec/request_spec.rb +40 -8
- data/spec/response_spec.rb +9 -0
- data/spec/restclient_spec.rb +5 -0
- metadata +10 -9
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
|
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,8 +122,8 @@ module RestClient
|
|
114
122
|
end
|
115
123
|
|
116
124
|
def process_result(res)
|
117
|
-
if
|
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)
|
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/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: rest-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
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-
|
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:
|