httpx 1.0.2 → 1.1.1
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/README.md +2 -2
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/lib/httpx/adapters/faraday.rb +28 -19
- data/lib/httpx/connection/http1.rb +13 -6
- data/lib/httpx/connection/http2.rb +1 -1
- data/lib/httpx/connection.rb +53 -15
- data/lib/httpx/domain_name.rb +6 -2
- data/lib/httpx/errors.rb +32 -0
- data/lib/httpx/io/ssl.rb +3 -1
- data/lib/httpx/io/tcp.rb +4 -2
- data/lib/httpx/io.rb +5 -1
- data/lib/httpx/options.rb +48 -1
- data/lib/httpx/plugins/expect.rb +10 -8
- data/lib/httpx/plugins/proxy/http.rb +0 -1
- data/lib/httpx/pool.rb +0 -4
- data/lib/httpx/request/body.rb +22 -9
- data/lib/httpx/request.rb +63 -4
- data/lib/httpx/resolver/native.rb +2 -2
- data/lib/httpx/resolver/resolver.rb +5 -2
- data/lib/httpx/resolver/system.rb +5 -2
- data/lib/httpx/resolver.rb +6 -4
- data/lib/httpx/response/body.rb +30 -5
- data/lib/httpx/response/buffer.rb +20 -14
- data/lib/httpx/response.rb +95 -16
- data/lib/httpx/selector.rb +2 -2
- data/lib/httpx/session.rb +64 -2
- data/lib/httpx/timers.rb +35 -8
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http1.rbs +3 -3
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +4 -1
- data/sig/io/tcp.rbs +1 -1
- data/sig/options.rbs +2 -2
- data/sig/pool.rbs +1 -1
- data/sig/request/body.rbs +0 -2
- data/sig/request.rbs +9 -3
- data/sig/resolver/native.rbs +1 -1
- data/sig/resolver.rbs +1 -1
- data/sig/response/body.rbs +0 -1
- data/sig/response.rbs +11 -3
- data/sig/timers.rbs +17 -7
- data/sig/transcoder/utils/inflater.rbs +12 -0
- metadata +8 -2
data/lib/httpx/response.rb
CHANGED
|
@@ -7,24 +7,48 @@ require "fileutils"
|
|
|
7
7
|
require "forwardable"
|
|
8
8
|
|
|
9
9
|
module HTTPX
|
|
10
|
+
# Defines a HTTP response is handled internally, with a few properties exposed as attributes,
|
|
11
|
+
# implements (indirectly, via the +body+) the IO write protocol to internally buffer payloads,
|
|
12
|
+
# implements the IO reader protocol in order for users to buffer/stream it, acts as an enumerable
|
|
13
|
+
# (of payload chunks).
|
|
10
14
|
class Response
|
|
11
15
|
extend Forwardable
|
|
12
16
|
include Callbacks
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
# the HTTP response status code
|
|
19
|
+
attr_reader :status
|
|
15
20
|
|
|
21
|
+
# an HTTPX::Headers object containing the response HTTP headers.
|
|
22
|
+
attr_reader :headers
|
|
23
|
+
|
|
24
|
+
# a HTTPX::Response::Body object wrapping the response body.
|
|
25
|
+
attr_reader :body
|
|
26
|
+
|
|
27
|
+
# The HTTP protocol version used to fetch the response.
|
|
28
|
+
attr_reader :version
|
|
29
|
+
|
|
30
|
+
# returns the response body buffered in a string.
|
|
16
31
|
def_delegator :@body, :to_s
|
|
17
32
|
|
|
18
33
|
def_delegator :@body, :to_str
|
|
19
34
|
|
|
35
|
+
# implements the IO reader +#read+ interface.
|
|
20
36
|
def_delegator :@body, :read
|
|
21
37
|
|
|
38
|
+
# copies the response body to a different location.
|
|
22
39
|
def_delegator :@body, :copy_to
|
|
23
40
|
|
|
41
|
+
# closes the body.
|
|
24
42
|
def_delegator :@body, :close
|
|
25
43
|
|
|
44
|
+
# the corresponding request uri.
|
|
26
45
|
def_delegator :@request, :uri
|
|
27
46
|
|
|
47
|
+
# the IP address of the peer server.
|
|
48
|
+
def_delegator :@request, :peer_address
|
|
49
|
+
|
|
50
|
+
# inits the instance with the corresponding +request+ to this response, an the
|
|
51
|
+
# response HTTP +status+, +version+ and HTTPX::Headers instance of +headers+.
|
|
28
52
|
def initialize(request, status, version, headers)
|
|
29
53
|
@request = request
|
|
30
54
|
@options = request.options
|
|
@@ -33,32 +57,49 @@ module HTTPX
|
|
|
33
57
|
@headers = @options.headers_class.new(headers)
|
|
34
58
|
@body = @options.response_body_class.new(self, @options)
|
|
35
59
|
@finished = complete?
|
|
60
|
+
@content_type = nil
|
|
36
61
|
end
|
|
37
62
|
|
|
63
|
+
# merges headers defined in +h+ into the response headers.
|
|
38
64
|
def merge_headers(h)
|
|
39
65
|
@headers = @headers.merge(h)
|
|
40
66
|
end
|
|
41
67
|
|
|
68
|
+
# writes +data+ chunk into the response body.
|
|
42
69
|
def <<(data)
|
|
43
70
|
@body.write(data)
|
|
44
71
|
end
|
|
45
72
|
|
|
73
|
+
# returns the response mime type, as per what's declared in the content-type header.
|
|
74
|
+
#
|
|
75
|
+
# response.content_type #=> "text/plain"
|
|
46
76
|
def content_type
|
|
47
77
|
@content_type ||= ContentType.new(@headers["content-type"])
|
|
48
78
|
end
|
|
49
79
|
|
|
80
|
+
# returns whether the response has been fully fetched.
|
|
50
81
|
def finished?
|
|
51
82
|
@finished
|
|
52
83
|
end
|
|
53
84
|
|
|
85
|
+
# marks the response as finished, freezes the headers.
|
|
54
86
|
def finish!
|
|
55
87
|
@finished = true
|
|
56
88
|
@headers.freeze
|
|
57
89
|
end
|
|
58
90
|
|
|
91
|
+
# returns whether the response contains body payload.
|
|
59
92
|
def bodyless?
|
|
60
93
|
@request.verb == "HEAD" ||
|
|
61
|
-
|
|
94
|
+
@status < 200 || # informational response
|
|
95
|
+
@status == 204 ||
|
|
96
|
+
@status == 205 ||
|
|
97
|
+
@status == 304 || begin
|
|
98
|
+
content_length = @headers["content-length"]
|
|
99
|
+
return false if content_length.nil?
|
|
100
|
+
|
|
101
|
+
content_length == "0"
|
|
102
|
+
end
|
|
62
103
|
end
|
|
63
104
|
|
|
64
105
|
def complete?
|
|
@@ -75,32 +116,53 @@ module HTTPX
|
|
|
75
116
|
end
|
|
76
117
|
# :nocov:
|
|
77
118
|
|
|
119
|
+
# returns an instance of HTTPX::HTTPError if the response has a 4xx or 5xx
|
|
120
|
+
# status code, or nothing.
|
|
121
|
+
#
|
|
122
|
+
# ok_response.error #=> nil
|
|
123
|
+
# not_found_response.error #=> HTTPX::HTTPError instance, status 404
|
|
78
124
|
def error
|
|
79
125
|
return if @status < 400
|
|
80
126
|
|
|
81
127
|
HTTPError.new(self)
|
|
82
128
|
end
|
|
83
129
|
|
|
130
|
+
# it raises the exception returned by +error+, or itself otherwise.
|
|
131
|
+
#
|
|
132
|
+
# ok_response.raise_for_status #=> ok_response
|
|
133
|
+
# not_found_response.raise_for_status #=> raises HTTPX::HTTPError exception
|
|
84
134
|
def raise_for_status
|
|
85
135
|
return self unless (err = error)
|
|
86
136
|
|
|
87
137
|
raise err
|
|
88
138
|
end
|
|
89
139
|
|
|
140
|
+
# decodes the response payload into a ruby object **if** the payload is valid json.
|
|
141
|
+
#
|
|
142
|
+
# response.json #≈> { "foo" => "bar" } for "{\"foo\":\"bar\"}" payload
|
|
143
|
+
# response.json(symbolize_names: true) #≈> { foo: "bar" } for "{\"foo\":\"bar\"}" payload
|
|
90
144
|
def json(*args)
|
|
91
145
|
decode(Transcoder::JSON, *args)
|
|
92
146
|
end
|
|
93
147
|
|
|
148
|
+
# decodes the response payload into a ruby object **if** the payload is valid
|
|
149
|
+
# "application/x-www-urlencoded" or "multipart/form-data".
|
|
94
150
|
def form
|
|
95
151
|
decode(Transcoder::Form)
|
|
96
152
|
end
|
|
97
153
|
|
|
154
|
+
# decodes the response payload into a Nokogiri::XML::Node object **if** the payload is valid
|
|
155
|
+
# "application/xml" (requires the "nokogiri" gem).
|
|
98
156
|
def xml
|
|
99
157
|
decode(Transcoder::Xml)
|
|
100
158
|
end
|
|
101
159
|
|
|
102
160
|
private
|
|
103
161
|
|
|
162
|
+
# decodes the response payload using the given +transcoder+, which implements the decoding logic.
|
|
163
|
+
#
|
|
164
|
+
# +transcoder+ must implement the internal transcoder API, i.e. respond to <tt>decode(HTTPX::Response response)</tt>,
|
|
165
|
+
# which returns a decoder which responds to <tt>call(HTTPX::Response response, **kwargs)</tt>
|
|
104
166
|
def decode(transcoder, *args)
|
|
105
167
|
# TODO: check if content-type is a valid format, i.e. "application/json" for json parsing
|
|
106
168
|
|
|
@@ -112,20 +174,9 @@ module HTTPX
|
|
|
112
174
|
|
|
113
175
|
decoder.call(self, *args)
|
|
114
176
|
end
|
|
115
|
-
|
|
116
|
-
def no_data?
|
|
117
|
-
@status < 200 || # informational response
|
|
118
|
-
@status == 204 ||
|
|
119
|
-
@status == 205 ||
|
|
120
|
-
@status == 304 || begin
|
|
121
|
-
content_length = @headers["content-length"]
|
|
122
|
-
return false if content_length.nil?
|
|
123
|
-
|
|
124
|
-
content_length == "0"
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
177
|
end
|
|
128
178
|
|
|
179
|
+
# Helper class which decodes the HTTP "content-type" header.
|
|
129
180
|
class ContentType
|
|
130
181
|
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
|
|
131
182
|
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
|
|
@@ -134,6 +185,9 @@ module HTTPX
|
|
|
134
185
|
@header_value = header_value
|
|
135
186
|
end
|
|
136
187
|
|
|
188
|
+
# returns the mime type declared in the header.
|
|
189
|
+
#
|
|
190
|
+
# ContentType.new("application/json; charset=utf-8").mime_type #=> "application/json"
|
|
137
191
|
def mime_type
|
|
138
192
|
return @mime_type if defined?(@mime_type)
|
|
139
193
|
|
|
@@ -141,6 +195,10 @@ module HTTPX
|
|
|
141
195
|
m && @mime_type = m.strip.downcase
|
|
142
196
|
end
|
|
143
197
|
|
|
198
|
+
# returns the charset declared in the header.
|
|
199
|
+
#
|
|
200
|
+
# ContentType.new("application/json; charset=utf-8").charset #=> "utf-8"
|
|
201
|
+
# ContentType.new("text/plain").charset #=> nil
|
|
144
202
|
def charset
|
|
145
203
|
return @charset if defined?(@charset)
|
|
146
204
|
|
|
@@ -149,14 +207,31 @@ module HTTPX
|
|
|
149
207
|
end
|
|
150
208
|
end
|
|
151
209
|
|
|
210
|
+
# Wraps an error which has happened while processing an HTTP Request. It has partial
|
|
211
|
+
# public API parity with HTTPX::Response, so users should rely on it to infer whether
|
|
212
|
+
# the returned response is one or the other.
|
|
213
|
+
#
|
|
214
|
+
# response = HTTPX.get("https://some-domain/path") #=> response is HTTPX::Response or HTTPX::ErrorResponse
|
|
215
|
+
# response.raise_for_status #=> raises if it wraps an error
|
|
152
216
|
class ErrorResponse
|
|
153
217
|
include Loggable
|
|
154
218
|
extend Forwardable
|
|
155
219
|
|
|
156
|
-
|
|
220
|
+
# the corresponding HTTPX::Request instance.
|
|
221
|
+
attr_reader :request
|
|
157
222
|
|
|
223
|
+
# the HTTPX::Response instance, when there is one (i.e. error happens fetching the response).
|
|
224
|
+
attr_reader :response
|
|
225
|
+
|
|
226
|
+
# the wrapped exception.
|
|
227
|
+
attr_reader :error
|
|
228
|
+
|
|
229
|
+
# the request uri
|
|
158
230
|
def_delegator :@request, :uri
|
|
159
231
|
|
|
232
|
+
# the IP address of the peer server.
|
|
233
|
+
def_delegator :@request, :peer_address
|
|
234
|
+
|
|
160
235
|
def initialize(request, error, options)
|
|
161
236
|
@request = request
|
|
162
237
|
@response = request.response if request.response.is_a?(Response)
|
|
@@ -165,18 +240,22 @@ module HTTPX
|
|
|
165
240
|
log_exception(@error)
|
|
166
241
|
end
|
|
167
242
|
|
|
243
|
+
# returns the exception full message.
|
|
168
244
|
def to_s
|
|
169
245
|
@error.full_message(highlight: false)
|
|
170
246
|
end
|
|
171
247
|
|
|
248
|
+
# closes the error resources.
|
|
172
249
|
def close
|
|
173
|
-
@response.close if @response.respond_to?(:close)
|
|
250
|
+
@response.close if @response && @response.respond_to?(:close)
|
|
174
251
|
end
|
|
175
252
|
|
|
253
|
+
# always true for error responses.
|
|
176
254
|
def finished?
|
|
177
255
|
true
|
|
178
256
|
end
|
|
179
257
|
|
|
258
|
+
# raises the wrapped exception.
|
|
180
259
|
def raise_for_status
|
|
181
260
|
raise @error
|
|
182
261
|
end
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -73,7 +73,7 @@ class HTTPX::Selector
|
|
|
73
73
|
readers, writers = IO.select(r, w, nil, interval)
|
|
74
74
|
|
|
75
75
|
if readers.nil? && writers.nil? && interval
|
|
76
|
-
[*r, *w].each { |io| io.
|
|
76
|
+
[*r, *w].each { |io| io.handle_socket_timeout(interval) }
|
|
77
77
|
return
|
|
78
78
|
end
|
|
79
79
|
rescue IOError, SystemCallError
|
|
@@ -110,7 +110,7 @@ class HTTPX::Selector
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
unless result || interval.nil?
|
|
113
|
-
io.
|
|
113
|
+
io.handle_socket_timeout(interval)
|
|
114
114
|
return
|
|
115
115
|
end
|
|
116
116
|
# raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
|
data/lib/httpx/session.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
|
+
# Class implementing the APIs being used publicly.
|
|
5
|
+
#
|
|
6
|
+
# HTTPX.get(..) #=> delegating to an internal HTTPX::Session object.
|
|
7
|
+
# HTTPX.plugin(..).get(..) #=> creating an intermediate HTTPX::Session with plugin, then sending the GET request
|
|
4
8
|
class Session
|
|
5
9
|
include Loggable
|
|
6
10
|
include Chainable
|
|
@@ -8,6 +12,10 @@ module HTTPX
|
|
|
8
12
|
|
|
9
13
|
EMPTY_HASH = {}.freeze
|
|
10
14
|
|
|
15
|
+
# initializes the session with a set of +options+, which will be shared by all
|
|
16
|
+
# requests sent from it.
|
|
17
|
+
#
|
|
18
|
+
# When pass a block, it'll yield itself to it, then closes after the block is evaluated.
|
|
11
19
|
def initialize(options = EMPTY_HASH, &blk)
|
|
12
20
|
@options = self.class.default_options.merge(options)
|
|
13
21
|
@responses = {}
|
|
@@ -15,6 +23,11 @@ module HTTPX
|
|
|
15
23
|
wrap(&blk) if blk
|
|
16
24
|
end
|
|
17
25
|
|
|
26
|
+
# Yields itself the block, then closes it after the block is evaluated.
|
|
27
|
+
#
|
|
28
|
+
# session.wrap do |http|
|
|
29
|
+
# http.get("https://wikipedia.com")
|
|
30
|
+
# end # wikipedia connection closes here
|
|
18
31
|
def wrap
|
|
19
32
|
begin
|
|
20
33
|
prev_persistent = @persistent
|
|
@@ -26,10 +39,31 @@ module HTTPX
|
|
|
26
39
|
end
|
|
27
40
|
end
|
|
28
41
|
|
|
42
|
+
# closes all the active connections from the session
|
|
29
43
|
def close(*args)
|
|
30
44
|
pool.close(*args)
|
|
31
45
|
end
|
|
32
46
|
|
|
47
|
+
# performs one, or multple requests; it accepts:
|
|
48
|
+
#
|
|
49
|
+
# 1. one or multiple HTTPX::Request objects;
|
|
50
|
+
# 2. an HTTP verb, then a sequence of URIs or URI/options tuples;
|
|
51
|
+
# 3. one or multiple HTTP verb / uri / (optional) options tuples;
|
|
52
|
+
#
|
|
53
|
+
# when present, the set of +options+ kwargs is applied to all of the
|
|
54
|
+
# sent requests.
|
|
55
|
+
#
|
|
56
|
+
# respectively returns a single HTTPX::Response response, or all of them in an Array, in the same order.
|
|
57
|
+
#
|
|
58
|
+
# resp1 = session.request(req1)
|
|
59
|
+
# resp1, resp2 = session.request(req1, req2)
|
|
60
|
+
# resp1 = session.request("GET", "https://server.org/a")
|
|
61
|
+
# resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"])
|
|
62
|
+
# resp1, resp2 = session.request(["GET", "https://server.org/a"], ["GET", "https://server.org/b"])
|
|
63
|
+
# resp1 = session.request("POST", "https://server.org/a", form: { "foo" => "bar" })
|
|
64
|
+
# resp1, resp2 = session.request(["POST", "https://server.org/a", form: { "foo" => "bar" }], ["GET", "https://server.org/b"])
|
|
65
|
+
# resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"], headers: { "x-api-token" => "TOKEN" })
|
|
66
|
+
#
|
|
33
67
|
def request(*args, **options)
|
|
34
68
|
raise ArgumentError, "must perform at least one request" if args.empty?
|
|
35
69
|
|
|
@@ -40,10 +74,17 @@ module HTTPX
|
|
|
40
74
|
responses
|
|
41
75
|
end
|
|
42
76
|
|
|
77
|
+
# returns a HTTP::Request instance built from the HTTP +verb+, the request +uri+, and
|
|
78
|
+
# the optional set of request-specific +options+. This request **must** be sent through
|
|
79
|
+
# the same session it was built from.
|
|
80
|
+
#
|
|
81
|
+
# req = session.build_request("GET", "https://server.com")
|
|
82
|
+
# resp = session.request(req)
|
|
43
83
|
def build_request(verb, uri, options = EMPTY_HASH)
|
|
44
84
|
rklass = @options.request_class
|
|
45
85
|
options = @options.merge(options) unless options.is_a?(Options)
|
|
46
|
-
request = rklass.new(verb, uri, options
|
|
86
|
+
request = rklass.new(verb, uri, options)
|
|
87
|
+
request.persistent = @persistent
|
|
47
88
|
request.on(:response, &method(:on_response).curry(2)[request])
|
|
48
89
|
request.on(:promise, &method(:on_promise))
|
|
49
90
|
|
|
@@ -76,23 +117,29 @@ module HTTPX
|
|
|
76
117
|
|
|
77
118
|
private
|
|
78
119
|
|
|
120
|
+
# returns the HTTPX::Pool object which manages the networking required to
|
|
121
|
+
# perform requests.
|
|
79
122
|
def pool
|
|
80
123
|
Thread.current[:httpx_connection_pool] ||= Pool.new
|
|
81
124
|
end
|
|
82
125
|
|
|
126
|
+
# callback executed when a response for a given request has been received.
|
|
83
127
|
def on_response(request, response)
|
|
84
128
|
@responses[request] = response
|
|
85
129
|
end
|
|
86
130
|
|
|
131
|
+
# callback executed when an HTTP/2 promise frame has been received.
|
|
87
132
|
def on_promise(_, stream)
|
|
88
133
|
log(level: 2) { "#{stream.id}: refusing stream!" }
|
|
89
134
|
stream.refuse
|
|
90
135
|
end
|
|
91
136
|
|
|
137
|
+
# returns the corresponding HTTP::Response to the given +request+ if it has been received.
|
|
92
138
|
def fetch_response(request, _, _)
|
|
93
139
|
@responses.delete(request)
|
|
94
140
|
end
|
|
95
141
|
|
|
142
|
+
# returns the HTTPX::Connection through which the +request+ should be sent through.
|
|
96
143
|
def find_connection(request, connections, options)
|
|
97
144
|
uri = request.uri
|
|
98
145
|
|
|
@@ -104,6 +151,8 @@ module HTTPX
|
|
|
104
151
|
connection
|
|
105
152
|
end
|
|
106
153
|
|
|
154
|
+
# sets the callbacks on the +connection+ required to process certain specific
|
|
155
|
+
# connection lifecycle events which deal with request rerouting.
|
|
107
156
|
def set_connection_callbacks(connection, connections, options)
|
|
108
157
|
connection.only(:misdirected) do |misdirected_request|
|
|
109
158
|
other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
|
|
@@ -131,6 +180,7 @@ module HTTPX
|
|
|
131
180
|
end
|
|
132
181
|
end
|
|
133
182
|
|
|
183
|
+
# returns an HTTPX::Connection for the negotiated Alternative Service (or none).
|
|
134
184
|
def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
|
|
135
185
|
# do not allow security downgrades on altsvc negotiation
|
|
136
186
|
return if existing_connection.origin.scheme == "https" && alt_origin.scheme != "https"
|
|
@@ -166,6 +216,7 @@ module HTTPX
|
|
|
166
216
|
nil
|
|
167
217
|
end
|
|
168
218
|
|
|
219
|
+
# returns a set of HTTPX::Request objects built from the given +args+ and +options+.
|
|
169
220
|
def build_requests(*args, options)
|
|
170
221
|
request_options = @options.merge(options)
|
|
171
222
|
|
|
@@ -189,6 +240,7 @@ module HTTPX
|
|
|
189
240
|
requests
|
|
190
241
|
end
|
|
191
242
|
|
|
243
|
+
# returns a new HTTPX::Connection object for the given +uri+ and set of +options+.
|
|
192
244
|
def build_connection(uri, options)
|
|
193
245
|
type = options.transport || begin
|
|
194
246
|
case uri.scheme
|
|
@@ -208,7 +260,9 @@ module HTTPX
|
|
|
208
260
|
connection.on(:open) do
|
|
209
261
|
emit(:connection_opened, connection.origin, connection.io.socket)
|
|
210
262
|
# only run close callback if it opened
|
|
211
|
-
|
|
263
|
+
end
|
|
264
|
+
connection.on(:close) do
|
|
265
|
+
emit(:connection_closed, connection.origin, connection.io.socket) if connection.used?
|
|
212
266
|
end
|
|
213
267
|
catch(:coalesced) do
|
|
214
268
|
pool.init_connection(connection, options)
|
|
@@ -216,11 +270,13 @@ module HTTPX
|
|
|
216
270
|
end
|
|
217
271
|
end
|
|
218
272
|
|
|
273
|
+
# sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
|
|
219
274
|
def send_requests(*requests)
|
|
220
275
|
connections = _send_requests(requests)
|
|
221
276
|
receive_requests(requests, connections)
|
|
222
277
|
end
|
|
223
278
|
|
|
279
|
+
# sends an array of HTTPX::Request objects
|
|
224
280
|
def _send_requests(requests)
|
|
225
281
|
connections = []
|
|
226
282
|
|
|
@@ -237,6 +293,7 @@ module HTTPX
|
|
|
237
293
|
connections
|
|
238
294
|
end
|
|
239
295
|
|
|
296
|
+
# returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
|
|
240
297
|
def receive_requests(requests, connections)
|
|
241
298
|
# @type var responses: Array[response]
|
|
242
299
|
responses = []
|
|
@@ -290,6 +347,11 @@ module HTTPX
|
|
|
290
347
|
klass.instance_variable_set(:@callbacks, @callbacks.dup)
|
|
291
348
|
end
|
|
292
349
|
|
|
350
|
+
# returns a new HTTPX::Session instance, with the plugin pointed by +pl+ loaded.
|
|
351
|
+
#
|
|
352
|
+
# session_with_retries = session.plugin(:retries)
|
|
353
|
+
# session_with_custom = session.plugin(CustomPlugin)
|
|
354
|
+
#
|
|
293
355
|
def plugin(pl, options = nil, &block)
|
|
294
356
|
# raise Error, "Cannot add a plugin to a frozen config" if frozen?
|
|
295
357
|
pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
|
data/lib/httpx/timers.rb
CHANGED
|
@@ -6,20 +6,27 @@ module HTTPX
|
|
|
6
6
|
@intervals = []
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def after(interval_in_secs, &blk)
|
|
9
|
+
def after(interval_in_secs, cb = nil, &blk)
|
|
10
10
|
return unless interval_in_secs
|
|
11
11
|
|
|
12
|
+
callback = cb || blk
|
|
13
|
+
|
|
12
14
|
# I'm assuming here that most requests will have the same
|
|
13
15
|
# request timeout, as in most cases they share common set of
|
|
14
16
|
# options. A user setting different request timeouts for 100s of
|
|
15
17
|
# requests will already have a hard time dealing with that.
|
|
16
|
-
unless (interval = @intervals.find { |t| t == interval_in_secs })
|
|
18
|
+
unless (interval = @intervals.find { |t| t.interval == interval_in_secs })
|
|
17
19
|
interval = Interval.new(interval_in_secs)
|
|
20
|
+
interval.on_empty { @intervals.delete(interval) }
|
|
18
21
|
@intervals << interval
|
|
19
22
|
@intervals.sort!
|
|
20
23
|
end
|
|
21
24
|
|
|
22
|
-
interval <<
|
|
25
|
+
interval << callback
|
|
26
|
+
|
|
27
|
+
@next_interval_at = nil
|
|
28
|
+
|
|
29
|
+
interval
|
|
23
30
|
end
|
|
24
31
|
|
|
25
32
|
def wait_interval
|
|
@@ -41,11 +48,6 @@ module HTTPX
|
|
|
41
48
|
@next_interval_at = nil if @intervals.empty?
|
|
42
49
|
end
|
|
43
50
|
|
|
44
|
-
def cancel
|
|
45
|
-
@next_interval_at = nil
|
|
46
|
-
@intervals.clear
|
|
47
|
-
end
|
|
48
|
-
|
|
49
51
|
class Interval
|
|
50
52
|
include Comparable
|
|
51
53
|
|
|
@@ -54,6 +56,11 @@ module HTTPX
|
|
|
54
56
|
def initialize(interval)
|
|
55
57
|
@interval = interval
|
|
56
58
|
@callbacks = []
|
|
59
|
+
@on_empty = nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def on_empty(&blk)
|
|
63
|
+
@on_empty = blk
|
|
57
64
|
end
|
|
58
65
|
|
|
59
66
|
def <=>(other)
|
|
@@ -74,6 +81,26 @@ module HTTPX
|
|
|
74
81
|
@callbacks << callback
|
|
75
82
|
end
|
|
76
83
|
|
|
84
|
+
if RUBY_ENGINE == "jruby" && JRUBY_VERSION < "9.4.5.0"
|
|
85
|
+
# https://github.com/jruby/jruby/issues/7976
|
|
86
|
+
def delete(callback)
|
|
87
|
+
@callbacks.delete(callback)
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
def delete(callback)
|
|
91
|
+
@callbacks.delete(callback)
|
|
92
|
+
@on_empty.call if @callbacks.empty?
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def no_callbacks?
|
|
97
|
+
@callbacks.empty?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def elapsed?
|
|
101
|
+
@interval <= 0
|
|
102
|
+
end
|
|
103
|
+
|
|
77
104
|
def elapse(elapsed)
|
|
78
105
|
@interval -= elapsed
|
|
79
106
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module HTTPX
|
|
2
|
+
module Transcoder
|
|
3
|
+
class Inflater
|
|
4
|
+
def initialize(bytesize)
|
|
5
|
+
@bytesize = bytesize
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(chunk)
|
|
9
|
+
buffer = @inflater.inflate(chunk)
|
|
10
|
+
@bytesize -= chunk.bytesize
|
|
11
|
+
if @bytesize <= 0
|
|
12
|
+
buffer << @inflater.finish
|
|
13
|
+
@inflater.close
|
|
14
|
+
end
|
|
15
|
+
buffer
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/httpx/version.rb
CHANGED
data/sig/connection/http1.rbs
CHANGED
|
@@ -53,19 +53,19 @@ module HTTPX
|
|
|
53
53
|
|
|
54
54
|
def ping: () -> void
|
|
55
55
|
|
|
56
|
-
def timeout: () -> Numeric
|
|
56
|
+
def timeout: () -> Numeric?
|
|
57
57
|
|
|
58
58
|
private
|
|
59
59
|
|
|
60
60
|
def initialize: (Buffer, options) -> untyped
|
|
61
61
|
|
|
62
|
-
def manage_connection: (Response) -> void
|
|
62
|
+
def manage_connection: (Request request, Response response) -> void
|
|
63
63
|
|
|
64
64
|
def disable: () -> void
|
|
65
65
|
|
|
66
66
|
def disable_pipelining: () -> void
|
|
67
67
|
|
|
68
|
-
def set_protocol_headers: (Request) -> _Each[[String, String]]
|
|
68
|
+
def set_protocol_headers: (Request request) -> _Each[[String, String]]
|
|
69
69
|
|
|
70
70
|
def handle: (Request request) -> void
|
|
71
71
|
|
data/sig/connection/http2.rbs
CHANGED
data/sig/connection.rbs
CHANGED
|
@@ -40,6 +40,7 @@ module HTTPX
|
|
|
40
40
|
@parser: HTTP1 | HTTP2 | _Parser
|
|
41
41
|
@connected_at: Float
|
|
42
42
|
@response_received_at: Float
|
|
43
|
+
@intervals: Array[Timers::Interval]
|
|
43
44
|
|
|
44
45
|
def addresses: () -> Array[ipaddr]?
|
|
45
46
|
|
|
@@ -89,7 +90,7 @@ module HTTPX
|
|
|
89
90
|
|
|
90
91
|
def open?: () -> bool
|
|
91
92
|
|
|
92
|
-
def
|
|
93
|
+
def handle_socket_timeout: (Numeric interval) -> void
|
|
93
94
|
|
|
94
95
|
private
|
|
95
96
|
|
|
@@ -129,6 +130,8 @@ module HTTPX
|
|
|
129
130
|
|
|
130
131
|
def read_timeout_callback: (Request request, Numeric read_timeout, ?singleton(RequestTimeoutError) error_type) -> void
|
|
131
132
|
|
|
133
|
+
def set_request_timeout: (Request request, Numeric timeout, Symbol start_event, Symbol | Array[Symbol] finish_events) { () -> void } -> void
|
|
134
|
+
|
|
132
135
|
def self.parser_type: (String protocol) -> (singleton(HTTP1) | singleton(HTTP2))
|
|
133
136
|
end
|
|
134
137
|
end
|
data/sig/io/tcp.rbs
CHANGED
data/sig/options.rbs
CHANGED
|
@@ -12,7 +12,7 @@ module HTTPX
|
|
|
12
12
|
DEFAULT_OPTIONS: Hash[Symbol, untyped]
|
|
13
13
|
|
|
14
14
|
type timeout_type = :connect_timeout | :settings_timeout | :operation_timeout | :keep_alive_timeout | :read_timeout | :write_timeout | :request_timeout
|
|
15
|
-
type timeout = Hash[timeout_type, Numeric]
|
|
15
|
+
type timeout = Hash[timeout_type, Numeric?]
|
|
16
16
|
|
|
17
17
|
def self.new: (?options) -> instance
|
|
18
18
|
|
|
@@ -32,7 +32,7 @@ module HTTPX
|
|
|
32
32
|
attr_reader max_concurrent_requests: Integer?
|
|
33
33
|
|
|
34
34
|
# max_requests
|
|
35
|
-
attr_reader max_requests:
|
|
35
|
+
attr_reader max_requests: Numeric?
|
|
36
36
|
|
|
37
37
|
# window_size
|
|
38
38
|
attr_reader window_size: Integer
|
data/sig/pool.rbs
CHANGED
|
@@ -45,7 +45,7 @@ module HTTPX
|
|
|
45
45
|
|
|
46
46
|
def coalesce_connections: (Connection coalescable, Connection coalescing) -> void
|
|
47
47
|
|
|
48
|
-
def next_timeout: () ->
|
|
48
|
+
def next_timeout: () -> Numeric?
|
|
49
49
|
|
|
50
50
|
def find_resolver_for: (Connection) { (Resolver::Resolver resolver) -> void } -> resolver_manager
|
|
51
51
|
end
|