httpx 0.13.2 → 0.14.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) 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/doc/release_notes/0_14_1.md +7 -0
  7. data/doc/release_notes/0_14_2.md +6 -0
  8. data/doc/release_notes/0_14_3.md +5 -0
  9. data/doc/release_notes/0_14_4.md +5 -0
  10. data/lib/httpx.rb +1 -2
  11. data/lib/httpx/callbacks.rb +12 -3
  12. data/lib/httpx/connection.rb +12 -9
  13. data/lib/httpx/connection/http1.rb +40 -15
  14. data/lib/httpx/connection/http2.rb +61 -15
  15. data/lib/httpx/headers.rb +7 -3
  16. data/lib/httpx/idna.rb +15 -0
  17. data/lib/httpx/io/tcp.rb +1 -1
  18. data/lib/httpx/options.rb +91 -56
  19. data/lib/httpx/plugins/aws_sdk_authentication.rb +5 -2
  20. data/lib/httpx/plugins/aws_sigv4.rb +4 -4
  21. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  22. data/lib/httpx/plugins/compression.rb +8 -8
  23. data/lib/httpx/plugins/compression/brotli.rb +4 -3
  24. data/lib/httpx/plugins/compression/deflate.rb +4 -3
  25. data/lib/httpx/plugins/compression/gzip.rb +2 -1
  26. data/lib/httpx/plugins/cookies.rb +3 -7
  27. data/lib/httpx/plugins/digest_authentication.rb +4 -4
  28. data/lib/httpx/plugins/expect.rb +6 -6
  29. data/lib/httpx/plugins/follow_redirects.rb +3 -3
  30. data/lib/httpx/plugins/grpc.rb +247 -0
  31. data/lib/httpx/plugins/grpc/call.rb +62 -0
  32. data/lib/httpx/plugins/grpc/message.rb +85 -0
  33. data/lib/httpx/plugins/multipart/part.rb +2 -2
  34. data/lib/httpx/plugins/proxy.rb +3 -7
  35. data/lib/httpx/plugins/proxy/http.rb +5 -4
  36. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  37. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  38. data/lib/httpx/plugins/retries.rb +13 -14
  39. data/lib/httpx/plugins/stream.rb +96 -74
  40. data/lib/httpx/plugins/upgrade.rb +4 -4
  41. data/lib/httpx/request.rb +25 -2
  42. data/lib/httpx/response.rb +4 -0
  43. data/lib/httpx/session.rb +17 -7
  44. data/lib/httpx/transcoder/chunker.rb +1 -1
  45. data/lib/httpx/version.rb +1 -1
  46. data/sig/callbacks.rbs +2 -0
  47. data/sig/connection/http1.rbs +5 -1
  48. data/sig/connection/http2.rbs +6 -2
  49. data/sig/headers.rbs +2 -2
  50. data/sig/options.rbs +9 -2
  51. data/sig/plugins/aws_sdk_authentication.rbs +2 -0
  52. data/sig/plugins/basic_authentication.rbs +2 -0
  53. data/sig/plugins/compression.rbs +2 -2
  54. data/sig/plugins/multipart.rbs +1 -1
  55. data/sig/plugins/stream.rbs +17 -16
  56. data/sig/request.rbs +7 -2
  57. data/sig/response.rbs +1 -0
  58. data/sig/session.rbs +4 -0
  59. metadata +19 -7
  60. data/lib/httpx/timeout.rb +0 -67
  61. data/sig/timeout.rbs +0 -29
@@ -67,13 +67,9 @@ module HTTPX
67
67
 
68
68
  def extra_options(options)
69
69
  Class.new(options.class) do
70
- def_option(:proxy) do |pr|
71
- if pr.is_a?(Parameters)
72
- pr
73
- else
74
- Hash[pr]
75
- end
76
- end
70
+ def_option(:proxy, <<-OUT)
71
+ value.is_a?(#{Parameters}) ? value : Hash[value]
72
+ OUT
77
73
  end.new(options)
78
74
  end
79
75
  end
@@ -82,11 +82,12 @@ module HTTPX
82
82
  end
83
83
 
84
84
  def set_protocol_headers(request)
85
- super
85
+ extra_headers = super
86
+
86
87
  proxy_params = @options.proxy
87
- request.headers["proxy-authorization"] = "Basic #{proxy_params.token_authentication}" if proxy_params.authenticated?
88
- request.headers["proxy-connection"] = request.headers["connection"]
89
- request.headers.delete("connection")
88
+ extra_headers["proxy-authorization"] = "Basic #{proxy_params.token_authentication}" if proxy_params.authenticated?
89
+ extra_headers["proxy-connection"] = extra_headers.delete("connection") if extra_headers.key?("connection")
90
+ extra_headers
90
91
  end
91
92
  end
92
93
 
@@ -12,9 +12,9 @@ module HTTPX
12
12
 
13
13
  def self.extra_options(options)
14
14
  Class.new(options.class) do
15
- def_option(:proxy) do |pr|
16
- Hash[pr]
17
- end
15
+ def_option(:proxy, <<-OUT)
16
+ Hash[value]
17
+ OUT
18
18
  end.new(options)
19
19
  end
20
20
 
@@ -15,7 +15,7 @@ module HTTPX
15
15
  class << self
16
16
  RATE_LIMIT_CODES = [429, 503].freeze
17
17
 
18
- def load_dependencies(klass)
18
+ def configure(klass)
19
19
  klass.plugin(:retries,
20
20
  retry_change_requests: true,
21
21
  retry_on: method(:retry_on_rate_limited_response),
@@ -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