httpx 1.8.3 → 1.9.0
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_4.md +13 -0
- data/doc/release_notes/1_9_0.md +75 -0
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/altsvc.rb +29 -15
- data/lib/httpx/callbacks.rb +2 -2
- data/lib/httpx/chainable.rb +6 -3
- data/lib/httpx/connection/http1.rb +11 -19
- data/lib/httpx/connection/http2.rb +112 -27
- data/lib/httpx/connection.rb +184 -46
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/tcp.rb +18 -14
- 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 -0
- data/lib/httpx/plugins/basic_auth.rb +2 -0
- data/lib/httpx/plugins/callbacks.rb +7 -6
- data/lib/httpx/plugins/circuit_breaker.rb +1 -0
- data/lib/httpx/plugins/cookies/cookie.rb +1 -1
- data/lib/httpx/plugins/cookies.rb +2 -2
- data/lib/httpx/plugins/digest_auth.rb +2 -0
- data/lib/httpx/plugins/expect.rb +2 -0
- data/lib/httpx/plugins/fiber_concurrency.rb +6 -0
- data/lib/httpx/plugins/follow_redirects.rb +8 -6
- data/lib/httpx/plugins/grpc/call.rb +4 -4
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -2
- data/lib/httpx/plugins/grpc.rb +21 -0
- data/lib/httpx/plugins/hedging.rb +284 -0
- data/lib/httpx/plugins/ntlm_auth.rb +1 -0
- data/lib/httpx/plugins/ntlm_v2_auth.rb +1 -0
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +71 -16
- data/lib/httpx/plugins/proxy/ssh.rb +30 -11
- data/lib/httpx/plugins/proxy.rb +66 -45
- data/lib/httpx/plugins/push_promise.rb +4 -2
- data/lib/httpx/plugins/retries.rb +13 -3
- data/lib/httpx/plugins/server_sent_events.rb +2 -1
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +2 -2
- data/lib/httpx/plugins/tracing.rb +61 -14
- data/lib/httpx/plugins/upgrade/h2.rb +1 -1
- 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 +10 -7
- data/lib/httpx/resolver/multi.rb +2 -2
- data/lib/httpx/resolver/resolver.rb +2 -2
- data/lib/httpx/resolver.rb +5 -1
- data/lib/httpx/response/body.rb +2 -2
- data/lib/httpx/response.rb +19 -11
- data/lib/httpx/selector.rb +3 -3
- data/lib/httpx/session.rb +38 -11
- data/lib/httpx/transcoder/chunker.rb +2 -2
- data/lib/httpx/transcoder/json.rb +7 -7
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +3 -3
- data/sig/callbacks.rbs +1 -0
- data/sig/chainable.rbs +1 -0
- data/sig/connection/http1.rbs +1 -1
- data/sig/connection/http2.rbs +30 -16
- data/sig/connection.rbs +19 -1
- data/sig/headers.rbs +1 -1
- data/sig/httpx.rbs +2 -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/hedging.rbs +44 -0
- data/sig/plugins/proxy/http.rbs +8 -1
- data/sig/plugins/proxy.rbs +3 -2
- data/sig/plugins/retries.rbs +2 -2
- data/sig/plugins/tracing.rbs +14 -5
- data/sig/request/body.rbs +1 -1
- data/sig/request.rbs +2 -1
- data/sig/resolver/entry.rbs +2 -2
- data/sig/response.rbs +1 -2
- data/sig/selector.rbs +2 -2
- data/sig/session.rbs +7 -5
- metadata +7 -1
|
@@ -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 if request.response&.finished?
|
|
277
|
+
|
|
278
|
+
super
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
register_plugin(:hedging, Hedging)
|
|
283
|
+
end
|
|
284
|
+
end
|
data/lib/httpx/plugins/oauth.rb
CHANGED
|
@@ -10,19 +10,44 @@ module HTTPX
|
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# adds support for the following options:
|
|
14
|
+
#
|
|
15
|
+
# :proxy_headers :: hash of HTTP headers to send while connecting to the proxy.
|
|
16
|
+
# :enable_proxy_tunnel :: when <tt>true</tt>, it forces the establishment of the CONNECT proxy
|
|
17
|
+
# tunnel, in cases where it wouldn't otherwise normally establish it
|
|
18
|
+
# (i.e. if the request is an http:// request).
|
|
19
|
+
module OptionsMethods
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def option_proxy_headers(value)
|
|
23
|
+
value = value.dup if value.frozen?
|
|
24
|
+
|
|
25
|
+
headers_class.new(value)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def option_enable_proxy_tunnel(value) # rubocop:disable Naming/PredicateMethod
|
|
29
|
+
!!value
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
13
33
|
module InstanceMethods
|
|
34
|
+
# returns a new session with the proxy authentication scheme set to <tt>"basic"</tt>, for HTTP Basic Auth.
|
|
14
35
|
def with_proxy_basic_auth(opts)
|
|
15
36
|
with(proxy: opts.merge(scheme: "basic"))
|
|
16
37
|
end
|
|
17
38
|
|
|
39
|
+
# returns a new session with the proxy authentication scheme set to <tt>"digest"</tt>, for HTTP Digest Auth.
|
|
18
40
|
def with_proxy_digest_auth(opts)
|
|
19
41
|
with(proxy: opts.merge(scheme: "digest"))
|
|
20
42
|
end
|
|
21
43
|
|
|
44
|
+
# returns a new session with the proxy authentication scheme set to <tt>"ntlm"</tt>, for NTLM Auth.
|
|
22
45
|
def with_proxy_ntlm_auth(opts)
|
|
23
46
|
with(proxy: opts.merge(scheme: "ntlm"))
|
|
24
47
|
end
|
|
25
48
|
|
|
49
|
+
private
|
|
50
|
+
|
|
26
51
|
def fetch_response(request, selector, options)
|
|
27
52
|
response = super
|
|
28
53
|
|
|
@@ -46,7 +71,12 @@ module HTTPX
|
|
|
46
71
|
end
|
|
47
72
|
|
|
48
73
|
module ConnectionMethods
|
|
49
|
-
def
|
|
74
|
+
def initialize(*)
|
|
75
|
+
@tunnel_parser = nil
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def handle_connect_error(*)
|
|
50
80
|
if @state == :connecting
|
|
51
81
|
# proxy connect related requests should not be reenqueed
|
|
52
82
|
@parser.reset
|
|
@@ -57,8 +87,20 @@ module HTTPX
|
|
|
57
87
|
super
|
|
58
88
|
end
|
|
59
89
|
|
|
90
|
+
def on_error(error, *)
|
|
91
|
+
error = error.to_connection_error if error.is_a?(RequestTimeoutError) && connecting?
|
|
92
|
+
|
|
93
|
+
super
|
|
94
|
+
end
|
|
95
|
+
|
|
60
96
|
private
|
|
61
97
|
|
|
98
|
+
def purge_after_closed
|
|
99
|
+
super
|
|
100
|
+
|
|
101
|
+
@tunnel_parser = nil
|
|
102
|
+
end
|
|
103
|
+
|
|
62
104
|
def handle_transition(nextstate)
|
|
63
105
|
return super unless @options.proxy && @options.proxy.uri.scheme == "http"
|
|
64
106
|
|
|
@@ -69,8 +111,15 @@ module HTTPX
|
|
|
69
111
|
@io.connect
|
|
70
112
|
return unless @io.connected?
|
|
71
113
|
|
|
72
|
-
@
|
|
73
|
-
@parser
|
|
114
|
+
@tunnel_parser || begin
|
|
115
|
+
if @parser
|
|
116
|
+
# in case this happens, requests from @parser need to be reenqueued for delivery.
|
|
117
|
+
enqueue_pending_requests_from_parser(@parser)
|
|
118
|
+
@parser = nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@tunnel_parser = @parser = parser =
|
|
122
|
+
parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
|
|
74
123
|
parser.extend(ProxyParser)
|
|
75
124
|
parser.on(:response, &method(:__http_on_connect))
|
|
76
125
|
parser.on(:close) do
|
|
@@ -86,6 +135,9 @@ module HTTPX
|
|
|
86
135
|
|
|
87
136
|
initial_state = @state
|
|
88
137
|
|
|
138
|
+
# because #reset will nillify @parser...
|
|
139
|
+
tunnel_parser = @parser
|
|
140
|
+
|
|
89
141
|
reset
|
|
90
142
|
|
|
91
143
|
if @pending.empty?
|
|
@@ -98,8 +150,8 @@ module HTTPX
|
|
|
98
150
|
connect_request = parser = nil
|
|
99
151
|
|
|
100
152
|
if initial_state == :connecting
|
|
101
|
-
parser =
|
|
102
|
-
|
|
153
|
+
parser = tunnel_parser
|
|
154
|
+
parser.reset
|
|
103
155
|
if @pending.first.is_a?(ConnectRequest)
|
|
104
156
|
connect_request = @pending.shift # this happened when reenqueing
|
|
105
157
|
end
|
|
@@ -107,7 +159,7 @@ module HTTPX
|
|
|
107
159
|
|
|
108
160
|
idling
|
|
109
161
|
|
|
110
|
-
@parser = parser
|
|
162
|
+
@tunnel_parser = @parser = parser
|
|
111
163
|
if connect_request
|
|
112
164
|
@inflight += 1
|
|
113
165
|
parser.send(connect_request)
|
|
@@ -115,7 +167,7 @@ module HTTPX
|
|
|
115
167
|
transition(:connecting)
|
|
116
168
|
end
|
|
117
169
|
end
|
|
118
|
-
__http_proxy_connect
|
|
170
|
+
__http_proxy_connect
|
|
119
171
|
end
|
|
120
172
|
return if @state == :connected
|
|
121
173
|
when :connected
|
|
@@ -124,7 +176,7 @@ module HTTPX
|
|
|
124
176
|
case @state
|
|
125
177
|
when :connecting
|
|
126
178
|
parser = @parser
|
|
127
|
-
@parser = nil
|
|
179
|
+
@tunnel_parser = @parser = nil
|
|
128
180
|
parser.close
|
|
129
181
|
when :idle
|
|
130
182
|
@parser.callbacks.clear
|
|
@@ -134,16 +186,19 @@ module HTTPX
|
|
|
134
186
|
super
|
|
135
187
|
end
|
|
136
188
|
|
|
137
|
-
def __http_proxy_connect
|
|
189
|
+
def __http_proxy_connect
|
|
138
190
|
req = @pending.first
|
|
139
|
-
|
|
191
|
+
enable_proxy_tunnel = (
|
|
192
|
+
req.options.enable_proxy_tunnel.nil? && req && req.uri.scheme == "https"
|
|
193
|
+
) || req.options.enable_proxy_tunnel
|
|
194
|
+
|
|
195
|
+
if enable_proxy_tunnel
|
|
140
196
|
# if the first request after CONNECT is to an https address, it is assumed that
|
|
141
197
|
# all requests in the queue are not only ALL HTTPS, but they also share the certificate,
|
|
142
198
|
# and therefore, will share the connection.
|
|
143
199
|
#
|
|
144
200
|
connect_request = ConnectRequest.new(req.uri, @options)
|
|
145
|
-
|
|
146
|
-
parser.send(connect_request)
|
|
201
|
+
send(connect_request)
|
|
147
202
|
else
|
|
148
203
|
handle_transition(:connected)
|
|
149
204
|
end
|
|
@@ -154,7 +209,7 @@ module HTTPX
|
|
|
154
209
|
if response.is_a?(Response) && response.status == 200
|
|
155
210
|
req = @pending.first
|
|
156
211
|
request_uri = req.uri
|
|
157
|
-
@io = ProxySSL.new(@io, request_uri, @options)
|
|
212
|
+
@io = ProxySSL.new(@io, request_uri, @options) if request_uri.scheme == "https"
|
|
158
213
|
transition(:connected)
|
|
159
214
|
throw(:called)
|
|
160
215
|
elsif response.is_a?(Response) &&
|
|
@@ -164,8 +219,7 @@ module HTTPX
|
|
|
164
219
|
|
|
165
220
|
request.transition(:idle)
|
|
166
221
|
request.headers["proxy-authorization"] = @options.proxy.authenticate(request, response.headers["proxy-authenticate"])
|
|
167
|
-
|
|
168
|
-
@inflight += 1
|
|
222
|
+
send(request)
|
|
169
223
|
else
|
|
170
224
|
pending = @pending + @parser.pending
|
|
171
225
|
while (req = pending.shift)
|
|
@@ -201,7 +255,8 @@ module HTTPX
|
|
|
201
255
|
class ConnectRequest < Request
|
|
202
256
|
def initialize(uri, options)
|
|
203
257
|
super("CONNECT", uri, options)
|
|
204
|
-
@headers.
|
|
258
|
+
@headers = options.headers_class.new
|
|
259
|
+
merge_headers(options.proxy_headers) if options.proxy_headers
|
|
205
260
|
end
|
|
206
261
|
|
|
207
262
|
def path
|
|
@@ -21,31 +21,50 @@ module HTTPX
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
module InstanceMethods
|
|
24
|
+
def initialize(*)
|
|
25
|
+
super
|
|
26
|
+
@_gateway = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
def request(*args, **options)
|
|
25
30
|
raise ArgumentError, "must perform at least one request" if args.empty?
|
|
26
31
|
|
|
27
32
|
requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
|
|
28
33
|
|
|
29
|
-
request = requests.first
|
|
34
|
+
request = requests.first
|
|
35
|
+
|
|
36
|
+
return super unless request
|
|
30
37
|
|
|
31
38
|
request_options = request.options
|
|
32
39
|
|
|
33
40
|
return super unless request_options.proxy
|
|
34
41
|
|
|
35
|
-
ssh_options = request_options.proxy
|
|
36
|
-
ssh_uris = ssh_options.delete(:uri)
|
|
37
|
-
|
|
42
|
+
ssh_options = request_options.proxy.dup
|
|
43
|
+
ssh_uris = Array(ssh_options.delete(:uri))
|
|
44
|
+
|
|
45
|
+
proxy_ns = 0
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
ssh_uri = URI(ssh_uris[proxy_ns])
|
|
49
|
+
|
|
50
|
+
return super unless ssh_uri.scheme == "ssh"
|
|
51
|
+
|
|
52
|
+
ssh_username = ssh_options.delete(:username)
|
|
53
|
+
ssh_options[:port] ||= ssh_uri.port || 22
|
|
54
|
+
if request_options.debug
|
|
55
|
+
ssh_options[:verbose] = request_options.debug_level == 2 ? :debug : :info
|
|
56
|
+
end
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
request_uri = URI(requests.first.uri)
|
|
59
|
+
@_gateway = Net::SSH::Gateway.new(ssh_uri.host, ssh_username, ssh_options)
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
log { "failed connecting to ssh proxy, trying next..." }
|
|
62
|
+
proxy_ns += 1
|
|
63
|
+
raise e if proxy_ns >= ssh_uris.size
|
|
40
64
|
|
|
41
|
-
|
|
42
|
-
ssh_options[:port] ||= ssh_uri.port || 22
|
|
43
|
-
if request_options.debug
|
|
44
|
-
ssh_options[:verbose] = request_options.debug_level == 2 ? :debug : :info
|
|
65
|
+
retry
|
|
45
66
|
end
|
|
46
67
|
|
|
47
|
-
request_uri = URI(requests.first.uri)
|
|
48
|
-
@_gateway = Net::SSH::Gateway.new(ssh_uri.host, ssh_username, ssh_options)
|
|
49
68
|
begin
|
|
50
69
|
@_gateway.open(request_uri.host, request_uri.port) do |local_port|
|
|
51
70
|
io = build_gateway_socket(local_port, request_uri, request_options)
|