httpx 0.18.0 → 0.18.4

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: 372f89cdf05727a32d23467503299ae77831cb5d23356467dc0c7f3a22bbfd0c
4
- data.tar.gz: bbb3df9079bf4f5a449f37f34dd6b1d3208a978e8a8eaa7fdd69ea9d1829ac6c
3
+ metadata.gz: 4c6e05e5bda153614ac00aac76cdaff0d5d727c58a99fb2297c545aa54b27c0e
4
+ data.tar.gz: 9d682e4136c3e8a3d769e02f96209a72dadbb5722a01d4ec8267cec06b5f1a3a
5
5
  SHA512:
6
- metadata.gz: 38211af1e56fbc823ad0780198579ff11d4fa4b5b64677af95e78361f8b274d0dc43b124697358f1a0b17c2d45e4cdd620f97ca983af7fa60ce59515e9b51785
7
- data.tar.gz: 2c9d48f3ad7a499046ab94ac3ee1831f1623ce44990de0f08320ac6e5ce3767ca938381cd7b96c3799477f201a811aa5c446d62f88bd58c6e1e0afd84c53af75
6
+ metadata.gz: f2549dd8e2b9786dfb6916f88bd9979e06fc32806a09ead664f97d67612eb3d4b182e4a48d1b23f037e899ecda50ad36b343e85e97258fd3ecb730f63238417e
7
+ data.tar.gz: f0388924dfba2717069b9cea8463e658d6262c6bfb874185c60baaab1650055fe4a98acd9fa39a6a060f00aa0aa05f1345d627bac138ecf5e17ad4d8aacd92b2
data/README.md CHANGED
@@ -45,7 +45,7 @@ response = HTTPX.get("https://nghttp2.org")
45
45
  puts response.status #=> 200
46
46
  body = response.body
47
47
  puts body #=> #<HTTPX::Response ...
48
- ```
48
+ ```
49
49
 
50
50
  You can also send as many requests as you want simultaneously:
51
51
 
@@ -79,7 +79,7 @@ In Ruby, HTTP client implementations are a known cheap commodity. Why this one?
79
79
 
80
80
  ### Concurrency
81
81
 
82
- This library supports HTTP/2 seamlessly (which means, if the request is secure, and the server support ALPN negotiation AND HTTP/2, the request will be made through HTTP/2). If you pass multiple URIs, and they can utilize the same connection, they will run concurrently in it.
82
+ This library supports HTTP/2 seamlessly (which means, if the request is secure, and the server support ALPN negotiation AND HTTP/2, the request will be made through HTTP/2). If you pass multiple URIs, and they can utilize the same connection, they will run concurrently in it.
83
83
 
84
84
  However if the server supports HTTP/1.1, it will try to use HTTP pipelining, falling back to 1 request at a time if the server doesn't support it (if the server support Keep-Alive connections, it will reuse the same connection).
85
85
 
@@ -137,7 +137,8 @@ In order to use HTTP/2 under JRuby, [check this link](https://gitlab.com/honeyry
137
137
 
138
138
  ### Known bugs
139
139
 
140
- Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryderchuck/httpx/issues/36)).
140
+ * Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryderchuck/httpx/issues/36)).
141
+ * Using `total_timeout` along with the `:persistent` plugin [does not work as you might expect](https://gitlab.com/honeyryderchuck/httpx/-/wikis/Timeouts#total_timeout).
141
142
 
142
143
  ## Contributing
143
144
 
@@ -0,0 +1,12 @@
1
+ # 0.18.1
2
+
3
+ ## Bugfixes
4
+
5
+ * HTTP/1.1 pipelining logs were logging the previously-buffered requests all together for each triggered request, which created some confusion for users when reporting errors. This has been fixed.
6
+ * HTTP/2 coalescing is now skipped when performing TLS connections with VERIFY_NONE.
7
+ * HTTP/2 peer GOAWAY frames will now result in a (retryable) connection error, instead of being ignored and leaving a "ghost" connection behind.
8
+ * fixed total timeout call which was not raising the exception.
9
+
10
+ ## Chore
11
+
12
+ This gem now requires MFA-based gem releases.
@@ -0,0 +1,10 @@
1
+ # 0.18.2
2
+
3
+ ## Bugfixes
4
+
5
+ * A bug was reported and fixed, whereby a persistent connection with a `:total_timeout` set was triggering the timeout and leaving the process looping indefinitely.
6
+
7
+
8
+ ## Chore
9
+
10
+ The quirk of using the `:persistent` plugin with `:total_timeout` has been documented: https://gitlab.com/honeyryderchuck/httpx/-/wikis/Timeouts#total_timeout.
@@ -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 where 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 innacurate 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 testinng against it).
@@ -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:
@@ -36,6 +36,8 @@ module HTTPX
36
36
 
37
37
  request = @requests.first
38
38
 
39
+ return unless request
40
+
39
41
  return :w if request.interests == :w || !@buffer.empty?
40
42
 
41
43
  :r
@@ -313,8 +315,9 @@ module HTTPX
313
315
  end
314
316
 
315
317
  def join_headers(request)
316
- @buffer << "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}" << CRLF
317
- log(color: :yellow) { "<- HEADLINE: #{@buffer.to_s.chomp.inspect}" }
318
+ headline = "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}"
319
+ @buffer << headline << CRLF
320
+ log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
318
321
  extra_headers = set_protocol_headers(request)
319
322
  join_headers2(request.headers.each(extra_headers))
320
323
  log { "<- " }
@@ -16,6 +16,12 @@ module HTTPX
16
16
  end
17
17
  end
18
18
 
19
+ class GoawayError < Error
20
+ def initialize
21
+ super(0, :no_error)
22
+ end
23
+ end
24
+
19
25
  attr_reader :streams, :pending
20
26
 
21
27
  def initialize(buffer, options)
@@ -302,7 +308,7 @@ module HTTPX
302
308
  @drains.delete(request)
303
309
  @streams.delete(request)
304
310
 
305
- if error && error != :no_error
311
+ if error
306
312
  ex = Error.new(stream.id, error)
307
313
  ex.set_backtrace(caller)
308
314
  response = ErrorResponse.new(request, ex, request.options)
@@ -344,9 +350,16 @@ module HTTPX
344
350
 
345
351
  def on_close(_last_frame, error, _payload)
346
352
  is_connection_closed = @connection.state == :closed
347
- if error && error != :no_error
353
+ if error
348
354
  @buffer.clear if is_connection_closed
349
- ex = Error.new(0, error)
355
+ if error == :no_error
356
+ ex = GoawayError.new
357
+ @pending.unshift(*@streams.keys)
358
+ @drains.clear
359
+ @streams.clear
360
+ else
361
+ ex = Error.new(0, error)
362
+ end
350
363
  ex.set_backtrace(caller)
351
364
  handle_error(ex)
352
365
  end
@@ -117,7 +117,8 @@ module HTTPX
117
117
  def coalescable?(connection)
118
118
  if @io.protocol == "h2" &&
119
119
  @origin.scheme == "https" &&
120
- connection.origin.scheme == "https"
120
+ connection.origin.scheme == "https" &&
121
+ @io.can_verify_peer?
121
122
  @io.verify_hostname(connection.origin.host)
122
123
  else
123
124
  @origin == connection.origin
@@ -241,7 +242,7 @@ module HTTPX
241
242
  if elapsed_time.negative?
242
243
  ex = TotalTimeoutError.new(@total_timeout, "Timed out after #{@total_timeout} seconds")
243
244
  ex.set_backtrace(caller)
244
- on_error(@total_timeout)
245
+ on_error(ex)
245
246
  return
246
247
  end
247
248
 
@@ -463,6 +464,7 @@ module HTTPX
463
464
  transition(:closing)
464
465
  transition(:closed)
465
466
  emit(:reset)
467
+
466
468
  @parser.reset if @parser
467
469
  transition(:idle)
468
470
  transition(:open)
data/lib/httpx/io/ssl.rb CHANGED
@@ -27,6 +27,10 @@ module HTTPX
27
27
  super
28
28
  end
29
29
 
30
+ def can_verify_peer?
31
+ @ctx.verify_mode == OpenSSL::SSL::VERIFY_PEER
32
+ end
33
+
30
34
  def verify_hostname(host)
31
35
  return false if @ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
32
36
  return false if !@io.respond_to?(:peer_cert) || @io.peer_cert.nil?
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,19 @@ 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
+ def call(file, _)
13
+ return nil if file.eof? # FileMagic returns "application/x-empty" for empty files
14
+
15
+ FileMagic.open(FileMagic::MAGIC_MIME_TYPE) do |filemagic|
16
+ filemagic.buffer(file.read(MAGIC_NUMBER))
17
+ end
18
+ end
19
+ elsif defined?(Marcel)
20
+ def call(file, filename)
21
+ return nil if file.eof? # marcel returns "application/octet-stream" for empty files
12
22
 
13
- def call(_file, filename)
14
- mime = MIME::Types.of(filename).first
15
- mime.content_type if mime
23
+ Marcel::MimeType.for(file, name: filename)
16
24
  end
17
25
 
18
26
  elsif defined?(MimeMagic)
@@ -12,16 +12,19 @@ module HTTPX
12
12
  # TODO: pass max_retries in a configure/load block
13
13
 
14
14
  IDEMPOTENT_METHODS = %i[get options head put delete].freeze
15
- RETRYABLE_ERRORS = [IOError,
16
- EOFError,
17
- Errno::ECONNRESET,
18
- Errno::ECONNABORTED,
19
- Errno::EPIPE,
20
- TLSError,
21
- TimeoutError,
22
- Parser::Error,
23
- Errno::EINVAL,
24
- Errno::ETIMEDOUT].freeze
15
+ RETRYABLE_ERRORS = [
16
+ IOError,
17
+ EOFError,
18
+ Errno::ECONNRESET,
19
+ Errno::ECONNABORTED,
20
+ Errno::EPIPE,
21
+ Errno::EINVAL,
22
+ Errno::ETIMEDOUT,
23
+ Parser::Error,
24
+ TLSError,
25
+ TimeoutError,
26
+ Connection::HTTP2::GoawayError,
27
+ ].freeze
25
28
  DEFAULT_JITTER = ->(interval) { interval * (0.5 * (1 + rand)) }
26
29
 
27
30
  if ENV.key?("HTTPX_NO_JITTER")
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
@@ -117,6 +117,13 @@ class HTTPX::Selector
117
117
  end
118
118
 
119
119
  def select(interval, &block)
120
+ # do not cause an infinite loop here.
121
+ #
122
+ # this may happen if timeout calculation actually triggered an error which causes
123
+ # the connections to be reaped (such as the total timeout error) before #select
124
+ # gets called.
125
+ return if interval.nil? && @selectables.empty?
126
+
120
127
  return select_one(interval, &block) if @selectables.size == 1
121
128
 
122
129
  select_many(interval, &block)
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.0"
4
+ VERSION = "0.18.4"
5
5
  end
@@ -15,6 +15,11 @@ module HTTPX
15
15
  @max_requests: Integer
16
16
  @parser: Parser::HTTP1
17
17
  @buffer: Buffer
18
+ @version: [Integer, Integer]
19
+ @handshake_completed: bool
20
+ @pipelining: bool
21
+
22
+ @request: Request?
18
23
 
19
24
  def interests: () -> io_interests?
20
25
 
@@ -92,5 +92,8 @@ module HTTPX
92
92
 
93
93
  class Error < ::HTTPX::Error
94
94
  end
95
+
96
+ class GoawayError < Error
97
+ end
95
98
  end
96
99
  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.0
4
+ version: 0.18.4
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-10-02 00:00:00.000000000 Z
11
+ date: 2021-12-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -63,6 +63,10 @@ extra_rdoc_files:
63
63
  - doc/release_notes/0_16_1.md
64
64
  - doc/release_notes/0_17_0.md
65
65
  - doc/release_notes/0_18_0.md
66
+ - doc/release_notes/0_18_1.md
67
+ - doc/release_notes/0_18_2.md
68
+ - doc/release_notes/0_18_3.md
69
+ - doc/release_notes/0_18_4.md
66
70
  - doc/release_notes/0_1_0.md
67
71
  - doc/release_notes/0_2_0.md
68
72
  - doc/release_notes/0_2_1.md
@@ -119,6 +123,10 @@ files:
119
123
  - doc/release_notes/0_16_1.md
120
124
  - doc/release_notes/0_17_0.md
121
125
  - doc/release_notes/0_18_0.md
126
+ - doc/release_notes/0_18_1.md
127
+ - doc/release_notes/0_18_2.md
128
+ - doc/release_notes/0_18_3.md
129
+ - doc/release_notes/0_18_4.md
122
130
  - doc/release_notes/0_1_0.md
123
131
  - doc/release_notes/0_2_0.md
124
132
  - doc/release_notes/0_2_1.md
@@ -300,6 +308,7 @@ metadata:
300
308
  documentation_uri: https://honeyryderchuck.gitlab.io/httpx/rdoc/
301
309
  source_code_uri: https://gitlab.com/honeyryderchuck/httpx
302
310
  homepage_uri: https://honeyryderchuck.gitlab.io/httpx/
311
+ rubygems_mfa_required: 'true'
303
312
  post_install_message:
304
313
  rdoc_options: []
305
314
  require_paths:
@@ -315,7 +324,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
315
324
  - !ruby/object:Gem::Version
316
325
  version: '0'
317
326
  requirements: []
318
- rubygems_version: 3.2.22
327
+ rubygems_version: 3.3.3
319
328
  signing_key:
320
329
  specification_version: 4
321
330
  summary: HTTPX, to the future, and beyond