httpx 0.10.2 → 0.12.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/README.md +13 -5
- data/doc/release_notes/0_11_0.md +76 -0
- data/doc/release_notes/0_11_1.md +5 -0
- data/doc/release_notes/0_11_2.md +5 -0
- data/doc/release_notes/0_11_3.md +5 -0
- data/doc/release_notes/0_12_0.md +55 -0
- data/lib/httpx.rb +2 -1
- data/lib/httpx/adapters/datadog.rb +205 -0
- data/lib/httpx/adapters/faraday.rb +4 -8
- data/lib/httpx/adapters/webmock.rb +123 -0
- data/lib/httpx/altsvc.rb +1 -0
- data/lib/httpx/chainable.rb +1 -1
- data/lib/httpx/connection.rb +63 -15
- data/lib/httpx/connection/http1.rb +16 -5
- data/lib/httpx/connection/http2.rb +36 -29
- data/lib/httpx/domain_name.rb +1 -3
- data/lib/httpx/errors.rb +2 -0
- data/lib/httpx/headers.rb +1 -0
- data/lib/httpx/io.rb +16 -3
- data/lib/httpx/io/ssl.rb +7 -13
- data/lib/httpx/io/tcp.rb +9 -8
- data/lib/httpx/io/tls.rb +218 -0
- data/lib/httpx/io/tls/box.rb +365 -0
- data/lib/httpx/io/tls/context.rb +199 -0
- data/lib/httpx/io/tls/ffi.rb +390 -0
- data/lib/httpx/io/udp.rb +4 -3
- data/lib/httpx/parser/http1.rb +4 -4
- data/lib/httpx/plugins/aws_sdk_authentication.rb +81 -0
- data/lib/httpx/plugins/aws_sigv4.rb +218 -0
- data/lib/httpx/plugins/compression.rb +1 -1
- data/lib/httpx/plugins/compression/deflate.rb +2 -5
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +1 -1
- data/lib/httpx/plugins/expect.rb +33 -8
- data/lib/httpx/plugins/internal_telemetry.rb +93 -0
- data/lib/httpx/plugins/multipart.rb +42 -35
- data/lib/httpx/plugins/multipart/encoder.rb +110 -0
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +64 -0
- data/lib/httpx/plugins/multipart/part.rb +34 -0
- data/lib/httpx/plugins/proxy.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +1 -1
- data/lib/httpx/plugins/proxy/socks4.rb +8 -0
- data/lib/httpx/plugins/proxy/socks5.rb +11 -2
- data/lib/httpx/plugins/push_promise.rb +5 -4
- data/lib/httpx/plugins/retries.rb +1 -1
- data/lib/httpx/plugins/stream.rb +3 -5
- data/lib/httpx/pool.rb +0 -1
- data/lib/httpx/registry.rb +1 -7
- data/lib/httpx/request.rb +32 -12
- data/lib/httpx/resolver.rb +7 -4
- data/lib/httpx/resolver/https.rb +7 -13
- data/lib/httpx/resolver/native.rb +10 -6
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/response.rb +9 -2
- data/lib/httpx/selector.rb +6 -0
- data/lib/httpx/session.rb +40 -20
- data/lib/httpx/transcoder.rb +6 -4
- data/lib/httpx/transcoder/body.rb +3 -5
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http1.rbs +2 -2
- data/sig/connection/http2.rbs +8 -7
- data/sig/headers.rbs +3 -0
- data/sig/plugins/aws_sdk_authentication.rbs +17 -0
- data/sig/plugins/aws_sigv4.rbs +65 -0
- data/sig/plugins/multipart.rbs +27 -4
- data/sig/plugins/push_promise.rbs +1 -1
- data/sig/request.rbs +1 -1
- data/sig/resolver/https.rbs +2 -0
- data/sig/response.rbs +1 -1
- data/sig/session.rbs +1 -1
- data/sig/transcoder.rbs +2 -2
- data/sig/transcoder/body.rbs +2 -0
- data/sig/transcoder/form.rbs +7 -1
- data/sig/transcoder/json.rbs +3 -1
- metadata +50 -47
- data/sig/missing.rbs +0 -12
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WebMock
|
4
|
+
module HttpLibAdapters
|
5
|
+
if RUBY_VERSION < "2.5"
|
6
|
+
require "webrick/httpstatus"
|
7
|
+
HTTP_REASONS = WEBrick::HTTPStatus::StatusMessage
|
8
|
+
else
|
9
|
+
require "net/http/status"
|
10
|
+
HTTP_REASONS = Net::HTTP::STATUS_CODES
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# HTTPX plugin for webmock.
|
15
|
+
#
|
16
|
+
# Requests are "hijacked" at the session, before they're distributed to a connection.
|
17
|
+
#
|
18
|
+
module Plugin
|
19
|
+
module InstanceMethods
|
20
|
+
private
|
21
|
+
|
22
|
+
def send_requests(*requests, options)
|
23
|
+
request_signatures = requests.map do |request|
|
24
|
+
request_signature = _build_webmock_request_signature(request)
|
25
|
+
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
26
|
+
request_signature
|
27
|
+
end
|
28
|
+
|
29
|
+
responses = request_signatures.map do |request_signature|
|
30
|
+
WebMock::StubRegistry.instance.response_for_request(request_signature)
|
31
|
+
end
|
32
|
+
|
33
|
+
real_requests = {}
|
34
|
+
|
35
|
+
requests.each_with_index.each_with_object([request_signatures, responses]) do |(request, idx), (sig_reqs, mock_responses)|
|
36
|
+
if (webmock_response = mock_responses[idx])
|
37
|
+
mock_responses[idx] = _build_from_webmock_response(request, webmock_response)
|
38
|
+
WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, sig_reqs[idx], webmock_response)
|
39
|
+
log { "mocking #{request.uri} with #{mock_responses[idx].inspect}" }
|
40
|
+
elsif WebMock.net_connect_allowed?(sig_reqs[idx].uri)
|
41
|
+
log { "performing #{request.uri}" }
|
42
|
+
real_requests[request] = idx
|
43
|
+
else
|
44
|
+
raise WebMock::NetConnectNotAllowedError, sig_reqs[idx]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
unless real_requests.empty?
|
49
|
+
reqs = real_requests.keys
|
50
|
+
reqs.zip(super(*reqs, options)).each do |req, res|
|
51
|
+
idx = real_requests[req]
|
52
|
+
|
53
|
+
if WebMock::CallbackRegistry.any_callbacks?
|
54
|
+
webmock_response = _build_webmock_response(req, res)
|
55
|
+
WebMock::CallbackRegistry.invoke_callbacks(
|
56
|
+
{ lib: :httpx, real_request: true }, request_signatures[idx],
|
57
|
+
webmock_response
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
responses[idx] = res
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
responses
|
66
|
+
end
|
67
|
+
|
68
|
+
def _build_webmock_request_signature(request)
|
69
|
+
uri = WebMock::Util::URI.heuristic_parse(request.uri)
|
70
|
+
uri.path = uri.normalized_path.gsub("[^:]//", "/")
|
71
|
+
|
72
|
+
WebMock::RequestSignature.new(
|
73
|
+
request.verb,
|
74
|
+
uri.to_s,
|
75
|
+
body: request.body.each.to_a.join,
|
76
|
+
headers: request.headers.to_h
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def _build_webmock_response(_request, response)
|
81
|
+
webmock_response = WebMock::Response.new
|
82
|
+
webmock_response.status = [response.status, HTTP_REASONS[response.status]]
|
83
|
+
webmock_response.body = response.body.to_s
|
84
|
+
webmock_response.headers = response.headers.to_h
|
85
|
+
webmock_response
|
86
|
+
end
|
87
|
+
|
88
|
+
def _build_from_webmock_response(request, webmock_response)
|
89
|
+
return ErrorResponse.new(request, webmock_response.exception, request.options) if webmock_response.exception
|
90
|
+
|
91
|
+
response = request.options.response_class.new(request,
|
92
|
+
webmock_response.status[0],
|
93
|
+
"2.0",
|
94
|
+
webmock_response.headers)
|
95
|
+
response << webmock_response.body.dup
|
96
|
+
response
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class HttpxAdapter < HttpLibAdapter
|
102
|
+
adapter_for :httpx
|
103
|
+
|
104
|
+
class << self
|
105
|
+
def enable!
|
106
|
+
@original_session = ::HTTPX::Session
|
107
|
+
|
108
|
+
webmock_session = ::HTTPX.plugin(Plugin)
|
109
|
+
|
110
|
+
::HTTPX.send(:remove_const, :Session)
|
111
|
+
::HTTPX.send(:const_set, :Session, webmock_session.class)
|
112
|
+
end
|
113
|
+
|
114
|
+
def disable!
|
115
|
+
return unless @original_session
|
116
|
+
|
117
|
+
HTTPX.send(:remove_const, :Session)
|
118
|
+
HTTPX.send(:const_set, :Session, @original_session)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/httpx/altsvc.rb
CHANGED
data/lib/httpx/chainable.rb
CHANGED
data/lib/httpx/connection.rb
CHANGED
@@ -170,7 +170,7 @@ module HTTPX
|
|
170
170
|
end
|
171
171
|
|
172
172
|
# if the write buffer is full, we drain it
|
173
|
-
return :w
|
173
|
+
return :w unless @write_buffer.empty?
|
174
174
|
|
175
175
|
return @parser.interests if @parser
|
176
176
|
|
@@ -251,11 +251,18 @@ module HTTPX
|
|
251
251
|
|
252
252
|
def consume
|
253
253
|
catch(:called) do
|
254
|
+
epiped = false
|
254
255
|
loop do
|
255
256
|
parser.consume
|
256
257
|
|
257
|
-
# we exit if there's no more
|
258
|
-
|
258
|
+
# we exit if there's no more requests to process
|
259
|
+
#
|
260
|
+
# this condition takes into account:
|
261
|
+
#
|
262
|
+
# * the number of inflight requests
|
263
|
+
# * the number of pending requests
|
264
|
+
# * whether the write buffer has bytes (i.e. for close handshake)
|
265
|
+
if @pending.size.zero? && @inflight.zero? && @write_buffer.empty?
|
259
266
|
log(level: 3) { "NO MORE REQUESTS..." }
|
260
267
|
return
|
261
268
|
end
|
@@ -265,9 +272,17 @@ module HTTPX
|
|
265
272
|
read_drained = false
|
266
273
|
write_drained = nil
|
267
274
|
|
268
|
-
#
|
275
|
+
#
|
276
|
+
# tight read loop.
|
277
|
+
#
|
278
|
+
# read as much of the socket as possible.
|
279
|
+
#
|
280
|
+
# this tight loop reads all the data it can from the socket and pipes it to
|
281
|
+
# its parser.
|
282
|
+
#
|
269
283
|
loop do
|
270
284
|
siz = @io.read(@window_size, @read_buffer)
|
285
|
+
log(level: 3, color: :cyan) { "IO READ: #{siz} bytes..." }
|
271
286
|
unless siz
|
272
287
|
ex = EOFError.new("descriptor closed")
|
273
288
|
ex.set_backtrace(caller)
|
@@ -275,27 +290,53 @@ module HTTPX
|
|
275
290
|
return
|
276
291
|
end
|
277
292
|
|
293
|
+
# socket has been drained. mark and exit the read loop.
|
278
294
|
if siz.zero?
|
279
295
|
read_drained = @read_buffer.empty?
|
296
|
+
epiped = false
|
280
297
|
break
|
281
298
|
end
|
282
299
|
|
283
300
|
parser << @read_buffer.to_s
|
284
301
|
|
302
|
+
# continue reading if possible.
|
303
|
+
break if interests == :w && !epiped
|
304
|
+
|
305
|
+
# exit the read loop if connection is preparing to be closed
|
285
306
|
break if @state == :closing || @state == :closed
|
286
307
|
|
287
|
-
#
|
288
|
-
|
308
|
+
# exit #consume altogether if all outstanding requests have been dealt with
|
309
|
+
return if @pending.size.zero? && @inflight.zero?
|
310
|
+
end unless (interests == :w || @state == :closing) && !epiped
|
289
311
|
|
290
|
-
#
|
312
|
+
#
|
313
|
+
# tight write loop.
|
314
|
+
#
|
315
|
+
# flush as many bytes as the sockets allow.
|
316
|
+
#
|
291
317
|
loop do
|
318
|
+
# buffer has been drainned, mark and exit the write loop.
|
292
319
|
if @write_buffer.empty?
|
293
320
|
# we only mark as drained on the first loop
|
294
321
|
write_drained = write_drained.nil? && @inflight.positive?
|
322
|
+
|
295
323
|
break
|
296
324
|
end
|
297
325
|
|
298
|
-
|
326
|
+
begin
|
327
|
+
siz = @io.write(@write_buffer)
|
328
|
+
rescue Errno::EPIPE
|
329
|
+
# this can happen if we still have bytes in the buffer to send to the server, but
|
330
|
+
# the server wants to respond immediately with some message, or an error. An example is
|
331
|
+
# when one's uploading a big file to an unintended endpoint, and the server stops the
|
332
|
+
# consumption, and responds immediately with an authorization of even method not allowed error.
|
333
|
+
# at this point, we have to let the connection switch to read-mode.
|
334
|
+
log(level: 2) { "pipe broken, could not flush buffer..." }
|
335
|
+
epiped = true
|
336
|
+
read_drained = false
|
337
|
+
break
|
338
|
+
end
|
339
|
+
log(level: 3, color: :cyan) { "IO WRITE: #{siz} bytes..." }
|
299
340
|
unless siz
|
300
341
|
ex = EOFError.new("descriptor closed")
|
301
342
|
ex.set_backtrace(caller)
|
@@ -303,21 +344,28 @@ module HTTPX
|
|
303
344
|
return
|
304
345
|
end
|
305
346
|
|
347
|
+
# socket closed for writing. mark and exit the write loop.
|
306
348
|
if siz.zero?
|
307
349
|
write_drained = !@write_buffer.empty?
|
308
350
|
break
|
309
351
|
end
|
310
352
|
|
311
|
-
|
353
|
+
# exit write loop if marked to consume from peer, or is closing.
|
354
|
+
break if interests == :r || @state == :closing || @state == :closed
|
312
355
|
|
313
356
|
write_drained = false
|
314
|
-
end
|
357
|
+
end unless interests == :r
|
315
358
|
|
316
359
|
# return if socket is drained
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
360
|
+
next unless (interests != :r || read_drained) &&
|
361
|
+
(interests != :w || write_drained)
|
362
|
+
|
363
|
+
# gotta go back to the event loop. It happens when:
|
364
|
+
#
|
365
|
+
# * the socket is drained of bytes or it's not the interest of the conn to read;
|
366
|
+
# * theres nothing more to write, or it's not in the interest of the conn to write;
|
367
|
+
log(level: 3) { "(#{interests}): WAITING FOR EVENTS..." }
|
368
|
+
return
|
321
369
|
end
|
322
370
|
end
|
323
371
|
end
|
@@ -444,7 +492,7 @@ module HTTPX
|
|
444
492
|
throw(:jump_tick)
|
445
493
|
rescue Errno::ECONNREFUSED,
|
446
494
|
Errno::EADDRNOTAVAIL,
|
447
|
-
|
495
|
+
TLSError => e
|
448
496
|
# connect errors, exit gracefully
|
449
497
|
handle_error(e)
|
450
498
|
@state = :closed
|
@@ -69,14 +69,16 @@ module HTTPX
|
|
69
69
|
|
70
70
|
return if @requests.include?(request)
|
71
71
|
|
72
|
+
request.once(:headers, &method(:set_protocol_headers))
|
72
73
|
@requests << request
|
73
74
|
@pipelining = true if @requests.size > 1
|
74
75
|
end
|
75
76
|
|
76
77
|
def consume
|
77
|
-
requests_limit = [@
|
78
|
+
requests_limit = [@max_requests, @requests.size].min
|
79
|
+
concurrent_requests_limit = [@max_concurrent_requests, requests_limit].min
|
78
80
|
@requests.each_with_index do |request, idx|
|
79
|
-
break if idx >=
|
81
|
+
break if idx >= concurrent_requests_limit
|
80
82
|
next if request.state == :done
|
81
83
|
|
82
84
|
request.headers["connection"] ||= request.options.persistent || idx < requests_limit - 1 ? "keep-alive" : "close"
|
@@ -115,7 +117,7 @@ module HTTPX
|
|
115
117
|
response = @request.response
|
116
118
|
log(level: 2) { "trailer headers received" }
|
117
119
|
|
118
|
-
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") }
|
120
|
+
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v.join(", ")}" }.join("\n") }
|
119
121
|
response.merge_headers(h)
|
120
122
|
end
|
121
123
|
|
@@ -161,6 +163,16 @@ module HTTPX
|
|
161
163
|
end
|
162
164
|
|
163
165
|
def handle_error(ex)
|
166
|
+
if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request && @request.response &&
|
167
|
+
!@request.response.headers.key?("content-length") &&
|
168
|
+
!@request.response.headers.key?("transfer-encoding")
|
169
|
+
# if the response does not contain a content-length header, the server closing the
|
170
|
+
# connnection is the indicator of response consumed.
|
171
|
+
# https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.4
|
172
|
+
catch(:called) { on_complete }
|
173
|
+
return
|
174
|
+
end
|
175
|
+
|
164
176
|
if @pipelining
|
165
177
|
disable
|
166
178
|
else
|
@@ -240,7 +252,7 @@ module HTTPX
|
|
240
252
|
@pipelining = false
|
241
253
|
end
|
242
254
|
|
243
|
-
def
|
255
|
+
def set_protocol_headers(request)
|
244
256
|
request.headers["host"] ||= request.authority
|
245
257
|
request.headers["connection"] ||= request.options.persistent ? "keep-alive" : "close"
|
246
258
|
if !request.headers.key?("content-length") &&
|
@@ -254,7 +266,6 @@ module HTTPX
|
|
254
266
|
end
|
255
267
|
|
256
268
|
def handle(request)
|
257
|
-
set_request_headers(request)
|
258
269
|
catch(:buffer_full) do
|
259
270
|
request.transition(:headers)
|
260
271
|
join_headers(request) if request.state == :headers
|
@@ -42,11 +42,15 @@ module HTTPX
|
|
42
42
|
return @buffer.empty? ? :r : :rw
|
43
43
|
end
|
44
44
|
|
45
|
-
return :w
|
45
|
+
return :w if !@pending.empty? && can_buffer_more_requests?
|
46
46
|
|
47
47
|
return :w if @streams.each_key.any? { |r| r.interests == :w }
|
48
48
|
|
49
|
-
|
49
|
+
if @buffer.empty?
|
50
|
+
return if @streams.empty? && @pings.empty?
|
51
|
+
|
52
|
+
return :r
|
53
|
+
end
|
50
54
|
|
51
55
|
:rw
|
52
56
|
end
|
@@ -70,10 +74,14 @@ module HTTPX
|
|
70
74
|
@connection << data
|
71
75
|
end
|
72
76
|
|
77
|
+
def can_buffer_more_requests?
|
78
|
+
@handshake_completed &&
|
79
|
+
@streams.size < @max_concurrent_requests &&
|
80
|
+
@streams.size < @max_requests
|
81
|
+
end
|
82
|
+
|
73
83
|
def send(request)
|
74
|
-
|
75
|
-
@streams.size >= @max_concurrent_requests ||
|
76
|
-
@streams.size >= @max_requests
|
84
|
+
unless can_buffer_more_requests?
|
77
85
|
@pending << request
|
78
86
|
return
|
79
87
|
end
|
@@ -83,6 +91,7 @@ module HTTPX
|
|
83
91
|
@streams[request] = stream
|
84
92
|
@max_requests -= 1
|
85
93
|
end
|
94
|
+
request.once(:headers, &method(:set_protocol_headers))
|
86
95
|
handle(request, stream)
|
87
96
|
true
|
88
97
|
rescue HTTP2Next::Error::StreamLimitExceeded
|
@@ -126,8 +135,6 @@ module HTTPX
|
|
126
135
|
request.path
|
127
136
|
end
|
128
137
|
|
129
|
-
def set_request_headers(request); end
|
130
|
-
|
131
138
|
def handle(request, stream)
|
132
139
|
catch(:buffer_full) do
|
133
140
|
request.transition(:headers)
|
@@ -163,27 +170,27 @@ module HTTPX
|
|
163
170
|
public :reset
|
164
171
|
|
165
172
|
def handle_stream(stream, request)
|
166
|
-
stream.on(:close, &method(:on_stream_close).curry[stream, request])
|
173
|
+
stream.on(:close, &method(:on_stream_close).curry(3)[stream, request])
|
167
174
|
stream.on(:half_close) do
|
168
175
|
log(level: 2) { "#{stream.id}: waiting for response..." }
|
169
176
|
end
|
170
|
-
stream.on(:altsvc, &method(:on_altsvc).curry[request.origin])
|
171
|
-
stream.on(:headers, &method(:on_stream_headers).curry[stream, request])
|
172
|
-
stream.on(:data, &method(:on_stream_data).curry[stream, request])
|
177
|
+
stream.on(:altsvc, &method(:on_altsvc).curry(2)[request.origin])
|
178
|
+
stream.on(:headers, &method(:on_stream_headers).curry(3)[stream, request])
|
179
|
+
stream.on(:data, &method(:on_stream_data).curry(3)[stream, request])
|
180
|
+
end
|
181
|
+
|
182
|
+
def set_protocol_headers(request)
|
183
|
+
request.headers[":scheme"] = request.scheme
|
184
|
+
request.headers[":method"] = request.verb.to_s.upcase
|
185
|
+
request.headers[":path"] = headline_uri(request)
|
186
|
+
request.headers[":authority"] = request.authority
|
173
187
|
end
|
174
188
|
|
175
189
|
def join_headers(stream, request)
|
176
|
-
set_request_headers(request)
|
177
|
-
headers = {}
|
178
|
-
headers[":scheme"] = request.scheme
|
179
|
-
headers[":method"] = request.verb.to_s.upcase
|
180
|
-
headers[":path"] = headline_uri(request)
|
181
|
-
headers[":authority"] = request.authority
|
182
|
-
headers = headers.merge(request.headers)
|
183
190
|
log(level: 1, color: :yellow) do
|
184
|
-
headers.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
|
191
|
+
request.headers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
|
185
192
|
end
|
186
|
-
stream.headers(headers, end_stream: request.empty?)
|
193
|
+
stream.headers(request.headers.each, end_stream: request.empty?)
|
187
194
|
end
|
188
195
|
|
189
196
|
def join_body(stream, request)
|
@@ -227,10 +234,15 @@ module HTTPX
|
|
227
234
|
end
|
228
235
|
|
229
236
|
def on_stream_close(stream, request, error)
|
237
|
+
log(level: 2) { "#{stream.id}: closing stream" }
|
238
|
+
@drains.delete(request)
|
239
|
+
@streams.delete(request)
|
240
|
+
|
230
241
|
if error && error != :no_error
|
231
242
|
ex = Error.new(stream.id, error)
|
232
243
|
ex.set_backtrace(caller)
|
233
|
-
|
244
|
+
response = ErrorResponse.new(request, ex, request.options)
|
245
|
+
emit(:response, request, response)
|
234
246
|
else
|
235
247
|
response = request.response
|
236
248
|
if response.status == 421
|
@@ -241,9 +253,6 @@ module HTTPX
|
|
241
253
|
emit(:response, request, response)
|
242
254
|
end
|
243
255
|
end
|
244
|
-
log(level: 2) { "#{stream.id}: closing stream" }
|
245
|
-
|
246
|
-
@streams.delete(request)
|
247
256
|
send(@pending.shift) unless @pending.empty?
|
248
257
|
return unless @streams.empty? && exhausted?
|
249
258
|
|
@@ -328,11 +337,9 @@ module HTTPX
|
|
328
337
|
end
|
329
338
|
|
330
339
|
def method_missing(meth, *args, &blk)
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
super
|
335
|
-
end
|
340
|
+
return super unless @connection.respond_to?(meth)
|
341
|
+
|
342
|
+
@connection.__send__(meth, *args, &blk)
|
336
343
|
end
|
337
344
|
end
|
338
345
|
Connection.register "h2", Connection::HTTP2
|