httpx 0.7.0 → 0.10.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/LICENSE.txt +48 -0
- data/README.md +9 -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_10_0.md +66 -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.rb +2 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/altsvc.rb +18 -2
- data/lib/httpx/chainable.rb +9 -8
- data/lib/httpx/connection.rb +177 -72
- data/lib/httpx/connection/http1.rb +44 -13
- data/lib/httpx/connection/http2.rb +77 -34
- data/lib/httpx/domain_name.rb +440 -0
- data/lib/httpx/errors.rb +1 -0
- data/lib/httpx/extensions.rb +23 -3
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +11 -4
- data/lib/httpx/io/tcp.rb +16 -5
- data/lib/httpx/io/udp.rb +4 -1
- data/lib/httpx/loggable.rb +6 -6
- data/lib/httpx/options.rb +22 -15
- data/lib/httpx/parser/http1.rb +14 -17
- data/lib/httpx/plugins/compression.rb +49 -64
- data/lib/httpx/plugins/compression/brotli.rb +10 -14
- data/lib/httpx/plugins/compression/deflate.rb +7 -6
- data/lib/httpx/plugins/compression/gzip.rb +45 -17
- data/lib/httpx/plugins/cookies.rb +21 -60
- data/lib/httpx/plugins/cookies/cookie.rb +173 -0
- data/lib/httpx/plugins/cookies/jar.rb +74 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
- data/lib/httpx/plugins/digest_authentication.rb +2 -0
- data/lib/httpx/plugins/expect.rb +12 -1
- data/lib/httpx/plugins/follow_redirects.rb +20 -2
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/multipart.rb +0 -8
- data/lib/httpx/plugins/persistent.rb +6 -1
- data/lib/httpx/plugins/proxy.rb +16 -12
- data/lib/httpx/plugins/proxy/http.rb +7 -2
- data/lib/httpx/plugins/proxy/socks4.rb +4 -2
- data/lib/httpx/plugins/proxy/socks5.rb +5 -1
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/rate_limiter.rb +51 -0
- data/lib/httpx/plugins/retries.rb +13 -6
- data/lib/httpx/plugins/stream.rb +109 -13
- data/lib/httpx/pool.rb +13 -15
- data/lib/httpx/registry.rb +2 -1
- data/lib/httpx/request.rb +14 -19
- data/lib/httpx/resolver.rb +7 -8
- data/lib/httpx/resolver/https.rb +22 -5
- data/lib/httpx/resolver/native.rb +27 -33
- data/lib/httpx/resolver/options.rb +2 -2
- data/lib/httpx/resolver/resolver_mixin.rb +1 -1
- data/lib/httpx/response.rb +22 -17
- data/lib/httpx/selector.rb +96 -97
- data/lib/httpx/session.rb +32 -24
- data/lib/httpx/timeout.rb +7 -1
- data/lib/httpx/transcoder/chunker.rb +0 -2
- data/lib/httpx/transcoder/form.rb +0 -6
- data/lib/httpx/transcoder/json.rb +0 -4
- data/lib/httpx/utils.rb +45 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/buffer.rbs +24 -0
- data/sig/callbacks.rbs +14 -0
- data/sig/chainable.rbs +37 -0
- data/sig/connection.rbs +2 -0
- data/sig/connection/http2.rbs +4 -0
- data/sig/domain_name.rbs +17 -0
- data/sig/errors.rbs +3 -0
- data/sig/headers.rbs +42 -0
- data/sig/httpx.rbs +14 -0
- data/sig/loggable.rbs +11 -0
- data/sig/missing.rbs +12 -0
- data/sig/options.rbs +118 -0
- data/sig/parser/http1.rbs +50 -0
- data/sig/plugins/authentication.rbs +11 -0
- data/sig/plugins/basic_authentication.rbs +13 -0
- data/sig/plugins/compression.rbs +55 -0
- data/sig/plugins/compression/brotli.rbs +21 -0
- data/sig/plugins/compression/deflate.rbs +17 -0
- data/sig/plugins/compression/gzip.rbs +29 -0
- data/sig/plugins/cookies.rbs +26 -0
- data/sig/plugins/cookies/cookie.rbs +50 -0
- data/sig/plugins/cookies/jar.rbs +27 -0
- data/sig/plugins/digest_authentication.rbs +33 -0
- data/sig/plugins/expect.rbs +19 -0
- data/sig/plugins/follow_redirects.rbs +37 -0
- data/sig/plugins/h2c.rbs +26 -0
- data/sig/plugins/multipart.rbs +19 -0
- data/sig/plugins/persistent.rbs +17 -0
- data/sig/plugins/proxy.rbs +47 -0
- data/sig/plugins/proxy/http.rbs +14 -0
- data/sig/plugins/proxy/socks4.rbs +33 -0
- data/sig/plugins/proxy/socks5.rbs +36 -0
- data/sig/plugins/proxy/ssh.rbs +18 -0
- data/sig/plugins/push_promise.rbs +22 -0
- data/sig/plugins/rate_limiter.rbs +11 -0
- data/sig/plugins/retries.rbs +48 -0
- data/sig/plugins/stream.rbs +39 -0
- data/sig/pool.rbs +2 -0
- data/sig/registry.rbs +9 -0
- data/sig/request.rbs +61 -0
- data/sig/response.rbs +87 -0
- data/sig/session.rbs +49 -0
- data/sig/test.rbs +9 -0
- data/sig/timeout.rbs +29 -0
- data/sig/transcoder.rbs +16 -0
- data/sig/transcoder/body.rbs +18 -0
- data/sig/transcoder/chunker.rbs +32 -0
- data/sig/transcoder/form.rbs +16 -0
- data/sig/transcoder/json.rbs +14 -0
- metadata +120 -21
data/lib/httpx/resolver/https.rb
CHANGED
|
@@ -25,7 +25,7 @@ module HTTPX
|
|
|
25
25
|
|
|
26
26
|
def_delegator :@connections, :empty?
|
|
27
27
|
|
|
28
|
-
def_delegators :@resolver_connection, :to_io, :call, :
|
|
28
|
+
def_delegators :@resolver_connection, :connecting?, :to_io, :call, :close
|
|
29
29
|
|
|
30
30
|
def initialize(options)
|
|
31
31
|
@options = Options.new(options)
|
|
@@ -62,8 +62,20 @@ module HTTPX
|
|
|
62
62
|
resolver_connection.closed?
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def interests
|
|
66
|
+
return if @queries.empty?
|
|
67
|
+
|
|
68
|
+
resolver_connection.__send__(__method__)
|
|
69
|
+
end
|
|
70
|
+
|
|
65
71
|
private
|
|
66
72
|
|
|
73
|
+
def connect
|
|
74
|
+
return if @queries.empty?
|
|
75
|
+
|
|
76
|
+
resolver_connection.__send__(__method__)
|
|
77
|
+
end
|
|
78
|
+
|
|
67
79
|
def pool
|
|
68
80
|
Thread.current[:httpx_connection_pool] ||= Pool.new
|
|
69
81
|
end
|
|
@@ -82,9 +94,14 @@ module HTTPX
|
|
|
82
94
|
def resolve(connection = @connections.first, hostname = nil)
|
|
83
95
|
return if @building_connection
|
|
84
96
|
|
|
85
|
-
hostname
|
|
97
|
+
hostname ||= @queries.key(connection)
|
|
98
|
+
|
|
99
|
+
if hostname.nil?
|
|
100
|
+
hostname = connection.origin.host
|
|
101
|
+
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
|
102
|
+
end
|
|
86
103
|
type = @_record_types[hostname].first
|
|
87
|
-
log
|
|
104
|
+
log { "resolver: query #{type} for #{hostname}" }
|
|
88
105
|
begin
|
|
89
106
|
request = build_request(hostname, type)
|
|
90
107
|
@requests[request] = connection
|
|
@@ -111,7 +128,7 @@ module HTTPX
|
|
|
111
128
|
end
|
|
112
129
|
|
|
113
130
|
def on_promise(_, stream)
|
|
114
|
-
log(level: 2
|
|
131
|
+
log(level: 2) { "#{stream.id}: refusing stream!" }
|
|
115
132
|
stream.refuse
|
|
116
133
|
end
|
|
117
134
|
|
|
@@ -194,7 +211,7 @@ module HTTPX
|
|
|
194
211
|
case response.headers["content-type"]
|
|
195
212
|
when "application/dns-json",
|
|
196
213
|
"application/json",
|
|
197
|
-
%r{^application
|
|
214
|
+
%r{^application/x-javascript} # because google...
|
|
198
215
|
payload = JSON.parse(response.to_s)
|
|
199
216
|
payload["Answer"]
|
|
200
217
|
when "application/dns-udpwireformat",
|
|
@@ -7,6 +7,7 @@ module HTTPX
|
|
|
7
7
|
class Resolver::Native
|
|
8
8
|
extend Forwardable
|
|
9
9
|
include Resolver::ResolverMixin
|
|
10
|
+
using URIExtensions
|
|
10
11
|
|
|
11
12
|
RESOLVE_TIMEOUT = 5
|
|
12
13
|
RECORD_TYPES = {
|
|
@@ -73,14 +74,6 @@ module HTTPX
|
|
|
73
74
|
end
|
|
74
75
|
|
|
75
76
|
def to_io
|
|
76
|
-
case @state
|
|
77
|
-
when :idle
|
|
78
|
-
transition(:open)
|
|
79
|
-
when :closed
|
|
80
|
-
transition(:idle)
|
|
81
|
-
transition(:open)
|
|
82
|
-
end
|
|
83
|
-
resolve if @queries.empty?
|
|
84
77
|
@io.to_io
|
|
85
78
|
end
|
|
86
79
|
|
|
@@ -93,11 +86,7 @@ module HTTPX
|
|
|
93
86
|
rescue Errno::EHOSTUNREACH => e
|
|
94
87
|
@ns_index += 1
|
|
95
88
|
if @ns_index < @nameserver.size
|
|
96
|
-
log
|
|
97
|
-
# :nocov:
|
|
98
|
-
"failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})"
|
|
99
|
-
# :nocov:
|
|
100
|
-
end
|
|
89
|
+
log { "resolver: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" }
|
|
101
90
|
transition(:idle)
|
|
102
91
|
else
|
|
103
92
|
handle_error(e)
|
|
@@ -107,6 +96,14 @@ module HTTPX
|
|
|
107
96
|
end
|
|
108
97
|
|
|
109
98
|
def interests
|
|
99
|
+
case @state
|
|
100
|
+
when :idle
|
|
101
|
+
transition(:open)
|
|
102
|
+
when :closed
|
|
103
|
+
transition(:idle)
|
|
104
|
+
transition(:open)
|
|
105
|
+
end
|
|
106
|
+
|
|
110
107
|
!@write_buffer.empty? || @queries.empty? ? :w : :r
|
|
111
108
|
end
|
|
112
109
|
|
|
@@ -160,11 +157,7 @@ module HTTPX
|
|
|
160
157
|
raise NativeResolveError.new(connection, host)
|
|
161
158
|
else
|
|
162
159
|
connections << connection
|
|
163
|
-
log
|
|
164
|
-
# :nocov:
|
|
165
|
-
"timeout after #{prev_timeout}s, retry(#{timeouts.first}) #{host}..."
|
|
166
|
-
# :nocov:
|
|
167
|
-
end
|
|
160
|
+
log { "resolver: timeout after #{prev_timeout}s, retry(#{timeouts.first}) #{host}..." }
|
|
168
161
|
end
|
|
169
162
|
end
|
|
170
163
|
@queries = queries
|
|
@@ -174,14 +167,10 @@ module HTTPX
|
|
|
174
167
|
def dread(wsize = @resolver_options.packet_size)
|
|
175
168
|
loop do
|
|
176
169
|
siz = @io.read(wsize, @read_buffer)
|
|
177
|
-
unless siz
|
|
178
|
-
emit(:close)
|
|
179
|
-
return
|
|
180
|
-
end
|
|
181
|
-
return if siz.zero?
|
|
170
|
+
return unless siz && siz.positive?
|
|
182
171
|
|
|
183
|
-
log(label: "resolver: ") { "READ: #{siz} bytes..." }
|
|
184
172
|
parse(@read_buffer)
|
|
173
|
+
return if @state == :closed
|
|
185
174
|
end
|
|
186
175
|
end
|
|
187
176
|
|
|
@@ -190,12 +179,9 @@ module HTTPX
|
|
|
190
179
|
return if @write_buffer.empty?
|
|
191
180
|
|
|
192
181
|
siz = @io.write(@write_buffer)
|
|
193
|
-
unless siz
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
end
|
|
197
|
-
log(label: "resolver: ") { "WRITE: #{siz} bytes..." }
|
|
198
|
-
return if siz.zero?
|
|
182
|
+
return unless siz && siz.positive?
|
|
183
|
+
|
|
184
|
+
return if @state == :closed
|
|
199
185
|
end
|
|
200
186
|
end
|
|
201
187
|
|
|
@@ -250,10 +236,15 @@ module HTTPX
|
|
|
250
236
|
raise Error, "no URI to resolve" unless connection
|
|
251
237
|
return unless @write_buffer.empty?
|
|
252
238
|
|
|
253
|
-
hostname
|
|
239
|
+
hostname ||= @queries.key(connection)
|
|
240
|
+
|
|
241
|
+
if hostname.nil?
|
|
242
|
+
hostname = connection.origin.host
|
|
243
|
+
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
|
244
|
+
end
|
|
254
245
|
@queries[hostname] = connection
|
|
255
246
|
type = @_record_types[hostname].first
|
|
256
|
-
log
|
|
247
|
+
log { "resolver: query #{type} for #{hostname}" }
|
|
257
248
|
begin
|
|
258
249
|
@write_buffer << Resolver.encode_dns_query(hostname, type: RECORD_TYPES[type])
|
|
259
250
|
rescue Resolv::DNS::EncodeError => e
|
|
@@ -269,7 +260,7 @@ module HTTPX
|
|
|
269
260
|
uri = URI::Generic.build(scheme: "udp", port: port)
|
|
270
261
|
uri.hostname = ip
|
|
271
262
|
type = IO.registry(uri.scheme)
|
|
272
|
-
log
|
|
263
|
+
log { "resolver: server: #{uri}..." }
|
|
273
264
|
@io = type.new(uri, [IPAddr.new(ip)], @options)
|
|
274
265
|
end
|
|
275
266
|
|
|
@@ -285,8 +276,11 @@ module HTTPX
|
|
|
285
276
|
return unless @state == :idle
|
|
286
277
|
|
|
287
278
|
build_socket
|
|
279
|
+
|
|
288
280
|
@io.connect
|
|
289
281
|
return unless @io.connected?
|
|
282
|
+
|
|
283
|
+
resolve if @queries.empty?
|
|
290
284
|
when :closed
|
|
291
285
|
return unless @state == :open
|
|
292
286
|
|
|
@@ -6,7 +6,7 @@ module HTTPX
|
|
|
6
6
|
@options = options
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def method_missing(m,
|
|
9
|
+
def method_missing(m, *, &block)
|
|
10
10
|
if @options.key?(m)
|
|
11
11
|
@options[m]
|
|
12
12
|
else
|
|
@@ -14,7 +14,7 @@ module HTTPX
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def respond_to_missing?(m)
|
|
17
|
+
def respond_to_missing?(m, *)
|
|
18
18
|
@options.key?(m) || super
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -30,7 +30,7 @@ module HTTPX
|
|
|
30
30
|
addresses.map! do |address|
|
|
31
31
|
address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
|
|
32
32
|
end
|
|
33
|
-
log
|
|
33
|
+
log { "resolver: answer #{connection.origin.host}: #{addresses.inspect}" }
|
|
34
34
|
connection.addresses = addresses
|
|
35
35
|
catch(:coalesced) { emit(:resolve, connection) }
|
|
36
36
|
end
|
data/lib/httpx/response.rb
CHANGED
|
@@ -58,7 +58,7 @@ module HTTPX
|
|
|
58
58
|
"HTTP/#{version} " \
|
|
59
59
|
"@status=#{@status} " \
|
|
60
60
|
"@headers=#{@headers} " \
|
|
61
|
-
"@body=#{@body}>"
|
|
61
|
+
"@body=#{@body.bytesize}>"
|
|
62
62
|
end
|
|
63
63
|
# :nocov:
|
|
64
64
|
|
|
@@ -75,11 +75,11 @@ module HTTPX
|
|
|
75
75
|
@status == 204 ||
|
|
76
76
|
@status == 205 ||
|
|
77
77
|
@status == 304 || begin
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
content_length = @headers["content-length"]
|
|
79
|
+
return false if content_length.nil?
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
content_length == "0"
|
|
82
|
+
end
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
class Body
|
|
@@ -95,6 +95,8 @@ module HTTPX
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def write(chunk)
|
|
98
|
+
return if @state == :closed
|
|
99
|
+
|
|
98
100
|
@length += chunk.bytesize
|
|
99
101
|
transition
|
|
100
102
|
@buffer.write(chunk)
|
|
@@ -116,7 +118,7 @@ module HTTPX
|
|
|
116
118
|
return enum_for(__method__) unless block_given?
|
|
117
119
|
|
|
118
120
|
begin
|
|
119
|
-
|
|
121
|
+
if @buffer
|
|
120
122
|
rewind
|
|
121
123
|
while (chunk = @buffer.read(@window_size))
|
|
122
124
|
yield(chunk.force_encoding(@encoding))
|
|
@@ -150,23 +152,24 @@ module HTTPX
|
|
|
150
152
|
def copy_to(dest)
|
|
151
153
|
return unless @buffer
|
|
152
154
|
|
|
155
|
+
rewind
|
|
156
|
+
|
|
153
157
|
if dest.respond_to?(:path) && @buffer.respond_to?(:path)
|
|
154
158
|
FileUtils.mv(@buffer.path, dest.path)
|
|
155
159
|
else
|
|
156
|
-
@buffer.rewind
|
|
157
160
|
::IO.copy_stream(@buffer, dest)
|
|
158
161
|
end
|
|
159
162
|
end
|
|
160
163
|
|
|
161
164
|
# closes/cleans the buffer, resets everything
|
|
162
165
|
def close
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
if @buffer
|
|
167
|
+
@buffer.close
|
|
168
|
+
@buffer.unlink if @buffer.respond_to?(:unlink)
|
|
169
|
+
@buffer = nil
|
|
170
|
+
end
|
|
168
171
|
@length = 0
|
|
169
|
-
@state = :
|
|
172
|
+
@state = :closed
|
|
170
173
|
end
|
|
171
174
|
|
|
172
175
|
def ==(other)
|
|
@@ -184,7 +187,7 @@ module HTTPX
|
|
|
184
187
|
private
|
|
185
188
|
|
|
186
189
|
def rewind
|
|
187
|
-
return
|
|
190
|
+
return unless @buffer
|
|
188
191
|
|
|
189
192
|
@buffer.rewind
|
|
190
193
|
end
|
|
@@ -237,13 +240,11 @@ module HTTPX
|
|
|
237
240
|
|
|
238
241
|
private
|
|
239
242
|
|
|
240
|
-
# :nodoc:
|
|
241
243
|
def mime_type(str)
|
|
242
244
|
m = str.to_s[MIME_TYPE_RE, 1]
|
|
243
245
|
m && m.strip.downcase
|
|
244
246
|
end
|
|
245
247
|
|
|
246
|
-
# :nodoc:
|
|
247
248
|
def charset(str)
|
|
248
249
|
m = str.to_s[CHARSET_RE, 1]
|
|
249
250
|
m && m.strip.delete('"')
|
|
@@ -267,13 +268,17 @@ module HTTPX
|
|
|
267
268
|
@error.message
|
|
268
269
|
end
|
|
269
270
|
|
|
271
|
+
def to_s
|
|
272
|
+
@error.backtrace.join("\n")
|
|
273
|
+
end
|
|
274
|
+
|
|
270
275
|
def raise_for_status
|
|
271
276
|
raise @error
|
|
272
277
|
end
|
|
273
278
|
|
|
274
279
|
# rubocop:disable Style/MissingRespondToMissing
|
|
275
280
|
def method_missing(meth, *, &block)
|
|
276
|
-
raise NoMethodError, "undefined response method `#{meth}' for error response" if
|
|
281
|
+
raise NoMethodError, "undefined response method `#{meth}' for error response" if @options.response_class.public_method_defined?(meth)
|
|
277
282
|
|
|
278
283
|
super
|
|
279
284
|
end
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "io/wait"
|
|
4
|
+
|
|
5
|
+
module IOExtensions
|
|
6
|
+
refine IO do
|
|
7
|
+
def wait(timeout = nil, mode = :read)
|
|
8
|
+
case mode
|
|
9
|
+
when :read
|
|
10
|
+
wait_readable(timeout)
|
|
11
|
+
when :write
|
|
12
|
+
wait_writable(timeout)
|
|
13
|
+
when :read_write
|
|
14
|
+
r, w = IO.select([self], [self], nil, timeout)
|
|
15
|
+
|
|
16
|
+
return unless r || w
|
|
17
|
+
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
3
24
|
class HTTPX::Selector
|
|
4
25
|
READABLE = %i[rw r].freeze
|
|
5
26
|
WRITABLE = %i[rw w].freeze
|
|
@@ -7,129 +28,107 @@ class HTTPX::Selector
|
|
|
7
28
|
private_constant :READABLE
|
|
8
29
|
private_constant :WRITABLE
|
|
9
30
|
|
|
10
|
-
|
|
11
|
-
# I/O monitor
|
|
12
|
-
#
|
|
13
|
-
class Monitor
|
|
14
|
-
attr_accessor :io, :interests, :readiness
|
|
15
|
-
|
|
16
|
-
def initialize(io, interests, reactor)
|
|
17
|
-
@io = io
|
|
18
|
-
@interests = interests
|
|
19
|
-
@reactor = reactor
|
|
20
|
-
@closed = false
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def readable?
|
|
24
|
-
READABLE.include?(@interests)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def writable?
|
|
28
|
-
WRITABLE.include?(@interests)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# closes +@io+, deregisters from reactor (unless +deregister+ is false)
|
|
32
|
-
def close(deregister = true)
|
|
33
|
-
return if @closed
|
|
34
|
-
|
|
35
|
-
@closed = true
|
|
36
|
-
@reactor.deregister(@io) if deregister
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def closed?
|
|
40
|
-
@closed
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# :nocov:
|
|
44
|
-
def to_s
|
|
45
|
-
"#<#{self.class}: #{@io}(closed:#{@closed}) #{@interests} #{object_id.to_s(16)}>"
|
|
46
|
-
end
|
|
47
|
-
# :nocov:
|
|
48
|
-
end
|
|
31
|
+
using IOExtensions unless IO.method_defined?(:wait) && IO.instance_method(:wait).arity == 2
|
|
49
32
|
|
|
50
33
|
def initialize
|
|
51
|
-
@selectables =
|
|
52
|
-
@__r__, @__w__ = IO.pipe
|
|
53
|
-
@closed = false
|
|
34
|
+
@selectables = []
|
|
54
35
|
end
|
|
55
36
|
|
|
56
37
|
# deregisters +io+ from selectables.
|
|
57
38
|
def deregister(io)
|
|
58
|
-
|
|
59
|
-
monitor.close(false) if monitor
|
|
39
|
+
@selectables.delete(io)
|
|
60
40
|
end
|
|
61
41
|
|
|
62
|
-
# register +io
|
|
63
|
-
def register(io
|
|
64
|
-
|
|
65
|
-
if monitor
|
|
66
|
-
monitor.interests = interests
|
|
67
|
-
else
|
|
68
|
-
monitor = Monitor.new(io, interests, self)
|
|
69
|
-
@selectables[io] = monitor
|
|
70
|
-
end
|
|
71
|
-
monitor
|
|
72
|
-
end
|
|
42
|
+
# register +io+.
|
|
43
|
+
def register(io)
|
|
44
|
+
return if @selectables.include?(io)
|
|
73
45
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
#
|
|
77
|
-
def select(interval)
|
|
78
|
-
begin
|
|
79
|
-
r = [@__r__]
|
|
80
|
-
w = []
|
|
46
|
+
@selectables << io
|
|
47
|
+
end
|
|
81
48
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
READ_INTERESTS = %i[r rw].freeze
|
|
52
|
+
WRITE_INTERESTS = %i[w rw].freeze
|
|
53
|
+
|
|
54
|
+
def select_many(interval, &block)
|
|
55
|
+
selectables, r, w = nil
|
|
56
|
+
|
|
57
|
+
# first, we group IOs based on interest type. On call to #interests however,
|
|
58
|
+
# things might already happen, and new IOs might be registered, so we might
|
|
59
|
+
# have to start all over again. We do this until we group all selectables
|
|
60
|
+
loop do
|
|
61
|
+
begin
|
|
62
|
+
r = nil
|
|
63
|
+
w = nil
|
|
64
|
+
|
|
65
|
+
selectables = @selectables
|
|
66
|
+
@selectables = []
|
|
67
|
+
|
|
68
|
+
selectables.each do |io|
|
|
69
|
+
interests = io.interests
|
|
70
|
+
|
|
71
|
+
(r ||= []) << io if READ_INTERESTS.include?(interests)
|
|
72
|
+
(w ||= []) << io if WRITE_INTERESTS.include?(interests)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if @selectables.empty?
|
|
76
|
+
@selectables = selectables
|
|
77
|
+
break
|
|
78
|
+
else
|
|
79
|
+
@selectables = [*selectables, @selectables]
|
|
80
|
+
end
|
|
81
|
+
rescue StandardError
|
|
82
|
+
@selectables = selectables if selectables
|
|
83
|
+
raise
|
|
86
84
|
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# TODO: what to do if there are no selectables?
|
|
87
88
|
|
|
89
|
+
begin
|
|
88
90
|
readers, writers = IO.select(r, w, nil, interval)
|
|
89
91
|
|
|
90
92
|
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil?
|
|
91
93
|
rescue IOError, SystemCallError
|
|
92
|
-
@selectables.reject!
|
|
94
|
+
@selectables.reject!(&:closed?)
|
|
93
95
|
retry
|
|
94
96
|
end
|
|
95
97
|
|
|
96
98
|
readers.each do |io|
|
|
97
|
-
|
|
98
|
-
# clean up wakeups
|
|
99
|
-
@__r__.read(@__r__.stat.size)
|
|
100
|
-
else
|
|
101
|
-
monitor = io.closed? ? @selectables.delete(io) : @selectables[io]
|
|
102
|
-
next unless monitor
|
|
103
|
-
|
|
104
|
-
monitor.readiness = writers.delete(io) ? :rw : :r
|
|
105
|
-
yield monitor
|
|
106
|
-
end
|
|
107
|
-
end if readers
|
|
99
|
+
yield io
|
|
108
100
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
101
|
+
# so that we don't yield 2 times
|
|
102
|
+
writers.delete(io)
|
|
103
|
+
end if readers
|
|
112
104
|
|
|
113
|
-
|
|
114
|
-
monitor.readiness = :w
|
|
115
|
-
yield monitor
|
|
116
|
-
end if writers
|
|
105
|
+
writers.each(&block) if writers
|
|
117
106
|
end
|
|
118
107
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
def select_one(interval)
|
|
109
|
+
io = @selectables.first
|
|
110
|
+
|
|
111
|
+
interests = io.interests
|
|
112
|
+
|
|
113
|
+
result = case interests
|
|
114
|
+
when :r then io.to_io.wait_readable(interval)
|
|
115
|
+
when :w then io.to_io.wait_writable(interval)
|
|
116
|
+
when :rw then io.to_io.wait(interval, :read_write)
|
|
117
|
+
when nil then return
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result
|
|
123
121
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
ensure
|
|
128
|
-
@closed = true
|
|
122
|
+
yield io
|
|
123
|
+
rescue IOError, SystemCallError
|
|
124
|
+
@selectables.reject!(&:closed?)
|
|
129
125
|
end
|
|
130
126
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
def select(interval, &block)
|
|
128
|
+
return select_one(interval, &block) if @selectables.size == 1
|
|
129
|
+
|
|
130
|
+
select_many(interval, &block)
|
|
134
131
|
end
|
|
132
|
+
|
|
133
|
+
public :select
|
|
135
134
|
end
|