httpx 1.5.1 → 1.6.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_6_0.md +50 -0
- data/lib/httpx/adapters/datadog.rb +23 -13
- data/lib/httpx/adapters/faraday.rb +14 -9
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/callbacks.rb +1 -1
- data/lib/httpx/connection/http1.rb +5 -6
- data/lib/httpx/connection/http2.rb +30 -12
- data/lib/httpx/connection.rb +17 -24
- data/lib/httpx/errors.rb +3 -1
- data/lib/httpx/io/ssl.rb +1 -4
- data/lib/httpx/io/tcp.rb +25 -16
- data/lib/httpx/io/unix.rb +4 -3
- data/lib/httpx/loggable.rb +4 -1
- data/lib/httpx/options.rb +252 -158
- data/lib/httpx/plugins/aws_sdk_authentication.rb +2 -0
- data/lib/httpx/plugins/aws_sigv4.rb +2 -0
- data/lib/httpx/plugins/callbacks.rb +13 -1
- data/lib/httpx/plugins/circuit_breaker.rb +2 -0
- data/lib/httpx/plugins/content_digest.rb +2 -0
- 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 +195 -0
- data/lib/httpx/plugins/follow_redirects.rb +2 -0
- data/lib/httpx/plugins/grpc.rb +2 -0
- data/lib/httpx/plugins/h2c.rb +26 -16
- data/lib/httpx/plugins/internal_telemetry.rb +0 -49
- data/lib/httpx/plugins/ntlm_auth.rb +2 -0
- data/lib/httpx/plugins/oauth.rb +2 -0
- data/lib/httpx/plugins/persistent.rb +27 -18
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy/ssh.rb +2 -0
- data/lib/httpx/plugins/proxy.rb +61 -20
- data/lib/httpx/plugins/response_cache/file_store.rb +2 -2
- data/lib/httpx/plugins/response_cache.rb +2 -0
- data/lib/httpx/plugins/retries.rb +2 -0
- data/lib/httpx/plugins/ssrf_filter.rb +2 -2
- data/lib/httpx/plugins/stream_bidi.rb +3 -3
- data/lib/httpx/plugins/upgrade/h2.rb +11 -1
- data/lib/httpx/plugins/upgrade.rb +8 -0
- data/lib/httpx/pool.rb +15 -10
- data/lib/httpx/request/body.rb +8 -3
- data/lib/httpx/request.rb +22 -11
- data/lib/httpx/resolver/entry.rb +30 -0
- data/lib/httpx/resolver/https.rb +3 -1
- data/lib/httpx/resolver/multi.rb +5 -2
- data/lib/httpx/resolver/native.rb +15 -6
- data/lib/httpx/resolver/resolver.rb +3 -5
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/resolver.rb +34 -21
- data/lib/httpx/response/body.rb +1 -1
- data/lib/httpx/response/buffer.rb +13 -18
- data/lib/httpx/selector.rb +92 -34
- data/lib/httpx/session.rb +89 -30
- data/lib/httpx/session_extensions.rb +3 -2
- data/lib/httpx/transcoder/form.rb +1 -13
- data/lib/httpx/transcoder/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/transcoder/multipart.rb +14 -0
- data/lib/httpx/transcoder/utils/deflater.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/callbacks.rbs +1 -1
- data/sig/chainable.rbs +1 -0
- data/sig/connection/http1.rbs +2 -0
- data/sig/connection/http2.rbs +4 -0
- data/sig/connection.rbs +6 -6
- data/sig/errors.rbs +3 -1
- data/sig/io/ssl.rbs +1 -1
- data/sig/io/tcp.rbs +13 -7
- data/sig/io/udp.rbs +7 -2
- data/sig/io/unix.rbs +0 -1
- data/sig/io.rbs +0 -3
- data/sig/options.rbs +63 -10
- data/sig/plugins/fiber_concurrency.rbs +51 -0
- data/sig/plugins/h2c.rbs +5 -1
- data/sig/plugins/persistent.rbs +1 -1
- data/sig/plugins/proxy/socks4.rbs +1 -1
- data/sig/plugins/proxy/socks5.rbs +1 -1
- data/sig/plugins/proxy.rbs +5 -2
- data/sig/plugins/ssrf_filter.rbs +1 -1
- data/sig/plugins/stream_bidi.rbs +2 -2
- data/sig/request.rbs +4 -1
- data/sig/resolver/entry.rbs +13 -0
- data/sig/resolver/native.rbs +1 -0
- data/sig/resolver/resolver.rbs +2 -3
- data/sig/resolver/system.rbs +2 -2
- data/sig/resolver.rbs +10 -11
- data/sig/response.rbs +2 -2
- data/sig/selector.rbs +18 -10
- data/sig/session.rbs +4 -0
- data/sig/transcoder/form.rbs +3 -3
- data/sig/transcoder/multipart.rbs +9 -3
- metadata +9 -3
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
# This plugin makes a session reuse the same selector across all fibers in a given thread.
|
6
|
+
#
|
7
|
+
# This enables integration with fiber scheduler implementations such as [async](https://github.com/async).
|
8
|
+
#
|
9
|
+
# # https://gitlab.com/os85/httpx/wikis/FiberConcurrency
|
10
|
+
#
|
11
|
+
module FiberConcurrency
|
12
|
+
def self.subplugins
|
13
|
+
{
|
14
|
+
h2c: FiberConcurrencyH2C,
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
private
|
20
|
+
|
21
|
+
def send_request(request, *)
|
22
|
+
request.set_context!
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_current_selector
|
28
|
+
super(&nil) || begin
|
29
|
+
return unless block_given?
|
30
|
+
|
31
|
+
default = yield
|
32
|
+
|
33
|
+
set_current_selector(default)
|
34
|
+
|
35
|
+
default
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module RequestMethods
|
41
|
+
# the execution context (fiber) this request was sent on.
|
42
|
+
attr_reader :context
|
43
|
+
|
44
|
+
def initialize(*)
|
45
|
+
super
|
46
|
+
@context = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# sets the execution context for this request. the default is the current fiber.
|
50
|
+
def set_context!
|
51
|
+
@context ||= Fiber.current # rubocop:disable Naming/MemoizedInstanceVariableName
|
52
|
+
end
|
53
|
+
|
54
|
+
# checks whether the current execution context is the one where the request was created.
|
55
|
+
def current_context?
|
56
|
+
@context == Fiber.current
|
57
|
+
end
|
58
|
+
|
59
|
+
def complete!(response = @response)
|
60
|
+
@context = nil
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module ConnectionMethods
|
66
|
+
def current_context?
|
67
|
+
@pending.any?(&:current_context?) || (
|
68
|
+
@sibling && @sibling.pending.any?(&:current_context?)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def interests
|
73
|
+
return if connecting? && @pending.none?(&:current_context?)
|
74
|
+
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
def send(request)
|
79
|
+
# DoH requests bypass the session, so context needs to be set here.
|
80
|
+
request.set_context!
|
81
|
+
|
82
|
+
super
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module HTTP1Methods
|
87
|
+
def interests
|
88
|
+
request = @request || @requests.first
|
89
|
+
|
90
|
+
return unless request
|
91
|
+
|
92
|
+
return unless request.current_context? || @requests.any?(&:current_context?) || @pending.any?(&:current_context?)
|
93
|
+
|
94
|
+
super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module HTTP2Methods
|
99
|
+
def initialize(*)
|
100
|
+
super
|
101
|
+
@contexts = Hash.new { |hs, k| hs[k] = Set.new }
|
102
|
+
end
|
103
|
+
|
104
|
+
def interests
|
105
|
+
if @connection.state == :connected && @handshake_completed && !@contexts.key?(Fiber.current)
|
106
|
+
return :w unless @pings.empty?
|
107
|
+
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
super
|
112
|
+
end
|
113
|
+
|
114
|
+
def send(request, *)
|
115
|
+
add_to_context(request)
|
116
|
+
|
117
|
+
super
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def on_close(_, error, _)
|
123
|
+
if error == :http_1_1_required
|
124
|
+
# remove all pending requests context
|
125
|
+
@pending.each do |req|
|
126
|
+
clear_from_context(req)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
super
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_stream_close(_, request, error)
|
134
|
+
clear_from_context(request) if error != :stream_closed && @streams.key?(request)
|
135
|
+
|
136
|
+
super
|
137
|
+
end
|
138
|
+
|
139
|
+
def teardown(request = nil)
|
140
|
+
super
|
141
|
+
|
142
|
+
if request
|
143
|
+
clear_from_context(request)
|
144
|
+
else
|
145
|
+
@contexts.clear
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def add_to_context(request)
|
150
|
+
@contexts[request.context] << request
|
151
|
+
end
|
152
|
+
|
153
|
+
def clear_from_context(request)
|
154
|
+
requests = @contexts[request.context]
|
155
|
+
|
156
|
+
requests.delete(request)
|
157
|
+
|
158
|
+
@contexts.delete(request.context) if requests.empty?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
module NativeResolverMethods
|
163
|
+
private
|
164
|
+
|
165
|
+
def calculate_interests
|
166
|
+
return if @queries.empty?
|
167
|
+
|
168
|
+
return unless @queries.values.any?(&:current_context?) || @connections.any?(&:current_context?)
|
169
|
+
|
170
|
+
super
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module SystemResolverMethods
|
175
|
+
def interests
|
176
|
+
return unless @queries.any? { |_, conn| conn.current_context? }
|
177
|
+
|
178
|
+
super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
module FiberConcurrencyH2C
|
183
|
+
module HTTP2Methods
|
184
|
+
def upgrade(request, *)
|
185
|
+
@contexts[request.context] << request
|
186
|
+
|
187
|
+
super
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
register_plugin :fiber_concurrency, FiberConcurrency
|
194
|
+
end
|
195
|
+
end
|
@@ -34,6 +34,8 @@ module HTTPX
|
|
34
34
|
# (defaults to <tt>false</tt>).
|
35
35
|
# :redirect_on :: optional callback which receives the redirect location and can halt the redirect chain if it returns <tt>false</tt>.
|
36
36
|
module OptionsMethods
|
37
|
+
private
|
38
|
+
|
37
39
|
def option_max_redirects(value)
|
38
40
|
num = Integer(value)
|
39
41
|
raise TypeError, ":max_redirects must be positive" if num.negative?
|
data/lib/httpx/plugins/grpc.rb
CHANGED
data/lib/httpx/plugins/h2c.rb
CHANGED
@@ -21,24 +21,17 @@ module HTTPX
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def extra_options(options)
|
24
|
-
options.merge(
|
24
|
+
options.merge(
|
25
|
+
h2c_class: Class.new(options.http2_class) { include(H2CParser) },
|
26
|
+
max_concurrent_requests: 1,
|
27
|
+
upgrade_handlers: options.upgrade_handlers.merge("h2c" => self),
|
28
|
+
)
|
25
29
|
end
|
26
30
|
end
|
27
31
|
|
28
|
-
|
29
|
-
def
|
30
|
-
|
31
|
-
# request in the connection
|
32
|
-
stream = @connection.upgrade
|
33
|
-
|
34
|
-
# on_settings
|
35
|
-
handle_stream(stream, request)
|
36
|
-
@streams[request] = stream
|
37
|
-
|
38
|
-
# clean up data left behind in the buffer, if the server started
|
39
|
-
# sending frames
|
40
|
-
data = response.read
|
41
|
-
@connection << data
|
32
|
+
module OptionsMethods
|
33
|
+
def option_h2c_class(value)
|
34
|
+
value
|
42
35
|
end
|
43
36
|
end
|
44
37
|
|
@@ -82,7 +75,7 @@ module HTTPX
|
|
82
75
|
@inflight -= prev_parser.requests.size
|
83
76
|
end
|
84
77
|
|
85
|
-
@parser =
|
78
|
+
@parser = request.options.h2c_class.new(@write_buffer, @options)
|
86
79
|
set_parser_callbacks(@parser)
|
87
80
|
@inflight += 1
|
88
81
|
@parser.upgrade(request, response)
|
@@ -111,6 +104,23 @@ module HTTPX
|
|
111
104
|
end
|
112
105
|
end
|
113
106
|
end
|
107
|
+
|
108
|
+
module H2CParser
|
109
|
+
def upgrade(request, response)
|
110
|
+
# skip checks, it is assumed that this is the first
|
111
|
+
# request in the connection
|
112
|
+
stream = @connection.upgrade
|
113
|
+
|
114
|
+
# on_settings
|
115
|
+
handle_stream(stream, request)
|
116
|
+
@streams[request] = stream
|
117
|
+
|
118
|
+
# clean up data left behind in the buffer, if the server started
|
119
|
+
# sending frames
|
120
|
+
data = response.read
|
121
|
+
@connection << data
|
122
|
+
end
|
123
|
+
end
|
114
124
|
end
|
115
125
|
register_plugin(:h2c, H2C)
|
116
126
|
end
|
@@ -50,15 +50,6 @@ module HTTPX
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
module NativeResolverMethods
|
54
|
-
def transition(nextstate)
|
55
|
-
state = @state
|
56
|
-
val = super
|
57
|
-
meter_elapsed_time("Resolver::Native: #{state} -> #{nextstate}")
|
58
|
-
val
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
53
|
module InstanceMethods
|
63
54
|
def self.included(klass)
|
64
55
|
klass.prepend TrackTimeMethods
|
@@ -69,13 +60,6 @@ module HTTPX
|
|
69
60
|
meter_elapsed_time("Session: initializing...")
|
70
61
|
super
|
71
62
|
meter_elapsed_time("Session: initialized!!!")
|
72
|
-
resolver_type = @options.resolver_class
|
73
|
-
resolver_type = Resolver.resolver_for(resolver_type)
|
74
|
-
return unless resolver_type <= Resolver::Native
|
75
|
-
|
76
|
-
resolver_type.prepend TrackTimeMethods
|
77
|
-
resolver_type.prepend NativeResolverMethods
|
78
|
-
@options = @options.merge(resolver_class: resolver_type)
|
79
63
|
end
|
80
64
|
|
81
65
|
def close(*)
|
@@ -104,33 +88,6 @@ module HTTPX
|
|
104
88
|
end
|
105
89
|
end
|
106
90
|
|
107
|
-
module RequestMethods
|
108
|
-
def self.included(klass)
|
109
|
-
klass.prepend Loggable
|
110
|
-
klass.prepend TrackTimeMethods
|
111
|
-
super
|
112
|
-
end
|
113
|
-
|
114
|
-
def transition(nextstate)
|
115
|
-
prev_state = @state
|
116
|
-
super
|
117
|
-
meter_elapsed_time("Request##{object_id}[#{@verb} #{@uri}: #{prev_state}] -> #{@state}") if prev_state != @state
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
module ConnectionMethods
|
122
|
-
def self.included(klass)
|
123
|
-
klass.prepend TrackTimeMethods
|
124
|
-
super
|
125
|
-
end
|
126
|
-
|
127
|
-
def handle_transition(nextstate)
|
128
|
-
state = @state
|
129
|
-
super
|
130
|
-
meter_elapsed_time("Connection##{object_id}[#{@origin}]: #{state} -> #{nextstate}") if nextstate == @state
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
91
|
module PoolMethods
|
135
92
|
def self.included(klass)
|
136
93
|
klass.prepend Loggable
|
@@ -138,12 +95,6 @@ module HTTPX
|
|
138
95
|
super
|
139
96
|
end
|
140
97
|
|
141
|
-
def checkout_connection(request_uri, options)
|
142
|
-
super.tap do |connection|
|
143
|
-
meter_elapsed_time("Pool##{object_id}: checked out connection for Connection##{connection.object_id}[#{connection.origin}]}")
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
98
|
def checkin_connection(connection)
|
148
99
|
super.tap do
|
149
100
|
meter_elapsed_time("Pool##{object_id}: checked in connection for Connection##{connection.object_id}[#{connection.origin}]}")
|
data/lib/httpx/plugins/oauth.rb
CHANGED
@@ -18,13 +18,17 @@ module HTTPX
|
|
18
18
|
# https://gitlab.com/os85/httpx/wikis/Persistent
|
19
19
|
#
|
20
20
|
module Persistent
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
class << self
|
22
|
+
def load_dependencies(klass)
|
23
|
+
klass.plugin(:fiber_concurrency)
|
24
|
+
|
25
|
+
max_retries = if klass.default_options.respond_to?(:max_retries)
|
26
|
+
[klass.default_options.max_retries, 1].max
|
27
|
+
else
|
28
|
+
1
|
29
|
+
end
|
30
|
+
klass.plugin(:retries, max_retries: max_retries)
|
26
31
|
end
|
27
|
-
klass.plugin(:retries, max_retries: max_retries)
|
28
32
|
end
|
29
33
|
|
30
34
|
def self.extra_options(options)
|
@@ -32,6 +36,23 @@ module HTTPX
|
|
32
36
|
end
|
33
37
|
|
34
38
|
module InstanceMethods
|
39
|
+
def close(*)
|
40
|
+
super
|
41
|
+
|
42
|
+
# traverse other threads and unlink respective selector
|
43
|
+
# WARNING: this is not thread safe, make sure that the session isn't being
|
44
|
+
# used anymore, or all non-main threads are stopped.
|
45
|
+
Thread.list.each do |th|
|
46
|
+
store = thread_selector_store(th)
|
47
|
+
|
48
|
+
next unless store && store.key?(self)
|
49
|
+
|
50
|
+
selector = store.delete(self)
|
51
|
+
|
52
|
+
selector_close(selector)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
35
56
|
private
|
36
57
|
|
37
58
|
def repeatable_request?(request, _)
|
@@ -54,18 +75,6 @@ module HTTPX
|
|
54
75
|
# consequences.
|
55
76
|
(!ex.is_a?(RequestTimeoutError) || @options.max_retries != 1)
|
56
77
|
end
|
57
|
-
|
58
|
-
def get_current_selector
|
59
|
-
super(&nil) || begin
|
60
|
-
return unless block_given?
|
61
|
-
|
62
|
-
default = yield
|
63
|
-
|
64
|
-
set_current_selector(default)
|
65
|
-
|
66
|
-
default
|
67
|
-
end
|
68
|
-
end
|
69
78
|
end
|
70
79
|
end
|
71
80
|
register_plugin :persistent, Persistent
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
-
class
|
4
|
+
class ProxyError < ConnectionError; end
|
5
5
|
|
6
6
|
module Plugins
|
7
7
|
#
|
@@ -15,7 +15,8 @@ module HTTPX
|
|
15
15
|
# https://gitlab.com/os85/httpx/wikis/Proxy
|
16
16
|
#
|
17
17
|
module Proxy
|
18
|
-
|
18
|
+
class ProxyConnectionError < ProxyError; end
|
19
|
+
|
19
20
|
PROXY_ERRORS = [TimeoutError, IOError, SystemCallError, Error].freeze
|
20
21
|
|
21
22
|
class << self
|
@@ -28,6 +29,12 @@ module HTTPX
|
|
28
29
|
def extra_options(options)
|
29
30
|
options.merge(supported_proxy_protocols: [])
|
30
31
|
end
|
32
|
+
|
33
|
+
def subplugins
|
34
|
+
{
|
35
|
+
retries: ProxyRetries,
|
36
|
+
}
|
37
|
+
end
|
31
38
|
end
|
32
39
|
|
33
40
|
class Parameters
|
@@ -135,6 +142,8 @@ module HTTPX
|
|
135
142
|
# :proxy :: proxy options defining *:uri*, *:username*, *:password* or
|
136
143
|
# *:scheme* (i.e. <tt>{ uri: "http://proxy" }</tt>)
|
137
144
|
module OptionsMethods
|
145
|
+
private
|
146
|
+
|
138
147
|
def option_proxy(value)
|
139
148
|
value.is_a?(Parameters) ? value : Parameters.new(**Hash[value])
|
140
149
|
end
|
@@ -160,9 +169,9 @@ module HTTPX
|
|
160
169
|
|
161
170
|
next_proxy = proxy.uri
|
162
171
|
|
163
|
-
raise
|
172
|
+
raise ProxyError, "Failed to connect to proxy" unless next_proxy
|
164
173
|
|
165
|
-
raise
|
174
|
+
raise ProxyError,
|
166
175
|
"#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
|
167
176
|
|
168
177
|
if (no_proxy = proxy.no_proxy)
|
@@ -179,20 +188,28 @@ module HTTPX
|
|
179
188
|
private
|
180
189
|
|
181
190
|
def fetch_response(request, selector, options)
|
182
|
-
response =
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
191
|
+
response = request.response # in case it goes wrong later
|
192
|
+
|
193
|
+
begin
|
194
|
+
response = super
|
195
|
+
|
196
|
+
if response.is_a?(ErrorResponse) && proxy_error?(request, response, options)
|
197
|
+
options.proxy.shift
|
198
|
+
|
199
|
+
# return last error response if no more proxies to try
|
200
|
+
return response if options.proxy.uri.nil?
|
201
|
+
|
202
|
+
log { "failed connecting to proxy, trying next..." }
|
203
|
+
request.transition(:idle)
|
204
|
+
send_request(request, selector, options)
|
205
|
+
return
|
206
|
+
end
|
207
|
+
response
|
208
|
+
rescue ProxyError
|
209
|
+
# may happen if coupled with retries, and there are no more proxies to try, in which case
|
210
|
+
# it'll end up here
|
211
|
+
response
|
194
212
|
end
|
195
|
-
response
|
196
213
|
end
|
197
214
|
|
198
215
|
def proxy_error?(_request, response, options)
|
@@ -203,15 +220,15 @@ module HTTPX
|
|
203
220
|
when NativeResolveError
|
204
221
|
proxy_uri = URI(options.proxy.uri)
|
205
222
|
|
206
|
-
|
223
|
+
unresolved_host = error.host
|
207
224
|
|
208
225
|
# failed resolving proxy domain
|
209
|
-
|
226
|
+
unresolved_host == proxy_uri.host
|
210
227
|
when ResolveError
|
211
228
|
proxy_uri = URI(options.proxy.uri)
|
212
229
|
|
213
230
|
error.message.end_with?(proxy_uri.to_s)
|
214
|
-
when
|
231
|
+
when ProxyConnectionError
|
215
232
|
# timeout errors connecting to proxy
|
216
233
|
true
|
217
234
|
else
|
@@ -251,6 +268,14 @@ module HTTPX
|
|
251
268
|
when :connecting
|
252
269
|
consume
|
253
270
|
end
|
271
|
+
rescue *PROXY_ERRORS => e
|
272
|
+
if connecting?
|
273
|
+
error = ProxyConnectionError.new(e.message)
|
274
|
+
error.set_backtrace(e.backtrace)
|
275
|
+
raise error
|
276
|
+
end
|
277
|
+
|
278
|
+
raise e
|
254
279
|
end
|
255
280
|
|
256
281
|
def reset
|
@@ -292,13 +317,29 @@ module HTTPX
|
|
292
317
|
end
|
293
318
|
super
|
294
319
|
end
|
320
|
+
|
321
|
+
def purge_after_closed
|
322
|
+
super
|
323
|
+
@io = @io.proxy_io if @io.respond_to?(:proxy_io)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
module ProxyRetries
|
328
|
+
module InstanceMethods
|
329
|
+
def retryable_error?(ex)
|
330
|
+
super || ex.is_a?(ProxyConnectionError)
|
331
|
+
end
|
332
|
+
end
|
295
333
|
end
|
296
334
|
end
|
297
335
|
register_plugin :proxy, Proxy
|
298
336
|
end
|
299
337
|
|
300
338
|
class ProxySSL < SSL
|
339
|
+
attr_reader :proxy_io
|
340
|
+
|
301
341
|
def initialize(tcp, request_uri, options)
|
342
|
+
@proxy_io = tcp
|
302
343
|
@io = tcp.to_io
|
303
344
|
super(request_uri, tcp.addresses, options)
|
304
345
|
@hostname = request_uri.host
|
@@ -81,7 +81,7 @@ module HTTPX::Plugins
|
|
81
81
|
|
82
82
|
response.body.rewind
|
83
83
|
|
84
|
-
|
84
|
+
IO.copy_stream(response.body, f)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
@@ -131,7 +131,7 @@ module HTTPX::Plugins
|
|
131
131
|
response.original_request = original_request
|
132
132
|
response.finish!
|
133
133
|
|
134
|
-
|
134
|
+
IO.copy_stream(f, response.body)
|
135
135
|
|
136
136
|
response
|
137
137
|
end
|
@@ -57,6 +57,8 @@ module HTTPX
|
|
57
57
|
# :retry_on :: callable which alternatively defines a different rule for when a response is to be retried
|
58
58
|
# (i.e. <tt>->(res) { ... }</tt>).
|
59
59
|
module OptionsMethods
|
60
|
+
private
|
61
|
+
|
60
62
|
def option_retry_after(value)
|
61
63
|
# return early if callable
|
62
64
|
unless value.respond_to?(:call)
|
@@ -91,6 +91,8 @@ module HTTPX
|
|
91
91
|
#
|
92
92
|
# :allowed_schemes :: list of URI schemes allowed (defaults to <tt>["https", "http"]</tt>)
|
93
93
|
module OptionsMethods
|
94
|
+
private
|
95
|
+
|
94
96
|
def option_allowed_schemes(value)
|
95
97
|
Array(value)
|
96
98
|
end
|
@@ -129,8 +131,6 @@ module HTTPX
|
|
129
131
|
end
|
130
132
|
|
131
133
|
def addresses=(addrs)
|
132
|
-
addrs = addrs.map { |addr| addr.is_a?(IPAddr) ? addr : IPAddr.new(addr) }
|
133
|
-
|
134
134
|
addrs.reject!(&SsrfFilter.method(:unsafe_ip_address?))
|
135
135
|
|
136
136
|
raise ServerSideRequestForgeryError, "#{@origin.host} has no public IP addresses" if addrs.empty?
|