httpx 0.18.2 → 0.18.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2f0ac68d969d4462c03c28fa818fa055e4e3aabad5a599197df6cc912274673
4
- data.tar.gz: '069b5100a14183e79a968f4bb69363304ce046d098d1283f934568ea0fc23e06'
3
+ metadata.gz: b6784fbd393b5caed888e6c4d1a421cde9c7aca49f35baaafbf13f10a68e9d02
4
+ data.tar.gz: ddd5bb8793fa72b19e462b411f5eaaaf553f30d589a68dfdde64224c49d902f6
5
5
  SHA512:
6
- metadata.gz: bddcf91c4cd851204ff789798bc8745e6e7f2b39face3ae77c83de4aa8be4de278f24a0ce1405f5e142f2f358fbf78472a5a816ae315ac43574c81adb6b82838
7
- data.tar.gz: f9d992536ae6e464833dbe05c6a884ba373f84d90e42657bd18bab9fc69288561361041563dbe26dee92e9b9f337ee57d7eb7b9f7586abe705905184cd57f9e3
6
+ metadata.gz: 949f18741f10eaa57463b8764569e69cdfb1ca6f4eefa30bbdcf142cbbd8b61b360d9bd78898a816a4ebd9e15d4ffd1c0d8282033a459826919ce8132d5b105f
7
+ data.tar.gz: e4430eea967c9f5628d51e6150bc4f63e704eaed2bd61fda191f3539a3f102e00932cc8f63ddb735c33bbf21e2694bb23fed5eaea588159e71fa6f484016c150
@@ -0,0 +1,7 @@
1
+ # 0.18.3
2
+
3
+ ## Bugfixes
4
+
5
+ * request bodies eager-loaded from enumerables yield duped partial chunks.
6
+
7
+ An error was observed while looking at webmock integration, where requests formed via the multipart plugin were returning an empty string as body. The issue was caused by an optimization on multipart encoder, which reuses the same buffer when reading chunks. Unfortunately, these cannot be yielded the same way via IO.copy_stream, as the same (cleared) buffer will be used to generate the eager-loaded body chunks.
@@ -0,0 +1,14 @@
1
+ # 0.18.4
2
+
3
+ ## Improvements
4
+
5
+ * faraday adapter: added support for `#on_data` callback in order to support [faraday streaming](https://lostisland.github.io/faraday/usage/streaming).
6
+
7
+ * multipart plugin: removed support for file mime type detection using `mime-types`. The reasoning behind it was that `mime-types` uses the filename, which is a very inaccurate detection strategy (ex: an mp4 video will be identified as `application/mp4`, instead of the correct `video/mp4`).
8
+ * multipart plugin: supported for file mime type detection using `marcel` and `filemagic` was added. Both use the magic header bytes, which is a more accurate strategy for file type detection.
9
+
10
+ ## Bugfixes
11
+
12
+ * webmock adapter has been reimplemented to work with `httpx` plugins (such as the `:retries` plugin). Some other fixes were applied to make it work better under `vcr` (a common `webmock` extension).
13
+
14
+ * fixed the URI-related bug which was making requests stall under ruby 3.1 (still not officially testing against it).
@@ -0,0 +1,10 @@
1
+ # 0.18.5
2
+
3
+ ## Improvements
4
+
5
+ * ruby 3.1 is now officially supported.
6
+ * when user sets a `Host` header for an HTTP/2 request, this will be used in the `:authority` HTTP/2 pseudo-header, instead of silently ignored (mimicking what "curl" does).
7
+
8
+ ## Bugfixes
9
+
10
+ * fixed "throw outside of catch block" error happening when pipelining requests on an HTTP/1 connnection and resulting in a timeout.
@@ -0,0 +1,5 @@
1
+ # 0.18.6
2
+
3
+ ## Bugfixes
4
+
5
+ * multipart plugin: fixed missing constant in `filemagic` integration.
@@ -21,6 +21,17 @@ module Faraday
21
21
  end
22
22
  # :nocov:
23
23
 
24
+ unless Faraday::RequestOptions.method_defined?(:stream_response?)
25
+ module RequestOptionsExtensions
26
+ refine Faraday::RequestOptions do
27
+ def stream_response?
28
+ false
29
+ end
30
+ end
31
+ end
32
+ using RequestOptionsExtensions
33
+ end
34
+
24
35
  module RequestMixin
25
36
  using ::HTTPX::HashExtensions
26
37
 
@@ -64,6 +75,27 @@ module Faraday
64
75
 
65
76
  include RequestMixin
66
77
 
78
+ module OnDataPlugin
79
+ module RequestMethods
80
+ attr_writer :response_on_data
81
+
82
+ def response=(response)
83
+ super
84
+ response.body.on_data = @response_on_data
85
+ end
86
+ end
87
+
88
+ module ResponseBodyMethods
89
+ attr_writer :on_data
90
+
91
+ def write(chunk)
92
+ return super unless @on_data
93
+
94
+ @on_data.call(chunk, chunk.bytesize)
95
+ end
96
+ end
97
+ end
98
+
67
99
  class Session < ::HTTPX::Session
68
100
  plugin(:compression)
69
101
  plugin(:persistent)
@@ -137,15 +169,21 @@ module Faraday
137
169
  end
138
170
 
139
171
  def run
140
- requests = @handlers.map { |handler| build_request(handler.env) }
141
172
  env = @handlers.last.env
142
173
 
143
- proxy_options = { uri: env.request.proxy }
144
-
145
174
  session = @session.with(options_from_env(env))
146
- session = session.plugin(:proxy).with(proxy: proxy_options) if env.request.proxy
175
+ session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
176
+ session = session.plugin(OnDataPlugin) if env.request.stream_response?
177
+
178
+ requests = @handlers.map { |handler| session.build_request(*build_request(handler.env)) }
179
+
180
+ if env.request.stream_response?
181
+ requests.each do |request|
182
+ request.response_on_data = env.request.on_data
183
+ end
184
+ end
147
185
 
148
- responses = session.request(requests)
186
+ responses = session.request(*requests)
149
187
  Array(responses).each_with_index do |response, index|
150
188
  handler = @handlers[index]
151
189
  handler.on_response.call(response)
@@ -179,11 +217,15 @@ module Faraday
179
217
  return handler
180
218
  end
181
219
 
182
- meth, uri, request_options = build_request(env)
183
-
184
220
  session = @session.with(options_from_env(env))
185
- session = session.plugin(:proxy).with(proxy: proxy_options) if env.request.proxy
186
- response = session.__send__(meth, uri, **request_options)
221
+ session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
222
+ session = session.plugin(OnDataPlugin) if env.request.stream_response?
223
+
224
+ request = session.build_request(*build_request(env))
225
+
226
+ request.response_on_data = env.request.on_data if env.request.stream_response?
227
+
228
+ response = session.request(request)
187
229
  response.raise_for_status unless response.is_a?(::HTTPX::Response)
188
230
  save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
189
231
  response_headers.merge!(response.headers)
@@ -16,56 +16,8 @@ module WebMock
16
16
  # Requests are "hijacked" at the session, before they're distributed to a connection.
17
17
  #
18
18
  module Plugin
19
- module InstanceMethods
20
- private
21
-
22
- def send_requests(*requests)
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)).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)
19
+ class << self
20
+ def build_webmock_request_signature(request)
69
21
  uri = WebMock::Util::URI.heuristic_parse(request.uri)
70
22
  uri.path = uri.normalized_path.gsub("[^:]//", "/")
71
23
 
@@ -77,7 +29,7 @@ module WebMock
77
29
  )
78
30
  end
79
31
 
80
- def _build_webmock_response(_request, response)
32
+ def build_webmock_response(_request, response)
81
33
  webmock_response = WebMock::Response.new
82
34
  webmock_response.status = [response.status, HTTP_REASONS[response.status]]
83
35
  webmock_response.body = response.body.to_s
@@ -85,10 +37,10 @@ module WebMock
85
37
  webmock_response
86
38
  end
87
39
 
88
- def _build_from_webmock_response(request, webmock_response)
89
- return _build_error_response(request, HTTPX::TimeoutError.new(1, "Timed out")) if webmock_response.should_timeout
40
+ def build_from_webmock_response(request, webmock_response)
41
+ return build_error_response(request, HTTPX::TimeoutError.new(1, "Timed out")) if webmock_response.should_timeout
90
42
 
91
- return _build_error_response(request, webmock_response.exception) if webmock_response.exception
43
+ return build_error_response(request, webmock_response.exception) if webmock_response.exception
92
44
 
93
45
  response = request.options.response_class.new(request,
94
46
  webmock_response.status[0],
@@ -98,10 +50,70 @@ module WebMock
98
50
  response
99
51
  end
100
52
 
101
- def _build_error_response(request, exception)
53
+ def build_error_response(request, exception)
102
54
  HTTPX::ErrorResponse.new(request, exception, request.options)
103
55
  end
104
56
  end
57
+
58
+ module InstanceMethods
59
+ def build_connection(*)
60
+ connection = super
61
+ connection.once(:unmock_connection) do
62
+ pool.__send__(:resolve_connection, connection)
63
+ pool.__send__(:unregister_connection, connection) unless connection.addresses
64
+ end
65
+ connection
66
+ end
67
+ end
68
+
69
+ module ConnectionMethods
70
+ def initialize(*)
71
+ super
72
+ @mocked = true
73
+ end
74
+
75
+ def open?
76
+ return true if @mocked
77
+
78
+ super
79
+ end
80
+
81
+ def interests
82
+ return if @mocked
83
+
84
+ super
85
+ end
86
+
87
+ def send(request)
88
+ request_signature = Plugin.build_webmock_request_signature(request)
89
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
90
+
91
+ if (mock_response = WebMock::StubRegistry.instance.response_for_request(request_signature))
92
+ response = Plugin.build_from_webmock_response(request, mock_response)
93
+ WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, request_signature, mock_response)
94
+ log { "mocking #{request.uri} with #{mock_response.inspect}" }
95
+ request.response = response
96
+ request.emit(:response, response)
97
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
98
+ if WebMock::CallbackRegistry.any_callbacks?
99
+ request.on(:response) do |resp|
100
+ unless resp.is_a?(HTTPX::ErrorResponse)
101
+ webmock_response = Plugin.build_webmock_response(request, resp)
102
+ WebMock::CallbackRegistry.invoke_callbacks(
103
+ { lib: :httpx, real_request: true }, request_signature,
104
+ webmock_response
105
+ )
106
+ end
107
+ end
108
+ end
109
+ @mocked = false
110
+ emit(:unmock_connection, self)
111
+ super
112
+ else
113
+ raise WebMock::NetConnectNotAllowedError, request_signature
114
+ end
115
+ end
116
+ end
105
117
  end
106
118
 
107
119
  class HttpxAdapter < HttpLibAdapter
@@ -109,12 +121,12 @@ module WebMock
109
121
 
110
122
  class << self
111
123
  def enable!
112
- @original_session = ::HTTPX::Session
124
+ @original_session = HTTPX::Session
113
125
 
114
- webmock_session = ::HTTPX.plugin(Plugin)
126
+ webmock_session = HTTPX.plugin(Plugin)
115
127
 
116
- ::HTTPX.send(:remove_const, :Session)
117
- ::HTTPX.send(:const_set, :Session, webmock_session.class)
128
+ HTTPX.send(:remove_const, :Session)
129
+ HTTPX.send(:const_set, :Session, webmock_session.class)
118
130
  end
119
131
 
120
132
  def disable!
data/lib/httpx/altsvc.rb CHANGED
@@ -70,7 +70,7 @@ module HTTPX
70
70
 
71
71
  scanner = StringScanner.new(altsvc)
72
72
  until scanner.eos?
73
- alt_origin = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
73
+ alt_service = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
74
74
 
75
75
  alt_params = []
76
76
  loop do
@@ -80,29 +80,45 @@ module HTTPX
80
80
  break if scanner.eos? || scanner.scan(/ *, */)
81
81
  end
82
82
  alt_params = Hash[alt_params.map { |field| field.split("=") }]
83
- yield(parse_altsvc_origin(alt_origin), alt_params)
83
+
84
+ alt_proto, alt_authority = alt_service.split("=")
85
+ alt_origin = parse_altsvc_origin(alt_proto, alt_authority)
86
+ return unless alt_origin
87
+
88
+ yield(alt_origin, alt_params.merge("proto" => alt_proto))
89
+ end
90
+ end
91
+
92
+ def parse_altsvc_scheme(alt_proto)
93
+ case alt_proto
94
+ when "h2c"
95
+ "http"
96
+ when "h2"
97
+ "https"
84
98
  end
85
99
  end
86
100
 
87
101
  # :nocov:
88
102
  if RUBY_VERSION < "2.2"
89
- def parse_altsvc_origin(alt_origin)
90
- alt_proto, alt_origin = alt_origin.split("=")
103
+ def parse_altsvc_origin(alt_proto, alt_origin)
104
+ alt_scheme = parse_altsvc_scheme(alt_proto) or return
105
+
91
106
  alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
92
107
  if alt_origin.start_with?(":")
93
- alt_origin = "#{alt_proto}://dummy#{alt_origin}"
108
+ alt_origin = "#{alt_scheme}://dummy#{alt_origin}"
94
109
  uri = URI.parse(alt_origin)
95
110
  uri.host = nil
96
111
  uri
97
112
  else
98
- URI.parse("#{alt_proto}://#{alt_origin}")
113
+ URI.parse("#{alt_scheme}://#{alt_origin}")
99
114
  end
100
115
  end
101
116
  else
102
- def parse_altsvc_origin(alt_origin)
103
- alt_proto, alt_origin = alt_origin.split("=")
117
+ def parse_altsvc_origin(alt_proto, alt_origin)
118
+ alt_scheme = parse_altsvc_scheme(alt_proto) or return
104
119
  alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
105
- URI.parse("#{alt_proto}://#{alt_origin}")
120
+
121
+ URI.parse("#{alt_scheme}://#{alt_origin}")
106
122
  end
107
123
  end
108
124
  # :nocov:
@@ -184,7 +184,7 @@ module HTTPX
184
184
  end
185
185
 
186
186
  if @pipelining
187
- disable
187
+ catch(:called) { disable }
188
188
  else
189
189
  @requests.each do |request|
190
190
  emit(:error, request, ex)
@@ -220,6 +220,12 @@ module HTTPX
220
220
 
221
221
  def join_headers(stream, request)
222
222
  extra_headers = set_protocol_headers(request)
223
+
224
+ if request.headers.key?("host")
225
+ log { "forbidden \"host\" header found (#{request.headers["host"]}), will use it as authority..." }
226
+ extra_headers[":authority"] = request.headers["host"]
227
+ end
228
+
223
229
  log(level: 1, color: :yellow) do
224
230
  request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
225
231
  end
data/lib/httpx/io/udp.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "socket"
4
3
  require "ipaddr"
5
4
 
6
5
  module HTTPX
@@ -8,11 +8,21 @@ module HTTPX
8
8
  DEFAULT_MIMETYPE = "application/octet-stream"
9
9
 
10
10
  # inspired by https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/determine_mime_type.rb
11
- if defined?(MIME::Types)
11
+ if defined?(FileMagic)
12
+ MAGIC_NUMBER = 256 * 1024
12
13
 
13
- def call(_file, filename)
14
- mime = MIME::Types.of(filename).first
15
- mime.content_type if mime
14
+ def call(file, _)
15
+ return nil if file.eof? # FileMagic returns "application/x-empty" for empty files
16
+
17
+ FileMagic.open(FileMagic::MAGIC_MIME_TYPE) do |filemagic|
18
+ filemagic.buffer(file.read(MAGIC_NUMBER))
19
+ end
20
+ end
21
+ elsif defined?(Marcel)
22
+ def call(file, filename)
23
+ return nil if file.eof? # marcel returns "application/octet-stream" for empty files
24
+
25
+ Marcel::MimeType.for(file, name: filename)
16
26
  end
17
27
 
18
28
  elsif defined?(MimeMagic)
@@ -138,10 +138,20 @@ module HTTPX
138
138
  error = response.error
139
139
  case error
140
140
  when NativeResolveError
141
+ return false unless @_proxy_uris && !@_proxy_uris.empty?
142
+
143
+ proxy_uri = URI(@_proxy_uris.first)
144
+
145
+ origin = error.connection.origin
146
+
141
147
  # failed resolving proxy domain
142
- error.connection.origin.to_s == @_proxy_uris.first
148
+ origin.host == proxy_uri.host && origin.port == proxy_uri.port
143
149
  when ResolveError
144
- error.message.end_with?(@_proxy_uris.first)
150
+ return false unless @_proxy_uris && !@_proxy_uris.empty?
151
+
152
+ proxy_uri = URI(@_proxy_uris.first)
153
+
154
+ error.message.end_with?(proxy_uri.to_s)
145
155
  when *PROXY_ERRORS
146
156
  # timeout errors connecting to proxy
147
157
  true
@@ -160,7 +170,9 @@ module HTTPX
160
170
 
161
171
  # redefining the connection origin as the proxy's URI,
162
172
  # as this will be used as the tcp peer ip.
163
- @origin = URI(@options.proxy.uri.origin)
173
+ proxy_uri = URI(@options.proxy.uri)
174
+ @origin.host = proxy_uri.host
175
+ @origin.port = proxy_uri.port
164
176
  end
165
177
 
166
178
  def match?(uri, options)
data/lib/httpx/request.rb CHANGED
@@ -285,7 +285,7 @@ module HTTPX
285
285
  end
286
286
 
287
287
  def write(data)
288
- @block.call(data)
288
+ @block.call(data.dup)
289
289
  data.bytesize
290
290
  end
291
291
  end
data/lib/httpx/session.rb CHANGED
@@ -163,7 +163,7 @@ module HTTPX
163
163
  case uri.scheme
164
164
  when "http"
165
165
  "tcp"
166
- when "https", "h2"
166
+ when "https"
167
167
  "ssl"
168
168
  else
169
169
  raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.18.2"
4
+ VERSION = "0.18.6"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.2
4
+ version: 0.18.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-09 00:00:00.000000000 Z
11
+ date: 2022-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -65,6 +65,10 @@ extra_rdoc_files:
65
65
  - doc/release_notes/0_18_0.md
66
66
  - doc/release_notes/0_18_1.md
67
67
  - doc/release_notes/0_18_2.md
68
+ - doc/release_notes/0_18_3.md
69
+ - doc/release_notes/0_18_4.md
70
+ - doc/release_notes/0_18_5.md
71
+ - doc/release_notes/0_18_6.md
68
72
  - doc/release_notes/0_1_0.md
69
73
  - doc/release_notes/0_2_0.md
70
74
  - doc/release_notes/0_2_1.md
@@ -123,6 +127,10 @@ files:
123
127
  - doc/release_notes/0_18_0.md
124
128
  - doc/release_notes/0_18_1.md
125
129
  - doc/release_notes/0_18_2.md
130
+ - doc/release_notes/0_18_3.md
131
+ - doc/release_notes/0_18_4.md
132
+ - doc/release_notes/0_18_5.md
133
+ - doc/release_notes/0_18_6.md
126
134
  - doc/release_notes/0_1_0.md
127
135
  - doc/release_notes/0_2_0.md
128
136
  - doc/release_notes/0_2_1.md
@@ -320,7 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
320
328
  - !ruby/object:Gem::Version
321
329
  version: '0'
322
330
  requirements: []
323
- rubygems_version: 3.2.22
331
+ rubygems_version: 3.2.32
324
332
  signing_key:
325
333
  specification_version: 4
326
334
  summary: HTTPX, to the future, and beyond