httpx 0.11.0 → 0.13.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/0_11_1.md +5 -0
  4. data/doc/release_notes/0_11_2.md +5 -0
  5. data/doc/release_notes/0_11_3.md +5 -0
  6. data/doc/release_notes/0_12_0.md +55 -0
  7. data/doc/release_notes/0_13_0.md +58 -0
  8. data/lib/httpx.rb +2 -1
  9. data/lib/httpx/adapters/faraday.rb +4 -6
  10. data/lib/httpx/altsvc.rb +1 -0
  11. data/lib/httpx/chainable.rb +2 -2
  12. data/lib/httpx/connection.rb +80 -28
  13. data/lib/httpx/connection/http1.rb +19 -6
  14. data/lib/httpx/connection/http2.rb +32 -25
  15. data/lib/httpx/io.rb +16 -3
  16. data/lib/httpx/io/ssl.rb +35 -24
  17. data/lib/httpx/io/tcp.rb +48 -28
  18. data/lib/httpx/io/tls.rb +218 -0
  19. data/lib/httpx/io/tls/box.rb +365 -0
  20. data/lib/httpx/io/tls/context.rb +199 -0
  21. data/lib/httpx/io/tls/ffi.rb +390 -0
  22. data/lib/httpx/io/udp.rb +3 -2
  23. data/lib/httpx/io/unix.rb +27 -12
  24. data/lib/httpx/options.rb +11 -23
  25. data/lib/httpx/parser/http1.rb +4 -4
  26. data/lib/httpx/plugins/aws_sdk_authentication.rb +81 -0
  27. data/lib/httpx/plugins/aws_sigv4.rb +218 -0
  28. data/lib/httpx/plugins/compression.rb +21 -9
  29. data/lib/httpx/plugins/compression/brotli.rb +8 -6
  30. data/lib/httpx/plugins/compression/deflate.rb +4 -7
  31. data/lib/httpx/plugins/compression/gzip.rb +2 -2
  32. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +1 -1
  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 +83 -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/response.rb +14 -7
  53. data/lib/httpx/selector.rb +5 -0
  54. data/lib/httpx/session.rb +25 -2
  55. data/lib/httpx/transcoder/body.rb +3 -5
  56. data/lib/httpx/version.rb +1 -1
  57. data/sig/chainable.rbs +2 -1
  58. data/sig/connection/http1.rbs +3 -2
  59. data/sig/connection/http2.rbs +5 -3
  60. data/sig/options.rbs +7 -20
  61. data/sig/plugins/aws_sdk_authentication.rbs +17 -0
  62. data/sig/plugins/aws_sigv4.rbs +64 -0
  63. data/sig/plugins/compression.rbs +5 -3
  64. data/sig/plugins/compression/brotli.rbs +1 -1
  65. data/sig/plugins/compression/deflate.rbs +1 -1
  66. data/sig/plugins/compression/gzip.rbs +1 -1
  67. data/sig/plugins/cookies.rbs +0 -1
  68. data/sig/plugins/digest_authentication.rbs +0 -1
  69. data/sig/plugins/expect.rbs +0 -2
  70. data/sig/plugins/follow_redirects.rbs +0 -2
  71. data/sig/plugins/h2c.rbs +5 -10
  72. data/sig/plugins/persistent.rbs +0 -1
  73. data/sig/plugins/proxy.rbs +0 -1
  74. data/sig/plugins/push_promise.rbs +1 -1
  75. data/sig/plugins/retries.rbs +0 -4
  76. data/sig/plugins/upgrade.rbs +23 -0
  77. data/sig/response.rbs +3 -1
  78. metadata +48 -26
@@ -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,83 @@
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 && response.headers.key?("upgrade")
37
+
38
+ upgrade_protocol = response.headers["upgrade"].split(/ *, */).first
39
+
40
+ return response unless upgrade_protocol && options.upgrade_handlers.registry.key?(upgrade_protocol)
41
+
42
+ protocol_handler = options.upgrade_handlers.registry(upgrade_protocol)
43
+
44
+ return response unless protocol_handler
45
+
46
+ log { "upgrading to #{upgrade_protocol}..." }
47
+ connection = find_connection(request, connections, options)
48
+ connections << connection unless connections.include?(connection)
49
+
50
+ # do not upgrade already upgraded connections
51
+ return if connection.upgrade_protocol == upgrade_protocol
52
+
53
+ protocol_handler.call(connection, request, response)
54
+
55
+ # keep in the loop if the server is switching, unless
56
+ # the connection has been hijacked, in which case you want
57
+ # to terminante immediately
58
+ return if response.status == 101 && !connection.hijacked
59
+ end
60
+
61
+ response
62
+ end
63
+
64
+ def close(*args)
65
+ return super if args.empty?
66
+
67
+ connections, = args
68
+
69
+ pool.close(connections.reject(&:hijacked))
70
+ end
71
+ end
72
+
73
+ module ConnectionMethods
74
+ attr_reader :upgrade_protocol, :hijacked
75
+
76
+ def hijack_io
77
+ @hijacked = true
78
+ end
79
+ end
80
+ end
81
+ register_plugin(:upgrade, Upgrade)
82
+ end
83
+ 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
@@ -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
@@ -44,11 +44,9 @@ module HTTPX::Transcoder
44
44
  end
45
45
 
46
46
  def method_missing(meth, *args, &block)
47
- if @raw.respond_to?(meth)
48
- @raw.__send__(meth, *args, &block)
49
- else
50
- super
51
- end
47
+ return super unless @raw.respond_to?(meth)
48
+
49
+ @raw.__send__(meth, *args, &block)
52
50
  end
53
51
  end
54
52
 
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.11.0"
4
+ VERSION = "0.13.0"
5
5
  end
data/sig/chainable.rbs CHANGED
@@ -18,7 +18,8 @@ module HTTPX
18
18
  | (:cookies) -> Plugins::sessionCookies
19
19
  | (:expect) -> Session
20
20
  | (:follow_redirects) -> Plugins::sessionFollowRedirects
21
- | (:h2c) -> Plugins::sessionH2C
21
+ | (:upgrade) -> Session
22
+ | (:h2c) -> Session
22
23
  | (:multipart) -> Session
23
24
  | (:persistent) -> Plugins::sessionPersistent
24
25
  | (:proxy) -> Plugins::sessionProxy
@@ -4,6 +4,7 @@ module HTTPX
4
4
  include Loggable
5
5
 
6
6
  attr_reader pending: Array[Request]
7
+ attr_reader requests: Array[Request]
7
8
 
8
9
  @options: Options
9
10
  @max_concurrent_requests: Integer
@@ -31,7 +32,7 @@ module HTTPX
31
32
 
32
33
  def on_headers: (Hash[String, Array[String]] headers) -> void
33
34
 
34
- def on_trailers: (Array[String, String] headers) -> void
35
+ def on_trailers: (Hash[String, Array[String]] headers) -> void
35
36
 
36
37
  def on_data: (string chunk) -> void
37
38
 
@@ -51,7 +52,7 @@ module HTTPX
51
52
 
52
53
  def disable_pipelining: () -> void
53
54
 
54
- def set_request_headers: (Request) -> void
55
+ def set_protocol_headers: (Request) -> void
55
56
 
56
57
  def headline_uri: (Request) -> String
57
58