httpx 0.6.7 → 0.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/README.md +7 -5
- data/doc/release_notes/0_0_1.md +7 -0
- data/doc/release_notes/0_0_2.md +9 -0
- data/doc/release_notes/0_0_3.md +9 -0
- data/doc/release_notes/0_0_4.md +7 -0
- data/doc/release_notes/0_0_5.md +5 -0
- data/doc/release_notes/0_1_0.md +9 -0
- data/doc/release_notes/0_2_0.md +5 -0
- data/doc/release_notes/0_2_1.md +16 -0
- data/doc/release_notes/0_3_0.md +12 -0
- data/doc/release_notes/0_3_1.md +6 -0
- data/doc/release_notes/0_4_0.md +51 -0
- data/doc/release_notes/0_4_1.md +3 -0
- data/doc/release_notes/0_5_0.md +15 -0
- data/doc/release_notes/0_5_1.md +14 -0
- data/doc/release_notes/0_6_0.md +5 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_6_2.md +6 -0
- data/doc/release_notes/0_6_3.md +13 -0
- data/doc/release_notes/0_6_4.md +21 -0
- data/doc/release_notes/0_6_5.md +22 -0
- data/doc/release_notes/0_6_6.md +19 -0
- data/doc/release_notes/0_6_7.md +5 -0
- data/doc/release_notes/0_7_0.md +46 -0
- data/doc/release_notes/0_8_0.md +27 -0
- data/doc/release_notes/0_8_1.md +8 -0
- data/doc/release_notes/0_8_2.md +7 -0
- data/doc/release_notes/0_9_0.md +38 -0
- data/lib/httpx/adapters/faraday.rb +2 -2
- data/lib/httpx/altsvc.rb +18 -2
- data/lib/httpx/chainable.rb +27 -9
- data/lib/httpx/connection.rb +215 -65
- data/lib/httpx/connection/http1.rb +54 -18
- data/lib/httpx/connection/http2.rb +100 -37
- data/lib/httpx/extensions.rb +2 -2
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +11 -3
- data/lib/httpx/io/tcp.rb +12 -2
- data/lib/httpx/loggable.rb +6 -6
- data/lib/httpx/options.rb +43 -28
- data/lib/httpx/plugins/authentication.rb +1 -1
- data/lib/httpx/plugins/compression.rb +28 -8
- data/lib/httpx/plugins/compression/gzip.rb +22 -12
- data/lib/httpx/plugins/cookies.rb +12 -8
- data/lib/httpx/plugins/digest_authentication.rb +2 -0
- data/lib/httpx/plugins/expect.rb +79 -0
- data/lib/httpx/plugins/follow_redirects.rb +1 -2
- data/lib/httpx/plugins/h2c.rb +0 -1
- data/lib/httpx/plugins/proxy.rb +23 -20
- data/lib/httpx/plugins/proxy/http.rb +9 -6
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +5 -1
- data/lib/httpx/plugins/proxy/ssh.rb +0 -4
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/retries.rb +32 -29
- data/lib/httpx/pool.rb +15 -10
- data/lib/httpx/registry.rb +2 -1
- data/lib/httpx/request.rb +8 -6
- data/lib/httpx/resolver.rb +7 -8
- data/lib/httpx/resolver/https.rb +15 -3
- data/lib/httpx/resolver/native.rb +22 -32
- data/lib/httpx/resolver/options.rb +2 -2
- data/lib/httpx/resolver/resolver_mixin.rb +1 -1
- data/lib/httpx/response.rb +17 -3
- data/lib/httpx/selector.rb +96 -95
- data/lib/httpx/session.rb +33 -34
- data/lib/httpx/timeout.rb +7 -1
- data/lib/httpx/version.rb +1 -1
- metadata +77 -20
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
#
|
6
|
+
# This plugin makes all HTTP/1.1 requests with a body send the "Expect: 100-continue".
|
7
|
+
#
|
8
|
+
# https://gitlab.com/honeyryderchuck/httpx/wikis/Expect#expect
|
9
|
+
#
|
10
|
+
module Expect
|
11
|
+
EXPECT_TIMEOUT = 2
|
12
|
+
|
13
|
+
def self.extra_options(options)
|
14
|
+
Class.new(options.class) do
|
15
|
+
def_option(:expect_timeout) do |seconds|
|
16
|
+
seconds = Integer(seconds)
|
17
|
+
raise Error, ":expect_timeout must be positive" unless seconds.positive?
|
18
|
+
|
19
|
+
seconds
|
20
|
+
end
|
21
|
+
|
22
|
+
def_option(:expect_threshold_size) do |bytes|
|
23
|
+
bytes = Integer(bytes)
|
24
|
+
raise Error, ":expect_threshold_size must be positive" unless bytes.positive?
|
25
|
+
|
26
|
+
bytes
|
27
|
+
end
|
28
|
+
end.new(options).merge(expect_timeout: EXPECT_TIMEOUT)
|
29
|
+
end
|
30
|
+
|
31
|
+
module RequestBodyMethods
|
32
|
+
def initialize(*, options)
|
33
|
+
super
|
34
|
+
return if @body.nil?
|
35
|
+
|
36
|
+
if (threshold = options.expect_threshold_size)
|
37
|
+
unless unbounded_body?
|
38
|
+
return if @body.bytesize < threshold
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@headers["expect"] = "100-continue"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module ConnectionMethods
|
47
|
+
def send(request)
|
48
|
+
request.once(:expects) do
|
49
|
+
@timers.after(@options.expect_timeout) do
|
50
|
+
if request.state == :expects && !request.expects?
|
51
|
+
request.headers.delete("expect")
|
52
|
+
handle(request)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module InstanceMethods
|
61
|
+
def fetch_response(request, connections, options)
|
62
|
+
response = @responses.delete(request)
|
63
|
+
return unless response
|
64
|
+
|
65
|
+
if response.status == 417 && request.headers.key?("expect")
|
66
|
+
request.headers.delete("expect")
|
67
|
+
request.transition(:idle)
|
68
|
+
connection = find_connection(request, connections, options)
|
69
|
+
connection.send(request)
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
response
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
register_plugin :expect, Expect
|
78
|
+
end
|
79
|
+
end
|
@@ -21,7 +21,7 @@ module HTTPX
|
|
21
21
|
Class.new(options.class) do
|
22
22
|
def_option(:max_redirects) do |num|
|
23
23
|
num = Integer(num)
|
24
|
-
raise Error, ":max_redirects must be positive"
|
24
|
+
raise Error, ":max_redirects must be positive" if num.negative?
|
25
25
|
|
26
26
|
num
|
27
27
|
end
|
@@ -61,7 +61,6 @@ module HTTPX
|
|
61
61
|
|
62
62
|
connection = find_connection(retry_request, connections, options)
|
63
63
|
connection.send(retry_request)
|
64
|
-
set_request_timeout(connection, retry_request, options)
|
65
64
|
nil
|
66
65
|
end
|
67
66
|
|
data/lib/httpx/plugins/h2c.rb
CHANGED
@@ -42,7 +42,6 @@ module HTTPX
|
|
42
42
|
connection = find_connection(request, connections, options)
|
43
43
|
connections << connection unless connections.include?(connection)
|
44
44
|
connection.upgrade(request, response)
|
45
|
-
set_request_timeout(connection, request, options)
|
46
45
|
end
|
47
46
|
response
|
48
47
|
end
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -68,17 +68,17 @@ module HTTPX
|
|
68
68
|
def extra_options(options)
|
69
69
|
Class.new(options.class) do
|
70
70
|
def_option(:proxy) do |pr|
|
71
|
-
|
71
|
+
if pr.is_a?(Parameters)
|
72
|
+
pr
|
73
|
+
else
|
74
|
+
Hash[pr]
|
75
|
+
end
|
72
76
|
end
|
73
77
|
end.new(options)
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
77
81
|
module InstanceMethods
|
78
|
-
def with_proxy(*args)
|
79
|
-
branch(default_options.with_proxy(*args))
|
80
|
-
end
|
81
|
-
|
82
82
|
private
|
83
83
|
|
84
84
|
def proxy_uris(uri, options)
|
@@ -104,7 +104,7 @@ module HTTPX
|
|
104
104
|
connection = pool.find_connection(uri, proxy_options) || build_connection(uri, proxy_options)
|
105
105
|
unless connections.nil? || connections.include?(connection)
|
106
106
|
connections << connection
|
107
|
-
set_connection_callbacks(connection, options)
|
107
|
+
set_connection_callbacks(connection, connections, options)
|
108
108
|
end
|
109
109
|
connection
|
110
110
|
end
|
@@ -129,13 +129,12 @@ module HTTPX
|
|
129
129
|
connection = find_connection(request, connections, options)
|
130
130
|
connections << connection unless connections.include?(connection)
|
131
131
|
connection.send(request)
|
132
|
-
set_request_timeout(connection, request, options)
|
133
132
|
return
|
134
133
|
end
|
135
134
|
response
|
136
135
|
end
|
137
136
|
|
138
|
-
def build_altsvc_connection(_, _, _, _, options)
|
137
|
+
def build_altsvc_connection(_, _, _, _, _, options)
|
139
138
|
return if options.proxy
|
140
139
|
|
141
140
|
super
|
@@ -180,20 +179,9 @@ module HTTPX
|
|
180
179
|
super || @state == :connecting || @state == :connected
|
181
180
|
end
|
182
181
|
|
183
|
-
def to_io
|
184
|
-
return super unless @options.proxy
|
185
|
-
|
186
|
-
case @state
|
187
|
-
when :idle
|
188
|
-
transition(:connecting)
|
189
|
-
when :connected
|
190
|
-
transition(:open)
|
191
|
-
end
|
192
|
-
@io.to_io
|
193
|
-
end
|
194
|
-
|
195
182
|
def call
|
196
183
|
super
|
184
|
+
|
197
185
|
return unless @options.proxy
|
198
186
|
|
199
187
|
case @state
|
@@ -211,11 +199,26 @@ module HTTPX
|
|
211
199
|
emit(:close)
|
212
200
|
end
|
213
201
|
|
202
|
+
private
|
203
|
+
|
204
|
+
def connect
|
205
|
+
return super unless @options.proxy
|
206
|
+
|
207
|
+
case @state
|
208
|
+
when :idle
|
209
|
+
transition(:connecting)
|
210
|
+
when :connected
|
211
|
+
transition(:open)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
214
215
|
def transition(nextstate)
|
215
216
|
return super unless @options.proxy
|
216
217
|
|
217
218
|
case nextstate
|
218
219
|
when :closing
|
220
|
+
# this is a hack so that we can use the super method
|
221
|
+
# and it'll thing that the current state is open
|
219
222
|
@state = :open if @state == :connecting
|
220
223
|
end
|
221
224
|
super
|
@@ -7,6 +7,10 @@ module HTTPX
|
|
7
7
|
module Proxy
|
8
8
|
module HTTP
|
9
9
|
module ConnectionMethods
|
10
|
+
def connecting?
|
11
|
+
super || @state == :connecting || @state == :connected
|
12
|
+
end
|
13
|
+
|
10
14
|
private
|
11
15
|
|
12
16
|
def transition(nextstate)
|
@@ -34,7 +38,6 @@ module HTTPX
|
|
34
38
|
when :idle
|
35
39
|
@parser = ProxyParser.new(@write_buffer, @options)
|
36
40
|
set_parser_callbacks(@parser)
|
37
|
-
@parser.on(:close) { transition(:closing) }
|
38
41
|
end
|
39
42
|
end
|
40
43
|
super
|
@@ -48,7 +51,7 @@ module HTTPX
|
|
48
51
|
#
|
49
52
|
if req.uri.scheme == "https"
|
50
53
|
connect_request = ConnectRequest.new(req.uri, @options)
|
51
|
-
|
54
|
+
@inflight += 1
|
52
55
|
parser.send(connect_request)
|
53
56
|
else
|
54
57
|
transition(:connected)
|
@@ -56,6 +59,7 @@ module HTTPX
|
|
56
59
|
end
|
57
60
|
|
58
61
|
def __http_on_connect(_, response)
|
62
|
+
@inflight -= 1
|
59
63
|
if response.status == 200
|
60
64
|
req = @pending.first
|
61
65
|
request_uri = req.uri
|
@@ -67,6 +71,7 @@ module HTTPX
|
|
67
71
|
while (req = pending.shift)
|
68
72
|
req.emit(:response, response)
|
69
73
|
end
|
74
|
+
reset
|
70
75
|
end
|
71
76
|
end
|
72
77
|
end
|
@@ -91,15 +96,13 @@ module HTTPX
|
|
91
96
|
def headline_uri(request)
|
92
97
|
return super unless request.verb == :connect
|
93
98
|
|
94
|
-
|
95
|
-
tunnel = "#{uri.hostname}:#{uri.port}"
|
99
|
+
tunnel = request.path
|
96
100
|
log { "establishing HTTP proxy tunnel to #{tunnel}" }
|
97
101
|
tunnel
|
98
102
|
end
|
99
103
|
|
100
104
|
def empty?
|
101
|
-
@requests.reject { |r| r.verb == :connect }.empty? ||
|
102
|
-
@requests.all? { |request| !request.response.nil? }
|
105
|
+
@requests.reject { |r| r.verb == :connect }.empty? || @requests.all? { |request| !request.response.nil? }
|
103
106
|
end
|
104
107
|
end
|
105
108
|
|
@@ -39,7 +39,7 @@ module HTTPX
|
|
39
39
|
|
40
40
|
@parser = nil
|
41
41
|
end
|
42
|
-
log(level: 1
|
42
|
+
log(level: 1) { "SOCKS4: #{nextstate}: #{@write_buffer.to_s.inspect}" } unless nextstate == :open
|
43
43
|
super
|
44
44
|
end
|
45
45
|
|
@@ -31,6 +31,10 @@ module HTTPX
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
def connecting?
|
35
|
+
super || @state == :authenticating || @state == :negotiating
|
36
|
+
end
|
37
|
+
|
34
38
|
private
|
35
39
|
|
36
40
|
def transition(nextstate)
|
@@ -60,7 +64,7 @@ module HTTPX
|
|
60
64
|
|
61
65
|
@parser = nil
|
62
66
|
end
|
63
|
-
log(level: 1
|
67
|
+
log(level: 1) { "SOCKS5: #{nextstate}: #{@write_buffer.to_s.inspect}" } unless nextstate == :open
|
64
68
|
super
|
65
69
|
end
|
66
70
|
|
@@ -43,9 +43,9 @@ module HTTPX
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def __on_promise_request(parser, stream, h)
|
46
|
-
log(level: 1
|
46
|
+
log(level: 1) do
|
47
47
|
# :nocov:
|
48
|
-
h.map { |k, v| "-> PROMISE HEADER: #{k}: #{v}" }.join("\n")
|
48
|
+
h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
|
49
49
|
# :nocov:
|
50
50
|
end
|
51
51
|
headers = @options.headers_class.new(h)
|
@@ -28,10 +28,10 @@ module HTTPX
|
|
28
28
|
# number of seconds after which one can retry the request
|
29
29
|
def_option(:retry_after) do |num|
|
30
30
|
# return early if callable
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
unless num.respond_to?(:call)
|
32
|
+
num = Integer(num)
|
33
|
+
raise Error, ":retry_after must be positive" unless num.positive?
|
34
|
+
end
|
35
35
|
|
36
36
|
num
|
37
37
|
end
|
@@ -46,7 +46,7 @@ module HTTPX
|
|
46
46
|
def_option(:retry_change_requests)
|
47
47
|
|
48
48
|
def_option(:retry_on) do |callback|
|
49
|
-
raise ":retry_on must be called with the response" unless callback.respond_to?(:call)
|
49
|
+
raise ":retry_on must be called with the response" unless callback.respond_to?(:call)
|
50
50
|
|
51
51
|
callback
|
52
52
|
end
|
@@ -63,18 +63,38 @@ module HTTPX
|
|
63
63
|
def fetch_response(request, connections, options)
|
64
64
|
response = super
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
if response.is_a?(ErrorResponse) &&
|
66
|
+
if response &&
|
69
67
|
request.retries.positive? &&
|
70
68
|
__repeatable_request?(request, options) &&
|
71
|
-
|
72
|
-
|
69
|
+
(
|
70
|
+
# rubocop:disable Style/MultilineTernaryOperator
|
71
|
+
options.retry_on ?
|
72
|
+
options.retry_on.call(response) :
|
73
|
+
(
|
74
|
+
response.is_a?(ErrorResponse) && __retryable_error?(response.error)
|
75
|
+
)
|
76
|
+
# rubocop:enable Style/MultilineTernaryOperator
|
77
|
+
)
|
78
|
+
|
73
79
|
request.retries -= 1
|
74
80
|
log { "failed to get response, #{request.retries} tries to go..." }
|
75
81
|
request.transition(:idle)
|
76
|
-
|
77
|
-
|
82
|
+
|
83
|
+
retry_after = options.retry_after
|
84
|
+
if retry_after
|
85
|
+
retry_after = retry_after.call(request) if retry_after.respond_to?(:call)
|
86
|
+
|
87
|
+
log { "retrying after #{retry_after} secs..." }
|
88
|
+
pool.after(retry_after) do
|
89
|
+
log { "retrying!!" }
|
90
|
+
connection = find_connection(request, connections, options)
|
91
|
+
connection.send(request)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
connection = find_connection(request, connections, options)
|
95
|
+
connection.send(request)
|
96
|
+
end
|
97
|
+
|
78
98
|
return
|
79
99
|
end
|
80
100
|
response
|
@@ -87,23 +107,6 @@ module HTTPX
|
|
87
107
|
def __retryable_error?(ex)
|
88
108
|
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
|
89
109
|
end
|
90
|
-
|
91
|
-
def __retry_request(connection, request, options)
|
92
|
-
retry_after = options.retry_after
|
93
|
-
unless retry_after
|
94
|
-
connection.send(request)
|
95
|
-
set_request_timeout(connection, request, options)
|
96
|
-
return
|
97
|
-
end
|
98
|
-
|
99
|
-
retry_after = retry_after.call(request) if retry_after.respond_to?(:call)
|
100
|
-
log { "retrying after #{retry_after} secs..." }
|
101
|
-
pool.after(retry_after) do
|
102
|
-
log { "retrying!!" }
|
103
|
-
connection.send(request)
|
104
|
-
set_request_timeout(connection, request, options)
|
105
|
-
end
|
106
|
-
end
|
107
110
|
end
|
108
111
|
|
109
112
|
module RequestMethods
|
data/lib/httpx/pool.rb
CHANGED
@@ -14,7 +14,7 @@ module HTTPX
|
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
@resolvers = {}
|
17
|
-
@
|
17
|
+
@_resolver_ios = {}
|
18
18
|
@timers = Timers::Group.new
|
19
19
|
@selector = Selector.new
|
20
20
|
@connections = []
|
@@ -27,12 +27,19 @@ module HTTPX
|
|
27
27
|
|
28
28
|
def next_tick
|
29
29
|
catch(:jump_tick) do
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
timeout = [next_timeout, @timers.wait_interval].compact.min
|
31
|
+
if timeout && timeout.negative?
|
32
|
+
@timers.fire
|
33
|
+
throw(:jump_tick)
|
33
34
|
end
|
35
|
+
|
36
|
+
@selector.select(timeout, &:call)
|
37
|
+
|
34
38
|
@timers.fire
|
35
39
|
end
|
40
|
+
rescue Interrupt
|
41
|
+
@connections.each(&:reset)
|
42
|
+
raise
|
36
43
|
rescue StandardError => e
|
37
44
|
@connections.each do |connection|
|
38
45
|
connection.emit(:error, e)
|
@@ -51,6 +58,7 @@ module HTTPX
|
|
51
58
|
|
52
59
|
def init_connection(connection, _options)
|
53
60
|
resolve_connection(connection)
|
61
|
+
connection.timers = @timers
|
54
62
|
connection.on(:open) do
|
55
63
|
@connected_connections += 1
|
56
64
|
end
|
@@ -79,7 +87,7 @@ module HTTPX
|
|
79
87
|
resolver << connection
|
80
88
|
return if resolver.empty?
|
81
89
|
|
82
|
-
@
|
90
|
+
@_resolver_ios[resolver] ||= @selector.register(resolver)
|
83
91
|
end
|
84
92
|
|
85
93
|
def on_resolver_connection(connection)
|
@@ -111,8 +119,7 @@ module HTTPX
|
|
111
119
|
@resolvers.delete(resolver_type)
|
112
120
|
|
113
121
|
@selector.deregister(resolver)
|
114
|
-
|
115
|
-
monitor.close if monitor
|
122
|
+
@_resolver_ios.delete(resolver)
|
116
123
|
resolver.close unless resolver.closed?
|
117
124
|
end
|
118
125
|
|
@@ -121,10 +128,8 @@ module HTTPX
|
|
121
128
|
# if open, an IO was passed upstream, therefore
|
122
129
|
# consider it connected already.
|
123
130
|
@connected_connections += 1
|
124
|
-
@selector.register(connection, :rw)
|
125
|
-
else
|
126
|
-
@selector.register(connection, :w)
|
127
131
|
end
|
132
|
+
@selector.register(connection)
|
128
133
|
connection.on(:close) do
|
129
134
|
unregister_connection(connection)
|
130
135
|
end
|