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.

@@ -14,14 +14,27 @@ of specifying actions: get, put, post, delete.
14
14
 
15
15
  RestClient.delete 'http://example.com/resource'
16
16
 
17
- See RestClient module docs for details.
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', :user => 'adam', :password => 'secret', :timeout => 20, :open_timeout => 5
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 (adam at heroku dot com)
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.heroku.com
93
+ Main page: http://github.com/archiloque/rest-client
94
+
95
+ Rdoc: http://rdoc.info/projects/archiloque/rest-client
149
96
 
150
- http://github.com/adamwiggins/rest-client
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/restclient.rb')
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.4
1
+ 1.1.0
@@ -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
@@ -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
- # access it internally if you'd like to use a method not directly supported by the
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 = process_payload(args[: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 = args[:ssl_client_key] || nil
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
- final
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
- net.verify_mode = OpenSSL::SSL::VERIFY_NONE
112
- elsif @verify_ssl.is_a? Integer
113
- net.verify_mode = @verify_ssl
114
- end
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
@@ -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.merge(additional_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.merge(additional_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.merge(additional_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.merge(additional_headers)
79
- ))
79
+ :headers => headers), &b)
80
80
  end
81
81
 
82
82
  def to_s
@@ -1,4 +1,10 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
3
 
4
+ begin
5
+ require "ruby-debug"
6
+ rescue LoadError
7
+ # NOP, ignore
8
+ end
9
+
4
10
  require File.dirname(__FILE__) + '/../lib/restclient'
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
@@ -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').should == { '1' => '2', '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').should == { '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').should == { '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', 'payload')
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
- it "should set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is false" do
354
- @net.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
355
- @http.stub!(:request)
356
- @request.stub!(:process_result)
357
- @request.stub!(:response_log)
358
- @request.transmit(@uri, 'req', 'payload')
359
- end
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
- it "should set net.verify_mode to the passed value if verify_ssl is an OpenSSL constant" do
371
- mode = OpenSSL::SSL::VERIFY_PEER |
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
- @net.should_receive(:verify_mode=).with(mode)
378
- @http.stub!(:request)
379
- @request.stub!(:process_result)
380
- @request.stub!(:response_log)
381
- @request.transmit(@uri, 'req', 'payload')
382
- end
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
@@ -42,19 +42,19 @@ describe RestClient::Resource do
42
42
  @resource.password.should == 'pass'
43
43
  end
44
44
 
45
- it "concatinates urls, inserting a slash when it needs one" do
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 "concatinates urls, using no slash if the first url ends with a slash" do
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 "concatinates urls, using no slash if the second url starts with a slash" do
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 "concatinates even non-string urls, :posts + 1 => 'posts/1'" do
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
 
@@ -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
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-12 00:00:00 -08:00
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: adam@heroku.com
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.heroku.com/
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