httpx 0.10.0 → 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -3
  3. data/doc/release_notes/0_10_1.md +37 -0
  4. data/doc/release_notes/0_10_2.md +5 -0
  5. data/doc/release_notes/0_11_0.md +76 -0
  6. data/doc/release_notes/0_11_1.md +1 -0
  7. data/doc/release_notes/0_11_2.md +5 -0
  8. data/lib/httpx/adapters/datadog.rb +205 -0
  9. data/lib/httpx/adapters/faraday.rb +0 -2
  10. data/lib/httpx/adapters/webmock.rb +123 -0
  11. data/lib/httpx/chainable.rb +8 -7
  12. data/lib/httpx/connection.rb +4 -15
  13. data/lib/httpx/connection/http1.rb +14 -1
  14. data/lib/httpx/connection/http2.rb +15 -16
  15. data/lib/httpx/domain_name.rb +1 -3
  16. data/lib/httpx/errors.rb +3 -1
  17. data/lib/httpx/headers.rb +1 -0
  18. data/lib/httpx/io/ssl.rb +4 -8
  19. data/lib/httpx/io/udp.rb +4 -3
  20. data/lib/httpx/plugins/compression.rb +1 -1
  21. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +1 -1
  22. data/lib/httpx/plugins/expect.rb +33 -8
  23. data/lib/httpx/plugins/multipart.rb +42 -23
  24. data/lib/httpx/plugins/multipart/encoder.rb +115 -0
  25. data/lib/httpx/plugins/multipart/mime_type_detector.rb +64 -0
  26. data/lib/httpx/plugins/multipart/part.rb +34 -0
  27. data/lib/httpx/plugins/proxy.rb +16 -2
  28. data/lib/httpx/plugins/proxy/socks4.rb +14 -16
  29. data/lib/httpx/plugins/proxy/socks5.rb +3 -2
  30. data/lib/httpx/plugins/push_promise.rb +2 -2
  31. data/lib/httpx/pool.rb +8 -14
  32. data/lib/httpx/request.rb +22 -12
  33. data/lib/httpx/resolver.rb +7 -6
  34. data/lib/httpx/resolver/https.rb +18 -23
  35. data/lib/httpx/resolver/native.rb +22 -19
  36. data/lib/httpx/resolver/resolver_mixin.rb +4 -2
  37. data/lib/httpx/resolver/system.rb +3 -3
  38. data/lib/httpx/selector.rb +9 -13
  39. data/lib/httpx/session.rb +24 -21
  40. data/lib/httpx/transcoder.rb +20 -0
  41. data/lib/httpx/transcoder/form.rb +9 -1
  42. data/lib/httpx/version.rb +1 -1
  43. data/sig/connection.rbs +84 -1
  44. data/sig/connection/http1.rbs +66 -0
  45. data/sig/connection/http2.rbs +73 -0
  46. data/sig/headers.rbs +3 -0
  47. data/sig/httpx.rbs +1 -0
  48. data/sig/options.rbs +3 -3
  49. data/sig/plugins/basic_authentication.rbs +1 -1
  50. data/sig/plugins/compression.rbs +1 -1
  51. data/sig/plugins/compression/brotli.rbs +1 -1
  52. data/sig/plugins/compression/deflate.rbs +1 -1
  53. data/sig/plugins/compression/gzip.rbs +1 -1
  54. data/sig/plugins/h2c.rbs +1 -1
  55. data/sig/plugins/multipart.rbs +29 -4
  56. data/sig/plugins/persistent.rbs +1 -1
  57. data/sig/plugins/proxy.rbs +2 -2
  58. data/sig/plugins/proxy/ssh.rbs +1 -1
  59. data/sig/plugins/rate_limiter.rbs +1 -1
  60. data/sig/pool.rbs +36 -2
  61. data/sig/request.rbs +2 -2
  62. data/sig/resolver.rbs +26 -0
  63. data/sig/resolver/https.rbs +51 -0
  64. data/sig/resolver/native.rbs +60 -0
  65. data/sig/resolver/resolver_mixin.rbs +27 -0
  66. data/sig/resolver/system.rbs +17 -0
  67. data/sig/response.rbs +2 -2
  68. data/sig/selector.rbs +20 -0
  69. data/sig/session.rbs +3 -3
  70. data/sig/transcoder.rbs +4 -2
  71. data/sig/transcoder/body.rbs +2 -0
  72. data/sig/transcoder/form.rbs +8 -2
  73. data/sig/transcoder/json.rbs +3 -1
  74. metadata +47 -48
  75. data/lib/httpx/resolver/options.rb +0 -25
  76. data/sig/missing.rbs +0 -12
  77. data/sig/test.rbs +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e3727a5374ad3c2d6d7c5dbfd61040e3aa3ea6c74aa6ad12d662a5bd0b9a7af
4
- data.tar.gz: 7b1a2aa15418b03fc276b81cfaf0565bbce59dd531c2bfc10f19ddc60506213b
3
+ metadata.gz: 3455bd74f9d6c0cf65936bae67bf74b4d1d5fde21d254446cb4a4615a65b9bb9
4
+ data.tar.gz: a8f4c6d743667725fcfb3f43c42ef0efbcdc9772f84ecd45f558d2d4c94c3345
5
5
  SHA512:
6
- metadata.gz: cda3ab9929396bdbad4ff38472dea4d7af34946e34cf995228ad5d52d05e37664b53f66e0a0ac8dd10193e55de52b4f7575f203bea8202b5474f0c3cca450d24
7
- data.tar.gz: 025a985fc7c05abbb1bd66d23356d5165f7536d6565464e0729c618da624fc9f428e1046bba7c09dc5d7c23ada0587f73fb23e42128c96847857bcc1ccd6d408
6
+ metadata.gz: 835072956f05fcb3a42b3e128d64e5753409aa32e875426dda24d8ddbbffdd0fc6585a6d48abd48cd470962f0a57492c4b74afe529c4ab5c750d3039fdbe2b3d
7
+ data.tar.gz: 8c8be2d19b36b4ad37f00a9f8ea9378e2f60e6e65e4f9513e84b24139480f529650e74c2a3c36a200d0e318ce86c2b15be37a2522032225ba0a9055cbac4534b
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # HTTPX: A Ruby HTTP library for tomorrow... and beyond!
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/httpx.svg)](http://rubygems.org/gems/httpx)
4
- [![pipeline status](https://gitlab.com/honeyryderchuck/httpx/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/httpx/commits/master)
4
+ [![pipeline status](https://gitlab.com/honeyryderchuck/httpx/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/httpx/pipelines?page=1&scope=all&ref=master)
5
5
  [![coverage report](https://gitlab.com/honeyryderchuck/httpx/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/httpx/coverage/#_AllFiles)
6
6
 
7
7
  HTTPX is an HTTP client library for the Ruby programming language.
@@ -85,7 +85,15 @@ However if the server supports HTTP/1.1, it will try to use HTTP pipelining, fal
85
85
 
86
86
  ### Clean API
87
87
 
88
- `HTTPX` acknowledges the ease-of-use of the [http gem](https://github.com/httprb/http) API (itself inspired by python [requests](http://docs.python-requests.org/en/latest/) library). It therefore aims at reusing the same facade, extending it for the use cases which the http gem doesn't support.
88
+ `httpx` builds all functions around the `HTTPX` module, so that all calls can compose of each other. Here are a few examples:
89
+
90
+ ```ruby
91
+ response = HTTPX.get("https://www.google.com")
92
+ response = HTTPX.post("https://www.nghttp2.org/httpbin/post", params: {name: "John", age: "22"})
93
+ response = HTTPX.plugin(:basic_authentication)
94
+ .basic_authentication("user", "pass")
95
+ .get("https://www.google.com")
96
+ ```
89
97
 
90
98
  ### Lightweight
91
99
 
@@ -135,7 +143,7 @@ Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryde
135
143
 
136
144
  * Discuss your contribution in an issue
137
145
  * Fork it
138
- * Make your changes, add some test
146
+ * Make your changes, add some tests
139
147
  * Ensure all tests pass (`bundle exec rake test`)
140
148
  * Open a Merge Request (that's Pull Request in Github-ish)
141
149
  * Wait for feedback
@@ -0,0 +1,37 @@
1
+ # 0.10.1
2
+
3
+ ## Improvements
4
+
5
+ ### URL-encoded nested params
6
+
7
+ url encoder now supports nested params, which is a standard of rack-based frameworks:
8
+
9
+ ```ruby
10
+ HTTPX.post("https://httpbin.org/post", form: { a: { b: 1 }, c: [2, 3] })
11
+ # a[b]=1&c[]=2&c[]=3
12
+ ```
13
+
14
+ This encoding scheme is now the standard for URL-encoded request bodies, query params, and `:multipart` plugin requests.
15
+
16
+ ### Socks4 IPv6 addresses
17
+
18
+ HTTPX supports IPv6 Socks4 proxies now. This support is restricted to rubies where `IPAddr#hton` is implemented though, so you are encouraged to upgrade.
19
+
20
+ ## More verbose HTTP Errors
21
+
22
+ `HTTPX::Response#raise_for_status` was raising exceptions for client/server HTTP errors codes (4xx/5xx). However, only the status code was part of the message.
23
+
24
+ From now on, both headers and the responnse payload will also appear, so expected more verbosity, but also more meaningful information.
25
+
26
+ ## Bugfixes
27
+
28
+ * HTTP/2 and HTTP/1.1 exhausted connections now get properly migrated into a new connection;
29
+ * HTTP/2 421 responses will now correctly migrate the connection and pendign requests to HTTP/1.1 (a hanging loop was being caused);
30
+ * HTTP/2 connection failed with a GOAWAY settings timeout will now return error responses (instead of hanging indefinitely);
31
+ * Non-IP proxy name-resolving errors will now move on to the next available proxy in the list (instead of hanging indefinitely);
32
+ * Non-IP DNS resolve errors for `native` and `https` variants will now return the appropriate error response (instead of hanging indefinitely);
33
+
34
+ ## Chore
35
+
36
+ * `HTTPX.plugins` is now officially deprecated (use `HTTPX.plugin` instead);
37
+
@@ -0,0 +1,5 @@
1
+ # 0.10.2
2
+
3
+ ## Bugfixes
4
+
5
+ Support for nested params in multipart forms introduced a bug where top-level params weren't being spread out correctly in the request body.
@@ -0,0 +1,76 @@
1
+ # 0.11.0
2
+
3
+ ## Features
4
+
5
+ ### Webmock Adapter
6
+
7
+ `httpx` can now be integrated with `webmock`, a popular HTTP requests stubbing library.
8
+
9
+ ```ruby
10
+ # minitest
11
+ require "webmock/minitest"
12
+ require "httpx/adapters/webmock"
13
+
14
+ # in rspec
15
+ require "webmock/rspec"
16
+ require "httpx/adapters/webmock"
17
+
18
+ # and now you're free for mocking
19
+ WebMock.enable!
20
+ stub_http_request(:get, "https://www.google.com").and_return(status: 200, body: "here's google")
21
+
22
+ ```
23
+
24
+ Read more about it in the [webmock integration documentation](https://honeyryderchuck.gitlab.io/httpx/wiki/Webmock-Adapter).
25
+
26
+ ### Datadog Adapter
27
+
28
+ `httpx` ships with integration for [ddtrace, datadog's official tracing client](https://github.com/DataDog/dd-trace-rb). You just need to initialize it the following way:
29
+
30
+ ```ruby
31
+ require "ddtrace"
32
+ require "httpx/adapters/datadog"
33
+
34
+ Datadog.configure do |c|
35
+ c.use :httpx
36
+ end
37
+ ```
38
+
39
+ A trace will be emitted for every request, so this should be an interesting visualization if concurrent requests are sent.
40
+
41
+ Customization options and traces are similar to what [the net-http adapter provides](https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#nethttp).
42
+
43
+ Read more about it in the [datadog integration documentation](https://honeyryderchuck.gitlab.io/httpx/wiki/Datadog-Adapter).
44
+
45
+ ## Improvements
46
+
47
+ ### Own multipart request encoder
48
+
49
+ `httpx` now ships with its own multipart formdata encoder, and does not rely on `http-form_data` anymore:
50
+
51
+ ```ruby
52
+ HTTPX.plugin(:multipart).post(uri, form: {file: File.new("path/to/file")})
53
+ ```
54
+
55
+ Read more about it in the [multipart plugin documentation](https://honeyryderchuck.gitlab.io/httpx/wiki/Multipart-Uploads), including also about why this was made.
56
+
57
+ ### Expect Plugin
58
+
59
+ The `:expect` plugin now works reliably when the server does not support the `expect: 100-continue` header, i.e. it'll upload the body after a certain timeout. Building onn that, two behaviours are now implemented:
60
+
61
+ * A cache of domains which did not respond to the `expect` header is now kept, so that subsequent requests can skip the timeout and immediately upload the payload.
62
+ * If the "100 Continue" response arrives **after** the timeout expired and the body has been uploaded, the domain is removed from the cache, and subsequent requests will send the `expect` header.
63
+
64
+ ### SNI/Host options
65
+
66
+ Some extension of the API was applied in order to support custom TLS negotiation parameters. You can now pass `:hostname` under the `:ssl` options, and this will be used for the SNI part of the TLS negotiation. This is useful in scenarios where a proxy certificate doesn't apply for the host one wants to send the request to:
67
+
68
+ ```ruby
69
+ response = session.get(proxy_ip, headers: { "host" => upstream_hostname }, ssl: { hostname: sni_hostname }
70
+ ```
71
+
72
+ ## Bugfixes
73
+
74
+ A default 5 second timeout is in-place when using the DNS `:system` resolver, as it was found out that. when using the `resolv` library, the DNS query will not be retried otherwise. You can change this setting py passing `resolver_options: { timeouts: ANOTHER_TIMEOUT}`. In the future, this may become another timeout option, however.
75
+
76
+
@@ -0,0 +1 @@
1
+ 0_11_1.md
@@ -0,0 +1,5 @@
1
+ # 0.11.2
2
+
3
+ ## Bugfixes
4
+
5
+ The `:cookies` plugin wasn't able to parse `Expires` values, as it was using `Time.httpdate` to parse timestamps, which is RFC 2616-compliant, whereas cookies datetime values need to be RFC 6265-compliant.
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ddtrace/contrib/integration"
4
+ require "ddtrace/contrib/rest_client/configuration/settings"
5
+ require "ddtrace/contrib/rest_client/patcher"
6
+
7
+ module Datadog
8
+ module Contrib
9
+ module HTTPX
10
+ # HTTPX Datadog Plugin
11
+ #
12
+ # Enables tracing for httpx requests. A span will be created for each individual requests,
13
+ # and it'll trace since the moment it is fed to the connection, until the moment the response is
14
+ # fed back to the session.
15
+ #
16
+ module Plugin
17
+ class RequestTracer
18
+ SPAN_REQUEST = "httpx.request"
19
+
20
+ def initialize(request)
21
+ @request = request
22
+ end
23
+
24
+ def call
25
+ return if skip_tracing?
26
+
27
+ @request.on(:response, &method(:finish))
28
+
29
+ verb = @request.verb.to_s.upcase
30
+ uri = @request.uri
31
+
32
+ @span = datadog_pin.tracer.trace(SPAN_REQUEST)
33
+ service_name = datadog_config[:split_by_domain] ? uri.host : datadog_pin.service_name
34
+
35
+ begin
36
+ @span.service = service_name
37
+ @span.span_type = Datadog::Ext::HTTP::TYPE_OUTBOUND
38
+ @span.resource = verb
39
+
40
+ Datadog::HTTPPropagator.inject!(@span.context, @request.headers) if datadog_pin.tracer.enabled && !skip_distributed_tracing?
41
+
42
+ # Add additional request specific tags to the span.
43
+
44
+ @span.set_tag(Datadog::Ext::HTTP::URL, @request.path)
45
+ @span.set_tag(Datadog::Ext::HTTP::METHOD, verb)
46
+
47
+ @span.set_tag(Datadog::Ext::NET::TARGET_HOST, uri.host)
48
+ @span.set_tag(Datadog::Ext::NET::TARGET_PORT, uri.port.to_s)
49
+
50
+ # Tag as an external peer service
51
+ @span.set_tag(Datadog::Ext::Integration::TAG_PEER_SERVICE, @span.service)
52
+
53
+ # Set analytics sample rate
54
+ if Contrib::Analytics.enabled?(datadog_config[:analytics_enabled])
55
+ Contrib::Analytics.set_sample_rate(@span, datadog_config[:analytics_sample_rate])
56
+ end
57
+ rescue StandardError => e
58
+ Datadog.logger.error("error preparing span for http request: #{e}")
59
+ end
60
+ rescue StandardError => e
61
+ Datadog.logger.debug("Failed to start span: #{e}")
62
+ end
63
+
64
+ def finish(response)
65
+ return unless @span
66
+
67
+ if response.respond_to?(:error)
68
+ @span.set_error(response.error)
69
+ else
70
+ @span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.status.to_s)
71
+
72
+ @span.set_error(::HTTPX::HTTPError.new(response)) if response.status >= 400 && response.status <= 599
73
+ end
74
+
75
+ @span.finish
76
+ end
77
+
78
+ private
79
+
80
+ def skip_tracing?
81
+ return true if @request.headers.key?(Datadog::Ext::Transport::HTTP::HEADER_META_TRACER_VERSION)
82
+
83
+ return false unless @datadog_pin
84
+
85
+ span = @datadog_pin.tracer.active_span
86
+
87
+ return true if span && (span.name == SPAN_REQUEST)
88
+
89
+ false
90
+ end
91
+
92
+ def skip_distributed_tracing?
93
+ return !datadog_pin.config[:distributed_tracing] if datadog_pin.config && datadog_pin.config.key?(:distributed_tracing)
94
+
95
+ !Datadog.configuration[:httpx][:distributed_tracing]
96
+ end
97
+
98
+ def datadog_pin
99
+ @datadog_pin ||= begin
100
+ service = datadog_config[:service_name]
101
+ tracer = datadog_config[:tracer]
102
+
103
+ Datadog::Pin.new(
104
+ service,
105
+ app: "httpx",
106
+ app_type: Datadog::Ext::AppTypes::WEB,
107
+ tracer: -> { tracer }
108
+ )
109
+ end
110
+ end
111
+
112
+ def datadog_config
113
+ @datadog_config ||= Datadog.configuration[:httpx, @request.uri.host]
114
+ end
115
+ end
116
+
117
+ module ConnectionMethods
118
+ def send(request)
119
+ RequestTracer.new(request).call
120
+ super
121
+ end
122
+ end
123
+ end
124
+
125
+ module Configuration
126
+ # Default settings for httpx
127
+ #
128
+ class Settings < Datadog::Contrib::Configuration::Settings
129
+ option :service_name, default: "httpx"
130
+ option :distributed_tracing, default: true
131
+ option :split_by_domain, default: false
132
+
133
+ option :enabled do |o|
134
+ o.default { env_to_bool("DD_TRACE_HTTPX_ENABLED", true) }
135
+ o.lazy
136
+ end
137
+
138
+ option :analytics_enabled do |o|
139
+ o.default { env_to_bool(%w[DD_TRACE_HTTPX_ANALYTICS_ENABLED DD_HTTPX_ANALYTICS_ENABLED], false) }
140
+ o.lazy
141
+ end
142
+
143
+ option :analytics_sample_rate do |o|
144
+ o.default { env_to_float(%w[DD_TRACE_HTTPX_ANALYTICS_SAMPLE_RATE DD_HTTPX_ANALYTICS_SAMPLE_RATE], 1.0) }
145
+ o.lazy
146
+ end
147
+
148
+ option :error_handler, default: Datadog::Tracer::DEFAULT_ON_ERROR
149
+ end
150
+ end
151
+
152
+ # Patcher enables patching of 'httpx' with datadog components.
153
+ #
154
+ module Patcher
155
+ include Datadog::Contrib::Patcher
156
+
157
+ module_function
158
+
159
+ def target_version
160
+ Integration.version
161
+ end
162
+
163
+ # loads a session instannce with the datadog plugin, and replaces the
164
+ # base HTTPX::Session with the patched session class.
165
+ def patch
166
+ datadog_session = ::HTTPX.plugin(Plugin)
167
+
168
+ ::HTTPX.send(:remove_const, :Session)
169
+ ::HTTPX.send(:const_set, :Session, datadog_session.class)
170
+ end
171
+ end
172
+
173
+ # Datadog Integration for HTTPX.
174
+ #
175
+ class Integration
176
+ include Contrib::Integration
177
+
178
+ # MINIMUM_VERSION = Gem::Version.new('0.11.0')
179
+ MINIMUM_VERSION = Gem::Version.new("0.10.2")
180
+
181
+ register_as :httpx
182
+
183
+ def self.version
184
+ Gem.loaded_specs["httpx"] && Gem.loaded_specs["httpx"].version
185
+ end
186
+
187
+ def self.loaded?
188
+ defined?(::HTTPX::Request)
189
+ end
190
+
191
+ def self.compatible?
192
+ super && version >= MINIMUM_VERSION
193
+ end
194
+
195
+ def default_configuration
196
+ Configuration::Settings.new
197
+ end
198
+
199
+ def patcher
200
+ Patcher
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -65,7 +65,6 @@ module Faraday
65
65
  plugin(:compression)
66
66
  plugin(:persistent)
67
67
 
68
- # :nocov:
69
68
  module ReasonPlugin
70
69
  if RUBY_VERSION < "2.5"
71
70
  def self.load_dependencies(*)
@@ -88,7 +87,6 @@ module Faraday
88
87
  end
89
88
  end
90
89
  end
91
- # :nocov:
92
90
  plugin(ReasonPlugin)
93
91
  end
94
92
 
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebMock
4
+ module HttpLibAdapters
5
+ if RUBY_VERSION < "2.5"
6
+ require "webrick/httpstatus"
7
+ HTTP_REASONS = WEBrick::HTTPStatus::StatusMessage
8
+ else
9
+ require "net/http/status"
10
+ HTTP_REASONS = Net::HTTP::STATUS_CODES
11
+ end
12
+
13
+ #
14
+ # HTTPX plugin for webmock.
15
+ #
16
+ # Requests are "hijacked" at the session, before they're distributed to a connection.
17
+ #
18
+ module Plugin
19
+ module InstanceMethods
20
+ private
21
+
22
+ def send_requests(*requests, options)
23
+ request_signatures = requests.map do |request|
24
+ request_signature = _build_webmock_request_signature(request)
25
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
26
+ request_signature
27
+ end
28
+
29
+ responses = request_signatures.map do |request_signature|
30
+ WebMock::StubRegistry.instance.response_for_request(request_signature)
31
+ end
32
+
33
+ real_requests = {}
34
+
35
+ requests.each_with_index.each_with_object([request_signatures, responses]) do |(request, idx), (sig_reqs, mock_responses)|
36
+ if (webmock_response = mock_responses[idx])
37
+ mock_responses[idx] = _build_from_webmock_response(request, webmock_response)
38
+ WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, sig_reqs[idx], webmock_response)
39
+ log { "mocking #{request.uri} with #{mock_responses[idx].inspect}" }
40
+ elsif WebMock.net_connect_allowed?(sig_reqs[idx].uri)
41
+ log { "performing #{request.uri}" }
42
+ real_requests[request] = idx
43
+ else
44
+ raise WebMock::NetConnectNotAllowedError, sig_reqs[idx]
45
+ end
46
+ end
47
+
48
+ unless real_requests.empty?
49
+ reqs = real_requests.keys
50
+ reqs.zip(super(*reqs, options)).each do |req, res|
51
+ idx = real_requests[req]
52
+
53
+ if WebMock::CallbackRegistry.any_callbacks?
54
+ webmock_response = _build_webmock_response(req, res)
55
+ WebMock::CallbackRegistry.invoke_callbacks(
56
+ { lib: :httpx, real_request: true }, request_signatures[idx],
57
+ webmock_response
58
+ )
59
+ end
60
+
61
+ responses[idx] = res
62
+ end
63
+ end
64
+
65
+ responses
66
+ end
67
+
68
+ def _build_webmock_request_signature(request)
69
+ uri = WebMock::Util::URI.heuristic_parse(request.uri)
70
+ uri.path = uri.normalized_path.gsub("[^:]//", "/")
71
+
72
+ WebMock::RequestSignature.new(
73
+ request.verb,
74
+ uri.to_s,
75
+ body: request.body.each.to_a.join,
76
+ headers: request.headers.to_h
77
+ )
78
+ end
79
+
80
+ def _build_webmock_response(_request, response)
81
+ webmock_response = WebMock::Response.new
82
+ webmock_response.status = [response.status, HTTP_REASONS[response.status]]
83
+ webmock_response.body = response.body.to_s
84
+ webmock_response.headers = response.headers.to_h
85
+ webmock_response
86
+ end
87
+
88
+ def _build_from_webmock_response(request, webmock_response)
89
+ return ErrorResponse.new(request, webmock_response.exception, request.options) if webmock_response.exception
90
+
91
+ response = request.options.response_class.new(request,
92
+ webmock_response.status[0],
93
+ "2.0",
94
+ webmock_response.headers)
95
+ response << webmock_response.body.dup
96
+ response
97
+ end
98
+ end
99
+ end
100
+
101
+ class HttpxAdapter < HttpLibAdapter
102
+ adapter_for :httpx
103
+
104
+ class << self
105
+ def enable!
106
+ @original_session = ::HTTPX::Session
107
+
108
+ webmock_session = ::HTTPX.plugin(Plugin)
109
+
110
+ ::HTTPX.send(:remove_const, :Session)
111
+ ::HTTPX.send(:const_set, :Session, webmock_session.class)
112
+ end
113
+
114
+ def disable!
115
+ return unless @original_session
116
+
117
+ HTTPX.send(:remove_const, :Session)
118
+ HTTPX.send(:const_set, :Session, @original_session)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end