httpx 0.11.1 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
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 -1
  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/doc/release_notes/0_13_1.md +5 -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/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 +20 -8
  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 +24 -2
@@ -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.1"
4
+ VERSION = "0.13.1"
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