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,547 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "resolv"
5
+
6
+ module HTTPX
7
+ # Implements a pure ruby name resolver, which abides by the Selectable API.
8
+ # It delegates DNS payload encoding/decoding to the +resolv+ stlid gem.
9
+ #
10
+ class Resolver::Native < Resolver::Resolver
11
+ extend Forwardable
12
+ using URIExtensions
13
+
14
+ DEFAULTS = {
15
+ nameserver: nil,
16
+ **Resolv::DNS::Config.default_config_hash,
17
+ packet_size: 512,
18
+ timeouts: Resolver::RESOLVE_TIMEOUT,
19
+ }.freeze
20
+
21
+ DNS_PORT = 53
22
+
23
+ def_delegator :@connections, :empty?
24
+
25
+ attr_reader :state
26
+
27
+ def initialize(family, options)
28
+ super
29
+ @ns_index = 0
30
+ @resolver_options = DEFAULTS.merge(@options.resolver_options)
31
+ @socket_type = @resolver_options.fetch(:socket_type, :udp)
32
+ @nameserver = if (nameserver = @resolver_options[:nameserver])
33
+ nameserver = nameserver[family] if nameserver.is_a?(Hash)
34
+ Array(nameserver)
35
+ end
36
+ @ndots = @resolver_options.fetch(:ndots, 1)
37
+ @search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) }
38
+ @_timeouts = Array(@resolver_options[:timeouts])
39
+ @timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
40
+ @name = nil
41
+ @queries = {}
42
+ @read_buffer = "".b
43
+ @write_buffer = Buffer.new(@resolver_options[:packet_size])
44
+ @state = :idle
45
+ @timer = nil
46
+ end
47
+
48
+ def close
49
+ transition(:closed)
50
+ end
51
+
52
+ def terminate
53
+ emit(:close, self)
54
+ end
55
+
56
+ def closed?
57
+ @state == :closed
58
+ end
59
+
60
+ def to_io
61
+ @io.to_io
62
+ end
63
+
64
+ def call
65
+ case @state
66
+ when :open
67
+ consume
68
+ end
69
+ end
70
+
71
+ def interests
72
+ case @state
73
+ when :idle
74
+ transition(:open)
75
+ when :closed
76
+ transition(:idle)
77
+ transition(:open)
78
+ end
79
+
80
+ calculate_interests
81
+ end
82
+
83
+ def <<(connection)
84
+ if @nameserver.nil?
85
+ ex = ResolveError.new("No available nameserver")
86
+ ex.set_backtrace(caller)
87
+ connection.force_reset
88
+ throw(:resolve_error, ex)
89
+ else
90
+ @connections << connection
91
+ resolve
92
+ end
93
+ end
94
+
95
+ def timeout
96
+ return if @connections.empty?
97
+
98
+ @start_timeout = Utils.now
99
+ hosts = @queries.keys
100
+ @timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
101
+ end
102
+
103
+ def handle_socket_timeout(interval); end
104
+
105
+ private
106
+
107
+ def calculate_interests
108
+ return if @queries.empty?
109
+
110
+ return :r if @write_buffer.empty?
111
+
112
+ :w
113
+ end
114
+
115
+ def consume
116
+ loop do
117
+ dread if calculate_interests == :r
118
+
119
+ break unless calculate_interests == :w
120
+
121
+ # do_retry
122
+ dwrite
123
+
124
+ break unless calculate_interests == :r
125
+ end
126
+ rescue Errno::EHOSTUNREACH => e
127
+ @ns_index += 1
128
+ nameserver = @nameserver
129
+ if nameserver && @ns_index < nameserver.size
130
+ log { "resolver #{FAMILY_TYPES[@record_type]}: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" }
131
+ transition(:idle)
132
+ @timeouts.clear
133
+ retry
134
+ else
135
+ handle_error(e)
136
+ emit(:close, self)
137
+ end
138
+ rescue NativeResolveError => e
139
+ handle_error(e)
140
+ close_or_resolve
141
+ retry unless closed?
142
+ end
143
+
144
+ def schedule_retry
145
+ h = @name
146
+
147
+ return unless h
148
+
149
+ connection = @queries[h]
150
+
151
+ timeouts = @timeouts[h]
152
+ timeout = timeouts.shift
153
+
154
+ @timer = @current_selector.after(timeout) do
155
+ next unless @connections.include?(connection)
156
+
157
+ @timer = nil
158
+
159
+ do_retry(h, connection, timeout)
160
+ end
161
+ end
162
+
163
+ def do_retry(h, connection, interval)
164
+ timeouts = @timeouts[h]
165
+
166
+ if !timeouts.empty?
167
+ log { "resolver #{FAMILY_TYPES[@record_type]}: timeout after #{interval}s, retry (with #{timeouts.first}s) #{h}..." }
168
+ # must downgrade to tcp AND retry on same host as last
169
+ downgrade_socket
170
+ resolve(connection, h)
171
+ elsif @ns_index + 1 < @nameserver.size
172
+ # try on the next nameserver
173
+ @ns_index += 1
174
+ log do
175
+ "resolver #{FAMILY_TYPES[@record_type]}: failed resolving #{h} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)"
176
+ end
177
+ transition(:idle)
178
+ @timeouts.clear
179
+ resolve(connection, h)
180
+ else
181
+
182
+ @timeouts.delete(h)
183
+ reset_hostname(h, reset_candidates: false)
184
+
185
+ unless @queries.empty?
186
+ resolve(connection)
187
+ return
188
+ end
189
+
190
+ @connections.delete(connection)
191
+
192
+ host = connection.peer.host
193
+
194
+ # This loop_time passed to the exception is bogus. Ideally we would pass the total
195
+ # resolve timeout, including from the previous retries.
196
+ ex = ResolveTimeoutError.new(interval, "Timed out while resolving #{host}")
197
+ ex.set_backtrace(ex ? ex.backtrace : caller)
198
+ emit_resolve_error(connection, host, ex)
199
+
200
+ close_or_resolve
201
+ end
202
+ end
203
+
204
+ def dread(wsize = @resolver_options[:packet_size])
205
+ loop do
206
+ wsize = @large_packet.capacity if @large_packet
207
+
208
+ siz = @io.read(wsize, @read_buffer)
209
+
210
+ unless siz
211
+ ex = EOFError.new("descriptor closed")
212
+ ex.set_backtrace(caller)
213
+ raise ex
214
+ end
215
+
216
+ return unless siz.positive?
217
+
218
+ if @socket_type == :tcp
219
+ # packet may be incomplete, need to keep draining from the socket
220
+ if @large_packet
221
+ # large packet buffer already exists, continue pumping
222
+ @large_packet << @read_buffer
223
+
224
+ next unless @large_packet.full?
225
+
226
+ parse(@large_packet.to_s)
227
+ @large_packet = nil
228
+ # downgrade to udp again
229
+ downgrade_socket
230
+ return
231
+ else
232
+ size = @read_buffer[0, 2].unpack1("n")
233
+ buffer = @read_buffer.byteslice(2..-1)
234
+
235
+ if size > @read_buffer.bytesize
236
+ # only do buffer logic if it's worth it, and the whole packet isn't here already
237
+ @large_packet = Buffer.new(size)
238
+ @large_packet << buffer
239
+
240
+ next
241
+ else
242
+ parse(buffer)
243
+ end
244
+ end
245
+ else # udp
246
+ parse(@read_buffer)
247
+ end
248
+
249
+ return if @state == :closed || !@write_buffer.empty?
250
+ end
251
+ end
252
+
253
+ def dwrite
254
+ loop do
255
+ return if @write_buffer.empty?
256
+
257
+ siz = @io.write(@write_buffer)
258
+
259
+ unless siz
260
+ ex = EOFError.new("descriptor closed")
261
+ ex.set_backtrace(caller)
262
+ raise ex
263
+ end
264
+
265
+ return unless siz.positive?
266
+
267
+ schedule_retry if @write_buffer.empty?
268
+
269
+ return if @state == :closed
270
+ end
271
+ end
272
+
273
+ def parse(buffer)
274
+ @timer.cancel
275
+
276
+ @timer = nil
277
+
278
+ code, result = Resolver.decode_dns_answer(buffer)
279
+
280
+ case code
281
+ when :ok
282
+ parse_addresses(result)
283
+ when :no_domain_found
284
+ # Indicates no such domain was found.
285
+ hostname, connection = @queries.first
286
+ reset_hostname(hostname, reset_candidates: false)
287
+
288
+ other_candidate, _ = @queries.find { |_, conn| conn == connection }
289
+
290
+ if other_candidate
291
+ resolve(connection, other_candidate)
292
+ else
293
+ @connections.delete(connection)
294
+ ex = NativeResolveError.new(connection, connection.peer.host, "name or service not known")
295
+ ex.set_backtrace(ex ? ex.backtrace : caller)
296
+ emit_resolve_error(connection, connection.peer.host, ex)
297
+ close_or_resolve
298
+ end
299
+ when :message_truncated
300
+ # TODO: what to do if it's already tcp??
301
+ return if @socket_type == :tcp
302
+
303
+ @socket_type = :tcp
304
+
305
+ hostname, _ = @queries.first
306
+ reset_hostname(hostname)
307
+ transition(:closed)
308
+ when :dns_error
309
+ hostname, connection = @queries.first
310
+ reset_hostname(hostname)
311
+ @connections.delete(connection)
312
+ ex = NativeResolveError.new(connection, connection.peer.host, "unknown DNS error (error code #{result})")
313
+ raise ex
314
+ when :decode_error
315
+ hostname, connection = @queries.first
316
+ reset_hostname(hostname)
317
+ @connections.delete(connection)
318
+ ex = NativeResolveError.new(connection, connection.peer.host, result.message)
319
+ ex.set_backtrace(result.backtrace)
320
+ raise ex
321
+ end
322
+ end
323
+
324
+ def parse_addresses(addresses)
325
+ if addresses.empty?
326
+ # no address found, eliminate candidates
327
+ hostname, connection = @queries.first
328
+ reset_hostname(hostname)
329
+ @connections.delete(connection)
330
+ raise NativeResolveError.new(connection, connection.peer.host)
331
+ else
332
+ address = addresses.first
333
+ name = address["name"]
334
+
335
+ connection = @queries.delete(name)
336
+
337
+ unless connection
338
+ orig_name = name
339
+ # absolute name
340
+ name_labels = Resolv::DNS::Name.create(name).to_a
341
+ name = @queries.each_key.first { |hname| name_labels == Resolv::DNS::Name.create(hname).to_a }
342
+
343
+ # probably a retried query for which there's an answer
344
+ unless name
345
+ @timeouts.delete(orig_name)
346
+ return
347
+ end
348
+
349
+ address["name"] = name
350
+ connection = @queries.delete(name)
351
+ end
352
+
353
+ alias_addresses, addresses = addresses.partition { |addr| addr.key?("alias") }
354
+
355
+ if addresses.empty? && !alias_addresses.empty? # CNAME
356
+ hostname_alias = alias_addresses.first["alias"]
357
+ # clean up intermediate queries
358
+ @timeouts.delete(name) unless connection.peer.host == name
359
+
360
+ if early_resolve(connection, hostname: hostname_alias)
361
+ @connections.delete(connection)
362
+ else
363
+ if @socket_type == :tcp
364
+ # must downgrade to udp if tcp
365
+ @socket_type = @resolver_options.fetch(:socket_type, :udp)
366
+ transition(:idle)
367
+ transition(:open)
368
+ end
369
+ log { "resolver #{FAMILY_TYPES[@record_type]}: ALIAS #{hostname_alias} for #{name}" }
370
+ resolve(connection, hostname_alias)
371
+ return
372
+ end
373
+ else
374
+ reset_hostname(name, connection: connection)
375
+ @timeouts.delete(connection.peer.host)
376
+ @connections.delete(connection)
377
+ Resolver.cached_lookup_set(connection.peer.host, @family, addresses) if @resolver_options[:cache]
378
+ catch(:coalesced) do
379
+ emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) })
380
+ end
381
+ end
382
+ end
383
+ close_or_resolve
384
+ end
385
+
386
+ def resolve(connection = nil, hostname = nil)
387
+ @connections.shift until @connections.empty? || @connections.first.state != :closed
388
+
389
+ connection ||= @connections.find { |c| !@queries.value?(c) }
390
+
391
+ raise Error, "no URI to resolve" unless connection
392
+
393
+ # do not buffer query if previous is still in the buffer or awaiting reply/retry
394
+ return unless @write_buffer.empty? && @timer.nil?
395
+
396
+ hostname ||= @queries.key(connection)
397
+
398
+ if hostname.nil?
399
+ hostname = connection.peer.host
400
+ if connection.peer.non_ascii_hostname
401
+ log { "resolver #{FAMILY_TYPES[@record_type]}: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}" }
402
+ end
403
+
404
+ hostname = generate_candidates(hostname).each do |name|
405
+ @queries[name] = connection
406
+ end.first
407
+ else
408
+ @queries[hostname] = connection
409
+ end
410
+
411
+ @name = hostname
412
+
413
+ log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
414
+ begin
415
+ @write_buffer << encode_dns_query(hostname)
416
+ rescue Resolv::DNS::EncodeError => e
417
+ reset_hostname(hostname, connection: connection)
418
+ @connections.delete(connection)
419
+ emit_resolve_error(connection, hostname, e)
420
+ close_or_resolve
421
+ end
422
+ end
423
+
424
+ def encode_dns_query(hostname)
425
+ message_id = Resolver.generate_id
426
+ msg = Resolver.encode_dns_query(hostname, type: @record_type, message_id: message_id)
427
+ msg[0, 2] = [msg.size, message_id].pack("nn") if @socket_type == :tcp
428
+ msg
429
+ end
430
+
431
+ def generate_candidates(name)
432
+ return [name] if name.end_with?(".")
433
+
434
+ candidates = []
435
+ name_parts = name.scan(/[^.]+/)
436
+ candidates = [name] if @ndots <= name_parts.size - 1
437
+ candidates.concat(@search.map { |domain| [*name_parts, *domain].join(".") })
438
+ fname = "#{name}."
439
+ candidates << fname unless candidates.include?(fname)
440
+
441
+ candidates
442
+ end
443
+
444
+ def build_socket
445
+ ip, port = @nameserver[@ns_index]
446
+ port ||= DNS_PORT
447
+
448
+ case @socket_type
449
+ when :udp
450
+ log { "resolver #{FAMILY_TYPES[@record_type]}: server: udp://#{ip}:#{port}..." }
451
+ UDP.new(ip, port, @options)
452
+ when :tcp
453
+ log { "resolver #{FAMILY_TYPES[@record_type]}: server: tcp://#{ip}:#{port}..." }
454
+ origin = URI("tcp://#{ip}:#{port}")
455
+ TCP.new(origin, [Resolver::Entry.new(ip)], @options)
456
+ end
457
+ end
458
+
459
+ def downgrade_socket
460
+ return unless @socket_type == :tcp
461
+
462
+ @socket_type = @resolver_options.fetch(:socket_type, :udp)
463
+ transition(:idle)
464
+ transition(:open)
465
+ end
466
+
467
+ def transition(nextstate)
468
+ case nextstate
469
+ when :idle
470
+ if @io
471
+ @io.close
472
+ @io = nil
473
+ end
474
+ when :open
475
+ return unless @state == :idle
476
+
477
+ @io ||= build_socket
478
+
479
+ @io.connect
480
+ return unless @io.connected?
481
+
482
+ resolve if @queries.empty? && !@connections.empty?
483
+ when :closed
484
+ return unless @state == :open
485
+
486
+ @io.close if @io
487
+ @start_timeout = nil
488
+ @write_buffer.clear
489
+ @read_buffer.clear
490
+ end
491
+ log(level: 3) { "#{@state} -> #{nextstate}" }
492
+ @state = nextstate
493
+ rescue Errno::ECONNREFUSED,
494
+ Errno::EADDRNOTAVAIL,
495
+ Errno::EHOSTUNREACH,
496
+ SocketError,
497
+ IOError,
498
+ ConnectTimeoutError => e
499
+ # these errors may happen during TCP handshake
500
+ # treat them as resolve errors.
501
+ handle_error(e)
502
+ emit(:close, self)
503
+ end
504
+
505
+ def handle_error(error)
506
+ if error.respond_to?(:connection) &&
507
+ error.respond_to?(:host)
508
+ reset_hostname(error.host, connection: error.connection)
509
+ @connections.delete(error.connection)
510
+ emit_resolve_error(error.connection, error.host, error)
511
+ else
512
+ @queries.each do |host, connection|
513
+ reset_hostname(host, connection: connection)
514
+ @connections.delete(connection)
515
+ emit_resolve_error(connection, host, error)
516
+ end
517
+
518
+ while (connection = @connections.shift)
519
+ emit_resolve_error(connection, connection.peer.host, error)
520
+ end
521
+ end
522
+ end
523
+
524
+ def reset_hostname(hostname, connection: @queries.delete(hostname), reset_candidates: true)
525
+ @timeouts.delete(hostname)
526
+
527
+ return unless connection && reset_candidates
528
+
529
+ # eliminate other candidates
530
+ candidates = @queries.select { |_, conn| connection == conn }.keys
531
+ @queries.delete_if { |h, _| candidates.include?(h) }
532
+ # reset timeouts
533
+ @timeouts.delete_if { |h, _| candidates.include?(h) }
534
+ end
535
+
536
+ def close_or_resolve
537
+ # drop already closed connections
538
+ @connections.shift until @connections.empty? || @connections.first.state != :closed
539
+
540
+ if (@connections - @queries.values).empty?
541
+ emit(:close, self)
542
+ else
543
+ resolve
544
+ end
545
+ end
546
+ end
547
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+
5
+ module HTTPX
6
+ # Base class for all internal internet name resolvers. It handles basic blocks
7
+ # from the Selectable API.
8
+ #
9
+ class Resolver::Resolver
10
+ include Callbacks
11
+ include Loggable
12
+
13
+ using ArrayExtensions::Intersect
14
+
15
+ RECORD_TYPES = {
16
+ Socket::AF_INET6 => Resolv::DNS::Resource::IN::AAAA,
17
+ Socket::AF_INET => Resolv::DNS::Resource::IN::A,
18
+ }.freeze
19
+
20
+ FAMILY_TYPES = {
21
+ Resolv::DNS::Resource::IN::AAAA => "AAAA",
22
+ Resolv::DNS::Resource::IN::A => "A",
23
+ }.freeze
24
+
25
+ class << self
26
+ def multi?
27
+ true
28
+ end
29
+ end
30
+
31
+ attr_reader :family, :options
32
+
33
+ attr_writer :current_selector, :current_session
34
+
35
+ attr_accessor :multi
36
+
37
+ def initialize(family, options)
38
+ @family = family
39
+ @record_type = RECORD_TYPES[family]
40
+ @options = options
41
+ @connections = []
42
+
43
+ set_resolver_callbacks
44
+ end
45
+
46
+ def each_connection(&block)
47
+ enum_for(__method__) unless block
48
+
49
+ return unless @connections
50
+
51
+ @connections.each(&block)
52
+ end
53
+
54
+ def close; end
55
+
56
+ alias_method :terminate, :close
57
+
58
+ def closed?
59
+ true
60
+ end
61
+
62
+ def empty?
63
+ true
64
+ end
65
+
66
+ def inflight?
67
+ false
68
+ end
69
+
70
+ def emit_addresses(connection, family, addresses, early_resolve = false)
71
+ addresses.map! { |address| address.is_a?(Resolver::Entry) ? address : Resolver::Entry.new(address) }
72
+
73
+ # double emission check, but allow early resolution to work
74
+ conn_addrs = connection.addresses
75
+ return if !early_resolve && conn_addrs && (!conn_addrs.empty? && !addresses.intersect?(!conn_addrs))
76
+
77
+ log do
78
+ "resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: " \
79
+ "answer #{connection.peer.host}: #{addresses.inspect} (early resolve: #{early_resolve})"
80
+ end
81
+
82
+ # do not apply resolution delay for non-dns name resolution
83
+ if !early_resolve &&
84
+ # just in case...
85
+ @current_selector &&
86
+ # resolution delay only applies to IPv4
87
+ family == Socket::AF_INET &&
88
+ # connection already has addresses and initiated/ended handshake
89
+ !connection.io &&
90
+ # no need to delay if not supporting dual stack / multi-homed IP
91
+ (connection.options.ip_families || Resolver.supported_ip_families).size > 1 &&
92
+ # connection URL host is already the IP (early resolve included perhaps?)
93
+ addresses.first.to_s != connection.peer.host.to_s
94
+ log { "resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: applying resolution delay..." }
95
+
96
+ @current_selector.after(0.05) do
97
+ # double emission check
98
+ unless connection.addresses && addresses.intersect?(connection.addresses)
99
+ emit_resolved_connection(connection, addresses, early_resolve)
100
+ end
101
+ end
102
+ else
103
+ emit_resolved_connection(connection, addresses, early_resolve)
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def emit_resolved_connection(connection, addresses, early_resolve)
110
+ begin
111
+ connection.addresses = addresses
112
+
113
+ return if connection.state == :closed
114
+
115
+ emit(:resolve, connection)
116
+ rescue StandardError => e
117
+ if early_resolve
118
+ connection.force_reset
119
+ throw(:resolve_error, e)
120
+ else
121
+ emit(:error, connection, e)
122
+ end
123
+ end
124
+ end
125
+
126
+ def early_resolve(connection, hostname: connection.peer.host)
127
+ addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
128
+
129
+ return false unless addresses
130
+
131
+ addresses = addresses.select { |addr| addr.family == @family }
132
+
133
+ return false if addresses.empty?
134
+
135
+ emit_addresses(connection, @family, addresses, true)
136
+
137
+ true
138
+ end
139
+
140
+ def emit_resolve_error(connection, hostname = connection.peer.host, ex = nil)
141
+ emit_connection_error(connection, resolve_error(hostname, ex))
142
+ end
143
+
144
+ def resolve_error(hostname, ex = nil)
145
+ return ex if ex.is_a?(ResolveError) || ex.is_a?(ResolveTimeoutError)
146
+
147
+ message = ex ? ex.message : "Can't resolve #{hostname}"
148
+ error = ResolveError.new(message)
149
+ error.set_backtrace(ex ? ex.backtrace : caller)
150
+ error
151
+ end
152
+
153
+ def set_resolver_callbacks
154
+ on(:resolve, &method(:resolve_connection))
155
+ on(:error, &method(:emit_connection_error))
156
+ on(:close, &method(:close_resolver))
157
+ end
158
+
159
+ def resolve_connection(connection)
160
+ @current_session.__send__(:on_resolver_connection, connection, @current_selector)
161
+ end
162
+
163
+ def emit_connection_error(connection, error)
164
+ return connection.handle_connect_error(error) if connection.connecting?
165
+
166
+ connection.emit(:error, error)
167
+ end
168
+
169
+ def close_resolver(resolver)
170
+ @current_session.__send__(:on_resolver_close, resolver, @current_selector)
171
+ end
172
+ end
173
+ end