httpx 0.11.3 → 0.14.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/0_10_1.md +1 -1
  4. data/doc/release_notes/0_11_1.md +5 -1
  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/doc/release_notes/0_14_0.md +79 -0
  10. data/lib/httpx.rb +3 -3
  11. data/lib/httpx/adapters/faraday.rb +4 -6
  12. data/lib/httpx/altsvc.rb +1 -0
  13. data/lib/httpx/callbacks.rb +12 -3
  14. data/lib/httpx/chainable.rb +2 -2
  15. data/lib/httpx/connection.rb +92 -37
  16. data/lib/httpx/connection/http1.rb +37 -19
  17. data/lib/httpx/connection/http2.rb +82 -31
  18. data/lib/httpx/headers.rb +1 -1
  19. data/lib/httpx/io.rb +16 -3
  20. data/lib/httpx/io/ssl.rb +35 -24
  21. data/lib/httpx/io/tcp.rb +50 -28
  22. data/lib/httpx/io/tls.rb +218 -0
  23. data/lib/httpx/io/tls/box.rb +365 -0
  24. data/lib/httpx/io/tls/context.rb +199 -0
  25. data/lib/httpx/io/tls/ffi.rb +390 -0
  26. data/lib/httpx/io/udp.rb +31 -7
  27. data/lib/httpx/io/unix.rb +27 -12
  28. data/lib/httpx/options.rb +97 -74
  29. data/lib/httpx/parser/http1.rb +4 -4
  30. data/lib/httpx/plugins/aws_sdk_authentication.rb +84 -0
  31. data/lib/httpx/plugins/aws_sigv4.rb +219 -0
  32. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  33. data/lib/httpx/plugins/compression.rb +24 -12
  34. data/lib/httpx/plugins/compression/brotli.rb +10 -7
  35. data/lib/httpx/plugins/compression/deflate.rb +8 -10
  36. data/lib/httpx/plugins/compression/gzip.rb +4 -3
  37. data/lib/httpx/plugins/cookies.rb +3 -7
  38. data/lib/httpx/plugins/digest_authentication.rb +5 -5
  39. data/lib/httpx/plugins/expect.rb +6 -6
  40. data/lib/httpx/plugins/follow_redirects.rb +4 -4
  41. data/lib/httpx/plugins/grpc.rb +247 -0
  42. data/lib/httpx/plugins/grpc/call.rb +62 -0
  43. data/lib/httpx/plugins/grpc/message.rb +85 -0
  44. data/lib/httpx/plugins/h2c.rb +43 -58
  45. data/lib/httpx/plugins/internal_telemetry.rb +93 -0
  46. data/lib/httpx/plugins/multipart.rb +2 -0
  47. data/lib/httpx/plugins/multipart/encoder.rb +4 -9
  48. data/lib/httpx/plugins/multipart/part.rb +1 -1
  49. data/lib/httpx/plugins/proxy.rb +4 -8
  50. data/lib/httpx/plugins/proxy/http.rb +1 -1
  51. data/lib/httpx/plugins/proxy/socks4.rb +8 -0
  52. data/lib/httpx/plugins/proxy/socks5.rb +8 -0
  53. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  54. data/lib/httpx/plugins/push_promise.rb +3 -2
  55. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  56. data/lib/httpx/plugins/retries.rb +15 -16
  57. data/lib/httpx/plugins/stream.rb +99 -77
  58. data/lib/httpx/plugins/upgrade.rb +84 -0
  59. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  60. data/lib/httpx/pool.rb +14 -6
  61. data/lib/httpx/registry.rb +1 -7
  62. data/lib/httpx/request.rb +36 -3
  63. data/lib/httpx/resolver/https.rb +3 -11
  64. data/lib/httpx/resolver/native.rb +7 -3
  65. data/lib/httpx/response.rb +18 -7
  66. data/lib/httpx/selector.rb +5 -0
  67. data/lib/httpx/session.rb +41 -8
  68. data/lib/httpx/transcoder/body.rb +3 -5
  69. data/lib/httpx/transcoder/chunker.rb +1 -1
  70. data/lib/httpx/version.rb +1 -1
  71. data/sig/callbacks.rbs +2 -0
  72. data/sig/chainable.rbs +2 -1
  73. data/sig/connection/http1.rbs +7 -2
  74. data/sig/connection/http2.rbs +10 -4
  75. data/sig/options.rbs +16 -22
  76. data/sig/plugins/aws_sdk_authentication.rbs +19 -0
  77. data/sig/plugins/aws_sigv4.rbs +64 -0
  78. data/sig/plugins/basic_authentication.rbs +2 -0
  79. data/sig/plugins/compression.rbs +7 -5
  80. data/sig/plugins/compression/brotli.rbs +1 -1
  81. data/sig/plugins/compression/deflate.rbs +1 -1
  82. data/sig/plugins/compression/gzip.rbs +1 -1
  83. data/sig/plugins/cookies.rbs +0 -1
  84. data/sig/plugins/digest_authentication.rbs +0 -1
  85. data/sig/plugins/expect.rbs +0 -2
  86. data/sig/plugins/follow_redirects.rbs +0 -2
  87. data/sig/plugins/h2c.rbs +5 -10
  88. data/sig/plugins/persistent.rbs +0 -1
  89. data/sig/plugins/proxy.rbs +0 -1
  90. data/sig/plugins/push_promise.rbs +1 -1
  91. data/sig/plugins/retries.rbs +0 -4
  92. data/sig/plugins/stream.rbs +17 -16
  93. data/sig/plugins/upgrade.rbs +23 -0
  94. data/sig/request.rbs +7 -2
  95. data/sig/response.rbs +4 -1
  96. data/sig/session.rbs +4 -0
  97. metadata +56 -33
  98. data/lib/httpx/timeout.rb +0 -67
  99. data/sig/timeout.rbs +0 -29
@@ -1,10 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
+ class StreamResponse
5
+ def initialize(request, session, connections)
6
+ @request = request
7
+ @session = session
8
+ @connections = connections
9
+ @options = @request.options
10
+ end
11
+
12
+ def each(&block)
13
+ return enum_for(__method__) unless block_given?
14
+
15
+ raise Error, "response already streamed" if @response
16
+
17
+ @request.stream = self
18
+
19
+ begin
20
+ @on_chunk = block
21
+
22
+ if @request.response
23
+ # if we've already started collecting the payload, yield it first
24
+ # before proceeding
25
+ body = @request.response.body
26
+
27
+ body.each do |chunk|
28
+ on_chunk(chunk)
29
+ end
30
+ end
31
+
32
+ response.raise_for_status
33
+ response.close
34
+ ensure
35
+ @on_chunk = nil
36
+ end
37
+ end
38
+
39
+ def each_line
40
+ return enum_for(__method__) unless block_given?
41
+
42
+ line = +""
43
+
44
+ each do |chunk|
45
+ line << chunk
46
+
47
+ while (idx = line.index("\n"))
48
+ yield line.byteslice(0..idx - 1)
49
+
50
+ line = line.byteslice(idx + 1..-1)
51
+ end
52
+ end
53
+ end
54
+
55
+ # This is a ghost method. It's to be used ONLY internally, when processing streams
56
+ def on_chunk(chunk)
57
+ raise NoMethodError unless @on_chunk
58
+
59
+ @on_chunk.call(chunk)
60
+ end
61
+
62
+ # :nocov:
63
+ def inspect
64
+ "#<StreamResponse:#{object_id}>"
65
+ end
66
+ # :nocov:
67
+
68
+ def to_s
69
+ response.to_s
70
+ end
71
+
72
+ private
73
+
74
+ def response
75
+ @session.__send__(:receive_requests, [@request], @connections, @options) until @request.response
76
+
77
+ @request.response
78
+ end
79
+
80
+ def respond_to_missing?(meth, *args)
81
+ response.respond_to?(meth, *args) || super
82
+ end
83
+
84
+ def method_missing(meth, *args, &block)
85
+ return super unless response.respond_to?(meth)
86
+
87
+ response.__send__(meth, *args, &block)
88
+ end
89
+ end
90
+
4
91
  module Plugins
5
92
  #
6
93
  # This plugin adds support for stream response (text/event-stream).
7
94
  #
95
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Stream
96
+ #
8
97
  module Stream
9
98
  module InstanceMethods
10
99
  private
@@ -13,10 +102,13 @@ module HTTPX
13
102
  return super(*args, **options) unless stream
14
103
 
15
104
  requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
16
-
17
105
  raise Error, "only 1 response at a time is supported for streaming requests" unless requests.size == 1
18
106
 
19
- StreamResponse.new(requests.first, self)
107
+ request = requests.first
108
+
109
+ connections = _send_requests(requests, request.options)
110
+
111
+ StreamResponse.new(request, self, connections)
20
112
  end
21
113
  end
22
114
 
@@ -31,7 +123,7 @@ module HTTPX
31
123
  end
32
124
 
33
125
  module ResponseBodyMethods
34
- def initialize(*, **)
126
+ def initialize(*)
35
127
  super
36
128
  @stream = @response.stream
37
129
  end
@@ -51,80 +143,10 @@ module HTTPX
51
143
  end
52
144
  end
53
145
 
54
- class StreamResponse
55
- def initialize(request, session)
56
- @request = request
57
- @session = session
58
- @options = @request.options
59
- end
60
-
61
- def each(&block)
62
- return enum_for(__method__) unless block_given?
63
-
64
- raise Error, "response already streamed" if @response
65
-
66
- @request.stream = self
67
-
68
- begin
69
- @on_chunk = block
70
-
71
- response.raise_for_status
72
- response.close
73
- ensure
74
- @on_chunk = nil
75
- end
76
- end
77
-
78
- def each_line
79
- return enum_for(__method__) unless block_given?
80
-
81
- line = +""
82
-
83
- each do |chunk|
84
- line << chunk
85
-
86
- while (idx = line.index("\n"))
87
- yield line.byteslice(0..idx - 1)
88
-
89
- line = line.byteslice(idx + 1..-1)
90
- end
91
- end
92
- end
93
-
94
- # This is a ghost method. It's to be used ONLY internally, when processing streams
95
- def on_chunk(chunk)
96
- raise NoMethodError unless @on_chunk
97
-
98
- @on_chunk.call(chunk)
99
- end
100
-
101
- # :nocov:
102
- def inspect
103
- "#<StreamResponse:#{object_id}>"
104
- end
105
- # :nocov:
106
-
107
- def to_s
108
- response.to_s
109
- end
110
-
111
- private
112
-
113
- def response
114
- @response ||= @session.__send__(:send_requests, @request, @options).first
115
- end
116
-
117
- def respond_to_missing?(*args)
118
- @options.response_class.respond_to?(*args) || super
119
- end
120
-
121
- 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
127
- end
146
+ def self.const_missing(const_name)
147
+ super unless const_name == :StreamResponse
148
+ warn "DEPRECATION WARNING: the class #{self}::StreamResponse is deprecated. Use HTTPX::StreamResponse instead."
149
+ HTTPX::StreamResponse
128
150
  end
129
151
  end
130
152
  register_plugin :stream, Stream
@@ -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, <<-OUT)
24
+ raise Error, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
25
+
26
+ value
27
+ OUT
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
@@ -35,14 +35,22 @@ module HTTPX
35
35
 
36
36
  attr_reader :verb, :uri, :headers, :body, :state, :options, :response
37
37
 
38
+ # Exception raised during enumerable body writes
39
+ attr_reader :drain_error
40
+
38
41
  def_delegator :@body, :empty?
39
42
 
40
43
  def_delegator :@body, :chunk!
41
44
 
42
45
  def initialize(verb, uri, options = {})
43
46
  @verb = verb.to_s.downcase.to_sym
44
- @uri = Utils.uri(uri)
45
47
  @options = Options.new(options)
48
+ @uri = Utils.uri(uri)
49
+ if @uri.relative?
50
+ raise(Error, "invalid URI: #{@uri}") unless @options.origin
51
+
52
+ @uri = @options.origin.merge(@uri)
53
+ end
46
54
 
47
55
  raise(Error, "unknown method: #{verb}") unless METHODS.include?(@verb)
48
56
 
@@ -54,6 +62,14 @@ module HTTPX
54
62
  @state = :idle
55
63
  end
56
64
 
65
+ def trailers?
66
+ defined?(@trailers)
67
+ end
68
+
69
+ def trailers
70
+ @trailers ||= @options.headers_class.new
71
+ end
72
+
57
73
  def interests
58
74
  return :r if @state == :done || @state == :expect
59
75
 
@@ -124,6 +140,9 @@ module HTTPX
124
140
  chunk.dup
125
141
  rescue StopIteration
126
142
  nil
143
+ rescue StandardError => e
144
+ @drain_error = e
145
+ nil
127
146
  end
128
147
 
129
148
  # :nocov:
@@ -200,7 +219,9 @@ module HTTPX
200
219
  end
201
220
 
202
221
  def unbounded_body?
203
- chunked? || @body.bytesize == Float::INFINITY
222
+ return @unbounded_body if defined?(@unbounded_body)
223
+
224
+ @unbounded_body = (chunked? || @body.bytesize == Float::INFINITY)
204
225
  end
205
226
 
206
227
  def chunked?
@@ -217,6 +238,16 @@ module HTTPX
217
238
  "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
218
239
  end
219
240
  # :nocov:
241
+
242
+ def respond_to_missing?(meth, *args)
243
+ @body.respond_to?(meth, *args) || super
244
+ end
245
+
246
+ def method_missing(meth, *args, &block)
247
+ return super unless @body.respond_to?(meth)
248
+
249
+ @body.__send__(meth, *args, &block)
250
+ end
220
251
  end
221
252
 
222
253
  def transition(nextstate)
@@ -243,11 +274,13 @@ module HTTPX
243
274
  nextstate = :expect
244
275
  end
245
276
  end
277
+ when :trailers
278
+ return unless @state == :body
246
279
  when :done
247
280
  return if @state == :expect
248
281
  end
249
282
  @state = nextstate
250
- emit(@state)
283
+ emit(@state, self)
251
284
  nil
252
285
  end
253
286