httpx 0.11.2 → 0.13.2

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/0_11_1.md +5 -1
  4. data/doc/release_notes/0_11_3.md +5 -0
  5. data/doc/release_notes/0_12_0.md +55 -0
  6. data/doc/release_notes/0_13_0.md +58 -0
  7. data/doc/release_notes/0_13_1.md +5 -0
  8. data/doc/release_notes/0_13_2.md +9 -0
  9. data/lib/httpx.rb +2 -1
  10. data/lib/httpx/adapters/faraday.rb +4 -6
  11. data/lib/httpx/altsvc.rb +1 -0
  12. data/lib/httpx/chainable.rb +2 -2
  13. data/lib/httpx/connection.rb +80 -28
  14. data/lib/httpx/connection/http1.rb +19 -6
  15. data/lib/httpx/connection/http2.rb +32 -25
  16. data/lib/httpx/io.rb +16 -3
  17. data/lib/httpx/io/ssl.rb +35 -24
  18. data/lib/httpx/io/tcp.rb +50 -28
  19. data/lib/httpx/io/tls.rb +218 -0
  20. data/lib/httpx/io/tls/box.rb +365 -0
  21. data/lib/httpx/io/tls/context.rb +199 -0
  22. data/lib/httpx/io/tls/ffi.rb +390 -0
  23. data/lib/httpx/io/udp.rb +31 -7
  24. data/lib/httpx/io/unix.rb +27 -12
  25. data/lib/httpx/options.rb +11 -23
  26. data/lib/httpx/parser/http1.rb +4 -4
  27. data/lib/httpx/plugins/aws_sdk_authentication.rb +81 -0
  28. data/lib/httpx/plugins/aws_sigv4.rb +219 -0
  29. data/lib/httpx/plugins/compression.rb +20 -8
  30. data/lib/httpx/plugins/compression/brotli.rb +8 -6
  31. data/lib/httpx/plugins/compression/deflate.rb +4 -7
  32. data/lib/httpx/plugins/compression/gzip.rb +2 -2
  33. data/lib/httpx/plugins/digest_authentication.rb +1 -1
  34. data/lib/httpx/plugins/follow_redirects.rb +1 -1
  35. data/lib/httpx/plugins/h2c.rb +43 -58
  36. data/lib/httpx/plugins/internal_telemetry.rb +93 -0
  37. data/lib/httpx/plugins/multipart.rb +2 -0
  38. data/lib/httpx/plugins/multipart/encoder.rb +4 -9
  39. data/lib/httpx/plugins/proxy.rb +1 -1
  40. data/lib/httpx/plugins/proxy/http.rb +1 -1
  41. data/lib/httpx/plugins/proxy/socks4.rb +8 -0
  42. data/lib/httpx/plugins/proxy/socks5.rb +8 -0
  43. data/lib/httpx/plugins/push_promise.rb +3 -2
  44. data/lib/httpx/plugins/retries.rb +2 -2
  45. data/lib/httpx/plugins/stream.rb +6 -6
  46. data/lib/httpx/plugins/upgrade.rb +84 -0
  47. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  48. data/lib/httpx/pool.rb +14 -6
  49. data/lib/httpx/registry.rb +1 -7
  50. data/lib/httpx/request.rb +11 -1
  51. data/lib/httpx/resolver/https.rb +3 -11
  52. data/lib/httpx/resolver/native.rb +7 -3
  53. data/lib/httpx/response.rb +14 -7
  54. data/lib/httpx/selector.rb +5 -0
  55. data/lib/httpx/session.rb +25 -2
  56. data/lib/httpx/transcoder/body.rb +3 -5
  57. data/lib/httpx/version.rb +1 -1
  58. data/sig/chainable.rbs +2 -1
  59. data/sig/connection/http1.rbs +3 -2
  60. data/sig/connection/http2.rbs +5 -3
  61. data/sig/options.rbs +7 -20
  62. data/sig/plugins/aws_sdk_authentication.rbs +17 -0
  63. data/sig/plugins/aws_sigv4.rbs +64 -0
  64. data/sig/plugins/compression.rbs +5 -3
  65. data/sig/plugins/compression/brotli.rbs +1 -1
  66. data/sig/plugins/compression/deflate.rbs +1 -1
  67. data/sig/plugins/compression/gzip.rbs +1 -1
  68. data/sig/plugins/cookies.rbs +0 -1
  69. data/sig/plugins/digest_authentication.rbs +0 -1
  70. data/sig/plugins/expect.rbs +0 -2
  71. data/sig/plugins/follow_redirects.rbs +0 -2
  72. data/sig/plugins/h2c.rbs +5 -10
  73. data/sig/plugins/persistent.rbs +0 -1
  74. data/sig/plugins/proxy.rbs +0 -1
  75. data/sig/plugins/push_promise.rbs +1 -1
  76. data/sig/plugins/retries.rbs +0 -4
  77. data/sig/plugins/upgrade.rbs +23 -0
  78. data/sig/response.rbs +3 -1
  79. metadata +24 -2
@@ -16,6 +16,14 @@ module HTTPX
16
16
  Error = Socks4Error
17
17
 
18
18
  module ConnectionMethods
19
+ def interests
20
+ if @state == :connecting
21
+ return @write_buffer.empty? ? :r : :w
22
+ end
23
+
24
+ super
25
+ end
26
+
19
27
  private
20
28
 
21
29
  def transition(nextstate)
@@ -35,6 +35,14 @@ module HTTPX
35
35
  super || @state == :authenticating || @state == :negotiating
36
36
  end
37
37
 
38
+ def interests
39
+ if @state == :connecting || @state == :authenticating || @state == :negotiating
40
+ return @write_buffer.empty? ? :r : :w
41
+ end
42
+
43
+ super
44
+ end
45
+
38
46
  private
39
47
 
40
48
  def transition(nextstate)
@@ -43,7 +43,7 @@ module HTTPX
43
43
  end
44
44
 
45
45
  def __on_promise_request(parser, stream, h)
46
- log(level: 1) do
46
+ log(level: 1, color: :yellow) do
47
47
  # :nocov:
48
48
  h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
49
49
  # :nocov:
@@ -57,6 +57,8 @@ module HTTPX
57
57
  request.merge_headers(headers)
58
58
  promise_headers[stream] = request
59
59
  parser.pending.delete(request)
60
+ parser.streams[request] = stream
61
+ request.transition(:done)
60
62
  else
61
63
  stream.refuse
62
64
  end
@@ -67,7 +69,6 @@ module HTTPX
67
69
  return unless request
68
70
 
69
71
  parser.__send__(:on_stream_headers, stream, request, h)
70
- request.transition(:done)
71
72
  response = request.response
72
73
  response.mark_as_pushed!
73
74
  stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
@@ -17,7 +17,7 @@ module HTTPX
17
17
  Errno::ECONNRESET,
18
18
  Errno::ECONNABORTED,
19
19
  Errno::EPIPE,
20
- (OpenSSL::SSL::SSLError if defined?(OpenSSL)),
20
+ (TLSError if defined?(TLSError)),
21
21
  TimeoutError,
22
22
  Parser::Error,
23
23
  Errno::EINVAL,
@@ -55,7 +55,7 @@ module HTTPX
55
55
 
56
56
  module InstanceMethods
57
57
  def max_retries(n)
58
- branch(default_options.with_max_retries(n.to_i))
58
+ with(max_retries: n.to_i)
59
59
  end
60
60
 
61
61
  private
@@ -5,6 +5,8 @@ module HTTPX
5
5
  #
6
6
  # This plugin adds support for stream response (text/event-stream).
7
7
  #
8
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Stream
9
+ #
8
10
  module Stream
9
11
  module InstanceMethods
10
12
  private
@@ -31,7 +33,7 @@ module HTTPX
31
33
  end
32
34
 
33
35
  module ResponseBodyMethods
34
- def initialize(*, **)
36
+ def initialize(*)
35
37
  super
36
38
  @stream = @response.stream
37
39
  end
@@ -119,11 +121,9 @@ module HTTPX
119
121
  end
120
122
 
121
123
  def method_missing(meth, *args, &block)
122
- if @options.response_class.public_method_defined?(meth)
123
- response.__send__(meth, *args, &block)
124
- else
125
- super
126
- end
124
+ return super unless @options.response_class.public_method_defined?(meth)
125
+
126
+ response.__send__(meth, *args, &block)
127
127
  end
128
128
  end
129
129
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin helps negotiating a new protocol from an HTTP/1.1 connection, via the
7
+ # Upgrade header.
8
+ #
9
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Upgrade
10
+ #
11
+ module Upgrade
12
+ class << self
13
+ def configure(klass)
14
+ klass.plugin(:"upgrade/h2")
15
+ end
16
+
17
+ def extra_options(options)
18
+ upgrade_handlers = Module.new do
19
+ extend Registry
20
+ end
21
+
22
+ Class.new(options.class) do
23
+ def_option(:upgrade_handlers) do |encs|
24
+ raise Error, ":upgrade_handlers must be a registry" unless encs.respond_to?(:registry)
25
+
26
+ encs
27
+ end
28
+ end.new(options).merge(upgrade_handlers: upgrade_handlers)
29
+ end
30
+ end
31
+
32
+ module InstanceMethods
33
+ def fetch_response(request, connections, options)
34
+ response = super
35
+
36
+ if response
37
+ return response unless response.respond_to?(:headers) && response.headers.key?("upgrade")
38
+
39
+ upgrade_protocol = response.headers["upgrade"].split(/ *, */).first
40
+
41
+ return response unless upgrade_protocol && options.upgrade_handlers.registry.key?(upgrade_protocol)
42
+
43
+ protocol_handler = options.upgrade_handlers.registry(upgrade_protocol)
44
+
45
+ return response unless protocol_handler
46
+
47
+ log { "upgrading to #{upgrade_protocol}..." }
48
+ connection = find_connection(request, connections, options)
49
+ connections << connection unless connections.include?(connection)
50
+
51
+ # do not upgrade already upgraded connections
52
+ return if connection.upgrade_protocol == upgrade_protocol
53
+
54
+ protocol_handler.call(connection, request, response)
55
+
56
+ # keep in the loop if the server is switching, unless
57
+ # the connection has been hijacked, in which case you want
58
+ # to terminante immediately
59
+ return if response.status == 101 && !connection.hijacked
60
+ end
61
+
62
+ response
63
+ end
64
+
65
+ def close(*args)
66
+ return super if args.empty?
67
+
68
+ connections, = args
69
+
70
+ pool.close(connections.reject(&:hijacked))
71
+ end
72
+ end
73
+
74
+ module ConnectionMethods
75
+ attr_reader :upgrade_protocol, :hijacked
76
+
77
+ def hijack_io
78
+ @hijacked = true
79
+ end
80
+ end
81
+ end
82
+ register_plugin(:upgrade, Upgrade)
83
+ end
84
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds support for upgrading an HTTP/1.1 connection to HTTP/2
7
+ # via an Upgrade: h2 response declaration
8
+ #
9
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Upgrade#h2
10
+ #
11
+ module H2
12
+ class << self
13
+ def configure(klass)
14
+ klass.default_options.upgrade_handlers.register "h2", self
15
+ end
16
+
17
+ def call(connection, _request, _response)
18
+ connection.upgrade_to_h2
19
+ end
20
+ end
21
+
22
+ module ConnectionMethods
23
+ using URIExtensions
24
+
25
+ def upgrade_to_h2
26
+ prev_parser = @parser
27
+
28
+ if prev_parser
29
+ prev_parser.reset
30
+ @inflight -= prev_parser.requests.size
31
+ end
32
+
33
+ @parser = Connection::HTTP2.new(@write_buffer, @options)
34
+ set_parser_callbacks(@parser)
35
+ @upgrade_protocol = :h2
36
+
37
+ # what's happening here:
38
+ # a deviation from the state machine is done to perform the actions when a
39
+ # connection is closed, without transitioning, so the connection is kept in the pool.
40
+ # the state is reset to initial, so that the socket reconnect works out of the box,
41
+ # while the parser is already here.
42
+ purge_after_closed
43
+ transition(:idle)
44
+
45
+ prev_parser.requests.each do |req|
46
+ req.transition(:idle)
47
+ send(req)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ register_plugin(:"upgrade/h2", H2)
53
+ end
54
+ end
data/lib/httpx/pool.rb CHANGED
@@ -64,11 +64,6 @@ module HTTPX
64
64
  connection.on(:open) do
65
65
  @connected_connections += 1
66
66
  end
67
- connection.on(:unreachable) do
68
- resolver = find_resolver_for(connection)
69
- resolver.uncache(connection) if resolver
70
- resolve_connection(connection)
71
- end
72
67
  end
73
68
 
74
69
  # opens a connection to the IP reachable through +uri+.
@@ -85,6 +80,20 @@ module HTTPX
85
80
 
86
81
  def resolve_connection(connection)
87
82
  @connections << connection unless @connections.include?(connection)
83
+
84
+ if connection.addresses || connection.state == :open
85
+ #
86
+ # there are two cases in which we want to activate initialization of
87
+ # connection immediately:
88
+ #
89
+ # 1. when the connection already has addresses, i.e. it doesn't need to
90
+ # resolve a name (not the same as name being an IP, yet)
91
+ # 2. when the connection is initialized with an external already open IO.
92
+ #
93
+ on_resolver_connection(connection)
94
+ return
95
+ end
96
+
88
97
  resolver = find_resolver_for(connection)
89
98
  resolver << connection
90
99
  return if resolver.empty?
@@ -135,7 +144,6 @@ module HTTPX
135
144
  connection.on(:close) do
136
145
  unregister_connection(connection)
137
146
  end
138
- return if connection.state == :open
139
147
  end
140
148
 
141
149
  def unregister_connection(connection)
@@ -62,13 +62,7 @@ module HTTPX
62
62
  handler = @registry.fetch(tag)
63
63
  raise(Error, "#{tag} is not registered in #{self}") unless handler
64
64
 
65
- case handler
66
- when Symbol, String
67
- obj = const_get(handler)
68
- @registry[tag] = obj
69
- else
70
- handler
71
- end
65
+ handler
72
66
  end
73
67
 
74
68
  # @param [Object] tag the identifier for the handler in the registry
data/lib/httpx/request.rb CHANGED
@@ -217,6 +217,16 @@ module HTTPX
217
217
  "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
218
218
  end
219
219
  # :nocov:
220
+
221
+ def respond_to_missing?(meth, *args)
222
+ @body.respond_to?(meth, *args) || super
223
+ end
224
+
225
+ def method_missing(meth, *args, &block)
226
+ return super unless @body.respond_to?(meth)
227
+
228
+ @body.__send__(meth, *args, &block)
229
+ end
220
230
  end
221
231
 
222
232
  def transition(nextstate)
@@ -247,7 +257,7 @@ module HTTPX
247
257
  return if @state == :expect
248
258
  end
249
259
  @state = nextstate
250
- emit(@state)
260
+ emit(@state, self)
251
261
  nil
252
262
  end
253
263
 
@@ -55,20 +55,12 @@ module HTTPX
55
55
  early_resolve(connection) || resolve(connection)
56
56
  end
57
57
 
58
- def timeout
59
- @connections.map(&:timeout).min
60
- end
61
-
62
58
  def closed?
63
- return true unless @resolver_connection
64
-
65
- resolver_connection.closed?
59
+ true
66
60
  end
67
61
 
68
- def interests
69
- return if @queries.empty?
70
-
71
- resolver_connection.__send__(__method__)
62
+ def empty?
63
+ true
72
64
  end
73
65
 
74
66
  private
@@ -101,7 +101,7 @@ module HTTPX
101
101
  transition(:open)
102
102
  end
103
103
 
104
- !@write_buffer.empty? || @queries.empty? ? :w : :r
104
+ calculate_interests
105
105
  end
106
106
 
107
107
  def <<(connection)
@@ -127,10 +127,14 @@ module HTTPX
127
127
 
128
128
  private
129
129
 
130
+ def calculate_interests
131
+ !@write_buffer.empty? || @queries.empty? ? :w : :r
132
+ end
133
+
130
134
  def consume
131
- dread
135
+ dread if calculate_interests == :r
132
136
  do_retry
133
- dwrite
137
+ dwrite if calculate_interests == :w
134
138
  end
135
139
 
136
140
  def do_retry
@@ -27,8 +27,7 @@ module HTTPX
27
27
  @version = version
28
28
  @status = Integer(status)
29
29
  @headers = @options.headers_class.new(headers)
30
- @body = @options.response_body_class.new(self, threshold_size: @options.body_threshold_size,
31
- window_size: @options.window_size)
30
+ @body = @options.response_body_class.new(self, @options)
32
31
  end
33
32
 
34
33
  def merge_headers(h)
@@ -83,11 +82,12 @@ module HTTPX
83
82
  end
84
83
 
85
84
  class Body
86
- def initialize(response, threshold_size:, window_size: 1 << 14)
85
+ def initialize(response, options)
87
86
  @response = response
88
87
  @headers = response.headers
89
- @threshold_size = threshold_size
90
- @window_size = window_size
88
+ @options = options
89
+ @threshold_size = options.body_threshold_size
90
+ @window_size = options.window_size
91
91
  @encoding = response.content_type.charset || Encoding::BINARY
92
92
  @length = 0
93
93
  @buffer = nil
@@ -268,8 +268,15 @@ module HTTPX
268
268
  @error.message
269
269
  end
270
270
 
271
- def to_s
272
- @error.backtrace.join("\n")
271
+ if Exception.method_defined?(:full_message)
272
+ def to_s
273
+ @error.full_message
274
+ end
275
+ else
276
+ def to_s
277
+ "#{@error.message} (#{@error.class})\n" \
278
+ "#{@error.backtrace.join("\n") if @error.backtrace}"
279
+ end
273
280
  end
274
281
 
275
282
  def raise_for_status
@@ -69,6 +69,11 @@ class HTTPX::Selector
69
69
 
70
70
  if @selectables.empty?
71
71
  @selectables = selectables
72
+
73
+ # do not run event loop if there's nothing to wait on.
74
+ # this might happen if connect failed and connection was unregistered.
75
+ return if (!r || r.empty?) && (!w || w.empty?)
76
+
72
77
  break
73
78
  else
74
79
  @selectables = [*selectables, @selectables]
data/lib/httpx/session.rb CHANGED
@@ -199,7 +199,18 @@ module HTTPX
199
199
  responses << response
200
200
  requests.shift
201
201
 
202
- break if requests.empty? || pool.empty?
202
+ break if requests.empty?
203
+
204
+ next unless pool.empty?
205
+
206
+ # in some cases, the pool of connections might have been drained because there was some
207
+ # handshake error, and the error responses have already been emitted, but there was no
208
+ # opportunity to traverse the requests, hence we're returning only a fraction of the errors
209
+ # we were supposed to. This effectively fetches the existing responses and return them.
210
+ while (request = requests.shift)
211
+ responses << fetch_response(request, connections, request_options)
212
+ end
213
+ break
203
214
  end
204
215
  responses
205
216
  ensure
@@ -269,7 +280,19 @@ module HTTPX
269
280
  end
270
281
  # :nocov:
271
282
  end
283
+ end
284
+
285
+ unless ENV.grep(/https?_proxy$/i).empty?
286
+ proxy_session = plugin(:proxy)
287
+ ::HTTPX.send(:remove_const, :Session)
288
+ ::HTTPX.send(:const_set, :Session, proxy_session.class)
289
+ end
272
290
 
273
- plugin(:proxy) unless ENV.grep(/https?_proxy$/i).empty?
291
+ # :nocov:
292
+ if Session.default_options.debug_level > 2
293
+ proxy_session = plugin(:internal_telemetry)
294
+ ::HTTPX.send(:remove_const, :Session)
295
+ ::HTTPX.send(:const_set, :Session, proxy_session.class)
274
296
  end
297
+ # :nocov:
275
298
  end