httpx 1.7.8 → 1.8.1
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_1.md +1 -2
- data/doc/release_notes/1_8_0.md +100 -0
- data/doc/release_notes/1_8_1.md +27 -0
- data/lib/httpx/adapters/datadog.rb +12 -1
- data/lib/httpx/adapters/faraday.rb +2 -2
- data/lib/httpx/adapters/webmock.rb +6 -0
- data/lib/httpx/connection/http1.rb +15 -12
- data/lib/httpx/connection/http2.rb +69 -9
- data/lib/httpx/connection.rb +119 -21
- data/lib/httpx/errors.rb +8 -1
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +8 -9
- data/lib/httpx/io/tcp.rb +22 -5
- data/lib/httpx/io/unix.rb +2 -2
- data/lib/httpx/loggable.rb +11 -4
- data/lib/httpx/options.rb +16 -4
- data/lib/httpx/parser/http1.rb +8 -2
- data/lib/httpx/plugins/auth/digest.rb +1 -1
- data/lib/httpx/plugins/auth.rb +61 -4
- data/lib/httpx/plugins/{response_cache → cache}/file_store.rb +1 -1
- data/lib/httpx/plugins/{response_cache → cache}/store.rb +1 -1
- data/lib/httpx/plugins/cache.rb +221 -0
- data/lib/httpx/plugins/callbacks.rb +12 -0
- data/lib/httpx/plugins/digest_auth.rb +4 -0
- data/lib/httpx/plugins/fiber_concurrency.rb +75 -4
- data/lib/httpx/plugins/ntlm_v2_auth.rb +92 -0
- data/lib/httpx/plugins/oauth.rb +66 -14
- data/lib/httpx/plugins/persistent.rb +18 -2
- data/lib/httpx/plugins/proxy.rb +5 -0
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/response_cache.rb +26 -105
- data/lib/httpx/plugins/retries.rb +7 -5
- data/lib/httpx/plugins/server_sent_events.rb +158 -0
- data/lib/httpx/plugins/ssrf_filter.rb +17 -2
- data/lib/httpx/plugins/stream.rb +9 -5
- data/lib/httpx/plugins/stream_bidi.rb +2 -0
- data/lib/httpx/plugins/tracing.rb +15 -4
- data/lib/httpx/pool.rb +2 -2
- data/lib/httpx/request/body.rb +2 -2
- data/lib/httpx/request.rb +42 -10
- data/lib/httpx/resolver/cache/base.rb +1 -8
- data/lib/httpx/resolver/cache/file.rb +56 -0
- data/lib/httpx/resolver/https.rb +20 -20
- data/lib/httpx/resolver/native.rb +21 -3
- data/lib/httpx/resolver/resolver.rb +4 -0
- data/lib/httpx/resolver/system.rb +5 -2
- data/lib/httpx/response/body.rb +6 -4
- data/lib/httpx/response.rb +12 -5
- data/lib/httpx/selector.rb +12 -1
- data/lib/httpx/session.rb +6 -1
- data/lib/httpx/session_extensions.rb +2 -2
- data/lib/httpx/timers.rb +3 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/chainable.rbs +3 -0
- data/sig/connection/http1.rbs +1 -1
- data/sig/connection/http2.rbs +4 -1
- data/sig/connection.rbs +16 -9
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +2 -0
- data/sig/io/tcp.rbs +2 -0
- data/sig/loggable.rbs +8 -0
- data/sig/options.rbs +25 -12
- data/sig/parser/http1.rbs +3 -1
- data/sig/plugins/auth/ntlm.rbs +1 -1
- data/sig/plugins/auth.rbs +4 -0
- data/sig/plugins/{response_cache → cache}/file_store.rbs +2 -2
- data/sig/plugins/{response_cache → cache}/store.rbs +2 -2
- data/sig/plugins/cache.rbs +69 -0
- data/sig/plugins/fiber_concurrency.rbs +4 -0
- data/sig/plugins/ntlm_v2_auth.rbs +36 -0
- data/sig/plugins/persistent.rbs +3 -0
- data/sig/plugins/response_cache.rbs +13 -38
- data/sig/plugins/retries.rbs +5 -5
- data/sig/plugins/server_sent_events.rbs +45 -0
- data/sig/plugins/ssrf_filter.rbs +5 -1
- data/sig/plugins/stream.rbs +1 -1
- data/sig/plugins/stream_bidi.rbs +0 -2
- data/sig/plugins/webdav.rbs +1 -1
- data/sig/pool.rbs +2 -2
- data/sig/request.rbs +9 -6
- data/sig/resolver/cache/file.rbs +13 -0
- data/sig/resolver/entry.rbs +1 -1
- data/sig/resolver/https.rbs +3 -3
- data/sig/resolver/multi.rbs +1 -1
- data/sig/resolver/native.rbs +5 -5
- data/sig/resolver/resolver.rbs +1 -3
- data/sig/resolver/system.rbs +5 -2
- data/sig/resolver.rbs +3 -0
- data/sig/response.rbs +3 -0
- data/sig/selector.rbs +13 -8
- data/sig/timers.rbs +7 -5
- data/sig/transcoder/body.rbs +1 -1
- data/sig/transcoder/gzip.rbs +3 -2
- data/sig/transcoder/multipart.rbs +4 -1
- data/sig/transcoder/utils/deflater.rbs +2 -0
- data/sig/transcoder.rbs +2 -0
- data/sig/utils.rbs +1 -1
- metadata +19 -7
data/lib/httpx/connection.rb
CHANGED
|
@@ -47,8 +47,8 @@ module HTTPX
|
|
|
47
47
|
def initialize(uri, options)
|
|
48
48
|
@current_session = @current_selector = @max_concurrent_requests =
|
|
49
49
|
@parser = @sibling = @coalesced_connection = @altsvc_connection =
|
|
50
|
-
@
|
|
51
|
-
|
|
50
|
+
@ping_timer = @family = @io = @ssl_session =
|
|
51
|
+
@timeout = @connected_at = @response_received_at = nil
|
|
52
52
|
|
|
53
53
|
@exhausted = @cloned = @main_sibling = false
|
|
54
54
|
|
|
@@ -161,15 +161,17 @@ module HTTPX
|
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
def purge_pending(&block)
|
|
164
|
-
pendings = []
|
|
165
164
|
if @parser
|
|
166
165
|
pending = @parser.pending
|
|
167
166
|
@inflight -= pending.size
|
|
168
|
-
|
|
167
|
+
pending.reject! do |req|
|
|
168
|
+
block.call(req)
|
|
169
|
+
true
|
|
170
|
+
end
|
|
169
171
|
end
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
@pending.reject! do |req|
|
|
173
|
+
block.call(req)
|
|
174
|
+
true
|
|
173
175
|
end
|
|
174
176
|
end
|
|
175
177
|
|
|
@@ -203,7 +205,7 @@ module HTTPX
|
|
|
203
205
|
return @parser.interests if @parser
|
|
204
206
|
|
|
205
207
|
nil
|
|
206
|
-
rescue
|
|
208
|
+
rescue Error => e
|
|
207
209
|
on_error(e)
|
|
208
210
|
nil
|
|
209
211
|
end
|
|
@@ -215,6 +217,8 @@ module HTTPX
|
|
|
215
217
|
def call
|
|
216
218
|
case @state
|
|
217
219
|
when :idle
|
|
220
|
+
return if no_more_requests?
|
|
221
|
+
|
|
218
222
|
connect
|
|
219
223
|
|
|
220
224
|
# when opening the tcp or ssl socket fails
|
|
@@ -222,18 +226,39 @@ module HTTPX
|
|
|
222
226
|
|
|
223
227
|
consume
|
|
224
228
|
when :closed
|
|
225
|
-
return
|
|
229
|
+
return if no_more_requests?
|
|
230
|
+
|
|
231
|
+
# there are pending requests to send, restart the state machine.
|
|
232
|
+
idling
|
|
233
|
+
|
|
234
|
+
# @fiber-switch-guard
|
|
235
|
+
# fiber may have switch after ensuring that @io is closed.
|
|
236
|
+
return unless @state == :idle
|
|
237
|
+
|
|
238
|
+
call
|
|
226
239
|
when :closing
|
|
227
240
|
consume
|
|
228
241
|
transition(:closed)
|
|
242
|
+
|
|
243
|
+
# @fiber-switch-guard
|
|
244
|
+
# fiber may have switch while closing @io.
|
|
245
|
+
return if @state == :closed &&
|
|
246
|
+
# only remain here if there are pending requests.
|
|
247
|
+
@pending.empty?
|
|
248
|
+
|
|
249
|
+
call
|
|
229
250
|
when :open
|
|
230
251
|
consume
|
|
231
252
|
end
|
|
232
253
|
nil
|
|
233
|
-
rescue
|
|
254
|
+
rescue Errno::ECONNRESET,
|
|
255
|
+
Errno::EINVAL,
|
|
256
|
+
SocketError,
|
|
257
|
+
IOError,
|
|
258
|
+
TLSError => e
|
|
234
259
|
@write_buffer.clear
|
|
235
260
|
on_io_error(e)
|
|
236
|
-
rescue
|
|
261
|
+
rescue Error => e
|
|
237
262
|
@write_buffer.clear
|
|
238
263
|
on_error(e)
|
|
239
264
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
@@ -241,6 +266,10 @@ module HTTPX
|
|
|
241
266
|
raise e
|
|
242
267
|
end
|
|
243
268
|
|
|
269
|
+
def initial_call
|
|
270
|
+
call
|
|
271
|
+
end
|
|
272
|
+
|
|
244
273
|
def close
|
|
245
274
|
transition(:active) if @state == :inactive
|
|
246
275
|
|
|
@@ -251,7 +280,12 @@ module HTTPX
|
|
|
251
280
|
case @state
|
|
252
281
|
when :idle
|
|
253
282
|
purge_after_closed
|
|
254
|
-
|
|
283
|
+
|
|
284
|
+
# @fiber-switch-guard
|
|
285
|
+
if @io.can_disconnect? && @pending.empty?
|
|
286
|
+
disconnect
|
|
287
|
+
return
|
|
288
|
+
end
|
|
255
289
|
when :closed
|
|
256
290
|
@connected_at = nil
|
|
257
291
|
end
|
|
@@ -292,6 +326,11 @@ module HTTPX
|
|
|
292
326
|
# (example: HTTP/1 parser disabling pipelining)
|
|
293
327
|
return if @state == :idle && @pending.any?
|
|
294
328
|
|
|
329
|
+
if @ping_timer
|
|
330
|
+
@ping_timer.cancel
|
|
331
|
+
@ping_timer = nil
|
|
332
|
+
end
|
|
333
|
+
|
|
295
334
|
parser = @parser
|
|
296
335
|
|
|
297
336
|
if parser && parser.respond_to?(:max_concurrent_requests)
|
|
@@ -319,7 +358,7 @@ module HTTPX
|
|
|
319
358
|
@pending << request
|
|
320
359
|
transition(:active) if @state == :inactive
|
|
321
360
|
request.ping!
|
|
322
|
-
ping
|
|
361
|
+
ping(request)
|
|
323
362
|
return
|
|
324
363
|
end
|
|
325
364
|
|
|
@@ -341,6 +380,9 @@ module HTTPX
|
|
|
341
380
|
|
|
342
381
|
def idling
|
|
343
382
|
purge_after_closed
|
|
383
|
+
|
|
384
|
+
return unless @state == :closed
|
|
385
|
+
|
|
344
386
|
@write_buffer.clear
|
|
345
387
|
transition(:idle)
|
|
346
388
|
return unless @parser
|
|
@@ -408,6 +450,11 @@ module HTTPX
|
|
|
408
450
|
|
|
409
451
|
def on_io_error(e)
|
|
410
452
|
on_error(e)
|
|
453
|
+
|
|
454
|
+
# do not force close if parser resets the connection.
|
|
455
|
+
# can happen i.e. when HTTP/1.1 pipelining is disabled.
|
|
456
|
+
return if @state == :idle && @pending.any?
|
|
457
|
+
|
|
411
458
|
force_close(true)
|
|
412
459
|
end
|
|
413
460
|
|
|
@@ -421,6 +468,8 @@ module HTTPX
|
|
|
421
468
|
if @timeout
|
|
422
469
|
@timeout -= error.timeout
|
|
423
470
|
return unless @timeout <= 0
|
|
471
|
+
|
|
472
|
+
@timeout = nil
|
|
424
473
|
end
|
|
425
474
|
|
|
426
475
|
error = error.to_connection_error if connecting?
|
|
@@ -429,7 +478,7 @@ module HTTPX
|
|
|
429
478
|
reset
|
|
430
479
|
end
|
|
431
480
|
|
|
432
|
-
# :
|
|
481
|
+
# simplecov:disable
|
|
433
482
|
def inspect
|
|
434
483
|
"#<#{self.class}:#{object_id} " \
|
|
435
484
|
"@origin=#{@origin} " \
|
|
@@ -437,7 +486,7 @@ module HTTPX
|
|
|
437
486
|
"@pending=#{@pending.size} " \
|
|
438
487
|
"@io=#{@io}>"
|
|
439
488
|
end
|
|
440
|
-
# :
|
|
489
|
+
# simplecov:enable
|
|
441
490
|
|
|
442
491
|
private
|
|
443
492
|
|
|
@@ -463,7 +512,7 @@ module HTTPX
|
|
|
463
512
|
# * the number of pending requests
|
|
464
513
|
# * the number of inflight requests
|
|
465
514
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
466
|
-
if
|
|
515
|
+
if no_more_requests? && @write_buffer.empty?
|
|
467
516
|
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
468
517
|
|
|
469
518
|
# terminate if an altsvc connection has been established
|
|
@@ -513,7 +562,7 @@ module HTTPX
|
|
|
513
562
|
break if @state == :closing || @state == :closed
|
|
514
563
|
|
|
515
564
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
516
|
-
if
|
|
565
|
+
if no_more_requests? && @write_buffer.empty? # rubocop:disable Style/Next
|
|
517
566
|
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
518
567
|
|
|
519
568
|
# terminate if an altsvc connection has been established
|
|
@@ -660,7 +709,12 @@ module HTTPX
|
|
|
660
709
|
@exhausted = true
|
|
661
710
|
parser.close
|
|
662
711
|
|
|
712
|
+
# @fiber-switch-guard
|
|
713
|
+
# fiber may have switched while closing @io, check whether still in the exhausted loop.
|
|
714
|
+
next unless @exhausted
|
|
715
|
+
|
|
663
716
|
idling
|
|
717
|
+
|
|
664
718
|
@exhausted = false
|
|
665
719
|
end
|
|
666
720
|
parser.on(:origin) do |origin|
|
|
@@ -676,6 +730,9 @@ module HTTPX
|
|
|
676
730
|
enqueue_pending_requests_from_parser(parser)
|
|
677
731
|
|
|
678
732
|
reset
|
|
733
|
+
|
|
734
|
+
next unless @state == :closed
|
|
735
|
+
|
|
679
736
|
# :reset event only fired in http/1.1, so this guarantees
|
|
680
737
|
# that the connection will be closed here.
|
|
681
738
|
idling unless @pending.empty?
|
|
@@ -766,6 +823,9 @@ module HTTPX
|
|
|
766
823
|
return unless @write_buffer.empty?
|
|
767
824
|
|
|
768
825
|
purge_after_closed
|
|
826
|
+
|
|
827
|
+
# @fiber-switch-guard
|
|
828
|
+
return unless @state == :closing && (@io.nil? || @io.can_disconnect?)
|
|
769
829
|
when :already_open
|
|
770
830
|
nextstate = :open
|
|
771
831
|
# the first check for given io readiness must still use a timeout.
|
|
@@ -833,7 +893,15 @@ module HTTPX
|
|
|
833
893
|
end
|
|
834
894
|
|
|
835
895
|
def purge_after_closed
|
|
836
|
-
|
|
896
|
+
if @io
|
|
897
|
+
@io.close
|
|
898
|
+
|
|
899
|
+
# @fiber-switch-guard
|
|
900
|
+
# due to fiber scheduler, multiple fibers may be listening on the same connection
|
|
901
|
+
# and moving the state machine forward; in such cases, when the control flow reaches
|
|
902
|
+
# this line, the io object may not be closed anymore.
|
|
903
|
+
return unless @io&.can_disconnect?
|
|
904
|
+
end
|
|
837
905
|
@read_buffer.clear
|
|
838
906
|
@timeout = nil
|
|
839
907
|
end
|
|
@@ -906,14 +974,25 @@ module HTTPX
|
|
|
906
974
|
end
|
|
907
975
|
end
|
|
908
976
|
|
|
909
|
-
def ping
|
|
977
|
+
def ping(_request)
|
|
910
978
|
return if parser.waiting_for_ping?
|
|
911
979
|
|
|
912
980
|
parser.ping
|
|
981
|
+
|
|
982
|
+
ping_timeout = @options.timeout[:ping_timeout]
|
|
983
|
+
|
|
984
|
+
@ping_timer = @current_selector.after(ping_timeout) do
|
|
985
|
+
log(level: 3) { "ping timeout expired..." }
|
|
986
|
+
error = PingTimeoutError.new(ping_timeout, "Timed out after #{ping_timeout} seconds")
|
|
987
|
+
on_error(error)
|
|
988
|
+
end
|
|
989
|
+
|
|
913
990
|
call
|
|
914
991
|
end
|
|
915
992
|
|
|
916
993
|
def pong
|
|
994
|
+
@ping_timer.cancel
|
|
995
|
+
@ping_timer = nil
|
|
917
996
|
@response_received_at = Utils.now
|
|
918
997
|
@no_more_requests_counter = 0
|
|
919
998
|
send_pending
|
|
@@ -930,6 +1009,11 @@ module HTTPX
|
|
|
930
1009
|
"along with debug logs"
|
|
931
1010
|
end
|
|
932
1011
|
|
|
1012
|
+
# true when there are no more pending nor inflight (in parser) requests
|
|
1013
|
+
def no_more_requests?
|
|
1014
|
+
@pending.empty? && @inflight.zero?
|
|
1015
|
+
end
|
|
1016
|
+
|
|
933
1017
|
# recover internal state and emit all relevant error responses when +error+ was raised.
|
|
934
1018
|
# this takes an optiona +request+ which may have already been handled and can be opted out
|
|
935
1019
|
# in the state recovery process.
|
|
@@ -964,6 +1048,7 @@ module HTTPX
|
|
|
964
1048
|
set_request_write_timeout(request)
|
|
965
1049
|
set_request_read_timeout(request)
|
|
966
1050
|
set_request_request_timeout(request)
|
|
1051
|
+
set_request_total_request_timeout(request)
|
|
967
1052
|
end
|
|
968
1053
|
|
|
969
1054
|
def set_request_read_timeout(request)
|
|
@@ -1016,6 +1101,18 @@ module HTTPX
|
|
|
1016
1101
|
request.handle_error(error)
|
|
1017
1102
|
end
|
|
1018
1103
|
|
|
1104
|
+
def set_request_total_request_timeout(request)
|
|
1105
|
+
return if request.started?
|
|
1106
|
+
|
|
1107
|
+
total_request_timeout = request.total_request_timeout
|
|
1108
|
+
|
|
1109
|
+
return if total_request_timeout.nil? || total_request_timeout.infinite?
|
|
1110
|
+
|
|
1111
|
+
set_request_timeout(:total_request_timeout, request, total_request_timeout, :headers, :complete) do
|
|
1112
|
+
read_timeout_callback(request, total_request_timeout, TotalRequestTimeoutError)
|
|
1113
|
+
end
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1019
1116
|
def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
|
|
1020
1117
|
request.set_timeout_callback(start_event) do
|
|
1021
1118
|
unless (selector = @current_selector)
|
|
@@ -1030,13 +1127,14 @@ module HTTPX
|
|
|
1030
1127
|
end
|
|
1031
1128
|
|
|
1032
1129
|
timer = selector.after(timeout, callback)
|
|
1033
|
-
|
|
1130
|
+
timer.label = label
|
|
1131
|
+
request.active_timeouts << timer
|
|
1034
1132
|
|
|
1035
1133
|
Array(finish_events).each do |event|
|
|
1036
1134
|
# clean up request timeouts if the connection errors out
|
|
1037
1135
|
request.set_timeout_callback(event) do
|
|
1038
1136
|
timer.cancel
|
|
1039
|
-
request.active_timeouts.delete(
|
|
1137
|
+
request.active_timeouts.delete(timer)
|
|
1040
1138
|
end
|
|
1041
1139
|
end
|
|
1042
1140
|
end
|
data/lib/httpx/errors.rb
CHANGED
|
@@ -62,15 +62,22 @@ module HTTPX
|
|
|
62
62
|
# Error raised when there was a timeout while sending a request from the server.
|
|
63
63
|
class WriteTimeoutError < RequestTimeoutError; end
|
|
64
64
|
|
|
65
|
+
# Error raised when a response couldn't be received for a request after multiple interactions.
|
|
66
|
+
# This error should not be retriable.
|
|
67
|
+
class TotalRequestTimeoutError < RequestTimeoutError; end
|
|
68
|
+
|
|
65
69
|
# Error raised when there was a timeout while waiting for the HTTP/2 settings frame from the server.
|
|
66
70
|
class SettingsTimeoutError < TimeoutError; end
|
|
67
71
|
|
|
68
72
|
# Error raised when there was a timeout while resolving a domain to an IP.
|
|
69
73
|
class ResolveTimeoutError < TimeoutError; end
|
|
70
74
|
|
|
71
|
-
# Error
|
|
75
|
+
# Error raised when there was a timeout waiting for readiness of the socket the request is related to.
|
|
72
76
|
class OperationTimeoutError < TimeoutError; end
|
|
73
77
|
|
|
78
|
+
# Error raised when a connection liveness probe (aka ping) times out.
|
|
79
|
+
class PingTimeoutError < TimeoutError; end
|
|
80
|
+
|
|
74
81
|
# Error raised when there was an error while resolving a domain to an IP.
|
|
75
82
|
class ResolveError < Error; end
|
|
76
83
|
|
data/lib/httpx/headers.rb
CHANGED
|
@@ -140,12 +140,12 @@ module HTTPX
|
|
|
140
140
|
@headers.to_s
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
-
# :
|
|
143
|
+
# simplecov:disable
|
|
144
144
|
def inspect
|
|
145
145
|
"#<#{self.class}:#{object_id} " \
|
|
146
146
|
"#{to_hash.inspect}>"
|
|
147
147
|
end
|
|
148
|
-
# :
|
|
148
|
+
# simplecov:enable
|
|
149
149
|
|
|
150
150
|
# this is internal API and doesn't abide to other public API
|
|
151
151
|
# guarantees, like downcasing strings.
|
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -20,8 +20,9 @@ module HTTPX
|
|
|
20
20
|
super
|
|
21
21
|
|
|
22
22
|
@ssl_session = nil
|
|
23
|
-
ctx_options = TLS_OPTIONS
|
|
24
|
-
|
|
23
|
+
ctx_options = TLS_OPTIONS
|
|
24
|
+
ctx_options = ctx_options.merge(options.ssl) if options.ssl && !options.ssl.empty?
|
|
25
|
+
@sni_hostname = (ctx_options.delete(:hostname) if ctx_options.key?(:hostname)) || @hostname
|
|
25
26
|
|
|
26
27
|
if @keep_open && @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
27
28
|
# externally initiated ssl socket
|
|
@@ -29,7 +30,7 @@ module HTTPX
|
|
|
29
30
|
@state = :negotiated
|
|
30
31
|
else
|
|
31
32
|
@ctx = OpenSSL::SSL::SSLContext.new
|
|
32
|
-
@ctx.set_params(ctx_options)
|
|
33
|
+
@ctx.set_params(ctx_options)
|
|
33
34
|
unless @ctx.session_cache_mode.nil? # a dummy method on JRuby
|
|
34
35
|
@ctx.session_cache_mode =
|
|
35
36
|
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
|
@@ -51,24 +52,22 @@ module HTTPX
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def protocol
|
|
54
|
-
|
|
55
|
+
return super unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
56
|
+
|
|
55
57
|
@io.alpn_protocol || super
|
|
56
|
-
rescue StandardError
|
|
57
|
-
super
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
if RUBY_ENGINE == "jruby"
|
|
61
61
|
# in jruby, alpn_protocol may return ""
|
|
62
62
|
# https://github.com/jruby/jruby-openssl/issues/287
|
|
63
63
|
def protocol
|
|
64
|
-
|
|
64
|
+
return super unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
65
|
+
|
|
65
66
|
proto = @io.alpn_protocol
|
|
66
67
|
|
|
67
68
|
return super if proto.nil? || proto.empty?
|
|
68
69
|
|
|
69
70
|
proto
|
|
70
|
-
rescue StandardError
|
|
71
|
-
super
|
|
72
71
|
end
|
|
73
72
|
end
|
|
74
73
|
|
data/lib/httpx/io/tcp.rb
CHANGED
|
@@ -182,18 +182,35 @@ module HTTPX
|
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
def close
|
|
185
|
-
return if @keep_open
|
|
185
|
+
return if @keep_open
|
|
186
|
+
|
|
187
|
+
# mark tcp as closed, so that it can be disconnected.
|
|
188
|
+
# this bypasses the state machine API as not not allow the transition
|
|
189
|
+
# from idle to closed in normal circumstances.
|
|
190
|
+
@state = :closed if @state == :idle
|
|
191
|
+
|
|
192
|
+
return if closed?
|
|
186
193
|
|
|
187
194
|
begin
|
|
188
195
|
@io.close
|
|
189
|
-
rescue
|
|
196
|
+
rescue IOError => e
|
|
190
197
|
log { "error closing socket" }
|
|
191
198
|
log { e.full_message(highlight: false) }
|
|
192
199
|
ensure
|
|
193
|
-
|
|
200
|
+
# @fiber-switch-guard
|
|
201
|
+
# ensure that all :closed IOs don't leave dangling sockets
|
|
202
|
+
# behind. This may happen in a fiber scheduler scenario where
|
|
203
|
+
# connection is reused across fibers.
|
|
204
|
+
transition(:closed) if @io.closed?
|
|
194
205
|
end
|
|
195
206
|
end
|
|
196
207
|
|
|
208
|
+
# signals that the connection that contains this IO can be checked back into the pool.
|
|
209
|
+
# that includes sockets opened outside of the scope of the session, or closed IOs.
|
|
210
|
+
def can_disconnect?
|
|
211
|
+
@keep_open || @state == :closed
|
|
212
|
+
end
|
|
213
|
+
|
|
197
214
|
def connected?
|
|
198
215
|
@state == :connected
|
|
199
216
|
end
|
|
@@ -202,7 +219,7 @@ module HTTPX
|
|
|
202
219
|
@state == :idle || @state == :closed
|
|
203
220
|
end
|
|
204
221
|
|
|
205
|
-
# :
|
|
222
|
+
# simplecov:disable
|
|
206
223
|
def inspect
|
|
207
224
|
"#<#{self.class}:#{object_id} " \
|
|
208
225
|
"#{@ip}:#{@port} " \
|
|
@@ -211,7 +228,7 @@ module HTTPX
|
|
|
211
228
|
"@addresses=#{@addresses} " \
|
|
212
229
|
"@state=#{@state}>"
|
|
213
230
|
end
|
|
214
|
-
# :
|
|
231
|
+
# simplecov:enable
|
|
215
232
|
|
|
216
233
|
private
|
|
217
234
|
|
data/lib/httpx/io/unix.rb
CHANGED
data/lib/httpx/loggable.rb
CHANGED
|
@@ -17,6 +17,12 @@ module HTTPX
|
|
|
17
17
|
|
|
18
18
|
USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
|
|
19
19
|
|
|
20
|
+
def self.log_identifiers
|
|
21
|
+
"pid=#{Process.pid} " \
|
|
22
|
+
"tid=#{Thread.current.object_id} " \
|
|
23
|
+
"fid=#{Fiber.current.object_id}"
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
def log(
|
|
21
27
|
level: @options.debug_level,
|
|
22
28
|
color: nil,
|
|
@@ -36,10 +42,9 @@ module HTTPX
|
|
|
36
42
|
klass = klass.superclass
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
message = +"(time
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"self:#{class_name}##{object_id}) "
|
|
45
|
+
message = +"(time=#{Time.now.utc} " \
|
|
46
|
+
"#{Loggable.log_identifiers} " \
|
|
47
|
+
"self=#{class_name}##{object_id}) "
|
|
43
48
|
message << msg.call << "\n"
|
|
44
49
|
message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
|
|
45
50
|
debug_stream << message
|
|
@@ -49,6 +54,8 @@ module HTTPX
|
|
|
49
54
|
log(level: level, color: color, debug_level: debug_level, debug: debug) { ex.full_message }
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
private
|
|
58
|
+
|
|
52
59
|
def log_redact_headers(text)
|
|
53
60
|
log_redact(text, @options.debug_redact == :headers)
|
|
54
61
|
end
|
data/lib/httpx/options.rb
CHANGED
|
@@ -8,12 +8,12 @@ module HTTPX
|
|
|
8
8
|
WINDOW_SIZE = 1 << 14 # 16K
|
|
9
9
|
MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
|
|
10
10
|
KEEP_ALIVE_TIMEOUT = 20
|
|
11
|
+
PING_TIMEOUT = 2
|
|
11
12
|
SETTINGS_TIMEOUT = 10
|
|
12
13
|
CLOSE_HANDSHAKE_TIMEOUT = 10
|
|
13
14
|
CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
|
|
14
|
-
REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
|
|
15
|
+
REQUEST_TIMEOUT = OPERATION_TIMEOUT = TOTAL_REQUEST_TIMEOUT = nil
|
|
15
16
|
RESOLVER_TYPES = %i[memory file].freeze
|
|
16
|
-
|
|
17
17
|
# default value used for "user-agent" header, when not overridden.
|
|
18
18
|
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
|
19
19
|
|
|
@@ -79,9 +79,15 @@ module HTTPX
|
|
|
79
79
|
# :decompress_response_body :: whether to auto-decompress response body (defaults to <tt>true</tt>).
|
|
80
80
|
# :compress_request_body :: whether to auto-decompress response body (defaults to <tt>true</tt>)
|
|
81
81
|
# :timeout :: hash of timeout configurations (supports <tt>:connect_timeout</tt>, <tt>:settings_timeout</tt>,
|
|
82
|
-
# <tt>:operation_timeout</tt>, <tt>:keep_alive_timeout</tt>,
|
|
83
|
-
# and <tt>:
|
|
82
|
+
# <tt>:operation_timeout</tt>, <tt>:keep_alive_timeout</tt>, <tt>:read_timeout</tt>, <tt>:write_timeout</tt>,
|
|
83
|
+
# <tt>:request_timeout</tt>, <tt>:total_request_timeout</tt> and <tt>:ping_timeout</tt>,
|
|
84
84
|
# :headers :: hash of HTTP headers (ex: <tt>{ "x-custom-foo" => "bar" }</tt>)
|
|
85
|
+
# :max_response_body_size :: maximum size (in bytes) that the response body can consume (no threshold by default), after which an
|
|
86
|
+
# error is raised.
|
|
87
|
+
# :max_response_headers :: maximum number of header fields that a response can receive, after which an error is raised.
|
|
88
|
+
# :max_response_header_value_size :: maximum size (in bytes) a header value can have (no threshold by default).
|
|
89
|
+
# for cases where the value is broken into multiple header fields (such as "cookie" or "set-cookie"),
|
|
90
|
+
# this is the total aggregated size.
|
|
85
91
|
# :window_size :: number of bytes to read from a socket
|
|
86
92
|
# :buffer_size :: internal read and write buffer size in bytes
|
|
87
93
|
# :body_threshold_size :: maximum size in bytes of response payload that is buffered in memory.
|
|
@@ -387,6 +393,7 @@ module HTTPX
|
|
|
387
393
|
# number options
|
|
388
394
|
%i[
|
|
389
395
|
max_concurrent_requests max_requests window_size buffer_size
|
|
396
|
+
max_response_body_size max_response_headers max_response_header_value_size
|
|
390
397
|
body_threshold_size debug_level
|
|
391
398
|
].each do |option|
|
|
392
399
|
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
@@ -553,15 +560,20 @@ module HTTPX
|
|
|
553
560
|
:supported_compression_formats => %w[gzip deflate],
|
|
554
561
|
:decompress_response_body => true,
|
|
555
562
|
:compress_request_body => true,
|
|
563
|
+
:max_response_headers => 1000,
|
|
564
|
+
:max_response_header_value_size => nil,
|
|
565
|
+
:max_response_body_size => Float::INFINITY,
|
|
556
566
|
:timeout => {
|
|
557
567
|
connect_timeout: CONNECT_TIMEOUT,
|
|
558
568
|
settings_timeout: SETTINGS_TIMEOUT,
|
|
559
569
|
close_handshake_timeout: CLOSE_HANDSHAKE_TIMEOUT,
|
|
560
570
|
operation_timeout: OPERATION_TIMEOUT,
|
|
561
571
|
keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
|
|
572
|
+
ping_timeout: PING_TIMEOUT,
|
|
562
573
|
read_timeout: READ_TIMEOUT,
|
|
563
574
|
write_timeout: WRITE_TIMEOUT,
|
|
564
575
|
request_timeout: REQUEST_TIMEOUT,
|
|
576
|
+
total_request_timeout: TOTAL_REQUEST_TIMEOUT,
|
|
565
577
|
}.freeze,
|
|
566
578
|
:headers_class => Class.new(Headers, &SET_TEMPORARY_NAME),
|
|
567
579
|
:headers => EMPTY_HASH,
|
data/lib/httpx/parser/http1.rb
CHANGED
|
@@ -9,11 +9,13 @@ module HTTPX
|
|
|
9
9
|
|
|
10
10
|
attr_reader :status_code, :http_version, :headers
|
|
11
11
|
|
|
12
|
-
def initialize(observer)
|
|
12
|
+
def initialize(observer, max_headers, max_header_value_size)
|
|
13
13
|
@observer = observer
|
|
14
14
|
@state = :idle
|
|
15
15
|
@buffer = "".b
|
|
16
16
|
@headers = {}
|
|
17
|
+
@max_headers = max_headers
|
|
18
|
+
@max_header_value_size = max_header_value_size
|
|
17
19
|
@content_length = nil
|
|
18
20
|
@_has_trailers = @upgrade = false
|
|
19
21
|
end
|
|
@@ -117,7 +119,11 @@ module HTTPX
|
|
|
117
119
|
value.strip!
|
|
118
120
|
raise Error, "wrong header format" if value.nil?
|
|
119
121
|
|
|
120
|
-
(headers[key.downcase] ||= []) << value
|
|
122
|
+
values = (headers[key.downcase] ||= []) << value
|
|
123
|
+
|
|
124
|
+
raise Error, "maximum header value size exceeded" if @max_header_value_size && (values.sum(&:size) > @max_header_value_size)
|
|
125
|
+
|
|
126
|
+
raise Error, "maximum number of response headers exceeded" if headers.size > @max_headers
|
|
121
127
|
end
|
|
122
128
|
end
|
|
123
129
|
|
|
@@ -24,7 +24,7 @@ module HTTPX
|
|
|
24
24
|
|
|
25
25
|
def authenticate(request, authenticate)
|
|
26
26
|
"Digest #{generate_header(request.verb, request.path, authenticate)}"
|
|
27
|
-
rescue
|
|
27
|
+
rescue HTTPX::Error => e
|
|
28
28
|
response = ErrorResponse.new(request, e)
|
|
29
29
|
request.response = response
|
|
30
30
|
request.emit_response(response)
|