restify 2.1.0 → 3.0.0.rc2

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 (213) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -0
  3. data/README.md +30 -4
  4. data/lib/restify/adapter/base.rb +10 -1
  5. data/lib/restify/adapter/ethon.rb +462 -0
  6. data/lib/restify/adapter/loop_lock.rb +124 -0
  7. data/lib/restify/adapter/telemetry.rb +6 -4
  8. data/lib/restify/adapter/typhoeus.rb +48 -23
  9. data/lib/restify/context.rb +5 -5
  10. data/lib/restify/error.rb +13 -0
  11. data/lib/restify/global.rb +9 -30
  12. data/lib/restify/logging.rb +10 -4
  13. data/lib/restify/processors/base/parsing.rb +13 -1
  14. data/lib/restify/promise.rb +20 -2
  15. data/lib/restify/registry.rb +2 -2
  16. data/lib/restify/relation.rb +8 -8
  17. data/lib/restify/request.rb +2 -2
  18. data/lib/restify/resource.rb +20 -2
  19. data/lib/restify/response.rb +39 -3
  20. data/lib/restify/timeout.rb +11 -5
  21. data/lib/restify/version.rb +3 -3
  22. data/lib/restify/webmock.rb +126 -0
  23. data/lib/restify.rb +5 -1
  24. data/spec/restify/adapter/ethon_spec.rb +263 -0
  25. data/spec/restify/adapter/loop_lock_spec.rb +144 -0
  26. data/spec/restify/adapter/telemetry_spec.rb +61 -0
  27. data/spec/restify/adapter/typhoeus_spec.rb +26 -0
  28. data/spec/restify/cache_spec.rb +1 -3
  29. data/spec/restify/context_spec.rb +55 -0
  30. data/spec/restify/features/protocols_spec.rb +28 -0
  31. data/spec/restify/features/redirects_spec.rb +45 -0
  32. data/spec/restify/features/request_errors_spec.rb +1 -1
  33. data/spec/restify/features/response_bodies_spec.rb +45 -0
  34. data/spec/restify/features/response_encoding_spec.rb +82 -0
  35. data/spec/restify/features/response_errors_spec.rb +22 -1
  36. data/spec/restify/features/webmock_spec.rb +92 -1
  37. data/spec/restify/global_spec.rb +78 -10
  38. data/spec/restify/processors/json_spec.rb +36 -0
  39. data/spec/restify/processors/msgpack_spec.rb +36 -0
  40. data/spec/restify/promise_spec.rb +82 -0
  41. data/spec/restify/relation_spec.rb +2 -6
  42. data/spec/restify/request_spec.rb +26 -0
  43. data/spec/restify/timeout_spec.rb +11 -0
  44. data/spec/spec_helper.rb +10 -6
  45. data/spec/support/stub_server.rb +4 -4
  46. metadata +26 -209
  47. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/LICENSE +0 -21
  48. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/README.md +0 -23
  49. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/CHANGELOG.md +0 -561
  50. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/MIT-LICENSE +0 -20
  51. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/README.rdoc +0 -40
  52. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/CHANGELOG.md +0 -326
  53. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/LICENSE.txt +0 -202
  54. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/README.md +0 -121
  55. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/LICENSE.MIT +0 -20
  56. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/README.YARD.md +0 -12
  57. data/vendor/bundle/ruby/4.0.0/gems/base64-0.3.0/README.md +0 -48
  58. data/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.1.2/LICENSE +0 -56
  59. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/CHANGELOG.md +0 -615
  60. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/LICENSE.txt +0 -21
  61. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/README.md +0 -409
  62. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/LICENSE +0 -20
  63. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/README.md +0 -182
  64. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/LICENSE +0 -20
  65. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/README.md +0 -43
  66. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/CHANGELOG.md +0 -518
  67. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/README.md +0 -92
  68. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/LICENSE +0 -21
  69. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/README.md +0 -409
  70. data/vendor/bundle/ruby/4.0.0/gems/drb-2.2.3/LICENSE.txt +0 -22
  71. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/CHANGELOG.md +0 -417
  72. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/LICENSE +0 -20
  73. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/README.md +0 -118
  74. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/CHANGELOG.md +0 -507
  75. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE +0 -24
  76. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE.SPECS +0 -22
  77. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/README.md +0 -138
  78. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/LICENSE +0 -19
  79. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/README.md +0 -323
  80. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/changelog.md +0 -127
  81. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/LICENSE.txt +0 -16
  82. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/README.md +0 -187
  83. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/MIT-LICENSE +0 -20
  84. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/README.md +0 -139
  85. data/vendor/bundle/ruby/4.0.0/gems/json-2.19.9/README.md +0 -310
  86. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/LICENSE.txt +0 -21
  87. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/README.md +0 -88
  88. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/CHANGELOG.md +0 -15
  89. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/LICENSE.txt +0 -21
  90. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/README.md +0 -173
  91. data/vendor/bundle/ruby/4.0.0/gems/little-plugger-1.1.4/README.rdoc +0 -53
  92. data/vendor/bundle/ruby/4.0.0/gems/logger-1.7.0/README.md +0 -104
  93. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/LICENSE +0 -22
  94. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/README.md +0 -140
  95. data/vendor/bundle/ruby/4.0.0/gems/minitest-6.0.6/README.rdoc +0 -763
  96. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/ChangeLog +0 -383
  97. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/LICENSE +0 -177
  98. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/README.md +0 -302
  99. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/LICENSE.md +0 -20
  100. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/README.md +0 -281
  101. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/LICENSE +0 -37
  102. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/README +0 -59
  103. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/license.md +0 -80
  104. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/readme.md +0 -91
  105. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/CHANGELOG.md +0 -219
  106. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/LICENSE +0 -201
  107. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/README.md +0 -68
  108. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/CHANGELOG.md +0 -110
  109. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/LICENSE +0 -201
  110. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/README.md +0 -62
  111. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/CHANGELOG.md +0 -97
  112. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/LICENSE +0 -201
  113. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/README.md +0 -155
  114. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/CHANGELOG.md +0 -192
  115. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/LICENSE +0 -201
  116. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/README.md +0 -66
  117. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/CHANGELOG.md +0 -31
  118. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/LICENSE +0 -201
  119. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/README.md +0 -40
  120. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/CHANGELOG.md +0 -302
  121. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/LICENSE +0 -201
  122. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/README.md +0 -101
  123. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/CHANGELOG.md +0 -65
  124. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/LICENSE +0 -201
  125. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/README.md +0 -115
  126. data/vendor/bundle/ruby/4.0.0/gems/parallel-2.1.0/MIT-LICENSE.txt +0 -20
  127. data/vendor/bundle/ruby/4.0.0/gems/parser-3.3.11.1/LICENSE.txt +0 -26
  128. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/CHANGELOG.md +0 -786
  129. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/LICENSE.md +0 -7
  130. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/README.md +0 -143
  131. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/CHANGELOG.md +0 -649
  132. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/LICENSE.txt +0 -22
  133. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/README.md +0 -231
  134. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/LICENSE +0 -29
  135. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/README.md +0 -484
  136. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/README.md +0 -9
  137. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/rc.d/README.md +0 -74
  138. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ChangeLog +0 -846
  139. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.ja.rdoc +0 -58
  140. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.rdoc +0 -60
  141. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/CHANGELOG.md +0 -1346
  142. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/MIT-LICENSE +0 -20
  143. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/README.md +0 -384
  144. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/Changelog.md +0 -101
  145. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/LICENSE +0 -20
  146. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/README.markdown +0 -227
  147. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/MIT-LICENSE +0 -21
  148. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/README.rdoc +0 -155
  149. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/LICENSE +0 -21
  150. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/README.md +0 -107
  151. data/vendor/bundle/ruby/4.0.0/gems/regexp_parser-2.12.0/LICENSE +0 -22
  152. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/LICENSE.txt +0 -22
  153. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/README.md +0 -57
  154. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/LICENSE.md +0 -27
  155. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/README.md +0 -47
  156. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/Changelog.md +0 -2447
  157. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/LICENSE.md +0 -26
  158. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/README.md +0 -389
  159. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/Changelog.md +0 -1366
  160. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/LICENSE.md +0 -25
  161. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/README.md +0 -326
  162. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/Changelog.md +0 -1351
  163. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/LICENSE.md +0 -25
  164. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/README.md +0 -465
  165. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/Changelog.md +0 -444
  166. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/LICENSE.md +0 -23
  167. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/README.md +0 -40
  168. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/LICENSE.txt +0 -20
  169. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/README.md +0 -255
  170. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/LICENSE.txt +0 -20
  171. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/README.md +0 -54
  172. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/CHANGELOG.md +0 -116
  173. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/MIT-LICENSE.md +0 -21
  174. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/README.md +0 -91
  175. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/CHANGELOG.md +0 -135
  176. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/MIT-LICENSE.md +0 -21
  177. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/README.md +0 -91
  178. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/LICENSE.txt +0 -20
  179. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/README.md +0 -100
  180. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/CHANGELOG.md +0 -1136
  181. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/MIT-LICENSE.md +0 -21
  182. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/README.md +0 -112
  183. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/CHANGELOG.md +0 -98
  184. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/MIT-LICENSE.md +0 -21
  185. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/README.md +0 -93
  186. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/LICENSE.txt +0 -19
  187. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/README.md +0 -131
  188. data/vendor/bundle/ruby/4.0.0/gems/securerandom-0.4.1/README.md +0 -72
  189. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/CHANGELOG.md +0 -191
  190. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/LICENSE +0 -20
  191. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/README.md +0 -974
  192. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/LICENSE +0 -202
  193. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/README.md +0 -65
  194. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/CHANGELOG.md +0 -114
  195. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/LICENSE +0 -20
  196. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/README.md +0 -30
  197. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/CHANGELOG.md +0 -13
  198. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/README.md +0 -29
  199. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/CHANGELOG.md +0 -465
  200. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/LICENSE +0 -22
  201. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/README.md +0 -588
  202. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/LICENSE +0 -19
  203. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/README.md +0 -406
  204. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/CHANGELOG.md +0 -299
  205. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/MIT-LICENSE.txt +0 -22
  206. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/README.md +0 -194
  207. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/CHANGELOG.md +0 -202
  208. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/MIT-LICENSE.txt +0 -20
  209. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/README.md +0 -205
  210. data/vendor/bundle/ruby/4.0.0/gems/uri-1.1.1/README.md +0 -55
  211. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/CHANGELOG.md +0 -2148
  212. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/LICENSE +0 -20
  213. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/README.md +0 -1229
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Restify
4
+ module Adapter
5
+ #
6
+ # Manages coordination for exclusive ownership of an event loop,
7
+ # shared between a background thread and threads waiting on results.
8
+ # This way, the event loop can run on waiting threads directly,
9
+ # avoiding costly context switches to the background thread.
10
+ #
11
+ # Waiting threads have priority: the background thread only acquires
12
+ # the loop while no thread is waiting for it, and is interrupted to
13
+ # release it as soon as one is.
14
+ #
15
+ # Waiting threads can give up waiting once their result is
16
+ # available, e.g. because another thread running the loop produced
17
+ # it. Whoever runs the loop must call `#notify` after producing a
18
+ # result.
19
+ #
20
+ class LoopLock
21
+ # @yield Called when a waiting thread needs the background thread to
22
+ # release the loop, e.g. to wake it up from `select`.
23
+ #
24
+ def initialize(&interrupt)
25
+ @interrupt = interrupt
26
+
27
+ reset!
28
+ end
29
+
30
+ # Acquire the loop for a thread waiting on a result.
31
+ #
32
+ # @param timeout [Restify::Timeout, Numeric] Maximum time to wait,
33
+ # derived from the promise timeout of the waiting thread.
34
+ #
35
+ # @yieldreturn [Boolean] Whether the result is available, and
36
+ # the loop is not needed anymore.
37
+ #
38
+ # @return [Boolean] true when the calling thread holds the loop,
39
+ # false when the result is available or the timeout expired
40
+ # first.
41
+ #
42
+ def acquire(timeout, &done)
43
+ timeout = Timeout.new(timeout)
44
+
45
+ background = @mutex.synchronize do
46
+ @waiters += 1
47
+ @background
48
+ end
49
+
50
+ @interrupt&.call if background
51
+
52
+ @mutex.synchronize do
53
+ loop do
54
+ return false if done&.call
55
+
56
+ remaining = timeout.remaining
57
+ return false unless remaining.positive?
58
+
59
+ if free?
60
+ @owner = Thread.current
61
+ return true
62
+ end
63
+
64
+ @changed.wait(@mutex, remaining)
65
+ end
66
+ ensure
67
+ @waiters -= 1
68
+
69
+ # Let the background thread continue.
70
+ @changed.broadcast if @waiters.zero?
71
+ end
72
+ end
73
+
74
+ # Acquire the loop for the background thread. Blocks while any
75
+ # other thread holds the loop or waits for it.
76
+ def acquire_background
77
+ @mutex.synchronize do
78
+ @changed.wait(@mutex) until free? && @waiters.zero?
79
+
80
+ @owner = Thread.current
81
+ @background = true
82
+ end
83
+ end
84
+
85
+ def release
86
+ @mutex.synchronize do
87
+ @owner = nil
88
+ @background = false
89
+ @changed.broadcast
90
+ end
91
+ end
92
+
93
+ def owned?
94
+ @owner.equal?(Thread.current)
95
+ end
96
+
97
+ def notify
98
+ return unless @waiters.positive?
99
+
100
+ @mutex.synchronize { @changed.broadcast }
101
+ end
102
+
103
+ # Forget all threads holding or waiting for the loop, e.g. in a
104
+ # forked child process, where these threads do not exist.
105
+ #
106
+ # Must not be called while other threads use the lock.
107
+ def reset!
108
+ @mutex = Mutex.new
109
+ @changed = ConditionVariable.new
110
+ @owner = nil
111
+ @background = false
112
+ @waiters = 0
113
+ end
114
+
115
+ private
116
+
117
+ # A thread holding the loop can be gone without releasing it, e.g.
118
+ # when killed.
119
+ def free?
120
+ @owner.nil? || !@owner.alive?
121
+ end
122
+ end
123
+ end
124
+ end
@@ -8,16 +8,18 @@ module Restify
8
8
  module Telemetry
9
9
  def call(request)
10
10
  method = request.method.to_s.upcase
11
- uri = URI.parse(request.uri)
12
- name = "#{method} #{uri.scheme}://#{uri.host}:#{uri.port}"
11
+ uri = request.uri
12
+ port = uri.inferred_port
13
+ name = "#{method} #{uri.scheme}://#{uri.host}:#{port}"
13
14
 
15
+ # Remove `nil` as OTEL spans must not contain any nil attributes
14
16
  attributes = {
15
17
  'http.request.method' => method,
16
18
  'server.address' => uri.host,
17
- 'server.port' => uri.port,
19
+ 'server.port' => port,
18
20
  'url.full' => uri.to_s,
19
21
  'url.scheme' => uri.scheme,
20
- }
22
+ }.compact
21
23
 
22
24
  span = tracer.start_span(name, attributes:, kind: :client)
23
25
  OpenTelemetry::Trace.with_span(span) do
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'typhoeus'
4
-
5
- Ethon.logger = Logging.logger[Ethon]
3
+ begin
4
+ require 'typhoeus'
5
+ rescue LoadError => e
6
+ raise LoadError.new(
7
+ "Restify::Adapter::Typhoeus requires the `typhoeus` gem, add it to your Gemfile (#{e.message})",
8
+ )
9
+ end
6
10
 
7
11
  module Restify
8
12
  module Adapter
@@ -23,6 +27,14 @@ module Restify
23
27
  tcp_keepintvl: 5,
24
28
  }.freeze
25
29
 
30
+ # Restify follows relations from URLs in server responses, and
31
+ # libcurl supports far more than HTTP, e.g. `file://` or `scp://`.
32
+ # Therefore only HTTP-like protocols must ever be used, both for
33
+ # the request itself and when following redirects.
34
+ #
35
+ # This is intentionally not configurable.
36
+ PROTOCOLS = %i[http https].freeze
37
+
26
38
  # Patch Hydra to restore the correct OpenTelemetry span when
27
39
  # adding the request, so that the Ethon instrumentation can
28
40
  # properly pick up the context where the Restify request
@@ -41,8 +53,8 @@ module Restify
41
53
  end
42
54
  end
43
55
 
44
- def initialize(sync: false, options: {}, **kwargs)
45
- @hydra = ::Typhoeus::Hydra.new(**kwargs)
56
+ def initialize(sync: false, options: {}, **)
57
+ @hydra = ::Typhoeus::Hydra.new(**)
46
58
  @hydra.extend(EasyOverride)
47
59
 
48
60
  @mutex = Mutex.new
@@ -62,8 +74,11 @@ module Restify
62
74
  req = convert(request, writer)
63
75
 
64
76
  if sync?
65
- @hydra.queue(req)
66
- @hydra.run
77
+ begin
78
+ req.run
79
+ rescue Exception => e # rubocop:disable Lint/RescueException
80
+ writer.reject(e)
81
+ end
67
82
  else
68
83
  debug 'request:add',
69
84
  tag: request.object_id,
@@ -71,7 +86,7 @@ module Restify
71
86
  url: request.uri,
72
87
  timeout: request.timeout
73
88
 
74
- @queue << convert(request, writer)
89
+ @queue << req
75
90
 
76
91
  thread.run unless thread.status
77
92
  end
@@ -88,33 +103,38 @@ module Restify
88
103
  body: request.body,
89
104
  timeout: request.timeout,
90
105
  connecttimeout: request.timeout,
106
+
107
+ # Block accessing and redirecting to non-HTTP protocols:
108
+ protocols: PROTOCOLS,
109
+ redir_protocols: PROTOCOLS,
91
110
  ).tap do |req|
92
111
  req._otel_span = OpenTelemetry::Trace.current_span
93
112
  req._restify_writer = writer
94
113
 
95
114
  req.on_complete do |response|
96
- debug 'request:complete',
97
- tag: request.object_id,
98
- status: response.code,
99
- message: response.return_message,
100
- timeout: response.timed_out?
101
-
102
- if response.timed_out? || response.code.zero?
103
- writer.reject \
104
- Restify::NetworkError.new(request, response.return_message)
105
- else
106
- writer.fulfill convert_back(response, request)
115
+ writer.set do
116
+ debug 'request:complete',
117
+ tag: request.object_id,
118
+ status: response.code,
119
+ message: response.return_message,
120
+ timeout: response.timed_out?
121
+
122
+ if response.timed_out? || response.code.zero?
123
+ raise Restify::NetworkError.new(request, response.return_message)
124
+ end
125
+
126
+ convert_back(response, request)
107
127
  end
108
128
 
109
- # Add all newly queued requests to active hydra, e.g. requests
110
- # queued in a completion callback.
129
+ # Add all newly queued requests to active hydra, e.g.
130
+ # requests queued in a completion callback.
111
131
  dequeue_all
112
132
  end
113
133
  end
114
134
  end
115
135
 
116
136
  def convert_back(response, request)
117
- uri = request.uri
137
+ uri = effective_uri(response, request)
118
138
  status = response.code
119
139
  body = response.body
120
140
  headers = convert_headers(response.headers)
@@ -122,6 +142,11 @@ module Restify
122
142
  ::Restify::Response.new(request, uri, status, headers, body)
123
143
  end
124
144
 
145
+ def effective_uri(response, request)
146
+ url = response.effective_url
147
+ url ? Addressable::URI.parse(url) : request.uri
148
+ end
149
+
125
150
  def convert_headers(headers)
126
151
  return {} unless headers.respond_to?(:each_pair)
127
152
 
@@ -165,7 +190,7 @@ module Restify
165
190
  runs += 1
166
191
  debug 'hydra:completed'
167
192
  rescue StandardError => e
168
- logger.error(e)
193
+ error(e)
169
194
  end
170
195
  ensure
171
196
  debug 'hydra:exit'
@@ -34,9 +34,9 @@ module Restify
34
34
  self.uri.join uri
35
35
  end
36
36
 
37
- def inherit(uri, **kwargs)
37
+ def inherit(uri, **)
38
38
  uri ||= self.uri
39
- Context.new(uri, **kwargs, **options)
39
+ Context.new(uri, **, **options)
40
40
  end
41
41
 
42
42
  def process(response)
@@ -47,16 +47,16 @@ module Restify
47
47
  processor.new(context, response).resource
48
48
  end
49
49
 
50
- def request(method, uri, data: nil, headers: {}, **kwargs)
50
+ def request(method, uri, data: nil, headers: {}, **)
51
51
  request = Request.new(
52
52
  headers: default_headers.merge(headers),
53
- **kwargs,
53
+ **,
54
54
  method:,
55
55
  uri: join(uri),
56
56
  data:,
57
57
  )
58
58
 
59
- ret = cache.call(request) {|req| adapter.call(req) }
59
+ ret = cache ? cache.call(request) {|req| adapter.call(req) } : adapter.call(request)
60
60
  ret.then do |response|
61
61
  raise ResponseError.from_code(response) if response.errored?
62
62
 
data/lib/restify/error.rb CHANGED
@@ -13,6 +13,19 @@ module Restify
13
13
  end
14
14
  end
15
15
 
16
+ # A {ParseError} is raised when accessing the data of a resource,
17
+ # whose response body could not be parsed, e.g. invalid JSON. The
18
+ # original error is available as {#cause}.
19
+ #
20
+ class ParseError < StandardError
21
+ attr_reader :response
22
+
23
+ def initialize(response, message)
24
+ @response = response
25
+ super("Could not parse response body for `#{response&.uri}': #{message}")
26
+ end
27
+ end
28
+
16
29
  # A {ResponseError} is returned on a non-successful
17
30
  # response from server. Usually it will either be a
18
31
  # {ClientError} or a {ServerError}.
@@ -2,47 +2,26 @@
2
2
 
3
3
  module Restify
4
4
  module Global
5
- def new(uri, **opts)
6
- context = resolve_context uri, **opts
5
+ def new(uri, **)
6
+ context = resolve_context(uri, **)
7
7
 
8
8
  Relation.new context, context.uri
9
9
  end
10
10
 
11
- def adapter
12
- @adapter ||= begin
13
- Restify::Adapter::Typhoeus.new
14
- end
15
- end
16
-
17
- def adapter=(adapter)
18
- @adapter = adapter
19
- end
20
-
21
- def cache
22
- @cache ||= begin
23
- require 'active_support'
24
- require 'active_support/cache'
25
- Restify::Cache.new store: ActiveSupport::Cache::MemoryStore.new
26
- end
27
- end
28
-
29
- def cache=(cache)
30
- @cache = cache
31
- end
11
+ attr_writer :adapter
12
+ attr_accessor :cache, :logger
32
13
 
33
- def logger
34
- ::Logging.logger[Restify]
14
+ def adapter
15
+ @adapter ||= Restify::Adapter::Ethon.new
35
16
  end
36
17
 
37
- def logger=(logger); end
38
-
39
18
  private
40
19
 
41
- def resolve_context(uri, **opts)
20
+ def resolve_context(uri, **)
42
21
  if uri.is_a? Symbol
43
- Restify::Registry.fetch(uri).inherit(nil, **opts)
22
+ Restify::Registry.fetch(uri).inherit(nil, **)
44
23
  else
45
- Context.new(uri, **opts)
24
+ Context.new(uri, **)
46
25
  end
47
26
  end
48
27
  end
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logging'
4
-
5
3
  module Restify
6
4
  module Logging
7
5
  def logger
8
- @logger ||= ::Logging.logger[self]
6
+ Restify.logger
9
7
  end
10
8
 
11
9
  def debug(message = nil, tag: nil, **kwargs)
12
- logger.debug do
10
+ logger&.debug(_log_name) do
13
11
  [
14
12
  _log_prefix,
15
13
  *Array(tag),
@@ -19,6 +17,14 @@ module Restify
19
17
  end
20
18
  end
21
19
 
20
+ def error(exception)
21
+ logger&.error(_log_name) { exception }
22
+ end
23
+
24
+ def _log_name
25
+ is_a?(Module) ? name : self.class.name
26
+ end
27
+
22
28
  def _log_prefix
23
29
  nil
24
30
  end
@@ -10,7 +10,19 @@ module Restify
10
10
  #
11
11
  module Parsing
12
12
  def load
13
- parse deserialized_body, root: true
13
+ # No data, e.g. when a server sets a content type but sends an
14
+ # empty body.
15
+ return if body.nil? || body.empty?
16
+
17
+ begin
18
+ data = deserialized_body
19
+ rescue StandardError => e
20
+ # Keep the resource, e.g. with relations from headers, but
21
+ # raise on accessing its data.
22
+ return Resource.new(context, response:, error: e)
23
+ end
24
+
25
+ parse(data, root: true)
14
26
  end
15
27
 
16
28
  def parse(object, root: false)
@@ -2,9 +2,16 @@
2
2
 
3
3
  module Restify
4
4
  class Promise < Concurrent::IVar
5
+ # A driver that can complete this promise in the waiting thread,
6
+ # e.g. an adapter running its event loop. See `#wait`.
7
+ #
8
+ # @api private
9
+ attr_writer :driver
10
+
5
11
  def initialize(*dependencies, &task)
6
12
  @task = task
7
13
  @dependencies = dependencies.flatten
14
+ @driver = nil
8
15
 
9
16
  super(&nil)
10
17
 
@@ -19,7 +26,11 @@ module Restify
19
26
 
20
27
  execute(t) if pending?
21
28
 
22
- super
29
+ # Let the driver run on the current thread instead of sleeping and
30
+ # switching to another thread if possible. If unsupported, the
31
+ # driver returns false.
32
+ super unless incomplete? && @driver&.drive(self, t)
33
+
23
34
  raise t if incomplete?
24
35
 
25
36
  self
@@ -62,8 +73,9 @@ module Restify
62
73
  end
63
74
 
64
75
  class << self
65
- def create
76
+ def create(driver: nil)
66
77
  promise = Promise.new
78
+ promise.driver = driver
67
79
  yield Writer.new(promise)
68
80
  promise
69
81
  end
@@ -93,6 +105,12 @@ module Restify
93
105
  def reject(reason)
94
106
  @promise.send :complete, false, nil, reason
95
107
  end
108
+
109
+ def set
110
+ fulfill(yield)
111
+ rescue Exception => e # rubocop:disable Lint/RescueException
112
+ reject(e)
113
+ end
96
114
  end
97
115
  end
98
116
  end
@@ -6,8 +6,8 @@ module Restify
6
6
  @registry = {}
7
7
  end
8
8
 
9
- def store(name, uri, **opts)
10
- @registry[name] = Context.new(uri, **opts)
9
+ def store(name, uri, **)
10
+ @registry[name] = Context.new(uri, **)
11
11
  end
12
12
 
13
13
  def fetch(name)
@@ -19,20 +19,20 @@ module Restify
19
19
  @template = Addressable::Template.new(template)
20
20
  end
21
21
 
22
- def request(method:, params: {}, **opts)
23
- context.request(method, expand(params), **opts)
22
+ def request(method:, params: {}, **)
23
+ context.request(method, expand(params), **)
24
24
  end
25
25
 
26
- def get(data = {}, params: {}, **opts)
27
- request(**opts, method: :get, params: data.merge(params))
26
+ def get(data = {}, params: {}, **)
27
+ request(**, method: :get, params: data.merge(params))
28
28
  end
29
29
 
30
- def head(data = {}, params: {}, **opts)
31
- request(**opts, method: :head, params: data.merge(params))
30
+ def head(data = {}, params: {}, **)
31
+ request(**, method: :head, params: data.merge(params))
32
32
  end
33
33
 
34
- def delete(data = {}, params: {}, **opts)
35
- request(**opts, method: :delete, params: data.merge(params))
34
+ def delete(data = {}, params: {}, **)
35
+ request(**, method: :delete, params: data.merge(params))
36
36
  end
37
37
 
38
38
  def post(data = nil, **opts)
@@ -11,7 +11,7 @@ module Restify
11
11
 
12
12
  # Request URI.
13
13
  #
14
- # @return [String] Request URI.
14
+ # @return [Addressable::URI] Request URI.
15
15
  #
16
16
  attr_reader :uri
17
17
 
@@ -30,7 +30,7 @@ module Restify
30
30
  attr_reader :timeout
31
31
 
32
32
  def initialize(uri:, method: :get, data: nil, timeout: 300, headers: {})
33
- @uri = uri
33
+ @uri = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri)
34
34
  @method = method.to_s.downcase
35
35
  @data = data
36
36
  @timeout = timeout
@@ -6,12 +6,13 @@ module Restify
6
6
  class Resource < SimpleDelegator
7
7
  # @api private
8
8
  #
9
- def initialize(context, response: nil, data: nil, relations: {})
9
+ def initialize(context, response: nil, data: nil, relations: {}, error: nil)
10
10
  super(data)
11
11
 
12
12
  @context = context
13
13
  @response = response
14
14
  @relations = relations
15
+ @error = error
15
16
  end
16
17
 
17
18
  # Check if resource has a relation with given name.
@@ -42,14 +43,31 @@ module Restify
42
43
 
43
44
  alias rel relation
44
45
 
46
+ # @api private
47
+ def __getobj__
48
+ # `Delegator` removes `Kernel#raise`, and the cause can only be
49
+ # set when raising.
50
+ ::Kernel.raise ParseError.new(@response, @error.message), cause: @error if @error
51
+
52
+ super
53
+ end
54
+
45
55
  # @!method data
46
56
  #
47
57
  # Return response data. Usually a hash or array.
48
58
  #
49
59
  # @return [Object] Response data.
60
+ # @raise [ParseError] If the response body could not be parsed.
50
61
  #
51
62
  alias data __getobj__
52
63
 
64
+ # @api private
65
+ def respond_to_missing?(name, include_private = false)
66
+ return false if @error
67
+
68
+ super
69
+ end
70
+
53
71
  # @!method response
54
72
  #
55
73
  # Return response if available.
@@ -95,7 +113,7 @@ module Restify
95
113
  # @api private
96
114
  def inspect
97
115
  text = {
98
- '@data' => data,
116
+ (@error ? '@error' : '@data') => @error || data,
99
117
  '@relations' => @relations,
100
118
  }.map {|k, v| "#{k}=#{v.inspect}" }.join(' ')
101
119