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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +75 -0
- data/README.md +30 -5
- data/lib/restify/adapter/base.rb +10 -1
- data/lib/restify/adapter/ethon/easy.rb +147 -0
- data/lib/restify/adapter/ethon/event_loop.rb +255 -0
- data/lib/restify/adapter/ethon/options.rb +81 -0
- data/lib/restify/adapter/ethon/pool.rb +43 -0
- data/lib/restify/adapter/ethon.rb +211 -0
- data/lib/restify/adapter/loop_lock.rb +124 -0
- data/lib/restify/adapter/telemetry.rb +7 -5
- data/lib/restify/adapter/typhoeus.rb +52 -23
- data/lib/restify/context.rb +16 -6
- data/lib/restify/error.rb +13 -0
- data/lib/restify/global.rb +9 -30
- data/lib/restify/logging.rb +10 -4
- data/lib/restify/processors/base/parsing.rb +43 -26
- data/lib/restify/promise.rb +194 -42
- data/lib/restify/registry.rb +2 -2
- data/lib/restify/relation.rb +27 -13
- data/lib/restify/request.rb +2 -2
- data/lib/restify/resource.rb +20 -2
- data/lib/restify/response.rb +39 -3
- data/lib/restify/timeout.rb +23 -34
- data/lib/restify/version.rb +3 -3
- data/lib/restify/webmock.rb +126 -0
- data/lib/restify.rb +5 -2
- data/spec/restify/adapter/ethon/easy_spec.rb +111 -0
- data/spec/restify/adapter/ethon/event_loop_spec.rb +96 -0
- data/spec/restify/adapter/ethon/options_spec.rb +104 -0
- data/spec/restify/adapter/ethon/pool_spec.rb +81 -0
- data/spec/restify/adapter/ethon_spec.rb +307 -0
- data/spec/restify/adapter/loop_lock_spec.rb +144 -0
- data/spec/restify/adapter/telemetry_spec.rb +61 -0
- data/spec/restify/adapter/typhoeus_spec.rb +26 -0
- data/spec/restify/cache_spec.rb +1 -3
- data/spec/restify/context_spec.rb +69 -0
- data/spec/restify/features/opentelemetry_spec.rb +2 -0
- data/spec/restify/features/protocols_spec.rb +28 -0
- data/spec/restify/features/redirects_spec.rb +45 -0
- data/spec/restify/features/request_errors_spec.rb +1 -1
- data/spec/restify/features/response_bodies_spec.rb +45 -0
- data/spec/restify/features/response_encoding_spec.rb +82 -0
- data/spec/restify/features/response_errors_spec.rb +22 -1
- data/spec/restify/features/webmock_spec.rb +92 -1
- data/spec/restify/global_spec.rb +78 -10
- data/spec/restify/processors/json_spec.rb +103 -0
- data/spec/restify/processors/msgpack_spec.rb +36 -0
- data/spec/restify/promise_spec.rb +288 -9
- data/spec/restify/relation_spec.rb +25 -6
- data/spec/restify/request_spec.rb +26 -0
- data/spec/restify/timeout_spec.rb +43 -33
- data/spec/spec_helper.rb +10 -6
- data/spec/support/stub_server.rb +4 -4
- metadata +32 -221
- data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/LICENSE +0 -21
- data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/README.md +0 -23
- data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/CHANGELOG.md +0 -561
- data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/MIT-LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/README.rdoc +0 -40
- data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/CHANGELOG.md +0 -326
- data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/LICENSE.txt +0 -202
- data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/README.md +0 -121
- data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/LICENSE.MIT +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/README.YARD.md +0 -12
- data/vendor/bundle/ruby/4.0.0/gems/base64-0.3.0/README.md +0 -48
- data/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.1.2/LICENSE +0 -56
- data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/CHANGELOG.md +0 -615
- data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/LICENSE.txt +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/README.md +0 -409
- data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/README.md +0 -182
- data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/README.md +0 -43
- data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/CHANGELOG.md +0 -518
- data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/README.md +0 -92
- data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/LICENSE +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/README.md +0 -409
- data/vendor/bundle/ruby/4.0.0/gems/drb-2.2.3/LICENSE.txt +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/CHANGELOG.md +0 -417
- data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/README.md +0 -118
- data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/CHANGELOG.md +0 -507
- data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE +0 -24
- data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE.SPECS +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/README.md +0 -138
- data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/LICENSE +0 -19
- data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/README.md +0 -323
- data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/changelog.md +0 -127
- data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/LICENSE.txt +0 -16
- data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/README.md +0 -187
- data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/MIT-LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/README.md +0 -139
- data/vendor/bundle/ruby/4.0.0/gems/json-2.19.9/README.md +0 -310
- data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/LICENSE.txt +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/README.md +0 -88
- data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/CHANGELOG.md +0 -15
- data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/LICENSE.txt +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/README.md +0 -173
- data/vendor/bundle/ruby/4.0.0/gems/little-plugger-1.1.4/README.rdoc +0 -53
- data/vendor/bundle/ruby/4.0.0/gems/logger-1.7.0/README.md +0 -104
- data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/LICENSE +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/README.md +0 -140
- data/vendor/bundle/ruby/4.0.0/gems/minitest-6.0.6/README.rdoc +0 -763
- data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/ChangeLog +0 -383
- data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/LICENSE +0 -177
- data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/README.md +0 -302
- data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/LICENSE.md +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/README.md +0 -281
- data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/LICENSE +0 -37
- data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/README +0 -59
- data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/license.md +0 -80
- data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/readme.md +0 -91
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/CHANGELOG.md +0 -219
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/LICENSE +0 -201
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/README.md +0 -68
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/CHANGELOG.md +0 -110
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/LICENSE +0 -201
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/README.md +0 -62
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/CHANGELOG.md +0 -97
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/LICENSE +0 -201
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/README.md +0 -155
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/CHANGELOG.md +0 -192
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/LICENSE +0 -201
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/README.md +0 -66
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/CHANGELOG.md +0 -31
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/LICENSE +0 -201
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/README.md +0 -40
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/CHANGELOG.md +0 -302
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/LICENSE +0 -201
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/README.md +0 -101
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/CHANGELOG.md +0 -65
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/LICENSE +0 -201
- data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/README.md +0 -115
- data/vendor/bundle/ruby/4.0.0/gems/parallel-2.1.0/MIT-LICENSE.txt +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/parser-3.3.11.1/LICENSE.txt +0 -26
- data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/CHANGELOG.md +0 -786
- data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/LICENSE.md +0 -7
- data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/README.md +0 -143
- data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/CHANGELOG.md +0 -649
- data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/LICENSE.txt +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/README.md +0 -231
- data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/LICENSE +0 -29
- data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/README.md +0 -484
- data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/README.md +0 -9
- data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/rc.d/README.md +0 -74
- data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ChangeLog +0 -846
- data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.ja.rdoc +0 -58
- data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.rdoc +0 -60
- data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/CHANGELOG.md +0 -1346
- data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/MIT-LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/README.md +0 -384
- data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/Changelog.md +0 -101
- data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/README.markdown +0 -227
- data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/MIT-LICENSE +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/README.rdoc +0 -155
- data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/LICENSE +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/README.md +0 -107
- data/vendor/bundle/ruby/4.0.0/gems/regexp_parser-2.12.0/LICENSE +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/LICENSE.txt +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/README.md +0 -57
- data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/LICENSE.md +0 -27
- data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/README.md +0 -47
- data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/Changelog.md +0 -2447
- data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/LICENSE.md +0 -26
- data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/README.md +0 -389
- data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/Changelog.md +0 -1366
- data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/LICENSE.md +0 -25
- data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/README.md +0 -326
- data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/Changelog.md +0 -1351
- data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/LICENSE.md +0 -25
- data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/README.md +0 -465
- data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/Changelog.md +0 -444
- data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/LICENSE.md +0 -23
- data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/README.md +0 -40
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/LICENSE.txt +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/README.md +0 -255
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/LICENSE.txt +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/README.md +0 -54
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/CHANGELOG.md +0 -116
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/MIT-LICENSE.md +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/README.md +0 -91
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/CHANGELOG.md +0 -135
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/MIT-LICENSE.md +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/README.md +0 -91
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/LICENSE.txt +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/README.md +0 -100
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/CHANGELOG.md +0 -1136
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/MIT-LICENSE.md +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/README.md +0 -112
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/CHANGELOG.md +0 -98
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/MIT-LICENSE.md +0 -21
- data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/README.md +0 -93
- data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/LICENSE.txt +0 -19
- data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/README.md +0 -131
- data/vendor/bundle/ruby/4.0.0/gems/securerandom-0.4.1/README.md +0 -72
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/CHANGELOG.md +0 -191
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/README.md +0 -974
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/LICENSE +0 -202
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/README.md +0 -65
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/CHANGELOG.md +0 -114
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/LICENSE +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/README.md +0 -30
- data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/CHANGELOG.md +0 -13
- data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/README.md +0 -29
- data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/CHANGELOG.md +0 -465
- data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/LICENSE +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/README.md +0 -588
- data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/LICENSE +0 -19
- data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/README.md +0 -406
- data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/CHANGELOG.md +0 -299
- data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/MIT-LICENSE.txt +0 -22
- data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/README.md +0 -194
- data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/CHANGELOG.md +0 -202
- data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/MIT-LICENSE.txt +0 -20
- data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/README.md +0 -205
- data/vendor/bundle/ruby/4.0.0/gems/uri-1.1.1/README.md +0 -55
- data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/CHANGELOG.md +0 -2148
- data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/LICENSE +0 -20
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8fa974df7fe0cd06afd87ffe8bef89d87559ece784a801070aa0b8342b883eb
|
|
4
|
+
data.tar.gz: 1f3cc52e1b4bf7995f50dbe5ef9360986d71c7d157b47b1527f79cca2ff66179
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdc59e5ca15e38eb8224b638e675807f669cf0ee00802427334238107ce8a5cddc16b71cbec12e0013055fe5431b38033509381f4c1a1727e07c38cfed1be4fc
|
|
7
|
+
data.tar.gz: 7da86c8fb8dbf2829da84e9f3e0c5d47d226e360a35cc7b6b33e1da02d7507ef4570d703d31c3ef7448fb3586592218d5924b926e70d1694abfdb916de6e1c4f
|
data/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,81 @@ 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-rc3 - (2026-09-27)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
### Changes
|
|
25
|
+
|
|
26
|
+
- Perf: Avoid parsing/joining URIs when not necessary, e.g., for relations without variables or absolute URIs.
|
|
27
|
+
- Perf: Reuse libcurl handles in the Ethon adapter
|
|
28
|
+
- Perf: Optimize libcurl option preparation in the Ethon adapter
|
|
29
|
+
- Perf: Optimize parsing of data and relations
|
|
30
|
+
- Perf: Replace `Concurrent::IVar` with a lightweight promise implementation
|
|
31
|
+
- Remove `concurrent-ruby` dependency
|
|
32
|
+
|
|
33
|
+
### Fixes
|
|
34
|
+
|
|
35
|
+
- Apply `Restify::Timeout.default_timeout` when waiting on a promise without
|
|
36
|
+
an explicit timeout, e.g. with the Typhoeus adapter, instead of waiting forever
|
|
37
|
+
- A timeout while waiting on a promise does not reject the promise anymore.
|
|
38
|
+
Waiting again, e.g. with a longer timeout, continues where the last wait gave up.
|
|
39
|
+
- Honor the timeout of all threads waiting on the same promise, not only of the
|
|
40
|
+
thread running its `#then` blocks
|
|
41
|
+
- Only create relations from `*_url` fields with string values, e.g. not from numbers, objects, or arrays
|
|
42
|
+
|
|
43
|
+
### Breaks
|
|
44
|
+
|
|
45
|
+
- `Restify::Promise` does not inherit from `Concurrent::IVar` anymore
|
|
46
|
+
|
|
47
|
+
## 3.0.0.rc2 - (2026-09-25)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
First release candidate for 3.0. `3.0.0.rc1` was tagged, but not published due to a broken release pipeline.
|
|
52
|
+
|
|
53
|
+
### New
|
|
54
|
+
|
|
55
|
+
- Add `Restify::Adapter::Ethon` adapter directly using libcurl in a background event loop
|
|
56
|
+
- Perf: Run event loop in waiting threads to avoid slow handover between threads
|
|
57
|
+
- Add `restify/webmock` to intercept requests with WebMock for all adapters
|
|
58
|
+
|
|
59
|
+
### Changes
|
|
60
|
+
|
|
61
|
+
- Restrict requests and redirects to HTTP(S), as libcurl supports many more
|
|
62
|
+
protocols such as `file://` or `gopher://`
|
|
63
|
+
- Drop EOL Ruby versions 3.1 and 3.2
|
|
64
|
+
- Drop `hitimes` dependency, using `Process.clock_gettime` for timeouts
|
|
65
|
+
- Return resources without data for empty response bodies, even with a JSON
|
|
66
|
+
or MessagePack content type (#31)
|
|
67
|
+
|
|
68
|
+
### Fixes
|
|
69
|
+
|
|
70
|
+
- Use the effective URI after redirects for responses and relations
|
|
71
|
+
- Do not set empty OpenTelemetry attributes for URIs without a port
|
|
72
|
+
- Encode response bodies using the charset from the `Content-Type` header
|
|
73
|
+
- Do not share connections of the Ethon adapter with forked child processes
|
|
74
|
+
- Do not deadlock the Typhoeus adapter with `sync: true` when used from multiple threads
|
|
75
|
+
- Raise the `ResponseError` for the status code, e.g. `NotFound`, instead of a
|
|
76
|
+
`JSON::ParserError` for error responses with an empty or invalid JSON body (#31)
|
|
77
|
+
|
|
78
|
+
### Breaks
|
|
79
|
+
|
|
80
|
+
- Raise minimum Ruby version to 3.3
|
|
81
|
+
- `Restify::Request#uri` is always parsed and returned as `Addressable::URI`
|
|
82
|
+
- Use `Restify::Adapter::Ethon` by default. With WebMock, require
|
|
83
|
+
`restify/webmock`, as WebMock's Typhoeus integration does not apply anymore.
|
|
84
|
+
- `typhoeus` is an optional dependency now. Add it to your Gemfile to use
|
|
85
|
+
`Restify::Adapter::Typhoeus`.
|
|
86
|
+
- Invalid response bodies do not raise from `#value!` anymore. The resource is
|
|
87
|
+
returned, e.g. with relations from headers, and accessing its data raises a
|
|
88
|
+
`Restify::ParseError` with the parser error as its cause (#31).
|
|
89
|
+
- Remove `activesupport` dependency. `Restify.cache` defaults to `nil`, as the
|
|
90
|
+
previous default cache did not cache anything (yet).
|
|
91
|
+
- Remove `logging` dependency. Set a `Logger`-compatible logger with
|
|
92
|
+
`Restify.logger = Logger.new(...)`, nothing is logged by default. Restify does
|
|
93
|
+
not change `Ethon.logger` anymore.
|
|
94
|
+
|
|
20
95
|
## 2.1.0 - (2026-06-20)
|
|
21
96
|
|
|
22
97
|
---
|
data/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/restify)
|
|
4
4
|
[](https://github.com/jgraichen/restify/actions)
|
|
5
|
-
[](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
|
|
|
@@ -12,11 +11,16 @@ Restify can be used to consume hypermedia REST APIs (like GitHubs), to build a s
|
|
|
12
11
|
|
|
13
12
|
Restify is build upon the following libraries:
|
|
14
13
|
|
|
15
|
-
* [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby)
|
|
16
14
|
* [addressable](https://github.com/sporkmonger/addressable)
|
|
17
|
-
* [
|
|
15
|
+
* [ethon](https://github.com/typhoeus/ethon), using libcurl
|
|
18
16
|
|
|
19
|
-
The
|
|
17
|
+
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:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
Restify.adapter = Restify::Adapter::Typhoeus.new
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
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
24
|
|
|
21
25
|
Restify includes processors to parse responses and to extract links between resources. The following formats are can be parsed:
|
|
22
26
|
|
|
@@ -102,6 +106,27 @@ puts "#{commit['commit']['message']}"
|
|
|
102
106
|
|
|
103
107
|
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
108
|
|
|
109
|
+
## Testing with WebMock
|
|
110
|
+
|
|
111
|
+
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`:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
require 'webmock/rspec'
|
|
115
|
+
require 'restify/webmock'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
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:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
WebMock.enable!(except: %i[typhoeus])
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Keep it enabled if your application uses Typhoeus directly, too.
|
|
129
|
+
|
|
105
130
|
## Contributing
|
|
106
131
|
|
|
107
132
|
1. [Fork it](http://github.com/jgraichen/restify/fork)
|
|
@@ -113,7 +138,7 @@ See commented example in main spec [`spec/restify_spec.rb`](https://github.com/j
|
|
|
113
138
|
|
|
114
139
|
## License
|
|
115
140
|
|
|
116
|
-
Copyright (C) 2014-
|
|
141
|
+
Copyright (C) 2014-2026 Jan Graichen
|
|
117
142
|
|
|
118
143
|
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
144
|
|
data/lib/restify/adapter/base.rb
CHANGED
|
@@ -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,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Restify
|
|
4
|
+
module Adapter
|
|
5
|
+
class Ethon < Base
|
|
6
|
+
#
|
|
7
|
+
# A libcurl easy handle for Restify requests, translating a
|
|
8
|
+
# request into libcurl options, and a completed transfer back into
|
|
9
|
+
# a response.
|
|
10
|
+
#
|
|
11
|
+
# Handles are reused for many requests, see `Pool`.
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
#
|
|
15
|
+
class Easy < ::Ethon::Easy
|
|
16
|
+
attr_accessor :_otel_span, :_restify_request, :_restify_writer
|
|
17
|
+
|
|
18
|
+
TIMEOUT_MS = Options.number(:timeout_ms)
|
|
19
|
+
CONNECTTIMEOUT_MS = Options.number(:connecttimeout_ms)
|
|
20
|
+
|
|
21
|
+
def reset
|
|
22
|
+
@_otel_span = @_restify_request = @_restify_writer = nil
|
|
23
|
+
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def prepare(request, options)
|
|
28
|
+
options.apply(self)
|
|
29
|
+
|
|
30
|
+
self.url = request.uri.to_s
|
|
31
|
+
self.headers = DEFAULT_HEADERS.merge(request.headers)
|
|
32
|
+
|
|
33
|
+
if request.timeout
|
|
34
|
+
# libcurl requires millisecond-based timeouts so fractional
|
|
35
|
+
# seconds must be converted and rounded.
|
|
36
|
+
timeout = (request.timeout * 1000).ceil
|
|
37
|
+
::Ethon::Curl.easy_setopt(handle, TIMEOUT_MS, :long, timeout)
|
|
38
|
+
::Ethon::Curl.easy_setopt(handle, CONNECTTIMEOUT_MS, :long, timeout)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
prepare_method(request.method, request.body)
|
|
42
|
+
|
|
43
|
+
# The OpenTelemetry instrumentation for Ethon takes the method
|
|
44
|
+
# from `#http_request`, which is not used here.
|
|
45
|
+
@otel_method = request.method
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def response(request)
|
|
49
|
+
code = return_code
|
|
50
|
+
if code != :ok
|
|
51
|
+
raise Restify::NetworkError.new(request, ::Ethon::Curl.easy_strerror(code))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Non-HTTP protocols are refused before a transfer is started,
|
|
55
|
+
# but libcurl still reports success with a zero status code
|
|
56
|
+
# whenever no HTTP status was received, and no status code at
|
|
57
|
+
# all when it cannot be read back.
|
|
58
|
+
status = response_code
|
|
59
|
+
if status.nil? || status.zero?
|
|
60
|
+
raise Restify::NetworkError.new(request, 'Response without HTTP status')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
::Restify::Response.new(
|
|
64
|
+
request,
|
|
65
|
+
effective_uri(request),
|
|
66
|
+
status,
|
|
67
|
+
parse_headers(response_headers),
|
|
68
|
+
response_body,
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Like Ethon's actions for each method, see `Ethon::Easy::Http`.
|
|
75
|
+
# An empty body counts as no body.
|
|
76
|
+
def prepare_method(method, body)
|
|
77
|
+
body = nil if body.nil? || body.empty?
|
|
78
|
+
|
|
79
|
+
case method
|
|
80
|
+
when 'get'
|
|
81
|
+
if body
|
|
82
|
+
postfields(body)
|
|
83
|
+
self.customrequest = 'GET'
|
|
84
|
+
end
|
|
85
|
+
when 'post'
|
|
86
|
+
postfields(body || '')
|
|
87
|
+
when 'put'
|
|
88
|
+
self.upload = true
|
|
89
|
+
self.infilesize = body ? body.bytesize : 0
|
|
90
|
+
set_read_callback(body) if body
|
|
91
|
+
when 'head'
|
|
92
|
+
postfields(body) if body
|
|
93
|
+
self.nobody = true
|
|
94
|
+
else
|
|
95
|
+
postfields(body) if body
|
|
96
|
+
self.customrequest = method.upcase
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def postfields(body)
|
|
101
|
+
self.postfieldsize = body.bytesize
|
|
102
|
+
self.copypostfields = body
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def effective_uri(request)
|
|
106
|
+
url = effective_url
|
|
107
|
+
|
|
108
|
+
# Only parse the URL after redirects, as parsing is expensive.
|
|
109
|
+
return request.uri if url.nil? || url == request.uri.to_s
|
|
110
|
+
|
|
111
|
+
Addressable::URI.parse(url)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def parse_headers(raw)
|
|
115
|
+
headers = {}
|
|
116
|
+
return headers if raw.nil?
|
|
117
|
+
|
|
118
|
+
# The raw headers can contain multiple blocks, e.g. from
|
|
119
|
+
# informational responses or when following redirects; only
|
|
120
|
+
# use the latest ones:
|
|
121
|
+
block = raw.split(/\r?\n\r?\n/).reject {|b| b.strip.empty? }.last
|
|
122
|
+
|
|
123
|
+
# Split on newlines that are not followed by whitespace to
|
|
124
|
+
# keep folded header values together.
|
|
125
|
+
block.to_s.split(/\r?\n(?!\s)/).each do |line|
|
|
126
|
+
line = line.strip
|
|
127
|
+
next if line.empty? || line.start_with?('HTTP/')
|
|
128
|
+
|
|
129
|
+
key, value = line.split(':', 2)
|
|
130
|
+
next if value.nil?
|
|
131
|
+
|
|
132
|
+
key = key.strip.upcase.tr('-', '_')
|
|
133
|
+
value = value.strip.gsub(/\r?\n\s*/, ' ')
|
|
134
|
+
|
|
135
|
+
case (current = headers[key])
|
|
136
|
+
when nil then headers[key] = value
|
|
137
|
+
when Array then current << value
|
|
138
|
+
else headers[key] = [current, value]
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
headers
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Restify
|
|
4
|
+
module Adapter
|
|
5
|
+
class Ethon < Base
|
|
6
|
+
#
|
|
7
|
+
# An event loop running libcurl's multi interface in
|
|
8
|
+
# `socket_action` mode, waiting for sockets with nio4r.
|
|
9
|
+
#
|
|
10
|
+
# Can woken up to add new easy handles immediately but can run
|
|
11
|
+
# from any thread. `LoopLock` ensures all interaction with libcurl
|
|
12
|
+
# is still serialized and threads are correctly prioritized.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
#
|
|
16
|
+
class EventLoop
|
|
17
|
+
include Logging
|
|
18
|
+
|
|
19
|
+
INTERESTS = {
|
|
20
|
+
none: nil,
|
|
21
|
+
in: :r,
|
|
22
|
+
out: :w,
|
|
23
|
+
inout: :rw,
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
def initialize(**multi)
|
|
27
|
+
@selector = NIO::Selector.new
|
|
28
|
+
@monitors = {}
|
|
29
|
+
@timer = nil
|
|
30
|
+
@queue = Queue.new
|
|
31
|
+
|
|
32
|
+
# libcurl only stores the function pointers, therefore the procs
|
|
33
|
+
# must be referenced here too, or they would be garbage
|
|
34
|
+
# collected.
|
|
35
|
+
@socketfunction = method(:on_socket).to_proc
|
|
36
|
+
@timerfunction = method(:on_timer).to_proc
|
|
37
|
+
|
|
38
|
+
@multi = ::Ethon::Multi.new(execution_mode: :socket_action, **multi)
|
|
39
|
+
@multi.socketfunction = @socketfunction
|
|
40
|
+
@multi.timerfunction = @timerfunction
|
|
41
|
+
|
|
42
|
+
# Wake up the background thread from `select` when a waiting
|
|
43
|
+
# thread wants to run the loop.
|
|
44
|
+
@lock = LoopLock.new { @selector.wakeup }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def enqueue(easy)
|
|
48
|
+
@queue << easy
|
|
49
|
+
@selector.wakeup
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Wake up threads waiting for the loop, e.g. after completing a
|
|
53
|
+
# transfer, to check if their result is available now.
|
|
54
|
+
def notify
|
|
55
|
+
@lock.notify
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Run the loop in the calling thread until the promise is
|
|
59
|
+
# complete or the timeout expires.
|
|
60
|
+
#
|
|
61
|
+
# Several threads can wait at the same time, but only one runs
|
|
62
|
+
# the loop. The others sleep until either their promise
|
|
63
|
+
# completes or the loop is released.
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean] false when the loop cannot be run in the
|
|
66
|
+
# calling thread, i.e. when waiting from within the loop
|
|
67
|
+
# itself.
|
|
68
|
+
#
|
|
69
|
+
def drive(promise, timeout) # rubocop:disable Naming/PredicateMethod
|
|
70
|
+
return false if @lock.owned?
|
|
71
|
+
|
|
72
|
+
while @lock.acquire(timeout) { promise.complete? }
|
|
73
|
+
begin
|
|
74
|
+
step(timeout.remaining) until promise.complete? || !timeout.remaining.positive?
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
error(e)
|
|
77
|
+
ensure
|
|
78
|
+
@lock.release
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Run the loop whenever no thread waits for it. Blocks forever,
|
|
86
|
+
# as meant for a background thread.
|
|
87
|
+
def run
|
|
88
|
+
loop do
|
|
89
|
+
@lock.acquire_background
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
step
|
|
93
|
+
ensure
|
|
94
|
+
@lock.release
|
|
95
|
+
end
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
error(e)
|
|
98
|
+
end
|
|
99
|
+
ensure
|
|
100
|
+
debug 'loop:exit'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Forget the loop in a forked child process, without releasing
|
|
104
|
+
# its libcurl handles, as they belong to the parent process.
|
|
105
|
+
# Transfers not completed yet are rejected, as they were started
|
|
106
|
+
# by the parent process too.
|
|
107
|
+
#
|
|
108
|
+
def forked!
|
|
109
|
+
pending = @multi.easy_handles.dup
|
|
110
|
+
pending << @queue.pop(true) until @queue.empty?
|
|
111
|
+
|
|
112
|
+
@multi.handle.autorelease = false
|
|
113
|
+
|
|
114
|
+
pending.each do |easy|
|
|
115
|
+
easy.handle.autorelease = false
|
|
116
|
+
easy._restify_writer.reject(
|
|
117
|
+
Restify::NetworkError.new(
|
|
118
|
+
easy._restify_request,
|
|
119
|
+
'Request started before fork',
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def step(limit = nil)
|
|
130
|
+
dequeue_all
|
|
131
|
+
|
|
132
|
+
# libcurl needs to be notified about its own timeouts, e.g. to
|
|
133
|
+
# start newly added transfers or to time out stalled ones.
|
|
134
|
+
timeout!
|
|
135
|
+
|
|
136
|
+
timeout = select_timeout
|
|
137
|
+
timeout = limit if limit && (timeout.nil? || timeout > limit)
|
|
138
|
+
debug 'loop:select', timeout: timeout
|
|
139
|
+
|
|
140
|
+
# nil on timeout; empty array when woken up
|
|
141
|
+
@selector.select(timeout)&.each do |monitor|
|
|
142
|
+
# libcurl can remove sockets while earlier events of the
|
|
143
|
+
# same batch are processed, e.g. when a completed transfer
|
|
144
|
+
# tears down other connections. Skip monitors that are gone
|
|
145
|
+
# by now.
|
|
146
|
+
next unless @monitors[monitor.value].equal?(monitor)
|
|
147
|
+
|
|
148
|
+
socket_action(monitor.value, readiness(monitor))
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def dequeue_all
|
|
153
|
+
loop do
|
|
154
|
+
easy = begin
|
|
155
|
+
@queue.pop(true)
|
|
156
|
+
rescue ThreadError
|
|
157
|
+
break
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
add(easy)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def add(easy)
|
|
165
|
+
# Restore the OpenTelemetry span the request originated from,
|
|
166
|
+
# so that the Ethon instrumentation picks up the correct
|
|
167
|
+
# parent when the easy handle is added to libcurl.
|
|
168
|
+
#
|
|
169
|
+
# Handle exceptions from Ethon or WebMock too, and reject the
|
|
170
|
+
# promise, so that the errors can be handled in user code.
|
|
171
|
+
OpenTelemetry::Trace.with_span(easy._otel_span) do
|
|
172
|
+
@multi.add(easy)
|
|
173
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
174
|
+
easy._restify_writer.reject(e)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Seconds until libcurl wants to be called again, or nil to
|
|
179
|
+
# block until a socket becomes ready or the loop is woken up.
|
|
180
|
+
def select_timeout
|
|
181
|
+
return nil unless @timer
|
|
182
|
+
|
|
183
|
+
[@timer - now, 0].max
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def timeout!
|
|
187
|
+
return unless @timer
|
|
188
|
+
return if now < @timer
|
|
189
|
+
|
|
190
|
+
# Reset before invoking libcurl, as it will set a new timeout
|
|
191
|
+
# from within the socket action.
|
|
192
|
+
@timer = nil
|
|
193
|
+
|
|
194
|
+
socket_action
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def readiness(monitor)
|
|
198
|
+
readiness = []
|
|
199
|
+
readiness << :in if monitor.readable?
|
|
200
|
+
readiness << :out if monitor.writable?
|
|
201
|
+
readiness
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def socket_action(socket = nil, readiness = 0)
|
|
205
|
+
@multi.socket_action(socket, readiness)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# libcurl: called when asking for readiness monitoring
|
|
209
|
+
def on_socket(_easy, socket, what, _userp, _socketp)
|
|
210
|
+
debug 'socket:action', tag: socket, what: what
|
|
211
|
+
|
|
212
|
+
if what == :remove
|
|
213
|
+
@monitors.delete(socket)&.close
|
|
214
|
+
else
|
|
215
|
+
monitor = (@monitors[socket] ||= register(socket))
|
|
216
|
+
monitor.interests = INTERESTS.fetch(what)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
:ok
|
|
220
|
+
rescue StandardError => e
|
|
221
|
+
error(e)
|
|
222
|
+
:ok
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# libcurl: called on timeout changes
|
|
226
|
+
def on_timer(_handle, timeout_ms, _userp)
|
|
227
|
+
debug 'timer:set', timeout: timeout_ms
|
|
228
|
+
@timer = timeout_ms.negative? ? nil : now + (timeout_ms / 1000.0)
|
|
229
|
+
|
|
230
|
+
:ok
|
|
231
|
+
rescue StandardError => e
|
|
232
|
+
error(e)
|
|
233
|
+
:ok
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def register(socket)
|
|
237
|
+
# FD owned by libcurl: IO/ruby must never close it
|
|
238
|
+
io = IO.for_fd(socket, autoclose: false)
|
|
239
|
+
|
|
240
|
+
@selector.register(io, :r).tap do |monitor|
|
|
241
|
+
monitor.value = socket
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def now
|
|
246
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def _log_prefix
|
|
250
|
+
"[#{object_id}/#{Thread.current.object_id}]"
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Restify
|
|
4
|
+
module Adapter
|
|
5
|
+
class Ethon < Base
|
|
6
|
+
#
|
|
7
|
+
# libcurl options, converted to the arguments for
|
|
8
|
+
# `curl_easy_setopt` once for performance reasons.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
#
|
|
12
|
+
class Options
|
|
13
|
+
DEFINITIONS = ::Ethon::Curl.easy_options(nil)
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def number(name)
|
|
17
|
+
definition(name).fetch(:opt)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def definition(name)
|
|
21
|
+
DEFINITIONS.fetch(name) do
|
|
22
|
+
raise ArgumentError.new("Unknown libcurl option: #{name}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Options by name, e.g. `followlocation: true`, nil values are
|
|
28
|
+
# skipped.
|
|
29
|
+
#
|
|
30
|
+
def initialize(options)
|
|
31
|
+
@options = options.filter_map do |name, value|
|
|
32
|
+
definition = self.class.definition(name)
|
|
33
|
+
next if value.nil?
|
|
34
|
+
|
|
35
|
+
compile(name, definition, value)
|
|
36
|
+
end.freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def apply(easy)
|
|
40
|
+
handle = easy.handle
|
|
41
|
+
|
|
42
|
+
@options.each do |opt, type, value|
|
|
43
|
+
if type
|
|
44
|
+
::Ethon::Curl.easy_setopt(handle, opt, type, value)
|
|
45
|
+
else
|
|
46
|
+
::Ethon::Curl.set_option(opt, value, handle)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_a
|
|
52
|
+
@options
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Like `Ethon::Curls::Options#set_option` for the supported types.
|
|
58
|
+
def compile(name, definition, value)
|
|
59
|
+
opt = definition[:opt]
|
|
60
|
+
|
|
61
|
+
case definition[:type]
|
|
62
|
+
when :bool then [opt, :long, value && value != 0 ? 1 : 0]
|
|
63
|
+
when :int, :time then [opt, :long, Integer(value)]
|
|
64
|
+
when :enum then [opt, :long, lookup(name, definition, value)]
|
|
65
|
+
when :bitmask then [opt, :long, Array(value).reduce(0) {|mask, v| mask | lookup(name, definition, v) }]
|
|
66
|
+
when :string then [opt, :string, value.to_s]
|
|
67
|
+
else [name, nil, value]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def lookup(name, definition, value)
|
|
72
|
+
return Integer(value) unless value.is_a?(Symbol) || value.is_a?(String)
|
|
73
|
+
|
|
74
|
+
definition[:opts].fetch(value.to_sym) do
|
|
75
|
+
raise ArgumentError.new("Unknown value for libcurl option #{name}: #{value.inspect}")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|