httpx 0.18.3 → 0.18.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/release_notes/0_18_3.md +1 -1
- data/doc/release_notes/0_18_4.md +14 -0
- data/doc/release_notes/0_18_5.md +10 -0
- data/doc/release_notes/0_18_6.md +5 -0
- data/doc/release_notes/0_18_7.md +5 -0
- data/lib/httpx/adapters/faraday.rb +51 -9
- data/lib/httpx/adapters/webmock.rb +71 -59
- data/lib/httpx/altsvc.rb +25 -9
- data/lib/httpx/connection/http1.rb +6 -6
- data/lib/httpx/connection/http2.rb +7 -5
- data/lib/httpx/connection.rb +17 -9
- data/lib/httpx/io/tcp.rb +3 -1
- data/lib/httpx/io/udp.rb +0 -1
- data/lib/httpx/plugins/internal_telemetry.rb +1 -1
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +18 -4
- data/lib/httpx/plugins/proxy/http.rb +10 -23
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +33 -10
- data/lib/httpx/resolver/https.rb +1 -1
- data/lib/httpx/resolver/native.rb +9 -2
- data/lib/httpx/resolver/resolver_mixin.rb +1 -1
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/session.rb +2 -16
- data/lib/httpx/session_extensions.rb +19 -0
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/connection/http1.rbs +0 -2
- data/sig/connection/http2.rbs +2 -2
- data/sig/connection.rbs +1 -0
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab9b9577b0e266b140e21030307b3f2436af346a13f1e0931af05020ba42f7e6
|
4
|
+
data.tar.gz: c5d043448ef41b0532c08d1622540d80c68511ef59cffcc810a6f8bd92f348d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b4ec58f2ab031d23f28a86bc47b40d0f0d79eea9aea64590b0cc3d07269738513de1364608e0c0282a6956d816b78b900520dbad4daab1f73522b8b7f73d138
|
7
|
+
data.tar.gz: f2d2c297b530bfb3567ebcd1678bad1d729187dd75c9107839c925def1a708c4648fe80df614a01af02bce309594e256e274e042f3c6b8347182aebff1df9935
|
data/doc/release_notes/0_18_3.md
CHANGED
@@ -4,4 +4,4 @@
|
|
4
4
|
|
5
5
|
* request bodies eager-loaded from enumerables yield duped partial chunks.
|
6
6
|
|
7
|
-
An error was observed while looking at webmock integration, where requests formed via the multipart plugin
|
7
|
+
An error was observed while looking at webmock integration, where requests formed via the multipart plugin were returning an empty string as body. The issue was caused by an optimization on multipart encoder, which reuses the same buffer when reading chunks. Unfortunately, these cannot be yielded the same way via IO.copy_stream, as the same (cleared) buffer will be used to generate the eager-loaded body chunks.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# 0.18.4
|
2
|
+
|
3
|
+
## Improvements
|
4
|
+
|
5
|
+
* faraday adapter: added support for `#on_data` callback in order to support [faraday streaming](https://lostisland.github.io/faraday/usage/streaming).
|
6
|
+
|
7
|
+
* multipart plugin: removed support for file mime type detection using `mime-types`. The reasoning behind it was that `mime-types` uses the filename, which is a very inaccurate detection strategy (ex: an mp4 video will be identified as `application/mp4`, instead of the correct `video/mp4`).
|
8
|
+
* multipart plugin: supported for file mime type detection using `marcel` and `filemagic` was added. Both use the magic header bytes, which is a more accurate strategy for file type detection.
|
9
|
+
|
10
|
+
## Bugfixes
|
11
|
+
|
12
|
+
* webmock adapter has been reimplemented to work with `httpx` plugins (such as the `:retries` plugin). Some other fixes were applied to make it work better under `vcr` (a common `webmock` extension).
|
13
|
+
|
14
|
+
* fixed the URI-related bug which was making requests stall under ruby 3.1 (still not officially testing against it).
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# 0.18.5
|
2
|
+
|
3
|
+
## Improvements
|
4
|
+
|
5
|
+
* ruby 3.1 is now officially supported.
|
6
|
+
* when a user sets a `Host` header for an HTTP/2 request, this will be used in the `:authority` HTTP/2 pseudo-header, instead of silently ignored (mimicking what "curl" does).
|
7
|
+
|
8
|
+
## Bugfixes
|
9
|
+
|
10
|
+
* fixed "throw outside of catch block" error happening when pipelining requests on an HTTP/1 connection and resulting in a timeout.
|
@@ -21,6 +21,17 @@ module Faraday
|
|
21
21
|
end
|
22
22
|
# :nocov:
|
23
23
|
|
24
|
+
unless Faraday::RequestOptions.method_defined?(:stream_response?)
|
25
|
+
module RequestOptionsExtensions
|
26
|
+
refine Faraday::RequestOptions do
|
27
|
+
def stream_response?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
using RequestOptionsExtensions
|
33
|
+
end
|
34
|
+
|
24
35
|
module RequestMixin
|
25
36
|
using ::HTTPX::HashExtensions
|
26
37
|
|
@@ -64,6 +75,27 @@ module Faraday
|
|
64
75
|
|
65
76
|
include RequestMixin
|
66
77
|
|
78
|
+
module OnDataPlugin
|
79
|
+
module RequestMethods
|
80
|
+
attr_writer :response_on_data
|
81
|
+
|
82
|
+
def response=(response)
|
83
|
+
super
|
84
|
+
response.body.on_data = @response_on_data
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module ResponseBodyMethods
|
89
|
+
attr_writer :on_data
|
90
|
+
|
91
|
+
def write(chunk)
|
92
|
+
return super unless @on_data
|
93
|
+
|
94
|
+
@on_data.call(chunk, chunk.bytesize)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
67
99
|
class Session < ::HTTPX::Session
|
68
100
|
plugin(:compression)
|
69
101
|
plugin(:persistent)
|
@@ -137,15 +169,21 @@ module Faraday
|
|
137
169
|
end
|
138
170
|
|
139
171
|
def run
|
140
|
-
requests = @handlers.map { |handler| build_request(handler.env) }
|
141
172
|
env = @handlers.last.env
|
142
173
|
|
143
|
-
proxy_options = { uri: env.request.proxy }
|
144
|
-
|
145
174
|
session = @session.with(options_from_env(env))
|
146
|
-
session = session.plugin(:proxy).with(proxy:
|
175
|
+
session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
|
176
|
+
session = session.plugin(OnDataPlugin) if env.request.stream_response?
|
177
|
+
|
178
|
+
requests = @handlers.map { |handler| session.build_request(*build_request(handler.env)) }
|
179
|
+
|
180
|
+
if env.request.stream_response?
|
181
|
+
requests.each do |request|
|
182
|
+
request.response_on_data = env.request.on_data
|
183
|
+
end
|
184
|
+
end
|
147
185
|
|
148
|
-
responses = session.request(requests)
|
186
|
+
responses = session.request(*requests)
|
149
187
|
Array(responses).each_with_index do |response, index|
|
150
188
|
handler = @handlers[index]
|
151
189
|
handler.on_response.call(response)
|
@@ -179,11 +217,15 @@ module Faraday
|
|
179
217
|
return handler
|
180
218
|
end
|
181
219
|
|
182
|
-
meth, uri, request_options = build_request(env)
|
183
|
-
|
184
220
|
session = @session.with(options_from_env(env))
|
185
|
-
session = session.plugin(:proxy).with(proxy:
|
186
|
-
|
221
|
+
session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
|
222
|
+
session = session.plugin(OnDataPlugin) if env.request.stream_response?
|
223
|
+
|
224
|
+
request = session.build_request(*build_request(env))
|
225
|
+
|
226
|
+
request.response_on_data = env.request.on_data if env.request.stream_response?
|
227
|
+
|
228
|
+
response = session.request(request)
|
187
229
|
response.raise_for_status unless response.is_a?(::HTTPX::Response)
|
188
230
|
save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
|
189
231
|
response_headers.merge!(response.headers)
|
@@ -16,56 +16,8 @@ module WebMock
|
|
16
16
|
# Requests are "hijacked" at the session, before they're distributed to a connection.
|
17
17
|
#
|
18
18
|
module Plugin
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def send_requests(*requests)
|
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)).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)
|
19
|
+
class << self
|
20
|
+
def build_webmock_request_signature(request)
|
69
21
|
uri = WebMock::Util::URI.heuristic_parse(request.uri)
|
70
22
|
uri.path = uri.normalized_path.gsub("[^:]//", "/")
|
71
23
|
|
@@ -77,7 +29,7 @@ module WebMock
|
|
77
29
|
)
|
78
30
|
end
|
79
31
|
|
80
|
-
def
|
32
|
+
def build_webmock_response(_request, response)
|
81
33
|
webmock_response = WebMock::Response.new
|
82
34
|
webmock_response.status = [response.status, HTTP_REASONS[response.status]]
|
83
35
|
webmock_response.body = response.body.to_s
|
@@ -85,10 +37,10 @@ module WebMock
|
|
85
37
|
webmock_response
|
86
38
|
end
|
87
39
|
|
88
|
-
def
|
89
|
-
return
|
40
|
+
def build_from_webmock_response(request, webmock_response)
|
41
|
+
return build_error_response(request, HTTPX::TimeoutError.new(1, "Timed out")) if webmock_response.should_timeout
|
90
42
|
|
91
|
-
return
|
43
|
+
return build_error_response(request, webmock_response.exception) if webmock_response.exception
|
92
44
|
|
93
45
|
response = request.options.response_class.new(request,
|
94
46
|
webmock_response.status[0],
|
@@ -98,10 +50,70 @@ module WebMock
|
|
98
50
|
response
|
99
51
|
end
|
100
52
|
|
101
|
-
def
|
53
|
+
def build_error_response(request, exception)
|
102
54
|
HTTPX::ErrorResponse.new(request, exception, request.options)
|
103
55
|
end
|
104
56
|
end
|
57
|
+
|
58
|
+
module InstanceMethods
|
59
|
+
def build_connection(*)
|
60
|
+
connection = super
|
61
|
+
connection.once(:unmock_connection) do
|
62
|
+
pool.__send__(:resolve_connection, connection)
|
63
|
+
pool.__send__(:unregister_connection, connection) unless connection.addresses
|
64
|
+
end
|
65
|
+
connection
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module ConnectionMethods
|
70
|
+
def initialize(*)
|
71
|
+
super
|
72
|
+
@mocked = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def open?
|
76
|
+
return true if @mocked
|
77
|
+
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
81
|
+
def interests
|
82
|
+
return if @mocked
|
83
|
+
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
def send(request)
|
88
|
+
request_signature = Plugin.build_webmock_request_signature(request)
|
89
|
+
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
90
|
+
|
91
|
+
if (mock_response = WebMock::StubRegistry.instance.response_for_request(request_signature))
|
92
|
+
response = Plugin.build_from_webmock_response(request, mock_response)
|
93
|
+
WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, request_signature, mock_response)
|
94
|
+
log { "mocking #{request.uri} with #{mock_response.inspect}" }
|
95
|
+
request.response = response
|
96
|
+
request.emit(:response, response)
|
97
|
+
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
98
|
+
if WebMock::CallbackRegistry.any_callbacks?
|
99
|
+
request.on(:response) do |resp|
|
100
|
+
unless resp.is_a?(HTTPX::ErrorResponse)
|
101
|
+
webmock_response = Plugin.build_webmock_response(request, resp)
|
102
|
+
WebMock::CallbackRegistry.invoke_callbacks(
|
103
|
+
{ lib: :httpx, real_request: true }, request_signature,
|
104
|
+
webmock_response
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@mocked = false
|
110
|
+
emit(:unmock_connection, self)
|
111
|
+
super
|
112
|
+
else
|
113
|
+
raise WebMock::NetConnectNotAllowedError, request_signature
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
105
117
|
end
|
106
118
|
|
107
119
|
class HttpxAdapter < HttpLibAdapter
|
@@ -109,12 +121,12 @@ module WebMock
|
|
109
121
|
|
110
122
|
class << self
|
111
123
|
def enable!
|
112
|
-
@original_session =
|
124
|
+
@original_session = HTTPX::Session
|
113
125
|
|
114
|
-
webmock_session =
|
126
|
+
webmock_session = HTTPX.plugin(Plugin)
|
115
127
|
|
116
|
-
|
117
|
-
|
128
|
+
HTTPX.send(:remove_const, :Session)
|
129
|
+
HTTPX.send(:const_set, :Session, webmock_session.class)
|
118
130
|
end
|
119
131
|
|
120
132
|
def disable!
|
data/lib/httpx/altsvc.rb
CHANGED
@@ -70,7 +70,7 @@ module HTTPX
|
|
70
70
|
|
71
71
|
scanner = StringScanner.new(altsvc)
|
72
72
|
until scanner.eos?
|
73
|
-
|
73
|
+
alt_service = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
|
74
74
|
|
75
75
|
alt_params = []
|
76
76
|
loop do
|
@@ -80,29 +80,45 @@ module HTTPX
|
|
80
80
|
break if scanner.eos? || scanner.scan(/ *, */)
|
81
81
|
end
|
82
82
|
alt_params = Hash[alt_params.map { |field| field.split("=") }]
|
83
|
-
|
83
|
+
|
84
|
+
alt_proto, alt_authority = alt_service.split("=")
|
85
|
+
alt_origin = parse_altsvc_origin(alt_proto, alt_authority)
|
86
|
+
return unless alt_origin
|
87
|
+
|
88
|
+
yield(alt_origin, alt_params.merge("proto" => alt_proto))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_altsvc_scheme(alt_proto)
|
93
|
+
case alt_proto
|
94
|
+
when "h2c"
|
95
|
+
"http"
|
96
|
+
when "h2"
|
97
|
+
"https"
|
84
98
|
end
|
85
99
|
end
|
86
100
|
|
87
101
|
# :nocov:
|
88
102
|
if RUBY_VERSION < "2.2"
|
89
|
-
def parse_altsvc_origin(alt_origin)
|
90
|
-
|
103
|
+
def parse_altsvc_origin(alt_proto, alt_origin)
|
104
|
+
alt_scheme = parse_altsvc_scheme(alt_proto) or return
|
105
|
+
|
91
106
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
92
107
|
if alt_origin.start_with?(":")
|
93
|
-
alt_origin = "#{
|
108
|
+
alt_origin = "#{alt_scheme}://dummy#{alt_origin}"
|
94
109
|
uri = URI.parse(alt_origin)
|
95
110
|
uri.host = nil
|
96
111
|
uri
|
97
112
|
else
|
98
|
-
URI.parse("#{
|
113
|
+
URI.parse("#{alt_scheme}://#{alt_origin}")
|
99
114
|
end
|
100
115
|
end
|
101
116
|
else
|
102
|
-
def parse_altsvc_origin(alt_origin)
|
103
|
-
|
117
|
+
def parse_altsvc_origin(alt_proto, alt_origin)
|
118
|
+
alt_scheme = parse_altsvc_scheme(alt_proto) or return
|
104
119
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
105
|
-
|
120
|
+
|
121
|
+
URI.parse("#{alt_scheme}://#{alt_origin}")
|
106
122
|
end
|
107
123
|
end
|
108
124
|
# :nocov:
|
@@ -184,7 +184,7 @@ module HTTPX
|
|
184
184
|
end
|
185
185
|
|
186
186
|
if @pipelining
|
187
|
-
disable
|
187
|
+
catch(:called) { disable }
|
188
188
|
else
|
189
189
|
@requests.each do |request|
|
190
190
|
emit(:error, request, ex)
|
@@ -297,10 +297,6 @@ module HTTPX
|
|
297
297
|
extra_headers
|
298
298
|
end
|
299
299
|
|
300
|
-
def headline_uri(request)
|
301
|
-
request.path
|
302
|
-
end
|
303
|
-
|
304
300
|
def handle(request)
|
305
301
|
catch(:buffer_full) do
|
306
302
|
request.transition(:headers)
|
@@ -314,8 +310,12 @@ module HTTPX
|
|
314
310
|
end
|
315
311
|
end
|
316
312
|
|
313
|
+
def join_headline(request)
|
314
|
+
"#{request.verb.to_s.upcase} #{request.path} HTTP/#{@version.join(".")}"
|
315
|
+
end
|
316
|
+
|
317
317
|
def join_headers(request)
|
318
|
-
headline =
|
318
|
+
headline = join_headline(request)
|
319
319
|
@buffer << headline << CRLF
|
320
320
|
log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
|
321
321
|
extra_headers = set_protocol_headers(request)
|
@@ -158,10 +158,6 @@ module HTTPX
|
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
161
|
-
def headline_uri(request)
|
162
|
-
request.path
|
163
|
-
end
|
164
|
-
|
165
161
|
def handle(request, stream)
|
166
162
|
catch(:buffer_full) do
|
167
163
|
request.transition(:headers)
|
@@ -213,13 +209,19 @@ module HTTPX
|
|
213
209
|
{
|
214
210
|
":scheme" => request.scheme,
|
215
211
|
":method" => request.verb.to_s.upcase,
|
216
|
-
":path" =>
|
212
|
+
":path" => request.path,
|
217
213
|
":authority" => request.authority,
|
218
214
|
}
|
219
215
|
end
|
220
216
|
|
221
217
|
def join_headers(stream, request)
|
222
218
|
extra_headers = set_protocol_headers(request)
|
219
|
+
|
220
|
+
if request.headers.key?("host")
|
221
|
+
log { "forbidden \"host\" header found (#{request.headers["host"]}), will use it as authority..." }
|
222
|
+
extra_headers[":authority"] = request.headers["host"]
|
223
|
+
end
|
224
|
+
|
223
225
|
log(level: 1, color: :yellow) do
|
224
226
|
request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
|
225
227
|
end
|
data/lib/httpx/connection.rb
CHANGED
@@ -44,7 +44,7 @@ module HTTPX
|
|
44
44
|
|
45
45
|
def_delegator :@write_buffer, :empty?
|
46
46
|
|
47
|
-
attr_reader :origin, :state, :pending, :options
|
47
|
+
attr_reader :origin, :origins, :state, :pending, :options
|
48
48
|
|
49
49
|
attr_writer :timers
|
50
50
|
|
@@ -489,6 +489,18 @@ module HTTPX
|
|
489
489
|
end
|
490
490
|
|
491
491
|
def transition(nextstate)
|
492
|
+
handle_transition(nextstate)
|
493
|
+
rescue Errno::ECONNREFUSED,
|
494
|
+
Errno::EADDRNOTAVAIL,
|
495
|
+
Errno::EHOSTUNREACH,
|
496
|
+
TLSError => e
|
497
|
+
# connect errors, exit gracefully
|
498
|
+
handle_error(e)
|
499
|
+
@state = :closed
|
500
|
+
emit(:close)
|
501
|
+
end
|
502
|
+
|
503
|
+
def handle_transition(nextstate)
|
492
504
|
case nextstate
|
493
505
|
when :idle
|
494
506
|
@timeout = @current_timeout = @options.timeout[:connect_timeout]
|
@@ -525,14 +537,6 @@ module HTTPX
|
|
525
537
|
emit(:activate)
|
526
538
|
end
|
527
539
|
@state = nextstate
|
528
|
-
rescue Errno::ECONNREFUSED,
|
529
|
-
Errno::EADDRNOTAVAIL,
|
530
|
-
Errno::EHOSTUNREACH,
|
531
|
-
TLSError => e
|
532
|
-
# connect errors, exit gracefully
|
533
|
-
handle_error(e)
|
534
|
-
@state = :closed
|
535
|
-
emit(:close)
|
536
540
|
end
|
537
541
|
|
538
542
|
def purge_after_closed
|
@@ -550,6 +554,10 @@ module HTTPX
|
|
550
554
|
ex.set_backtrace(error.backtrace)
|
551
555
|
error = ex
|
552
556
|
else
|
557
|
+
# inactive connections do not contribute to the select loop, therefore
|
558
|
+
# they should fail due to such errors.
|
559
|
+
return if @state == :inactive
|
560
|
+
|
553
561
|
if @timeout
|
554
562
|
@timeout -= error.timeout
|
555
563
|
return unless @timeout <= 0
|
data/lib/httpx/io/tcp.rb
CHANGED
data/lib/httpx/io/udp.rb
CHANGED
@@ -8,11 +8,25 @@ module HTTPX
|
|
8
8
|
DEFAULT_MIMETYPE = "application/octet-stream"
|
9
9
|
|
10
10
|
# inspired by https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/determine_mime_type.rb
|
11
|
-
if defined?(
|
11
|
+
if defined?(FileMagic)
|
12
|
+
MAGIC_NUMBER = 256 * 1024
|
12
13
|
|
13
|
-
def call(
|
14
|
-
|
15
|
-
|
14
|
+
def call(file, _)
|
15
|
+
return nil if file.eof? # FileMagic returns "application/x-empty" for empty files
|
16
|
+
|
17
|
+
mime = FileMagic.open(FileMagic::MAGIC_MIME_TYPE) do |filemagic|
|
18
|
+
filemagic.buffer(file.read(MAGIC_NUMBER))
|
19
|
+
end
|
20
|
+
|
21
|
+
file.rewind
|
22
|
+
|
23
|
+
mime
|
24
|
+
end
|
25
|
+
elsif defined?(Marcel)
|
26
|
+
def call(file, filename)
|
27
|
+
return nil if file.eof? # marcel returns "application/octet-stream" for empty files
|
28
|
+
|
29
|
+
Marcel::MimeType.for(file, name: filename)
|
16
30
|
end
|
17
31
|
|
18
32
|
elsif defined?(MimeMagic)
|
@@ -13,7 +13,7 @@ module HTTPX
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
def
|
16
|
+
def handle_transition(nextstate)
|
17
17
|
return super unless @options.proxy && @options.proxy.uri.scheme == "http"
|
18
18
|
|
19
19
|
case nextstate
|
@@ -23,7 +23,8 @@ module HTTPX
|
|
23
23
|
@io.connect
|
24
24
|
return unless @io.connected?
|
25
25
|
|
26
|
-
@parser =
|
26
|
+
@parser = registry(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
|
27
|
+
@parser.extend(ProxyParser)
|
27
28
|
@parser.once(:response, &method(:__http_on_connect))
|
28
29
|
@parser.on(:close) { transition(:closing) }
|
29
30
|
__http_proxy_connect
|
@@ -36,7 +37,7 @@ module HTTPX
|
|
36
37
|
@parser.close
|
37
38
|
@parser = nil
|
38
39
|
when :idle
|
39
|
-
@parser
|
40
|
+
@parser.callbacks.clear
|
40
41
|
set_parser_callbacks(@parser)
|
41
42
|
end
|
42
43
|
end
|
@@ -54,7 +55,7 @@ module HTTPX
|
|
54
55
|
@inflight += 1
|
55
56
|
parser.send(connect_request)
|
56
57
|
else
|
57
|
-
|
58
|
+
handle_transition(:connected)
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
@@ -76,9 +77,11 @@ module HTTPX
|
|
76
77
|
end
|
77
78
|
end
|
78
79
|
|
79
|
-
|
80
|
-
def
|
81
|
-
request.
|
80
|
+
module ProxyParser
|
81
|
+
def join_headline(request)
|
82
|
+
return super if request.verb == :connect
|
83
|
+
|
84
|
+
"#{request.verb.to_s.upcase} #{request.uri} HTTP/#{@version.join(".")}"
|
82
85
|
end
|
83
86
|
|
84
87
|
def set_protocol_headers(request)
|
@@ -91,22 +94,6 @@ module HTTPX
|
|
91
94
|
end
|
92
95
|
end
|
93
96
|
|
94
|
-
class ConnectProxyParser < ProxyParser
|
95
|
-
attr_reader :pending
|
96
|
-
|
97
|
-
def headline_uri(request)
|
98
|
-
return super unless request.verb == :connect
|
99
|
-
|
100
|
-
tunnel = request.path
|
101
|
-
log { "establishing HTTP proxy tunnel to #{tunnel}" }
|
102
|
-
tunnel
|
103
|
-
end
|
104
|
-
|
105
|
-
def empty?
|
106
|
-
@requests.reject { |r| r.verb == :connect }.empty? || @requests.all? { |request| !request.response.nil? }
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
97
|
class ConnectRequest < Request
|
111
98
|
def initialize(uri, _options)
|
112
99
|
super(:connect, uri, {})
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -85,7 +85,7 @@ module HTTPX
|
|
85
85
|
end
|
86
86
|
uris
|
87
87
|
end
|
88
|
-
|
88
|
+
{ uri: @_proxy_uris.first } unless @_proxy_uris.empty?
|
89
89
|
end
|
90
90
|
|
91
91
|
def find_connection(request, connections, options)
|
@@ -109,8 +109,10 @@ module HTTPX
|
|
109
109
|
return super unless proxy
|
110
110
|
|
111
111
|
connection = options.connection_class.new("tcp", uri, options)
|
112
|
-
|
113
|
-
|
112
|
+
catch(:coalesced) do
|
113
|
+
pool.init_connection(connection, options)
|
114
|
+
connection
|
115
|
+
end
|
114
116
|
end
|
115
117
|
|
116
118
|
def fetch_response(request, connections, options)
|
@@ -138,10 +140,20 @@ module HTTPX
|
|
138
140
|
error = response.error
|
139
141
|
case error
|
140
142
|
when NativeResolveError
|
143
|
+
return false unless @_proxy_uris && !@_proxy_uris.empty?
|
144
|
+
|
145
|
+
proxy_uri = URI(@_proxy_uris.first)
|
146
|
+
|
147
|
+
origin = error.connection.origin
|
148
|
+
|
141
149
|
# failed resolving proxy domain
|
142
|
-
|
150
|
+
origin.host == proxy_uri.host && origin.port == proxy_uri.port
|
143
151
|
when ResolveError
|
144
|
-
|
152
|
+
return false unless @_proxy_uris && !@_proxy_uris.empty?
|
153
|
+
|
154
|
+
proxy_uri = URI(@_proxy_uris.first)
|
155
|
+
|
156
|
+
error.message.end_with?(proxy_uri.to_s)
|
145
157
|
when *PROXY_ERRORS
|
146
158
|
# timeout errors connecting to proxy
|
147
159
|
true
|
@@ -160,7 +172,9 @@ module HTTPX
|
|
160
172
|
|
161
173
|
# redefining the connection origin as the proxy's URI,
|
162
174
|
# as this will be used as the tcp peer ip.
|
163
|
-
|
175
|
+
proxy_uri = URI(@options.proxy.uri)
|
176
|
+
@origin.host = proxy_uri.host
|
177
|
+
@origin.port = proxy_uri.port
|
164
178
|
end
|
165
179
|
|
166
180
|
def match?(uri, options)
|
@@ -169,11 +183,20 @@ module HTTPX
|
|
169
183
|
super && @options.proxy == options.proxy
|
170
184
|
end
|
171
185
|
|
172
|
-
|
173
|
-
def coalescable?(*)
|
186
|
+
def coalescable?(connection)
|
174
187
|
return super unless @options.proxy
|
175
188
|
|
176
|
-
|
189
|
+
if @io.protocol == "h2" &&
|
190
|
+
@origin.scheme == "https" &&
|
191
|
+
connection.origin.scheme == "https" &&
|
192
|
+
@io.can_verify_peer?
|
193
|
+
# in proxied connections, .origin is the proxy ; Given names
|
194
|
+
# are stored in .origins, this is what is used.
|
195
|
+
origin = URI(connection.origins.first)
|
196
|
+
@io.verify_hostname(origin.host)
|
197
|
+
else
|
198
|
+
@origin == connection.origin
|
199
|
+
end
|
177
200
|
end
|
178
201
|
|
179
202
|
def send(request)
|
@@ -222,7 +245,7 @@ module HTTPX
|
|
222
245
|
end
|
223
246
|
end
|
224
247
|
|
225
|
-
def
|
248
|
+
def handle_transition(nextstate)
|
226
249
|
return super unless @options.proxy
|
227
250
|
|
228
251
|
case nextstate
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -29,7 +29,7 @@ module HTTPX
|
|
29
29
|
attr_writer :pool
|
30
30
|
|
31
31
|
def initialize(options)
|
32
|
-
@options = Options.new(options)
|
32
|
+
@options = HTTPX::Options.new(options)
|
33
33
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
34
34
|
@_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
|
35
35
|
@queries = {}
|
@@ -50,7 +50,7 @@ module HTTPX
|
|
50
50
|
attr_reader :state
|
51
51
|
|
52
52
|
def initialize(options)
|
53
|
-
@options = Options.new(options)
|
53
|
+
@options = HTTPX::Options.new(options)
|
54
54
|
@ns_index = 0
|
55
55
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
56
56
|
@nameserver = @resolver_options[:nameserver]
|
@@ -200,6 +200,7 @@ module HTTPX
|
|
200
200
|
hostname, connection = @queries.first
|
201
201
|
if @_record_types[hostname].empty?
|
202
202
|
@queries.delete(hostname)
|
203
|
+
@timeouts.delete(hostname)
|
203
204
|
@connections.delete(connection)
|
204
205
|
ex = NativeResolveError.new(connection, hostname, e.message)
|
205
206
|
ex.set_backtrace(e.backtrace)
|
@@ -213,6 +214,7 @@ module HTTPX
|
|
213
214
|
if @_record_types[hostname].empty?
|
214
215
|
@queries.delete(hostname)
|
215
216
|
@_record_types.delete(hostname)
|
217
|
+
@timeouts.delete(hostname)
|
216
218
|
@connections.delete(connection)
|
217
219
|
|
218
220
|
raise NativeResolveError.new(connection, hostname)
|
@@ -236,13 +238,18 @@ module HTTPX
|
|
236
238
|
end
|
237
239
|
|
238
240
|
if address.key?("alias") # CNAME
|
239
|
-
|
241
|
+
# clean up intermediate queries
|
242
|
+
@timeouts.delete(address["name"]) unless connection.origin.host == address["name"]
|
243
|
+
|
244
|
+
if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) }
|
245
|
+
@timeouts.delete(connection.origin.host)
|
240
246
|
@connections.delete(connection)
|
241
247
|
else
|
242
248
|
resolve(connection, address["alias"])
|
243
249
|
return
|
244
250
|
end
|
245
251
|
else
|
252
|
+
@timeouts.delete(connection.origin.host)
|
246
253
|
@connections.delete(connection)
|
247
254
|
Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options[:cache]
|
248
255
|
emit_addresses(connection, addresses.map { |addr| addr["data"] })
|
@@ -32,7 +32,7 @@ module HTTPX
|
|
32
32
|
end
|
33
33
|
log { "resolver: answer #{connection.origin.host}: #{addresses.inspect}" }
|
34
34
|
connection.addresses = addresses
|
35
|
-
|
35
|
+
emit(:resolve, connection)
|
36
36
|
end
|
37
37
|
|
38
38
|
def early_resolve(connection, hostname: connection.origin.host)
|
data/lib/httpx/session.rb
CHANGED
@@ -163,7 +163,7 @@ module HTTPX
|
|
163
163
|
case uri.scheme
|
164
164
|
when "http"
|
165
165
|
"tcp"
|
166
|
-
when "https"
|
166
|
+
when "https"
|
167
167
|
"ssl"
|
168
168
|
else
|
169
169
|
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
@@ -207,7 +207,7 @@ module HTTPX
|
|
207
207
|
|
208
208
|
return responses unless request
|
209
209
|
|
210
|
-
pool.next_tick until (response = fetch_response(request, connections, request.options))
|
210
|
+
catch(:coalesced) { pool.next_tick } until (response = fetch_response(request, connections, request.options))
|
211
211
|
|
212
212
|
responses << response
|
213
213
|
requests.shift
|
@@ -309,18 +309,4 @@ module HTTPX
|
|
309
309
|
# :nocov:
|
310
310
|
end
|
311
311
|
end
|
312
|
-
|
313
|
-
unless ENV.grep(/https?_proxy$/i).empty?
|
314
|
-
proxy_session = plugin(:proxy)
|
315
|
-
::HTTPX.send(:remove_const, :Session)
|
316
|
-
::HTTPX.send(:const_set, :Session, proxy_session.class)
|
317
|
-
end
|
318
|
-
|
319
|
-
# :nocov:
|
320
|
-
if Session.default_options.debug_level > 2
|
321
|
-
proxy_session = plugin(:internal_telemetry)
|
322
|
-
::HTTPX.send(:remove_const, :Session)
|
323
|
-
::HTTPX.send(:const_set, :Session, proxy_session.class)
|
324
|
-
end
|
325
|
-
# :nocov:
|
326
312
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
unless ENV.keys.grep(/\Ahttps?_proxy\z/i).empty?
|
5
|
+
proxy_session = plugin(:proxy)
|
6
|
+
remove_const(:Session)
|
7
|
+
const_set(:Session, proxy_session.class)
|
8
|
+
remove_const(:Options)
|
9
|
+
const_set(:Options, proxy_session.class.default_options.class)
|
10
|
+
end
|
11
|
+
|
12
|
+
# :nocov:
|
13
|
+
if Session.default_options.debug_level > 2
|
14
|
+
proxy_session = plugin(:internal_telemetry)
|
15
|
+
remove_const(:Session)
|
16
|
+
const_set(:Session, proxy_session.class)
|
17
|
+
end
|
18
|
+
# :nocov:
|
19
|
+
end
|
data/lib/httpx/version.rb
CHANGED
data/lib/httpx.rb
CHANGED
data/sig/connection/http1.rbs
CHANGED
data/sig/connection/http2.rbs
CHANGED
@@ -46,8 +46,6 @@ module HTTPX
|
|
46
46
|
|
47
47
|
def send_pending: () -> void
|
48
48
|
|
49
|
-
def headline_uri: (Request) -> String
|
50
|
-
|
51
49
|
def set_protocol_headers: (Request) -> _Each[[String, String]]
|
52
50
|
|
53
51
|
def handle: (Request request, HTTP2Next::Stream stream) -> void
|
@@ -56,6 +54,8 @@ module HTTPX
|
|
56
54
|
|
57
55
|
def handle_stream: (HTTP2Next::Stream stream, Request request) -> void
|
58
56
|
|
57
|
+
def join_headline: (Request request) -> String
|
58
|
+
|
59
59
|
def join_headers: (HTTP2Next::Stream stream, Request request) -> void
|
60
60
|
|
61
61
|
def join_trailers: (HTTP2Next::Stream stream, Request request) -> void
|
data/sig/connection.rbs
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: httpx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.18.
|
4
|
+
version: 0.18.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2-next
|
@@ -66,6 +66,10 @@ extra_rdoc_files:
|
|
66
66
|
- doc/release_notes/0_18_1.md
|
67
67
|
- doc/release_notes/0_18_2.md
|
68
68
|
- doc/release_notes/0_18_3.md
|
69
|
+
- doc/release_notes/0_18_4.md
|
70
|
+
- doc/release_notes/0_18_5.md
|
71
|
+
- doc/release_notes/0_18_6.md
|
72
|
+
- doc/release_notes/0_18_7.md
|
69
73
|
- doc/release_notes/0_1_0.md
|
70
74
|
- doc/release_notes/0_2_0.md
|
71
75
|
- doc/release_notes/0_2_1.md
|
@@ -125,6 +129,10 @@ files:
|
|
125
129
|
- doc/release_notes/0_18_1.md
|
126
130
|
- doc/release_notes/0_18_2.md
|
127
131
|
- doc/release_notes/0_18_3.md
|
132
|
+
- doc/release_notes/0_18_4.md
|
133
|
+
- doc/release_notes/0_18_5.md
|
134
|
+
- doc/release_notes/0_18_6.md
|
135
|
+
- doc/release_notes/0_18_7.md
|
128
136
|
- doc/release_notes/0_1_0.md
|
129
137
|
- doc/release_notes/0_2_0.md
|
130
138
|
- doc/release_notes/0_2_1.md
|
@@ -228,6 +236,7 @@ files:
|
|
228
236
|
- lib/httpx/selector.rb
|
229
237
|
- lib/httpx/session.rb
|
230
238
|
- lib/httpx/session2.rb
|
239
|
+
- lib/httpx/session_extensions.rb
|
231
240
|
- lib/httpx/timers.rb
|
232
241
|
- lib/httpx/transcoder.rb
|
233
242
|
- lib/httpx/transcoder/body.rb
|
@@ -322,7 +331,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
322
331
|
- !ruby/object:Gem::Version
|
323
332
|
version: '0'
|
324
333
|
requirements: []
|
325
|
-
rubygems_version: 3.2.
|
334
|
+
rubygems_version: 3.2.32
|
326
335
|
signing_key:
|
327
336
|
specification_version: 4
|
328
337
|
summary: HTTPX, to the future, and beyond
|