rest-client 1.8.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,28 +10,26 @@ of specifying actions: get, put, post, delete.
10
10
 
11
11
  == Requirements
12
12
 
13
- MRI Ruby 1.9.2 and newer are supported. Alternative interpreters compatible with
14
- 1.9.1+ should work as well.
13
+ MRI Ruby 1.9.3 and newer are supported. Alternative interpreters compatible with
14
+ 1.9+ should work as well.
15
15
 
16
- Ruby 1.8.7 is no longer supported. That's because the Ruby 1.8.7 interpreter
17
- itself no longer has official support, _not_ _even_ _security_ _patches!_ If you
18
- have been putting off upgrading your servers, now is the time.
19
- ({More info is on the Ruby developers'
20
- blog.}[http://www.ruby-lang.org/en/news/2013/06/30/we-retire-1-8-7/])
16
+ Earlier Ruby versions such as 1.8.7 and 1.9.2 are no longer supported. These
17
+ versions no longer have any official support, and do not receive security
18
+ updates.
21
19
 
22
- The rest-client gem depends on these other gems for installation and usage:
20
+ The rest-client gem depends on these other gems for usage at runtime:
23
21
 
24
22
  * {mime-types}[http://rubygems.org/gems/mime-types]
25
23
  * {netrc}[http://rubygems.org/gems/netrc]
26
- * {rdoc}[http://rubygems.org/gems/rdoc]
24
+ * {http-cookie}[https://rubygems.org/gems/http-cookie]
27
25
 
28
- If you want to hack on the code, you should also have {the Bundler
29
- gem}[http://bundler.io/] installed so it can manage all necessary development
30
- dependencies for you.
26
+ There are also several development dependencies. It's recommended to use
27
+ {bundler}[http://bundler.io/] to manage these dependencies for hacking on
28
+ rest-client.
31
29
 
32
30
  == Usage: Raw URL
33
31
 
34
- require 'rest_client'
32
+ require 'rest-client'
35
33
 
36
34
  RestClient.get 'http://example.com/resource'
37
35
 
@@ -67,6 +65,32 @@ dependencies for you.
67
65
  }
68
66
  })
69
67
 
68
+ == Passing advanced options
69
+
70
+ The top level helper methods like RestClient.get accept a headers hash as
71
+ their last argument and don't allow passing more complex options. But these
72
+ helpers are just thin wrappers around RestClient::Request.execute.
73
+
74
+ RestClient::Request.execute(method: :get, url: 'http://example.com/resource',
75
+ timeout: 10)
76
+
77
+ You can also use this to pass a payload for HTTP verbs like DELETE, where the
78
+ RestClient.delete helper doesn't accept a payload.
79
+
80
+ RestClient::Request.execute(method: :delete, url: 'http://example.com/resource',
81
+ payload: 'foo', headers: {myheader: 'bar'})
82
+
83
+ Due to unfortunate choices in the original API, the params used to populate the
84
+ query string are actually taken out of the headers hash. So if you want to pass
85
+ both the params hash and more complex options, use the special key
86
+ <tt>:params</tt> in the headers hash. This design may change in a future major
87
+ release.
88
+
89
+ RestClient::Request.execute(method: :get, url: 'http://example.com/resource',
90
+ timeout: 10, headers: {params: {foo: 'bar'}})
91
+
92
+ ➔ GET http://example.com/resource?foo=bar
93
+
70
94
  == Multipart
71
95
 
72
96
  Yeah, that's right! This does multipart sends for you!
@@ -105,6 +129,7 @@ See RestClient::Resource docs for details.
105
129
  * for result codes 301, 302 or 307, the redirection will be followed if the request is a GET or a HEAD
106
130
  * for result code 303, the redirection will be followed and the request transformed into a GET
107
131
  * for other cases, a RestClient::Exception holding the Response will be raised; a specific exception class will be thrown for known error codes
132
+ * call <tt>.response</tt> on the exception to get the server's response
108
133
 
109
134
  RestClient.get 'http://example.com/resource'
110
135
  ➔ RestClient::ResourceNotFound: RestClient::ResourceNotFound
@@ -118,6 +143,34 @@ See RestClient::Resource docs for details.
118
143
 
119
144
  == Result handling
120
145
 
146
+ The result of a RestClient::Request is a RestClient::Response object.
147
+
148
+ <b>New in 2.0:</b> RestClient::Response objects are now a subclass of String.
149
+ Previously, they were a real String object with response functionality mixed
150
+ in, which was very confusing to work with.
151
+
152
+ Response objects have several useful methods. (See the class rdoc for more details.)
153
+
154
+ * Response#code: The HTTP response code
155
+ * Response#body: The response body as a string. (AKA .to_s)
156
+ * Response#headers: A hash of HTTP response headers
157
+ * Response#cookies: A hash of HTTP cookies set by the server
158
+ * Response#cookie_jar: <em>New in 1.8</em> An HTTP::CookieJar of cookies
159
+ * Response#request: The RestClient::Request object used to make the request
160
+ * Response#history: If redirection was followed, a list of prior Response objects
161
+
162
+ >> RestClient.get('http://example.com')
163
+ => <RestClient::Response 200 "<!doctype h...">
164
+
165
+ >> begin
166
+ >> RestClient.get('http://example.com/notfound')
167
+ >> rescue RestClient::ExceptionWithResponse => err
168
+ >> err.response
169
+ >> end
170
+ => <RestClient::Response 404 "<!doctype h...">
171
+
172
+ === Response callbacks
173
+
121
174
  A block can be passed to the RestClient method. This block will then be called with the Response.
122
175
  Response.return! can be called to invoke the default response's behavior.
123
176
 
@@ -249,6 +302,20 @@ the URL for GET, HEAD and DELETE requests, escaping the keys and values as neede
249
302
  RestClient.get 'http://example.com/resource', :params => {:foo => 'bar', :baz => 'qux'}
250
303
  # will GET http://example.com/resource?foo=bar&baz=qux
251
304
 
305
+ == Headers
306
+
307
+ Request headers can be set by passing a ruby hash containing keys and values
308
+ representing header names and values:
309
+
310
+ # GET request with modified headers
311
+ RestClient.get 'http://example.com/resource', {:Authorization => 'Bearer cT0febFoD5lxAlNAXHo6g'}
312
+
313
+ # POST request with modified headers
314
+ RestClient.post 'http://example.com/resource', {:foo => 'bar', :baz => 'qux'}, {:Authorization => 'Bearer cT0febFoD5lxAlNAXHo6g'}
315
+
316
+ # DELETE request with modified headers
317
+ RestClient.delete 'http://example.com/resource', {:Authorization => 'Bearer cT0febFoD5lxAlNAXHo6g'}
318
+
252
319
  == Cookies
253
320
 
254
321
  Request and Response objects know about HTTP cookies, and will automatically
@@ -302,15 +369,16 @@ Have a look at rest-client-components: http://github.com/crohr/rest-client-compo
302
369
 
303
370
  == Credits
304
371
 
305
- REST Client Team:: Matthew Manning, Lawrence Leonard Gilbert, Andy Brody
372
+ REST Client Team:: Andy Brody
306
373
 
307
374
  Creator:: Adam Wiggins
308
375
 
309
- Maintainer Emeritus:: Julien Kirch
376
+ Maintainers Emeriti:: Lawrence Leonard Gilbert, Matthew Manning, Julien Kirch
310
377
 
311
378
  Major contributions:: Blake Mizerany, Julien Kirch
312
379
 
313
- Patches contributed by many, including Chris Anderson, Greg Borenstein, Ardekantur, Pedro Belo, Rafael Souza, Rick Olson, Aman Gupta, François Beausoleil and Nick Plante.
380
+ A great many generous folks have contributed features and patches.
381
+ See AUTHORS for the full list.
314
382
 
315
383
  == Legal
316
384
 
data/Rakefile CHANGED
@@ -34,6 +34,22 @@ RSpec::Core::RakeTask.new('rcov') do |t|
34
34
  t.rcov_opts = ['--exclude', 'examples']
35
35
  end
36
36
 
37
+ desc 'Regenerate authors file'
38
+ task :authors do
39
+ Dir.chdir(File.dirname(__FILE__)) do
40
+ File.open('AUTHORS', 'w') do |f|
41
+ f.write( <<-EOM
42
+ The Ruby REST Client would not be what it is today without the help of
43
+ the following kind souls:
44
+
45
+ EOM
46
+ )
47
+ end
48
+
49
+ sh 'git shortlog -s | cut -f 2 >> AUTHORS'
50
+ end
51
+ end
52
+
37
53
  task :default do
38
54
  sh 'rake -T'
39
55
  end
@@ -56,11 +56,9 @@ if @verb
56
56
  end
57
57
 
58
58
  POSSIBLE_VERBS.each do |m|
59
- eval <<-end_eval
60
- def #{m}(path, *args, &b)
61
- r[path].#{m}(*args, &b)
62
- end
63
- end_eval
59
+ define_method(m.to_sym) do |path, *args, &b|
60
+ r[path].public_send(m.to_sym, *args, &b)
61
+ end
64
62
  end
65
63
 
66
64
  def method_missing(s, * args, & b)
data/history.md CHANGED
@@ -1,3 +1,64 @@
1
+ # 2.0.0
2
+
3
+ This release is largely API compatible, but makes several breaking changes.
4
+
5
+ - Drop support for Ruby 1.9.2
6
+ - Respect Content-Type charset header provided by server. Previously,
7
+ rest-client would not override the string encoding chosen by Net::HTTP. Now
8
+ responses that specify a charset will yield a body string in that encoding.
9
+ For example, `Content-Type: text/plain; charset=EUC-JP` will return a String
10
+ encoded with `Encoding::EUC_JP`.
11
+ - Change exceptions raised on request timeout. Instead of
12
+ RestClient::RequestTimeout (which is still used for HTTP 408), network
13
+ timeouts will now raise either RestClient::Exceptions::ReadTimeout or
14
+ RestClient::Exceptions::OpenTimeout, both of which inherit from
15
+ RestClient::Exceptions::Timeout. This class also makes the original wrapped
16
+ exception available as `#original_exception`.
17
+ - Rename `:timeout` to `:read_timeout`. When `:timeout` is passed, now set both
18
+ `:read_timeout` and `:open_timeout`.
19
+ - Change default HTTP Accept header to `*/*`
20
+ - Use a more descriptive User-Agent header by default
21
+ - Drop RC4-MD5 from default cipher list
22
+ - Only prepend http:// to URIs without a scheme
23
+ - Fix some support for using IPv6 addresses in URLs (still affected by Ruby
24
+ 2.0+ bug https://bugs.ruby-lang.org/issues/9129, with the fix expected to be
25
+ backported to 2.0 and 2.1)
26
+ - `Response` objects are now a subclass of `String` rather than a `String` that
27
+ mixes in the response functionality. Most of the methods remain unchanged,
28
+ but this makes it much easier to understand what is happening when you look
29
+ at a RestClient response object. There are a few additional changes:
30
+ - Response objects now implement `.inspect` to make this distinction clearer.
31
+ - `Response#to_i` will now behave like `String#to_i` instead of returning the
32
+ HTTP response code, which was very surprising behavior.
33
+ - `Response#body` and `#to_s` will now return a true `String` object rather
34
+ than self. Previously there was no easy way to get the true `String` response
35
+ instead of the Frankenstein response string object with AbstractResponse
36
+ mixed in.
37
+ - Handle multiple HTTP response headers with the same name (except for
38
+ Set-Cookie, which is special) by joining the values with a comma space,
39
+ compliant with RFC 7230
40
+ - Don't set basic auth header if explicit `Authorization` header is specified
41
+ - Add `:proxy` option to requests, which can be used for thread-safe
42
+ per-request proxy configuration, overriding `RestClient.proxy`
43
+ - Allow overriding `ENV['http_proxy']` to disable proxies by setting
44
+ `RestClient.proxy` to a falsey value. Previously there was no way in Ruby 2.x
45
+ to turn off a proxy specified in the environment without changing `ENV`.
46
+ - Add actual support for streaming request payloads. Previously rest-client
47
+ would call `.to_s` even on RestClient::Payload::Streamed objects. Instead,
48
+ treat any object that responds to `.read` as a streaming payload and pass it
49
+ through to `.body_stream=` on the Net:HTTP object. This massively reduces the
50
+ memory required for large file uploads.
51
+ - Remove `RestClient::MaxRedirectsReached` in favor of the normal
52
+ `ExceptionWithResponse` subclasses. This makes the response accessible on the
53
+ exception object as `.response`, making it possible for callers to tell what
54
+ has actually happened when the redirect limit is reached.
55
+ - When following HTTP redirection, store a list of each previous response on
56
+ the response object as `.history`. This makes it possible to access the
57
+ original response headers and body before the redirection was followed.
58
+ - Add `:before_execution_proc` option to `RestClient::Request`. This makes it
59
+ possible to add procs like `RestClient.add_before_execution_proc` to a single
60
+ request without global state.
61
+
1
62
  # 1.8.0
2
63
 
3
64
  - Security: implement standards compliant cookie handling by adding a
@@ -7,6 +7,7 @@ require 'zlib'
7
7
  require File.dirname(__FILE__) + '/restclient/version'
8
8
  require File.dirname(__FILE__) + '/restclient/platform'
9
9
  require File.dirname(__FILE__) + '/restclient/exceptions'
10
+ require File.dirname(__FILE__) + '/restclient/utils'
10
11
  require File.dirname(__FILE__) + '/restclient/request'
11
12
  require File.dirname(__FILE__) + '/restclient/abstract_response'
12
13
  require File.dirname(__FILE__) + '/restclient/response'
@@ -89,8 +90,23 @@ module RestClient
89
90
  Request.execute(:method => :options, :url => url, :headers => headers, &block)
90
91
  end
91
92
 
92
- class << self
93
- attr_accessor :proxy
93
+ # A global proxy URL to use for all requests. This can be overridden on a
94
+ # per-request basis by passing `:proxy` to RestClient::Request.
95
+ def self.proxy
96
+ @proxy
97
+ end
98
+ def self.proxy=(value)
99
+ @proxy = value
100
+ @proxy_set = true
101
+ end
102
+
103
+ # Return whether RestClient.proxy was set explicitly. We use this to
104
+ # differentiate between no value being set and a value explicitly set to nil.
105
+ #
106
+ # @return [Boolean]
107
+ #
108
+ def self.proxy_set?
109
+ !!@proxy_set
94
110
  end
95
111
 
96
112
  # Setup the log for RestClient calls.
@@ -150,6 +166,7 @@ module RestClient
150
166
  # Add a Proc to be called before each request in executed.
151
167
  # The proc parameters will be the http request and the request params.
152
168
  def self.add_before_execution_proc &proc
169
+ raise ArgumentError.new('block is required') unless proc
153
170
  @@before_execution_procs << proc
154
171
  end
155
172
 
@@ -7,11 +7,19 @@ module RestClient
7
7
 
8
8
  attr_reader :net_http_res, :args, :request
9
9
 
10
+ def inspect
11
+ raise NotImplementedError.new('must override in subclass')
12
+ end
13
+
10
14
  # HTTP status code
11
15
  def code
12
16
  @code ||= @net_http_res.code.to_i
13
17
  end
14
18
 
19
+ def history
20
+ @history ||= request.redirection_history || []
21
+ end
22
+
15
23
  # A hash of the headers, beautified with symbols and underscores.
16
24
  # e.g. "Content-type" will become :content_type.
17
25
  def headers
@@ -27,6 +35,9 @@ module RestClient
27
35
  @net_http_res = net_http_res
28
36
  @args = args
29
37
  @request = request
38
+
39
+ # prime redirection history
40
+ history
30
41
  end
31
42
 
32
43
  # Hash of cookies extracted from response headers
@@ -57,79 +68,133 @@ module RestClient
57
68
 
58
69
  # Return the default behavior corresponding to the response code:
59
70
  # the response itself for code in 200..206, redirection for 301, 302 and 307 in get and head cases, redirection for 303 and an exception in other cases
60
- def return! request = nil, result = nil, & block
71
+ def return!(&block)
61
72
  if (200..207).include? code
62
73
  self
63
74
  elsif [301, 302, 307].include? code
64
75
  unless [:get, :head].include? args[:method]
65
- raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
76
+ raise exception_with_response
66
77
  else
67
- follow_redirection(request, result, & block)
78
+ follow_redirection(&block)
68
79
  end
69
80
  elsif code == 303
70
- args[:method] = :get
71
- args.delete :payload
72
- follow_redirection(request, result, & block)
73
- elsif Exceptions::EXCEPTIONS_MAP[code]
74
- raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
81
+ follow_get_redirection(&block)
75
82
  else
76
- raise RequestFailed.new(self, code)
83
+ raise exception_with_response
77
84
  end
78
85
  end
79
86
 
80
87
  def to_i
81
- code
88
+ warn('warning: calling Response#to_i is not recommended')
89
+ super
82
90
  end
83
91
 
84
92
  def description
85
93
  "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
86
94
  end
87
95
 
88
- # Follow a redirection
89
- def follow_redirection request = nil, result = nil, & block
90
- new_args = @args.dup
96
+ # Follow a redirection response by making a new HTTP request to the
97
+ # redirection target.
98
+ def follow_redirection(&block)
99
+ _follow_redirection(@args.dup, &block)
100
+ end
91
101
 
92
- url = headers[:location]
93
- if url !~ /^http/
94
- url = URI.parse(request.url).merge(url).to_s
95
- end
96
- new_args[:url] = url
97
- if request
98
- if request.max_redirects == 0
99
- raise MaxRedirectsReached
100
- end
101
- new_args[:password] = request.password
102
- new_args[:user] = request.user
103
- new_args[:headers] = request.headers
104
- new_args[:max_redirects] = request.max_redirects - 1
105
-
106
- # TODO: figure out what to do with original :cookie, :cookies values
107
- new_args[:headers]['Cookie'] = HTTP::Cookie.cookie_value(
108
- cookie_jar.cookies(new_args.fetch(:url)))
109
- end
102
+ # Follow a redirection response, but change the HTTP method to GET and drop
103
+ # the payload from the original request.
104
+ def follow_get_redirection(&block)
105
+ new_args = @args.dup
106
+ new_args[:method] = :get
107
+ new_args.delete(:payload)
110
108
 
111
- Request.execute(new_args, &block)
109
+ _follow_redirection(new_args, &block)
112
110
  end
113
111
 
112
+ # Convert headers hash into canonical form.
113
+ #
114
+ # Header names will be converted to lowercase symbols with underscores
115
+ # instead of hyphens.
116
+ #
117
+ # Headers specified multiple times will be joined by comma and space,
118
+ # except for Set-Cookie, which will always be an array.
119
+ #
120
+ # Per RFC 2616, if a server sends multiple headers with the same key, they
121
+ # MUST be able to be joined into a single header by a comma. However,
122
+ # Set-Cookie (RFC 6265) cannot because commas are valid within cookie
123
+ # definitions. The newer RFC 7230 notes (3.2.2) that Set-Cookie should be
124
+ # handled as a special case.
125
+ #
126
+ # http://tools.ietf.org/html/rfc2616#section-4.2
127
+ # http://tools.ietf.org/html/rfc7230#section-3.2.2
128
+ # http://tools.ietf.org/html/rfc6265
129
+ #
130
+ # @param headers [Hash]
131
+ # @return [Hash]
132
+ #
114
133
  def self.beautify_headers(headers)
115
134
  headers.inject({}) do |out, (key, value)|
116
- out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first
135
+ key_sym = key.gsub(/-/, '_').downcase.to_sym
136
+
137
+ # Handle Set-Cookie specially since it cannot be joined by comma.
138
+ if key.downcase == 'set-cookie'
139
+ out[key_sym] = value
140
+ else
141
+ out[key_sym] = value.join(', ')
142
+ end
143
+
117
144
  out
118
145
  end
119
146
  end
120
147
 
121
148
  private
122
149
 
123
- # Parse a cookie value and return its content in an Hash
124
- def parse_cookie cookie_content
125
- out = {}
126
- CGI::Cookie::parse(cookie_content).each do |key, cookie|
127
- unless ['expires', 'path'].include? key
128
- out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : ''
129
- end
150
+ # Follow a redirection
151
+ #
152
+ # @param new_args [Hash] Start with this hash of arguments for the
153
+ # redirection request. The hash will be mutated, so be sure to dup any
154
+ # existing hash that should not be modified.
155
+ #
156
+ def _follow_redirection(new_args, &block)
157
+
158
+ # parse location header and merge into existing URL
159
+ url = headers[:location]
160
+
161
+ # handle relative redirects
162
+ unless url.start_with?('http')
163
+ url = URI.parse(request.url).merge(url).to_s
164
+ end
165
+ new_args[:url] = url
166
+
167
+ if request.max_redirects <= 0
168
+ raise exception_with_response
169
+ end
170
+ new_args[:password] = request.password
171
+ new_args[:user] = request.user
172
+ new_args[:headers] = request.headers
173
+ new_args[:max_redirects] = request.max_redirects - 1
174
+
175
+ # TODO: figure out what to do with original :cookie, :cookies values
176
+ new_args[:headers]['Cookie'] = HTTP::Cookie.cookie_value(
177
+ cookie_jar.cookies(new_args.fetch(:url)))
178
+
179
+
180
+ # prepare new request
181
+ new_req = Request.new(new_args)
182
+
183
+ # append self to redirection history
184
+ new_req.redirection_history = history + [self]
185
+
186
+ # execute redirected request
187
+ new_req.execute(&block)
188
+ end
189
+
190
+ def exception_with_response
191
+ begin
192
+ klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
193
+ rescue KeyError
194
+ raise RequestFailed.new(self, code)
130
195
  end
131
- out
196
+
197
+ raise klass.new(self, code)
132
198
  end
133
199
  end
134
-
135
200
  end