restify 1.13.0 → 1.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -6
  3. data/lib/restify/adapter/em.rb +6 -13
  4. data/lib/restify/adapter/pooled_em.rb +35 -40
  5. data/lib/restify/adapter/typhoeus.rb +57 -51
  6. data/lib/restify/context.rb +5 -9
  7. data/lib/restify/error.rb +24 -0
  8. data/lib/restify/global.rb +1 -0
  9. data/lib/restify/logging.rb +1 -1
  10. data/lib/restify/processors/base/parsing.rb +5 -9
  11. data/lib/restify/processors/base.rb +2 -6
  12. data/lib/restify/promise.rb +1 -3
  13. data/lib/restify/request.rb +13 -5
  14. data/lib/restify/resource.rb +2 -2
  15. data/lib/restify/response.rb +0 -2
  16. data/lib/restify/timeout.rb +1 -3
  17. data/lib/restify/version.rb +3 -3
  18. data/spec/restify/cache_spec.rb +2 -2
  19. data/spec/restify/context_spec.rb +10 -7
  20. data/spec/restify/error_spec.rb +10 -0
  21. data/spec/restify/features/head_requests_spec.rb +7 -7
  22. data/spec/restify/features/request_bodies_spec.rb +84 -0
  23. data/spec/restify/features/request_errors_spec.rb +19 -0
  24. data/spec/restify/features/request_headers_spec.rb +16 -17
  25. data/spec/restify/features/response_errors_spec.rb +127 -0
  26. data/spec/restify/global_spec.rb +6 -6
  27. data/spec/restify/link_spec.rb +9 -9
  28. data/spec/restify/processors/base_spec.rb +1 -0
  29. data/spec/restify/processors/json_spec.rb +2 -1
  30. data/spec/restify/processors/msgpack_spec.rb +8 -7
  31. data/spec/restify/promise_spec.rb +8 -4
  32. data/spec/restify/registry_spec.rb +2 -2
  33. data/spec/restify/relation_spec.rb +18 -17
  34. data/spec/restify/resource_spec.rb +9 -8
  35. data/spec/restify/timeout_spec.rb +4 -4
  36. data/spec/restify_spec.rb +52 -57
  37. data/spec/spec_helper.rb +11 -8
  38. data/spec/support/stub_server.rb +106 -0
  39. metadata +30 -23
  40. data/spec/restify/features/response_errors.rb +0 -79
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2b5dacc128de6cbfed604453544f51eaeffd16b9b6cb83f5353ecca66d24a8d
4
- data.tar.gz: 5c3eba335362aea288f851327fa96262e5b464b9a747b092955241bb362e9c0d
3
+ metadata.gz: c29c95a9b1c0b18f2a73b51c99da7cdddf163e1cee33c43272810f5557b1375b
4
+ data.tar.gz: 95f956684154058cc6f25b7d4ffebde6706864b00c1369e91c8f8b5fcc50f0d9
5
5
  SHA512:
6
- metadata.gz: 32569c3544a1ac6734b3c81c1e2b6a46ea7dba324c1f71f5e85bd1df93b8b73bb70114a7cec37dd8e38e561a821e674e8c437c78757d78df75e1e51b1a5a79f8
7
- data.tar.gz: 6f03d156fcc20e0dfba3d7fda0a5c7851ff5d9342fbb9b50c4d90d1f43bf65e92f0ecabb714c4023ce4ca8cfb95b231abd7ea5d1e05339e839ea046b17f3d1fe
6
+ metadata.gz: f420cc4d0c4ebbdade1bc1a780c24218b5eea707bd8e420c113d155d58067936464dc3d4807717de8f515ed586784c9f13476b46c66f68c8d1181501937db3a2
7
+ data.tar.gz: 71da11cdf50d25abc968af02c6157c63f1aa5703c88f7ae28c3394aa028d9f038013d220283510532f7b540dffc69d0abad5fe4952c4e27df4910a4009475ae5
data/CHANGELOG.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Changelog
2
+
2
3
  All notable changes to this project will be documented in this file.
3
4
 
4
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
7
 
7
8
 
8
-
9
9
  ## Unreleased
10
10
  ---
11
11
 
@@ -18,135 +18,227 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
18
18
  ### Breaks
19
19
 
20
20
 
21
+ ## 1.15.2 - (2021-12-23)
22
+
23
+ ---
24
+
25
+ ### Fixes
26
+
27
+ - ActiveSupport v7.0 issues with cache module
28
+
29
+
30
+ ## 1.15.1 - (2021-07-15)
31
+
32
+ ---
33
+
34
+ ### Fixes
35
+
36
+ - Typhoeus internal exception when request timed out
37
+
38
+ ## 1.15.0 - (2021-07-09)
39
+
40
+ ---
41
+
42
+ ### New
43
+
44
+ - Improve memory usage when running lots of requests with typhoeus adapter
45
+ - Use hydra for synchronous requests
46
+ - Increased thread stability of typhoeus adapter (new internal queuing mechanism)
47
+
48
+ ### Changes
49
+
50
+ - Use Ruby 2.5 as baseline for testing and linting
51
+ - Add Ruby 3.0 to automated testing
52
+ - Changed timing behavior for multiple requests due to new internal queuing mechanism for the typhoeus adapter
53
+
54
+ ## 1.14.0 - (2020-12-15)
55
+
56
+ ---
57
+
58
+ ### New
59
+
60
+ - Allow making requests with non-JSON bodies and custom content types (#42)
61
+
21
62
  ## 1.13.0 - (2020-06-12)
63
+
22
64
  ---
23
65
 
24
66
  ### New
25
- * typhoeus: Support setting per-request libcurl options on adapter
26
- * typhoeus: Enable short TCP keepalive probes by default (5s/5s)
27
67
 
68
+ - typhoeus: Support setting per-request libcurl options on adapter
69
+ - typhoeus: Enable short TCP keepalive probes by default (5s/5s)
28
70
 
29
71
  ## 1.12.0 - (2020-04-01)
72
+
30
73
  ---
31
74
 
32
75
  ### Added
33
- * Explicit exception class for HTTP status code 410 (Gone)
76
+
77
+ - Explicit exception class for HTTP status code 410 (Gone)
34
78
 
35
79
  ### Changed
36
80
 
37
81
  ### Fixed
38
- * `GatewayError` exception classes introduced in v1.11.0 now properly inherit from `ServerError` (#30)
39
82
 
83
+ - `GatewayError` exception classes introduced in v1.11.0 now properly inherit from `ServerError` (#30)
40
84
 
41
85
  ## 1.11.0 - (2019-07-11)
86
+
42
87
  ### Added
43
- * Explicit exception classes for HTTP status codes 500, 502, 503, 504
88
+
89
+ - Explicit exception classes for HTTP status codes 500, 502, 503, 504
44
90
 
45
91
  ## 1.10.0 - 2018-12-11
92
+
46
93
  ### Changed
94
+
47
95
  - Raise more specific error on a few status codes (#17)
48
96
  - Complete promises with an empty list (but a list) of dependencies (#18)
49
97
 
50
98
  ## 1.9.0 - 2018-11-13
99
+
51
100
  ### Changed
101
+
52
102
  - Do not raise error on 3XX responses but return responses
53
103
 
54
104
  ## 1.8.0 - 2018-08-22
105
+
55
106
  ### Added
107
+
56
108
  - Add HEAD request method (#16)
57
109
 
58
110
  ## 1.7.0 - 2018-08-15
111
+
59
112
  ### Added
113
+
60
114
  - Introduce promise dependency timeouts (#15)
61
115
 
62
116
  ## 1.6.0 - 2018-08-09
117
+
63
118
  ### Changed
119
+
64
120
  - Specify headers on restify clients and individual requests (#14)
65
121
 
66
122
  ## 1.5.0 - 2018-07-31
123
+
67
124
  ### Added
125
+
68
126
  - Add MessagePack processor enabled by default
69
127
 
70
128
  ### Changed
129
+
71
130
  - Tune typhoeus adapter to be more race-condition resilent
72
131
 
73
132
  ## 1.4.4 - 2018-07-13
133
+
74
134
  ### Added
135
+
75
136
  - Add `#request` to `NetworkError` to ease debugging
76
137
 
77
138
  ### Changed
139
+
78
140
  - Fix race condition in typhoeus adapter
79
141
 
80
142
  ## 1.4.3 - 2017-11-15
143
+
81
144
  ### Added
145
+
82
146
  - Add advanced logging capabilities using logging gem
83
147
 
84
148
  ### Changed
149
+
85
150
  - Improve compatibility with webmocks returning `nil` as headers
86
151
 
87
152
  ## 1.4.1 - 2017-11-15
153
+
88
154
  ### Changed
155
+
89
156
  - Fix possible deadlock issues
90
157
 
91
158
  ## 1.4.0 - 2017-11-10
159
+
92
160
  ### Added
161
+
93
162
  - Add timeout option to requests (only supported by typhoeus adapter)
94
163
 
95
164
  ### Changed
165
+
96
166
  - Fix possible concurrency issue with typhoeus adapter
97
167
 
98
168
  ## 1.3.1 - 2017-11-10
169
+
99
170
  ### Changed
171
+
100
172
  - Improve typhoeus adapters initial request queuing
101
173
  - Disable default pipelining
102
174
 
103
175
  ## 1.3.0 - 2017-11-08
176
+
104
177
  ### Changed
178
+
105
179
  - Improve typhoeus adapter to better utilize concurrency
106
180
  - Default to new typhoeus adapter
107
181
 
108
182
  ## 1.2.1 - 2017-10-30
183
+
109
184
  ### Changed
185
+
110
186
  - Fix issue with Ruby 2.2 compatibility
111
187
 
112
188
  ## 1.2.0 - 2017-10-30
189
+
113
190
  ### Added
191
+
114
192
  - Add experimental PooledEM adapter (#10)
115
193
 
116
194
  ### Changed
195
+
117
196
  - Improve marshaling of resources
118
197
 
119
198
  ## 1.1.0 - 2017-05-12
199
+
120
200
  ### Added
201
+
121
202
  - Add shortcuts for creating fulfilled / rejected promises (#6)
122
203
 
123
204
  ### Changed
205
+
124
206
  - Return response body if no processor matches (#7)
125
207
 
126
208
  ## 1.0.0 - 2016-08-22
209
+
127
210
  ### Added
211
+
128
212
  - Experimental cache API doing nothing for now
129
213
 
130
214
  ### Changed
215
+
131
216
  - Use `~> 1.0` of `concurrent-ruby`
132
217
 
133
218
  ## 0.5.0 - 2016-04-04
219
+
134
220
  ### Added
221
+
135
222
  - Add `sync` option to typhoeus adapter
136
223
  - Add registry for storing entry points
137
224
 
138
225
  ### Changed
226
+
139
227
  - Make eventmachine based adapter default
140
228
 
141
229
  ## 0.4.0 - 2016-02-24
230
+
142
231
  ### Added
232
+
143
233
  - Add method to explicit access resource data
144
234
 
145
235
  ### Changed
236
+
146
237
  - Use typhoeus as default adapter
147
238
  - `Restify.new` returns relation now instead of resource
148
239
 
149
240
  ### Removed
241
+
150
242
  - Drop obligation in favor of simple Concurrent::IVar based promise class.
151
243
  Notable changes:
152
244
  - Returned object us of type `Restify::Promise` now.
@@ -29,7 +29,7 @@ module Restify
29
29
  end
30
30
 
31
31
  # rubocop:disable Style/IdenticalConditionalBranches
32
- def call(request, writer, retried = false)
32
+ def call(request, writer, retried: false)
33
33
  if requests.empty?
34
34
  requests << [request, writer, retried]
35
35
  process_next
@@ -47,10 +47,6 @@ module Restify
47
47
  @pipeline
48
48
  end
49
49
 
50
- # rubocop:disable Metrics/AbcSize
51
- # rubocop:disable Metrics/CyclomaticComplexity
52
- # rubocop:disable Metrics/MethodLength
53
- # rubocop:disable Metrics/PerceivedComplexity
54
50
  def process_next
55
51
  return if requests.empty?
56
52
 
@@ -77,7 +73,7 @@ module Restify
77
73
  req.last_effective_url,
78
74
  req.response_header.status,
79
75
  req.response_header,
80
- req.response
76
+ req.response,
81
77
  )
82
78
 
83
79
  if req.response_header['CONNECTION'] == 'close'
@@ -108,7 +104,6 @@ module Restify
108
104
  end
109
105
  end
110
106
  end
111
- # rubocop:enable all
112
107
  end
113
108
 
114
109
  def call_native(request, writer)
@@ -128,12 +123,10 @@ module Restify
128
123
  return if EventMachine.reactor_running?
129
124
 
130
125
  Thread.new do
131
- begin
132
- EventMachine.run {}
133
- rescue StandardError => e
134
- puts "#{self.class} -> #{e}\n#{e.backtrace.join("\n")}"
135
- raise e
136
- end
126
+ EventMachine.run
127
+ rescue StandardError => e
128
+ puts "#{self.class} -> #{e}\n#{e.backtrace.join("\n")}"
129
+ raise e
137
130
  end
138
131
  end
139
132
  end
@@ -63,7 +63,7 @@ module Restify
63
63
  #
64
64
  def release(conn)
65
65
  @available.unshift(conn) if @available.size < @size
66
- @used -= 1 if @used > 0
66
+ @used -= 1 if @used.positive?
67
67
 
68
68
  logger.debug do
69
69
  "[#{conn.uri}] Released to pool (#{@available.size}/#{@used}/#{size})"
@@ -97,7 +97,7 @@ module Restify
97
97
  private
98
98
 
99
99
  def close(conn)
100
- @used -= 1 if @used > 0
100
+ @used -= 1 if @used.positive?
101
101
  @host[conn.uri.to_s] -= 1
102
102
 
103
103
  conn.close
@@ -164,7 +164,7 @@ module Restify
164
164
  def new(origin)
165
165
  logger.debug do
166
166
  "Connect to '#{origin}' " \
167
- "(#{@connect_timeout}/#{@inactivity_timeout})..."
167
+ "(#{@connect_timeout}/#{@inactivity_timeout})..."
168
168
  end
169
169
 
170
170
  @host[origin] += 1
@@ -197,11 +197,10 @@ module Restify
197
197
  end
198
198
 
199
199
  def initialize(**kwargs)
200
+ super()
200
201
  @pool = Pool.new(**kwargs)
201
202
  end
202
203
 
203
- # rubocop:disable Metrics/MethodLength
204
- # rubocop:disable Metrics/AbcSize
205
204
  # rubocop:disable Metrics/BlockLength
206
205
  def call_native(request, writer)
207
206
  next_tick do
@@ -212,39 +211,37 @@ module Restify
212
211
  end
213
212
 
214
213
  defer.callback do |conn|
215
- begin
216
- req = conn.send request.method.downcase,
217
- keepalive: true,
218
- redirects: 3,
219
- path: request.uri.normalized_path,
220
- query: request.uri.normalized_query,
221
- body: request.body,
222
- head: request.headers
223
-
224
- req.callback do
225
- writer.fulfill Response.new(
226
- request,
227
- req.last_effective_url,
228
- req.response_header.status,
229
- req.response_header,
230
- req.response
231
- )
232
-
233
- if req.response_header['CONNECTION'] == 'close'
234
- @pool.remove(conn)
235
- else
236
- @pool << conn
237
- end
238
- end
239
-
240
- req.errback do
214
+ req = conn.send request.method.downcase,
215
+ keepalive: true,
216
+ redirects: 3,
217
+ path: request.uri.normalized_path,
218
+ query: request.uri.normalized_query,
219
+ body: request.body,
220
+ head: request.headers
221
+
222
+ req.callback do
223
+ writer.fulfill Response.new(
224
+ request,
225
+ req.last_effective_url,
226
+ req.response_header.status,
227
+ req.response_header,
228
+ req.response,
229
+ )
230
+
231
+ if req.response_header['CONNECTION'] == 'close'
241
232
  @pool.remove(conn)
242
- writer.reject(req.error)
233
+ else
234
+ @pool << conn
243
235
  end
244
- rescue Exception => e # rubocop:disable Lint/RescueException
236
+ end
237
+
238
+ req.errback do
245
239
  @pool.remove(conn)
246
- writer.reject(e)
240
+ writer.reject(req.error)
247
241
  end
242
+ rescue Exception => e # rubocop:disable Lint/RescueException
243
+ @pool.remove(conn)
244
+ writer.reject(e)
248
245
  end
249
246
  end
250
247
  end
@@ -261,12 +258,10 @@ module Restify
261
258
  return if EventMachine.reactor_running?
262
259
 
263
260
  Thread.new do
264
- begin
265
- EventMachine.run {}
266
- rescue StandardError => e
267
- logger.error(e)
268
- raise e
269
- end
261
+ EventMachine.run
262
+ rescue StandardError => e
263
+ logger.error(e)
264
+ raise e
270
265
  end
271
266
  end
272
267
  end
@@ -13,60 +13,52 @@ module Restify
13
13
 
14
14
  DEFAULT_HEADERS = {
15
15
  'Expect' => '',
16
- 'Transfer-Encoding' => ''
16
+ 'Transfer-Encoding' => '',
17
17
  }.freeze
18
18
 
19
19
  DEFAULT_OPTIONS = {
20
20
  followlocation: true,
21
21
  tcp_keepalive: true,
22
22
  tcp_keepidle: 5,
23
- tcp_keepintvl: 5
23
+ tcp_keepintvl: 5,
24
24
  }.freeze
25
25
 
26
26
  def initialize(sync: false, options: {}, **kwargs)
27
- @sync = sync
28
27
  @hydra = ::Typhoeus::Hydra.new(**kwargs)
29
28
  @mutex = Mutex.new
30
- @signal = ConditionVariable.new
31
- @thread = nil
32
29
  @options = DEFAULT_OPTIONS.merge(options)
30
+ @queue = Queue.new
31
+ @sync = sync
32
+ @thread = nil
33
+
34
+ super()
33
35
  end
34
36
 
35
37
  def sync?
36
38
  @sync
37
39
  end
38
40
 
39
- # rubocop:disable Metrics/AbcSize
40
- # rubocop:disable Metrics/MethodLength
41
41
  def call_native(request, writer)
42
42
  req = convert(request, writer)
43
43
 
44
44
  if sync?
45
- req.run
45
+ @hydra.queue(req)
46
+ @hydra.run
46
47
  else
47
- @mutex.synchronize do
48
- debug 'request:add',
49
- tag: request.object_id,
50
- method: request.method.upcase,
51
- url: request.uri
52
-
53
- @hydra.queue(req)
54
- @hydra.dequeue_many
55
-
56
- thread.run unless thread.status
57
- end
48
+ debug 'request:add',
49
+ tag: request.object_id,
50
+ method: request.method.upcase,
51
+ url: request.uri,
52
+ timeout: request.timeout
58
53
 
59
- debug 'request:signal', tag: request.object_id
54
+ @queue << convert(request, writer)
60
55
 
61
- @signal.signal
56
+ thread.run unless thread.status
62
57
  end
63
58
  end
64
- # rubocop:enable all
65
59
 
66
60
  private
67
61
 
68
- # rubocop:disable Metrics/AbcSize
69
- # rubocop:disable Metrics/MethodLength
70
62
  def convert(request, writer)
71
63
  ::Typhoeus::Request.new(
72
64
  request.uri,
@@ -75,25 +67,28 @@ module Restify
75
67
  headers: DEFAULT_HEADERS.merge(request.headers),
76
68
  body: request.body,
77
69
  timeout: request.timeout,
78
- connecttimeout: request.timeout
70
+ connecttimeout: request.timeout,
79
71
  ).tap do |req|
80
72
  req.on_complete do |response|
81
73
  debug 'request:complete',
82
74
  tag: request.object_id,
83
- status: response.code
75
+ status: response.code,
76
+ message: response.return_message,
77
+ timeout: response.timed_out?
84
78
 
85
- if response.timed_out?
86
- writer.reject Restify::Timeout.new request
87
- elsif response.code.zero?
79
+ if response.timed_out? || response.code.zero?
88
80
  writer.reject \
89
81
  Restify::NetworkError.new(request, response.return_message)
90
82
  else
91
83
  writer.fulfill convert_back(response, request)
92
84
  end
85
+
86
+ # Add all newly queued requests to active hydra, e.g. requests
87
+ # queued in a completion callback.
88
+ dequeue_all
93
89
  end
94
90
  end
95
91
  end
96
- # rubocop:enable all
97
92
 
98
93
  def convert_back(response, request)
99
94
  uri = request.uri
@@ -117,38 +112,49 @@ module Restify
117
112
  # Recreate thread if nil or dead
118
113
  debug 'hydra:spawn'
119
114
 
120
- @thread = Thread.new { _loop }
115
+ @thread = Thread.new do
116
+ Thread.current.name = 'Restify/Typhoeus Background'
117
+ run
118
+ end
121
119
  end
122
120
 
123
121
  @thread
124
122
  end
125
123
 
126
- def _loop
127
- Thread.current.name = 'Restify/Typhoeus Background'
128
- loop { _run }
129
- end
124
+ def run
125
+ runs = 0
130
126
 
131
- def _ongoing?
132
- @hydra.queued_requests.any? || @hydra.multi.easy_handles.any?
133
- end
127
+ loop do
128
+ if @queue.empty? && runs > 100
129
+ debug 'hydra:gc'
130
+ GC.start(full_mark: false, immediate_sweep: false)
131
+ runs = 0
132
+ end
133
+
134
+ debug 'hydra:pop'
134
135
 
135
- # rubocop:disable Metrics/MethodLength
136
- def _run
137
- debug 'hydra:run'
138
- @hydra.run while _ongoing?
139
- debug 'hydra:completed'
136
+ # Wait for next item and add all available requests to hydra
137
+ @hydra.queue @queue.pop
138
+ dequeue_all
140
139
 
141
- @mutex.synchronize do
142
- return if _ongoing?
140
+ debug 'hydra:run'
141
+ @hydra.run
142
+ runs += 1
143
+ debug 'hydra:completed'
144
+ rescue StandardError => e
145
+ logger.error(e)
146
+ end
147
+ ensure
148
+ debug 'hydra:exit'
149
+ end
143
150
 
144
- debug 'hydra:pause'
145
- @signal.wait(@mutex, 60)
146
- debug 'hydra:resumed'
151
+ def dequeue_all
152
+ loop do
153
+ @hydra.queue @queue.pop(true)
154
+ rescue ThreadError
155
+ break
147
156
  end
148
- rescue StandardError => e
149
- logger.error(e)
150
157
  end
151
- # rubocop:enable all
152
158
 
153
159
  def _log_prefix
154
160
  "[#{object_id}/#{Thread.current.object_id}]"
@@ -47,26 +47,22 @@ module Restify
47
47
  processor.new(context, response).resource
48
48
  end
49
49
 
50
- # rubocop:disable Metrics/MethodLength
51
50
  def request(method, uri, data: nil, headers: {}, **kwargs)
52
51
  request = Request.new(
53
52
  headers: default_headers.merge(headers),
54
53
  **kwargs,
55
54
  method: method,
56
55
  uri: join(uri),
57
- data: data
56
+ data: data,
58
57
  )
59
58
 
60
59
  ret = cache.call(request) {|req| adapter.call(req) }
61
60
  ret.then do |response|
62
- if !response.errored?
63
- process response
64
- else
65
- raise ResponseError.from_code(response)
66
- end
61
+ raise ResponseError.from_code(response) if response.errored?
62
+
63
+ process(response)
67
64
  end
68
65
  end
69
- # rubocop:enable all
70
66
 
71
67
  def encode_with(coder)
72
68
  coder.map = marshal_dump
@@ -79,7 +75,7 @@ module Restify
79
75
  def marshal_dump
80
76
  {
81
77
  uri: uri.to_s,
82
- headers: default_headers
78
+ headers: default_headers,
83
79
  }
84
80
  end
85
81
 
data/lib/restify/error.rb CHANGED
@@ -34,6 +34,8 @@ module Restify
34
34
  Gone.new(response)
35
35
  when 422
36
36
  UnprocessableEntity.new(response)
37
+ when 429
38
+ TooManyRequests.new(response)
37
39
  when 400...500
38
40
  ClientError.new(response)
39
41
  when 500
@@ -110,15 +112,37 @@ module Restify
110
112
  # This makes it easy to rescue specific expected error types.
111
113
 
112
114
  class BadRequest < ClientError; end
115
+
113
116
  class Unauthorized < ClientError; end
117
+
114
118
  class NotFound < ClientError; end
119
+
115
120
  class NotAcceptable < ClientError; end
121
+
116
122
  class Gone < ClientError; end
123
+
117
124
  class UnprocessableEntity < ClientError; end
118
125
 
126
+ class TooManyRequests < ClientError
127
+ def retry_after
128
+ case response.headers['RETRY_AFTER']
129
+ when /^\d+$/
130
+ DateTime.now + Rational(response.headers['RETRY_AFTER'].to_i, 86_400)
131
+ when String
132
+ begin
133
+ DateTime.httpdate response.headers['RETRY_AFTER']
134
+ rescue ArgumentError
135
+ nil
136
+ end
137
+ end
138
+ end
139
+ end
140
+
119
141
  class InternalServerError < ServerError; end
120
142
 
121
143
  class BadGateway < GatewayError; end
144
+
122
145
  class ServiceUnavailable < GatewayError; end
146
+
123
147
  class GatewayTimeout < GatewayError; end
124
148
  end