httpx 0.3.1 → 0.4.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/lib/httpx.rb +8 -2
- data/lib/httpx/adapters/faraday.rb +203 -0
- data/lib/httpx/altsvc.rb +4 -0
- data/lib/httpx/callbacks.rb +1 -4
- data/lib/httpx/chainable.rb +4 -3
- data/lib/httpx/connection.rb +326 -104
- data/lib/httpx/{channel → connection}/http1.rb +29 -15
- data/lib/httpx/{channel → connection}/http2.rb +12 -6
- data/lib/httpx/errors.rb +2 -0
- data/lib/httpx/headers.rb +4 -1
- data/lib/httpx/io/ssl.rb +5 -1
- data/lib/httpx/io/tcp.rb +13 -7
- data/lib/httpx/io/udp.rb +1 -0
- data/lib/httpx/io/unix.rb +1 -0
- data/lib/httpx/loggable.rb +34 -9
- data/lib/httpx/options.rb +57 -31
- data/lib/httpx/parser/http1.rb +8 -0
- data/lib/httpx/plugins/authentication.rb +4 -0
- data/lib/httpx/plugins/basic_authentication.rb +4 -0
- data/lib/httpx/plugins/compression.rb +22 -5
- data/lib/httpx/plugins/cookies.rb +89 -36
- data/lib/httpx/plugins/digest_authentication.rb +45 -26
- data/lib/httpx/plugins/follow_redirects.rb +61 -62
- data/lib/httpx/plugins/h2c.rb +78 -39
- data/lib/httpx/plugins/multipart.rb +5 -0
- data/lib/httpx/plugins/persistent.rb +29 -0
- data/lib/httpx/plugins/proxy.rb +125 -78
- data/lib/httpx/plugins/proxy/http.rb +31 -27
- data/lib/httpx/plugins/proxy/socks4.rb +30 -24
- data/lib/httpx/plugins/proxy/socks5.rb +49 -39
- data/lib/httpx/plugins/proxy/ssh.rb +81 -0
- data/lib/httpx/plugins/push_promise.rb +18 -9
- data/lib/httpx/plugins/retries.rb +43 -15
- data/lib/httpx/pool.rb +159 -0
- data/lib/httpx/registry.rb +2 -0
- data/lib/httpx/request.rb +10 -0
- data/lib/httpx/resolver.rb +2 -1
- data/lib/httpx/resolver/https.rb +62 -56
- data/lib/httpx/resolver/native.rb +48 -37
- data/lib/httpx/resolver/resolver_mixin.rb +16 -11
- data/lib/httpx/resolver/system.rb +11 -7
- data/lib/httpx/response.rb +24 -10
- data/lib/httpx/selector.rb +32 -39
- data/lib/httpx/{client.rb → session.rb} +99 -62
- data/lib/httpx/timeout.rb +7 -15
- data/lib/httpx/transcoder/body.rb +4 -0
- data/lib/httpx/transcoder/chunker.rb +4 -0
- data/lib/httpx/version.rb +1 -1
- metadata +10 -8
- data/lib/httpx/channel.rb +0 -367
data/lib/httpx/channel.rb
DELETED
@@ -1,367 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "resolv"
|
4
|
-
require "forwardable"
|
5
|
-
require "httpx/io"
|
6
|
-
require "httpx/buffer"
|
7
|
-
|
8
|
-
module HTTPX
|
9
|
-
# The Channel entity can be watched for IO events.
|
10
|
-
#
|
11
|
-
# It contains the +io+ object to read/write from, and knows what to do when it can.
|
12
|
-
#
|
13
|
-
# It defers connecting until absolutely necessary. Connection should be triggered from
|
14
|
-
# the IO selector (until then, any request will be queued).
|
15
|
-
#
|
16
|
-
# A channel boots up its parser after connection is established. All pending requests
|
17
|
-
# will be redirected there after connection.
|
18
|
-
#
|
19
|
-
# A channel can be prevented from closing by the parser, that is, if there are pending
|
20
|
-
# requests. This will signal that the channel was prematurely closed, due to a possible
|
21
|
-
# number of conditions:
|
22
|
-
#
|
23
|
-
# * Remote peer closed the connection ("Connection: close");
|
24
|
-
# * Remote peer doesn't support pipelining;
|
25
|
-
#
|
26
|
-
# A channel may also route requests for a different host for which the +io+ was connected
|
27
|
-
# to, provided that the IP is the same and the port and scheme as well. This will allow to
|
28
|
-
# share the same socket to send HTTP/2 requests to different hosts.
|
29
|
-
# TODO: For this to succeed, the certificates sent by the servers to the client must be
|
30
|
-
# identical (or match both hosts).
|
31
|
-
#
|
32
|
-
class Channel
|
33
|
-
extend Forwardable
|
34
|
-
include Registry
|
35
|
-
include Loggable
|
36
|
-
include Callbacks
|
37
|
-
|
38
|
-
using URIExtensions
|
39
|
-
|
40
|
-
require "httpx/channel/http2"
|
41
|
-
require "httpx/channel/http1"
|
42
|
-
|
43
|
-
BUFFER_SIZE = 1 << 14
|
44
|
-
|
45
|
-
class << self
|
46
|
-
def by(uri, options)
|
47
|
-
type = options.transport || begin
|
48
|
-
case uri.scheme
|
49
|
-
when "http"
|
50
|
-
"tcp"
|
51
|
-
when "https"
|
52
|
-
"ssl"
|
53
|
-
when "h2"
|
54
|
-
options = options.merge(ssl: { alpn_protocols: %(h2) })
|
55
|
-
"ssl"
|
56
|
-
else
|
57
|
-
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
new(type, uri, options)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def_delegator :@io, :closed?
|
65
|
-
|
66
|
-
def_delegator :@write_buffer, :empty?
|
67
|
-
|
68
|
-
attr_reader :uri, :state, :pending
|
69
|
-
|
70
|
-
def initialize(type, uri, options)
|
71
|
-
@type = type
|
72
|
-
@uri = uri
|
73
|
-
@origins = [@uri.origin]
|
74
|
-
@options = Options.new(options)
|
75
|
-
@window_size = @options.window_size
|
76
|
-
@read_buffer = Buffer.new(BUFFER_SIZE)
|
77
|
-
@write_buffer = Buffer.new(BUFFER_SIZE)
|
78
|
-
@pending = []
|
79
|
-
on(:error) { |ex| on_error(ex) }
|
80
|
-
transition(:idle)
|
81
|
-
end
|
82
|
-
|
83
|
-
def addresses=(addrs)
|
84
|
-
@io = IO.registry(@type).new(@uri, addrs, @options)
|
85
|
-
end
|
86
|
-
|
87
|
-
def mergeable?(addresses)
|
88
|
-
return false if @state == :closing || !@io
|
89
|
-
!(@io.addresses & addresses).empty?
|
90
|
-
end
|
91
|
-
|
92
|
-
# coalescable channels need to be mergeable!
|
93
|
-
# but internally, #mergeable? is called before #coalescable?
|
94
|
-
def coalescable?(channel)
|
95
|
-
if @io.protocol == "h2" && @uri.scheme == "https"
|
96
|
-
@io.verify_hostname(channel.uri.host)
|
97
|
-
else
|
98
|
-
@uri.origin == channel.uri.origin
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def merge(channel)
|
103
|
-
@origins += channel.instance_variable_get(:@origins)
|
104
|
-
pending = channel.instance_variable_get(:@pending)
|
105
|
-
pending.each do |req, args|
|
106
|
-
send(req, args)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def unmerge(channel)
|
111
|
-
@origins -= channel.instance_variable_get(:@origins)
|
112
|
-
purge_pending do |request, args|
|
113
|
-
request.uri == channel.uri && begin
|
114
|
-
request.transition(:idle)
|
115
|
-
channel.send(request, *args)
|
116
|
-
true
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def purge_pending
|
122
|
-
[@parser.pending, @pending].each do |pending|
|
123
|
-
pending.reject! do |request, *args|
|
124
|
-
yield(request, args)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def match?(uri)
|
130
|
-
return false if @state == :closing
|
131
|
-
|
132
|
-
@origins.include?(uri.origin) || match_altsvcs?(uri)
|
133
|
-
end
|
134
|
-
|
135
|
-
# checks if this is channel is an alternative service of
|
136
|
-
# +uri+
|
137
|
-
def match_altsvcs?(uri)
|
138
|
-
AltSvc.cached_altsvc(@uri.origin).any? do |altsvc|
|
139
|
-
origin = altsvc["origin"]
|
140
|
-
origin.altsvc_match?(uri.origin)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def connecting?
|
145
|
-
@state == :idle
|
146
|
-
end
|
147
|
-
|
148
|
-
def interests
|
149
|
-
return :w if @state == :idle
|
150
|
-
readable = !@read_buffer.full?
|
151
|
-
writable = !@write_buffer.empty?
|
152
|
-
if readable
|
153
|
-
writable ? :rw : :r
|
154
|
-
else
|
155
|
-
writable ? :w : :r
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def to_io
|
160
|
-
case @state
|
161
|
-
when :idle
|
162
|
-
transition(:open)
|
163
|
-
end
|
164
|
-
@io.to_io
|
165
|
-
end
|
166
|
-
|
167
|
-
def close
|
168
|
-
@parser.close if @parser
|
169
|
-
transition(:closing)
|
170
|
-
end
|
171
|
-
|
172
|
-
def reset
|
173
|
-
transition(:closing)
|
174
|
-
transition(:closed)
|
175
|
-
emit(:close)
|
176
|
-
end
|
177
|
-
|
178
|
-
def send(request, **args)
|
179
|
-
if @error_response
|
180
|
-
emit(:response, request, @error_response)
|
181
|
-
elsif @parser && !@write_buffer.full?
|
182
|
-
request.headers["alt-used"] = @uri.authority if match_altsvcs?(request.uri)
|
183
|
-
parser.send(request, **args)
|
184
|
-
else
|
185
|
-
@pending << [request, args]
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def call
|
190
|
-
@timeout = @timeout_threshold
|
191
|
-
case @state
|
192
|
-
when :closed
|
193
|
-
return
|
194
|
-
when :closing
|
195
|
-
dwrite
|
196
|
-
transition(:closed)
|
197
|
-
emit(:close)
|
198
|
-
when :open
|
199
|
-
consume
|
200
|
-
end
|
201
|
-
nil
|
202
|
-
end
|
203
|
-
|
204
|
-
def upgrade_parser(protocol)
|
205
|
-
@parser.reset if @parser
|
206
|
-
@parser = build_parser(protocol)
|
207
|
-
end
|
208
|
-
|
209
|
-
def handle_timeout_error(e)
|
210
|
-
return emit(:error, e) unless @timeout
|
211
|
-
@timeout -= e.timeout
|
212
|
-
return unless @timeout <= 0
|
213
|
-
if connecting?
|
214
|
-
emit(:error, e.to_connection_error)
|
215
|
-
else
|
216
|
-
emit(:error, e)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
private
|
221
|
-
|
222
|
-
def consume
|
223
|
-
catch(:called) do
|
224
|
-
dread
|
225
|
-
dwrite
|
226
|
-
parser.consume
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def dread(wsize = @window_size)
|
231
|
-
loop do
|
232
|
-
siz = @io.read(wsize, @read_buffer)
|
233
|
-
unless siz
|
234
|
-
ex = EOFError.new("descriptor closed")
|
235
|
-
ex.set_backtrace(caller)
|
236
|
-
on_error(ex)
|
237
|
-
return
|
238
|
-
end
|
239
|
-
return if siz.zero?
|
240
|
-
log { "READ: #{siz} bytes..." }
|
241
|
-
parser << @read_buffer.to_s
|
242
|
-
return if @state == :closing || @state == :closed
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def dwrite
|
247
|
-
loop do
|
248
|
-
return if @write_buffer.empty?
|
249
|
-
siz = @io.write(@write_buffer)
|
250
|
-
unless siz
|
251
|
-
ex = EOFError.new("descriptor closed")
|
252
|
-
ex.set_backtrace(caller)
|
253
|
-
on_error(ex)
|
254
|
-
return
|
255
|
-
end
|
256
|
-
log { "WRITE: #{siz} bytes..." }
|
257
|
-
return if siz.zero?
|
258
|
-
return if @state == :closing || @state == :closed
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
def send_pending
|
263
|
-
while !@write_buffer.full? && (req_args = @pending.shift)
|
264
|
-
request, args = req_args
|
265
|
-
parser.send(request, **args)
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
def parser
|
270
|
-
@parser ||= build_parser
|
271
|
-
end
|
272
|
-
|
273
|
-
def build_parser(protocol = @io.protocol)
|
274
|
-
parser = registry(protocol).new(@write_buffer, @options)
|
275
|
-
parser.on(:response) do |*args|
|
276
|
-
AltSvc.emit(*args) do |alt_origin, origin, alt_params|
|
277
|
-
emit(:altsvc, alt_origin, origin, alt_params)
|
278
|
-
end
|
279
|
-
emit(:response, *args)
|
280
|
-
end
|
281
|
-
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
282
|
-
emit(:altsvc, alt_origin, origin, alt_params)
|
283
|
-
end
|
284
|
-
|
285
|
-
parser.on(:promise) do |*args|
|
286
|
-
emit(:promise, *args)
|
287
|
-
end
|
288
|
-
parser.on(:close) do
|
289
|
-
transition(:closing)
|
290
|
-
end
|
291
|
-
parser.on(:reset) do
|
292
|
-
transition(:closing)
|
293
|
-
unless parser.empty?
|
294
|
-
transition(:closed)
|
295
|
-
emit(:reset)
|
296
|
-
transition(:idle)
|
297
|
-
transition(:open)
|
298
|
-
end
|
299
|
-
end
|
300
|
-
parser.on(:timeout) do |timeout|
|
301
|
-
@timeout = timeout
|
302
|
-
end
|
303
|
-
parser.on(:error) do |request, ex|
|
304
|
-
case ex
|
305
|
-
when MisdirectedRequestError
|
306
|
-
emit(:uncoalesce, request.uri)
|
307
|
-
else
|
308
|
-
response = ErrorResponse.new(ex, @options)
|
309
|
-
emit(:response, request, response)
|
310
|
-
end
|
311
|
-
end
|
312
|
-
parser
|
313
|
-
end
|
314
|
-
|
315
|
-
def transition(nextstate)
|
316
|
-
case nextstate
|
317
|
-
# when :idle
|
318
|
-
when :idle
|
319
|
-
@error_response = nil
|
320
|
-
@timeout_threshold = @options.timeout.connect_timeout
|
321
|
-
@timeout = @timeout_threshold
|
322
|
-
when :open
|
323
|
-
return if @state == :closed
|
324
|
-
@io.connect
|
325
|
-
return unless @io.connected?
|
326
|
-
send_pending
|
327
|
-
@timeout_threshold = @options.timeout.operation_timeout
|
328
|
-
@timeout = @timeout_threshold
|
329
|
-
emit(:open)
|
330
|
-
when :closing
|
331
|
-
return unless @state == :open
|
332
|
-
when :closed
|
333
|
-
return unless @state == :closing
|
334
|
-
return unless @write_buffer.empty?
|
335
|
-
@io.close
|
336
|
-
@read_buffer.clear
|
337
|
-
end
|
338
|
-
@state = nextstate
|
339
|
-
rescue Errno::EHOSTUNREACH
|
340
|
-
# at this point, all addresses from the IO object have failed
|
341
|
-
reset
|
342
|
-
emit(:unreachable)
|
343
|
-
throw(:jump_tick)
|
344
|
-
rescue Errno::ECONNREFUSED,
|
345
|
-
Errno::EADDRNOTAVAIL,
|
346
|
-
Errno::EHOSTUNREACH,
|
347
|
-
OpenSSL::SSL::SSLError => e
|
348
|
-
# connect errors, exit gracefully
|
349
|
-
handle_error(e)
|
350
|
-
@state = :closed
|
351
|
-
emit(:close)
|
352
|
-
end
|
353
|
-
|
354
|
-
def on_error(ex)
|
355
|
-
handle_error(ex)
|
356
|
-
reset
|
357
|
-
end
|
358
|
-
|
359
|
-
def handle_error(e)
|
360
|
-
parser.handle_error(e) if @parser && parser.respond_to?(:handle_error)
|
361
|
-
@error_response = ErrorResponse.new(e, @options)
|
362
|
-
@pending.each do |request, _|
|
363
|
-
emit(:response, request, @error_response)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
end
|
367
|
-
end
|