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.
- checksums.yaml +4 -4
- data/doc/release_notes/0_10_1.md +1 -1
- data/doc/release_notes/0_13_0.md +2 -2
- data/doc/release_notes/0_13_1.md +1 -1
- data/doc/release_notes/0_14_0.md +79 -0
- data/lib/httpx.rb +1 -2
- data/lib/httpx/callbacks.rb +12 -3
- data/lib/httpx/connection.rb +12 -9
- data/lib/httpx/connection/http1.rb +26 -11
- data/lib/httpx/connection/http2.rb +52 -8
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/options.rb +91 -56
- data/lib/httpx/plugins/aws_sdk_authentication.rb +5 -2
- data/lib/httpx/plugins/aws_sigv4.rb +4 -4
- data/lib/httpx/plugins/basic_authentication.rb +8 -3
- data/lib/httpx/plugins/compression.rb +8 -8
- data/lib/httpx/plugins/compression/brotli.rb +4 -3
- data/lib/httpx/plugins/compression/deflate.rb +4 -3
- data/lib/httpx/plugins/compression/gzip.rb +2 -1
- data/lib/httpx/plugins/cookies.rb +3 -7
- data/lib/httpx/plugins/digest_authentication.rb +4 -4
- data/lib/httpx/plugins/expect.rb +6 -6
- data/lib/httpx/plugins/follow_redirects.rb +3 -3
- data/lib/httpx/plugins/grpc.rb +247 -0
- data/lib/httpx/plugins/grpc/call.rb +62 -0
- data/lib/httpx/plugins/grpc/message.rb +85 -0
- data/lib/httpx/plugins/multipart/part.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +3 -7
- data/lib/httpx/plugins/proxy/ssh.rb +3 -3
- data/lib/httpx/plugins/rate_limiter.rb +1 -1
- data/lib/httpx/plugins/retries.rb +13 -14
- data/lib/httpx/plugins/stream.rb +96 -74
- data/lib/httpx/plugins/upgrade.rb +4 -4
- data/lib/httpx/request.rb +25 -2
- data/lib/httpx/response.rb +4 -0
- data/lib/httpx/session.rb +17 -7
- data/lib/httpx/transcoder/chunker.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/callbacks.rbs +2 -0
- data/sig/connection/http1.rbs +4 -0
- data/sig/connection/http2.rbs +5 -1
- data/sig/options.rbs +9 -2
- data/sig/plugins/aws_sdk_authentication.rbs +2 -0
- data/sig/plugins/basic_authentication.rbs +2 -0
- data/sig/plugins/compression.rbs +2 -2
- data/sig/plugins/stream.rbs +17 -16
- data/sig/request.rbs +7 -2
- data/sig/response.rbs +1 -0
- data/sig/session.rbs +4 -0
- metadata +38 -35
- data/lib/httpx/timeout.rb +0 -67
- 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
|
-
|
29
|
-
def_option(:retry_after) do |num|
|
28
|
+
def_option(:retry_after, <<-OUT)
|
30
29
|
# return early if callable
|
31
|
-
unless
|
32
|
-
|
33
|
-
raise Error, ":retry_after must be positive" unless
|
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
|
-
|
37
|
-
|
35
|
+
value
|
36
|
+
OUT
|
38
37
|
|
39
|
-
def_option(:max_retries)
|
40
|
-
num = Integer(
|
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
|
-
|
43
|
+
OUT
|
45
44
|
|
46
45
|
def_option(:retry_change_requests)
|
47
46
|
|
48
|
-
def_option(:retry_on)
|
49
|
-
raise ":retry_on must be called with the response" unless
|
47
|
+
def_option(:retry_on, <<-OUT)
|
48
|
+
raise ":retry_on must be called with the response" unless value.respond_to?(:call)
|
50
49
|
|
51
|
-
|
52
|
-
|
50
|
+
value
|
51
|
+
OUT
|
53
52
|
end.new(options).merge(max_retries: MAX_RETRIES)
|
54
53
|
end
|
55
54
|
|
data/lib/httpx/plugins/stream.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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)
|
24
|
-
raise Error, ":upgrade_handlers must be a registry" unless
|
23
|
+
def_option(:upgrade_handlers, <<-OUT)
|
24
|
+
raise Error, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
|
25
25
|
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
data/lib/httpx/response.rb
CHANGED
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.
|
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.
|
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.
|
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,
|
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,
|
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,
|
221
|
+
responses << fetch_response(request, connections, options)
|
212
222
|
end
|
213
223
|
break
|
214
224
|
end
|
data/lib/httpx/version.rb
CHANGED
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
|
data/sig/connection/http1.rbs
CHANGED
@@ -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
|
data/sig/connection/http2.rbs
CHANGED
@@ -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
|
|