restify 2.1.0 → 3.0.0.rc3

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 (222) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/README.md +30 -5
  4. data/lib/restify/adapter/base.rb +10 -1
  5. data/lib/restify/adapter/ethon/easy.rb +147 -0
  6. data/lib/restify/adapter/ethon/event_loop.rb +255 -0
  7. data/lib/restify/adapter/ethon/options.rb +81 -0
  8. data/lib/restify/adapter/ethon/pool.rb +43 -0
  9. data/lib/restify/adapter/ethon.rb +211 -0
  10. data/lib/restify/adapter/loop_lock.rb +124 -0
  11. data/lib/restify/adapter/telemetry.rb +7 -5
  12. data/lib/restify/adapter/typhoeus.rb +52 -23
  13. data/lib/restify/context.rb +16 -6
  14. data/lib/restify/error.rb +13 -0
  15. data/lib/restify/global.rb +9 -30
  16. data/lib/restify/logging.rb +10 -4
  17. data/lib/restify/processors/base/parsing.rb +43 -26
  18. data/lib/restify/promise.rb +194 -42
  19. data/lib/restify/registry.rb +2 -2
  20. data/lib/restify/relation.rb +27 -13
  21. data/lib/restify/request.rb +2 -2
  22. data/lib/restify/resource.rb +20 -2
  23. data/lib/restify/response.rb +39 -3
  24. data/lib/restify/timeout.rb +23 -34
  25. data/lib/restify/version.rb +3 -3
  26. data/lib/restify/webmock.rb +126 -0
  27. data/lib/restify.rb +5 -2
  28. data/spec/restify/adapter/ethon/easy_spec.rb +111 -0
  29. data/spec/restify/adapter/ethon/event_loop_spec.rb +96 -0
  30. data/spec/restify/adapter/ethon/options_spec.rb +104 -0
  31. data/spec/restify/adapter/ethon/pool_spec.rb +81 -0
  32. data/spec/restify/adapter/ethon_spec.rb +307 -0
  33. data/spec/restify/adapter/loop_lock_spec.rb +144 -0
  34. data/spec/restify/adapter/telemetry_spec.rb +61 -0
  35. data/spec/restify/adapter/typhoeus_spec.rb +26 -0
  36. data/spec/restify/cache_spec.rb +1 -3
  37. data/spec/restify/context_spec.rb +69 -0
  38. data/spec/restify/features/opentelemetry_spec.rb +2 -0
  39. data/spec/restify/features/protocols_spec.rb +28 -0
  40. data/spec/restify/features/redirects_spec.rb +45 -0
  41. data/spec/restify/features/request_errors_spec.rb +1 -1
  42. data/spec/restify/features/response_bodies_spec.rb +45 -0
  43. data/spec/restify/features/response_encoding_spec.rb +82 -0
  44. data/spec/restify/features/response_errors_spec.rb +22 -1
  45. data/spec/restify/features/webmock_spec.rb +92 -1
  46. data/spec/restify/global_spec.rb +78 -10
  47. data/spec/restify/processors/json_spec.rb +103 -0
  48. data/spec/restify/processors/msgpack_spec.rb +36 -0
  49. data/spec/restify/promise_spec.rb +288 -9
  50. data/spec/restify/relation_spec.rb +25 -6
  51. data/spec/restify/request_spec.rb +26 -0
  52. data/spec/restify/timeout_spec.rb +43 -33
  53. data/spec/spec_helper.rb +10 -6
  54. data/spec/support/stub_server.rb +4 -4
  55. metadata +32 -221
  56. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/LICENSE +0 -21
  57. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/README.md +0 -23
  58. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/CHANGELOG.md +0 -561
  59. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/MIT-LICENSE +0 -20
  60. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/README.rdoc +0 -40
  61. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/CHANGELOG.md +0 -326
  62. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/LICENSE.txt +0 -202
  63. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/README.md +0 -121
  64. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/LICENSE.MIT +0 -20
  65. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/README.YARD.md +0 -12
  66. data/vendor/bundle/ruby/4.0.0/gems/base64-0.3.0/README.md +0 -48
  67. data/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.1.2/LICENSE +0 -56
  68. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/CHANGELOG.md +0 -615
  69. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/LICENSE.txt +0 -21
  70. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/README.md +0 -409
  71. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/LICENSE +0 -20
  72. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/README.md +0 -182
  73. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/LICENSE +0 -20
  74. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/README.md +0 -43
  75. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/CHANGELOG.md +0 -518
  76. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/README.md +0 -92
  77. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/LICENSE +0 -21
  78. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/README.md +0 -409
  79. data/vendor/bundle/ruby/4.0.0/gems/drb-2.2.3/LICENSE.txt +0 -22
  80. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/CHANGELOG.md +0 -417
  81. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/LICENSE +0 -20
  82. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/README.md +0 -118
  83. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/CHANGELOG.md +0 -507
  84. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE +0 -24
  85. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE.SPECS +0 -22
  86. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/README.md +0 -138
  87. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/LICENSE +0 -19
  88. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/README.md +0 -323
  89. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/changelog.md +0 -127
  90. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/LICENSE.txt +0 -16
  91. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/README.md +0 -187
  92. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/MIT-LICENSE +0 -20
  93. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/README.md +0 -139
  94. data/vendor/bundle/ruby/4.0.0/gems/json-2.19.9/README.md +0 -310
  95. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/LICENSE.txt +0 -21
  96. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/README.md +0 -88
  97. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/CHANGELOG.md +0 -15
  98. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/LICENSE.txt +0 -21
  99. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/README.md +0 -173
  100. data/vendor/bundle/ruby/4.0.0/gems/little-plugger-1.1.4/README.rdoc +0 -53
  101. data/vendor/bundle/ruby/4.0.0/gems/logger-1.7.0/README.md +0 -104
  102. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/LICENSE +0 -22
  103. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/README.md +0 -140
  104. data/vendor/bundle/ruby/4.0.0/gems/minitest-6.0.6/README.rdoc +0 -763
  105. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/ChangeLog +0 -383
  106. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/LICENSE +0 -177
  107. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/README.md +0 -302
  108. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/LICENSE.md +0 -20
  109. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/README.md +0 -281
  110. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/LICENSE +0 -37
  111. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/README +0 -59
  112. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/license.md +0 -80
  113. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/readme.md +0 -91
  114. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/CHANGELOG.md +0 -219
  115. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/LICENSE +0 -201
  116. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/README.md +0 -68
  117. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/CHANGELOG.md +0 -110
  118. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/LICENSE +0 -201
  119. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/README.md +0 -62
  120. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/CHANGELOG.md +0 -97
  121. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/LICENSE +0 -201
  122. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/README.md +0 -155
  123. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/CHANGELOG.md +0 -192
  124. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/LICENSE +0 -201
  125. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/README.md +0 -66
  126. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/CHANGELOG.md +0 -31
  127. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/LICENSE +0 -201
  128. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/README.md +0 -40
  129. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/CHANGELOG.md +0 -302
  130. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/LICENSE +0 -201
  131. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/README.md +0 -101
  132. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/CHANGELOG.md +0 -65
  133. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/LICENSE +0 -201
  134. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/README.md +0 -115
  135. data/vendor/bundle/ruby/4.0.0/gems/parallel-2.1.0/MIT-LICENSE.txt +0 -20
  136. data/vendor/bundle/ruby/4.0.0/gems/parser-3.3.11.1/LICENSE.txt +0 -26
  137. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/CHANGELOG.md +0 -786
  138. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/LICENSE.md +0 -7
  139. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/README.md +0 -143
  140. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/CHANGELOG.md +0 -649
  141. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/LICENSE.txt +0 -22
  142. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/README.md +0 -231
  143. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/LICENSE +0 -29
  144. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/README.md +0 -484
  145. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/README.md +0 -9
  146. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/rc.d/README.md +0 -74
  147. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ChangeLog +0 -846
  148. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.ja.rdoc +0 -58
  149. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.rdoc +0 -60
  150. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/CHANGELOG.md +0 -1346
  151. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/MIT-LICENSE +0 -20
  152. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/README.md +0 -384
  153. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/Changelog.md +0 -101
  154. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/LICENSE +0 -20
  155. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/README.markdown +0 -227
  156. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/MIT-LICENSE +0 -21
  157. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/README.rdoc +0 -155
  158. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/LICENSE +0 -21
  159. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/README.md +0 -107
  160. data/vendor/bundle/ruby/4.0.0/gems/regexp_parser-2.12.0/LICENSE +0 -22
  161. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/LICENSE.txt +0 -22
  162. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/README.md +0 -57
  163. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/LICENSE.md +0 -27
  164. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/README.md +0 -47
  165. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/Changelog.md +0 -2447
  166. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/LICENSE.md +0 -26
  167. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/README.md +0 -389
  168. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/Changelog.md +0 -1366
  169. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/LICENSE.md +0 -25
  170. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/README.md +0 -326
  171. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/Changelog.md +0 -1351
  172. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/LICENSE.md +0 -25
  173. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/README.md +0 -465
  174. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/Changelog.md +0 -444
  175. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/LICENSE.md +0 -23
  176. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/README.md +0 -40
  177. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/LICENSE.txt +0 -20
  178. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/README.md +0 -255
  179. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/LICENSE.txt +0 -20
  180. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/README.md +0 -54
  181. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/CHANGELOG.md +0 -116
  182. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/MIT-LICENSE.md +0 -21
  183. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/README.md +0 -91
  184. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/CHANGELOG.md +0 -135
  185. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/MIT-LICENSE.md +0 -21
  186. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/README.md +0 -91
  187. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/LICENSE.txt +0 -20
  188. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/README.md +0 -100
  189. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/CHANGELOG.md +0 -1136
  190. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/MIT-LICENSE.md +0 -21
  191. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/README.md +0 -112
  192. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/CHANGELOG.md +0 -98
  193. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/MIT-LICENSE.md +0 -21
  194. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/README.md +0 -93
  195. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/LICENSE.txt +0 -19
  196. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/README.md +0 -131
  197. data/vendor/bundle/ruby/4.0.0/gems/securerandom-0.4.1/README.md +0 -72
  198. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/CHANGELOG.md +0 -191
  199. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/LICENSE +0 -20
  200. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/README.md +0 -974
  201. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/LICENSE +0 -202
  202. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/README.md +0 -65
  203. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/CHANGELOG.md +0 -114
  204. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/LICENSE +0 -20
  205. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/README.md +0 -30
  206. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/CHANGELOG.md +0 -13
  207. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/README.md +0 -29
  208. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/CHANGELOG.md +0 -465
  209. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/LICENSE +0 -22
  210. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/README.md +0 -588
  211. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/LICENSE +0 -19
  212. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/README.md +0 -406
  213. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/CHANGELOG.md +0 -299
  214. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/MIT-LICENSE.txt +0 -22
  215. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/README.md +0 -194
  216. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/CHANGELOG.md +0 -202
  217. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/MIT-LICENSE.txt +0 -20
  218. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/README.md +0 -205
  219. data/vendor/bundle/ruby/4.0.0/gems/uri-1.1.1/README.md +0 -55
  220. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/CHANGELOG.md +0 -2148
  221. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/LICENSE +0 -20
  222. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/README.md +0 -1229
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'hitimes'
3
+ require 'timeout'
4
4
 
5
5
  module Restify
6
6
  class Timeout
@@ -8,56 +8,44 @@ module Restify
8
8
  attr_accessor :default_timeout
9
9
  end
10
10
 
11
- # Default wait timeout of 300 seconds.
12
11
  self.default_timeout = 300
13
12
 
13
+ # @return [Float] Seconds to wait in total.
14
+ attr_reader :duration
15
+
14
16
  def initialize(timeout, target = nil)
15
- @timeout = parse_timeout(timeout)
16
17
  @target = target
17
-
18
- @interval = ::Hitimes::Interval.new
19
- @interval.start
18
+ @duration = parse_timeout(timeout)
19
+ @deadline = now + @duration
20
20
  end
21
21
 
22
- def wait_on!(ivar)
23
- ivar.value!(wait_interval).tap do
24
- raise self unless ivar.complete?
25
- end
26
- rescue ::Timeout::Error
27
- raise self
28
- end
29
-
30
- def timeout!
31
- raise self if wait_interval <= 0
22
+ def remaining
23
+ @deadline - now
32
24
  end
33
25
 
34
26
  def exception
35
- Error.new(@target)
27
+ Error.new(@target, @duration)
36
28
  end
37
29
 
38
30
  private
39
31
 
40
- def wait_interval
41
- @timeout - @interval.to_f
32
+ def now
33
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
42
34
  end
43
35
 
44
36
  def parse_timeout(value)
45
- return self.class.default_timeout if value.nil?
46
-
47
- begin
48
- value = Float(value)
49
- rescue ArgumentError
50
- raise ArgumentError.new \
51
- "Timeout must be an number but is #{value}"
52
- end
37
+ value = self.class.default_timeout if value.nil?
38
+ timeout = Float(value, exception: false)
53
39
 
54
- raise ArgumentError.new "Timeout must be > 0 but is #{value.inspect}." unless value.positive?
40
+ raise ArgumentError.new("Timeout must be a number but is #{value.inspect}") unless timeout
41
+ raise ArgumentError.new("Timeout must be > 0 but is #{value.inspect}.") unless timeout.positive?
42
+ raise ArgumentError.new("Timeout must be finite but is #{value.inspect}.") unless timeout.finite?
55
43
 
56
- value
44
+ timeout
57
45
  end
58
46
 
59
47
  class << self
60
- def new(timeout, *args)
48
+ def new(timeout, target = nil)
61
49
  return timeout if timeout.is_a?(self)
62
50
 
63
51
  super
@@ -65,15 +53,16 @@ module Restify
65
53
  end
66
54
 
67
55
  class Error < ::Timeout::Error
68
- attr_reader :target
56
+ attr_reader :target, :duration
69
57
 
70
- def initialize(target)
58
+ def initialize(target, duration)
71
59
  @target = target
60
+ @duration = duration
72
61
 
73
62
  if @target
74
- super("Operation on #{@target} timed out")
63
+ super("Operation on #{@target} timed out after #{duration}s")
75
64
  else
76
- super('Operation timed out')
65
+ super("Operation timed out after #{duration}s")
77
66
  end
78
67
  end
79
68
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Restify
4
4
  module VERSION
5
- MAJOR = 2
6
- MINOR = 1
5
+ MAJOR = 3
6
+ MINOR = 0
7
7
  PATCH = 0
8
- STAGE = nil
8
+ STAGE = :rc3
9
9
  STRING = [MAJOR, MINOR, PATCH, STAGE].compact.join('.').freeze
10
10
 
11
11
  def self.to_s
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'restify'
4
+ require 'webmock'
5
+
6
+ module WebMock
7
+ module HttpLibAdapters
8
+ #
9
+ # WebMock support for Restify (independent of the adapter)
10
+ #
11
+ # Requests are intercepted before they are handed over to an
12
+ # adapter. Stubbed responses are returned as they are, including
13
+ # redirects, like WebMock does for other libcurl-based libraries.
14
+ # Requests not stubbed are passed on to the adapter, if allowed.
15
+ #
16
+ # Must be loaded before WebMock is enabled, e.g. in
17
+ # `spec_helper.rb`:
18
+ #
19
+ # require 'webmock/rspec'
20
+ # require 'restify/webmock'
21
+ #
22
+ class RestifyAdapter < HttpLibAdapter
23
+ adapter_for :restify
24
+
25
+ class << self
26
+ def enable!
27
+ @enabled = true
28
+ end
29
+
30
+ def disable!
31
+ @enabled = false
32
+ end
33
+
34
+ def enabled?
35
+ @enabled == true
36
+ end
37
+
38
+ # Respond to a request with a stubbed response, or pass it on to
39
+ # the given block if allowed.
40
+ #
41
+ # @api private
42
+ def call(request, &)
43
+ signature = signature(request)
44
+ RequestRegistry.instance.requested_signatures.put(signature)
45
+
46
+ stub = StubRegistry.instance.response_for_request(signature)
47
+ return passthrough(signature, &) unless stub
48
+
49
+ CallbackRegistry.invoke_callbacks({lib: :restify}, signature, stub)
50
+
51
+ Restify::Promise.fulfilled(respond(request, stub))
52
+ rescue Exception => e # rubocop:disable Lint/RescueException
53
+ # e.g. `NetConnectNotAllowedError`
54
+ Restify::Promise.rejected(e)
55
+ end
56
+
57
+ private
58
+
59
+ def signature(request)
60
+ RequestSignature.new(
61
+ request.method.to_sym,
62
+ request.uri.to_s,
63
+ body: request.body,
64
+ headers: request.headers,
65
+ )
66
+ end
67
+
68
+ def passthrough(signature)
69
+ unless ::WebMock.net_connect_allowed?(signature.uri)
70
+ raise NetConnectNotAllowedError.new(signature)
71
+ end
72
+
73
+ yield.tap do |promise|
74
+ # Observers run when the request is complete, even when
75
+ # nobody waits on the promise.
76
+ promise.add_observer do |response, _|
77
+ next unless response
78
+
79
+ CallbackRegistry.invoke_callbacks(
80
+ {lib: :restify, real_request: true},
81
+ signature,
82
+ webmock_response(response),
83
+ )
84
+ end
85
+ end
86
+ end
87
+
88
+ def respond(request, stub)
89
+ raise Restify::NetworkError.new(request, 'Timeout was reached') if stub.should_timeout
90
+
91
+ stub.raise_error_if_any
92
+
93
+ headers = (stub.headers || {}).to_h do |name, value|
94
+ [name.upcase.tr('-', '_'), value]
95
+ end
96
+
97
+ Restify::Response.new(request, request.uri, stub.status[0], headers, stub.body)
98
+ end
99
+
100
+ def webmock_response(response)
101
+ Response.new.tap do |webmock|
102
+ webmock.status = [response.code, response.message.to_s]
103
+ webmock.headers = response.headers.to_h do |name, value|
104
+ [name.split('_').map(&:capitalize).join('-'), value]
105
+ end
106
+ webmock.body = response.body.to_s
107
+ end
108
+ end
109
+ end
110
+
111
+ # Intercept requests after `Adapter::Telemetry` but before
112
+ # `Adapter::Base#call` so that telemetry is included.
113
+ #
114
+ # @api private
115
+ module Intercept
116
+ def call(request)
117
+ return super unless RestifyAdapter.enabled?
118
+
119
+ RestifyAdapter.call(request) { super }
120
+ end
121
+ end
122
+
123
+ ::Restify::Adapter::Telemetry.include(Intercept)
124
+ end
125
+ end
126
+ end
data/lib/restify.rb CHANGED
@@ -4,7 +4,6 @@ require 'forwardable'
4
4
 
5
5
  require 'restify/version'
6
6
 
7
- require 'concurrent'
8
7
  require 'addressable/uri'
9
8
  require 'addressable/template'
10
9
 
@@ -28,7 +27,11 @@ module Restify
28
27
 
29
28
  module Adapter
30
29
  require 'restify/adapter/base'
31
- require 'restify/adapter/typhoeus'
30
+ require 'restify/adapter/loop_lock'
31
+ require 'restify/adapter/ethon'
32
+
33
+ # Optional, requires the `typhoeus` gem.
34
+ autoload :Typhoeus, 'restify/adapter/typhoeus'
32
35
  end
33
36
 
34
37
  module Processors
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::Ethon::Easy do
6
+ let(:easy) { described_class.new }
7
+
8
+ describe '#response' do
9
+ subject(:response) { easy.response(request) }
10
+
11
+ let(:request) { Restify::Request.new(uri: 'http://example.org/base') }
12
+
13
+ # A transfer is handed back from libcurl with its return code and, for
14
+ # HTTP, the status code of the response.
15
+ let(:info) do
16
+ {
17
+ return_code: :ok,
18
+ response_code: 200,
19
+ effective_url: 'http://example.org/base',
20
+ response_headers: "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n",
21
+ response_body: '{}',
22
+ }
23
+ end
24
+
25
+ before { allow(easy).to receive_messages(info) }
26
+
27
+ it 'returns the response' do
28
+ expect(response).to be_a Restify::Response
29
+ expect(response).to have_attributes(code: 200, body: '{}', request:)
30
+ end
31
+
32
+ it 'reuses the request URI without redirects' do
33
+ expect(response.uri).to be request.uri
34
+ end
35
+
36
+ context 'when redirected' do
37
+ let(:info) { super().merge(effective_url: 'http://example.org/other/base') }
38
+
39
+ it 'uses the effective URL as the response URI' do
40
+ expect(response.uri.to_s).to eq 'http://example.org/other/base'
41
+ end
42
+ end
43
+
44
+ context 'when the transfer failed' do
45
+ let(:info) { super().merge(return_code: :couldnt_connect, response_code: 0) }
46
+
47
+ it { expect { response }.to raise_error Restify::NetworkError, /connect/i }
48
+ end
49
+
50
+ # Non-HTTP protocols are refused before a transfer is started, but
51
+ # libcurl still reports success with a zero status code whenever no
52
+ # HTTP status was received, and no status code at all when it cannot
53
+ # be read back. Neither has a response to process.
54
+ context 'without an HTTP status' do
55
+ let(:info) { super().merge(response_code: 0) }
56
+
57
+ it { expect { response }.to raise_error Restify::NetworkError, /without HTTP status/ }
58
+ end
59
+
60
+ context 'with an unavailable HTTP status' do
61
+ let(:info) { super().merge(response_code: nil) }
62
+
63
+ it { expect { response }.to raise_error Restify::NetworkError, /without HTTP status/ }
64
+ end
65
+
66
+ describe 'headers' do
67
+ subject(:headers) { response.headers }
68
+
69
+ context 'with multiple values' do
70
+ let(:info) { super().merge(response_headers: "HTTP/1.1 200 OK\r\nLink: <a>\r\nLink: <b>\r\n\r\n") }
71
+
72
+ it { is_expected.to eq('LINK' => ['<a>', '<b>']) }
73
+ end
74
+
75
+ context 'with a folded value' do
76
+ let(:info) { super().merge(response_headers: "HTTP/1.1 200 OK\r\nX-Long: first\r\n\tsecond\r\n\r\n") }
77
+
78
+ it { is_expected.to eq('X_LONG' => 'first second') }
79
+ end
80
+
81
+ context 'with multiple responses, e.g. after a redirect' do
82
+ let(:info) do
83
+ super().merge(
84
+ response_headers: "HTTP/1.1 302 Found\r\nLocation: /b\r\n\r\n" \
85
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n",
86
+ )
87
+ end
88
+
89
+ it { is_expected.to eq('CONTENT_TYPE' => 'text/plain') }
90
+ end
91
+
92
+ context 'without headers' do
93
+ let(:info) { super().merge(response_headers: nil) }
94
+
95
+ it { is_expected.to eq({}) }
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#reset' do
101
+ it 'forgets the previous request' do
102
+ easy._otel_span = Object.new
103
+ easy._restify_request = Object.new
104
+ easy._restify_writer = Object.new
105
+
106
+ easy.reset
107
+
108
+ expect([easy._otel_span, easy._restify_request, easy._restify_writer]).to all be_nil
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::Ethon::EventLoop do
6
+ subject(:events) { described_class.new }
7
+
8
+ let(:threads) { [] }
9
+
10
+ let(:options) do
11
+ Restify::Adapter::Ethon::Options.new(Restify::Adapter::Ethon::ENFORCED_OPTIONS)
12
+ end
13
+
14
+ before do
15
+ stub_request(:get, 'http://stubserver/base').to_return(status: 200, body: '{}')
16
+ end
17
+
18
+ after { threads.each(&:kill).each(&:join) }
19
+
20
+ # A prepared easy handle with its promise, and a queue recording the
21
+ # thread completing it.
22
+ def transfer
23
+ request = Restify::Request.new(uri: 'http://localhost:9292/base')
24
+ writer = nil
25
+ promise = Restify::Promise.create {|w| writer = w }
26
+ completed_by = Queue.new
27
+
28
+ easy = Restify::Adapter::Ethon::Easy.new
29
+ easy._restify_request = request
30
+ easy._restify_writer = writer
31
+ easy.prepare(request, options)
32
+ easy.on_complete do |completed|
33
+ completed_by << Thread.current
34
+ writer.set { completed.response(request) }
35
+ events.notify
36
+ end
37
+
38
+ [easy, promise, completed_by]
39
+ end
40
+
41
+ describe '#drive' do
42
+ it 'runs transfers in the calling thread' do
43
+ easy, promise, completed_by = transfer
44
+ events.enqueue(easy)
45
+
46
+ expect(events.drive(promise, Restify::Timeout.new(1))).to be true
47
+ expect(promise.value.code).to eq 200
48
+ expect(completed_by.pop(timeout: 1)).to eq Thread.current
49
+ end
50
+
51
+ it 'gives up when the timeout expires' do
52
+ expect(events.drive(Restify::Promise.new, Restify::Timeout.new(0.1))).to be true
53
+ end
54
+
55
+ it 'does not run the loop again from within the loop' do
56
+ events.instance_variable_get(:@lock).acquire(0.1)
57
+
58
+ expect(events.drive(Restify::Promise.new, Restify::Timeout.new(0.1))).to be false
59
+ end
60
+ end
61
+
62
+ describe '#run' do
63
+ it 'completes transfers enqueued from other threads' do
64
+ background = Thread.new { events.run }
65
+ threads << background
66
+
67
+ easy, promise, completed_by = transfer
68
+ events.enqueue(easy)
69
+
70
+ expect(completed_by.pop(timeout: 1)).to eq background
71
+ expect(promise).to be_fulfilled
72
+ end
73
+ end
74
+
75
+ describe '#forked!' do
76
+ it 'rejects pending transfers' do
77
+ easy, promise, = transfer
78
+ events.enqueue(easy)
79
+
80
+ events.forked!
81
+
82
+ expect(promise).to be_rejected
83
+ expect(promise.reason).to be_a Restify::NetworkError
84
+ expect(promise.reason.message).to include 'Request started before fork'
85
+ end
86
+
87
+ it 'does not release pending handles' do
88
+ easy, = transfer
89
+ events.enqueue(easy)
90
+
91
+ events.forked!
92
+
93
+ expect(easy.handle.autorelease?).to be false
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::Ethon::Options do
6
+ subject(:options) { described_class.new(values) }
7
+
8
+ def number(name)
9
+ described_class.number(name)
10
+ end
11
+
12
+ describe '#to_a' do
13
+ subject(:compiled) { options.to_a }
14
+
15
+ context 'with bool options' do
16
+ let(:values) { {followlocation: true, verbose: false, tcp_keepalive: 0} }
17
+
18
+ it 'converts to 1 or 0' do
19
+ expect(compiled).to eq [
20
+ [number(:followlocation), :long, 1],
21
+ [number(:verbose), :long, 0],
22
+ [number(:tcp_keepalive), :long, 0],
23
+ ]
24
+ end
25
+ end
26
+
27
+ context 'with int options' do
28
+ let(:values) { {timeout_ms: 1500, maxredirs: '5'} }
29
+
30
+ it { expect(compiled).to eq [[number(:timeout_ms), :long, 1500], [number(:maxredirs), :long, 5]] }
31
+ end
32
+
33
+ context 'with enum options' do
34
+ let(:values) { {sslversion: :tlsv1, proxytype: 'https', timecondition: 1} }
35
+
36
+ it 'converts symbols, strings, and numbers' do
37
+ expect(compiled).to eq [
38
+ [number(:sslversion), :long, 1],
39
+ [number(:proxytype), :long, 2],
40
+ [number(:timecondition), :long, 1],
41
+ ]
42
+ end
43
+ end
44
+
45
+ context 'with bitmask options' do
46
+ let(:values) { {protocols: %i[http https], redir_protocols: :https} }
47
+
48
+ it { expect(compiled).to eq [[number(:protocols), :long, 3], [number(:redir_protocols), :long, 2]] }
49
+ end
50
+
51
+ context 'with string options' do
52
+ let(:values) { {useragent: :restify} }
53
+
54
+ it { expect(compiled).to eq [[number(:useragent), :string, 'restify']] }
55
+ end
56
+
57
+ context 'with nil values' do
58
+ let(:values) { {useragent: nil, timeout_ms: 5} }
59
+
60
+ it { expect(compiled).to eq [[number(:timeout_ms), :long, 5]] }
61
+ end
62
+
63
+ context 'with other option types' do
64
+ let(:values) { {postquote: []} }
65
+
66
+ it 'uses the generic handling' do
67
+ expect(compiled).to eq [[:postquote, nil, []]]
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#initialize' do
73
+ it 'raises on unknown options' do
74
+ expect { described_class.new(unknown: 1) }.to raise_error ArgumentError, /Unknown libcurl option: unknown/
75
+ end
76
+
77
+ it 'raises on unknown values' do
78
+ expect { described_class.new(sslversion: :tlsv9) }.to raise_error ArgumentError, /sslversion: :tlsv9/
79
+ end
80
+ end
81
+
82
+ describe '#apply' do
83
+ let(:values) { {followlocation: true, useragent: 'restify', postquote: []} }
84
+ let(:easy) { instance_double(Restify::Adapter::Ethon::Easy, handle: FFI::Pointer.new(1)) }
85
+
86
+ it 'sets converted options directly, and others with the generic handling' do
87
+ expect(Ethon::Curl).to receive(:easy_setopt).with(easy.handle, number(:followlocation), :long, 1)
88
+ expect(Ethon::Curl).to receive(:easy_setopt).with(easy.handle, number(:useragent), :string, 'restify')
89
+ expect(Ethon::Curl).to receive(:set_option).with(:postquote, [], easy.handle)
90
+
91
+ options.apply(easy)
92
+ end
93
+ end
94
+
95
+ describe '.number' do
96
+ it 'returns the number of the libcurl option' do
97
+ expect(number(:timeout_ms)).to eq 155
98
+ end
99
+
100
+ it 'raises on unknown options' do
101
+ expect { number(:unknown) }.to raise_error ArgumentError, /Unknown libcurl option/
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::Ethon::Pool do
6
+ subject(:pool) do
7
+ described_class.new(size: 2) do
8
+ created << Handle.new(0, Pointer.new(true))
9
+ created.last
10
+ end
11
+ end
12
+
13
+ let(:created) { [] }
14
+
15
+ before do
16
+ stub_const('Pointer', Struct.new(:autorelease))
17
+ stub_const('Handle', Struct.new(:resets, :handle) do
18
+ def reset
19
+ self.resets += 1
20
+ end
21
+ end,)
22
+ end
23
+
24
+ def release(*handles)
25
+ handles.each {|handle| pool.release(handle) }
26
+ end
27
+
28
+ describe '#checkout' do
29
+ it 'creates a handle when none is idle' do
30
+ expect(pool.checkout).to be created.first
31
+ end
32
+
33
+ it 'reuses released handles' do
34
+ handle = pool.checkout
35
+ release(handle)
36
+
37
+ expect(pool.checkout).to be handle
38
+ expect(created.size).to eq 1
39
+ end
40
+
41
+ it 'returns distinct handles to concurrent threads' do
42
+ release(*Array.new(2) { pool.checkout })
43
+
44
+ handles = Array.new(4) { Thread.new { pool.checkout } }.map(&:value)
45
+
46
+ expect(handles.map(&:object_id).uniq.size).to eq 4
47
+ end
48
+ end
49
+
50
+ describe '#release' do
51
+ it 'resets handles' do
52
+ handle = pool.checkout
53
+ release(handle)
54
+
55
+ expect(handle.resets).to eq 1
56
+ end
57
+
58
+ it 'keeps a limited number of idle handles' do
59
+ released = Array.new(3) { pool.checkout }
60
+ release(*released)
61
+
62
+ # Two of them are reused, the third one is created anew.
63
+ reused = Array.new(3) { pool.checkout }
64
+
65
+ expect(reused.count {|handle| released.any? {|r| r.equal?(handle) } }).to eq 2
66
+ expect(created.size).to eq 4
67
+ end
68
+ end
69
+
70
+ describe '#forked!' do
71
+ it 'does not release idle handles' do
72
+ idle = pool.checkout
73
+ release(idle)
74
+
75
+ pool.forked!
76
+
77
+ expect(idle.handle.autorelease).to be false
78
+ expect(pool.checkout).not_to be idle
79
+ end
80
+ end
81
+ end