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,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+
5
+ module HTTPX
6
+ # Implementation of a synchronous name resolver which relies on the system resolver,
7
+ # which is lib'c getaddrinfo function (abstracted in ruby via Addrinfo.getaddrinfo).
8
+ #
9
+ # Its main advantage is relying on the reference implementation for name resolution
10
+ # across most/all OSs which deploy ruby (it's what TCPSocket also uses), its main
11
+ # disadvantage is the inability to set timeouts / check socket for readiness events,
12
+ # hence why it relies on using the Timeout module, which poses a lot of problems for
13
+ # the selector loop, specially when network is unstable.
14
+ #
15
+ class Resolver::System < Resolver::Resolver
16
+ using URIExtensions
17
+
18
+ RESOLV_ERRORS = [Resolv::ResolvError,
19
+ Resolv::DNS::Requester::RequestError,
20
+ Resolv::DNS::EncodeError,
21
+ Resolv::DNS::DecodeError].freeze
22
+
23
+ DONE = 1
24
+ ERROR = 2
25
+
26
+ class << self
27
+ def multi?
28
+ false
29
+ end
30
+ end
31
+
32
+ attr_reader :state
33
+
34
+ def initialize(options)
35
+ super(0, options)
36
+ @resolver_options = @options.resolver_options
37
+ resolv_options = @resolver_options.dup
38
+ timeouts = resolv_options.delete(:timeouts) || Resolver::RESOLVE_TIMEOUT
39
+ @_timeouts = Array(timeouts)
40
+ @timeouts = Hash.new { |tims, host| tims[host] = @_timeouts.dup }
41
+ resolv_options.delete(:cache)
42
+ @queries = []
43
+ @ips = []
44
+ @pipe_mutex = Thread::Mutex.new
45
+ @state = :idle
46
+ end
47
+
48
+ def resolvers
49
+ return enum_for(__method__) unless block_given?
50
+
51
+ yield self
52
+ end
53
+
54
+ def multi
55
+ self
56
+ end
57
+
58
+ def empty?
59
+ true
60
+ end
61
+
62
+ def close
63
+ transition(:closed)
64
+ end
65
+
66
+ def closed?
67
+ @state == :closed
68
+ end
69
+
70
+ def to_io
71
+ @pipe_read.to_io
72
+ end
73
+
74
+ def call
75
+ case @state
76
+ when :open
77
+ consume
78
+ end
79
+ nil
80
+ end
81
+
82
+ def interests
83
+ return if @queries.empty?
84
+
85
+ :r
86
+ end
87
+
88
+ def timeout
89
+ return unless @queries.empty?
90
+
91
+ _, connection = @queries.first
92
+
93
+ return unless connection
94
+
95
+ @timeouts[connection.peer.host].first
96
+ end
97
+
98
+ def <<(connection)
99
+ @connections << connection
100
+ resolve
101
+ end
102
+
103
+ def early_resolve(connection, **)
104
+ self << connection
105
+ true
106
+ end
107
+
108
+ def handle_socket_timeout(interval)
109
+ error = HTTPX::ResolveTimeoutError.new(interval, "timed out while waiting on select")
110
+ error.set_backtrace(caller)
111
+ @queries.each do |host, connection|
112
+ @connections.delete(connection)
113
+ emit_resolve_error(connection, host, error)
114
+ end
115
+
116
+ while (connection = @connections.shift)
117
+ emit_resolve_error(connection, connection.peer.host, error)
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def transition(nextstate)
124
+ case nextstate
125
+ when :idle
126
+ @timeouts.clear
127
+ when :open
128
+ return unless @state == :idle
129
+
130
+ @pipe_read, @pipe_write = IO.pipe
131
+ when :closed
132
+ return unless @state == :open
133
+
134
+ @pipe_write.close
135
+ @pipe_read.close
136
+ end
137
+ @state = nextstate
138
+ end
139
+
140
+ def consume
141
+ return if @connections.empty?
142
+
143
+ if @pipe_read.wait_readable
144
+ event = @pipe_read.getbyte
145
+
146
+ case event
147
+ when DONE
148
+ *pair, addrs = @pipe_mutex.synchronize { @ips.pop }
149
+ if pair
150
+ @queries.delete(pair)
151
+ family, connection = pair
152
+ @connections.delete(connection)
153
+
154
+ catch(:coalesced) { emit_addresses(connection, family, addrs) }
155
+ end
156
+ when ERROR
157
+ *pair, error = @pipe_mutex.synchronize { @ips.pop }
158
+ if pair && error
159
+ @queries.delete(pair)
160
+ @connections.delete(connection)
161
+
162
+ _, connection = pair
163
+ emit_resolve_error(connection, connection.peer.host, error)
164
+ end
165
+ end
166
+ end
167
+
168
+ return emit(:close, self) if @connections.empty?
169
+
170
+ resolve
171
+ end
172
+
173
+ def resolve(connection = nil, hostname = nil)
174
+ @connections.shift until @connections.empty? || @connections.first.state != :closed
175
+
176
+ connection ||= @connections.first
177
+
178
+ raise Error, "no URI to resolve" unless connection
179
+
180
+ return unless @queries.empty?
181
+
182
+ hostname ||= connection.peer.host
183
+ scheme = connection.origin.scheme
184
+ log do
185
+ "resolver: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
186
+ end if connection.peer.non_ascii_hostname
187
+
188
+ transition(:open)
189
+
190
+ ip_families = connection.options.ip_families || Resolver.supported_ip_families
191
+
192
+ ip_families.each do |family|
193
+ @queries << [family, connection]
194
+ end
195
+ async_resolve(connection, hostname, scheme)
196
+ consume
197
+ end
198
+
199
+ def async_resolve(connection, hostname, scheme)
200
+ families = connection.options.ip_families || Resolver.supported_ip_families
201
+ log { "resolver: query for #{hostname}" }
202
+ timeouts = @timeouts[connection.peer.host]
203
+ resolve_timeout = timeouts.first
204
+
205
+ Thread.start do
206
+ Thread.current.report_on_exception = false
207
+ begin
208
+ addrs = if resolve_timeout
209
+
210
+ Timeout.timeout(resolve_timeout) do
211
+ __addrinfo_resolve(hostname, scheme)
212
+ end
213
+ else
214
+ __addrinfo_resolve(hostname, scheme)
215
+ end
216
+ addrs = addrs.sort_by(&:afamily).group_by(&:afamily)
217
+ families.each do |family|
218
+ addresses = addrs[family]
219
+ next unless addresses
220
+
221
+ addresses.map!(&:ip_address)
222
+ addresses.uniq!
223
+ @pipe_mutex.synchronize do
224
+ @ips.unshift([family, connection, addresses])
225
+ @pipe_write.putc(DONE) unless @pipe_write.closed?
226
+ end
227
+ end
228
+ rescue StandardError => e
229
+ if e.is_a?(Timeout::Error)
230
+ timeouts.shift
231
+ retry unless timeouts.empty?
232
+ e = ResolveTimeoutError.new(resolve_timeout, e.message)
233
+ e.set_backtrace(e.backtrace)
234
+ end
235
+ @pipe_mutex.synchronize do
236
+ families.each do |family|
237
+ @ips.unshift([family, connection, e])
238
+ @pipe_write.putc(ERROR) unless @pipe_write.closed?
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ def __addrinfo_resolve(host, scheme)
246
+ Addrinfo.getaddrinfo(host, scheme, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
247
+ end
248
+
249
+ def emit_connection_error(_, error)
250
+ throw(:resolve_error, error)
251
+ end
252
+
253
+ def close_resolver(resolver); end
254
+ end
255
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "resolv"
5
+
6
+ module HTTPX
7
+ module Resolver
8
+ RESOLVE_TIMEOUT = [2, 3].freeze
9
+
10
+ require "httpx/resolver/entry"
11
+ require "httpx/resolver/resolver"
12
+ require "httpx/resolver/system"
13
+ require "httpx/resolver/native"
14
+ require "httpx/resolver/https"
15
+ require "httpx/resolver/multi"
16
+
17
+ @lookup_mutex = Thread::Mutex.new
18
+ @lookups = Hash.new { |h, k| h[k] = [] }
19
+
20
+ @identifier_mutex = Thread::Mutex.new
21
+ @identifier = 1
22
+ @hosts_resolver = Resolv::Hosts.new
23
+
24
+ module_function
25
+
26
+ def supported_ip_families
27
+ @supported_ip_families ||= begin
28
+ # https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
29
+ list = Socket.ip_address_list
30
+ if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
31
+ [Socket::AF_INET6, Socket::AF_INET]
32
+ else
33
+ [Socket::AF_INET]
34
+ end
35
+ rescue NotImplementedError
36
+ [Socket::AF_INET]
37
+ end.freeze
38
+ end
39
+
40
+ def resolver_for(resolver_type, options)
41
+ case resolver_type
42
+ when Symbol
43
+ meth = :"resolver_#{resolver_type}_class"
44
+
45
+ return options.__send__(meth) if options.respond_to?(meth)
46
+ when Class
47
+ return resolver_type if resolver_type < Resolver
48
+ end
49
+
50
+ raise Error, "unsupported resolver type (#{resolver_type})"
51
+ end
52
+
53
+ def nolookup_resolve(hostname)
54
+ ip_resolve(hostname) || cached_lookup(hostname) || hosts_resolve(hostname)
55
+ end
56
+
57
+ # tries to convert +hostname+ into an IPAddr, returns <tt>nil</tt> otherwise.
58
+ def ip_resolve(hostname)
59
+ [Entry.new(hostname)]
60
+ rescue ArgumentError
61
+ end
62
+
63
+ # matches +hostname+ to entries in the hosts file, returns <tt>nil</nil> if none is
64
+ # found, or there is no hosts file.
65
+ def hosts_resolve(hostname)
66
+ ips = @hosts_resolver.getaddresses(hostname)
67
+ return if ips.empty?
68
+
69
+ ips.map { |ip| Entry.new(ip) }
70
+ rescue IOError
71
+ end
72
+
73
+ def cached_lookup(hostname)
74
+ now = Utils.now
75
+ lookup_synchronize do |lookups|
76
+ lookup(hostname, lookups, now)
77
+ end
78
+ end
79
+
80
+ def cached_lookup_set(hostname, family, entries)
81
+ lookup_synchronize do |lookups|
82
+ case family
83
+ when Socket::AF_INET6
84
+ lookups[hostname].concat(entries)
85
+ when Socket::AF_INET
86
+ lookups[hostname].unshift(*entries)
87
+ end
88
+ entries.each do |entry|
89
+ next unless entry["name"] != hostname
90
+
91
+ case family
92
+ when Socket::AF_INET6
93
+ lookups[entry["name"]] << entry
94
+ when Socket::AF_INET
95
+ lookups[entry["name"]].unshift(entry)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ def cached_lookup_evict(hostname, ip)
102
+ ip = ip.to_s
103
+
104
+ lookup_synchronize do |lookups|
105
+ entries = lookups[hostname]
106
+
107
+ return unless entries
108
+
109
+ lookups.delete_if { |entry| entry["data"] == ip }
110
+ end
111
+ end
112
+
113
+ # do not use directly!
114
+ def lookup(hostname, lookups, ttl)
115
+ return unless lookups.key?(hostname)
116
+
117
+ entries = lookups[hostname] = lookups[hostname].select do |address|
118
+ address["TTL"] > ttl
119
+ end
120
+
121
+ ips = entries.flat_map do |address|
122
+ if (als = address["alias"])
123
+ lookup(als, lookups, ttl)
124
+ else
125
+ Entry.new(address["data"], address["TTL"])
126
+ end
127
+ end.compact
128
+
129
+ ips unless ips.empty?
130
+ end
131
+
132
+ def generate_id
133
+ id_synchronize { @identifier = (@identifier + 1) & 0xFFFF }
134
+ end
135
+
136
+ def encode_dns_query(hostname, type: Resolv::DNS::Resource::IN::A, message_id: generate_id)
137
+ Resolv::DNS::Message.new(message_id).tap do |query|
138
+ query.rd = 1
139
+ query.add_question(hostname, type)
140
+ end.encode
141
+ end
142
+
143
+ def decode_dns_answer(payload)
144
+ begin
145
+ message = Resolv::DNS::Message.decode(payload)
146
+ rescue Resolv::DNS::DecodeError => e
147
+ return :decode_error, e
148
+ end
149
+
150
+ # no domain was found
151
+ return :no_domain_found if message.rcode == Resolv::DNS::RCode::NXDomain
152
+
153
+ return :message_truncated if message.tc == 1
154
+
155
+ return :dns_error, message.rcode if message.rcode != Resolv::DNS::RCode::NoError
156
+
157
+ addresses = []
158
+
159
+ now = Utils.now
160
+ message.each_answer do |question, _, value|
161
+ case value
162
+ when Resolv::DNS::Resource::IN::CNAME
163
+ addresses << {
164
+ "name" => question.to_s,
165
+ "TTL" => (now + value.ttl),
166
+ "alias" => value.name.to_s,
167
+ }
168
+ when Resolv::DNS::Resource::IN::A,
169
+ Resolv::DNS::Resource::IN::AAAA
170
+ addresses << {
171
+ "name" => question.to_s,
172
+ "TTL" => (now + value.ttl),
173
+ "data" => value.address.to_s,
174
+ }
175
+ end
176
+ end
177
+
178
+ [:ok, addresses]
179
+ end
180
+
181
+ def lookup_synchronize
182
+ @lookup_mutex.synchronize { yield(@lookups) }
183
+ end
184
+
185
+ def id_synchronize(&block)
186
+ @identifier_mutex.synchronize(&block)
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ # Implementation of the HTTP Response body as a buffer which implements the IO writer protocol
5
+ # (for buffering the response payload), the IO reader protocol (for consuming the response payload),
6
+ # and can be iterated over (via #each, which yields the payload in chunks).
7
+ class Response::Body
8
+ # the payload encoding (i.e. "utf-8", "ASCII-8BIT")
9
+ attr_reader :encoding
10
+
11
+ # Array of encodings contained in the response "content-encoding" header.
12
+ attr_reader :encodings
13
+
14
+ attr_reader :buffer
15
+ protected :buffer
16
+
17
+ # initialized with the corresponding HTTPX::Response +response+ and HTTPX::Options +options+.
18
+ def initialize(response, options)
19
+ @response = response
20
+ @headers = response.headers
21
+ @options = options
22
+ @window_size = options.window_size
23
+ @encodings = []
24
+ @length = 0
25
+ @buffer = nil
26
+ @reader = nil
27
+ @state = :idle
28
+
29
+ # initialize response encoding
30
+ @encoding = if (enc = response.content_type.charset)
31
+ begin
32
+ Encoding.find(enc)
33
+ rescue ArgumentError
34
+ Encoding::BINARY
35
+ end
36
+ else
37
+ Encoding::BINARY
38
+ end
39
+
40
+ initialize_inflaters
41
+ end
42
+
43
+ def initialize_dup(other)
44
+ super
45
+
46
+ @buffer = other.instance_variable_get(:@buffer).dup
47
+ end
48
+
49
+ def closed?
50
+ @state == :closed
51
+ end
52
+
53
+ # write the response payload +chunk+ into the buffer. Inflates the chunk when required
54
+ # and supported.
55
+ def write(chunk)
56
+ return if @state == :closed
57
+
58
+ return 0 if chunk.empty?
59
+
60
+ chunk = decode_chunk(chunk)
61
+
62
+ size = chunk.bytesize
63
+ @length += size
64
+ transition(:open)
65
+ @buffer.write(chunk)
66
+
67
+ @response.emit(:chunk_received, chunk)
68
+ size
69
+ end
70
+
71
+ # reads a chunk from the payload (implementation of the IO reader protocol).
72
+ def read(*args)
73
+ return unless @buffer
74
+
75
+ unless @reader
76
+ rewind
77
+ @reader = @buffer
78
+ end
79
+
80
+ @reader.read(*args)
81
+ end
82
+
83
+ # size of the decoded response payload. May differ from "content-length" header if
84
+ # response was encoded over-the-wire.
85
+ def bytesize
86
+ @length
87
+ end
88
+
89
+ # yields the payload in chunks.
90
+ def each
91
+ return enum_for(__method__) unless block_given?
92
+
93
+ begin
94
+ if @buffer
95
+ rewind
96
+ while (chunk = @buffer.read(@window_size))
97
+ yield(chunk.force_encoding(@encoding))
98
+ end
99
+ end
100
+ ensure
101
+ close
102
+ end
103
+ end
104
+
105
+ # returns the declared filename in the "contennt-disposition" header, when present.
106
+ def filename
107
+ return unless @headers.key?("content-disposition")
108
+
109
+ Utils.get_filename(@headers["content-disposition"])
110
+ end
111
+
112
+ # returns the full response payload as a string.
113
+ def to_s
114
+ return "".b unless @buffer
115
+
116
+ @buffer.to_s
117
+ end
118
+
119
+ alias_method :to_str, :to_s
120
+
121
+ # whether the payload is empty.
122
+ def empty?
123
+ @length.zero?
124
+ end
125
+
126
+ # copies the payload to +dest+.
127
+ #
128
+ # body.copy_to("path/to/file")
129
+ # body.copy_to(Pathname.new("path/to/file"))
130
+ # body.copy_to(File.new("path/to/file"))
131
+ def copy_to(dest)
132
+ return unless @buffer
133
+
134
+ rewind
135
+
136
+ if dest.respond_to?(:path) && @buffer.respond_to?(:path)
137
+ FileUtils.mv(@buffer.path, dest.path)
138
+ else
139
+ IO.copy_stream(@buffer, dest)
140
+ end
141
+ end
142
+
143
+ # closes/cleans the buffer, resets everything
144
+ def close
145
+ if @buffer
146
+ @buffer.close
147
+ @buffer = nil
148
+ end
149
+ @length = 0
150
+ transition(:closed)
151
+ end
152
+
153
+ def ==(other)
154
+ super || case other
155
+ when Response::Body
156
+ @buffer == other.buffer
157
+ else
158
+ @buffer = other
159
+ end
160
+ end
161
+
162
+ # :nocov:
163
+ def inspect
164
+ "#<#{self.class}:#{object_id} " \
165
+ "@state=#{@state} " \
166
+ "@length=#{@length}>"
167
+ end
168
+ # :nocov:
169
+
170
+ # rewinds the response payload buffer.
171
+ def rewind
172
+ return unless @buffer
173
+
174
+ # in case there's some reading going on
175
+ @reader = nil
176
+
177
+ @buffer.rewind
178
+ end
179
+
180
+ private
181
+
182
+ # prepares inflaters for the advertised encodings in "content-encoding" header.
183
+ def initialize_inflaters
184
+ @inflaters = nil
185
+
186
+ return unless @headers.key?("content-encoding")
187
+
188
+ return unless @options.decompress_response_body
189
+
190
+ @inflaters = @headers.get("content-encoding").filter_map do |encoding|
191
+ next if encoding == "identity"
192
+
193
+ inflater = self.class.initialize_inflater_by_encoding(encoding, @response)
194
+
195
+ # do not uncompress if there is no decoder available. In fact, we can't reliably
196
+ # continue decompressing beyond that, so ignore.
197
+ break unless inflater
198
+
199
+ @encodings << encoding
200
+ inflater
201
+ end
202
+ end
203
+
204
+ # passes the +chunk+ through all inflaters to decode it.
205
+ def decode_chunk(chunk)
206
+ @inflaters.reverse_each do |inflater|
207
+ chunk = inflater.call(chunk)
208
+ end if @inflaters
209
+
210
+ chunk
211
+ end
212
+
213
+ # tries transitioning the body STM to the +nextstate+.
214
+ def transition(nextstate)
215
+ case nextstate
216
+ when :open
217
+ return unless @state == :idle
218
+
219
+ @buffer = Response::Buffer.new(
220
+ threshold_size: @options.body_threshold_size,
221
+ bytesize: @length,
222
+ encoding: @encoding
223
+ )
224
+ when :closed
225
+ return if @state == :closed
226
+ end
227
+
228
+ @state = nextstate
229
+ end
230
+
231
+ class << self
232
+ def initialize_inflater_by_encoding(encoding, response, **kwargs) # :nodoc:
233
+ case encoding
234
+ when "gzip"
235
+ Transcoder::GZIP.decode(response, **kwargs)
236
+ when "deflate"
237
+ Transcoder::Deflate.decode(response, **kwargs)
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end