httpx 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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