httpx 1.7.2 → 1.7.6
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.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/doc/release_notes/1_7_3.md +29 -0
- data/doc/release_notes/1_7_4.md +42 -0
- data/doc/release_notes/1_7_5.md +10 -0
- data/doc/release_notes/1_7_6.md +24 -0
- data/lib/httpx/adapters/datadog.rb +37 -64
- data/lib/httpx/adapters/webmock.rb +3 -4
- data/lib/httpx/altsvc.rb +4 -2
- data/lib/httpx/connection/http1.rb +26 -18
- data/lib/httpx/connection/http2.rb +53 -33
- data/lib/httpx/connection.rb +152 -63
- data/lib/httpx/io/ssl.rb +20 -8
- data/lib/httpx/io/tcp.rb +18 -12
- data/lib/httpx/io/unix.rb +13 -9
- data/lib/httpx/options.rb +23 -7
- data/lib/httpx/parser/http1.rb +14 -4
- data/lib/httpx/plugins/auth/digest.rb +2 -1
- data/lib/httpx/plugins/auth.rb +23 -9
- data/lib/httpx/plugins/brotli.rb +33 -5
- data/lib/httpx/plugins/cookies/cookie.rb +34 -11
- data/lib/httpx/plugins/cookies/jar.rb +93 -18
- data/lib/httpx/plugins/cookies.rb +7 -3
- data/lib/httpx/plugins/expect.rb +33 -3
- data/lib/httpx/plugins/fiber_concurrency.rb +2 -4
- data/lib/httpx/plugins/follow_redirects.rb +7 -1
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +15 -8
- data/lib/httpx/plugins/proxy.rb +10 -2
- data/lib/httpx/plugins/rate_limiter.rb +19 -19
- data/lib/httpx/plugins/retries.rb +17 -9
- data/lib/httpx/plugins/ssrf_filter.rb +1 -0
- data/lib/httpx/plugins/stream_bidi.rb +6 -0
- data/lib/httpx/plugins/tracing.rb +137 -0
- data/lib/httpx/pool.rb +7 -9
- data/lib/httpx/request.rb +15 -3
- data/lib/httpx/resolver/multi.rb +1 -8
- data/lib/httpx/resolver/native.rb +2 -2
- data/lib/httpx/resolver/resolver.rb +21 -2
- data/lib/httpx/resolver/system.rb +3 -1
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +19 -16
- data/lib/httpx/session.rb +34 -44
- data/lib/httpx/timers.rb +4 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +2 -0
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +3 -1
- data/sig/connection/http2.rbs +11 -4
- data/sig/connection.rbs +16 -2
- data/sig/io/ssl.rbs +1 -0
- data/sig/io/tcp.rbs +2 -2
- data/sig/options.rbs +8 -3
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth.rbs +5 -2
- data/sig/plugins/brotli.rbs +11 -6
- data/sig/plugins/cookies/cookie.rbs +3 -2
- data/sig/plugins/cookies/jar.rbs +11 -0
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/expect.rbs +21 -2
- data/sig/plugins/fiber_concurrency.rbs +2 -2
- data/sig/plugins/proxy/socks4.rbs +4 -0
- data/sig/plugins/rate_limiter.rbs +2 -2
- data/sig/plugins/response_cache.rbs +3 -3
- data/sig/plugins/retries.rbs +17 -13
- data/sig/plugins/tracing.rbs +41 -0
- data/sig/pool.rbs +1 -1
- data/sig/request.rbs +4 -0
- data/sig/resolver/native.rbs +2 -0
- data/sig/resolver/resolver.rbs +4 -2
- data/sig/resolver/system.rbs +0 -2
- data/sig/response/body.rbs +1 -1
- data/sig/selector.rbs +7 -2
- data/sig/session.rbs +2 -0
- data/sig/timers.rbs +2 -0
- data/sig/transcoder/gzip.rbs +1 -1
- data/sig/transcoder.rbs +0 -2
- metadata +13 -3
data/lib/httpx/connection.rb
CHANGED
|
@@ -45,10 +45,10 @@ module HTTPX
|
|
|
45
45
|
protected :ssl_session, :sibling
|
|
46
46
|
|
|
47
47
|
def initialize(uri, options)
|
|
48
|
-
@current_session = @current_selector =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
@current_session = @current_selector = @max_concurrent_requests =
|
|
49
|
+
@parser = @sibling = @coalesced_connection = @altsvc_connection =
|
|
50
|
+
@family = @io = @ssl_session = @timeout =
|
|
51
|
+
@connected_at = @response_received_at = nil
|
|
52
52
|
|
|
53
53
|
@exhausted = @cloned = @main_sibling = false
|
|
54
54
|
|
|
@@ -62,6 +62,7 @@ module HTTPX
|
|
|
62
62
|
@pending = []
|
|
63
63
|
@inflight = 0
|
|
64
64
|
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
|
65
|
+
@no_more_requests_counter = 0
|
|
65
66
|
|
|
66
67
|
if @options.io
|
|
67
68
|
# if there's an already open IO, get its
|
|
@@ -106,7 +107,7 @@ module HTTPX
|
|
|
106
107
|
# origin came from an ORIGIN frame, we're going to verify the hostname with the
|
|
107
108
|
# SSL certificate
|
|
108
109
|
(@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host))) &&
|
|
109
|
-
@options
|
|
110
|
+
@options.connection_options_match?(options)
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def mergeable?(connection)
|
|
@@ -117,7 +118,7 @@ module HTTPX
|
|
|
117
118
|
(
|
|
118
119
|
(open? && @origin == connection.origin) ||
|
|
119
120
|
!(@io.addresses & (connection.addresses || [])).empty?
|
|
120
|
-
) && @options
|
|
121
|
+
) && @options.connection_options_match?(connection.options)
|
|
121
122
|
end
|
|
122
123
|
|
|
123
124
|
# coalesces +self+ into +connection+.
|
|
@@ -154,6 +155,7 @@ module HTTPX
|
|
|
154
155
|
end if @io
|
|
155
156
|
end
|
|
156
157
|
connection.purge_pending do |req|
|
|
158
|
+
req.transition(:idle)
|
|
157
159
|
send(req)
|
|
158
160
|
end
|
|
159
161
|
end
|
|
@@ -161,8 +163,9 @@ module HTTPX
|
|
|
161
163
|
def purge_pending(&block)
|
|
162
164
|
pendings = []
|
|
163
165
|
if @parser
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
pending = @parser.pending
|
|
167
|
+
@inflight -= pending.size
|
|
168
|
+
pendings << pending
|
|
166
169
|
end
|
|
167
170
|
pendings << @pending
|
|
168
171
|
pendings.each do |pending|
|
|
@@ -227,6 +230,9 @@ module HTTPX
|
|
|
227
230
|
consume
|
|
228
231
|
end
|
|
229
232
|
nil
|
|
233
|
+
rescue IOError => e
|
|
234
|
+
@write_buffer.clear
|
|
235
|
+
on_io_error(e)
|
|
230
236
|
rescue StandardError => e
|
|
231
237
|
@write_buffer.clear
|
|
232
238
|
on_error(e)
|
|
@@ -256,16 +262,17 @@ module HTTPX
|
|
|
256
262
|
# bypasses state machine rules while setting the connection in the
|
|
257
263
|
# :closed state.
|
|
258
264
|
def force_close(delete_pending = false)
|
|
265
|
+
force_purge
|
|
266
|
+
return unless @state == :closed
|
|
267
|
+
|
|
259
268
|
if delete_pending
|
|
260
269
|
@pending.clear
|
|
261
270
|
elsif (parser = @parser)
|
|
262
271
|
enqueue_pending_requests_from_parser(parser)
|
|
263
272
|
end
|
|
264
|
-
return if @state == :closed
|
|
265
273
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
purge_after_closed
|
|
274
|
+
return unless @pending.empty?
|
|
275
|
+
|
|
269
276
|
disconnect
|
|
270
277
|
emit(:force_closed, delete_pending)
|
|
271
278
|
end
|
|
@@ -281,6 +288,19 @@ module HTTPX
|
|
|
281
288
|
def reset
|
|
282
289
|
return if @state == :closing || @state == :closed
|
|
283
290
|
|
|
291
|
+
# do not reset a connection which may have restarted back to :idle, such when the parser resets
|
|
292
|
+
# (example: HTTP/1 parser disabling pipelining)
|
|
293
|
+
return if @state == :idle && @pending.any?
|
|
294
|
+
|
|
295
|
+
parser = @parser
|
|
296
|
+
|
|
297
|
+
if parser && parser.respond_to?(:max_concurrent_requests)
|
|
298
|
+
# if connection being reset has at some downgraded the number of concurrent
|
|
299
|
+
# requests, such as in the case where an attempt to use HTTP/1 pipelining failed,
|
|
300
|
+
# keep that information around.
|
|
301
|
+
@max_concurrent_requests = parser.max_concurrent_requests
|
|
302
|
+
end
|
|
303
|
+
|
|
284
304
|
transition(:closing)
|
|
285
305
|
|
|
286
306
|
transition(:closed)
|
|
@@ -298,8 +318,8 @@ module HTTPX
|
|
|
298
318
|
log(level: 3) { "keep alive timeout expired, pinging connection..." }
|
|
299
319
|
@pending << request
|
|
300
320
|
transition(:active) if @state == :inactive
|
|
301
|
-
parser.ping
|
|
302
321
|
request.ping!
|
|
322
|
+
ping
|
|
303
323
|
return
|
|
304
324
|
end
|
|
305
325
|
|
|
@@ -323,7 +343,10 @@ module HTTPX
|
|
|
323
343
|
purge_after_closed
|
|
324
344
|
@write_buffer.clear
|
|
325
345
|
transition(:idle)
|
|
326
|
-
|
|
346
|
+
return unless @parser
|
|
347
|
+
|
|
348
|
+
enqueue_pending_requests_from_parser(parser)
|
|
349
|
+
@parser = nil
|
|
327
350
|
end
|
|
328
351
|
|
|
329
352
|
def used?
|
|
@@ -375,6 +398,19 @@ module HTTPX
|
|
|
375
398
|
current_session.deselect_connection(self, current_selector, @cloned)
|
|
376
399
|
end
|
|
377
400
|
|
|
401
|
+
def on_connect_error(e)
|
|
402
|
+
# connect errors, exit gracefully
|
|
403
|
+
error = ConnectionError.new(e.message)
|
|
404
|
+
error.set_backtrace(e.backtrace)
|
|
405
|
+
handle_connect_error(error) if connecting?
|
|
406
|
+
force_close
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def on_io_error(e)
|
|
410
|
+
on_error(e)
|
|
411
|
+
force_close(true)
|
|
412
|
+
end
|
|
413
|
+
|
|
378
414
|
def on_error(error, request = nil)
|
|
379
415
|
if error.is_a?(OperationTimeoutError)
|
|
380
416
|
|
|
@@ -424,11 +460,11 @@ module HTTPX
|
|
|
424
460
|
#
|
|
425
461
|
# this condition takes into account:
|
|
426
462
|
#
|
|
427
|
-
# * the number of inflight requests
|
|
428
463
|
# * the number of pending requests
|
|
464
|
+
# * the number of inflight requests
|
|
429
465
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
430
466
|
if @pending.empty? && @inflight.zero? && @write_buffer.empty?
|
|
431
|
-
|
|
467
|
+
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
432
468
|
|
|
433
469
|
# terminate if an altsvc connection has been established
|
|
434
470
|
terminate if @altsvc_connection
|
|
@@ -478,7 +514,7 @@ module HTTPX
|
|
|
478
514
|
|
|
479
515
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
480
516
|
if @pending.empty? && @inflight.zero? && @write_buffer.empty? # rubocop:disable Style/Next
|
|
481
|
-
|
|
517
|
+
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
482
518
|
|
|
483
519
|
# terminate if an altsvc connection has been established
|
|
484
520
|
terminate if @altsvc_connection
|
|
@@ -493,7 +529,7 @@ module HTTPX
|
|
|
493
529
|
# flush as many bytes as the sockets allow.
|
|
494
530
|
#
|
|
495
531
|
loop do
|
|
496
|
-
# buffer has been
|
|
532
|
+
# buffer has been drained, mark and exit the write loop.
|
|
497
533
|
if @write_buffer.empty?
|
|
498
534
|
# we only mark as drained on the first loop
|
|
499
535
|
write_drained = write_drained.nil? && @inflight.positive?
|
|
@@ -578,6 +614,7 @@ module HTTPX
|
|
|
578
614
|
end
|
|
579
615
|
|
|
580
616
|
def enqueue_pending_requests_from_parser(parser)
|
|
617
|
+
parser.reset_requests # move sequential requests back to pending queue.
|
|
581
618
|
parser_pending_requests = parser.pending
|
|
582
619
|
|
|
583
620
|
return if parser_pending_requests.empty?
|
|
@@ -586,11 +623,14 @@ module HTTPX
|
|
|
586
623
|
# back to the pending list before the parser is reset.
|
|
587
624
|
@inflight -= parser_pending_requests.size
|
|
588
625
|
@pending.unshift(*parser_pending_requests)
|
|
626
|
+
|
|
627
|
+
parser.pending.clear
|
|
589
628
|
end
|
|
590
629
|
|
|
591
630
|
def build_parser(protocol = @io.protocol)
|
|
592
631
|
parser = parser_type(protocol).new(@write_buffer, @options)
|
|
593
632
|
set_parser_callbacks(parser)
|
|
633
|
+
parser.max_concurrent_requests = @max_concurrent_requests if @max_concurrent_requests && parser.respond_to?(:max_concurrent_requests=)
|
|
594
634
|
parser
|
|
595
635
|
end
|
|
596
636
|
|
|
@@ -600,6 +640,7 @@ module HTTPX
|
|
|
600
640
|
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
601
641
|
end
|
|
602
642
|
@response_received_at = Utils.now
|
|
643
|
+
@no_more_requests_counter = 0
|
|
603
644
|
@inflight -= 1
|
|
604
645
|
response.finish!
|
|
605
646
|
request.emit(:response, response)
|
|
@@ -608,7 +649,7 @@ module HTTPX
|
|
|
608
649
|
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
609
650
|
end
|
|
610
651
|
|
|
611
|
-
parser.on(:pong, &method(:
|
|
652
|
+
parser.on(:pong, &method(:pong))
|
|
612
653
|
|
|
613
654
|
parser.on(:promise) do |request, stream|
|
|
614
655
|
request.emit(:promise, parser, stream)
|
|
@@ -627,7 +668,6 @@ module HTTPX
|
|
|
627
668
|
end
|
|
628
669
|
parser.on(:close) do
|
|
629
670
|
reset
|
|
630
|
-
disconnect
|
|
631
671
|
end
|
|
632
672
|
parser.on(:close_handshake) do
|
|
633
673
|
consume unless @state == :closed
|
|
@@ -684,11 +724,7 @@ module HTTPX
|
|
|
684
724
|
Errno::ENOENT,
|
|
685
725
|
SocketError,
|
|
686
726
|
IOError => e
|
|
687
|
-
|
|
688
|
-
error = ConnectionError.new(e.message)
|
|
689
|
-
error.set_backtrace(e.backtrace)
|
|
690
|
-
handle_connect_error(error) if connecting?
|
|
691
|
-
force_close
|
|
727
|
+
on_connect_error(e)
|
|
692
728
|
rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
|
|
693
729
|
# connect errors, exit gracefully
|
|
694
730
|
handle_error(e)
|
|
@@ -719,29 +755,17 @@ module HTTPX
|
|
|
719
755
|
when :inactive
|
|
720
756
|
return unless @state == :open
|
|
721
757
|
|
|
758
|
+
# @type ivar @parser: HTTP1 | HTTP2
|
|
759
|
+
|
|
722
760
|
# do not deactivate connection in use
|
|
723
761
|
return if @inflight.positive? || @parser.waiting_for_ping?
|
|
724
|
-
|
|
725
|
-
disconnect
|
|
726
762
|
when :closing
|
|
727
763
|
return unless connecting? || @state == :open
|
|
728
|
-
|
|
729
|
-
unless @write_buffer.empty?
|
|
730
|
-
# preset state before handshake, as error callbacks
|
|
731
|
-
# may take it back here.
|
|
732
|
-
@state = nextstate
|
|
733
|
-
# handshakes, try sending
|
|
734
|
-
consume
|
|
735
|
-
@write_buffer.clear
|
|
736
|
-
return
|
|
737
|
-
end
|
|
738
764
|
when :closed
|
|
739
765
|
return unless @state == :closing
|
|
740
766
|
return unless @write_buffer.empty?
|
|
741
767
|
|
|
742
768
|
purge_after_closed
|
|
743
|
-
disconnect if @pending.empty?
|
|
744
|
-
|
|
745
769
|
when :already_open
|
|
746
770
|
nextstate = :open
|
|
747
771
|
# the first check for given io readiness must still use a timeout.
|
|
@@ -758,19 +782,51 @@ module HTTPX
|
|
|
758
782
|
end
|
|
759
783
|
log(level: 3) { "#{@state} -> #{nextstate}" }
|
|
760
784
|
@state = nextstate
|
|
785
|
+
# post state change
|
|
786
|
+
case nextstate
|
|
787
|
+
when :inactive
|
|
788
|
+
disconnect
|
|
789
|
+
when :closing
|
|
790
|
+
return if @write_buffer.empty?
|
|
791
|
+
|
|
792
|
+
# try flushing termination handshakes
|
|
793
|
+
consume
|
|
794
|
+
@write_buffer.clear
|
|
795
|
+
when :closed
|
|
796
|
+
# TODO: should this raise an error instead?
|
|
797
|
+
return unless @pending.empty?
|
|
798
|
+
|
|
799
|
+
disconnect
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
def force_purge
|
|
804
|
+
return if @state == :closed
|
|
805
|
+
|
|
806
|
+
@state = :closed
|
|
807
|
+
@write_buffer.clear
|
|
808
|
+
begin
|
|
809
|
+
purge_after_closed
|
|
810
|
+
rescue IOError
|
|
811
|
+
# may be raised when closing the socket.
|
|
812
|
+
# due to connection reuse / fiber scheduling, it may
|
|
813
|
+
# have been reopened, to bail out in that case.
|
|
814
|
+
end
|
|
761
815
|
end
|
|
762
816
|
|
|
763
817
|
def close_sibling
|
|
764
|
-
|
|
818
|
+
sibling = @sibling
|
|
765
819
|
|
|
766
|
-
|
|
820
|
+
return unless sibling
|
|
821
|
+
|
|
822
|
+
if sibling.io_connected?
|
|
767
823
|
reset
|
|
768
824
|
# TODO: transition connection to closed
|
|
769
825
|
end
|
|
770
826
|
|
|
771
|
-
unless
|
|
772
|
-
merge(
|
|
773
|
-
|
|
827
|
+
unless sibling.state == :closed
|
|
828
|
+
merge(sibling) unless @main_sibling
|
|
829
|
+
sibling.force_reset(true)
|
|
774
830
|
end
|
|
775
831
|
|
|
776
832
|
@sibling = nil
|
|
@@ -850,28 +906,61 @@ module HTTPX
|
|
|
850
906
|
end
|
|
851
907
|
end
|
|
852
908
|
|
|
909
|
+
def ping
|
|
910
|
+
return if parser.waiting_for_ping?
|
|
911
|
+
|
|
912
|
+
parser.ping
|
|
913
|
+
call
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
def pong
|
|
917
|
+
@response_received_at = Utils.now
|
|
918
|
+
@no_more_requests_counter = 0
|
|
919
|
+
send_pending
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
def no_more_requests_loop_check
|
|
923
|
+
log(level: 3) { "NO MORE REQUESTS..." }
|
|
924
|
+
@no_more_requests_counter += 1
|
|
925
|
+
|
|
926
|
+
return if @no_more_requests_counter < 50
|
|
927
|
+
|
|
928
|
+
raise Error, "connection corrupted, aborted after looping for a while, " \
|
|
929
|
+
"please report this https://gitlab.com/os85/httpx/-/work_items " \
|
|
930
|
+
"along with debug logs"
|
|
931
|
+
end
|
|
932
|
+
|
|
853
933
|
# recover internal state and emit all relevant error responses when +error+ was raised.
|
|
854
934
|
# this takes an optiona +request+ which may have already been handled and can be opted out
|
|
855
935
|
# in the state recovery process.
|
|
856
936
|
def handle_error(error, request = nil)
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
937
|
+
if request
|
|
938
|
+
@inflight -= 1
|
|
939
|
+
response = ErrorResponse.new(request, error)
|
|
940
|
+
request.response = response
|
|
941
|
+
request.emit(:response, response)
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
pending = @pending
|
|
945
|
+
if (parser = @parser) && parser.respond_to?(:handle_error)
|
|
946
|
+
# parser.handle_error may disconnect the connection
|
|
947
|
+
pending = @pending.dup
|
|
948
|
+
@pending = []
|
|
860
949
|
|
|
861
|
-
|
|
862
|
-
req.response = response
|
|
863
|
-
req.emit(:response, response)
|
|
950
|
+
parser.handle_error(error, request)
|
|
864
951
|
end
|
|
865
952
|
|
|
866
|
-
|
|
953
|
+
while (req = pending.shift)
|
|
954
|
+
next if request && req == request
|
|
867
955
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
956
|
+
resp = ErrorResponse.new(req, error)
|
|
957
|
+
req.response = resp
|
|
958
|
+
req.emit(:response, resp)
|
|
959
|
+
end
|
|
872
960
|
end
|
|
873
961
|
|
|
874
962
|
def set_request_timeouts(request)
|
|
963
|
+
request.connection = self
|
|
875
964
|
set_request_write_timeout(request)
|
|
876
965
|
set_request_read_timeout(request)
|
|
877
966
|
set_request_request_timeout(request)
|
|
@@ -907,29 +996,29 @@ module HTTPX
|
|
|
907
996
|
end
|
|
908
997
|
end
|
|
909
998
|
|
|
910
|
-
def write_timeout_callback(request,
|
|
999
|
+
def write_timeout_callback(request, timeout)
|
|
911
1000
|
return if request.state == :done
|
|
912
1001
|
|
|
913
1002
|
@write_buffer.clear
|
|
914
|
-
error = WriteTimeoutError.new(request, nil,
|
|
1003
|
+
error = WriteTimeoutError.new(request, nil, timeout)
|
|
915
1004
|
|
|
916
|
-
|
|
1005
|
+
request.handle_error(error)
|
|
917
1006
|
end
|
|
918
1007
|
|
|
919
|
-
def read_timeout_callback(request,
|
|
1008
|
+
def read_timeout_callback(request, timeout, error_type = ReadTimeoutError)
|
|
920
1009
|
response = request.response
|
|
921
1010
|
|
|
922
1011
|
return if response && response.finished?
|
|
923
1012
|
|
|
924
1013
|
@write_buffer.clear
|
|
925
|
-
error = error_type.new(request,
|
|
1014
|
+
error = error_type.new(request, response, timeout)
|
|
926
1015
|
|
|
927
|
-
|
|
1016
|
+
request.handle_error(error)
|
|
928
1017
|
end
|
|
929
1018
|
|
|
930
1019
|
def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
|
|
931
1020
|
request.set_timeout_callback(start_event) do
|
|
932
|
-
unless @current_selector
|
|
1021
|
+
unless (selector = @current_selector)
|
|
933
1022
|
raise Error, "request has been resend to an out-of-session connection, and this " \
|
|
934
1023
|
"should never happen!!! Please report this error! " \
|
|
935
1024
|
"(state:#{@state}, " \
|
|
@@ -940,7 +1029,7 @@ module HTTPX
|
|
|
940
1029
|
"coalesced?:#{coalesced?})"
|
|
941
1030
|
end
|
|
942
1031
|
|
|
943
|
-
timer =
|
|
1032
|
+
timer = selector.after(timeout, callback)
|
|
944
1033
|
request.active_timeouts << label
|
|
945
1034
|
|
|
946
1035
|
Array(finish_events).each do |event|
|
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -51,6 +51,7 @@ module HTTPX
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def protocol
|
|
54
|
+
# @type ivar @io: OpenSSL::SSL::SSLSocket
|
|
54
55
|
@io.alpn_protocol || super
|
|
55
56
|
rescue StandardError
|
|
56
57
|
super
|
|
@@ -60,6 +61,7 @@ module HTTPX
|
|
|
60
61
|
# in jruby, alpn_protocol may return ""
|
|
61
62
|
# https://github.com/jruby/jruby-openssl/issues/287
|
|
62
63
|
def protocol
|
|
64
|
+
# @type ivar @io: OpenSSL::SSL::SSLSocket
|
|
63
65
|
proto = @io.alpn_protocol
|
|
64
66
|
|
|
65
67
|
return super if proto.nil? || proto.empty?
|
|
@@ -76,9 +78,10 @@ module HTTPX
|
|
|
76
78
|
|
|
77
79
|
def verify_hostname(host)
|
|
78
80
|
return false if @ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
|
|
79
|
-
|
|
81
|
+
# @type ivar @io: OpenSSL::SSL::SSLSocket
|
|
82
|
+
return false if !@io.respond_to?(:peer_cert) || (peer_cert = @io.peer_cert).nil?
|
|
80
83
|
|
|
81
|
-
OpenSSL::SSL.verify_certificate_identity(
|
|
84
|
+
OpenSSL::SSL.verify_certificate_identity(peer_cert, host)
|
|
82
85
|
end
|
|
83
86
|
|
|
84
87
|
def connected?
|
|
@@ -86,7 +89,9 @@ module HTTPX
|
|
|
86
89
|
end
|
|
87
90
|
|
|
88
91
|
def ssl_session_expired?
|
|
89
|
-
|
|
92
|
+
ssl_session = @ssl_session
|
|
93
|
+
|
|
94
|
+
ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (ssl_session.time.to_f + ssl_session.timeout)
|
|
90
95
|
end
|
|
91
96
|
|
|
92
97
|
def connect
|
|
@@ -97,6 +102,8 @@ module HTTPX
|
|
|
97
102
|
return unless @state == :connected
|
|
98
103
|
end
|
|
99
104
|
|
|
105
|
+
# @type ivar @io: OpenSSL::SSL::SSLSocket
|
|
106
|
+
|
|
100
107
|
unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
101
108
|
if (hostname_is_ip = (@ip == @sni_hostname)) && @ctx.verify_hostname
|
|
102
109
|
# IPv6 address would be "[::1]", must turn to "0000:0000:0000:0000:0000:0000:0000:0001" for cert SAN check
|
|
@@ -105,16 +112,19 @@ module HTTPX
|
|
|
105
112
|
@ctx.verify_hostname = false
|
|
106
113
|
end
|
|
107
114
|
|
|
108
|
-
|
|
115
|
+
ssl = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
|
|
109
116
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
ssl.hostname = @sni_hostname unless hostname_is_ip
|
|
118
|
+
ssl.session = @ssl_session unless ssl_session_expired?
|
|
119
|
+
ssl.sync_close = true
|
|
120
|
+
|
|
121
|
+
@io = ssl
|
|
113
122
|
end
|
|
114
123
|
try_ssl_connect
|
|
115
124
|
end
|
|
116
125
|
|
|
117
126
|
def try_ssl_connect
|
|
127
|
+
# @type ivar @io: OpenSSL::SSL::SSLSocket
|
|
118
128
|
ret = @io.connect_nonblock(exception: false)
|
|
119
129
|
log(level: 3, color: :cyan) { "TLS CONNECT: #{ret}..." }
|
|
120
130
|
case ret
|
|
@@ -147,7 +157,9 @@ module HTTPX
|
|
|
147
157
|
def log_transition_state(nextstate)
|
|
148
158
|
return super unless nextstate == :negotiated
|
|
149
159
|
|
|
150
|
-
|
|
160
|
+
# @type ivar @io: OpenSSL::SSL::SSLSocket
|
|
161
|
+
|
|
162
|
+
server_cert = @io.peer_cert #: OpenSSL::X509::Certificate
|
|
151
163
|
|
|
152
164
|
"#{super}\n\n" \
|
|
153
165
|
"SSL connection using #{@io.ssl_version} / #{Array(@io.cipher).first}\n" \
|
data/lib/httpx/io/tcp.rb
CHANGED
|
@@ -23,18 +23,21 @@ module HTTPX
|
|
|
23
23
|
@fallback_protocol = @options.fallback_protocol
|
|
24
24
|
@port = origin.port
|
|
25
25
|
@interests = :w
|
|
26
|
-
if @options.io
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
|
|
26
|
+
if (io = @options.io)
|
|
27
|
+
io =
|
|
28
|
+
case io
|
|
29
|
+
when Hash
|
|
30
|
+
io[origin.authority]
|
|
31
|
+
else
|
|
32
|
+
io
|
|
33
|
+
end
|
|
34
|
+
raise Error, "Given IO objects do not match the request authority" unless io
|
|
35
|
+
|
|
36
|
+
# @type var io: TCPSocket | OpenSSL::SSL::SSLSocket
|
|
37
|
+
|
|
38
|
+
_, _, _, ip = io.addr
|
|
39
|
+
@io = io
|
|
40
|
+
@addresses << (@ip = Resolver::Entry.new(ip))
|
|
38
41
|
@keep_open = true
|
|
39
42
|
@state = :connected
|
|
40
43
|
else
|
|
@@ -183,6 +186,9 @@ module HTTPX
|
|
|
183
186
|
|
|
184
187
|
begin
|
|
185
188
|
@io.close
|
|
189
|
+
rescue StandardError => e
|
|
190
|
+
log { "error closing socket" }
|
|
191
|
+
log { e.full_message(highlight: false) }
|
|
186
192
|
ensure
|
|
187
193
|
transition(:closed)
|
|
188
194
|
end
|
data/lib/httpx/io/unix.rb
CHANGED
|
@@ -14,16 +14,20 @@ module HTTPX
|
|
|
14
14
|
@state = :idle
|
|
15
15
|
@options = options
|
|
16
16
|
@fallback_protocol = @options.fallback_protocol
|
|
17
|
-
if @options.io
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
if (io = @options.io)
|
|
18
|
+
io =
|
|
19
|
+
case io
|
|
20
|
+
when Hash
|
|
21
|
+
io[origin.authority]
|
|
22
|
+
else
|
|
23
|
+
io
|
|
24
|
+
end
|
|
25
|
+
raise Error, "Given IO objects do not match the request authority" unless io
|
|
26
|
+
|
|
27
|
+
# @type var io: UNIXSocket
|
|
25
28
|
|
|
26
|
-
@path =
|
|
29
|
+
_, @path = io.addr
|
|
30
|
+
@io = io
|
|
27
31
|
@keep_open = true
|
|
28
32
|
@state = :connected
|
|
29
33
|
elsif path
|
data/lib/httpx/options.rb
CHANGED
|
@@ -202,16 +202,19 @@ module HTTPX
|
|
|
202
202
|
|
|
203
203
|
REQUEST_BODY_IVARS = %i[@headers].freeze
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
205
|
+
# checks whether +other+ matches the same connection-level options
|
|
206
|
+
def connection_options_match?(other, ignore_ivars = nil)
|
|
207
|
+
return true if self == other
|
|
208
208
|
|
|
209
|
-
# checks whether +other+ is equal by comparing the session options
|
|
210
|
-
def options_equals?(other, ignore_ivars = REQUEST_BODY_IVARS)
|
|
211
209
|
# headers and other request options do not play a role, as they are
|
|
212
210
|
# relevant only for the request.
|
|
213
|
-
ivars = instance_variables
|
|
214
|
-
|
|
211
|
+
ivars = instance_variables
|
|
212
|
+
ivars.reject! { |iv| REQUEST_BODY_IVARS.include?(iv) }
|
|
213
|
+
ivars.reject! { |iv| ignore_ivars.include?(iv) } if ignore_ivars
|
|
214
|
+
|
|
215
|
+
other_ivars = other.instance_variables
|
|
216
|
+
other_ivars.reject! { |iv| REQUEST_BODY_IVARS.include?(iv) }
|
|
217
|
+
other_ivars.reject! { |iv| ignore_ivars.include?(iv) } if ignore_ivars
|
|
215
218
|
|
|
216
219
|
return false if ivars.size != other_ivars.size
|
|
217
220
|
|
|
@@ -222,6 +225,19 @@ module HTTPX
|
|
|
222
225
|
end
|
|
223
226
|
end
|
|
224
227
|
|
|
228
|
+
RESOLVER_IVARS = %i[
|
|
229
|
+
@resolver_class @resolver_cache @resolver_options
|
|
230
|
+
@resolver_native_class @resolver_system_class @resolver_https_class
|
|
231
|
+
].freeze
|
|
232
|
+
|
|
233
|
+
# checks whether +other+ matches the same resolver-level options
|
|
234
|
+
def resolver_options_match?(other)
|
|
235
|
+
self == other ||
|
|
236
|
+
RESOLVER_IVARS.all? do |ivar|
|
|
237
|
+
instance_variable_get(ivar) == other.instance_variable_get(ivar)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
225
241
|
# returns a HTTPX::Options instance resulting of the merging of +other+ with self.
|
|
226
242
|
# it may return self if +other+ is self or equal to self.
|
|
227
243
|
def merge(other)
|