rest-client 0.9 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
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:
|