httpx 1.8.3 → 1.9.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_8_4.md +13 -0
  3. data/doc/release_notes/1_9_0.md +75 -0
  4. data/lib/httpx/adapters/webmock.rb +1 -1
  5. data/lib/httpx/altsvc.rb +29 -15
  6. data/lib/httpx/callbacks.rb +2 -2
  7. data/lib/httpx/chainable.rb +6 -3
  8. data/lib/httpx/connection/http1.rb +11 -19
  9. data/lib/httpx/connection/http2.rb +112 -27
  10. data/lib/httpx/connection.rb +184 -46
  11. data/lib/httpx/headers.rb +2 -2
  12. data/lib/httpx/io/tcp.rb +18 -14
  13. data/lib/httpx/options.rb +86 -35
  14. data/lib/httpx/parser/http1.rb +3 -6
  15. data/lib/httpx/plugins/auth/digest.rb +1 -2
  16. data/lib/httpx/plugins/auth.rb +11 -2
  17. data/lib/httpx/plugins/aws_sdk_authentication.rb +7 -3
  18. data/lib/httpx/plugins/aws_sigv4.rb +1 -0
  19. data/lib/httpx/plugins/basic_auth.rb +2 -0
  20. data/lib/httpx/plugins/callbacks.rb +7 -6
  21. data/lib/httpx/plugins/circuit_breaker.rb +1 -0
  22. data/lib/httpx/plugins/cookies/cookie.rb +1 -1
  23. data/lib/httpx/plugins/cookies.rb +2 -2
  24. data/lib/httpx/plugins/digest_auth.rb +2 -0
  25. data/lib/httpx/plugins/expect.rb +2 -0
  26. data/lib/httpx/plugins/fiber_concurrency.rb +6 -0
  27. data/lib/httpx/plugins/follow_redirects.rb +8 -6
  28. data/lib/httpx/plugins/grpc/call.rb +4 -4
  29. data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -2
  30. data/lib/httpx/plugins/grpc.rb +21 -0
  31. data/lib/httpx/plugins/hedging.rb +284 -0
  32. data/lib/httpx/plugins/ntlm_auth.rb +1 -0
  33. data/lib/httpx/plugins/ntlm_v2_auth.rb +1 -0
  34. data/lib/httpx/plugins/oauth.rb +1 -1
  35. data/lib/httpx/plugins/proxy/http.rb +71 -16
  36. data/lib/httpx/plugins/proxy/ssh.rb +30 -11
  37. data/lib/httpx/plugins/proxy.rb +66 -45
  38. data/lib/httpx/plugins/push_promise.rb +4 -2
  39. data/lib/httpx/plugins/retries.rb +13 -3
  40. data/lib/httpx/plugins/server_sent_events.rb +2 -1
  41. data/lib/httpx/plugins/ssrf_filter.rb +1 -1
  42. data/lib/httpx/plugins/stream.rb +2 -2
  43. data/lib/httpx/plugins/tracing.rb +61 -14
  44. data/lib/httpx/plugins/upgrade/h2.rb +1 -1
  45. data/lib/httpx/plugins/upgrade.rb +2 -0
  46. data/lib/httpx/plugins/webdav.rb +8 -0
  47. data/lib/httpx/request/body.rb +2 -1
  48. data/lib/httpx/request.rb +10 -7
  49. data/lib/httpx/resolver/multi.rb +2 -2
  50. data/lib/httpx/resolver/resolver.rb +2 -2
  51. data/lib/httpx/resolver.rb +5 -1
  52. data/lib/httpx/response/body.rb +2 -2
  53. data/lib/httpx/response.rb +19 -11
  54. data/lib/httpx/selector.rb +3 -3
  55. data/lib/httpx/session.rb +38 -11
  56. data/lib/httpx/transcoder/chunker.rb +2 -2
  57. data/lib/httpx/transcoder/json.rb +7 -7
  58. data/lib/httpx/version.rb +1 -1
  59. data/sig/altsvc.rbs +3 -3
  60. data/sig/callbacks.rbs +1 -0
  61. data/sig/chainable.rbs +1 -0
  62. data/sig/connection/http1.rbs +1 -1
  63. data/sig/connection/http2.rbs +30 -16
  64. data/sig/connection.rbs +19 -1
  65. data/sig/headers.rbs +1 -1
  66. data/sig/httpx.rbs +2 -0
  67. data/sig/options.rbs +43 -12
  68. data/sig/parser/http1.rbs +1 -1
  69. data/sig/plugins/auth/digest.rbs +0 -1
  70. data/sig/plugins/auth.rbs +12 -5
  71. data/sig/plugins/hedging.rbs +44 -0
  72. data/sig/plugins/proxy/http.rbs +8 -1
  73. data/sig/plugins/proxy.rbs +3 -2
  74. data/sig/plugins/retries.rbs +2 -2
  75. data/sig/plugins/tracing.rbs +14 -5
  76. data/sig/request/body.rbs +1 -1
  77. data/sig/request.rbs +2 -1
  78. data/sig/resolver/entry.rbs +2 -2
  79. data/sig/response.rbs +1 -2
  80. data/sig/selector.rbs +2 -2
  81. data/sig/session.rbs +7 -5
  82. metadata +7 -1
@@ -51,7 +51,11 @@ module HTTPX
51
51
  @callbacks = @ping_timer = @family =
52
52
  @io = @ssl_session = @timeout = @connected_at = @response_received_at = nil
53
53
 
54
- @exhausted = @cloned = @main_sibling = false
54
+ @exhausted = @cloned = @main_sibling =
55
+ @reset_timeout_on_call =
56
+ # variable used to gate against a potential endless loop where the peer continuously closes the connection with
57
+ # GOAWAY frames without ever processing a request.
58
+ @previously_exhausted_with_error = false
55
59
 
56
60
  @options = Options.new(options)
57
61
  @type = initialize_type(uri, @options)
@@ -62,7 +66,7 @@ module HTTPX
62
66
  @write_buffer = Buffer.new(@options.buffer_size)
63
67
  @pending = []
64
68
  @inflight = 0
65
- @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
69
+ @keep_alive_timeout = @options.timeout.keep_alive_timeout
66
70
  @no_more_requests_counter = 0
67
71
 
68
72
  if @options.io
@@ -74,7 +78,14 @@ module HTTPX
74
78
  else
75
79
  transition(:idle)
76
80
  end
77
- self.addresses = @options.addresses if @options.addresses
81
+
82
+ case (addresses = @options.addresses)
83
+ when Array
84
+ self.addresses = addresses
85
+ when Hash
86
+ # TODO: && @origin.host not an IP
87
+ self.addresses = addresses[@origin.host]
88
+ end
78
89
  end
79
90
 
80
91
  # dupped initialization
@@ -184,6 +195,14 @@ module HTTPX
184
195
  end
185
196
  end
186
197
 
198
+ # returns the number of pending requests both in the connection as well as parser
199
+ # pending queues
200
+ def pending_count
201
+ count = @pending.size
202
+ count += @parser.pending.size if @parser.respond_to?(:pending)
203
+ count
204
+ end
205
+
187
206
  def purge_pending(&block)
188
207
  if @parser
189
208
  pending = @parser.pending
@@ -221,14 +240,12 @@ module HTTPX
221
240
  def interests
222
241
  # connecting
223
242
  if connecting?
224
- connect
243
+ try_connect
225
244
 
226
245
  return @io.interests if connecting?
227
246
  end
228
247
 
229
- return @parser.interests if @parser
230
-
231
- nil
248
+ @parser&.interests
232
249
  rescue Error => e
233
250
  on_error(e)
234
251
  nil
@@ -239,11 +256,16 @@ module HTTPX
239
256
  end
240
257
 
241
258
  def call
259
+ if @reset_timeout_on_call
260
+ @timeout = nil
261
+ @reset_timeout_on_call = false
262
+ end
263
+
242
264
  case @state
243
265
  when :idle
244
266
  return if no_more_requests?
245
267
 
246
- connect
268
+ try_connect
247
269
 
248
270
  # when opening the tcp or ssl socket fails
249
271
  return if @state == :closed
@@ -297,7 +319,7 @@ module HTTPX
297
319
  def close
298
320
  transition(:active) if @state == :inactive
299
321
 
300
- @parser.close if @parser
322
+ @parser&.close
301
323
  end
302
324
 
303
325
  def terminate
@@ -327,10 +349,9 @@ module HTTPX
327
349
  @pending.clear
328
350
  elsif (parser = @parser)
329
351
  enqueue_pending_requests_from_parser(parser)
352
+ return unless @pending.empty?
330
353
  end
331
354
 
332
- return unless @pending.empty?
333
-
334
355
  disconnect
335
356
  emit(:force_closed, delete_pending)
336
357
  end
@@ -350,10 +371,7 @@ module HTTPX
350
371
  # (example: HTTP/1 parser disabling pipelining)
351
372
  return if @state == :idle && @pending.any?
352
373
 
353
- if @ping_timer
354
- @ping_timer.cancel
355
- @ping_timer = nil
356
- end
374
+ reset_ping_timer
357
375
 
358
376
  parser = @parser
359
377
 
@@ -371,6 +389,8 @@ module HTTPX
371
389
  def send(request)
372
390
  return @coalesced_connection.send(request) if @coalesced_connection
373
391
 
392
+ request.connection = self
393
+
374
394
  if @parser && !@write_buffer.full?
375
395
  if @response_received_at && @keep_alive_timeout &&
376
396
  Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
@@ -387,6 +407,8 @@ module HTTPX
387
407
 
388
408
  send_request_to_parser(request)
389
409
  else
410
+ set_connection_timeouts(request) if connecting?
411
+
390
412
  @pending << request
391
413
  end
392
414
  end
@@ -396,9 +418,7 @@ module HTTPX
396
418
 
397
419
  return @timeout if @timeout
398
420
 
399
- return @options.timeout[:connect_timeout] if @state == :idle
400
-
401
- @options.timeout[:operation_timeout]
421
+ @options.timeout.operation_timeout
402
422
  end
403
423
 
404
424
  def idling
@@ -410,7 +430,7 @@ module HTTPX
410
430
  transition(:idle)
411
431
  return unless @parser
412
432
 
413
- enqueue_pending_requests_from_parser(parser)
433
+ enqueue_pending_requests_from_parser(@parser)
414
434
  @parser = nil
415
435
  end
416
436
 
@@ -483,16 +503,20 @@ module HTTPX
483
503
 
484
504
  def on_error(error, request = nil)
485
505
  if error.is_a?(OperationTimeoutError)
486
-
487
506
  # inactive connections do not contribute to the select loop, therefore
488
507
  # they should not fail due to such errors.
489
508
  return if @state == :inactive
490
509
 
491
- if @timeout
492
- @timeout -= error.timeout
510
+ if (current_timeout = @timeout || timeout)
511
+ # if @timeout isn't set, it's not a completion-based timeout, i.e.
512
+ # operation timeout; in such a case, we have to reset it once it piggybacks.
513
+ @reset_timeout_on_call ||= @timeout.nil?
514
+
515
+ @timeout = current_timeout - error.timeout
493
516
  return unless @timeout <= 0
494
517
 
495
518
  @timeout = nil
519
+ @reset_timeout_on_call = false
496
520
  end
497
521
 
498
522
  error = error.to_connection_error if connecting?
@@ -513,6 +537,11 @@ module HTTPX
513
537
 
514
538
  private
515
539
 
540
+ def try_connect
541
+ emit(:connecting)
542
+ connect
543
+ end
544
+
516
545
  def connect
517
546
  transition(:open)
518
547
  end
@@ -711,6 +740,7 @@ module HTTPX
711
740
  AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
712
741
  build_altsvc_connection(alt_origin, origin, alt_params)
713
742
  end
743
+ @previously_exhausted_with_error = false
714
744
  @response_received_at = Utils.now
715
745
  @no_more_requests_counter = 0
716
746
  @inflight -= 1
@@ -726,9 +756,22 @@ module HTTPX
726
756
  parser.on(:promise) do |request, stream|
727
757
  request.emit(:promise, parser, stream)
728
758
  end
729
- parser.on(:exhausted) do
759
+ parser.on(:exhausted) do |error|
730
760
  enqueue_pending_requests_from_parser(parser)
731
761
 
762
+ if error
763
+ if @previously_exhausted_with_error
764
+ @previously_exhausted_with_error = false
765
+ on_error(error)
766
+ next
767
+ else
768
+ # in case the error came while waiting for a PING ack.
769
+ reset_ping_timer
770
+
771
+ @previously_exhausted_with_error = true
772
+ end
773
+ end
774
+
732
775
  @exhausted = true
733
776
  parser.close
734
777
 
@@ -773,11 +816,11 @@ module HTTPX
773
816
  current_selector = @current_selector
774
817
  parser.close
775
818
 
776
- other_connection = current_session.find_connection(@origin, current_selector,
777
- @options.merge(ssl: { alpn_protocols: %w[http/1.1] }))
778
- other_connection.merge(self)
779
819
  request.transition(:idle)
780
- other_connection.send(request)
820
+ current_session.__send__(:send_request, request, current_selector,
821
+ @options.merge(ssl: { alpn_protocols: %w[http/1.1] })) do |other_conn|
822
+ other_conn.merge(self)
823
+ end
781
824
  next
782
825
  when OperationTimeoutError
783
826
  # request level timeouts should take precedence
@@ -820,7 +863,7 @@ module HTTPX
820
863
  def handle_transition(nextstate)
821
864
  case nextstate
822
865
  when :idle
823
- @timeout = @current_timeout = @options.timeout[:connect_timeout]
866
+ @timeout = @current_timeout = @options.timeout.connect_timeout
824
867
 
825
868
  @connected_at = @response_received_at = nil
826
869
  when :open
@@ -858,13 +901,15 @@ module HTTPX
858
901
  nextstate = :open
859
902
  # the first check for given io readiness must still use a timeout.
860
903
  # connect is the reasonable choice in such a case.
861
- @timeout = @options.timeout[:connect_timeout]
904
+ @timeout = @options.timeout.connect_timeout
862
905
  send_pending
863
906
  when :active
864
907
  return unless @state == :inactive
865
908
 
866
909
  nextstate = :open
867
910
 
911
+ @timeout = @current_timeout = parser.timeout
912
+
868
913
  # activate
869
914
  @current_session.select_connection(self, @current_selector)
870
915
  end
@@ -902,6 +947,13 @@ module HTTPX
902
947
  end
903
948
  end
904
949
 
950
+ def reset_ping_timer
951
+ return unless @ping_timer
952
+
953
+ @ping_timer.cancel
954
+ @ping_timer = nil
955
+ end
956
+
905
957
  def close_sibling
906
958
  sibling = @sibling
907
959
 
@@ -949,8 +1001,6 @@ module HTTPX
949
1001
 
950
1002
  # returns an HTTPX::Connection for the negotiated Alternative Service (or none).
951
1003
  def build_altsvc_connection(alt_origin, origin, alt_params)
952
- return if @altsvc_connection
953
-
954
1004
  # do not allow security downgrades on altsvc negotiation
955
1005
  return if @origin.scheme == "https" && alt_origin.scheme != "https"
956
1006
 
@@ -959,6 +1009,8 @@ module HTTPX
959
1009
  # altsvc already exists, somehow it wasn't advertised, probably noop
960
1010
  return unless altsvc
961
1011
 
1012
+ return if @altsvc_connection
1013
+
962
1014
  alt_options = @options.merge(ssl: @options.ssl.merge(hostname: URI(origin).host))
963
1015
 
964
1016
  connection = @current_session.find_connection(alt_origin, @current_selector, alt_options)
@@ -1009,7 +1061,7 @@ module HTTPX
1009
1061
 
1010
1062
  return unless parser.waiting_for_ping?
1011
1063
 
1012
- ping_timeout = @options.timeout[:ping_timeout]
1064
+ ping_timeout = @options.timeout.ping_timeout
1013
1065
 
1014
1066
  @ping_timer = @current_selector.after(ping_timeout) do
1015
1067
  log(level: 3) { "ping timeout expired..." }
@@ -1021,22 +1073,67 @@ module HTTPX
1021
1073
  end
1022
1074
 
1023
1075
  def pong
1024
- @ping_timer.cancel
1025
- @ping_timer = nil
1076
+ reset_ping_timer
1026
1077
  @response_received_at = Utils.now
1027
1078
  @no_more_requests_counter = 0
1028
1079
  send_pending
1029
1080
  end
1030
1081
 
1082
+ def no_more_selector_check
1083
+ return if @current_selector
1084
+
1085
+ raise Error, "request has been resend to an out-of-session connection, and this " \
1086
+ "should never happen!!! Please report this error! " \
1087
+ "(state:#{@state}, " \
1088
+ "parser?:#{!!@parser}, " \
1089
+ "bytes in write buffer?:#{!@write_buffer.empty?}, " \
1090
+ "cloned?:#{@cloned}, " \
1091
+ "sibling?:#{!!@sibling}, " \
1092
+ "coalesced?:#{coalesced?})"
1093
+ end
1094
+
1031
1095
  def no_more_requests_loop_check
1032
1096
  log(level: 3) { "NO MORE REQUESTS..." }
1033
1097
  @no_more_requests_counter += 1
1034
1098
 
1035
1099
  return if @no_more_requests_counter < 50
1036
1100
 
1101
+ per_request_state = @parser.pending.map do |req|
1102
+ "[state:#{req.state},ping?:#{req.ping?}]"
1103
+ end.join(",")
1104
+
1105
+ parser_state =
1106
+ case parser
1107
+ when HTTP2
1108
+ parser_pending_requests = parser.pending.map do |r|
1109
+ num_retries = r.respond_to(:retries) ? r.retries : nil
1110
+ req_conn = r.instance_variable_get(:@connection)
1111
+ "id:#{r.object_id},num_retries:#{num_retries}," \
1112
+ "is-same-conn:#{req_conn.equal?(self)},started?:#{r.started?}"
1113
+ end.join(",")
1114
+ +"parser-streams:#{parser.streams.size}," \
1115
+ "parser-handshake-completed:#{parser.instance_variable_get(:@handshake_completed)}," \
1116
+ "parser-wait-for-handshake:#{parser.instance_variable_get(:@wait_for_handshake)}," \
1117
+ "parser-pending:[#{parser_pending_requests}],"
1118
+ else
1119
+ +""
1120
+ end
1121
+
1122
+ parser_state << "parser-requests:#{per_request_state}"
1123
+
1037
1124
  raise Error, "connection corrupted, aborted after looping for a while, " \
1038
1125
  "please report this https://gitlab.com/os85/httpx/-/work_items " \
1039
- "along with debug logs"
1126
+ "along with debug logs\n(" \
1127
+ "state:#{@state},inflight:#{@inflight},pending:#{@pending.size}," \
1128
+ "exhausted:#{@exhausted}," \
1129
+ "previously-exhausted-with-error:#{@previously_exhausted_with_error}," \
1130
+ "connected_for_secs:#{Utils.elapsed_time(@connected_at) if @connected_at}," \
1131
+ "num-origins:#{@origins.size}," \
1132
+ "last_response_received:#{Utils.elapsed_time(@response_received_at) if @response_received_at}," \
1133
+ "#{parser_state}," \
1134
+ "in-selector:#{!!@current_selector}," \
1135
+ "in-session:#{!!@current_session}" \
1136
+ ")"
1040
1137
  end
1041
1138
 
1042
1139
  # true when there are no more pending nor inflight (in parser) requests
@@ -1074,13 +1171,17 @@ module HTTPX
1074
1171
  end
1075
1172
 
1076
1173
  def set_request_timeouts(request)
1077
- request.connection = self
1078
1174
  set_request_write_timeout(request)
1079
1175
  set_request_read_timeout(request)
1080
1176
  set_request_request_timeout(request)
1081
1177
  set_request_total_request_timeout(request)
1082
1178
  end
1083
1179
 
1180
+ def set_connection_timeouts(request)
1181
+ set_connecting_request_timeout(request)
1182
+ set_connecting_total_request_timeout(request)
1183
+ end
1184
+
1084
1185
  def set_request_read_timeout(request)
1085
1186
  read_timeout = request.read_timeout
1086
1187
 
@@ -1144,19 +1245,52 @@ module HTTPX
1144
1245
  end
1145
1246
 
1146
1247
  def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
1248
+ return if request.active_timeouts.any? { |timer| timer.label == label }
1249
+
1147
1250
  request.set_timeout_callback(start_event) do
1148
- unless (selector = @current_selector)
1149
- raise Error, "request has been resend to an out-of-session connection, and this " \
1150
- "should never happen!!! Please report this error! " \
1151
- "(state:#{@state}, " \
1152
- "parser?:#{!!@parser}, " \
1153
- "bytes in write buffer?:#{!@write_buffer.empty?}, " \
1154
- "cloned?:#{@cloned}, " \
1155
- "sibling?:#{!!@sibling}, " \
1156
- "coalesced?:#{coalesced?})"
1251
+ no_more_selector_check
1252
+
1253
+ timer = @current_selector.after(timeout, callback)
1254
+ timer.label = label
1255
+ request.active_timeouts << timer
1256
+
1257
+ Array(finish_events).each do |event|
1258
+ # clean up request timeouts if the connection errors out
1259
+ request.set_timeout_callback(event) do
1260
+ timer.cancel
1261
+ request.active_timeouts.delete(timer)
1262
+ end
1157
1263
  end
1264
+ end
1265
+ end
1158
1266
 
1159
- timer = selector.after(timeout, callback)
1267
+ def set_connecting_request_timeout(request)
1268
+ request_timeout = request.request_timeout
1269
+
1270
+ return if request_timeout.nil? || request_timeout.infinite?
1271
+
1272
+ set_connection_timeout(:request_timeout, request, request_timeout, :connecting, :complete) do
1273
+ read_timeout_callback(request, request_timeout, RequestTimeoutError)
1274
+ end
1275
+ end
1276
+
1277
+ def set_connecting_total_request_timeout(request)
1278
+ return if request.started?
1279
+
1280
+ total_request_timeout = request.total_request_timeout
1281
+
1282
+ return if total_request_timeout.nil? || total_request_timeout.infinite?
1283
+
1284
+ set_connection_timeout(:total_request_timeout, request, total_request_timeout, :connecting, :complete) do
1285
+ read_timeout_callback(request, total_request_timeout, TotalRequestTimeoutError)
1286
+ end
1287
+ end
1288
+
1289
+ def set_connection_timeout(label, request, timeout, start_event, finish_events, &callback)
1290
+ cb = once(start_event) do
1291
+ no_more_selector_check
1292
+
1293
+ timer = @current_selector.after(timeout, callback)
1160
1294
  timer.label = label
1161
1295
  request.active_timeouts << timer
1162
1296
 
@@ -1168,6 +1302,10 @@ module HTTPX
1168
1302
  end
1169
1303
  end
1170
1304
  end
1305
+
1306
+ request.once(:idle) do
1307
+ callbacks(start_event).delete(cb)
1308
+ end
1171
1309
  end
1172
1310
 
1173
1311
  def parser_type(protocol)
data/lib/httpx/headers.rb CHANGED
@@ -159,8 +159,8 @@ module HTTPX
159
159
  # This method is more internal, and for this reason doesn't try
160
160
  # to "correct" the user input, i.e. it doesn't downcase the key.
161
161
  #
162
- def get(field)
163
- @headers[field] || EMPTY
162
+ def get(downcased_key)
163
+ @headers[downcased_key] || EMPTY
164
164
  end
165
165
 
166
166
  private
data/lib/httpx/io/tcp.rb CHANGED
@@ -132,7 +132,7 @@ module HTTPX
132
132
  rescue Errno::ETIMEDOUT => e
133
133
  @ip_index -= 1
134
134
 
135
- raise ConnectTimeoutError.new(@options.timeout[:connect_timeout], e.message) if @ip_index.negative?
135
+ raise ConnectTimeoutError.new(@options.timeout.connect_timeout, e.message) if @ip_index.negative?
136
136
 
137
137
  log { "failed connecting to #{@ip} (#{e.message}), trying next..." }
138
138
 
@@ -159,26 +159,29 @@ module HTTPX
159
159
  private :try_connect
160
160
 
161
161
  def read(size, buffer)
162
- ret = @io.read_nonblock(size, buffer, exception: false)
163
- if ret == :wait_readable
162
+ siz = @io.read_nonblock(size, buffer, exception: false)
163
+
164
+ case siz
165
+ when String
166
+ log { "READ: #{buffer.bytesize} bytes..." }
167
+ buffer.bytesize
168
+ when :wait_readable, :wait_writable
164
169
  buffer.clear
165
- return 0
170
+ 0
166
171
  end
167
- return if ret.nil?
168
-
169
- log { "READ: #{buffer.bytesize} bytes..." }
170
- buffer.bytesize
171
172
  end
172
173
 
173
174
  def write(buffer)
174
175
  siz = @io.write_nonblock(buffer, exception: false)
175
- return 0 if siz == :wait_writable
176
- return if siz.nil?
177
176
 
178
- log { "WRITE: #{siz} bytes..." }
179
-
180
- buffer.shift!(siz)
181
- siz
177
+ case siz
178
+ when Integer
179
+ log { "WRITE: #{siz} bytes..." }
180
+ buffer.shift!(siz)
181
+ siz
182
+ when :wait_readable, :wait_writable
183
+ 0
184
+ end
182
185
  end
183
186
 
184
187
  def close
@@ -255,6 +258,7 @@ module HTTPX
255
258
 
256
259
  def log_transition_state(nextstate)
257
260
  label = host
261
+ label = "#{label}:#{@port}" if label
258
262
  label = "#{label}(##{@io.fileno})" if nextstate == :connected
259
263
  "#{label} #{@state} -> #{nextstate}"
260
264
  end