httpx 0.11.3 → 0.14.0

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.
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