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
data/lib/httpx/pool.rb ADDED
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httpx/selector"
4
+ require "httpx/connection"
5
+ require "httpx/connection/http2"
6
+ require "httpx/connection/http1"
7
+ require "httpx/resolver"
8
+
9
+ module HTTPX
10
+ class Pool
11
+ using ArrayExtensions::FilterMap
12
+ using URIExtensions
13
+
14
+ POOL_TIMEOUT = 5
15
+
16
+ # Sets up the connection pool with the given +options+, which can be the following:
17
+ #
18
+ # :max_connections:: the maximum number of connections held in the pool.
19
+ # :max_connections_per_origin :: the maximum number of connections held in the pool pointing to a given origin.
20
+ # :pool_timeout :: the number of seconds to wait for a connection to a given origin (before raising HTTPX::PoolTimeoutError)
21
+ #
22
+ def initialize(options)
23
+ @max_connections = options.fetch(:max_connections, Float::INFINITY)
24
+ @max_connections_per_origin = options.fetch(:max_connections_per_origin, Float::INFINITY)
25
+ @pool_timeout = options.fetch(:pool_timeout, POOL_TIMEOUT)
26
+ @resolvers = Hash.new { |hs, resolver_type| hs[resolver_type] = [] }
27
+ @resolver_mtx = Thread::Mutex.new
28
+ @connections = []
29
+ @connection_mtx = Thread::Mutex.new
30
+ @connections_counter = 0
31
+ @max_connections_cond = ConditionVariable.new
32
+ @origin_counters = Hash.new(0)
33
+ @origin_conds = Hash.new { |hs, orig| hs[orig] = ConditionVariable.new }
34
+ end
35
+
36
+ # connections returned by this function are not expected to return to the connection pool.
37
+ def pop_connection
38
+ @connection_mtx.synchronize do
39
+ drop_connection
40
+ end
41
+ end
42
+
43
+ # opens a connection to the IP reachable through +uri+.
44
+ # Many hostnames are reachable through the same IP, so we try to
45
+ # maximize pipelining by opening as few connections as possible.
46
+ #
47
+ def checkout_connection(uri, options)
48
+ return checkout_new_connection(uri, options) if options.io
49
+
50
+ @connection_mtx.synchronize do
51
+ acquire_connection(uri, options) || begin
52
+ if @connections_counter == @max_connections
53
+ # this takes precedence over per-origin
54
+ @max_connections_cond.wait(@connection_mtx, @pool_timeout)
55
+
56
+ if (conn = acquire_connection(uri, options))
57
+ return conn
58
+ end
59
+
60
+ if @connections_counter == @max_connections
61
+ # if no matching usable connection was found, the pool will make room and drop a closed connection. if none is found,
62
+ # this means that all of them are persistent or being used, so raise a timeout error.
63
+ conn = @connections.find { |c| c.state == :closed }
64
+
65
+ raise PoolTimeoutError.new(@pool_timeout,
66
+ "Timed out after #{@pool_timeout} seconds while waiting for a connection") unless conn
67
+
68
+ drop_connection(conn)
69
+ end
70
+
71
+ end
72
+
73
+ if @origin_counters[uri.origin] == @max_connections_per_origin
74
+
75
+ @origin_conds[uri.origin].wait(@connection_mtx, @pool_timeout)
76
+
77
+ return acquire_connection(uri, options) ||
78
+ raise(PoolTimeoutError.new(@pool_timeout,
79
+ "Timed out after #{@pool_timeout} seconds while waiting for a connection to #{uri.origin}"))
80
+ end
81
+
82
+ @connections_counter += 1
83
+ @origin_counters[uri.origin] += 1
84
+
85
+ checkout_new_connection(uri, options)
86
+ end
87
+ end
88
+ end
89
+
90
+ def checkin_connection(connection)
91
+ return if connection.options.io
92
+
93
+ @connection_mtx.synchronize do
94
+ @connections << connection
95
+
96
+ @max_connections_cond.signal
97
+ @origin_conds[connection.origin.to_s].signal
98
+ end
99
+ end
100
+
101
+ def checkout_mergeable_connection(connection)
102
+ return if connection.options.io
103
+
104
+ @connection_mtx.synchronize do
105
+ idx = @connections.find_index do |ch|
106
+ ch != connection && ch.mergeable?(connection)
107
+ end
108
+ @connections.delete_at(idx) if idx
109
+ end
110
+ end
111
+
112
+ def reset_resolvers
113
+ @resolver_mtx.synchronize { @resolvers.clear }
114
+ end
115
+
116
+ def checkout_resolver(options)
117
+ resolver_type = options.resolver_class
118
+ resolver_type = Resolver.resolver_for(resolver_type, options)
119
+
120
+ @resolver_mtx.synchronize do
121
+ resolvers = @resolvers[resolver_type]
122
+
123
+ idx = resolvers.find_index do |res|
124
+ res.options == options
125
+ end
126
+ resolvers.delete_at(idx) if idx
127
+ end || checkout_new_resolver(resolver_type, options)
128
+ end
129
+
130
+ def checkin_resolver(resolver)
131
+ @resolver_mtx.synchronize do
132
+ resolvers = @resolvers[resolver.class]
133
+
134
+ resolver = resolver.multi
135
+
136
+ resolvers << resolver unless resolvers.include?(resolver)
137
+ end
138
+ end
139
+
140
+ # :nocov:
141
+ def inspect
142
+ "#<#{self.class}:#{object_id} " \
143
+ "@max_connections_per_origin=#{@max_connections_per_origin} " \
144
+ "@pool_timeout=#{@pool_timeout} " \
145
+ "@connections=#{@connections.size}>"
146
+ end
147
+ # :nocov:
148
+
149
+ private
150
+
151
+ def acquire_connection(uri, options)
152
+ idx = @connections.find_index do |connection|
153
+ connection.match?(uri, options)
154
+ end
155
+
156
+ return unless idx
157
+
158
+ @connections.delete_at(idx)
159
+ end
160
+
161
+ def checkout_new_connection(uri, options)
162
+ options.connection_class.new(uri, options)
163
+ end
164
+
165
+ def checkout_new_resolver(resolver_type, options)
166
+ if resolver_type.multi?
167
+ Resolver::Multi.new(resolver_type, options)
168
+ else
169
+ resolver_type.new(options)
170
+ end
171
+ end
172
+
173
+ # drops and returns the +connection+ from the connection pool; if +connection+ is <tt>nil</tt> (default),
174
+ # the first available connection from the pool will be dropped.
175
+ def drop_connection(connection = nil)
176
+ if connection
177
+ @connections.delete(connection)
178
+ else
179
+ connection = @connections.shift
180
+
181
+ return unless connection
182
+ end
183
+
184
+ @connections_counter -= 1
185
+ @origin_conds.delete(connection.origin) if (@origin_counters[connection.origin.to_s] -= 1).zero?
186
+
187
+ connection
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Punycode
5
+ module_function
6
+
7
+ begin
8
+ require "idnx"
9
+
10
+ def encode_hostname(hostname)
11
+ Idnx.to_punycode(hostname)
12
+ end
13
+ rescue LoadError
14
+ def encode_hostname(hostname)
15
+ warn "#{hostname} cannot be converted to punycode. Install the " \
16
+ "\"idnx\" gem: https://github.com/HoneyryderChuck/idnx"
17
+
18
+ hostname
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ # Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
5
+ class Request::Body < SimpleDelegator
6
+ class << self
7
+ def new(_, options, body: nil, **params)
8
+ if body.is_a?(self)
9
+ # request derives its options from body
10
+ body.options = options.merge(params)
11
+ return body
12
+ end
13
+
14
+ super
15
+ end
16
+ end
17
+
18
+ attr_accessor :options
19
+
20
+ # inits the instance with the request +headers+, +options+ and +params+, which contain the payload definition.
21
+ # it wraps the given body with the appropriate encoder on initialization.
22
+ #
23
+ # ..., json: { foo: "bar" }) #=> json encoder
24
+ # ..., form: { foo: "bar" }) #=> form urlencoded encoder
25
+ # ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
26
+ # ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
27
+ # ..., form: { body: "bla") }) #=> raw data encoder
28
+ def initialize(h, options, **params)
29
+ @headers = h
30
+ @body = self.class.initialize_body(params)
31
+ @options = options.merge(params)
32
+
33
+ if @body
34
+ if @options.compress_request_body && @headers.key?("content-encoding")
35
+
36
+ @headers.get("content-encoding").each do |encoding|
37
+ @body = self.class.initialize_deflater_body(@body, encoding)
38
+ end
39
+ end
40
+
41
+ @headers["content-type"] ||= @body.content_type
42
+ @headers["content-length"] = @body.bytesize unless unbounded_body?
43
+ end
44
+
45
+ super(@body)
46
+ end
47
+
48
+ # consumes and yields the request payload in chunks.
49
+ def each(&block)
50
+ return enum_for(__method__) unless block
51
+ return if @body.nil?
52
+
53
+ body = stream(@body)
54
+ if body.respond_to?(:read)
55
+ while (chunk = body.read(16_384))
56
+ block.call(chunk)
57
+ end
58
+ # TODO: use copy_stream once bug is resolved: https://bugs.ruby-lang.org/issues/21131
59
+ # IO.copy_stream(body, ProcIO.new(block))
60
+ elsif body.respond_to?(:each)
61
+ body.each(&block)
62
+ else
63
+ block[body.to_s]
64
+ end
65
+ end
66
+
67
+ def close
68
+ @body.close if @body.respond_to?(:close)
69
+ end
70
+
71
+ # if the +@body+ is rewindable, it rewinnds it.
72
+ def rewind
73
+ return if empty?
74
+
75
+ @body.rewind if @body.respond_to?(:rewind)
76
+ end
77
+
78
+ # return +true+ if the +body+ has been fully drained (or does nnot exist).
79
+ def empty?
80
+ return true if @body.nil?
81
+ return false if chunked?
82
+
83
+ @body.bytesize.zero?
84
+ end
85
+
86
+ # returns the +@body+ payload size in bytes.
87
+ def bytesize
88
+ return 0 if @body.nil?
89
+
90
+ @body.bytesize
91
+ end
92
+
93
+ # sets the body to yield using chunked trannsfer encoding format.
94
+ def stream(body)
95
+ return body unless chunked?
96
+
97
+ Transcoder::Chunker.encode(body.enum_for(:each))
98
+ end
99
+
100
+ # returns whether the body yields infinitely.
101
+ def unbounded_body?
102
+ return @unbounded_body if defined?(@unbounded_body)
103
+
104
+ @unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
105
+ end
106
+
107
+ # returns whether the chunked transfer encoding header is set.
108
+ def chunked?
109
+ @headers["transfer-encoding"] == "chunked"
110
+ end
111
+
112
+ # sets the chunked transfer encoding header.
113
+ def chunk!
114
+ @headers.add("transfer-encoding", "chunked")
115
+ end
116
+
117
+ # :nocov:
118
+ def inspect
119
+ "#<#{self.class}:#{object_id} " \
120
+ "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
121
+ end
122
+ # :nocov:
123
+
124
+ class << self
125
+ def initialize_body(params)
126
+ if (body = params.delete(:body))
127
+ # @type var body: bodyIO
128
+ Transcoder::Body.encode(body)
129
+ elsif (form = params.delete(:form))
130
+ if Transcoder::Multipart.multipart?(form)
131
+ # @type var form: Transcoder::multipart_input
132
+ Transcoder::Multipart.encode(form)
133
+ else
134
+ # @type var form: Transcoder::urlencoded_input
135
+ Transcoder::Form.encode(form)
136
+ end
137
+ elsif (json = params.delete(:json))
138
+ # @type var body: _ToJson
139
+ Transcoder::JSON.encode(json)
140
+ end
141
+ end
142
+
143
+ # returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
144
+ def initialize_deflater_body(body, encoding)
145
+ case encoding
146
+ when "gzip"
147
+ Transcoder::GZIP.encode(body)
148
+ when "deflate"
149
+ Transcoder::Deflate.encode(body)
150
+ when "identity"
151
+ body
152
+ else
153
+ body
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,328 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "forwardable"
5
+
6
+ module HTTPX
7
+ # Defines how an HTTP request is handled internally, both in terms of making attributes accessible,
8
+ # as well as maintaining the state machine which manages streaming the request onto the wire.
9
+ class Request
10
+ extend Forwardable
11
+ include Loggable
12
+ include Callbacks
13
+ using URIExtensions
14
+
15
+ ALLOWED_URI_SCHEMES = %w[https http].freeze
16
+
17
+ # default value used for "user-agent" header, when not overridden.
18
+ USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
19
+
20
+ # the upcased string HTTP verb for this request.
21
+ attr_reader :verb
22
+
23
+ # the absolute URI object for this request.
24
+ attr_reader :uri
25
+
26
+ # an HTTPX::Headers object containing the request HTTP headers.
27
+ attr_reader :headers
28
+
29
+ # an HTTPX::Request::Body object containing the request body payload (or +nil+, whenn there is none).
30
+ attr_reader :body
31
+
32
+ # a symbol describing which frame is currently being flushed.
33
+ attr_reader :state
34
+
35
+ # an HTTPX::Options object containing request options.
36
+ attr_reader :options
37
+
38
+ # the corresponding HTTPX::Response object, when there is one.
39
+ attr_reader :response
40
+
41
+ # Exception raised during enumerable body writes.
42
+ attr_reader :drain_error
43
+
44
+ # The IP address from the peer server.
45
+ attr_accessor :peer_address
46
+
47
+ attr_writer :persistent
48
+
49
+ attr_reader :active_timeouts
50
+
51
+ # will be +true+ when request body has been completely flushed.
52
+ def_delegator :@body, :empty?
53
+
54
+ # closes the body
55
+ def_delegator :@body, :close
56
+
57
+ # initializes the instance with the given +verb+ (an upppercase String, ex. 'GEt'),
58
+ # an absolute or relative +uri+ (either as String or URI::HTTP object), the
59
+ # request +options+ (instance of HTTPX::Options) and an optional Hash of +params+.
60
+ #
61
+ # Besides any of the options documented in HTTPX::Options (which would override or merge with what
62
+ # +options+ sets), it accepts also the following:
63
+ #
64
+ # :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
65
+ # :body :: to be encoded in the request body payload. can be a String, an IO object (i.e. a File), or an Enumerable.
66
+ # :form :: hash of array of key-values which will be form-urlencoded- or multipart-encoded in requests body payload.
67
+ # :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
68
+ # :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
69
+ #
70
+ # :body, :form, :json and :xml are all mutually exclusive, i.e. only one of them gets picked up.
71
+ def initialize(verb, uri, options, params = EMPTY_HASH)
72
+ @verb = verb.to_s.upcase
73
+ @uri = Utils.to_uri(uri)
74
+
75
+ @headers = options.headers.dup
76
+ merge_headers(params.delete(:headers)) if params.key?(:headers)
77
+
78
+ @headers["user-agent"] ||= USER_AGENT
79
+ @headers["accept"] ||= "*/*"
80
+
81
+ # forego compression in the Range request case
82
+ if @headers.key?("range")
83
+ @headers.delete("accept-encoding")
84
+ else
85
+ @headers["accept-encoding"] ||= options.supported_compression_formats
86
+ end
87
+
88
+ @query_params = params.delete(:params) if params.key?(:params)
89
+
90
+ @body = options.request_body_class.new(@headers, options, **params)
91
+
92
+ @options = @body.options
93
+
94
+ if @uri.relative? || @uri.host.nil?
95
+ origin = @options.origin
96
+ raise(Error, "invalid URI: #{@uri}") unless origin
97
+
98
+ base_path = @options.base_path
99
+
100
+ @uri = origin.merge("#{base_path}#{@uri}")
101
+ end
102
+
103
+ raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
104
+
105
+ @state = :idle
106
+ @response = @peer_address = @context = @informational_status = nil
107
+ @ping = false
108
+ @persistent = @options.persistent
109
+ @active_timeouts = []
110
+ end
111
+
112
+ def complete!(response = @response)
113
+ emit(:complete, response)
114
+ end
115
+
116
+ # whether request has been buffered with a ping
117
+ def ping?
118
+ @ping
119
+ end
120
+
121
+ # marks the request as having been buffered with a ping
122
+ def ping!
123
+ @ping = true
124
+ end
125
+
126
+ # the read timeout defined for this request.
127
+ def read_timeout
128
+ @options.timeout[:read_timeout]
129
+ end
130
+
131
+ # the write timeout defined for this request.
132
+ def write_timeout
133
+ @options.timeout[:write_timeout]
134
+ end
135
+
136
+ # the request timeout defined for this request.
137
+ def request_timeout
138
+ @options.timeout[:request_timeout]
139
+ end
140
+
141
+ def persistent?
142
+ @persistent
143
+ end
144
+
145
+ # if the request contains trailer headers
146
+ def trailers?
147
+ defined?(@trailers)
148
+ end
149
+
150
+ # returns an instance of HTTPX::Headers containing the trailer headers
151
+ def trailers
152
+ @trailers ||= @options.headers_class.new
153
+ end
154
+
155
+ # returns +:r+ or +:w+, depending on whether the request is waiting for a response or flushing.
156
+ def interests
157
+ return :r if @state == :done || @state == :expect
158
+
159
+ :w
160
+ end
161
+
162
+ def can_buffer?
163
+ @state != :done
164
+ end
165
+
166
+ # merges +h+ into the instance of HTTPX::Headers of the request.
167
+ def merge_headers(h)
168
+ @headers = @headers.merge(h)
169
+ end
170
+
171
+ # the URI scheme of the request +uri+.
172
+ def scheme
173
+ @uri.scheme
174
+ end
175
+
176
+ # sets the +response+ on this request.
177
+ def response=(response)
178
+ return unless response
179
+
180
+ case response
181
+ when Response
182
+ if response.status < 200
183
+ # deal with informational responses
184
+
185
+ if response.status == 100 && @headers.key?("expect")
186
+ @informational_status = response.status
187
+ return
188
+ end
189
+
190
+ # 103 Early Hints advertises resources in document to browsers.
191
+ # not very relevant for an HTTP client, discard.
192
+ return if response.status >= 103
193
+
194
+ end
195
+ when ErrorResponse
196
+ response.error.connection = nil if response.error.respond_to?(:connection=)
197
+ end
198
+
199
+ @response = response
200
+
201
+ emit(:response_started, response)
202
+ end
203
+
204
+ # returnns the URI path of the request +uri+.
205
+ def path
206
+ path = uri.path.dup
207
+ path = +"" if path.nil?
208
+ path << "/" if path.empty?
209
+ path << "?#{query}" unless query.empty?
210
+ path
211
+ end
212
+
213
+ # returs the URI authority of the request.
214
+ #
215
+ # session.build_request("GET", "https://google.com/query").authority #=> "google.com"
216
+ # session.build_request("GET", "http://internal:3182/a").authority #=> "internal:3182"
217
+ def authority
218
+ @uri.authority
219
+ end
220
+
221
+ # returs the URI origin of the request.
222
+ #
223
+ # session.build_request("GET", "https://google.com/query").authority #=> "https://google.com"
224
+ # session.build_request("GET", "http://internal:3182/a").authority #=> "http://internal:3182"
225
+ def origin
226
+ @uri.origin
227
+ end
228
+
229
+ # returs the URI query string of the request (when available).
230
+ #
231
+ # session.build_request("GET", "https://search.com").query #=> ""
232
+ # session.build_request("GET", "https://search.com?q=a").query #=> "q=a"
233
+ # session.build_request("GET", "https://search.com", params: { q: "a"}).query #=> "q=a"
234
+ # session.build_request("GET", "https://search.com?q=a", params: { foo: "bar"}).query #=> "q=a&foo&bar"
235
+ def query
236
+ return @query if defined?(@query)
237
+
238
+ query = []
239
+ if (q = @query_params) && !q.empty?
240
+ query << Transcoder::Form.encode(q)
241
+ end
242
+ query << @uri.query if @uri.query
243
+ @query = query.join("&")
244
+ end
245
+
246
+ # consumes and returns the next available chunk of request body that can be sent
247
+ def drain_body
248
+ return nil if @body.nil?
249
+
250
+ @drainer ||= @body.each
251
+ chunk = @drainer.next.dup
252
+
253
+ emit(:body_chunk, chunk)
254
+ chunk
255
+ rescue StopIteration
256
+ nil
257
+ rescue StandardError => e
258
+ @drain_error = e
259
+ nil
260
+ end
261
+
262
+ # :nocov:
263
+ def inspect
264
+ "#<#{self.class}:#{object_id} " \
265
+ "#{@verb} " \
266
+ "#{uri} " \
267
+ "@headers=#{@headers} " \
268
+ "@body=#{@body}>"
269
+ end
270
+ # :nocov:
271
+
272
+ # moves on to the +nextstate+ of the request state machine (when all preconditions are met)
273
+ def transition(nextstate)
274
+ case nextstate
275
+ when :idle
276
+ @body.rewind
277
+ @ping = false
278
+ @response = nil
279
+ @drainer = nil
280
+ @active_timeouts.clear
281
+ when :headers
282
+ return unless @state == :idle
283
+
284
+ when :body
285
+ return unless @state == :headers ||
286
+ @state == :expect
287
+
288
+ if @headers.key?("expect")
289
+ if @informational_status && @informational_status == 100
290
+ # check for 100 Continue response, and deallocate the var
291
+ # if @informational_status == 100
292
+ # @response = nil
293
+ # end
294
+ else
295
+ return if @state == :expect # do not re-set it
296
+
297
+ nextstate = :expect
298
+ end
299
+ end
300
+ when :trailers
301
+ return unless @state == :body
302
+ when :done
303
+ return if @state == :expect
304
+
305
+ end
306
+ log(level: 3) { "#{@state}] -> #{nextstate}" }
307
+ @state = nextstate
308
+ emit(@state, self)
309
+ nil
310
+ end
311
+
312
+ # whether the request supports the 100-continue handshake and already processed the 100 response.
313
+ def expects?
314
+ @headers["expect"] == "100-continue" && @informational_status == 100 && !@response
315
+ end
316
+
317
+ def set_timeout_callback(event, &callback)
318
+ clb = once(event, &callback)
319
+
320
+ # reset timeout callbacks when requests get rerouted to a different connection
321
+ once(:idle) do
322
+ callbacks(event).delete(clb)
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ require_relative "request/body"