httpx 1.7.5 → 1.7.7
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/doc/release_notes/1_7_4.md +1 -1
- data/doc/release_notes/1_7_6.md +24 -0
- data/doc/release_notes/1_7_7.md +17 -0
- data/lib/httpx/adapters/datadog.rb +14 -1
- data/lib/httpx/adapters/faraday.rb +0 -10
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/altsvc.rb +4 -2
- data/lib/httpx/connection/http1.rb +52 -43
- data/lib/httpx/connection/http2.rb +23 -16
- data/lib/httpx/connection.rb +80 -43
- data/lib/httpx/io/ssl.rb +24 -12
- data/lib/httpx/io/tcp.rb +18 -12
- data/lib/httpx/io/unix.rb +13 -9
- data/lib/httpx/loggable.rb +1 -1
- data/lib/httpx/options.rb +23 -7
- data/lib/httpx/parser/http1.rb +14 -5
- data/lib/httpx/plugins/auth/digest.rb +6 -0
- data/lib/httpx/plugins/auth.rb +23 -9
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +1 -0
- data/lib/httpx/plugins/cookies/cookie.rb +0 -1
- data/lib/httpx/plugins/digest_auth.rb +3 -1
- data/lib/httpx/plugins/follow_redirects.rb +13 -1
- data/lib/httpx/plugins/h2c.rb +2 -12
- data/lib/httpx/plugins/proxy/http.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +1 -1
- data/lib/httpx/plugins/response_cache.rb +11 -4
- data/lib/httpx/plugins/retries.rb +6 -6
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/tracing.rb +1 -6
- data/lib/httpx/plugins/upgrade/h2.rb +1 -11
- data/lib/httpx/plugins/upgrade.rb +17 -17
- data/lib/httpx/pool.rb +7 -9
- data/lib/httpx/request.rb +28 -3
- data/lib/httpx/resolver/native.rb +1 -1
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +18 -10
- data/lib/httpx/session.rb +32 -24
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +2 -0
- data/sig/connection/http1.rbs +4 -2
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +9 -2
- data/sig/io/ssl.rbs +1 -0
- data/sig/io/tcp.rbs +2 -2
- data/sig/loggable.rbs +1 -1
- data/sig/options.rbs +8 -3
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth/basic.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +1 -1
- data/sig/plugins/auth/ntlm.rbs +2 -0
- data/sig/plugins/auth.rbs +5 -2
- data/sig/plugins/follow_redirects.rbs +1 -1
- data/sig/plugins/proxy.rbs +1 -0
- data/sig/plugins/response_cache.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/tracing.rbs +1 -1
- data/sig/pool.rbs +1 -1
- data/sig/request.rbs +6 -0
- data/sig/session.rbs +0 -2
- metadata +5 -1
data/lib/httpx/connection.rb
CHANGED
|
@@ -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+.
|
|
@@ -287,6 +288,10 @@ module HTTPX
|
|
|
287
288
|
def reset
|
|
288
289
|
return if @state == :closing || @state == :closed
|
|
289
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
|
+
|
|
290
295
|
parser = @parser
|
|
291
296
|
|
|
292
297
|
if parser && parser.respond_to?(:max_concurrent_requests)
|
|
@@ -313,8 +318,8 @@ module HTTPX
|
|
|
313
318
|
log(level: 3) { "keep alive timeout expired, pinging connection..." }
|
|
314
319
|
@pending << request
|
|
315
320
|
transition(:active) if @state == :inactive
|
|
316
|
-
parser.ping
|
|
317
321
|
request.ping!
|
|
322
|
+
ping
|
|
318
323
|
return
|
|
319
324
|
end
|
|
320
325
|
|
|
@@ -455,11 +460,11 @@ module HTTPX
|
|
|
455
460
|
#
|
|
456
461
|
# this condition takes into account:
|
|
457
462
|
#
|
|
458
|
-
# * the number of inflight requests
|
|
459
463
|
# * the number of pending requests
|
|
464
|
+
# * the number of inflight requests
|
|
460
465
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
461
466
|
if @pending.empty? && @inflight.zero? && @write_buffer.empty?
|
|
462
|
-
|
|
467
|
+
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
463
468
|
|
|
464
469
|
# terminate if an altsvc connection has been established
|
|
465
470
|
terminate if @altsvc_connection
|
|
@@ -509,7 +514,7 @@ module HTTPX
|
|
|
509
514
|
|
|
510
515
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
511
516
|
if @pending.empty? && @inflight.zero? && @write_buffer.empty? # rubocop:disable Style/Next
|
|
512
|
-
|
|
517
|
+
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
513
518
|
|
|
514
519
|
# terminate if an altsvc connection has been established
|
|
515
520
|
terminate if @altsvc_connection
|
|
@@ -635,15 +640,16 @@ module HTTPX
|
|
|
635
640
|
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
636
641
|
end
|
|
637
642
|
@response_received_at = Utils.now
|
|
643
|
+
@no_more_requests_counter = 0
|
|
638
644
|
@inflight -= 1
|
|
639
645
|
response.finish!
|
|
640
|
-
request.
|
|
646
|
+
request.emit_response(response)
|
|
641
647
|
end
|
|
642
648
|
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
|
643
649
|
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
644
650
|
end
|
|
645
651
|
|
|
646
|
-
parser.on(:pong, &method(:
|
|
652
|
+
parser.on(:pong, &method(:pong))
|
|
647
653
|
|
|
648
654
|
parser.on(:promise) do |request, stream|
|
|
649
655
|
request.emit(:promise, parser, stream)
|
|
@@ -701,7 +707,7 @@ module HTTPX
|
|
|
701
707
|
@inflight -= 1
|
|
702
708
|
response = ErrorResponse.new(request, error)
|
|
703
709
|
request.response = response
|
|
704
|
-
request.
|
|
710
|
+
request.emit_response(response)
|
|
705
711
|
end
|
|
706
712
|
end
|
|
707
713
|
|
|
@@ -755,16 +761,6 @@ module HTTPX
|
|
|
755
761
|
return if @inflight.positive? || @parser.waiting_for_ping?
|
|
756
762
|
when :closing
|
|
757
763
|
return unless connecting? || @state == :open
|
|
758
|
-
|
|
759
|
-
unless @write_buffer.empty?
|
|
760
|
-
# preset state before handshake, as error callbacks
|
|
761
|
-
# may take it back here.
|
|
762
|
-
@state = nextstate
|
|
763
|
-
# handshakes, try sending
|
|
764
|
-
consume
|
|
765
|
-
@write_buffer.clear
|
|
766
|
-
return
|
|
767
|
-
end
|
|
768
764
|
when :closed
|
|
769
765
|
return unless @state == :closing
|
|
770
766
|
return unless @write_buffer.empty?
|
|
@@ -790,6 +786,12 @@ module HTTPX
|
|
|
790
786
|
case nextstate
|
|
791
787
|
when :inactive
|
|
792
788
|
disconnect
|
|
789
|
+
when :closing
|
|
790
|
+
return if @write_buffer.empty?
|
|
791
|
+
|
|
792
|
+
# try flushing termination handshakes
|
|
793
|
+
consume
|
|
794
|
+
@write_buffer.clear
|
|
793
795
|
when :closed
|
|
794
796
|
# TODO: should this raise an error instead?
|
|
795
797
|
return unless @pending.empty?
|
|
@@ -813,16 +815,18 @@ module HTTPX
|
|
|
813
815
|
end
|
|
814
816
|
|
|
815
817
|
def close_sibling
|
|
816
|
-
|
|
818
|
+
sibling = @sibling
|
|
817
819
|
|
|
818
|
-
|
|
820
|
+
return unless sibling
|
|
821
|
+
|
|
822
|
+
if sibling.io_connected?
|
|
819
823
|
reset
|
|
820
824
|
# TODO: transition connection to closed
|
|
821
825
|
end
|
|
822
826
|
|
|
823
|
-
unless
|
|
824
|
-
merge(
|
|
825
|
-
|
|
827
|
+
unless sibling.state == :closed
|
|
828
|
+
merge(sibling) unless @main_sibling
|
|
829
|
+
sibling.force_reset(true)
|
|
826
830
|
end
|
|
827
831
|
|
|
828
832
|
@sibling = nil
|
|
@@ -902,28 +906,61 @@ module HTTPX
|
|
|
902
906
|
end
|
|
903
907
|
end
|
|
904
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
|
+
|
|
905
933
|
# recover internal state and emit all relevant error responses when +error+ was raised.
|
|
906
934
|
# this takes an optiona +request+ which may have already been handled and can be opted out
|
|
907
935
|
# in the state recovery process.
|
|
908
936
|
def handle_error(error, request = nil)
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
937
|
+
if request
|
|
938
|
+
@inflight -= 1
|
|
939
|
+
response = ErrorResponse.new(request, error)
|
|
940
|
+
request.response = response
|
|
941
|
+
request.emit_response(response)
|
|
942
|
+
end
|
|
912
943
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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 = []
|
|
949
|
+
|
|
950
|
+
parser.handle_error(error, request)
|
|
916
951
|
end
|
|
917
952
|
|
|
918
|
-
|
|
953
|
+
while (req = pending.shift)
|
|
954
|
+
next if request && req == request
|
|
919
955
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
956
|
+
resp = ErrorResponse.new(req, error)
|
|
957
|
+
req.response = resp
|
|
958
|
+
req.emit_response(resp)
|
|
959
|
+
end
|
|
924
960
|
end
|
|
925
961
|
|
|
926
962
|
def set_request_timeouts(request)
|
|
963
|
+
request.connection = self
|
|
927
964
|
set_request_write_timeout(request)
|
|
928
965
|
set_request_read_timeout(request)
|
|
929
966
|
set_request_request_timeout(request)
|
|
@@ -959,29 +996,29 @@ module HTTPX
|
|
|
959
996
|
end
|
|
960
997
|
end
|
|
961
998
|
|
|
962
|
-
def write_timeout_callback(request,
|
|
999
|
+
def write_timeout_callback(request, timeout)
|
|
963
1000
|
return if request.state == :done
|
|
964
1001
|
|
|
965
1002
|
@write_buffer.clear
|
|
966
|
-
error = WriteTimeoutError.new(request, nil,
|
|
1003
|
+
error = WriteTimeoutError.new(request, nil, timeout)
|
|
967
1004
|
|
|
968
|
-
|
|
1005
|
+
request.handle_error(error)
|
|
969
1006
|
end
|
|
970
1007
|
|
|
971
|
-
def read_timeout_callback(request,
|
|
1008
|
+
def read_timeout_callback(request, timeout, error_type = ReadTimeoutError)
|
|
972
1009
|
response = request.response
|
|
973
1010
|
|
|
974
1011
|
return if response && response.finished?
|
|
975
1012
|
|
|
976
1013
|
@write_buffer.clear
|
|
977
|
-
error = error_type.new(request,
|
|
1014
|
+
error = error_type.new(request, response, timeout)
|
|
978
1015
|
|
|
979
|
-
|
|
1016
|
+
request.handle_error(error)
|
|
980
1017
|
end
|
|
981
1018
|
|
|
982
1019
|
def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
|
|
983
1020
|
request.set_timeout_callback(start_event) do
|
|
984
|
-
unless @current_selector
|
|
1021
|
+
unless (selector = @current_selector)
|
|
985
1022
|
raise Error, "request has been resend to an out-of-session connection, and this " \
|
|
986
1023
|
"should never happen!!! Please report this error! " \
|
|
987
1024
|
"(state:#{@state}, " \
|
|
@@ -992,7 +1029,7 @@ module HTTPX
|
|
|
992
1029
|
"coalesced?:#{coalesced?})"
|
|
993
1030
|
end
|
|
994
1031
|
|
|
995
|
-
timer =
|
|
1032
|
+
timer = selector.after(timeout, callback)
|
|
996
1033
|
request.active_timeouts << label
|
|
997
1034
|
|
|
998
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,16 +157,18 @@ 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" \
|
|
154
166
|
"ALPN, server accepted to use #{protocol}\n" \
|
|
155
167
|
"Server certificate:\n " \
|
|
156
|
-
"subject: #{server_cert.subject}\n " \
|
|
157
|
-
"start date: #{server_cert.not_before}\n " \
|
|
158
|
-
"expire date: #{server_cert.not_after}\n " \
|
|
159
|
-
"issuer: #{server_cert.issuer}\n " \
|
|
168
|
+
"subject: #{log_redact(server_cert.subject)}\n " \
|
|
169
|
+
"start date: #{log_redact(server_cert.not_before)}\n " \
|
|
170
|
+
"expire date: #{log_redact(server_cert.not_after)}\n " \
|
|
171
|
+
"issuer: #{log_redact(server_cert.issuer)}\n " \
|
|
160
172
|
"SSL certificate verify ok."
|
|
161
173
|
end
|
|
162
174
|
end
|
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/loggable.rb
CHANGED
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)
|
data/lib/httpx/parser/http1.rb
CHANGED
|
@@ -14,6 +14,8 @@ module HTTPX
|
|
|
14
14
|
@state = :idle
|
|
15
15
|
@buffer = "".b
|
|
16
16
|
@headers = {}
|
|
17
|
+
@content_length = nil
|
|
18
|
+
@_has_trailers = @upgrade = false
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def <<(chunk)
|
|
@@ -25,8 +27,8 @@ module HTTPX
|
|
|
25
27
|
@state = :idle
|
|
26
28
|
@headers = {}
|
|
27
29
|
@content_length = nil
|
|
28
|
-
@_has_trailers =
|
|
29
|
-
@buffer.
|
|
30
|
+
@_has_trailers = @upgrade = false
|
|
31
|
+
@buffer = @buffer.to_s
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def upgrade?
|
|
@@ -34,7 +36,7 @@ module HTTPX
|
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def upgrade_data
|
|
37
|
-
@buffer
|
|
39
|
+
@buffer.to_s
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
private
|
|
@@ -55,6 +57,7 @@ module HTTPX
|
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def parse_headline
|
|
60
|
+
#: @type ivar @buffer: String
|
|
58
61
|
idx = @buffer.index("\n")
|
|
59
62
|
return unless idx
|
|
60
63
|
|
|
@@ -75,6 +78,8 @@ module HTTPX
|
|
|
75
78
|
headers = @headers
|
|
76
79
|
buffer = @buffer
|
|
77
80
|
|
|
81
|
+
#: @type var buffer: String
|
|
82
|
+
|
|
78
83
|
while (idx = buffer.index("\n"))
|
|
79
84
|
# @type var line: String
|
|
80
85
|
line = buffer.byteslice(0..idx)
|
|
@@ -118,17 +123,20 @@ module HTTPX
|
|
|
118
123
|
|
|
119
124
|
def parse_data
|
|
120
125
|
if @buffer.respond_to?(:each)
|
|
126
|
+
# @type ivar @buffer: Transcoder::Chunker::Decoder
|
|
121
127
|
@buffer.each do |chunk|
|
|
122
128
|
@observer.on_data(chunk)
|
|
123
129
|
end
|
|
124
130
|
elsif @content_length
|
|
125
|
-
# @type
|
|
131
|
+
# @type ivar @buffer: String
|
|
126
132
|
data = @buffer.byteslice(0, @content_length)
|
|
133
|
+
# @type var data: String
|
|
127
134
|
@buffer = @buffer.byteslice(@content_length..-1) || "".b
|
|
128
135
|
@content_length -= data.bytesize
|
|
129
136
|
@observer.on_data(data)
|
|
130
137
|
data.clear
|
|
131
138
|
else
|
|
139
|
+
# @type ivar @buffer: String
|
|
132
140
|
@observer.on_data(@buffer)
|
|
133
141
|
@buffer.clear
|
|
134
142
|
end
|
|
@@ -152,7 +160,7 @@ module HTTPX
|
|
|
152
160
|
tr_encoding.split(/ *, */).each do |encoding|
|
|
153
161
|
case encoding
|
|
154
162
|
when "chunked"
|
|
155
|
-
@buffer = Transcoder::Chunker::Decoder.new(@buffer, @_has_trailers)
|
|
163
|
+
@buffer = Transcoder::Chunker::Decoder.new(@buffer.to_s, @_has_trailers)
|
|
156
164
|
end
|
|
157
165
|
end
|
|
158
166
|
end
|
|
@@ -165,6 +173,7 @@ module HTTPX
|
|
|
165
173
|
if @content_length
|
|
166
174
|
@content_length <= 0
|
|
167
175
|
elsif @buffer.respond_to?(:finished?)
|
|
176
|
+
# @type ivar @buffer: Transcoder::Chunker::Decoder
|
|
168
177
|
@buffer.finished?
|
|
169
178
|
else
|
|
170
179
|
false
|
|
@@ -24,6 +24,11 @@ module HTTPX
|
|
|
24
24
|
|
|
25
25
|
def authenticate(request, authenticate)
|
|
26
26
|
"Digest #{generate_header(request.verb, request.path, authenticate)}"
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
response = ErrorResponse.new(request, e)
|
|
29
|
+
request.response = response
|
|
30
|
+
request.emit_response(response)
|
|
31
|
+
nil
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
private
|
|
@@ -77,6 +82,7 @@ module HTTPX
|
|
|
77
82
|
|
|
78
83
|
if params["algorithm"] =~ /(.*?)(-sess)?$/
|
|
79
84
|
alg = Regexp.last_match(1)
|
|
85
|
+
raise_format_error unless alg
|
|
80
86
|
algorithm = ::Digest.const_get(alg)
|
|
81
87
|
raise Error, "unknown algorithm \"#{alg}\"" unless algorithm
|
|
82
88
|
|
data/lib/httpx/plugins/auth.rb
CHANGED
|
@@ -44,6 +44,7 @@ module HTTPX
|
|
|
44
44
|
super
|
|
45
45
|
|
|
46
46
|
@auth_header_value = nil
|
|
47
|
+
@auth_header_value_mtx = Thread::Mutex.new
|
|
47
48
|
@skip_auth_header_value = false
|
|
48
49
|
end
|
|
49
50
|
|
|
@@ -63,7 +64,9 @@ module HTTPX
|
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
def reset_auth_header_value!
|
|
66
|
-
@
|
|
67
|
+
@auth_header_value_mtx.synchronize do
|
|
68
|
+
@auth_header_value = nil
|
|
69
|
+
end
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
private
|
|
@@ -71,9 +74,11 @@ module HTTPX
|
|
|
71
74
|
def send_request(request, *)
|
|
72
75
|
return super if @skip_auth_header_value || request.authorized?
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
auth_header_value = @auth_header_value_mtx.synchronize do
|
|
78
|
+
@auth_header_value ||= generate_auth_token
|
|
79
|
+
end
|
|
75
80
|
|
|
76
|
-
request.authorize(
|
|
81
|
+
request.authorize(auth_header_value) if auth_header_value
|
|
77
82
|
|
|
78
83
|
super
|
|
79
84
|
end
|
|
@@ -92,9 +97,11 @@ module HTTPX
|
|
|
92
97
|
end
|
|
93
98
|
|
|
94
99
|
module RequestMethods
|
|
100
|
+
attr_reader :auth_token_value
|
|
101
|
+
|
|
95
102
|
def initialize(*)
|
|
96
103
|
super
|
|
97
|
-
@auth_token_value = nil
|
|
104
|
+
@auth_token_value = @auth_header_value = nil
|
|
98
105
|
end
|
|
99
106
|
|
|
100
107
|
def authorized?
|
|
@@ -102,19 +109,20 @@ module HTTPX
|
|
|
102
109
|
end
|
|
103
110
|
|
|
104
111
|
def unauthorize!
|
|
105
|
-
return unless (auth_value = @
|
|
112
|
+
return unless (auth_value = @auth_header_value)
|
|
106
113
|
|
|
107
114
|
@headers.get("authorization").delete(auth_value)
|
|
108
115
|
|
|
109
|
-
@auth_token_value = nil
|
|
116
|
+
@auth_token_value = @auth_header_value = nil
|
|
110
117
|
end
|
|
111
118
|
|
|
112
119
|
def authorize(auth_value)
|
|
120
|
+
@auth_header_value = auth_value
|
|
113
121
|
if (auth_type = @options.auth_header_type)
|
|
114
|
-
|
|
122
|
+
@auth_header_value = "#{auth_type} #{@auth_header_value}"
|
|
115
123
|
end
|
|
116
124
|
|
|
117
|
-
@headers.add("authorization",
|
|
125
|
+
@headers.add("authorization", @auth_header_value)
|
|
118
126
|
|
|
119
127
|
@auth_token_value = auth_value
|
|
120
128
|
end
|
|
@@ -138,8 +146,14 @@ module HTTPX
|
|
|
138
146
|
return unless auth_error?(response, request.options) ||
|
|
139
147
|
(@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response))
|
|
140
148
|
|
|
149
|
+
# regenerate token before retry, but only if it's the first request from batch failing.
|
|
150
|
+
# otherwise, it means that the first request already passed here, so this request should
|
|
151
|
+
# use whatever was generated for it.
|
|
152
|
+
@auth_header_value_mtx.synchronize do
|
|
153
|
+
@auth_header_value = generate_auth_token if request.auth_token_value == @auth_header_value
|
|
154
|
+
end
|
|
155
|
+
|
|
141
156
|
request.unauthorize!
|
|
142
|
-
@auth_header_value = generate_auth_token
|
|
143
157
|
end
|
|
144
158
|
|
|
145
159
|
def auth_error?(response, options)
|