httpx-patched 1.6.2.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.
Files changed (336) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +191 -0
  3. data/README.md +162 -0
  4. data/doc/release_notes/0_0_1.md +7 -0
  5. data/doc/release_notes/0_0_2.md +9 -0
  6. data/doc/release_notes/0_0_3.md +9 -0
  7. data/doc/release_notes/0_0_4.md +7 -0
  8. data/doc/release_notes/0_0_5.md +5 -0
  9. data/doc/release_notes/0_10_0.md +66 -0
  10. data/doc/release_notes/0_10_1.md +37 -0
  11. data/doc/release_notes/0_10_2.md +5 -0
  12. data/doc/release_notes/0_11_0.md +74 -0
  13. data/doc/release_notes/0_11_1.md +5 -0
  14. data/doc/release_notes/0_11_2.md +5 -0
  15. data/doc/release_notes/0_11_3.md +5 -0
  16. data/doc/release_notes/0_12_0.md +55 -0
  17. data/doc/release_notes/0_13_0.md +58 -0
  18. data/doc/release_notes/0_13_1.md +5 -0
  19. data/doc/release_notes/0_13_2.md +9 -0
  20. data/doc/release_notes/0_14_0.md +79 -0
  21. data/doc/release_notes/0_14_1.md +7 -0
  22. data/doc/release_notes/0_14_2.md +6 -0
  23. data/doc/release_notes/0_14_3.md +5 -0
  24. data/doc/release_notes/0_14_4.md +5 -0
  25. data/doc/release_notes/0_14_5.md +11 -0
  26. data/doc/release_notes/0_15_0.md +53 -0
  27. data/doc/release_notes/0_15_1.md +8 -0
  28. data/doc/release_notes/0_15_2.md +9 -0
  29. data/doc/release_notes/0_15_3.md +5 -0
  30. data/doc/release_notes/0_15_4.md +5 -0
  31. data/doc/release_notes/0_16_0.md +93 -0
  32. data/doc/release_notes/0_16_1.md +5 -0
  33. data/doc/release_notes/0_17_0.md +49 -0
  34. data/doc/release_notes/0_18_0.md +69 -0
  35. data/doc/release_notes/0_18_1.md +12 -0
  36. data/doc/release_notes/0_18_2.md +10 -0
  37. data/doc/release_notes/0_18_3.md +7 -0
  38. data/doc/release_notes/0_18_4.md +14 -0
  39. data/doc/release_notes/0_18_5.md +10 -0
  40. data/doc/release_notes/0_18_6.md +5 -0
  41. data/doc/release_notes/0_18_7.md +5 -0
  42. data/doc/release_notes/0_19_0.md +39 -0
  43. data/doc/release_notes/0_19_1.md +5 -0
  44. data/doc/release_notes/0_19_2.md +7 -0
  45. data/doc/release_notes/0_19_3.md +6 -0
  46. data/doc/release_notes/0_19_4.md +14 -0
  47. data/doc/release_notes/0_19_5.md +13 -0
  48. data/doc/release_notes/0_19_6.md +5 -0
  49. data/doc/release_notes/0_19_7.md +5 -0
  50. data/doc/release_notes/0_19_8.md +5 -0
  51. data/doc/release_notes/0_1_0.md +9 -0
  52. data/doc/release_notes/0_20_0.md +36 -0
  53. data/doc/release_notes/0_20_1.md +5 -0
  54. data/doc/release_notes/0_20_2.md +7 -0
  55. data/doc/release_notes/0_20_3.md +6 -0
  56. data/doc/release_notes/0_20_4.md +17 -0
  57. data/doc/release_notes/0_20_5.md +3 -0
  58. data/doc/release_notes/0_21_0.md +96 -0
  59. data/doc/release_notes/0_21_1.md +12 -0
  60. data/doc/release_notes/0_22_0.md +13 -0
  61. data/doc/release_notes/0_22_1.md +11 -0
  62. data/doc/release_notes/0_22_2.md +5 -0
  63. data/doc/release_notes/0_22_3.md +55 -0
  64. data/doc/release_notes/0_22_4.md +6 -0
  65. data/doc/release_notes/0_22_5.md +6 -0
  66. data/doc/release_notes/0_23_0.md +42 -0
  67. data/doc/release_notes/0_23_1.md +5 -0
  68. data/doc/release_notes/0_23_2.md +5 -0
  69. data/doc/release_notes/0_23_3.md +6 -0
  70. data/doc/release_notes/0_23_4.md +5 -0
  71. data/doc/release_notes/0_24_0.md +48 -0
  72. data/doc/release_notes/0_24_1.md +12 -0
  73. data/doc/release_notes/0_24_2.md +12 -0
  74. data/doc/release_notes/0_24_3.md +12 -0
  75. data/doc/release_notes/0_24_4.md +18 -0
  76. data/doc/release_notes/0_24_5.md +6 -0
  77. data/doc/release_notes/0_24_6.md +5 -0
  78. data/doc/release_notes/0_24_7.md +10 -0
  79. data/doc/release_notes/0_2_0.md +5 -0
  80. data/doc/release_notes/0_2_1.md +16 -0
  81. data/doc/release_notes/0_3_0.md +12 -0
  82. data/doc/release_notes/0_3_1.md +6 -0
  83. data/doc/release_notes/0_4_0.md +51 -0
  84. data/doc/release_notes/0_4_1.md +3 -0
  85. data/doc/release_notes/0_5_0.md +15 -0
  86. data/doc/release_notes/0_5_1.md +14 -0
  87. data/doc/release_notes/0_6_0.md +5 -0
  88. data/doc/release_notes/0_6_1.md +6 -0
  89. data/doc/release_notes/0_6_2.md +6 -0
  90. data/doc/release_notes/0_6_3.md +13 -0
  91. data/doc/release_notes/0_6_4.md +21 -0
  92. data/doc/release_notes/0_6_5.md +22 -0
  93. data/doc/release_notes/0_6_6.md +19 -0
  94. data/doc/release_notes/0_6_7.md +5 -0
  95. data/doc/release_notes/0_7_0.md +46 -0
  96. data/doc/release_notes/0_8_0.md +27 -0
  97. data/doc/release_notes/0_8_1.md +8 -0
  98. data/doc/release_notes/0_8_2.md +7 -0
  99. data/doc/release_notes/0_9_0.md +38 -0
  100. data/doc/release_notes/1_0_0.md +60 -0
  101. data/doc/release_notes/1_0_1.md +5 -0
  102. data/doc/release_notes/1_0_2.md +7 -0
  103. data/doc/release_notes/1_1_0.md +32 -0
  104. data/doc/release_notes/1_1_1.md +17 -0
  105. data/doc/release_notes/1_1_2.md +12 -0
  106. data/doc/release_notes/1_1_3.md +18 -0
  107. data/doc/release_notes/1_1_4.md +6 -0
  108. data/doc/release_notes/1_1_5.md +12 -0
  109. data/doc/release_notes/1_2_0.md +49 -0
  110. data/doc/release_notes/1_2_1.md +6 -0
  111. data/doc/release_notes/1_2_2.md +10 -0
  112. data/doc/release_notes/1_2_3.md +16 -0
  113. data/doc/release_notes/1_2_4.md +8 -0
  114. data/doc/release_notes/1_2_5.md +7 -0
  115. data/doc/release_notes/1_2_6.md +13 -0
  116. data/doc/release_notes/1_3_0.md +18 -0
  117. data/doc/release_notes/1_3_1.md +17 -0
  118. data/doc/release_notes/1_3_2.md +6 -0
  119. data/doc/release_notes/1_3_3.md +5 -0
  120. data/doc/release_notes/1_3_4.md +6 -0
  121. data/doc/release_notes/1_4_0.md +43 -0
  122. data/doc/release_notes/1_4_1.md +19 -0
  123. data/doc/release_notes/1_4_2.md +20 -0
  124. data/doc/release_notes/1_4_3.md +11 -0
  125. data/doc/release_notes/1_4_4.md +14 -0
  126. data/doc/release_notes/1_5_0.md +126 -0
  127. data/doc/release_notes/1_5_1.md +6 -0
  128. data/doc/release_notes/1_6_0.md +50 -0
  129. data/doc/release_notes/1_6_1.md +17 -0
  130. data/doc/release_notes/1_6_2.md +11 -0
  131. data/lib/httpx/adapters/datadog.rb +359 -0
  132. data/lib/httpx/adapters/faraday.rb +303 -0
  133. data/lib/httpx/adapters/sentry.rb +121 -0
  134. data/lib/httpx/adapters/webmock.rb +175 -0
  135. data/lib/httpx/altsvc.rb +163 -0
  136. data/lib/httpx/base64.rb +27 -0
  137. data/lib/httpx/buffer.rb +61 -0
  138. data/lib/httpx/callbacks.rb +35 -0
  139. data/lib/httpx/chainable.rb +106 -0
  140. data/lib/httpx/connection/http1.rb +399 -0
  141. data/lib/httpx/connection/http2.rb +468 -0
  142. data/lib/httpx/connection.rb +954 -0
  143. data/lib/httpx/domain_name.rb +145 -0
  144. data/lib/httpx/errors.rb +111 -0
  145. data/lib/httpx/extensions.rb +59 -0
  146. data/lib/httpx/headers.rb +176 -0
  147. data/lib/httpx/io/ssl.rb +163 -0
  148. data/lib/httpx/io/tcp.rb +239 -0
  149. data/lib/httpx/io/udp.rb +62 -0
  150. data/lib/httpx/io/unix.rb +71 -0
  151. data/lib/httpx/io.rb +11 -0
  152. data/lib/httpx/loggable.rb +56 -0
  153. data/lib/httpx/options.rb +463 -0
  154. data/lib/httpx/parser/http1.rb +186 -0
  155. data/lib/httpx/plugins/auth/basic.rb +20 -0
  156. data/lib/httpx/plugins/auth/digest.rb +102 -0
  157. data/lib/httpx/plugins/auth/ntlm.rb +35 -0
  158. data/lib/httpx/plugins/auth/socks5.rb +22 -0
  159. data/lib/httpx/plugins/auth.rb +25 -0
  160. data/lib/httpx/plugins/aws_sdk_authentication.rb +111 -0
  161. data/lib/httpx/plugins/aws_sigv4.rb +239 -0
  162. data/lib/httpx/plugins/basic_auth.rb +29 -0
  163. data/lib/httpx/plugins/brotli.rb +50 -0
  164. data/lib/httpx/plugins/callbacks.rb +127 -0
  165. data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
  166. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
  167. data/lib/httpx/plugins/circuit_breaker.rb +147 -0
  168. data/lib/httpx/plugins/content_digest.rb +204 -0
  169. data/lib/httpx/plugins/cookies/cookie.rb +174 -0
  170. data/lib/httpx/plugins/cookies/jar.rb +95 -0
  171. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +143 -0
  172. data/lib/httpx/plugins/cookies.rb +107 -0
  173. data/lib/httpx/plugins/digest_auth.rb +67 -0
  174. data/lib/httpx/plugins/expect.rb +120 -0
  175. data/lib/httpx/plugins/fiber_concurrency.rb +195 -0
  176. data/lib/httpx/plugins/follow_redirects.rb +233 -0
  177. data/lib/httpx/plugins/grpc/call.rb +63 -0
  178. data/lib/httpx/plugins/grpc/grpc_encoding.rb +90 -0
  179. data/lib/httpx/plugins/grpc/message.rb +55 -0
  180. data/lib/httpx/plugins/grpc.rb +282 -0
  181. data/lib/httpx/plugins/h2c.rb +127 -0
  182. data/lib/httpx/plugins/internal_telemetry.rb +107 -0
  183. data/lib/httpx/plugins/ntlm_auth.rb +62 -0
  184. data/lib/httpx/plugins/oauth.rb +183 -0
  185. data/lib/httpx/plugins/persistent.rb +82 -0
  186. data/lib/httpx/plugins/proxy/http.rb +184 -0
  187. data/lib/httpx/plugins/proxy/socks4.rb +135 -0
  188. data/lib/httpx/plugins/proxy/socks5.rb +194 -0
  189. data/lib/httpx/plugins/proxy/ssh.rb +94 -0
  190. data/lib/httpx/plugins/proxy.rb +349 -0
  191. data/lib/httpx/plugins/push_promise.rb +81 -0
  192. data/lib/httpx/plugins/query.rb +35 -0
  193. data/lib/httpx/plugins/rate_limiter.rb +55 -0
  194. data/lib/httpx/plugins/response_cache/file_store.rb +140 -0
  195. data/lib/httpx/plugins/response_cache/store.rb +33 -0
  196. data/lib/httpx/plugins/response_cache.rb +333 -0
  197. data/lib/httpx/plugins/retries.rb +230 -0
  198. data/lib/httpx/plugins/ssrf_filter.rb +145 -0
  199. data/lib/httpx/plugins/stream.rb +183 -0
  200. data/lib/httpx/plugins/stream_bidi.rb +315 -0
  201. data/lib/httpx/plugins/upgrade/h2.rb +64 -0
  202. data/lib/httpx/plugins/upgrade.rb +86 -0
  203. data/lib/httpx/plugins/webdav.rb +86 -0
  204. data/lib/httpx/plugins/xml.rb +76 -0
  205. data/lib/httpx/pmatch_extensions.rb +33 -0
  206. data/lib/httpx/pool.rb +190 -0
  207. data/lib/httpx/punycode.rb +22 -0
  208. data/lib/httpx/request/body.rb +158 -0
  209. data/lib/httpx/request.rb +328 -0
  210. data/lib/httpx/resolver/entry.rb +30 -0
  211. data/lib/httpx/resolver/https.rb +256 -0
  212. data/lib/httpx/resolver/multi.rb +102 -0
  213. data/lib/httpx/resolver/native.rb +547 -0
  214. data/lib/httpx/resolver/resolver.rb +173 -0
  215. data/lib/httpx/resolver/system.rb +255 -0
  216. data/lib/httpx/resolver.rb +189 -0
  217. data/lib/httpx/response/body.rb +242 -0
  218. data/lib/httpx/response/buffer.rb +115 -0
  219. data/lib/httpx/response.rb +304 -0
  220. data/lib/httpx/selector.rb +282 -0
  221. data/lib/httpx/session.rb +612 -0
  222. data/lib/httpx/session_extensions.rb +30 -0
  223. data/lib/httpx/timers.rb +133 -0
  224. data/lib/httpx/transcoder/body.rb +43 -0
  225. data/lib/httpx/transcoder/chunker.rb +115 -0
  226. data/lib/httpx/transcoder/deflate.rb +37 -0
  227. data/lib/httpx/transcoder/form.rb +68 -0
  228. data/lib/httpx/transcoder/gzip.rb +71 -0
  229. data/lib/httpx/transcoder/json.rb +71 -0
  230. data/lib/httpx/transcoder/multipart/decoder.rb +141 -0
  231. data/lib/httpx/transcoder/multipart/encoder.rb +120 -0
  232. data/lib/httpx/transcoder/multipart/mime_type_detector.rb +78 -0
  233. data/lib/httpx/transcoder/multipart/part.rb +35 -0
  234. data/lib/httpx/transcoder/multipart.rb +31 -0
  235. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  236. data/lib/httpx/transcoder/utils/deflater.rb +75 -0
  237. data/lib/httpx/transcoder.rb +91 -0
  238. data/lib/httpx/utils.rb +75 -0
  239. data/lib/httpx/version.rb +5 -0
  240. data/lib/httpx.rb +66 -0
  241. data/sig/altsvc.rbs +33 -0
  242. data/sig/buffer.rbs +27 -0
  243. data/sig/callbacks.rbs +15 -0
  244. data/sig/chainable.rbs +55 -0
  245. data/sig/connection/http1.rbs +85 -0
  246. data/sig/connection/http2.rbs +116 -0
  247. data/sig/connection.rbs +169 -0
  248. data/sig/domain_name.rbs +17 -0
  249. data/sig/errors.rbs +69 -0
  250. data/sig/headers.rbs +49 -0
  251. data/sig/httpx.rbs +27 -0
  252. data/sig/io/ssl.rbs +27 -0
  253. data/sig/io/tcp.rbs +72 -0
  254. data/sig/io/udp.rbs +25 -0
  255. data/sig/io/unix.rbs +26 -0
  256. data/sig/io.rbs +3 -0
  257. data/sig/loggable.rbs +17 -0
  258. data/sig/options.rbs +202 -0
  259. data/sig/parser/http1.rbs +59 -0
  260. data/sig/plugins/auth/basic.rbs +17 -0
  261. data/sig/plugins/auth/digest.rbs +25 -0
  262. data/sig/plugins/auth/ntlm.rbs +20 -0
  263. data/sig/plugins/auth/socks5.rbs +18 -0
  264. data/sig/plugins/auth.rbs +13 -0
  265. data/sig/plugins/aws_sdk_authentication.rbs +43 -0
  266. data/sig/plugins/aws_sigv4.rbs +78 -0
  267. data/sig/plugins/basic_auth.rbs +15 -0
  268. data/sig/plugins/brotli.rbs +22 -0
  269. data/sig/plugins/callbacks.rbs +38 -0
  270. data/sig/plugins/circuit_breaker.rbs +71 -0
  271. data/sig/plugins/compression.rbs +57 -0
  272. data/sig/plugins/content_digest.rbs +51 -0
  273. data/sig/plugins/cookies/cookie.rbs +55 -0
  274. data/sig/plugins/cookies/jar.rbs +26 -0
  275. data/sig/plugins/cookies/set_cookie_parser.rbs +22 -0
  276. data/sig/plugins/cookies.rbs +28 -0
  277. data/sig/plugins/digest_auth.rbs +21 -0
  278. data/sig/plugins/expect.rbs +15 -0
  279. data/sig/plugins/fiber_concurrency.rbs +51 -0
  280. data/sig/plugins/follow_redirects.rbs +47 -0
  281. data/sig/plugins/grpc/call.rbs +23 -0
  282. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  283. data/sig/plugins/grpc/message.rbs +17 -0
  284. data/sig/plugins/grpc.rbs +65 -0
  285. data/sig/plugins/h2c.rbs +27 -0
  286. data/sig/plugins/ntlm_auth.rbs +21 -0
  287. data/sig/plugins/oauth.rbs +68 -0
  288. data/sig/plugins/persistent.rbs +14 -0
  289. data/sig/plugins/proxy/http.rbs +30 -0
  290. data/sig/plugins/proxy/socks4.rbs +37 -0
  291. data/sig/plugins/proxy/socks5.rbs +49 -0
  292. data/sig/plugins/proxy/ssh.rbs +18 -0
  293. data/sig/plugins/proxy.rbs +70 -0
  294. data/sig/plugins/push_promise.rbs +23 -0
  295. data/sig/plugins/query.rbs +18 -0
  296. data/sig/plugins/rate_limiter.rbs +13 -0
  297. data/sig/plugins/response_cache/file_store.rbs +19 -0
  298. data/sig/plugins/response_cache/store.rbs +13 -0
  299. data/sig/plugins/response_cache.rbs +86 -0
  300. data/sig/plugins/retries.rbs +66 -0
  301. data/sig/plugins/ssrf_filter.rbs +26 -0
  302. data/sig/plugins/stream.rbs +54 -0
  303. data/sig/plugins/stream_bidi.rbs +68 -0
  304. data/sig/plugins/upgrade/h2.rbs +9 -0
  305. data/sig/plugins/upgrade.rbs +29 -0
  306. data/sig/plugins/webdav.rbs +23 -0
  307. data/sig/plugins/xml.rbs +37 -0
  308. data/sig/pool.rbs +51 -0
  309. data/sig/punycode.rbs +5 -0
  310. data/sig/request/body.rbs +34 -0
  311. data/sig/request.rbs +88 -0
  312. data/sig/resolver/entry.rbs +13 -0
  313. data/sig/resolver/https.rbs +45 -0
  314. data/sig/resolver/multi.rbs +32 -0
  315. data/sig/resolver/native.rbs +74 -0
  316. data/sig/resolver/resolver.rbs +64 -0
  317. data/sig/resolver/system.rbs +34 -0
  318. data/sig/resolver.rbs +48 -0
  319. data/sig/response/body.rbs +52 -0
  320. data/sig/response/buffer.rbs +23 -0
  321. data/sig/response.rbs +103 -0
  322. data/sig/selector.rbs +68 -0
  323. data/sig/session.rbs +104 -0
  324. data/sig/timers.rbs +54 -0
  325. data/sig/transcoder/body.rbs +24 -0
  326. data/sig/transcoder/chunker.rbs +49 -0
  327. data/sig/transcoder/deflate.rbs +12 -0
  328. data/sig/transcoder/form.rbs +34 -0
  329. data/sig/transcoder/gzip.rbs +27 -0
  330. data/sig/transcoder/json.rbs +28 -0
  331. data/sig/transcoder/multipart.rbs +103 -0
  332. data/sig/transcoder/utils/body_reader.rbs +15 -0
  333. data/sig/transcoder/utils/deflater.rbs +28 -0
  334. data/sig/transcoder.rbs +43 -0
  335. data/sig/utils.rbs +19 -0
  336. metadata +518 -0
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
5
+ module HTTPX
6
+ module Resolver
7
+ class Entry < SimpleDelegator
8
+ attr_reader :address
9
+
10
+ def self.convert(address)
11
+ new(address, rescue_on_convert: true)
12
+ end
13
+
14
+ def initialize(address, expires_in = Float::INFINITY, rescue_on_convert: false)
15
+ @expires_in = expires_in
16
+ @address = address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
17
+ super(@address)
18
+ rescue IPAddr::InvalidAddressError
19
+ raise unless rescue_on_convert
20
+
21
+ @address = address.to_s
22
+ super(@address)
23
+ end
24
+
25
+ def expired?
26
+ @expires_in < Utils.now
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+ require "uri"
5
+ require "forwardable"
6
+ require "httpx/base64"
7
+
8
+ module HTTPX
9
+ # Implementation of a DoH name resolver (https://www.youtube.com/watch?v=unMXvnY2FNM).
10
+ # It wraps an HTTPX::Connection object which integrates with the main session in the
11
+ # same manner as other performed HTTP requests.
12
+ #
13
+ class Resolver::HTTPS < Resolver::Resolver
14
+ extend Forwardable
15
+ using URIExtensions
16
+
17
+ module DNSExtensions
18
+ refine Resolv::DNS do
19
+ def generate_candidates(name)
20
+ @config.generate_candidates(name)
21
+ end
22
+ end
23
+ end
24
+ using DNSExtensions
25
+
26
+ NAMESERVER = "https://1.1.1.1/dns-query"
27
+
28
+ DEFAULTS = {
29
+ uri: NAMESERVER,
30
+ use_get: false,
31
+ }.freeze
32
+
33
+ def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate, :inflight?, :handle_socket_timeout
34
+
35
+ def initialize(_, options)
36
+ super
37
+ @resolver_options = DEFAULTS.merge(@options.resolver_options)
38
+ @queries = {}
39
+ @requests = {}
40
+ @uri = URI(@resolver_options[:uri])
41
+ @uri_addresses = nil
42
+ @resolver = Resolv::DNS.new
43
+ @resolver.timeouts = @resolver_options.fetch(:timeouts, Resolver::RESOLVE_TIMEOUT)
44
+ @resolver.lazy_initialize
45
+ end
46
+
47
+ def <<(connection)
48
+ return if @uri.origin == connection.peer.to_s
49
+
50
+ @uri_addresses ||= HTTPX::Resolver.nolookup_resolve(@uri.host) || @resolver.getaddresses(@uri.host)
51
+
52
+ if @uri_addresses.empty?
53
+ ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
54
+ ex.set_backtrace(caller)
55
+ connection.force_reset
56
+ throw(:resolve_error, ex)
57
+ end
58
+
59
+ resolve(connection)
60
+ end
61
+
62
+ # This is already indirectly monitored bt the HTTP connection. In order to skip
63
+ # monitoring, this method returns <tt>true</tt>.
64
+ def closed?
65
+ true
66
+ end
67
+
68
+ def empty?
69
+ true
70
+ end
71
+
72
+ def resolver_connection
73
+ # TODO: leaks connection object into the pool
74
+ @resolver_connection ||= @current_session.find_connection(@uri, @current_selector,
75
+ @options.merge(ssl: { alpn_protocols: %w[h2] })).tap do |conn|
76
+ emit_addresses(conn, @family, @uri_addresses) unless conn.addresses
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def resolve(connection = nil, hostname = nil)
83
+ @connections.shift until @connections.empty? || @connections.first.state != :closed
84
+
85
+ connection ||= @connections.first
86
+
87
+ return unless connection
88
+
89
+ hostname ||= @queries.key(connection)
90
+
91
+ if hostname.nil?
92
+ hostname = connection.peer.host
93
+ log do
94
+ "resolver #{FAMILY_TYPES[@record_type]}: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
95
+ end if connection.peer.non_ascii_hostname
96
+
97
+ hostname = @resolver.generate_candidates(hostname).each do |name|
98
+ @queries[name.to_s] = connection
99
+ end.first.to_s
100
+ else
101
+ @queries[hostname] = connection
102
+ end
103
+ log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
104
+
105
+ begin
106
+ request = build_request(hostname)
107
+ request.on(:response, &method(:on_response).curry(2)[request])
108
+ request.on(:promise, &method(:on_promise))
109
+ @requests[request] = hostname
110
+ resolver_connection.send(request)
111
+ @connections << connection
112
+ rescue ResolveError, Resolv::DNS::EncodeError => e
113
+ reset_hostname(hostname)
114
+ emit_resolve_error(connection, connection.peer.host, e)
115
+ end
116
+ end
117
+
118
+ def on_response(request, response)
119
+ response.raise_for_status
120
+ rescue StandardError => e
121
+ hostname = @requests.delete(request)
122
+ connection = reset_hostname(hostname)
123
+ emit_resolve_error(connection, connection.peer.host, e)
124
+ else
125
+ # @type var response: HTTPX::Response
126
+ parse(request, response)
127
+ ensure
128
+ @requests.delete(request)
129
+ end
130
+
131
+ def on_promise(_, stream)
132
+ log(level: 2) { "#{stream.id}: refusing stream!" }
133
+ stream.refuse
134
+ end
135
+
136
+ def parse(request, response)
137
+ code, result = decode_response_body(response)
138
+
139
+ case code
140
+ when :ok
141
+ parse_addresses(result, request)
142
+ when :no_domain_found
143
+ # Indicates no such domain was found.
144
+
145
+ host = @requests.delete(request)
146
+ connection = reset_hostname(host, reset_candidates: false)
147
+
148
+ unless @queries.value?(connection)
149
+ emit_resolve_error(connection)
150
+ return
151
+ end
152
+
153
+ resolve
154
+ when :dns_error
155
+ host = @requests.delete(request)
156
+ connection = reset_hostname(host)
157
+
158
+ emit_resolve_error(connection)
159
+ when :decode_error
160
+ host = @requests.delete(request)
161
+ connection = reset_hostname(host)
162
+ emit_resolve_error(connection, connection.peer.host, result)
163
+ end
164
+ end
165
+
166
+ def parse_addresses(answers, request)
167
+ if answers.empty?
168
+ # no address found, eliminate candidates
169
+ host = @requests.delete(request)
170
+ connection = reset_hostname(host)
171
+ emit_resolve_error(connection)
172
+ return
173
+
174
+ else
175
+ answers = answers.group_by { |answer| answer["name"] }
176
+ answers.each do |hostname, addresses|
177
+ addresses = addresses.flat_map do |address|
178
+ if address.key?("alias")
179
+ alias_address = answers[address["alias"]]
180
+ if alias_address.nil?
181
+ reset_hostname(address["name"])
182
+ if early_resolve(connection, hostname: address["alias"])
183
+ @connections.delete(connection)
184
+ else
185
+ resolve(connection, address["alias"])
186
+ return # rubocop:disable Lint/NonLocalExitFromIterator
187
+ end
188
+ else
189
+ alias_address
190
+ end
191
+ else
192
+ address
193
+ end
194
+ end.compact
195
+ next if addresses.empty?
196
+
197
+ hostname.delete_suffix!(".") if hostname.end_with?(".")
198
+ connection = reset_hostname(hostname, reset_candidates: false)
199
+ next unless connection # probably a retried query for which there's an answer
200
+
201
+ @connections.delete(connection)
202
+
203
+ # eliminate other candidates
204
+ @queries.delete_if { |_, conn| connection == conn }
205
+
206
+ Resolver.cached_lookup_set(hostname, @family, addresses) if @resolver_options[:cache]
207
+ catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) }) }
208
+ end
209
+ end
210
+ return if @connections.empty?
211
+
212
+ resolve
213
+ end
214
+
215
+ def build_request(hostname)
216
+ uri = @uri.dup
217
+ rklass = @options.request_class
218
+ payload = Resolver.encode_dns_query(hostname, type: @record_type)
219
+
220
+ if @resolver_options[:use_get]
221
+ params = URI.decode_www_form(uri.query.to_s)
222
+ params << ["type", FAMILY_TYPES[@record_type]]
223
+ params << ["dns", Base64.urlsafe_encode64(payload, padding: false)]
224
+ uri.query = URI.encode_www_form(params)
225
+ request = rklass.new("GET", uri, @options)
226
+ else
227
+ request = rklass.new("POST", uri, @options, body: [payload])
228
+ request.headers["content-type"] = "application/dns-message"
229
+ end
230
+ request.headers["accept"] = "application/dns-message"
231
+ request
232
+ end
233
+
234
+ def decode_response_body(response)
235
+ case response.headers["content-type"]
236
+ when "application/dns-udpwireformat",
237
+ "application/dns-message"
238
+ Resolver.decode_dns_answer(response.to_s)
239
+ else
240
+ raise Error, "unsupported DNS mime-type (#{response.headers["content-type"]})"
241
+ end
242
+ end
243
+
244
+ def reset_hostname(hostname, reset_candidates: true)
245
+ connection = @queries.delete(hostname)
246
+
247
+ return connection unless connection && reset_candidates
248
+
249
+ # eliminate other candidates
250
+ candidates = @queries.select { |_, conn| connection == conn }.keys
251
+ @queries.delete_if { |h, _| candidates.include?(h) }
252
+
253
+ connection
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "resolv"
5
+
6
+ module HTTPX
7
+ class Resolver::Multi
8
+ include Callbacks
9
+ using ArrayExtensions::FilterMap
10
+
11
+ attr_reader :resolvers, :options
12
+
13
+ def initialize(resolver_type, options)
14
+ @current_selector = @current_session = nil
15
+ @options = options
16
+ @resolver_options = @options.resolver_options
17
+
18
+ ip_families = options.ip_families || Resolver.supported_ip_families
19
+
20
+ @resolvers = ip_families.map do |ip_family|
21
+ resolver = resolver_type.new(ip_family, options)
22
+ resolver.multi = self
23
+ resolver
24
+ end
25
+
26
+ @errors = Hash.new { |hs, k| hs[k] = [] }
27
+ end
28
+
29
+ def current_selector=(s)
30
+ @current_selector = s
31
+ @resolvers.each { |r| r.__send__(__method__, s) }
32
+ end
33
+
34
+ def current_session=(s)
35
+ @current_session = s
36
+ @resolvers.each { |r| r.__send__(__method__, s) }
37
+ end
38
+
39
+ def log(*args, **kwargs, &blk)
40
+ @resolvers.each { |r| r.__send__(__method__, *args, **kwargs, &blk) }
41
+ end
42
+
43
+ def closed?
44
+ @resolvers.all?(&:closed?)
45
+ end
46
+
47
+ def empty?
48
+ @resolvers.all?(&:empty?)
49
+ end
50
+
51
+ def inflight?
52
+ @resolvers.any(&:inflight?)
53
+ end
54
+
55
+ def timeout
56
+ @resolvers.filter_map(&:timeout).min
57
+ end
58
+
59
+ def close
60
+ @resolvers.each(&:close)
61
+ end
62
+
63
+ def connections
64
+ @resolvers.filter_map { |r| r.resolver_connection if r.respond_to?(:resolver_connection) }
65
+ end
66
+
67
+ def early_resolve(connection)
68
+ hostname = connection.peer.host
69
+ addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
70
+ return false unless addresses
71
+
72
+ ip_families = connection.options.ip_families
73
+
74
+ resolved = false
75
+ addresses.group_by(&:family).sort { |(f1, _), (f2, _)| f2 <=> f1 }.each do |family, addrs|
76
+ next unless ip_families.nil? || ip_families.include?(family)
77
+
78
+ # try to match the resolver by family. However, there are cases where that's not possible, as when
79
+ # the system does not have IPv6 connectivity, but it does support IPv6 via loopback/link-local.
80
+ resolver = @resolvers.find { |r| r.family == family } || @resolvers.first
81
+
82
+ next unless resolver # this should ever happen
83
+
84
+ # it does not matter which resolver it is, as early-resolve code is shared.
85
+ resolver.emit_addresses(connection, family, addrs, true)
86
+
87
+ resolved = true
88
+ end
89
+
90
+ resolved
91
+ end
92
+
93
+ def lazy_resolve(connection)
94
+ @resolvers.each do |resolver|
95
+ resolver << @current_session.try_clone_connection(connection, @current_selector, resolver.family)
96
+ next if resolver.empty?
97
+
98
+ @current_session.select_resolver(resolver, @current_selector)
99
+ end
100
+ end
101
+ end
102
+ end