httpx 0.13.2 → 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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_10_1.md +1 -1
  3. data/doc/release_notes/0_13_0.md +2 -2
  4. data/doc/release_notes/0_13_1.md +1 -1
  5. data/doc/release_notes/0_14_0.md +79 -0
  6. data/lib/httpx.rb +1 -2
  7. data/lib/httpx/callbacks.rb +12 -3
  8. data/lib/httpx/connection.rb +12 -9
  9. data/lib/httpx/connection/http1.rb +26 -11
  10. data/lib/httpx/connection/http2.rb +52 -8
  11. data/lib/httpx/headers.rb +1 -1
  12. data/lib/httpx/io/tcp.rb +1 -1
  13. data/lib/httpx/options.rb +91 -56
  14. data/lib/httpx/plugins/aws_sdk_authentication.rb +5 -2
  15. data/lib/httpx/plugins/aws_sigv4.rb +4 -4
  16. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  17. data/lib/httpx/plugins/compression.rb +8 -8
  18. data/lib/httpx/plugins/compression/brotli.rb +4 -3
  19. data/lib/httpx/plugins/compression/deflate.rb +4 -3
  20. data/lib/httpx/plugins/compression/gzip.rb +2 -1
  21. data/lib/httpx/plugins/cookies.rb +3 -7
  22. data/lib/httpx/plugins/digest_authentication.rb +4 -4
  23. data/lib/httpx/plugins/expect.rb +6 -6
  24. data/lib/httpx/plugins/follow_redirects.rb +3 -3
  25. data/lib/httpx/plugins/grpc.rb +247 -0
  26. data/lib/httpx/plugins/grpc/call.rb +62 -0
  27. data/lib/httpx/plugins/grpc/message.rb +85 -0
  28. data/lib/httpx/plugins/multipart/part.rb +1 -1
  29. data/lib/httpx/plugins/proxy.rb +3 -7
  30. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  31. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  32. data/lib/httpx/plugins/retries.rb +13 -14
  33. data/lib/httpx/plugins/stream.rb +96 -74
  34. data/lib/httpx/plugins/upgrade.rb +4 -4
  35. data/lib/httpx/request.rb +25 -2
  36. data/lib/httpx/response.rb +4 -0
  37. data/lib/httpx/session.rb +17 -7
  38. data/lib/httpx/transcoder/chunker.rb +1 -1
  39. data/lib/httpx/version.rb +1 -1
  40. data/sig/callbacks.rbs +2 -0
  41. data/sig/connection/http1.rbs +4 -0
  42. data/sig/connection/http2.rbs +5 -1
  43. data/sig/options.rbs +9 -2
  44. data/sig/plugins/aws_sdk_authentication.rbs +2 -0
  45. data/sig/plugins/basic_authentication.rbs +2 -0
  46. data/sig/plugins/compression.rbs +2 -2
  47. data/sig/plugins/stream.rbs +17 -16
  48. data/sig/request.rbs +7 -2
  49. data/sig/response.rbs +1 -0
  50. data/sig/session.rbs +4 -0
  51. metadata +38 -35
  52. data/lib/httpx/timeout.rb +0 -67
  53. data/sig/timeout.rbs +0 -29
@@ -25,31 +25,30 @@ module HTTPX
25
25
 
26
26
  def self.extra_options(options)
27
27
  Class.new(options.class) do
28
- # number of seconds after which one can retry the request
29
- def_option(:retry_after) do |num|
28
+ def_option(:retry_after, <<-OUT)
30
29
  # return early if callable
31
- unless num.respond_to?(:call)
32
- num = Integer(num)
33
- raise Error, ":retry_after must be positive" unless num.positive?
30
+ unless value.respond_to?(:call)
31
+ value = Integer(value)
32
+ raise Error, ":retry_after must be positive" unless value.positive?
34
33
  end
35
34
 
36
- num
37
- end
35
+ value
36
+ OUT
38
37
 
39
- def_option(:max_retries) do |num|
40
- num = Integer(num)
38
+ def_option(:max_retries, <<-OUT)
39
+ num = Integer(value)
41
40
  raise Error, ":max_retries must be positive" unless num.positive?
42
41
 
43
42
  num
44
- end
43
+ OUT
45
44
 
46
45
  def_option(:retry_change_requests)
47
46
 
48
- def_option(:retry_on) do |callback|
49
- raise ":retry_on must be called with the response" unless callback.respond_to?(:call)
47
+ def_option(:retry_on, <<-OUT)
48
+ raise ":retry_on must be called with the response" unless value.respond_to?(:call)
50
49
 
51
- callback
52
- end
50
+ value
51
+ OUT
53
52
  end.new(options).merge(max_retries: MAX_RETRIES)
54
53
  end
55
54
 
@@ -1,6 +1,93 @@
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).
@@ -15,10 +102,13 @@ module HTTPX
15
102
  return super(*args, **options) unless stream
16
103
 
17
104
  requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
18
-
19
105
  raise Error, "only 1 response at a time is supported for streaming requests" unless requests.size == 1
20
106
 
21
- StreamResponse.new(requests.first, self)
107
+ request = requests.first
108
+
109
+ connections = _send_requests(requests, request.options)
110
+
111
+ StreamResponse.new(request, self, connections)
22
112
  end
23
113
  end
24
114
 
@@ -53,78 +143,10 @@ module HTTPX
53
143
  end
54
144
  end
55
145
 
56
- class StreamResponse
57
- def initialize(request, session)
58
- @request = request
59
- @session = session
60
- @options = @request.options
61
- end
62
-
63
- def each(&block)
64
- return enum_for(__method__) unless block_given?
65
-
66
- raise Error, "response already streamed" if @response
67
-
68
- @request.stream = self
69
-
70
- begin
71
- @on_chunk = block
72
-
73
- response.raise_for_status
74
- response.close
75
- ensure
76
- @on_chunk = nil
77
- end
78
- end
79
-
80
- def each_line
81
- return enum_for(__method__) unless block_given?
82
-
83
- line = +""
84
-
85
- each do |chunk|
86
- line << chunk
87
-
88
- while (idx = line.index("\n"))
89
- yield line.byteslice(0..idx - 1)
90
-
91
- line = line.byteslice(idx + 1..-1)
92
- end
93
- end
94
- end
95
-
96
- # This is a ghost method. It's to be used ONLY internally, when processing streams
97
- def on_chunk(chunk)
98
- raise NoMethodError unless @on_chunk
99
-
100
- @on_chunk.call(chunk)
101
- end
102
-
103
- # :nocov:
104
- def inspect
105
- "#<StreamResponse:#{object_id}>"
106
- end
107
- # :nocov:
108
-
109
- def to_s
110
- response.to_s
111
- end
112
-
113
- private
114
-
115
- def response
116
- @response ||= @session.__send__(:send_requests, @request, @options).first
117
- end
118
-
119
- def respond_to_missing?(*args)
120
- @options.response_class.respond_to?(*args) || super
121
- end
122
-
123
- def method_missing(meth, *args, &block)
124
- return super unless @options.response_class.public_method_defined?(meth)
125
-
126
- response.__send__(meth, *args, &block)
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
@@ -20,11 +20,11 @@ module HTTPX
20
20
  end
21
21
 
22
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)
23
+ def_option(:upgrade_handlers, <<-OUT)
24
+ raise Error, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
25
25
 
26
- encs
27
- end
26
+ value
27
+ OUT
28
28
  end.new(options).merge(upgrade_handlers: upgrade_handlers)
29
29
  end
30
30
  end
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?
@@ -253,6 +274,8 @@ module HTTPX
253
274
  nextstate = :expect
254
275
  end
255
276
  end
277
+ when :trailers
278
+ return unless @state == :body
256
279
  when :done
257
280
  return if @state == :expect
258
281
  end
@@ -94,6 +94,10 @@ module HTTPX
94
94
  @state = :idle
95
95
  end
96
96
 
97
+ def closed?
98
+ @state == :closed
99
+ end
100
+
97
101
  def write(chunk)
98
102
  return if @state == :closed
99
103
 
data/lib/httpx/session.rb CHANGED
@@ -77,7 +77,7 @@ module HTTPX
77
77
  end
78
78
 
79
79
  def set_connection_callbacks(connection, connections, options)
80
- connection.on(:misdirected) do |misdirected_request|
80
+ connection.only(:misdirected) do |misdirected_request|
81
81
  other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
82
82
  other_connection.merge(connection)
83
83
  catch(:coalesced) do
@@ -88,11 +88,11 @@ module HTTPX
88
88
  misdirected_request.transition(:idle)
89
89
  other_connection.send(misdirected_request)
90
90
  end
91
- connection.on(:altsvc) do |alt_origin, origin, alt_params|
91
+ connection.only(:altsvc) do |alt_origin, origin, alt_params|
92
92
  other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
93
93
  connections << other_connection if other_connection
94
94
  end
95
- connection.on(:exhausted) do
95
+ connection.only(:exhausted) do
96
96
  other_connection = connection.create_idle
97
97
  other_connection.merge(connection)
98
98
  catch(:coalesced) do
@@ -175,12 +175,18 @@ module HTTPX
175
175
  end
176
176
 
177
177
  def send_requests(*requests, options)
178
- connections = []
179
178
  request_options = @options.merge(options)
180
179
 
180
+ connections = _send_requests(requests, request_options)
181
+ receive_requests(requests, connections, request_options)
182
+ end
183
+
184
+ def _send_requests(requests, options)
185
+ connections = []
186
+
181
187
  requests.each do |request|
182
188
  error = catch(:resolve_error) do
183
- connection = find_connection(request, connections, request_options)
189
+ connection = find_connection(request, connections, options)
184
190
  connection.send(request)
185
191
  end
186
192
  next unless error.is_a?(ResolveError)
@@ -188,13 +194,17 @@ module HTTPX
188
194
  request.emit(:response, ErrorResponse.new(request, error, options))
189
195
  end
190
196
 
197
+ connections
198
+ end
199
+
200
+ def receive_requests(requests, connections, options)
191
201
  responses = []
192
202
 
193
203
  begin
194
204
  # guarantee ordered responses
195
205
  loop do
196
206
  request = requests.first
197
- pool.next_tick until (response = fetch_response(request, connections, request_options))
207
+ pool.next_tick until (response = fetch_response(request, connections, options))
198
208
 
199
209
  responses << response
200
210
  requests.shift
@@ -208,7 +218,7 @@ module HTTPX
208
218
  # opportunity to traverse the requests, hence we're returning only a fraction of the errors
209
219
  # we were supposed to. This effectively fetches the existing responses and return them.
210
220
  while (request = requests.shift)
211
- responses << fetch_response(request, connections, request_options)
221
+ responses << fetch_response(request, connections, options)
212
222
  end
213
223
  break
214
224
  end
@@ -20,7 +20,7 @@ module HTTPX::Transcoder
20
20
  @raw.each do |chunk|
21
21
  yield "#{chunk.bytesize.to_s(16)}#{CRLF}#{chunk}#{CRLF}"
22
22
  end
23
- yield "0#{CRLF}#{CRLF}"
23
+ yield "0#{CRLF}"
24
24
  end
25
25
 
26
26
  def respond_to_missing?(meth, *args)
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.13.2"
4
+ VERSION = "0.14.0"
5
5
  end
data/sig/callbacks.rbs CHANGED
@@ -6,8 +6,10 @@ module HTTPX
6
6
  module Callbacks
7
7
  def on: (Symbol) { (*untyped) -> void } -> void
8
8
  def once: (Symbol) { (*untyped) -> void } -> void
9
+ def only: (Symbol) { (*untyped) -> void } -> void
9
10
  def emit: (Symbol, *untyped) -> void
10
11
 
12
+ def callbacks_for?: (Symbol) -> bool
11
13
  def callbacks: () -> Hash[Symbol, Array[_Callable]]
12
14
  | (Symbol) -> Array[_Callable]
13
15
  end
@@ -60,6 +60,10 @@ module HTTPX
60
60
 
61
61
  def join_headers: (Request request) -> void
62
62
 
63
+ def join_trailers: (Request request) -> void
64
+
65
+ def join_headers2: (Headers headers) -> void
66
+
63
67
  def join_body: (Request request) -> void
64
68
 
65
69
  def capitalized: (String field) -> String
@@ -53,13 +53,17 @@ module HTTPX
53
53
 
54
54
  def join_headers: (HTTP2Next::Stream stream, Request request) -> void
55
55
 
56
+ def join_trailers: (HTTP2Next::Stream stream, Request request) -> void
57
+
56
58
  def join_body: (HTTP2Next::Stream stream, Request request) -> void
57
59
 
58
60
  def on_stream_headers: (HTTP2Next::Stream stream, Request request, Array[[String, String]] headers) -> void
59
61
 
62
+ def on_stream_trailers: (HTTP2Next::Stream stream, Request request, Array[[String, String]] headers) -> void
63
+
60
64
  def on_stream_data: (HTTP2Next::Stream stream, Request request, string data) -> void
61
65
 
62
- def on_stream_close: (HTTP2Next::Stream stream, Request request, Symbol? error) -> void
66
+ def on_stream_close: (HTTP2Next::Stream stream, Request request, (Symbol | StandardError)? error) -> void
63
67
 
64
68
  def on_frame: (string bytes) -> void
65
69