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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6db18b2b418bd450289f6e0c64f429e0c3f3066e6a45c082af2f34fa87b3219f
4
- data.tar.gz: ca2c0abe620c7a679e0c0ec0b12e57448ea7d709b5e0ee18d523183e97085715
3
+ metadata.gz: 99d1fd08757bc62db3bd58386310893f58224c6f0424980c412448c821021b20
4
+ data.tar.gz: 97ea53432dcee6d6089332eb1783dfd04108f62f88ce3070f1c06b03d7cb1360
5
5
  SHA512:
6
- metadata.gz: 85f715444240b80d9a269662779b69791a9240b6d37d69586948aaed3c138c8b8bc11d78e0eadd42d7da600f65e628b589053f33991b66a0a9b2c923248b4999
7
- data.tar.gz: 9a1509904bcc695749e2cfb2b95976a38a94013db01aabb7374c270de7af788ac1f8e633a846aafe56e14b3cbb244d3952a99c769d845ea3c68c57f8d4f66c8e
6
+ metadata.gz: 705892e87eeedcbc9bda88352e2444b9217c01fce181f59fd1e228870c5b812cfc28208bf4d93db436ff65fb94ccc87782320ecd4bb0f17c260e6fc8e90446c5
7
+ data.tar.gz: d9a471e407b564ce115c1e197f0b0e74d74a5553e174529837b36d81d1e936d84121be97406d0ef075138d04a010d32e541eab9f1bf8d7fc1862b975acf7eed4
data/CHANGELOG.md CHANGED
@@ -17,6 +17,54 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
17
17
 
18
18
  ### Breaks
19
19
 
20
+ ## 3.0.0.rc2 - (2026-09-25)
21
+
22
+ ---
23
+
24
+ First release candidate for 3.0. `3.0.0.rc1` was tagged, but not published due to a broken release pipeline.
25
+
26
+ ### New
27
+
28
+ - Add `Restify::Adapter::Ethon` adapter directly using libcurl in a background event loop
29
+ - Perf: Run event loop in waiting threads to avoid slow handover between threads
30
+ - Add `restify/webmock` to intercept requests with WebMock for all adapters
31
+
32
+ ### Changes
33
+
34
+ - Restrict requests and redirects to HTTP(S), as libcurl supports many more
35
+ protocols such as `file://` or `gopher://`
36
+ - Drop EOL Ruby versions 3.1 and 3.2
37
+ - Drop `hitimes` dependency, using `Process.clock_gettime` for timeouts
38
+ - Return resources without data for empty response bodies, even with a JSON
39
+ or MessagePack content type (#31)
40
+
41
+ ### Fixes
42
+
43
+ - Use the effective URI after redirects for responses and relations
44
+ - Do not set empty OpenTelemetry attributes for URIs without a port
45
+ - Encode response bodies using the charset from the `Content-Type` header
46
+ - Do not share connections of the Ethon adapter with forked child processes
47
+ - Do not deadlock the Typhoeus adapter with `sync: true` when used from multiple threads
48
+ - Raise the `ResponseError` for the status code, e.g. `NotFound`, instead of a
49
+ `JSON::ParserError` for error responses with an empty or invalid JSON body (#31)
50
+
51
+ ### Breaks
52
+
53
+ - Raise minimum Ruby version to 3.3
54
+ - `Restify::Request#uri` is always parsed and returned as `Addressable::URI`
55
+ - Use `Restify::Adapter::Ethon` by default. With WebMock, require
56
+ `restify/webmock`, as WebMock's Typhoeus integration does not apply anymore.
57
+ - `typhoeus` is an optional dependency now. Add it to your Gemfile to use
58
+ `Restify::Adapter::Typhoeus`.
59
+ - Invalid response bodies do not raise from `#value!` anymore. The resource is
60
+ returned, e.g. with relations from headers, and accessing its data raises a
61
+ `Restify::ParseError` with the parser error as its cause (#31).
62
+ - Remove `activesupport` dependency. `Restify.cache` defaults to `nil`, as the
63
+ previous default cache did not cache anything (yet).
64
+ - Remove `logging` dependency. Set a `Logger`-compatible logger with
65
+ `Restify.logger = Logger.new(...)`, nothing is logged by default. Restify does
66
+ not change `Ethon.logger` anymore.
67
+
20
68
  ## 2.1.0 - (2026-06-20)
21
69
 
22
70
  ---
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/restify?logo=ruby)](https://rubygems.org/gems/restify)
4
4
  [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/jgraichen/restify/test.yml?logo=github)](https://github.com/jgraichen/restify/actions)
5
- [![Code Quality](https://codebeat.co/badges/368f8033-bd76-48bc-9777-85f1d4befa94)](https://codebeat.co/projects/github-com-jgraichen-restify-main)
6
5
 
7
6
  Restify is an hypermedia REST client that does parallel, concurrent and keep-alive requests by default.
8
7
 
@@ -14,9 +13,15 @@ Restify is build upon the following libraries:
14
13
 
15
14
  * [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby)
16
15
  * [addressable](https://github.com/sporkmonger/addressable)
17
- * [typhoeus](https://github.com/typhoeus/typhoeus)
16
+ * [ethon](https://github.com/typhoeus/ethon), using libcurl
18
17
 
19
- The HTTP adapters are mostly run in a background thread and may not survive mid-application forks.
18
+ Restify uses libcurl via Ethon by default. The older [Typhoeus](https://github.com/typhoeus/typhoeus) adapter is still available. To use it, add `typhoeus` to your Gemfile and configure it:
19
+
20
+ ```ruby
21
+ Restify.adapter = Restify::Adapter::Typhoeus.new
22
+ ```
23
+
24
+ The default Ethon adapter should work with forking, e.g. in Puma or Unicorn workers. Requests still pending when forking are aborted in the child process.
20
25
 
21
26
  Restify includes processors to parse responses and to extract links between resources. The following formats are can be parsed:
22
27
 
@@ -102,6 +107,27 @@ puts "#{commit['commit']['message']}"
102
107
 
103
108
  See commented example in main spec [`spec/restify_spec.rb`](https://github.com/jgraichen/restify/blob/master/spec/restify_spec.rb#L100) or in the `examples` directory.
104
109
 
110
+ ## Testing with WebMock
111
+
112
+ Restify can intercept requests for [WebMock](https://github.com/bblimke/webmock), independent of the adapter in use. Load it after WebMock, before WebMock is enabled, e.g. in your `spec_helper.rb`:
113
+
114
+ ```ruby
115
+ require 'webmock/rspec'
116
+ require 'restify/webmock'
117
+ ```
118
+
119
+ Note: Stubbed responses are returned as they are, including redirects, like WebMock does for other libcurl-based libraries. Requests not stubbed are passed on to the adapter if WebMock allows connections.
120
+
121
+ For tests relying on actual HTTP behavior, e.g. following redirects, run a local stub server instead, so that requests are sent over the network by the adapter.
122
+
123
+ WebMock's own Typhoeus integration is not needed for Restify and can be disabled. Otherwise, requests are reported a second time, when they are passed on to the Typhoeus adapter:
124
+
125
+ ```ruby
126
+ WebMock.enable!(except: %i[typhoeus])
127
+ ```
128
+
129
+ Keep it enabled if your application uses Typhoeus directly, too.
130
+
105
131
  ## Contributing
106
132
 
107
133
  1. [Fork it](http://github.com/jgraichen/restify/fork)
@@ -113,7 +139,7 @@ See commented example in main spec [`spec/restify_spec.rb`](https://github.com/j
113
139
 
114
140
  ## License
115
141
 
116
- Copyright (C) 2014-2025 Jan Graichen
142
+ Copyright (C) 2014-2026 Jan Graichen
117
143
 
118
144
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
119
145
 
@@ -8,7 +8,7 @@ module Restify
8
8
  prepend Telemetry
9
9
 
10
10
  def call(request)
11
- Promise.create do |writer|
11
+ Promise.create(driver:) do |writer|
12
12
  call_native request, writer
13
13
  end
14
14
  end
@@ -16,6 +16,15 @@ module Restify
16
16
  def call_native(_request, _writer)
17
17
  raise NotImplementedError.new 'Subclass responsibility'
18
18
  end
19
+
20
+ private
21
+
22
+ # Adapters that can process requests in the thread waiting on the
23
+ # returned promise return an object responding to
24
+ # `#drive(promise, timeout)` here, see `Promise#wait`.
25
+ def driver
26
+ nil
27
+ end
19
28
  end
20
29
  end
21
30
  end
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ethon'
4
+ require 'nio'
5
+
6
+ module Restify
7
+ module Adapter
8
+ #
9
+ # An adapter using libcurl's multi interface via `Ethon` in
10
+ # `socket_action` mode, driven by a nio4r-based event loop.
11
+ #
12
+ # Requests are handed over to the event loop using a queue and the
13
+ # loop is woken up via `NIO::Selector#wakeup`. Both are safe to call
14
+ # from any thread.
15
+ #
16
+ # This is important because the event loop is run from either any
17
+ # waiting thread or a dedicated background thread.
18
+ #
19
+ # The background thread that is spawned on demand and restarted if
20
+ # it ever dies. A thread waiting on a request's promise takes over
21
+ # the loop and runs it itself until the promise is complete, see
22
+ # `#drive`. This avoids handing each request and response between
23
+ # threads, which is expensive (thread wakeup). Only one thread runs
24
+ # the loop at a time, therefore all libcurl and selector interaction
25
+ # is still serialized.
26
+ #
27
+ class Ethon < Base
28
+ include Logging
29
+
30
+ DEFAULT_HEADERS = {
31
+ 'Expect' => '',
32
+ 'Transfer-Encoding' => '',
33
+ }.freeze
34
+
35
+ DEFAULT_OPTIONS = {
36
+ followlocation: true,
37
+ tcp_keepalive: true,
38
+ tcp_keepidle: 5,
39
+ tcp_keepintvl: 5,
40
+ }.freeze
41
+
42
+ # Restify follows relations from URLs in server responses, and
43
+ # libcurl supports far more than HTTP, e.g. `file://` or `scp://`.
44
+ # Therefore only HTTP-like protocols must ever be used, both for
45
+ # the request itself and when following redirects.
46
+ #
47
+ # This is intentionally not configurable.
48
+ PROTOCOLS = %i[http https].freeze
49
+
50
+ INTERESTS = {
51
+ none: nil,
52
+ in: :r,
53
+ out: :w,
54
+ inout: :rw,
55
+ }.freeze
56
+
57
+ def initialize(options: {}, **multi)
58
+ @options = DEFAULT_OPTIONS.merge(options)
59
+ @multi_options = multi
60
+
61
+ # libcurl only stores the function pointers, therefore the procs
62
+ # must be referenced here too, or they would be garbage
63
+ # collected.
64
+ @socketfunction = method(:on_socket).to_proc
65
+ @timerfunction = method(:on_timer).to_proc
66
+
67
+ @mutex = Mutex.new
68
+ @thread = nil
69
+
70
+ # Wake up the background thread from `select` when a waiting
71
+ # thread wants to run the loop.
72
+ @loop = LoopLock.new { @selector.wakeup }
73
+
74
+ setup
75
+
76
+ super()
77
+ end
78
+
79
+ def call_native(request, writer)
80
+ check_fork!
81
+
82
+ easy = convert(request, writer)
83
+
84
+ debug 'request:add',
85
+ tag: request.object_id,
86
+ method: request.method.upcase,
87
+ url: request.uri,
88
+ timeout: request.timeout
89
+
90
+ @queue << easy
91
+
92
+ # Ensure the event loop is running and let it pick up the new
93
+ # request.
94
+ thread
95
+ @selector.wakeup
96
+ end
97
+
98
+ # Run the event loop in the calling thread until the promise is
99
+ # complete or the timeout expires, see `Promise#wait`.
100
+ #
101
+ # Several threads can wait at the same time, but only one runs the
102
+ # loop. The others sleep until either their promise completes or
103
+ # the loop is released. The background thread keeps processing
104
+ # requests nobody waits on, but releases the loop as soon as a
105
+ # thread starts waiting.
106
+ #
107
+ # This only processes transfers, i.e. fulfills or rejects the
108
+ # adapter's promises. Callbacks chained with `Promise#then` still
109
+ # only run in the thread waiting on them.
110
+ #
111
+ # Returns false when the loop cannot be run in the calling thread,
112
+ # i.e. when waiting from within the loop itself.
113
+ #
114
+ def drive(promise, timeout) # rubocop:disable Naming/PredicateMethod
115
+ check_fork!
116
+
117
+ return false if @loop.owned?
118
+
119
+ while @loop.acquire(timeout) { promise.complete? }
120
+ begin
121
+ step(timeout.remaining) until promise.complete? || !timeout.remaining.positive?
122
+ rescue StandardError => e
123
+ error(e)
124
+ ensure
125
+ @loop.release
126
+ end
127
+ end
128
+
129
+ true
130
+ end
131
+
132
+ private
133
+
134
+ def driver
135
+ self
136
+ end
137
+
138
+ def setup
139
+ @pid = Process.pid
140
+ @selector = NIO::Selector.new
141
+ @monitors = {}
142
+ @timer = nil
143
+ @queue = Queue.new
144
+
145
+ @multi = ::Ethon::Multi.new(execution_mode: :socket_action, **@multi_options)
146
+ @multi.socketfunction = @socketfunction
147
+ @multi.timerfunction = @timerfunction
148
+ end
149
+
150
+ def check_fork!
151
+ return if @pid == Process.pid
152
+
153
+ @mutex.synchronize { forked! if @pid != Process.pid }
154
+ end
155
+
156
+ # A forked child process inherits the parent's event loop,
157
+ # connections and sockets. They must neither be used nor cleaned
158
+ # up here, as the parent still uses them.
159
+ #
160
+ # Therefore, abandon the state without releasing it, and set up
161
+ # everything again.
162
+ def forked!
163
+ abandoned = @multi.easy_handles.dup
164
+ abandoned << @queue.pop(true) until @queue.empty?
165
+
166
+ @multi.handle.autorelease = false
167
+ abandoned.each {|easy| easy.handle.autorelease = false }
168
+
169
+ @thread = nil
170
+ @loop.reset!
171
+ setup
172
+
173
+ abandoned.each do |easy|
174
+ easy._restify_writer.reject(
175
+ Restify::NetworkError.new(easy._restify_request, 'Request started before fork'),
176
+ )
177
+ end
178
+ end
179
+
180
+ def convert(request, writer)
181
+ Easy.new.tap do |easy|
182
+ easy._otel_span = OpenTelemetry::Trace.current_span
183
+ easy._restify_request = request
184
+ easy._restify_writer = writer
185
+
186
+ easy.http_request(
187
+ request.uri.to_s,
188
+ request.method,
189
+ request_options(request),
190
+ )
191
+
192
+ easy.on_complete do |completed|
193
+ complete(completed, request, writer)
194
+
195
+ # Wake up threads waiting on the loop to check if their
196
+ # result is available now.
197
+ @loop.notify
198
+ end
199
+ end
200
+ end
201
+
202
+ def request_options(request)
203
+ # libcurl requires millisecond-based timeouts so fractional
204
+ # seconds must be converted and rounded.
205
+ timeout = request.timeout && (request.timeout * 1000).ceil
206
+
207
+ @options.merge(
208
+ headers: DEFAULT_HEADERS.merge(request.headers),
209
+ body: request.body,
210
+ timeout_ms: timeout,
211
+ connecttimeout_ms: timeout,
212
+
213
+ # Merged last, so that neither can be overridden via `options`.
214
+ protocols: PROTOCOLS,
215
+ redir_protocols: PROTOCOLS,
216
+
217
+ # Do not use signal handlers in libcurl, as it would interfere
218
+ # with the threads.
219
+ nosignal: true,
220
+ )
221
+ end
222
+
223
+ def complete(easy, request, writer)
224
+ writer.set do
225
+ code = easy.return_code
226
+ status = easy.response_code
227
+
228
+ debug 'request:complete',
229
+ tag: request.object_id,
230
+ status: status,
231
+ message: code
232
+
233
+ if code != :ok
234
+ raise Restify::NetworkError.new(
235
+ request,
236
+ ::Ethon::Curl.easy_strerror(code),
237
+ )
238
+ end
239
+
240
+ if status.nil? || status.zero?
241
+ raise Restify::NetworkError.new(
242
+ request,
243
+ 'Response without HTTP status',
244
+ )
245
+ end
246
+
247
+ convert_back(easy, request)
248
+ end
249
+ rescue StandardError => e
250
+ # This runs inside a libcurl callback, therefore no exception
251
+ # must ever escape from here. Anything reaching this point could
252
+ # not be handed to the promise anymore.
253
+ error(e)
254
+ end
255
+
256
+ def convert_back(easy, request)
257
+ ::Restify::Response.new(
258
+ request,
259
+ effective_uri(easy, request),
260
+ easy.response_code,
261
+ convert_headers(easy.response_headers),
262
+ easy.response_body,
263
+ )
264
+ end
265
+
266
+ def effective_uri(easy, request)
267
+ url = easy.effective_url
268
+ url ? Addressable::URI.parse(url) : request.uri
269
+ end
270
+
271
+ def convert_headers(raw)
272
+ headers = {}
273
+ return headers if raw.nil?
274
+
275
+ # The raw headers can contain multiple blocks, e.g. from
276
+ # informational responses or when following redirects; only use
277
+ # the latest ones:
278
+ block = raw.split(/\r?\n\r?\n/).reject {|b| b.strip.empty? }.last
279
+
280
+ # Split on newlines that are not followed by whitespace to keep
281
+ # folded header values together.
282
+ block.to_s.split(/\r?\n(?!\s)/).each do |line|
283
+ line = line.strip
284
+ next if line.empty? || line.start_with?('HTTP/')
285
+
286
+ key, value = line.split(':', 2)
287
+ next if value.nil?
288
+
289
+ key = key.strip.upcase.tr('-', '_')
290
+ value = value.strip.gsub(/\r?\n\s*/, ' ')
291
+
292
+ case (current = headers[key])
293
+ when nil then headers[key] = value
294
+ when Array then current << value
295
+ else headers[key] = [current, value]
296
+ end
297
+ end
298
+
299
+ headers
300
+ end
301
+
302
+ def thread
303
+ @mutex.synchronize do
304
+ # Spawn thread if not yet started, or recreate it if it died.
305
+ if @thread.nil? || !@thread.status
306
+ debug 'loop:spawn'
307
+ @thread = Thread.new { run }
308
+ end
309
+
310
+ @thread
311
+ end
312
+ end
313
+
314
+ def run
315
+ loop do
316
+ @loop.acquire_background
317
+
318
+ begin
319
+ step
320
+ ensure
321
+ @loop.release
322
+ end
323
+ rescue StandardError => e
324
+ error(e)
325
+ end
326
+ ensure
327
+ debug 'loop:exit'
328
+ end
329
+
330
+ # Run one iteration of the event loop. Must only be called by the
331
+ # thread owning the loop.
332
+ def step(limit = nil)
333
+ dequeue_all
334
+
335
+ # libcurl needs to be notified about its own timeouts, e.g. to
336
+ # start newly added transfers or to time out stalled ones.
337
+ timeout!
338
+
339
+ timeout = select_timeout
340
+ timeout = limit if limit && (timeout.nil? || timeout > limit)
341
+ debug 'loop:select', timeout: timeout
342
+
343
+ # nil on timeout; empty array when woken up
344
+ @selector.select(timeout)&.each do |monitor|
345
+ # libcurl can remove sockets while earlier events of the same
346
+ # batch are processed, e.g. when a completed transfer tears
347
+ # down other connections. Skip monitors that are gone by now.
348
+ next unless @monitors[monitor.value].equal?(monitor)
349
+
350
+ socket_action(monitor.value, readiness(monitor))
351
+ end
352
+ end
353
+
354
+ def dequeue_all
355
+ loop do
356
+ easy = begin
357
+ @queue.pop(true)
358
+ rescue ThreadError
359
+ break
360
+ end
361
+
362
+ add(easy)
363
+ end
364
+ end
365
+
366
+ def add(easy)
367
+ # Restore the OpenTelemetry span the request originated from, so
368
+ # that the Ethon instrumentation picks up the correct parent
369
+ # when the easy handle is added to libcurl.
370
+ #
371
+ # Handle exceptions from Ethon or WebMock too, and reject the
372
+ # promise, so that the errors can be handled in user code.
373
+ OpenTelemetry::Trace.with_span(easy._otel_span) do
374
+ @multi.add(easy)
375
+ rescue Exception => e # rubocop:disable Lint/RescueException
376
+ easy._restify_writer.reject(e)
377
+ end
378
+ end
379
+
380
+ # Seconds until libcurl wants to be called again, or nil to block
381
+ # until a socket becomes ready or the loop is woken up.
382
+ def select_timeout
383
+ return nil unless @timer
384
+
385
+ [@timer - now, 0].max
386
+ end
387
+
388
+ def timeout!
389
+ return unless @timer
390
+ return if now < @timer
391
+
392
+ # Reset before invoking libcurl, as it will set a new timeout
393
+ # from within the socket action.
394
+ @timer = nil
395
+
396
+ socket_action
397
+ end
398
+
399
+ def readiness(monitor)
400
+ readiness = []
401
+ readiness << :in if monitor.readable?
402
+ readiness << :out if monitor.writable?
403
+ readiness
404
+ end
405
+
406
+ def socket_action(socket = nil, readiness = 0)
407
+ @multi.socket_action(socket, readiness)
408
+ end
409
+
410
+ # libcurl: called when asking for readiness monitoring
411
+ def on_socket(_easy, socket, what, _userp, _socketp)
412
+ debug 'socket:action', tag: socket, what: what
413
+
414
+ if what == :remove
415
+ @monitors.delete(socket)&.close
416
+ else
417
+ monitor = (@monitors[socket] ||= register(socket))
418
+ monitor.interests = INTERESTS.fetch(what)
419
+ end
420
+
421
+ :ok
422
+ rescue StandardError => e
423
+ error(e)
424
+ :ok
425
+ end
426
+
427
+ # libcurl: called on timeout changes
428
+ def on_timer(_handle, timeout_ms, _userp)
429
+ debug 'timer:set', timeout: timeout_ms
430
+ @timer = timeout_ms.negative? ? nil : now + (timeout_ms / 1000.0)
431
+
432
+ :ok
433
+ rescue StandardError => e
434
+ error(e)
435
+ :ok
436
+ end
437
+
438
+ def register(socket)
439
+ # FD owned by libcurl: IO/ruby must never close it
440
+ io = IO.for_fd(socket, autoclose: false)
441
+
442
+ @selector.register(io, :r).tap do |monitor|
443
+ monitor.value = socket
444
+ end
445
+ end
446
+
447
+ def now
448
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
449
+ end
450
+
451
+ def _log_prefix
452
+ "[#{object_id}/#{Thread.current.object_id}]"
453
+ end
454
+
455
+ # Keep track of the OTEL span, the request and the promise writer to
456
+ # reject on errors in the background thread or after fork
457
+ class Easy < ::Ethon::Easy
458
+ attr_accessor :_otel_span, :_restify_request, :_restify_writer
459
+ end
460
+ end
461
+ end
462
+ end