httpx 1.8.4 → 1.9.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_8_1.md +1 -1
- data/doc/release_notes/1_8_2.md +4 -4
- data/doc/release_notes/1_8_3.md +3 -5
- data/doc/release_notes/1_8_4.md +1 -1
- data/doc/release_notes/1_9_0.md +75 -0
- data/doc/release_notes/1_9_1.md +32 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +15 -2
- data/lib/httpx/altsvc.rb +29 -15
- data/lib/httpx/chainable.rb +4 -1
- data/lib/httpx/connection/http1.rb +34 -35
- data/lib/httpx/connection/http2.rb +146 -33
- data/lib/httpx/connection.rb +282 -162
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/tcp.rb +42 -21
- data/lib/httpx/options.rb +86 -35
- data/lib/httpx/parser/http1.rb +3 -6
- data/lib/httpx/plugins/auth/digest.rb +1 -2
- data/lib/httpx/plugins/auth.rb +11 -2
- data/lib/httpx/plugins/aws_sdk_authentication.rb +7 -3
- data/lib/httpx/plugins/aws_sigv4.rb +1 -1
- data/lib/httpx/plugins/basic_auth.rb +2 -0
- data/lib/httpx/plugins/cache.rb +1 -1
- data/lib/httpx/plugins/callbacks.rb +8 -6
- data/lib/httpx/plugins/circuit_breaker.rb +1 -0
- data/lib/httpx/plugins/content_digest.rb +3 -5
- data/lib/httpx/plugins/digest_auth.rb +2 -0
- data/lib/httpx/plugins/expect.rb +10 -1
- data/lib/httpx/plugins/fiber_concurrency.rb +59 -16
- data/lib/httpx/plugins/follow_redirects.rb +1 -1
- data/lib/httpx/plugins/grpc.rb +21 -0
- data/lib/httpx/plugins/h2c.rb +12 -11
- data/lib/httpx/plugins/hedging.rb +284 -0
- data/lib/httpx/plugins/ntlm_auth.rb +5 -3
- data/lib/httpx/plugins/ntlm_v2_auth.rb +5 -3
- data/lib/httpx/plugins/oauth.rb +2 -2
- data/lib/httpx/plugins/proxy/http.rb +67 -26
- data/lib/httpx/plugins/proxy/socks5.rb +4 -4
- data/lib/httpx/plugins/proxy/ssh.rb +30 -11
- data/lib/httpx/plugins/proxy.rb +68 -54
- data/lib/httpx/plugins/push_promise.rb +4 -2
- data/lib/httpx/plugins/query.rb +1 -0
- data/lib/httpx/plugins/retries.rb +2 -2
- data/lib/httpx/plugins/server_sent_events.rb +1 -0
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +12 -10
- data/lib/httpx/plugins/stream_bidi.rb +4 -0
- data/lib/httpx/plugins/tracing.rb +99 -11
- data/lib/httpx/plugins/upgrade/h2.rb +3 -3
- data/lib/httpx/plugins/upgrade.rb +2 -0
- data/lib/httpx/plugins/webdav.rb +8 -0
- data/lib/httpx/request/body.rb +2 -1
- data/lib/httpx/request.rb +6 -6
- data/lib/httpx/resolver/https.rb +3 -2
- data/lib/httpx/resolver/native.rb +19 -9
- data/lib/httpx/resolver/resolver.rb +2 -2
- data/lib/httpx/resolver/system.rb +4 -0
- data/lib/httpx/resolver.rb +10 -6
- data/lib/httpx/response.rb +19 -7
- data/lib/httpx/selector.rb +111 -10
- data/lib/httpx/session.rb +36 -10
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/altsvc.rbs +3 -3
- data/sig/callbacks.rbs +1 -0
- data/sig/chainable.rbs +3 -1
- data/sig/connection/http1.rbs +4 -2
- data/sig/connection/http2.rbs +23 -17
- data/sig/connection.rbs +29 -7
- data/sig/headers.rbs +1 -1
- data/sig/httpx.rbs +2 -0
- data/sig/io/tcp.rbs +5 -0
- data/sig/options.rbs +43 -12
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +0 -1
- data/sig/plugins/auth.rbs +12 -5
- data/sig/plugins/expect.rbs +6 -0
- data/sig/plugins/fiber_concurrency.rbs +10 -1
- data/sig/plugins/hedging.rbs +44 -0
- data/sig/plugins/proxy/http.rbs +9 -2
- data/sig/plugins/proxy.rbs +3 -2
- data/sig/plugins/retries.rbs +2 -2
- data/sig/plugins/stream_bidi.rbs +0 -2
- data/sig/plugins/tracing.rbs +22 -5
- data/sig/request/body.rbs +1 -1
- data/sig/resolver/entry.rbs +2 -2
- data/sig/resolver/https.rbs +2 -0
- data/sig/resolver/native.rbs +3 -0
- data/sig/resolver/resolver.rbs +1 -1
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +1 -1
- data/sig/response.rbs +1 -2
- data/sig/selector.rbs +17 -3
- data/sig/session.rbs +8 -6
- metadata +7 -1
|
@@ -76,10 +76,10 @@ module HTTPX
|
|
|
76
76
|
super
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
super
|
|
79
|
+
def closed_for_select?
|
|
80
|
+
# @fiber-switch-guard
|
|
81
|
+
# a closed connection still holding requests will reconnect once the fiber closing its socket resumes.
|
|
82
|
+
super && @pending.empty?
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def on_io_error(e)
|
|
@@ -88,17 +88,12 @@ module HTTPX
|
|
|
88
88
|
# @fiber-switch-guard
|
|
89
89
|
# sockets closed during fiber scheduler switches are raised in separate fibers than the fiber the
|
|
90
90
|
# socket may be used in. this check verifies that this is actually about this socket.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if @state == :closing
|
|
94
|
-
# @fiber-switch-guard
|
|
95
|
-
# if the connection is reused across fibers, the socket may have been closed in the other fiber
|
|
96
|
-
# and switched here during the process, so continue what it was doing and transition to closed
|
|
97
|
-
# via #call.
|
|
91
|
+
unless to_io.closed? && !backlog?
|
|
98
92
|
call
|
|
99
|
-
|
|
100
|
-
super
|
|
93
|
+
return
|
|
101
94
|
end
|
|
95
|
+
|
|
96
|
+
super
|
|
102
97
|
end
|
|
103
98
|
|
|
104
99
|
def on_connect_error(e)
|
|
@@ -107,18 +102,50 @@ module HTTPX
|
|
|
107
102
|
# @fiber-switch-guard
|
|
108
103
|
# sockets closed during fiber scheduler switches are raised in separate fibers than the fiber the
|
|
109
104
|
# socket may be used in. this check verifies that this is actually about this socket.
|
|
110
|
-
|
|
105
|
+
unless to_io.closed? && !backlog?
|
|
106
|
+
call
|
|
107
|
+
return
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
super
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def pending_count
|
|
114
|
+
@pending.count(&:current_context?)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def interests
|
|
118
|
+
idling if (@state == :closed) && !no_more_requests?
|
|
111
119
|
|
|
112
120
|
super
|
|
113
121
|
end
|
|
114
122
|
|
|
115
123
|
private
|
|
116
124
|
|
|
125
|
+
def handle_transition(nextstate)
|
|
126
|
+
case nextstate
|
|
127
|
+
when :inactive
|
|
128
|
+
# @fiber-switch-guard
|
|
129
|
+
# connections may have requests (pending or inflight) from other fibers.
|
|
130
|
+
return if @state == :open && backlog?
|
|
131
|
+
end
|
|
132
|
+
super
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def no_requests_to_handle?
|
|
136
|
+
pending_count.zero? && @parser&.no_requests_to_handle?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @fiber-switch-guard
|
|
140
|
+
# pending requests from other fibers are still being waited for.
|
|
141
|
+
def clear_pending_requests
|
|
142
|
+
@pending.reject!(&:current_context?)
|
|
143
|
+
end
|
|
144
|
+
|
|
117
145
|
# checks whether the connection has any pending request (which the connection itself may
|
|
118
146
|
# have stored, or it may be somewhere in the parser).
|
|
119
147
|
def backlog?
|
|
120
|
-
@pending.any? ||
|
|
121
|
-
(@parser && (@parser.pending.any? || @parser.requests.any?))
|
|
148
|
+
@pending.any? || @parser&.backlog? || @parser&.waiting_for_ping?
|
|
122
149
|
end
|
|
123
150
|
end
|
|
124
151
|
|
|
@@ -132,6 +159,14 @@ module HTTPX
|
|
|
132
159
|
|
|
133
160
|
super
|
|
134
161
|
end
|
|
162
|
+
|
|
163
|
+
def backlog?
|
|
164
|
+
!no_more_requests?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def no_requests_to_handle?
|
|
168
|
+
@requests.none?(&:current_context?)
|
|
169
|
+
end
|
|
135
170
|
end
|
|
136
171
|
|
|
137
172
|
module HTTP2Methods
|
|
@@ -156,6 +191,14 @@ module HTTPX
|
|
|
156
191
|
super
|
|
157
192
|
end
|
|
158
193
|
|
|
194
|
+
def backlog?
|
|
195
|
+
!no_more_requests?
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def no_requests_to_handle?
|
|
199
|
+
@streams.each_key.none?(&:current_context?)
|
|
200
|
+
end
|
|
201
|
+
|
|
159
202
|
private
|
|
160
203
|
|
|
161
204
|
def on_close(_, error, _)
|
data/lib/httpx/plugins/grpc.rb
CHANGED
|
@@ -69,6 +69,17 @@ module HTTPX
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# adds support for the following options:
|
|
73
|
+
#
|
|
74
|
+
# :grpc_service :: name of the GRPC service service.
|
|
75
|
+
# :grpc_compression :: whether payloads should be GZIP-compressed (<tt>false</tt> by default).
|
|
76
|
+
# :grpc_rpcs :: maps local rpc names to the service RCP information.
|
|
77
|
+
# :grpc_deadline :: sets the service call deadline that the peer should stick to.
|
|
78
|
+
# :call_credentials :: callable which should yield a key-value hash which will be passed in the
|
|
79
|
+
# request HTTP headers.
|
|
80
|
+
#
|
|
81
|
+
# You're recommended not to set any of these options directly, and instead use methods such as
|
|
82
|
+
# InstanceMethods#rpc and InstanceMethods#build_stub.
|
|
72
83
|
module OptionsMethods
|
|
73
84
|
private
|
|
74
85
|
|
|
@@ -125,6 +136,9 @@ module HTTPX
|
|
|
125
136
|
end
|
|
126
137
|
|
|
127
138
|
module InstanceMethods
|
|
139
|
+
# returns a new session which sets the TLS channel credentials (+ca_path+ as the CA file,
|
|
140
|
+
# +key+ and +cert+ as the TLS public key and certificate, as well as other TLS options via
|
|
141
|
+
# +ssl_opts+, which should be compatible with what OpenSSL::SSL::SSLContext#set_params receives).
|
|
128
142
|
def with_channel_credentials(ca_path, key = nil, cert = nil, **ssl_opts)
|
|
129
143
|
# @type var ssl_params: ::Hash[::Symbol, untyped]
|
|
130
144
|
ssl_params = {
|
|
@@ -144,6 +158,8 @@ module HTTPX
|
|
|
144
158
|
with(ssl: ssl_params)
|
|
145
159
|
end
|
|
146
160
|
|
|
161
|
+
# returns a new session with a service RPC identified by +rpc_name+, passing +input+ and +output+ as its
|
|
162
|
+
# input encoder and output decoder respectively; extra options (like <tt>:deadline</tt>) can be passed using +opts+.
|
|
147
163
|
def rpc(rpc_name, input, output, **opts)
|
|
148
164
|
rpc_name = rpc_name.to_s
|
|
149
165
|
raise Error, "rpc #{rpc_name} already defined" if @options.grpc_rpcs.key?(rpc_name)
|
|
@@ -179,6 +195,9 @@ module HTTPX
|
|
|
179
195
|
))
|
|
180
196
|
end
|
|
181
197
|
|
|
198
|
+
# returns a new GRPC Service enabled session pointing to a +origin+ URI; when the
|
|
199
|
+
# +service+ is a GRPC Service object (as the ones provided by the `grpc` gem), it'll
|
|
200
|
+
# automatically generate the RPC methods.
|
|
182
201
|
def build_stub(origin, service: nil, compression: false)
|
|
183
202
|
scheme = @options.ssl.empty? ? "http" : "https"
|
|
184
203
|
|
|
@@ -212,6 +231,8 @@ module HTTPX
|
|
|
212
231
|
session.with(origin: origin, grpc_service: service, grpc_compression: compression)
|
|
213
232
|
end
|
|
214
233
|
|
|
234
|
+
# executes an RPC service identified by +rpc_method+. You're recommended to use the generated
|
|
235
|
+
# RPC methods directly though, in case you're using #build_stub.
|
|
215
236
|
def execute(rpc_method, input,
|
|
216
237
|
deadline: DEADLINE,
|
|
217
238
|
metadata: nil,
|
data/lib/httpx/plugins/h2c.rb
CHANGED
|
@@ -68,29 +68,30 @@ module HTTPX
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def upgrade_to_h2c(request, response)
|
|
71
|
-
|
|
71
|
+
@parser.reset_requests
|
|
72
72
|
|
|
73
|
-
@parser = request.options.h2c_class.new(@write_buffer, @options)
|
|
73
|
+
@parser = request.options.h2c_class.new(@write_buffer, @pending, @options)
|
|
74
74
|
set_parser_callbacks(@parser)
|
|
75
|
-
@inflight += 1 # request is being completed below
|
|
76
75
|
@parser.upgrade(request, response)
|
|
77
76
|
@upgrade_protocol = "h2c"
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
private
|
|
81
80
|
|
|
82
|
-
def send_request_to_parser(request)
|
|
83
|
-
super
|
|
81
|
+
def send_request_to_parser(request, *)
|
|
82
|
+
super.tap do |is_inflight|
|
|
83
|
+
next unless is_inflight
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
next unless request.headers["upgrade"] == "h2c" && parser.is_a?(Connection::HTTP1)
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
max_concurrent_requests = parser.max_concurrent_requests
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
next if max_concurrent_requests == 1
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
parser.max_concurrent_requests = 1
|
|
92
|
+
request.once(:response) do
|
|
93
|
+
parser.max_concurrent_requests = max_concurrent_requests
|
|
94
|
+
end
|
|
94
95
|
end
|
|
95
96
|
end
|
|
96
97
|
end
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
#
|
|
6
|
+
# This plugin adds support for request hedging, i.e. a latency-mitigation strategy that involves
|
|
7
|
+
# sending requests through multiple connections and returning the fastest response.
|
|
8
|
+
#
|
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Hedging#hedging
|
|
10
|
+
#
|
|
11
|
+
module Hedging
|
|
12
|
+
# :hedge_after:: seconds after which, if the request didn't complete yet, it'll be sent on a different connection
|
|
13
|
+
module OptionsMethods
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def option_hedge_after(value)
|
|
17
|
+
if value.respond_to?(:call)
|
|
18
|
+
value1 = value
|
|
19
|
+
value1 = value1.method(:call) unless value1.respond_to?(:arity)
|
|
20
|
+
|
|
21
|
+
# allow ->(*) arity as well, which is < 0
|
|
22
|
+
raise TypeError, "`:hedge_after` proc has invalid number of parameters" unless value1.arity.negative? || value1.arity.between?(
|
|
23
|
+
1, 2
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
else
|
|
27
|
+
value = Float(value)
|
|
28
|
+
raise TypeError, "`:hedge_after` must be positive" unless value.positive?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module InstanceMethods
|
|
36
|
+
def select_connection(connection, selector)
|
|
37
|
+
connection.try_yield_hedge_connection do |hedge_conn|
|
|
38
|
+
super.tap do
|
|
39
|
+
select_connection(hedge_conn, selector) if hedge_conn
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def do_request(request, connection, selector)
|
|
47
|
+
super.tap do
|
|
48
|
+
# forego hedging if the connection is already open, or if
|
|
49
|
+
# the connection is scheduled for DNS resolution, as pending requests
|
|
50
|
+
# will be hedged afterwards.
|
|
51
|
+
next if connection.open? || !connection.addresses?
|
|
52
|
+
|
|
53
|
+
hedge_request!(request, connection, selector)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# creates the hedge connection before a connection initiates the connection handshake.
|
|
58
|
+
# This excludes connections which are already open.
|
|
59
|
+
def on_resolver_connection(connection, selector)
|
|
60
|
+
super.tap do
|
|
61
|
+
next if connection.open? || connection.hedge_connection
|
|
62
|
+
|
|
63
|
+
connection.pending.each do |request|
|
|
64
|
+
hedge_request!(request, connection, selector)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
on_resolver_connection(connection.hedge_connection, selector) if connection.hedge_connection
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def hedge_connection(connection)
|
|
72
|
+
connection.hedge_connection || begin
|
|
73
|
+
new_connection = connection.dup
|
|
74
|
+
new_connection.log { "cloning hedge connection of connection##{connection.object_id}..." }
|
|
75
|
+
connection.hedge_connection = new_connection
|
|
76
|
+
new_connection
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def hedge_request!(request, connection, selector)
|
|
81
|
+
hedge_after = when_to_hedge(request)
|
|
82
|
+
|
|
83
|
+
if hedge_after&.positive?
|
|
84
|
+
# hedge after interval
|
|
85
|
+
cb = selector.after(hedge_after) do
|
|
86
|
+
next if request.response
|
|
87
|
+
|
|
88
|
+
request.hedge_request = hedge_request = request.dup
|
|
89
|
+
hedge_conn = hedge_connection(connection)
|
|
90
|
+
hedge_conn.send(hedge_request)
|
|
91
|
+
select_connection(hedge_conn, selector)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# forego hedging if request is over
|
|
95
|
+
request.once(:response) do
|
|
96
|
+
cb.cancel
|
|
97
|
+
end
|
|
98
|
+
else
|
|
99
|
+
request.hedge_request = hedge_request = request.dup
|
|
100
|
+
hedge_connection(connection).send(hedge_request)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def when_to_hedge(request)
|
|
105
|
+
hedge_after = request.options.hedge_after
|
|
106
|
+
|
|
107
|
+
return unless hedge_after
|
|
108
|
+
|
|
109
|
+
hedge_after = hedge_after.call(request) if hedge_after.respond_to?(:call)
|
|
110
|
+
|
|
111
|
+
hedge_after
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# hedge connections should not deactivate, instead they should terminate.
|
|
115
|
+
# this ensures that persistent hedge connections aren't treated as persistent,
|
|
116
|
+
# and close instead of deactivating.
|
|
117
|
+
def deactivate(selector, *)
|
|
118
|
+
hedge_connections = selector.each_connection.select(&:is_hedge_connection)
|
|
119
|
+
|
|
120
|
+
return super unless hedge_connections.any?
|
|
121
|
+
|
|
122
|
+
selector_close(selector, hedge_connections)
|
|
123
|
+
|
|
124
|
+
super
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# do not check hedge connections back in
|
|
128
|
+
def can_checkin?(connection)
|
|
129
|
+
super && !connection.is_hedge_connection
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
module RequestMethods
|
|
134
|
+
# TODO: would be nice to get rid of this
|
|
135
|
+
attr_reader :connection
|
|
136
|
+
|
|
137
|
+
attr_reader :hedge_request
|
|
138
|
+
|
|
139
|
+
def initialize(*)
|
|
140
|
+
super
|
|
141
|
+
@is_hedge_request = false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def hedge_request=(request)
|
|
145
|
+
@hedge_request = request
|
|
146
|
+
|
|
147
|
+
return unless request
|
|
148
|
+
|
|
149
|
+
@is_hedge_request = !request.hedge_request.nil?
|
|
150
|
+
|
|
151
|
+
return if @is_hedge_request
|
|
152
|
+
|
|
153
|
+
request.hedge_request = self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def response=(response)
|
|
157
|
+
return super unless @hedge_request # rubocop:disable Lint/ReturnInVoidContext
|
|
158
|
+
|
|
159
|
+
prev_response = @response
|
|
160
|
+
|
|
161
|
+
begin
|
|
162
|
+
super
|
|
163
|
+
ensure
|
|
164
|
+
@response = prev_response if prev_response
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def emit_response(response)
|
|
169
|
+
hedge_request = @hedge_request
|
|
170
|
+
|
|
171
|
+
return super unless hedge_request
|
|
172
|
+
|
|
173
|
+
return super unless hedge_request &&
|
|
174
|
+
(
|
|
175
|
+
hedge_request.response.nil? ||
|
|
176
|
+
!hedge_request.response.finished?
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# if this response is for a hedge request, and the main request
|
|
180
|
+
# didn't complete yet, then we replace it.
|
|
181
|
+
|
|
182
|
+
super.tap do
|
|
183
|
+
hedge_request.response = response
|
|
184
|
+
|
|
185
|
+
# should cancel any pending HTTP/2 stream; there's no way to
|
|
186
|
+
# cancel an HTTP/1 request, so that one will run to completion.
|
|
187
|
+
hedge_request.emit(:refuse)
|
|
188
|
+
end
|
|
189
|
+
ensure
|
|
190
|
+
self.hedge_request = nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
module ResponseMethods
|
|
195
|
+
def finish!
|
|
196
|
+
connection = @request.connection
|
|
197
|
+
|
|
198
|
+
super
|
|
199
|
+
|
|
200
|
+
return unless connection && (hedge_connection = connection.hedge_connection)
|
|
201
|
+
|
|
202
|
+
# disconnect connection with its hedge, so that objects do not leak after
|
|
203
|
+
# they're not needed.
|
|
204
|
+
connection.hedge_connection = hedge_connection.hedge_connection = nil
|
|
205
|
+
|
|
206
|
+
# remarking itself as the main connection, so that it's not discarded when
|
|
207
|
+
# going back to the pool.
|
|
208
|
+
connection.is_hedge_connection = false
|
|
209
|
+
hedge_connection.is_hedge_connection = true
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
module ConnectionMethods
|
|
214
|
+
attr_reader :hedge_connection
|
|
215
|
+
|
|
216
|
+
attr_accessor :is_hedge_connection
|
|
217
|
+
|
|
218
|
+
def initialize(*)
|
|
219
|
+
super
|
|
220
|
+
@is_hedge_connection = false
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def addresses=(addrs)
|
|
224
|
+
try_yield_hedge_connection do |hedge_conn|
|
|
225
|
+
super.tap do
|
|
226
|
+
hedge_conn.addresses = addrs if hedge_conn
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def hedge_connection=(connection)
|
|
232
|
+
@hedge_connection = connection
|
|
233
|
+
|
|
234
|
+
return unless connection
|
|
235
|
+
|
|
236
|
+
@is_hedge_connection = !connection.hedge_connection.nil?
|
|
237
|
+
|
|
238
|
+
return if @is_hedge_connection
|
|
239
|
+
|
|
240
|
+
connection.hedge_connection = self
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def try_yield_hedge_connection
|
|
244
|
+
return yield(nil) unless (hedge_conn = @hedge_connection)
|
|
245
|
+
|
|
246
|
+
@hedge_connection = nil
|
|
247
|
+
|
|
248
|
+
begin
|
|
249
|
+
hedge_conn.try_yield_hedge_connection do
|
|
250
|
+
yield(hedge_conn)
|
|
251
|
+
end
|
|
252
|
+
ensure
|
|
253
|
+
@hedge_connection = hedge_conn
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# connections should not merge with their hedge connections
|
|
258
|
+
def mergeable?(connection)
|
|
259
|
+
super && connection.hedge_connection != self
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def send(request)
|
|
263
|
+
super.tap do
|
|
264
|
+
if @hedge_connection && !request.hedge_request
|
|
265
|
+
request.hedge_request = hedge_request = request.dup
|
|
266
|
+
@hedge_connection.send(hedge_request)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
private
|
|
272
|
+
|
|
273
|
+
def send_request_to_parser(request, *)
|
|
274
|
+
# if original request was in the queue, but has since been finished
|
|
275
|
+
# in the hedge connection.
|
|
276
|
+
return false if request.response&.finished?
|
|
277
|
+
|
|
278
|
+
super
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
register_plugin(:hedging, Hedging)
|
|
283
|
+
end
|
|
284
|
+
end
|
|
@@ -28,6 +28,7 @@ module HTTPX
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
module InstanceMethods
|
|
31
|
+
# returns a new session which authenticates requests using NTLM auth.
|
|
31
32
|
def ntlm_auth(user, password, domain = nil)
|
|
32
33
|
with(ntlm: Authentication::Ntlm.new(user, password, domain: domain))
|
|
33
34
|
end
|
|
@@ -40,9 +41,10 @@ module HTTPX
|
|
|
40
41
|
|
|
41
42
|
if ntlm
|
|
42
43
|
request.authorize(ntlm.negotiate)
|
|
43
|
-
|
|
44
|
+
responses = wrap { super(request) }
|
|
45
|
+
probe_response = responses.first
|
|
44
46
|
|
|
45
|
-
return
|
|
47
|
+
return responses unless probe_response.is_a?(Response)
|
|
46
48
|
|
|
47
49
|
if probe_response.status == 401 && ntlm.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
48
50
|
request.transition(:idle)
|
|
@@ -50,7 +52,7 @@ module HTTPX
|
|
|
50
52
|
request.authorize(ntlm.authenticate(request, probe_response.headers["www-authenticate"]).encode("utf-8"))
|
|
51
53
|
super(request)
|
|
52
54
|
else
|
|
53
|
-
|
|
55
|
+
responses
|
|
54
56
|
end
|
|
55
57
|
else
|
|
56
58
|
super(request)
|
|
@@ -54,6 +54,7 @@ module HTTPX
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
module InstanceMethods
|
|
57
|
+
# returns a new session which authenticates requests using NTLM v2 auth.
|
|
57
58
|
def ntlm_auth(user, password, domain = nil)
|
|
58
59
|
with(ntlm: Authenticator.new(user, password, domain: domain))
|
|
59
60
|
end
|
|
@@ -66,9 +67,10 @@ module HTTPX
|
|
|
66
67
|
|
|
67
68
|
if ntlm
|
|
68
69
|
request.authorize(ntlm.negotiate)
|
|
69
|
-
|
|
70
|
+
responses = wrap { super(request) }
|
|
71
|
+
probe_response = responses.first
|
|
70
72
|
|
|
71
|
-
return
|
|
73
|
+
return responses unless probe_response.is_a?(Response)
|
|
72
74
|
|
|
73
75
|
if probe_response.status == 401 && ntlm.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
74
76
|
request.transition(:idle)
|
|
@@ -77,7 +79,7 @@ module HTTPX
|
|
|
77
79
|
probe_response.headers["www-authenticate"]).encode("utf-8"))
|
|
78
80
|
super(request)
|
|
79
81
|
else
|
|
80
|
-
|
|
82
|
+
responses
|
|
81
83
|
end
|
|
82
84
|
else
|
|
83
85
|
super(request)
|
data/lib/httpx/plugins/oauth.rb
CHANGED
|
@@ -290,7 +290,7 @@ module HTTPX
|
|
|
290
290
|
|
|
291
291
|
def reset_auth_header_value!
|
|
292
292
|
super.tap do
|
|
293
|
-
@oauth_session
|
|
293
|
+
@oauth_session&.reset!
|
|
294
294
|
end
|
|
295
295
|
end
|
|
296
296
|
|
|
@@ -323,7 +323,7 @@ module HTTPX
|
|
|
323
323
|
module InstanceMethods
|
|
324
324
|
private
|
|
325
325
|
|
|
326
|
-
def prepare_to_retry(
|
|
326
|
+
def prepare_to_retry(*)
|
|
327
327
|
@oauth_session.reset! if @oauth_session
|
|
328
328
|
|
|
329
329
|
super
|