rest-client 1.0.4 → 1.1.0
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 +26 -78
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/lib/restclient.rb +8 -0
- data/lib/restclient/mixin/response.rb +6 -1
- data/lib/restclient/net_http_ext.rb +21 -0
- data/lib/restclient/payload.rb +171 -0
- data/lib/restclient/request.rb +24 -10
- data/lib/restclient/resource.rb +12 -12
- data/spec/base.rb +6 -0
- data/spec/master_shake.jpg +0 -0
- data/spec/payload_spec.rb +131 -0
- data/spec/request_spec.rb +41 -20
- data/spec/resource_spec.rb +4 -4
- data/spec/response_spec.rb +6 -1
- metadata +21 -6
data/README.rdoc
CHANGED
@@ -14,14 +14,27 @@ of specifying actions: get, put, post, delete.
|
|
14
14
|
|
15
15
|
RestClient.delete 'http://example.com/resource'
|
16
16
|
|
17
|
-
|
17
|
+
== Multipart
|
18
|
+
|
19
|
+
Yeah, that's right! This does multipart sends for you!
|
20
|
+
|
21
|
+
RestClient.post '/data', :myfile => File.new("/path/to/image.jpg")
|
22
|
+
|
23
|
+
This does two things for you:
|
24
|
+
|
25
|
+
* Auto-detects that you have a File value sends it as multipart
|
26
|
+
* Auto-detects the mime of the file and sets it in the HEAD of the payload for each entry
|
27
|
+
|
28
|
+
If you are sending params that do not contain a File object but the payload needs to be multipart then:
|
29
|
+
|
30
|
+
RestClient.post '/data', :foo => 'bar', :multipart => true
|
18
31
|
|
19
32
|
== Usage: ActiveResource-Style
|
20
33
|
|
21
34
|
resource = RestClient::Resource.new 'http://example.com/resource'
|
22
35
|
resource.get
|
23
36
|
|
24
|
-
private_resource = RestClient::Resource.new 'https://example.com/private/resource',
|
37
|
+
private_resource = RestClient::Resource.new 'https://example.com/private/resource', 'user', 'pass'
|
25
38
|
private_resource.put File.read('pic.jpg'), :content_type => 'image/jpg'
|
26
39
|
|
27
40
|
See RestClient::Resource module docs for details.
|
@@ -33,6 +46,10 @@ See RestClient::Resource module docs for details.
|
|
33
46
|
|
34
47
|
See RestClient::Resource docs for details.
|
35
48
|
|
49
|
+
== Lower-level access
|
50
|
+
|
51
|
+
For cases not covered by the general API, you can use the RestClient::Resource class which provide a lower-level API, see the class' rdoc for more information.
|
52
|
+
|
36
53
|
== Shell
|
37
54
|
|
38
55
|
The restclient shell command gives an IRB session with RestClient already loaded:
|
@@ -64,88 +81,19 @@ Create ~/.restclient for named sessions:
|
|
64
81
|
Then invoke:
|
65
82
|
|
66
83
|
$ 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
|
-
== SSL Client Certificates
|
125
|
-
|
126
|
-
RestClient::Resource.new(
|
127
|
-
'https://example.com',
|
128
|
-
:ssl_client_cert => OpenSSL::X509::Certificate.new(File.read("cert.pem")),
|
129
|
-
:ssl_client_key => OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"),
|
130
|
-
:ssl_ca_file => "ca_certificate.pem",
|
131
|
-
:verify_ssl => OpenSSL::SSL::VERIFY_PEER
|
132
|
-
).get
|
133
|
-
|
134
|
-
Self-signed certificates can be generated with the openssl command-line tool.
|
135
84
|
|
136
85
|
== Meta
|
137
86
|
|
138
|
-
Written by Adam Wiggins
|
87
|
+
Written by Adam Wiggins, major modifications by Blake Mizerany, maintained by Archiloque
|
139
88
|
|
140
|
-
Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro
|
141
|
-
Belo, Rafael Souza, Rick Olson, Aman Gupta, Blake Mizerany, Brian Donovan, Ivan
|
142
|
-
Makfinsky, Marc-André Cournoyer, Coda Hale, Tetsuo Watanabe, Dusty Doris,
|
143
|
-
Lennon Day-Reynolds, James Edward Gray II, Cyril Rohr, Juan Alvarez, and Adam
|
144
|
-
Jacob, Paul Dlug, and Brad Ediger
|
89
|
+
Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro Belo, Rafael Souza, Rick Olson, Aman Gupta, François Beausoleil and Nick Plante.
|
145
90
|
|
146
91
|
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
|
147
92
|
|
148
|
-
http://rest-client
|
93
|
+
Main page: http://github.com/archiloque/rest-client
|
94
|
+
|
95
|
+
Rdoc: http://rdoc.info/projects/archiloque/rest-client
|
149
96
|
|
150
|
-
|
97
|
+
Mailing list: rest.client@librelist.com (send a mail to subscribe).
|
151
98
|
|
99
|
+
IRC: #rest-client at freenode
|
data/Rakefile
CHANGED
@@ -52,7 +52,6 @@ Rake::RDocTask.new do |t|
|
|
52
52
|
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
53
53
|
t.options << '--charset' << 'utf-8'
|
54
54
|
t.rdoc_files.include('README.rdoc')
|
55
|
-
t.rdoc_files.include('lib
|
56
|
-
t.rdoc_files.include('lib/restclient/*.rb')
|
55
|
+
t.rdoc_files.include('lib/*.rb')
|
57
56
|
end
|
58
57
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/lib/restclient.rb
CHANGED
@@ -15,6 +15,8 @@ require File.dirname(__FILE__) + '/restclient/response'
|
|
15
15
|
require File.dirname(__FILE__) + '/restclient/raw_response'
|
16
16
|
require File.dirname(__FILE__) + '/restclient/resource'
|
17
17
|
require File.dirname(__FILE__) + '/restclient/exceptions'
|
18
|
+
require File.dirname(__FILE__) + '/restclient/payload'
|
19
|
+
require File.dirname(__FILE__) + '/restclient/net_http_ext'
|
18
20
|
|
19
21
|
# This module's static methods are the entry point for using the REST client.
|
20
22
|
#
|
@@ -96,4 +98,10 @@ module RestClient
|
|
96
98
|
return @@log if defined? @@log
|
97
99
|
nil
|
98
100
|
end
|
101
|
+
|
102
|
+
def self.version
|
103
|
+
version_path = File.dirname(__FILE__) + "/../VERSION"
|
104
|
+
return File.read(version_path).chomp if File.file?(version_path)
|
105
|
+
"0.0.0"
|
106
|
+
end
|
99
107
|
end
|
@@ -15,6 +15,11 @@ module RestClient
|
|
15
15
|
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
16
16
|
end
|
17
17
|
|
18
|
+
# The raw headers.
|
19
|
+
def raw_headers
|
20
|
+
@raw_headers ||= @net_http_res.to_hash
|
21
|
+
end
|
22
|
+
|
18
23
|
# Hash of cookies extracted from response headers
|
19
24
|
def cookies
|
20
25
|
@cookies ||= (self.headers[:set_cookie] || "").split('; ').inject({}) do |out, raw_c|
|
@@ -33,7 +38,7 @@ module RestClient
|
|
33
38
|
module ClassMethods
|
34
39
|
def beautify_headers(headers)
|
35
40
|
headers.inject({}) do |out, (key, value)|
|
36
|
-
out[key.gsub(/-/, '_').to_sym] = value.first
|
41
|
+
out[key.gsub(/-/, '_').downcase.to_sym] = value.first
|
37
42
|
out
|
38
43
|
end
|
39
44
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Replace the request method in Net::HTTP to sniff the body type
|
3
|
+
# and set the stream if appropriate
|
4
|
+
#
|
5
|
+
# Taken from:
|
6
|
+
# http://www.missiondata.com/blog/ruby/29/streaming-data-to-s3-with-ruby/
|
7
|
+
|
8
|
+
module Net
|
9
|
+
class HTTP
|
10
|
+
alias __request__ request
|
11
|
+
|
12
|
+
def request(req, body=nil, &block)
|
13
|
+
if body != nil && body.respond_to?(:read)
|
14
|
+
req.body_stream = body
|
15
|
+
return __request__(req, nil, &block)
|
16
|
+
else
|
17
|
+
return __request__(req, body, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
require "stringio"
|
3
|
+
require "mime/types"
|
4
|
+
|
5
|
+
module RestClient
|
6
|
+
module Payload
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def generate(params)
|
10
|
+
if params.is_a?(String)
|
11
|
+
Base.new(params)
|
12
|
+
elsif params
|
13
|
+
if params.delete(:multipart) == true || has_file?(params)
|
14
|
+
Multipart.new(params)
|
15
|
+
else
|
16
|
+
UrlEncoded.new(params)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_file?(params)
|
24
|
+
params.any? do |_, v|
|
25
|
+
case v
|
26
|
+
when Hash
|
27
|
+
has_file?(v)
|
28
|
+
else
|
29
|
+
v.respond_to?(:path) && v.respond_to?(:read)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Base
|
35
|
+
def initialize(params)
|
36
|
+
build_stream(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_stream(params)
|
40
|
+
@stream = StringIO.new(params)
|
41
|
+
@stream.seek(0)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read(bytes=nil)
|
45
|
+
@stream.read(bytes)
|
46
|
+
end
|
47
|
+
alias :to_s :read
|
48
|
+
|
49
|
+
def escape(v)
|
50
|
+
URI.escape(v.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
51
|
+
end
|
52
|
+
|
53
|
+
# Flatten parameters by converting hashes of hashes to flat hashes
|
54
|
+
# {keys1 => {keys2 => value}} will be transformed into {keys1[key2] => value}
|
55
|
+
def flatten_params(params, parent_key = nil)
|
56
|
+
result = {}
|
57
|
+
params.keys.map do |key|
|
58
|
+
calculated_key = parent_key ? "#{parent_key}[#{escape key}]" : escape(key)
|
59
|
+
value = params[key]
|
60
|
+
if value.is_a? Hash
|
61
|
+
result.merge!(flatten_params(value, calculated_key))
|
62
|
+
else
|
63
|
+
result[calculated_key] = value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def headers
|
70
|
+
{ 'Content-Length' => size.to_s }
|
71
|
+
end
|
72
|
+
|
73
|
+
def size
|
74
|
+
@stream.size
|
75
|
+
end
|
76
|
+
alias :length :size
|
77
|
+
|
78
|
+
def close
|
79
|
+
@stream.close
|
80
|
+
end
|
81
|
+
|
82
|
+
def inspect
|
83
|
+
result = to_s.inspect
|
84
|
+
@stream.seek(0)
|
85
|
+
result
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class UrlEncoded < Base
|
90
|
+
def build_stream(params = nil)
|
91
|
+
@stream = StringIO.new(flatten_params(params).map do |k,v|
|
92
|
+
"#{k}=#{escape(v)}"
|
93
|
+
end.join("&"))
|
94
|
+
@stream.seek(0)
|
95
|
+
end
|
96
|
+
|
97
|
+
def headers
|
98
|
+
super.merge({ 'Content-Type' => 'application/x-www-form-urlencoded' })
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Multipart < Base
|
103
|
+
EOL = "\r\n"
|
104
|
+
|
105
|
+
def build_stream(params)
|
106
|
+
b = "--#{boundary}"
|
107
|
+
|
108
|
+
@stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
|
109
|
+
@stream.write(b + EOL)
|
110
|
+
|
111
|
+
if params.is_a? Hash
|
112
|
+
x = flatten_params(params).to_a
|
113
|
+
else
|
114
|
+
x = params.to_a
|
115
|
+
end
|
116
|
+
|
117
|
+
last_index = x.length - 1
|
118
|
+
x.each_with_index do |a, index|
|
119
|
+
k, v = *a
|
120
|
+
if v.respond_to?(:read) && v.respond_to?(:path)
|
121
|
+
create_file_field(@stream, k,v)
|
122
|
+
else
|
123
|
+
create_regular_field(@stream, k,v)
|
124
|
+
end
|
125
|
+
@stream.write(EOL + b)
|
126
|
+
@stream.write(EOL) unless last_index == index
|
127
|
+
end
|
128
|
+
@stream.write('--')
|
129
|
+
@stream.write(EOL)
|
130
|
+
@stream.seek(0)
|
131
|
+
end
|
132
|
+
|
133
|
+
def create_regular_field(s, k, v)
|
134
|
+
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"")
|
135
|
+
s.write(EOL)
|
136
|
+
s.write(EOL)
|
137
|
+
s.write(v)
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_file_field(s, k, v)
|
141
|
+
begin
|
142
|
+
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"; filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
|
143
|
+
s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
|
144
|
+
s.write(EOL)
|
145
|
+
while data = v.read(8124)
|
146
|
+
s.write(data)
|
147
|
+
end
|
148
|
+
ensure
|
149
|
+
v.close
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def mime_for(path)
|
154
|
+
mime = MIME::Types.type_for path
|
155
|
+
mime.empty? ? 'text/plain' : mime[0].content_type
|
156
|
+
end
|
157
|
+
|
158
|
+
def boundary
|
159
|
+
@boundary ||= rand(1_000_000).to_s
|
160
|
+
end
|
161
|
+
|
162
|
+
def headers
|
163
|
+
super.merge({'Content-Type' => %Q{multipart/form-data; boundary="#{boundary}"}})
|
164
|
+
end
|
165
|
+
|
166
|
+
def close
|
167
|
+
@stream.close
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/restclient/request.rb
CHANGED
@@ -2,11 +2,22 @@ require 'tempfile'
|
|
2
2
|
|
3
3
|
module RestClient
|
4
4
|
# This class is used internally by RestClient to send the request, but you can also
|
5
|
-
#
|
5
|
+
# call it directly if you'd like to use a method not supported by the
|
6
6
|
# main API. For example:
|
7
7
|
#
|
8
8
|
# RestClient::Request.execute(:method => :head, :url => 'http://example.com')
|
9
9
|
#
|
10
|
+
# Mandatory parameters:
|
11
|
+
# * :method
|
12
|
+
# * :url
|
13
|
+
# Optional parameters (have a look at ssl and/or uri for some explanations):
|
14
|
+
# * :headers a hash containing the request headers
|
15
|
+
# * :cookies will replace possible cookies in the :headers
|
16
|
+
# * :user and :password for basic auth, will be replaced by a user/password available in the :url
|
17
|
+
# * :raw_response return a low-level RawResponse instead of a Response
|
18
|
+
# * :verify_ssl enable ssl verification, possible values are constants from OpenSSL::SSL
|
19
|
+
# * :timeout and :open_timeout
|
20
|
+
# * :ssl_client_cert, :ssl_client_key, :ssl_ca_file
|
10
21
|
class Request
|
11
22
|
attr_reader :method, :url, :payload, :headers,
|
12
23
|
:cookies, :user, :password, :timeout, :open_timeout,
|
@@ -22,7 +33,7 @@ module RestClient
|
|
22
33
|
@url = args[:url] or raise ArgumentError, "must pass :url"
|
23
34
|
@headers = args[:headers] || {}
|
24
35
|
@cookies = @headers.delete(:cookies) || args[:cookies] || {}
|
25
|
-
@payload =
|
36
|
+
@payload = Payload.generate(args[:payload])
|
26
37
|
@user = args[:user]
|
27
38
|
@password = args[:password]
|
28
39
|
@timeout = args[:timeout]
|
@@ -30,7 +41,7 @@ module RestClient
|
|
30
41
|
@raw_response = args[:raw_response] || false
|
31
42
|
@verify_ssl = args[:verify_ssl] || false
|
32
43
|
@ssl_client_cert = args[:ssl_client_cert] || nil
|
33
|
-
@ssl_client_key
|
44
|
+
@ssl_client_key = args[:ssl_client_key] || nil
|
34
45
|
@ssl_ca_file = args[:ssl_ca_file] || nil
|
35
46
|
@tf = nil # If you are a raw request, this is your tempfile
|
36
47
|
end
|
@@ -54,10 +65,13 @@ module RestClient
|
|
54
65
|
user_headers[:cookie] = @cookies.map {|key, val| "#{key.to_s}=#{val}" }.join('; ')
|
55
66
|
end
|
56
67
|
|
57
|
-
default_headers.merge(user_headers).inject({}) do |final, (key, value)|
|
68
|
+
headers = default_headers.merge(user_headers).inject({}) do |final, (key, value)|
|
58
69
|
final[key.to_s.gsub(/_/, '-').capitalize] = value.to_s
|
59
|
-
|
70
|
+
final
|
60
71
|
end
|
72
|
+
|
73
|
+
headers.merge!(@payload.headers) if @payload
|
74
|
+
headers
|
61
75
|
end
|
62
76
|
|
63
77
|
def net_http_class
|
@@ -108,10 +122,10 @@ module RestClient
|
|
108
122
|
net = net_http_class.new(uri.host, uri.port)
|
109
123
|
net.use_ssl = uri.is_a?(URI::HTTPS)
|
110
124
|
if @verify_ssl == false
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
125
|
+
net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
126
|
+
elsif @verify_ssl.is_a? Integer
|
127
|
+
net.verify_mode = @verify_ssl
|
128
|
+
end
|
115
129
|
net.cert = @ssl_client_cert if @ssl_client_cert
|
116
130
|
net.key = @ssl_client_key if @ssl_client_key
|
117
131
|
net.ca_file = @ssl_ca_file if @ssl_ca_file
|
@@ -130,7 +144,7 @@ module RestClient
|
|
130
144
|
elsif @raw_response
|
131
145
|
RawResponse.new(@tf, res)
|
132
146
|
else
|
133
|
-
nil
|
147
|
+
Response.new(nil, res)
|
134
148
|
end
|
135
149
|
end
|
136
150
|
rescue EOFError
|
data/lib/restclient/resource.rb
CHANGED
@@ -45,38 +45,38 @@ module RestClient
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
def get(additional_headers={})
|
48
|
+
def get(additional_headers={}, &b)
|
49
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
49
50
|
Request.execute(options.merge(
|
50
51
|
:method => :get,
|
51
52
|
:url => url,
|
52
|
-
:headers => headers
|
53
|
-
))
|
53
|
+
:headers => headers), &b)
|
54
54
|
end
|
55
55
|
|
56
|
-
def post(payload, additional_headers={})
|
56
|
+
def post(payload, additional_headers={}, &b)
|
57
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
57
58
|
Request.execute(options.merge(
|
58
59
|
:method => :post,
|
59
60
|
:url => url,
|
60
61
|
:payload => payload,
|
61
|
-
:headers => headers
|
62
|
-
))
|
62
|
+
:headers => headers), &b)
|
63
63
|
end
|
64
64
|
|
65
|
-
def put(payload, additional_headers={})
|
65
|
+
def put(payload, additional_headers={}, &b)
|
66
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
66
67
|
Request.execute(options.merge(
|
67
68
|
:method => :put,
|
68
69
|
:url => url,
|
69
70
|
:payload => payload,
|
70
|
-
:headers => headers
|
71
|
-
))
|
71
|
+
:headers => headers), &b)
|
72
72
|
end
|
73
73
|
|
74
|
-
def delete(additional_headers={})
|
74
|
+
def delete(additional_headers={}, &b)
|
75
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
75
76
|
Request.execute(options.merge(
|
76
77
|
:method => :delete,
|
77
78
|
:url => url,
|
78
|
-
:headers => headers
|
79
|
-
))
|
79
|
+
:headers => headers), &b)
|
80
80
|
end
|
81
81
|
|
82
82
|
def to_s
|
data/spec/base.rb
CHANGED
Binary file
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/base"
|
2
|
+
|
3
|
+
describe RestClient::Payload do
|
4
|
+
context "A regular Payload" do
|
5
|
+
it "should use standard enctype as default content-type" do
|
6
|
+
RestClient::Payload::UrlEncoded.new({}).headers['Content-Type'].
|
7
|
+
should == 'application/x-www-form-urlencoded'
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should form properly encoded params" do
|
11
|
+
RestClient::Payload::UrlEncoded.new({:foo => 'bar'}).to_s.
|
12
|
+
should == "foo=bar"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should properly handle hashes as parameter" do
|
16
|
+
RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz' }}).to_s.
|
17
|
+
should == "foo[bar]=baz"
|
18
|
+
RestClient::Payload::UrlEncoded.new({:foo => {:bar => {:baz => 'qux' }}}).to_s.
|
19
|
+
should == "foo[bar][baz]=qux"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should form properly use symbols as parameters" do
|
23
|
+
RestClient::Payload::UrlEncoded.new({:foo => :bar}).to_s.
|
24
|
+
should == "foo=bar"
|
25
|
+
RestClient::Payload::UrlEncoded.new({:foo => {:bar => :baz }}).to_s.
|
26
|
+
should == "foo[bar]=baz"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context "A multipart Payload" do
|
32
|
+
it "should use standard enctype as default content-type" do
|
33
|
+
m = RestClient::Payload::Multipart.new({})
|
34
|
+
m.stub!(:boundary).and_return(123)
|
35
|
+
m.headers['Content-Type'].should == 'multipart/form-data; boundary="123"'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should form properly seperated multipart data" do
|
39
|
+
m = RestClient::Payload::Multipart.new([[:bar , "baz"], [:foo, "bar"]])
|
40
|
+
m.to_s.should == <<-EOS
|
41
|
+
--#{m.boundary}\r
|
42
|
+
Content-Disposition: multipart/form-data; name="bar"\r
|
43
|
+
\r
|
44
|
+
baz\r
|
45
|
+
--#{m.boundary}\r
|
46
|
+
Content-Disposition: multipart/form-data; name="foo"\r
|
47
|
+
\r
|
48
|
+
bar\r
|
49
|
+
--#{m.boundary}--\r
|
50
|
+
EOS
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should form properly seperated multipart data" do
|
54
|
+
f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
|
55
|
+
m = RestClient::Payload::Multipart.new({:foo => f})
|
56
|
+
m.to_s.should == <<-EOS
|
57
|
+
--#{m.boundary}\r
|
58
|
+
Content-Disposition: multipart/form-data; name="foo"; filename="master_shake.jpg"\r
|
59
|
+
Content-Type: image/jpeg\r
|
60
|
+
\r
|
61
|
+
#{IO.read(f.path)}\r
|
62
|
+
--#{m.boundary}--\r
|
63
|
+
EOS
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should detect optional (original) content type and filename" do
|
67
|
+
f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
|
68
|
+
f.instance_eval "def content_type; 'text/plain'; end"
|
69
|
+
f.instance_eval "def original_filename; 'foo.txt'; end"
|
70
|
+
m = RestClient::Payload::Multipart.new({:foo => f})
|
71
|
+
m.to_s.should == <<-EOS
|
72
|
+
--#{m.boundary}\r
|
73
|
+
Content-Disposition: multipart/form-data; name="foo"; filename="foo.txt"\r
|
74
|
+
Content-Type: text/plain\r
|
75
|
+
\r
|
76
|
+
#{IO.read(f.path)}\r
|
77
|
+
--#{m.boundary}--\r
|
78
|
+
EOS
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should handle hash in hash parameters" do
|
82
|
+
m = RestClient::Payload::Multipart.new({:bar => {:baz => "foo"}})
|
83
|
+
m.to_s.should == <<-EOS
|
84
|
+
--#{m.boundary}\r
|
85
|
+
Content-Disposition: multipart/form-data; name="bar[baz]"\r
|
86
|
+
\r
|
87
|
+
foo\r
|
88
|
+
--#{m.boundary}--\r
|
89
|
+
EOS
|
90
|
+
|
91
|
+
f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
|
92
|
+
f.instance_eval "def content_type; 'text/plain'; end"
|
93
|
+
f.instance_eval "def original_filename; 'foo.txt'; end"
|
94
|
+
m = RestClient::Payload::Multipart.new({:foo => {:bar => f}})
|
95
|
+
m.to_s.should == <<-EOS
|
96
|
+
--#{m.boundary}\r
|
97
|
+
Content-Disposition: multipart/form-data; name="foo[bar]"; filename="foo.txt"\r
|
98
|
+
Content-Type: text/plain\r
|
99
|
+
\r
|
100
|
+
#{IO.read(f.path)}\r
|
101
|
+
--#{m.boundary}--\r
|
102
|
+
EOS
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
context "Payload generation" do
|
108
|
+
it "should recognize standard urlencoded params" do
|
109
|
+
RestClient::Payload.generate({"foo" => 'bar'}).should be_kind_of(RestClient::Payload::UrlEncoded)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should recognize multipart params" do
|
113
|
+
f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
|
114
|
+
RestClient::Payload.generate({"foo" => f}).should be_kind_of(RestClient::Payload::Multipart)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should be multipart if forced" do
|
118
|
+
RestClient::Payload.generate({"foo" => "bar", :multipart => true}).should be_kind_of(RestClient::Payload::Multipart)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should return data if no of the above" do
|
122
|
+
RestClient::Payload.generate("data").should be_kind_of(RestClient::Payload::Base)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should recognize nested multipart payloads" do
|
126
|
+
f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
|
127
|
+
RestClient::Payload.generate({"foo" => {"file" => f}}).should be_kind_of(RestClient::Payload::Multipart)
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
data/spec/request_spec.rb
CHANGED
@@ -93,17 +93,25 @@ describe RestClient::Request do
|
|
93
93
|
|
94
94
|
it "merges user headers with the default headers" do
|
95
95
|
@request.should_receive(:default_headers).and_return({ '1' => '2' })
|
96
|
-
@request.make_headers('3' => '4')
|
96
|
+
headers = @request.make_headers('3' => '4')
|
97
|
+
headers.should have_key('1')
|
98
|
+
headers['1'].should == '2'
|
99
|
+
headers.should have_key('3')
|
100
|
+
headers['3'].should == '4'
|
97
101
|
end
|
98
102
|
|
99
103
|
it "prefers the user header when the same header exists in the defaults" do
|
100
104
|
@request.should_receive(:default_headers).and_return({ '1' => '2' })
|
101
|
-
@request.make_headers('1' => '3')
|
105
|
+
headers = @request.make_headers('1' => '3')
|
106
|
+
headers.should have_key('1')
|
107
|
+
headers['1'].should == '3'
|
102
108
|
end
|
103
109
|
|
104
110
|
it "converts header symbols from :content_type to 'Content-type'" do
|
105
111
|
@request.should_receive(:default_headers).and_return({})
|
106
|
-
@request.make_headers(:content_type => 'abc')
|
112
|
+
headers = @request.make_headers(:content_type => 'abc')
|
113
|
+
headers.should have_key('Content-type')
|
114
|
+
headers['Content-type'].should == 'abc'
|
107
115
|
end
|
108
116
|
|
109
117
|
it "converts header values to strings" do
|
@@ -115,7 +123,7 @@ describe RestClient::Request do
|
|
115
123
|
klass = mock("net:http class")
|
116
124
|
@request.should_receive(:net_http_request_class).with(:put).and_return(klass)
|
117
125
|
klass.should_receive(:new).and_return('result')
|
118
|
-
@request.should_receive(:transmit).with(@uri, 'result',
|
126
|
+
@request.should_receive(:transmit).with(@uri, 'result', kind_of(RestClient::Payload::Base))
|
119
127
|
@request.execute_inner
|
120
128
|
end
|
121
129
|
|
@@ -350,13 +358,13 @@ describe RestClient::Request do
|
|
350
358
|
@request.verify_ssl.should == false
|
351
359
|
end
|
352
360
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
361
|
+
it "should set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is false" do
|
362
|
+
@net.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
363
|
+
@http.stub!(:request)
|
364
|
+
@request.stub!(:process_result)
|
365
|
+
@request.stub!(:response_log)
|
366
|
+
@request.transmit(@uri, 'req', 'payload')
|
367
|
+
end
|
360
368
|
|
361
369
|
it "should not set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is true" do
|
362
370
|
@request = RestClient::Request.new(:method => :put, :url => 'https://some/resource', :payload => 'payload', :verify_ssl => true)
|
@@ -367,19 +375,18 @@ describe RestClient::Request do
|
|
367
375
|
@request.transmit(@uri, 'req', 'payload')
|
368
376
|
end
|
369
377
|
|
370
|
-
|
371
|
-
|
372
|
-
OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
378
|
+
it "should set net.verify_mode to the passed value if verify_ssl is an OpenSSL constant" do
|
379
|
+
mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
373
380
|
@request = RestClient::Request.new( :method => :put,
|
374
381
|
:url => 'https://some/resource',
|
375
382
|
:payload => 'payload',
|
376
383
|
:verify_ssl => mode )
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
384
|
+
@net.should_receive(:verify_mode=).with(mode)
|
385
|
+
@http.stub!(:request)
|
386
|
+
@request.stub!(:process_result)
|
387
|
+
@request.stub!(:response_log)
|
388
|
+
@request.transmit(@uri, 'req', 'payload')
|
389
|
+
end
|
383
390
|
|
384
391
|
it "should default to not having an ssl_client_cert" do
|
385
392
|
@request.ssl_client_cert.should be(nil)
|
@@ -473,4 +480,18 @@ describe RestClient::Request do
|
|
473
480
|
@request.stub!(:response_log)
|
474
481
|
@request.transmit(@uri, 'req', 'payload')
|
475
482
|
end
|
483
|
+
|
484
|
+
it "should still return a response object for 204 No Content responses" do
|
485
|
+
@request = RestClient::Request.new(
|
486
|
+
:method => :put,
|
487
|
+
:url => 'https://some/resource',
|
488
|
+
:payload => 'payload'
|
489
|
+
)
|
490
|
+
net_http_res = Net::HTTPNoContent.new("", "204", "No Content")
|
491
|
+
net_http_res.stub(:read_body).and_return(nil)
|
492
|
+
@http.should_receive(:request).and_return(@request.fetch_body(net_http_res))
|
493
|
+
response = @request.transmit(@uri, 'req', 'payload')
|
494
|
+
response.should_not be_nil
|
495
|
+
response.code.should equal(204)
|
496
|
+
end
|
476
497
|
end
|
data/spec/resource_spec.rb
CHANGED
@@ -42,19 +42,19 @@ describe RestClient::Resource do
|
|
42
42
|
@resource.password.should == 'pass'
|
43
43
|
end
|
44
44
|
|
45
|
-
it "
|
45
|
+
it "concatenates urls, inserting a slash when it needs one" do
|
46
46
|
@resource.concat_urls('http://example.com', 'resource').should == 'http://example.com/resource'
|
47
47
|
end
|
48
48
|
|
49
|
-
it "
|
49
|
+
it "concatenates urls, using no slash if the first url ends with a slash" do
|
50
50
|
@resource.concat_urls('http://example.com/', 'resource').should == 'http://example.com/resource'
|
51
51
|
end
|
52
52
|
|
53
|
-
it "
|
53
|
+
it "concatenates urls, using no slash if the second url starts with a slash" do
|
54
54
|
@resource.concat_urls('http://example.com', '/resource').should == 'http://example.com/resource'
|
55
55
|
end
|
56
56
|
|
57
|
-
it "
|
57
|
+
it "concatenates even non-string urls, :posts + 1 => 'posts/1'" do
|
58
58
|
@resource.concat_urls(:posts, 1).should == 'posts/1'
|
59
59
|
end
|
60
60
|
|
data/spec/response_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/base'
|
|
2
2
|
|
3
3
|
describe RestClient::Response do
|
4
4
|
before do
|
5
|
-
@net_http_res = mock('net http response')
|
5
|
+
@net_http_res = mock('net http response', :to_hash => {"Status" => ["200 OK"]})
|
6
6
|
@response = RestClient::Response.new('abc', @net_http_res)
|
7
7
|
end
|
8
8
|
|
@@ -13,4 +13,9 @@ describe RestClient::Response do
|
|
13
13
|
it "accepts nil strings and sets it to empty for the case of HEAD" do
|
14
14
|
RestClient::Response.new(nil, @net_http_res).should == ""
|
15
15
|
end
|
16
|
+
|
17
|
+
it "test headers and raw headers" do
|
18
|
+
@response.raw_headers["Status"][0].should == "200 OK"
|
19
|
+
@response.headers[:status].should == "200 OK"
|
20
|
+
end
|
16
21
|
end
|
metadata
CHANGED
@@ -1,20 +1,30 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Wiggins
|
8
|
+
- Archiloque
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date: 2009-12-
|
13
|
+
date: 2009-12-28 00:00:00 +01:00
|
13
14
|
default_executable: restclient
|
14
|
-
dependencies:
|
15
|
-
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: mime-types
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "1.16"
|
25
|
+
version:
|
16
26
|
description: "A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
|
17
|
-
email:
|
27
|
+
email: rest.client@librelist.com
|
18
28
|
executables:
|
19
29
|
- restclient
|
20
30
|
extensions: []
|
@@ -30,20 +40,24 @@ files:
|
|
30
40
|
- lib/restclient.rb
|
31
41
|
- lib/restclient/exceptions.rb
|
32
42
|
- lib/restclient/mixin/response.rb
|
43
|
+
- lib/restclient/net_http_ext.rb
|
44
|
+
- lib/restclient/payload.rb
|
33
45
|
- lib/restclient/raw_response.rb
|
34
46
|
- lib/restclient/request.rb
|
35
47
|
- lib/restclient/resource.rb
|
36
48
|
- lib/restclient/response.rb
|
37
49
|
- spec/base.rb
|
38
50
|
- spec/exceptions_spec.rb
|
51
|
+
- spec/master_shake.jpg
|
39
52
|
- spec/mixin/response_spec.rb
|
53
|
+
- spec/payload_spec.rb
|
40
54
|
- spec/raw_response_spec.rb
|
41
55
|
- spec/request_spec.rb
|
42
56
|
- spec/resource_spec.rb
|
43
57
|
- spec/response_spec.rb
|
44
58
|
- spec/restclient_spec.rb
|
45
59
|
has_rdoc: true
|
46
|
-
homepage: http://rest-client
|
60
|
+
homepage: http://github.com/archiloque/rest-client
|
47
61
|
licenses: []
|
48
62
|
|
49
63
|
post_install_message:
|
@@ -74,6 +88,7 @@ test_files:
|
|
74
88
|
- spec/base.rb
|
75
89
|
- spec/exceptions_spec.rb
|
76
90
|
- spec/mixin/response_spec.rb
|
91
|
+
- spec/payload_spec.rb
|
77
92
|
- spec/raw_response_spec.rb
|
78
93
|
- spec/request_spec.rb
|
79
94
|
- spec/resource_spec.rb
|