httpx 0.11.3 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/0_10_1.md +1 -1
  4. data/doc/release_notes/0_11_1.md +5 -1
  5. data/doc/release_notes/0_12_0.md +55 -0
  6. data/doc/release_notes/0_13_0.md +58 -0
  7. data/doc/release_notes/0_13_1.md +5 -0
  8. data/doc/release_notes/0_13_2.md +9 -0
  9. data/doc/release_notes/0_14_0.md +79 -0
  10. data/lib/httpx.rb +3 -3
  11. data/lib/httpx/adapters/faraday.rb +4 -6
  12. data/lib/httpx/altsvc.rb +1 -0
  13. data/lib/httpx/callbacks.rb +12 -3
  14. data/lib/httpx/chainable.rb +2 -2
  15. data/lib/httpx/connection.rb +92 -37
  16. data/lib/httpx/connection/http1.rb +37 -19
  17. data/lib/httpx/connection/http2.rb +82 -31
  18. data/lib/httpx/headers.rb +1 -1
  19. data/lib/httpx/io.rb +16 -3
  20. data/lib/httpx/io/ssl.rb +35 -24
  21. data/lib/httpx/io/tcp.rb +50 -28
  22. data/lib/httpx/io/tls.rb +218 -0
  23. data/lib/httpx/io/tls/box.rb +365 -0
  24. data/lib/httpx/io/tls/context.rb +199 -0
  25. data/lib/httpx/io/tls/ffi.rb +390 -0
  26. data/lib/httpx/io/udp.rb +31 -7
  27. data/lib/httpx/io/unix.rb +27 -12
  28. data/lib/httpx/options.rb +97 -74
  29. data/lib/httpx/parser/http1.rb +4 -4
  30. data/lib/httpx/plugins/aws_sdk_authentication.rb +84 -0
  31. data/lib/httpx/plugins/aws_sigv4.rb +219 -0
  32. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  33. data/lib/httpx/plugins/compression.rb +24 -12
  34. data/lib/httpx/plugins/compression/brotli.rb +10 -7
  35. data/lib/httpx/plugins/compression/deflate.rb +8 -10
  36. data/lib/httpx/plugins/compression/gzip.rb +4 -3
  37. data/lib/httpx/plugins/cookies.rb +3 -7
  38. data/lib/httpx/plugins/digest_authentication.rb +5 -5
  39. data/lib/httpx/plugins/expect.rb +6 -6
  40. data/lib/httpx/plugins/follow_redirects.rb +4 -4
  41. data/lib/httpx/plugins/grpc.rb +247 -0
  42. data/lib/httpx/plugins/grpc/call.rb +62 -0
  43. data/lib/httpx/plugins/grpc/message.rb +85 -0
  44. data/lib/httpx/plugins/h2c.rb +43 -58
  45. data/lib/httpx/plugins/internal_telemetry.rb +93 -0
  46. data/lib/httpx/plugins/multipart.rb +2 -0
  47. data/lib/httpx/plugins/multipart/encoder.rb +4 -9
  48. data/lib/httpx/plugins/multipart/part.rb +1 -1
  49. data/lib/httpx/plugins/proxy.rb +4 -8
  50. data/lib/httpx/plugins/proxy/http.rb +1 -1
  51. data/lib/httpx/plugins/proxy/socks4.rb +8 -0
  52. data/lib/httpx/plugins/proxy/socks5.rb +8 -0
  53. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  54. data/lib/httpx/plugins/push_promise.rb +3 -2
  55. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  56. data/lib/httpx/plugins/retries.rb +15 -16
  57. data/lib/httpx/plugins/stream.rb +99 -77
  58. data/lib/httpx/plugins/upgrade.rb +84 -0
  59. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  60. data/lib/httpx/pool.rb +14 -6
  61. data/lib/httpx/registry.rb +1 -7
  62. data/lib/httpx/request.rb +36 -3
  63. data/lib/httpx/resolver/https.rb +3 -11
  64. data/lib/httpx/resolver/native.rb +7 -3
  65. data/lib/httpx/response.rb +18 -7
  66. data/lib/httpx/selector.rb +5 -0
  67. data/lib/httpx/session.rb +41 -8
  68. data/lib/httpx/transcoder/body.rb +3 -5
  69. data/lib/httpx/transcoder/chunker.rb +1 -1
  70. data/lib/httpx/version.rb +1 -1
  71. data/sig/callbacks.rbs +2 -0
  72. data/sig/chainable.rbs +2 -1
  73. data/sig/connection/http1.rbs +7 -2
  74. data/sig/connection/http2.rbs +10 -4
  75. data/sig/options.rbs +16 -22
  76. data/sig/plugins/aws_sdk_authentication.rbs +19 -0
  77. data/sig/plugins/aws_sigv4.rbs +64 -0
  78. data/sig/plugins/basic_authentication.rbs +2 -0
  79. data/sig/plugins/compression.rbs +7 -5
  80. data/sig/plugins/compression/brotli.rbs +1 -1
  81. data/sig/plugins/compression/deflate.rbs +1 -1
  82. data/sig/plugins/compression/gzip.rbs +1 -1
  83. data/sig/plugins/cookies.rbs +0 -1
  84. data/sig/plugins/digest_authentication.rbs +0 -1
  85. data/sig/plugins/expect.rbs +0 -2
  86. data/sig/plugins/follow_redirects.rbs +0 -2
  87. data/sig/plugins/h2c.rbs +5 -10
  88. data/sig/plugins/persistent.rbs +0 -1
  89. data/sig/plugins/proxy.rbs +0 -1
  90. data/sig/plugins/push_promise.rbs +1 -1
  91. data/sig/plugins/retries.rbs +0 -4
  92. data/sig/plugins/stream.rbs +17 -16
  93. data/sig/plugins/upgrade.rbs +23 -0
  94. data/sig/request.rbs +7 -2
  95. data/sig/response.rbs +4 -1
  96. data/sig/session.rbs +4 -0
  97. metadata +56 -33
  98. data/lib/httpx/timeout.rb +0 -67
  99. data/sig/timeout.rbs +0 -29
@@ -10,7 +10,7 @@ module HTTPX
10
10
  MAX_REQUESTS = 100
11
11
  CRLF = "\r\n"
12
12
 
13
- attr_reader :pending
13
+ attr_reader :pending, :requests
14
14
 
15
15
  def initialize(buffer, options)
16
16
  @options = Options.new(options)
@@ -74,9 +74,10 @@ module HTTPX
74
74
  end
75
75
 
76
76
  def consume
77
- requests_limit = [@max_concurrent_requests, @max_requests, @requests.size].min
77
+ requests_limit = [@max_requests, @requests.size].min
78
+ concurrent_requests_limit = [@max_concurrent_requests, requests_limit].min
78
79
  @requests.each_with_index do |request, idx|
79
- break if idx >= requests_limit
80
+ break if idx >= concurrent_requests_limit
80
81
  next if request.state == :done
81
82
 
82
83
  request.headers["connection"] ||= request.options.persistent || idx < requests_limit - 1 ? "keep-alive" : "close"
@@ -115,7 +116,7 @@ module HTTPX
115
116
  response = @request.response
116
117
  log(level: 2) { "trailer headers received" }
117
118
 
118
- log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") }
119
+ log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v.join(", ")}" }.join("\n") }
119
120
  response.merge_headers(h)
120
121
  end
121
122
 
@@ -161,13 +162,13 @@ module HTTPX
161
162
  end
162
163
 
163
164
  def handle_error(ex)
164
- if ex.is_a?(EOFError) && @request && @request.response &&
165
+ if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request && @request.response &&
165
166
  !@request.response.headers.key?("content-length") &&
166
167
  !@request.response.headers.key?("transfer-encoding")
167
168
  # if the response does not contain a content-length header, the server closing the
168
169
  # connnection is the indicator of response consumed.
169
170
  # https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.4
170
- on_complete
171
+ catch(:called) { on_complete }
171
172
  return
172
173
  end
173
174
 
@@ -234,6 +235,8 @@ module HTTPX
234
235
 
235
236
  def disable_pipelining
236
237
  return if @requests.empty?
238
+ # do not disable pipelining if already set to 1 request at a time
239
+ return if @max_concurrent_requests == 1
237
240
 
238
241
  @requests.each do |r|
239
242
  r.transition(:idle)
@@ -250,7 +253,7 @@ module HTTPX
250
253
  @pipelining = false
251
254
  end
252
255
 
253
- def set_request_headers(request)
256
+ def set_protocol_headers(request)
254
257
  request.headers["host"] ||= request.authority
255
258
  request.headers["connection"] ||= request.options.persistent ? "keep-alive" : "close"
256
259
  if !request.headers.key?("content-length") &&
@@ -264,28 +267,23 @@ module HTTPX
264
267
  end
265
268
 
266
269
  def handle(request)
267
- set_request_headers(request)
268
270
  catch(:buffer_full) do
269
271
  request.transition(:headers)
270
272
  join_headers(request) if request.state == :headers
271
273
  request.transition(:body)
272
274
  join_body(request) if request.state == :body
275
+ request.transition(:trailers)
276
+ # HTTP/1.1 trailers should only work for chunked encoding
277
+ join_trailers(request) if request.body.chunked? && request.state == :trailers
273
278
  request.transition(:done)
274
279
  end
275
280
  end
276
281
 
277
282
  def join_headers(request)
278
- buffer = +""
279
- buffer << "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}" << CRLF
280
- log(color: :yellow) { "<- HEADLINE: #{buffer.chomp.inspect}" }
281
- @buffer << buffer
282
- buffer.clear
283
- request.headers.each do |field, value|
284
- buffer << "#{capitalized(field)}: #{value}" << CRLF
285
- log(color: :yellow) { "<- HEADER: #{buffer.chomp}" }
286
- @buffer << buffer
287
- buffer.clear
288
- end
283
+ @buffer << "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}" << CRLF
284
+ log(color: :yellow) { "<- HEADLINE: #{@buffer.to_s.chomp.inspect}" }
285
+ set_protocol_headers(request)
286
+ join_headers2(request.headers)
289
287
  log { "<- " }
290
288
  @buffer << CRLF
291
289
  end
@@ -299,6 +297,26 @@ module HTTPX
299
297
  @buffer << chunk
300
298
  throw(:buffer_full, request) if @buffer.full?
301
299
  end
300
+
301
+ raise request.drain_error if request.drain_error
302
+ end
303
+
304
+ def join_trailers(request)
305
+ return unless request.trailers? && request.callbacks_for?(:trailers)
306
+
307
+ join_headers2(request.trailers)
308
+ log { "<- " }
309
+ @buffer << CRLF
310
+ end
311
+
312
+ def join_headers2(headers)
313
+ buffer = "".b
314
+ headers.each do |field, value|
315
+ buffer << "#{capitalized(field)}: #{value}" << CRLF
316
+ log(color: :yellow) { "<- HEADER: #{buffer.chomp}" }
317
+ @buffer << buffer
318
+ buffer.clear
319
+ end
302
320
  end
303
321
 
304
322
  UPCASED = {
@@ -21,14 +21,16 @@ module HTTPX
21
21
 
22
22
  def initialize(buffer, options)
23
23
  @options = Options.new(options)
24
- @max_concurrent_requests = @options.max_concurrent_requests || MAX_CONCURRENT_REQUESTS
25
- @max_requests = @options.max_requests || 0
24
+ @settings = @options.http2_settings
26
25
  @pending = []
27
26
  @streams = {}
28
27
  @drains = {}
29
28
  @pings = []
30
29
  @buffer = buffer
31
30
  @handshake_completed = false
31
+ @wait_for_handshake = @settings.key?(:wait_for_handshake) ? @settings.delete(:wait_for_handshake) : true
32
+ @max_concurrent_requests = @options.max_concurrent_requests || MAX_CONCURRENT_REQUESTS
33
+ @max_requests = @options.max_requests || 0
32
34
  init_connection
33
35
  end
34
36
 
@@ -36,17 +38,25 @@ module HTTPX
36
38
  # waiting for WINDOW_UPDATE frames
37
39
  return :r if @buffer.full?
38
40
 
39
- return :w if @connection.state == :closed
41
+ if @connection.state == :closed
42
+ return unless @handshake_completed
43
+
44
+ return :w
45
+ end
40
46
 
41
47
  unless (@connection.state == :connected && @handshake_completed)
42
48
  return @buffer.empty? ? :r : :rw
43
49
  end
44
50
 
45
- return :w unless @pending.empty?
51
+ return :w if !@pending.empty? && can_buffer_more_requests?
46
52
 
47
53
  return :w if @streams.each_key.any? { |r| r.interests == :w }
48
54
 
49
- return :r if @buffer.empty?
55
+ if @buffer.empty?
56
+ return if @streams.empty? && @pings.empty?
57
+
58
+ return :r
59
+ end
50
60
 
51
61
  :rw
52
62
  end
@@ -70,10 +80,18 @@ module HTTPX
70
80
  @connection << data
71
81
  end
72
82
 
83
+ def can_buffer_more_requests?
84
+ if @handshake_completed
85
+ @streams.size < @max_concurrent_requests &&
86
+ @streams.size < @max_requests
87
+ else
88
+ !@wait_for_handshake &&
89
+ @streams.size < @max_concurrent_requests
90
+ end
91
+ end
92
+
73
93
  def send(request)
74
- if !@handshake_completed ||
75
- @streams.size >= @max_concurrent_requests ||
76
- @streams.size >= @max_requests
94
+ unless can_buffer_more_requests?
77
95
  @pending << request
78
96
  return
79
97
  end
@@ -126,20 +144,20 @@ module HTTPX
126
144
  request.path
127
145
  end
128
146
 
129
- def set_request_headers(request); end
130
-
131
147
  def handle(request, stream)
132
148
  catch(:buffer_full) do
133
149
  request.transition(:headers)
134
150
  join_headers(stream, request) if request.state == :headers
135
151
  request.transition(:body)
136
152
  join_body(stream, request) if request.state == :body
153
+ request.transition(:trailers)
154
+ join_trailers(stream, request) if request.state == :trailers && !request.body.empty?
137
155
  request.transition(:done)
138
156
  end
139
157
  end
140
158
 
141
159
  def init_connection
142
- @connection = HTTP2Next::Client.new(@options.http2_settings)
160
+ @connection = HTTP2Next::Client.new(@settings)
143
161
  @connection.max_streams = @max_requests if @connection.respond_to?(:max_streams=) && @max_requests.positive?
144
162
  @connection.on(:frame, &method(:on_frame))
145
163
  @connection.on(:frame_sent, &method(:on_frame_sent))
@@ -163,6 +181,7 @@ module HTTPX
163
181
  public :reset
164
182
 
165
183
  def handle_stream(stream, request)
184
+ request.on(:refuse, &method(:on_stream_refuse).curry(3)[stream, request])
166
185
  stream.on(:close, &method(:on_stream_close).curry(3)[stream, request])
167
186
  stream.on(:half_close) do
168
187
  log(level: 2) { "#{stream.id}: waiting for response..." }
@@ -172,18 +191,31 @@ module HTTPX
172
191
  stream.on(:data, &method(:on_stream_data).curry(3)[stream, request])
173
192
  end
174
193
 
194
+ def set_protocol_headers(request)
195
+ request.headers[":scheme"] = request.scheme
196
+ request.headers[":method"] = request.verb.to_s.upcase
197
+ request.headers[":path"] = headline_uri(request)
198
+ request.headers[":authority"] = request.authority
199
+ end
200
+
175
201
  def join_headers(stream, request)
176
- set_request_headers(request)
177
- headers = {}
178
- headers[":scheme"] = request.scheme
179
- headers[":method"] = request.verb.to_s.upcase
180
- headers[":path"] = headline_uri(request)
181
- headers[":authority"] = request.authority
182
- headers = headers.merge(request.headers)
202
+ set_protocol_headers(request)
203
+ log(level: 1, color: :yellow) do
204
+ request.headers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
205
+ end
206
+ stream.headers(request.headers.each, end_stream: request.empty?)
207
+ end
208
+
209
+ def join_trailers(stream, request)
210
+ unless request.trailers?
211
+ stream.data("", end_stream: true) if request.callbacks_for?(:trailers)
212
+ return
213
+ end
214
+
183
215
  log(level: 1, color: :yellow) do
184
- headers.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
216
+ request.trailers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
185
217
  end
186
- stream.headers(headers, end_stream: request.empty?)
218
+ stream.headers(request.trailers.each, end_stream: true)
187
219
  end
188
220
 
189
221
  def join_body(stream, request)
@@ -194,13 +226,15 @@ module HTTPX
194
226
  next_chunk = request.drain_body
195
227
  log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
196
228
  log(level: 2, color: :green) { "#{stream.id}: -> #{chunk.inspect}" }
197
- stream.data(chunk, end_stream: !next_chunk)
198
- if next_chunk && @buffer.full?
229
+ stream.data(chunk, end_stream: !(next_chunk || request.trailers? || request.callbacks_for?(:trailers)))
230
+ if next_chunk && (@buffer.full? || request.body.unbounded_body?)
199
231
  @drains[request] = next_chunk
200
232
  throw(:buffer_full)
201
233
  end
202
234
  chunk = next_chunk
203
235
  end
236
+
237
+ on_stream_refuse(stream, request, request.drain_error) if request.drain_error
204
238
  end
205
239
 
206
240
  ######
@@ -208,6 +242,11 @@ module HTTPX
208
242
  ######
209
243
 
210
244
  def on_stream_headers(stream, request, h)
245
+ if request.response && request.response.version == "2.0"
246
+ on_stream_trailers(stream, request, h)
247
+ return
248
+ end
249
+
211
250
  log(color: :yellow) do
212
251
  h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{v}" }.join("\n")
213
252
  end
@@ -220,17 +259,34 @@ module HTTPX
220
259
  handle(request, stream) if request.expects?
221
260
  end
222
261
 
262
+ def on_stream_trailers(stream, request, h)
263
+ log(color: :yellow) do
264
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{v}" }.join("\n")
265
+ end
266
+ request.response.merge_headers(h)
267
+ end
268
+
223
269
  def on_stream_data(stream, request, data)
224
270
  log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
225
271
  log(level: 2, color: :green) { "#{stream.id}: <- #{data.inspect}" }
226
272
  request.response << data
227
273
  end
228
274
 
275
+ def on_stream_refuse(stream, request, error)
276
+ stream.close
277
+ on_stream_close(stream, request, error)
278
+ end
279
+
229
280
  def on_stream_close(stream, request, error)
281
+ log(level: 2) { "#{stream.id}: closing stream" }
282
+ @drains.delete(request)
283
+ @streams.delete(request)
284
+
230
285
  if error && error != :no_error
231
286
  ex = Error.new(stream.id, error)
232
287
  ex.set_backtrace(caller)
233
- emit(:error, request, ex)
288
+ response = ErrorResponse.new(request, ex, request.options)
289
+ emit(:response, request, response)
234
290
  else
235
291
  response = request.response
236
292
  if response.status == 421
@@ -241,9 +297,6 @@ module HTTPX
241
297
  emit(:response, request, response)
242
298
  end
243
299
  end
244
- log(level: 2) { "#{stream.id}: closing stream" }
245
-
246
- @streams.delete(request)
247
300
  send(@pending.shift) unless @pending.empty?
248
301
  return unless @streams.empty? && exhausted?
249
302
 
@@ -328,11 +381,9 @@ module HTTPX
328
381
  end
329
382
 
330
383
  def method_missing(meth, *args, &blk)
331
- if @connection.respond_to?(meth)
332
- @connection.__send__(meth, *args, &blk)
333
- else
334
- super
335
- end
384
+ return super unless @connection.respond_to?(meth)
385
+
386
+ @connection.__send__(meth, *args, &blk)
336
387
  end
337
388
  end
338
389
  Connection.register "h2", Connection::HTTP2
data/lib/httpx/headers.rb CHANGED
@@ -112,7 +112,7 @@ module HTTPX
112
112
  end
113
113
 
114
114
  def ==(other)
115
- to_hash == Headers.new(other).to_hash
115
+ other == to_hash
116
116
  end
117
117
 
118
118
  # the headers store in Hash format
data/lib/httpx/io.rb CHANGED
@@ -2,16 +2,29 @@
2
2
 
3
3
  require "socket"
4
4
  require "httpx/io/tcp"
5
- require "httpx/io/ssl"
6
5
  require "httpx/io/unix"
7
6
  require "httpx/io/udp"
8
7
 
9
8
  module HTTPX
10
9
  module IO
11
10
  extend Registry
12
- register "tcp", TCP
13
- register "ssl", SSL
14
11
  register "udp", UDP
15
12
  register "unix", HTTPX::UNIX
13
+ register "tcp", TCP
14
+
15
+ if RUBY_ENGINE == "jruby"
16
+ begin
17
+ require "httpx/io/tls"
18
+ register "ssl", TLS
19
+ rescue LoadError
20
+ # :nocov:
21
+ require "httpx/io/ssl"
22
+ register "ssl", SSL
23
+ # :nocov:
24
+ end
25
+ else
26
+ require "httpx/io/ssl"
27
+ register "ssl", SSL
28
+ end
16
29
  end
17
30
  end
data/lib/httpx/io/ssl.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require "openssl"
4
4
 
5
5
  module HTTPX
6
+ TLSError = OpenSSL::SSL::SSLError
7
+
6
8
  class SSL < TCP
7
9
  TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
8
10
  { alpn_protocols: %w[h2 http/1.1] }
@@ -11,19 +13,14 @@ module HTTPX
11
13
  end
12
14
 
13
15
  def initialize(_, _, options)
16
+ super
14
17
  @ctx = OpenSSL::SSL::SSLContext.new
15
18
  ctx_options = TLS_OPTIONS.merge(options.ssl)
16
- @tls_hostname = ctx_options.delete(:hostname)
19
+ @sni_hostname = ctx_options.delete(:hostname) || @hostname
17
20
  @ctx.set_params(ctx_options) unless ctx_options.empty?
18
- super
19
- @tls_hostname ||= @hostname
20
21
  @state = :negotiated if @keep_open
21
22
  end
22
23
 
23
- def interests
24
- @interests || super
25
- end
26
-
27
24
  def protocol
28
25
  @io.alpn_protocol || super
29
26
  rescue StandardError
@@ -50,28 +47,30 @@ module HTTPX
50
47
 
51
48
  def connect
52
49
  super
53
- if @keep_open
54
- @state = :negotiated
55
- return
56
- end
57
50
  return if @state == :negotiated ||
58
51
  @state != :connected
59
52
 
60
53
  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
61
54
  @io = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
62
- @io.hostname = @tls_hostname
55
+ @io.hostname = @sni_hostname
63
56
  @io.sync_close = true
64
57
  end
65
- @io.connect_nonblock
66
- @io.post_connection_check(@tls_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
67
- transition(:negotiated)
68
- rescue ::IO::WaitReadable
69
- @interests = :r
70
- rescue ::IO::WaitWritable
71
- @interests = :w
58
+ try_ssl_connect
72
59
  end
73
60
 
74
61
  if RUBY_VERSION < "2.3"
62
+ # :nocov:
63
+ def try_ssl_connect
64
+ @io.connect_nonblock
65
+ @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
66
+ transition(:negotiated)
67
+ @interests = :w
68
+ rescue ::IO::WaitReadable
69
+ @interests = :r
70
+ rescue ::IO::WaitWritable
71
+ @interests = :w
72
+ end
73
+
75
74
  def read(_, buffer)
76
75
  super
77
76
  rescue ::IO::WaitWritable
@@ -84,7 +83,23 @@ module HTTPX
84
83
  rescue ::IO::WaitReadable
85
84
  0
86
85
  end
86
+ # :nocov:
87
87
  else
88
+ def try_ssl_connect
89
+ case @io.connect_nonblock(exception: false)
90
+ when :wait_readable
91
+ @interests = :r
92
+ return
93
+ when :wait_writable
94
+ @interests = :w
95
+ return
96
+ end
97
+ @io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
98
+ transition(:negotiated)
99
+ @interests = :w
100
+ end
101
+
102
+ # :nocov:
88
103
  if OpenSSL::VERSION < "2.0.6"
89
104
  def read(size, buffer)
90
105
  @io.read_nonblock(size, buffer)
@@ -97,11 +112,7 @@ module HTTPX
97
112
  nil
98
113
  end
99
114
  end
100
- end
101
-
102
- def inspect
103
- id = @io.closed? ? "closed" : @io.to_io.fileno
104
- "#<SSL(fd: #{id}): #{@ip}:#{@port} state: #{@state}>"
115
+ # :nocov:
105
116
  end
106
117
 
107
118
  private