httpx 1.1.4 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/doc/release_notes/1_1_4.md +1 -1
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/lib/httpx/adapters/webmock.rb +29 -8
- data/lib/httpx/altsvc.rb +57 -2
- data/lib/httpx/chainable.rb +40 -29
- data/lib/httpx/connection/http1.rb +27 -22
- data/lib/httpx/connection/http2.rb +7 -3
- data/lib/httpx/connection.rb +45 -60
- data/lib/httpx/extensions.rb +0 -15
- data/lib/httpx/options.rb +84 -27
- 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/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +1 -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 +14 -32
- 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 +9 -4
- 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 +12 -3
- data/lib/httpx/resolver/https.rb +3 -2
- data/lib/httpx/resolver/native.rb +1 -0
- data/lib/httpx/resolver/resolver.rb +17 -6
- data/lib/httpx/response.rb +1 -1
- 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/plugins/stream.rbs +24 -22
- data/sig/pool.rbs +1 -3
- data/sig/resolver/resolver.rbs +3 -1
- data/sig/session.rbs +4 -4
- metadata +12 -4
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,33 @@ 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
|
+
unless @write_buffer.empty?
|
232
|
+
# handshakes, try sending
|
233
|
+
consume
|
234
|
+
@write_buffer.clear
|
235
|
+
end
|
249
236
|
transition(:closed)
|
250
|
-
emit(:close)
|
251
237
|
end
|
252
238
|
|
253
239
|
def send(request)
|
254
240
|
if @parser && !@write_buffer.full?
|
255
|
-
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
256
|
-
|
257
241
|
if @response_received_at && @keep_alive_timeout &&
|
258
242
|
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
|
259
243
|
# when pushing a request into an existing connection, we have to check whether there
|
@@ -319,10 +303,6 @@ module HTTPX
|
|
319
303
|
transition(:open)
|
320
304
|
end
|
321
305
|
|
322
|
-
def exhausted?
|
323
|
-
@parser && parser.exhausted?
|
324
|
-
end
|
325
|
-
|
326
306
|
def consume
|
327
307
|
return unless @io
|
328
308
|
|
@@ -497,34 +477,25 @@ module HTTPX
|
|
497
477
|
request.emit(:promise, parser, stream)
|
498
478
|
end
|
499
479
|
parser.on(:exhausted) do
|
480
|
+
@pending.concat(parser.pending)
|
500
481
|
emit(:exhausted)
|
501
482
|
end
|
502
483
|
parser.on(:origin) do |origin|
|
503
484
|
@origins |= [origin]
|
504
485
|
end
|
505
486
|
parser.on(:close) do |force|
|
506
|
-
if
|
507
|
-
|
508
|
-
|
509
|
-
transition(:closed)
|
510
|
-
emit(:close)
|
511
|
-
end
|
487
|
+
if force
|
488
|
+
reset
|
489
|
+
emit(:terminate)
|
512
490
|
end
|
513
491
|
end
|
514
492
|
parser.on(:close_handshake) do
|
515
493
|
consume
|
516
494
|
end
|
517
495
|
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
|
496
|
+
@pending.concat(parser.pending) unless parser.empty?
|
497
|
+
reset
|
498
|
+
idling unless @pending.empty?
|
528
499
|
end
|
529
500
|
parser.on(:current_timeout) do
|
530
501
|
@current_timeout = @timeout = parser.timeout
|
@@ -593,13 +564,14 @@ module HTTPX
|
|
593
564
|
when :inactive
|
594
565
|
return unless @state == :open
|
595
566
|
when :closing
|
596
|
-
return unless @state == :open
|
567
|
+
return unless @state == :idle || @state == :open
|
597
568
|
|
598
569
|
when :closed
|
599
570
|
return unless @state == :closing
|
600
571
|
return unless @write_buffer.empty?
|
601
572
|
|
602
573
|
purge_after_closed
|
574
|
+
emit(:close) if @pending.empty?
|
603
575
|
when :already_open
|
604
576
|
nextstate = :open
|
605
577
|
# the first check for given io readiness must still use a timeout.
|
@@ -621,6 +593,19 @@ module HTTPX
|
|
621
593
|
@timeout = nil
|
622
594
|
end
|
623
595
|
|
596
|
+
def initialize_type(uri, options)
|
597
|
+
options.transport || begin
|
598
|
+
case uri.scheme
|
599
|
+
when "http"
|
600
|
+
"tcp"
|
601
|
+
when "https"
|
602
|
+
"ssl"
|
603
|
+
else
|
604
|
+
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
624
609
|
def build_socket(addrs = nil)
|
625
610
|
case @type
|
626
611
|
when "tcp"
|
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,
|
@@ -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.
|
@@ -11,6 +11,7 @@ module HTTPX
|
|
11
11
|
@response = response
|
12
12
|
@decoder = ->(z) { z }
|
13
13
|
@consumed = false
|
14
|
+
@grpc_response = nil
|
14
15
|
end
|
15
16
|
|
16
17
|
def inspect
|
@@ -34,9 +35,7 @@ module HTTPX
|
|
34
35
|
private
|
35
36
|
|
36
37
|
def grpc_response
|
37
|
-
|
38
|
-
|
39
|
-
@grpc_response = if @response.respond_to?(:each)
|
38
|
+
@grpc_response ||= if @response.respond_to?(:each)
|
40
39
|
Enumerator.new do |y|
|
41
40
|
Message.stream(@response).each do |message|
|
42
41
|
y << @decoder.call(message)
|
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
|