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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +30 -4
- data/lib/restify/adapter/base.rb +10 -1
- data/lib/restify/adapter/ethon.rb +462 -0
- data/lib/restify/adapter/loop_lock.rb +124 -0
- data/lib/restify/adapter/telemetry.rb +6 -4
- data/lib/restify/adapter/typhoeus.rb +48 -23
- data/lib/restify/context.rb +5 -5
- 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 +13 -1
- data/lib/restify/promise.rb +20 -2
- data/lib/restify/registry.rb +2 -2
- data/lib/restify/relation.rb +8 -8
- 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 +11 -5
- data/lib/restify/version.rb +3 -3
- data/lib/restify/webmock.rb +126 -0
- data/lib/restify.rb +5 -1
- data/spec/restify/adapter/ethon_spec.rb +263 -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 +55 -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 +36 -0
- data/spec/restify/processors/msgpack_spec.rb +36 -0
- data/spec/restify/promise_spec.rb +82 -0
- data/spec/restify/relation_spec.rb +2 -6
- data/spec/restify/request_spec.rb +26 -0
- data/spec/restify/timeout_spec.rb +11 -0
- data/spec/spec_helper.rb +10 -6
- data/spec/support/stub_server.rb +4 -4
- metadata +26 -209
- 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: 99d1fd08757bc62db3bd58386310893f58224c6f0424980c412448c821021b20
|
|
4
|
+
data.tar.gz: 97ea53432dcee6d6089332eb1783dfd04108f62f88ce3070f1c06b03d7cb1360
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
[](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
|
|
|
@@ -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
|
-
* [
|
|
16
|
+
* [ethon](https://github.com/typhoeus/ethon), using libcurl
|
|
18
17
|
|
|
19
|
-
The
|
|
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-
|
|
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
|
|
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,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
|