httpx 0.11.3 → 0.14.0

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