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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/httpx.rb +8 -2
  3. data/lib/httpx/adapters/faraday.rb +203 -0
  4. data/lib/httpx/altsvc.rb +4 -0
  5. data/lib/httpx/callbacks.rb +1 -4
  6. data/lib/httpx/chainable.rb +4 -3
  7. data/lib/httpx/connection.rb +326 -104
  8. data/lib/httpx/{channel → connection}/http1.rb +29 -15
  9. data/lib/httpx/{channel → connection}/http2.rb +12 -6
  10. data/lib/httpx/errors.rb +2 -0
  11. data/lib/httpx/headers.rb +4 -1
  12. data/lib/httpx/io/ssl.rb +5 -1
  13. data/lib/httpx/io/tcp.rb +13 -7
  14. data/lib/httpx/io/udp.rb +1 -0
  15. data/lib/httpx/io/unix.rb +1 -0
  16. data/lib/httpx/loggable.rb +34 -9
  17. data/lib/httpx/options.rb +57 -31
  18. data/lib/httpx/parser/http1.rb +8 -0
  19. data/lib/httpx/plugins/authentication.rb +4 -0
  20. data/lib/httpx/plugins/basic_authentication.rb +4 -0
  21. data/lib/httpx/plugins/compression.rb +22 -5
  22. data/lib/httpx/plugins/cookies.rb +89 -36
  23. data/lib/httpx/plugins/digest_authentication.rb +45 -26
  24. data/lib/httpx/plugins/follow_redirects.rb +61 -62
  25. data/lib/httpx/plugins/h2c.rb +78 -39
  26. data/lib/httpx/plugins/multipart.rb +5 -0
  27. data/lib/httpx/plugins/persistent.rb +29 -0
  28. data/lib/httpx/plugins/proxy.rb +125 -78
  29. data/lib/httpx/plugins/proxy/http.rb +31 -27
  30. data/lib/httpx/plugins/proxy/socks4.rb +30 -24
  31. data/lib/httpx/plugins/proxy/socks5.rb +49 -39
  32. data/lib/httpx/plugins/proxy/ssh.rb +81 -0
  33. data/lib/httpx/plugins/push_promise.rb +18 -9
  34. data/lib/httpx/plugins/retries.rb +43 -15
  35. data/lib/httpx/pool.rb +159 -0
  36. data/lib/httpx/registry.rb +2 -0
  37. data/lib/httpx/request.rb +10 -0
  38. data/lib/httpx/resolver.rb +2 -1
  39. data/lib/httpx/resolver/https.rb +62 -56
  40. data/lib/httpx/resolver/native.rb +48 -37
  41. data/lib/httpx/resolver/resolver_mixin.rb +16 -11
  42. data/lib/httpx/resolver/system.rb +11 -7
  43. data/lib/httpx/response.rb +24 -10
  44. data/lib/httpx/selector.rb +32 -39
  45. data/lib/httpx/{client.rb → session.rb} +99 -62
  46. data/lib/httpx/timeout.rb +7 -15
  47. data/lib/httpx/transcoder/body.rb +4 -0
  48. data/lib/httpx/transcoder/chunker.rb +4 -0
  49. data/lib/httpx/version.rb +1 -1
  50. metadata +10 -8
  51. data/lib/httpx/channel.rb +0 -367
@@ -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