httpx 1.1.5 → 1.2.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/README.md +6 -6
- data/doc/release_notes/1_1_1.md +2 -2
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/lib/httpx/adapters/webmock.rb +25 -3
- data/lib/httpx/altsvc.rb +57 -2
- data/lib/httpx/buffer.rb +8 -0
- data/lib/httpx/chainable.rb +48 -29
- data/lib/httpx/connection/http1.rb +27 -22
- data/lib/httpx/connection/http2.rb +7 -3
- data/lib/httpx/connection.rb +52 -62
- data/lib/httpx/extensions.rb +0 -15
- data/lib/httpx/options.rb +85 -28
- data/lib/httpx/plugins/aws_sigv4.rb +2 -2
- data/lib/httpx/plugins/basic_auth.rb +1 -1
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker.rb +2 -0
- data/lib/httpx/plugins/cookies.rb +19 -9
- data/lib/httpx/plugins/digest_auth.rb +1 -1
- data/lib/httpx/plugins/follow_redirects.rb +11 -0
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/h2c.rb +20 -8
- data/lib/httpx/plugins/proxy/socks4.rb +2 -2
- data/lib/httpx/plugins/proxy/socks5.rb +2 -2
- data/lib/httpx/plugins/proxy.rb +16 -34
- data/lib/httpx/plugins/rate_limiter.rb +1 -1
- data/lib/httpx/plugins/retries.rb +4 -0
- data/lib/httpx/plugins/ssrf_filter.rb +142 -0
- data/lib/httpx/plugins/stream.rb +1 -1
- data/lib/httpx/plugins/upgrade/h2.rb +1 -1
- data/lib/httpx/plugins/upgrade.rb +1 -1
- data/lib/httpx/plugins/webdav.rb +1 -1
- data/lib/httpx/pool.rb +32 -28
- data/lib/httpx/request/body.rb +3 -3
- data/lib/httpx/request.rb +3 -5
- data/lib/httpx/resolver/https.rb +10 -4
- data/lib/httpx/resolver/native.rb +1 -0
- data/lib/httpx/resolver/resolver.rb +17 -6
- data/lib/httpx/response/body.rb +3 -0
- data/lib/httpx/response.rb +3 -2
- data/lib/httpx/session.rb +13 -82
- data/lib/httpx/timers.rb +3 -10
- data/lib/httpx/transcoder.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +33 -0
- data/sig/chainable.rbs +1 -0
- data/sig/connection/http1.rbs +2 -1
- data/sig/connection.rbs +16 -16
- data/sig/options.rbs +10 -2
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/follow_redirects.rbs +2 -0
- data/sig/plugins/proxy/socks4.rbs +2 -1
- data/sig/plugins/proxy/socks5.rbs +2 -1
- data/sig/plugins/proxy.rbs +11 -1
- data/sig/pool.rbs +1 -3
- data/sig/resolver/resolver.rbs +3 -1
- data/sig/session.rbs +4 -4
- metadata +14 -6
data/lib/httpx/connection.rb
CHANGED
@@ -47,11 +47,11 @@ module HTTPX
|
|
47
47
|
|
48
48
|
attr_accessor :family
|
49
49
|
|
50
|
-
def initialize(
|
51
|
-
@type = type
|
50
|
+
def initialize(uri, options)
|
52
51
|
@origins = [uri.origin]
|
53
52
|
@origin = Utils.to_uri(uri.origin)
|
54
53
|
@options = Options.new(options)
|
54
|
+
@type = initialize_type(uri, @options)
|
55
55
|
@window_size = @options.window_size
|
56
56
|
@read_buffer = Buffer.new(@options.buffer_size)
|
57
57
|
@write_buffer = Buffer.new(@options.buffer_size)
|
@@ -92,18 +92,14 @@ module HTTPX
|
|
92
92
|
def match?(uri, options)
|
93
93
|
return false if !used? && (@state == :closing || @state == :closed)
|
94
94
|
|
95
|
-
return false if exhausted?
|
96
|
-
|
97
95
|
(
|
98
|
-
(
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
) && @options == options
|
106
|
-
) || (match_altsvcs?(uri) && match_altsvc_options?(uri, options))
|
96
|
+
@origins.include?(uri.origin) &&
|
97
|
+
# if there is more than one origin to match, it means that this connection
|
98
|
+
# was the result of coalescing. To prevent blind trust in the case where the
|
99
|
+
# origin came from an ORIGIN frame, we're going to verify the hostname with the
|
100
|
+
# SSL certificate
|
101
|
+
(@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
|
102
|
+
) && @options == options
|
107
103
|
end
|
108
104
|
|
109
105
|
def expired?
|
@@ -115,8 +111,6 @@ module HTTPX
|
|
115
111
|
def mergeable?(connection)
|
116
112
|
return false if @state == :closing || @state == :closed || !@io
|
117
113
|
|
118
|
-
return false if exhausted?
|
119
|
-
|
120
114
|
return false unless connection.addresses
|
121
115
|
|
122
116
|
(
|
@@ -139,7 +133,7 @@ module HTTPX
|
|
139
133
|
end
|
140
134
|
|
141
135
|
def create_idle(options = {})
|
142
|
-
self.class.new(@
|
136
|
+
self.class.new(@origin, @options.merge(options))
|
143
137
|
end
|
144
138
|
|
145
139
|
def merge(connection)
|
@@ -167,24 +161,6 @@ module HTTPX
|
|
167
161
|
end
|
168
162
|
end
|
169
163
|
|
170
|
-
# checks if this is connection is an alternative service of
|
171
|
-
# +uri+
|
172
|
-
def match_altsvcs?(uri)
|
173
|
-
@origins.any? { |origin| uri.altsvc_match?(origin) } ||
|
174
|
-
AltSvc.cached_altsvc(@origin).any? do |altsvc|
|
175
|
-
origin = altsvc["origin"]
|
176
|
-
origin.altsvc_match?(uri.origin)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def match_altsvc_options?(uri, options)
|
181
|
-
return @options == options unless @options.ssl[:hostname] == uri.host
|
182
|
-
|
183
|
-
dup_options = @options.merge(ssl: { hostname: nil })
|
184
|
-
dup_options.ssl.delete(:hostname)
|
185
|
-
dup_options == options
|
186
|
-
end
|
187
|
-
|
188
164
|
def connecting?
|
189
165
|
@state == :idle
|
190
166
|
end
|
@@ -223,7 +199,6 @@ module HTTPX
|
|
223
199
|
when :closing
|
224
200
|
consume
|
225
201
|
transition(:closed)
|
226
|
-
emit(:close)
|
227
202
|
when :open
|
228
203
|
consume
|
229
204
|
end
|
@@ -236,24 +211,29 @@ module HTTPX
|
|
236
211
|
@parser.close if @parser
|
237
212
|
end
|
238
213
|
|
214
|
+
def terminate
|
215
|
+
@connected_at = nil if @state == :closed
|
216
|
+
|
217
|
+
close
|
218
|
+
end
|
219
|
+
|
239
220
|
# bypasses the state machine to force closing of connections still connecting.
|
240
221
|
# **only** used for Happy Eyeballs v2.
|
241
222
|
def force_reset
|
242
223
|
@state = :closing
|
243
224
|
transition(:closed)
|
244
|
-
emit(:close)
|
245
225
|
end
|
246
226
|
|
247
227
|
def reset
|
228
|
+
return if @state == :closing || @state == :closed
|
229
|
+
|
248
230
|
transition(:closing)
|
231
|
+
|
249
232
|
transition(:closed)
|
250
|
-
emit(:close)
|
251
233
|
end
|
252
234
|
|
253
235
|
def send(request)
|
254
236
|
if @parser && !@write_buffer.full?
|
255
|
-
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
256
|
-
|
257
237
|
if @response_received_at && @keep_alive_timeout &&
|
258
238
|
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
|
259
239
|
# when pushing a request into an existing connection, we have to check whether there
|
@@ -319,10 +299,6 @@ module HTTPX
|
|
319
299
|
transition(:open)
|
320
300
|
end
|
321
301
|
|
322
|
-
def exhausted?
|
323
|
-
@parser && parser.exhausted?
|
324
|
-
end
|
325
|
-
|
326
302
|
def consume
|
327
303
|
return unless @io
|
328
304
|
|
@@ -497,34 +473,25 @@ module HTTPX
|
|
497
473
|
request.emit(:promise, parser, stream)
|
498
474
|
end
|
499
475
|
parser.on(:exhausted) do
|
476
|
+
@pending.concat(parser.pending)
|
500
477
|
emit(:exhausted)
|
501
478
|
end
|
502
479
|
parser.on(:origin) do |origin|
|
503
480
|
@origins |= [origin]
|
504
481
|
end
|
505
482
|
parser.on(:close) do |force|
|
506
|
-
if
|
507
|
-
|
508
|
-
|
509
|
-
transition(:closed)
|
510
|
-
emit(:close)
|
511
|
-
end
|
483
|
+
if force
|
484
|
+
reset
|
485
|
+
emit(:terminate)
|
512
486
|
end
|
513
487
|
end
|
514
488
|
parser.on(:close_handshake) do
|
515
489
|
consume
|
516
490
|
end
|
517
491
|
parser.on(:reset) do
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
transition(:closing)
|
522
|
-
transition(:closed)
|
523
|
-
|
524
|
-
@parser.reset if @parser
|
525
|
-
transition(:idle)
|
526
|
-
transition(:open)
|
527
|
-
end
|
492
|
+
@pending.concat(parser.pending) unless parser.empty?
|
493
|
+
reset
|
494
|
+
idling unless @pending.empty?
|
528
495
|
end
|
529
496
|
parser.on(:current_timeout) do
|
530
497
|
@current_timeout = @timeout = parser.timeout
|
@@ -593,13 +560,23 @@ module HTTPX
|
|
593
560
|
when :inactive
|
594
561
|
return unless @state == :open
|
595
562
|
when :closing
|
596
|
-
return unless @state == :open
|
597
|
-
|
563
|
+
return unless @state == :idle || @state == :open
|
564
|
+
|
565
|
+
unless @write_buffer.empty?
|
566
|
+
# preset state before handshake, as error callbacks
|
567
|
+
# may take it back here.
|
568
|
+
@state = nextstate
|
569
|
+
# handshakes, try sending
|
570
|
+
consume
|
571
|
+
@write_buffer.clear
|
572
|
+
return
|
573
|
+
end
|
598
574
|
when :closed
|
599
575
|
return unless @state == :closing
|
600
576
|
return unless @write_buffer.empty?
|
601
577
|
|
602
578
|
purge_after_closed
|
579
|
+
emit(:close) if @pending.empty?
|
603
580
|
when :already_open
|
604
581
|
nextstate = :open
|
605
582
|
# the first check for given io readiness must still use a timeout.
|
@@ -621,6 +598,19 @@ module HTTPX
|
|
621
598
|
@timeout = nil
|
622
599
|
end
|
623
600
|
|
601
|
+
def initialize_type(uri, options)
|
602
|
+
options.transport || begin
|
603
|
+
case uri.scheme
|
604
|
+
when "http"
|
605
|
+
"tcp"
|
606
|
+
when "https"
|
607
|
+
"ssl"
|
608
|
+
else
|
609
|
+
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
624
614
|
def build_socket(addrs = nil)
|
625
615
|
case @type
|
626
616
|
when "tcp"
|
@@ -715,7 +705,7 @@ module HTTPX
|
|
715
705
|
interval = @timers.after(timeout, callback)
|
716
706
|
|
717
707
|
Array(finish_events).each do |event|
|
718
|
-
# clean up
|
708
|
+
# clean up request timeouts if the connection errors out
|
719
709
|
request.once(event) do
|
720
710
|
if @intervals.include?(interval)
|
721
711
|
interval.delete(callback)
|
data/lib/httpx/extensions.rb
CHANGED
@@ -54,21 +54,6 @@ module HTTPX
|
|
54
54
|
def origin
|
55
55
|
"#{scheme}://#{authority}"
|
56
56
|
end unless URI::HTTP.method_defined?(:origin)
|
57
|
-
|
58
|
-
def altsvc_match?(uri)
|
59
|
-
uri = URI.parse(uri)
|
60
|
-
|
61
|
-
origin == uri.origin || begin
|
62
|
-
case scheme
|
63
|
-
when "h2"
|
64
|
-
(uri.scheme == "https" || uri.scheme == "h2") &&
|
65
|
-
host == uri.host &&
|
66
|
-
(port || default_port) == (uri.port || uri.default_port)
|
67
|
-
else
|
68
|
-
false
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
57
|
end
|
73
58
|
end
|
74
59
|
end
|
data/lib/httpx/options.rb
CHANGED
@@ -11,6 +11,7 @@ module HTTPX
|
|
11
11
|
MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
|
12
12
|
KEEP_ALIVE_TIMEOUT = 20
|
13
13
|
SETTINGS_TIMEOUT = 10
|
14
|
+
CLOSE_HANDSHAKE_TIMEOUT = 10
|
14
15
|
CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
|
15
16
|
REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
|
16
17
|
|
@@ -39,6 +40,7 @@ module HTTPX
|
|
39
40
|
:timeout => {
|
40
41
|
connect_timeout: CONNECT_TIMEOUT,
|
41
42
|
settings_timeout: SETTINGS_TIMEOUT,
|
43
|
+
close_handshake_timeout: CLOSE_HANDSHAKE_TIMEOUT,
|
42
44
|
operation_timeout: OPERATION_TIMEOUT,
|
43
45
|
keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
|
44
46
|
read_timeout: READ_TIMEOUT,
|
@@ -97,7 +99,7 @@ module HTTPX
|
|
97
99
|
# :compress_request_body :: whether to auto-decompress response body (defaults to <tt>true</tt>)
|
98
100
|
# :timeout :: hash of timeout configurations (supports <tt>:connect_timeout</tt>, <tt>:settings_timeout</tt>,
|
99
101
|
# <tt>:operation_timeout</tt>, <tt>:keep_alive_timeout</tt>, <tt>:read_timeout</tt>, <tt>:write_timeout</tt>
|
100
|
-
#
|
102
|
+
# and <tt>:request_timeout</tt>
|
101
103
|
# :headers :: hash of HTTP headers (ex: <tt>{ "x-custom-foo" => "bar" }</tt>)
|
102
104
|
# :window_size :: number of bytes to read from a socket
|
103
105
|
# :buffer_size :: internal read and write buffer size in bytes
|
@@ -226,44 +228,69 @@ module HTTPX
|
|
226
228
|
OUT
|
227
229
|
end
|
228
230
|
|
229
|
-
|
230
|
-
private_constant :REQUEST_IVARS
|
231
|
+
REQUEST_BODY_IVARS = %i[@headers @params @form @xml @json @body].freeze
|
231
232
|
|
232
233
|
def ==(other)
|
233
|
-
|
234
|
+
super || options_equals?(other)
|
235
|
+
end
|
236
|
+
|
237
|
+
def options_equals?(other, ignore_ivars = REQUEST_BODY_IVARS)
|
238
|
+
# headers and other request options do not play a role, as they are
|
239
|
+
# relevant only for the request.
|
240
|
+
ivars = instance_variables - ignore_ivars
|
241
|
+
other_ivars = other.instance_variables - ignore_ivars
|
242
|
+
|
243
|
+
return false if ivars.size != other_ivars.size
|
244
|
+
|
245
|
+
return false if ivars.sort != other_ivars.sort
|
246
|
+
|
234
247
|
ivars.all? do |ivar|
|
235
|
-
|
236
|
-
when :@headers
|
237
|
-
# currently, this is used to pick up an available matching connection.
|
238
|
-
# the headers do not play a role, as they are relevant only for the request.
|
239
|
-
true
|
240
|
-
when *REQUEST_IVARS
|
241
|
-
true
|
242
|
-
else
|
243
|
-
instance_variable_get(ivar) == other.instance_variable_get(ivar)
|
244
|
-
end
|
248
|
+
instance_variable_get(ivar) == other.instance_variable_get(ivar)
|
245
249
|
end
|
246
250
|
end
|
247
251
|
|
252
|
+
OTHER_LOOKUP = ->(obj, k, ivar_map) {
|
253
|
+
case obj
|
254
|
+
when Hash
|
255
|
+
obj[ivar_map[k]]
|
256
|
+
else
|
257
|
+
obj.instance_variable_get(k)
|
258
|
+
end
|
259
|
+
}
|
248
260
|
def merge(other)
|
249
|
-
|
261
|
+
ivar_map = nil
|
262
|
+
other_ivars = case other
|
263
|
+
when Hash
|
264
|
+
ivar_map = other.keys.to_h { |k| [:"@#{k}", k] }
|
265
|
+
ivar_map.keys
|
266
|
+
else
|
267
|
+
other.instance_variables
|
268
|
+
end
|
269
|
+
|
270
|
+
return self if other_ivars.empty?
|
250
271
|
|
251
|
-
|
252
|
-
return self if h2.empty?
|
272
|
+
return self if other_ivars.all? { |ivar| instance_variable_get(ivar) == OTHER_LOOKUP[other, ivar, ivar_map] }
|
253
273
|
|
254
|
-
|
274
|
+
opts = dup
|
255
275
|
|
256
|
-
|
276
|
+
other_ivars.each do |ivar|
|
277
|
+
v = OTHER_LOOKUP[other, ivar, ivar_map]
|
257
278
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
else
|
262
|
-
v2
|
279
|
+
unless v
|
280
|
+
opts.instance_variable_set(ivar, v)
|
281
|
+
next
|
263
282
|
end
|
283
|
+
|
284
|
+
v = opts.__send__(:"option_#{ivar[1..-1]}", v)
|
285
|
+
|
286
|
+
orig_v = instance_variable_get(ivar)
|
287
|
+
|
288
|
+
v = orig_v.merge(v) if orig_v.respond_to?(:merge) && v.respond_to?(:merge)
|
289
|
+
|
290
|
+
opts.instance_variable_set(ivar, v)
|
264
291
|
end
|
265
292
|
|
266
|
-
|
293
|
+
opts
|
267
294
|
end
|
268
295
|
|
269
296
|
def to_hash
|
@@ -272,10 +299,40 @@ module HTTPX
|
|
272
299
|
end
|
273
300
|
end
|
274
301
|
|
275
|
-
def
|
276
|
-
|
277
|
-
|
302
|
+
def extend_with_plugin_classes(pl)
|
303
|
+
if defined?(pl::RequestMethods) || defined?(pl::RequestClassMethods)
|
304
|
+
@request_class = @request_class.dup
|
305
|
+
@request_class.__send__(:include, pl::RequestMethods) if defined?(pl::RequestMethods)
|
306
|
+
@request_class.extend(pl::RequestClassMethods) if defined?(pl::RequestClassMethods)
|
307
|
+
end
|
308
|
+
if defined?(pl::ResponseMethods) || defined?(pl::ResponseClassMethods)
|
309
|
+
@response_class = @response_class.dup
|
310
|
+
@response_class.__send__(:include, pl::ResponseMethods) if defined?(pl::ResponseMethods)
|
311
|
+
@response_class.extend(pl::ResponseClassMethods) if defined?(pl::ResponseClassMethods)
|
278
312
|
end
|
313
|
+
if defined?(pl::HeadersMethods) || defined?(pl::HeadersClassMethods)
|
314
|
+
@headers_class = @headers_class.dup
|
315
|
+
@headers_class.__send__(:include, pl::HeadersMethods) if defined?(pl::HeadersMethods)
|
316
|
+
@headers_class.extend(pl::HeadersClassMethods) if defined?(pl::HeadersClassMethods)
|
317
|
+
end
|
318
|
+
if defined?(pl::RequestBodyMethods) || defined?(pl::RequestBodyClassMethods)
|
319
|
+
@request_body_class = @request_body_class.dup
|
320
|
+
@request_body_class.__send__(:include, pl::RequestBodyMethods) if defined?(pl::RequestBodyMethods)
|
321
|
+
@request_body_class.extend(pl::RequestBodyClassMethods) if defined?(pl::RequestBodyClassMethods)
|
322
|
+
end
|
323
|
+
if defined?(pl::ResponseBodyMethods) || defined?(pl::ResponseBodyClassMethods)
|
324
|
+
@response_body_class = @response_body_class.dup
|
325
|
+
@response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
|
326
|
+
@response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
|
327
|
+
end
|
328
|
+
if defined?(pl::ConnectionMethods)
|
329
|
+
@connection_class = @connection_class.dup
|
330
|
+
@connection_class.__send__(:include, pl::ConnectionMethods)
|
331
|
+
end
|
332
|
+
return unless defined?(pl::OptionsMethods)
|
333
|
+
|
334
|
+
@options_class = @options_class.dup
|
335
|
+
@options_class.__send__(:include, pl::OptionsMethods)
|
279
336
|
end
|
280
337
|
|
281
338
|
private
|
@@ -5,7 +5,7 @@ module HTTPX
|
|
5
5
|
#
|
6
6
|
# This plugin adds AWS Sigv4 authentication.
|
7
7
|
#
|
8
|
-
# https://docs.aws.amazon.com/
|
8
|
+
# https://docs.aws.amazon.com/IAM/latest/UserGuide/signing-elements.html
|
9
9
|
#
|
10
10
|
# https://gitlab.com/os85/httpx/wikis/AWS-SigV4
|
11
11
|
#
|
@@ -185,7 +185,7 @@ module HTTPX
|
|
185
185
|
def canonical_query
|
186
186
|
params = query.split("&")
|
187
187
|
# params = params.map { |p| p.match(/=/) ? p : p + '=' }
|
188
|
-
# From: https://docs.aws.amazon.com/
|
188
|
+
# From: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request
|
189
189
|
# Sort the parameter names by character code point in ascending order.
|
190
190
|
# Parameters with duplicate names should be sorted by value.
|
191
191
|
#
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module HTTPX
|
4
4
|
module Plugins
|
5
5
|
#
|
6
|
-
# This plugin adds helper methods to implement HTTP Basic Auth (https://
|
6
|
+
# This plugin adds helper methods to implement HTTP Basic Auth (https://datatracker.ietf.org/doc/html/rfc7617)
|
7
7
|
#
|
8
8
|
# https://gitlab.com/os85/httpx/wikis/Auth#basic-auth
|
9
9
|
#
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
#
|
6
|
+
# This plugin adds suppoort for callbacks around the request/response lifecycle.
|
7
|
+
#
|
8
|
+
# https://gitlab.com/os85/httpx/-/wikis/Events
|
9
|
+
#
|
10
|
+
module Callbacks
|
11
|
+
# connection closed user-space errors happen after errors can be surfaced to requests,
|
12
|
+
# so they need to pierce through the scheduler, which is only possible by simulating an
|
13
|
+
# interrupt.
|
14
|
+
class CallbackError < Exception; end # rubocop:disable Lint/InheritException
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
include HTTPX::Callbacks
|
18
|
+
|
19
|
+
%i[
|
20
|
+
connection_opened connection_closed
|
21
|
+
request_error
|
22
|
+
request_started request_body_chunk request_completed
|
23
|
+
response_started response_body_chunk response_completed
|
24
|
+
].each do |meth|
|
25
|
+
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
26
|
+
def on_#{meth}(&blk) # def on_connection_opened(&blk)
|
27
|
+
on(:#{meth}, &blk) # on(:connection_opened, &blk)
|
28
|
+
end # end
|
29
|
+
MOD
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def init_connection(uri, options)
|
35
|
+
connection = super
|
36
|
+
connection.on(:open) do
|
37
|
+
emit_or_callback_error(:connection_opened, connection.origin, connection.io.socket)
|
38
|
+
end
|
39
|
+
connection.on(:close) do
|
40
|
+
emit_or_callback_error(:connection_closed, connection.origin) if connection.used?
|
41
|
+
end
|
42
|
+
|
43
|
+
connection
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_request_callbacks(request)
|
47
|
+
super
|
48
|
+
|
49
|
+
request.on(:headers) do
|
50
|
+
emit_or_callback_error(:request_started, request)
|
51
|
+
end
|
52
|
+
request.on(:body_chunk) do |chunk|
|
53
|
+
emit_or_callback_error(:request_body_chunk, request, chunk)
|
54
|
+
end
|
55
|
+
request.on(:done) do
|
56
|
+
emit_or_callback_error(:request_completed, request)
|
57
|
+
end
|
58
|
+
|
59
|
+
request.on(:response_started) do |res|
|
60
|
+
if res.is_a?(Response)
|
61
|
+
emit_or_callback_error(:response_started, request, res)
|
62
|
+
res.on(:chunk_received) do |chunk|
|
63
|
+
emit_or_callback_error(:response_body_chunk, request, res, chunk)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
emit_or_callback_error(:request_error, request, res.error)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
request.on(:response) do |res|
|
70
|
+
emit_or_callback_error(:response_completed, request, res)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def emit_or_callback_error(*args)
|
75
|
+
emit(*args)
|
76
|
+
rescue StandardError => e
|
77
|
+
ex = CallbackError.new(e.message)
|
78
|
+
ex.set_backtrace(e.backtrace)
|
79
|
+
raise ex
|
80
|
+
end
|
81
|
+
|
82
|
+
def receive_requests(*)
|
83
|
+
super
|
84
|
+
rescue CallbackError => e
|
85
|
+
raise e.cause
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
register_plugin :callbacks, Callbacks
|
90
|
+
end
|
91
|
+
end
|
@@ -71,22 +71,32 @@ module HTTPX
|
|
71
71
|
end
|
72
72
|
|
73
73
|
module OptionsMethods
|
74
|
-
def
|
75
|
-
super
|
74
|
+
def option_headers(*)
|
75
|
+
value = super
|
76
|
+
|
77
|
+
merge_cookie_in_jar(value.delete("cookie"), @cookies) if defined?(@cookies) && value.key?("cookie")
|
76
78
|
|
77
|
-
|
79
|
+
value
|
80
|
+
end
|
78
81
|
|
79
|
-
|
82
|
+
def option_cookies(value)
|
83
|
+
jar = value.is_a?(Jar) ? value : Jar.new(value)
|
84
|
+
|
85
|
+
merge_cookie_in_jar(@headers.delete("cookie"), jar) if defined?(@headers) && @headers.key?("cookie")
|
86
|
+
|
87
|
+
jar
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def merge_cookie_in_jar(cookies, jar)
|
93
|
+
cookies.each do |ck|
|
80
94
|
ck.split(/ *; */).each do |cookie|
|
81
95
|
name, value = cookie.split("=", 2)
|
82
|
-
|
96
|
+
jar.add(Cookie.new(name, value))
|
83
97
|
end
|
84
98
|
end
|
85
99
|
end
|
86
|
-
|
87
|
-
def option_cookies(value)
|
88
|
-
value.is_a?(Jar) ? value : Jar.new(value)
|
89
|
-
end
|
90
100
|
end
|
91
101
|
end
|
92
102
|
register_plugin :cookies, Cookies
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module HTTPX
|
4
4
|
module Plugins
|
5
5
|
#
|
6
|
-
# This plugin adds helper methods to implement HTTP Digest Auth (https://
|
6
|
+
# This plugin adds helper methods to implement HTTP Digest Auth (https://datatracker.ietf.org/doc/html/rfc7616)
|
7
7
|
#
|
8
8
|
# https://gitlab.com/os85/httpx/wikis/Auth#digest-auth
|
9
9
|
#
|
@@ -34,6 +34,12 @@ module HTTPX
|
|
34
34
|
def option_allow_auth_to_other_origins(value)
|
35
35
|
value
|
36
36
|
end
|
37
|
+
|
38
|
+
def option_redirect_on(value)
|
39
|
+
raise TypeError, ":redirect_on must be callable" unless value.respond_to?(:call)
|
40
|
+
|
41
|
+
value
|
42
|
+
end
|
37
43
|
end
|
38
44
|
|
39
45
|
module InstanceMethods
|
@@ -57,6 +63,11 @@ module HTTPX
|
|
57
63
|
# build redirect request
|
58
64
|
redirect_uri = __get_location_from_response(response)
|
59
65
|
|
66
|
+
if options.redirect_on
|
67
|
+
redirect_allowed = options.redirect_on.call(redirect_uri)
|
68
|
+
return response unless redirect_allowed
|
69
|
+
end
|
70
|
+
|
60
71
|
if response.status == 305 && options.respond_to?(:proxy)
|
61
72
|
# The requested resource MUST be accessed through the proxy given by
|
62
73
|
# the Location field. The Location field gives the URI of the proxy.
|
data/lib/httpx/plugins/grpc.rb
CHANGED
@@ -261,14 +261,14 @@ module HTTPX
|
|
261
261
|
headers["grpc-timeout"] = "#{deadline}m"
|
262
262
|
end
|
263
263
|
|
264
|
-
headers = headers.merge(metadata) if metadata
|
264
|
+
headers = headers.merge(metadata.transform_keys(&:to_s)) if metadata
|
265
265
|
|
266
266
|
# prepare compressor
|
267
267
|
compression = @options.grpc_compression == true ? "gzip" : @options.grpc_compression
|
268
268
|
|
269
269
|
headers["grpc-encoding"] = compression if compression
|
270
270
|
|
271
|
-
headers.merge!(@options.call_credentials.call) if @options.call_credentials
|
271
|
+
headers.merge!(@options.call_credentials.call.transform_keys(&:to_s)) if @options.call_credentials
|
272
272
|
|
273
273
|
build_request("POST", uri, headers: headers, body: input)
|
274
274
|
end
|