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,954 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+ require "forwardable"
5
+ require "httpx/io"
6
+ require "httpx/buffer"
7
+
8
+ module HTTPX
9
+ # The Connection can be watched for IO events.
10
+ #
11
+ # It contains the +io+ object to read/write from, and knows what to do when it can.
12
+ #
13
+ # It defers connecting until absolutely necessary. Connection should be triggered from
14
+ # the IO selector (until then, any request will be queued).
15
+ #
16
+ # A connection boots up its parser after connection is established. All pending requests
17
+ # will be redirected there after connection.
18
+ #
19
+ # A connection can be prevented from closing by the parser, that is, if there are pending
20
+ # requests. This will signal that the connection was prematurely closed, due to a possible
21
+ # number of conditions:
22
+ #
23
+ # * Remote peer closed the connection ("Connection: close");
24
+ # * Remote peer doesn't support pipelining;
25
+ #
26
+ # A connection may also route requests for a different host for which the +io+ was connected
27
+ # to, provided that the IP is the same and the port and scheme as well. This will allow to
28
+ # share the same socket to send HTTP/2 requests to different hosts.
29
+ #
30
+ class Connection
31
+ extend Forwardable
32
+ include Loggable
33
+ include Callbacks
34
+
35
+ using URIExtensions
36
+
37
+ def_delegator :@io, :closed?
38
+
39
+ def_delegator :@write_buffer, :empty?
40
+
41
+ attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
42
+
43
+ attr_writer :current_selector
44
+
45
+ attr_accessor :current_session, :family
46
+
47
+ protected :ssl_session, :sibling
48
+
49
+ def initialize(uri, options)
50
+ @current_session = @current_selector =
51
+ @parser = @sibling = @coalesced_connection =
52
+ @family = @io = @ssl_session = @timeout =
53
+ @connected_at = @response_received_at = nil
54
+
55
+ @exhausted = @cloned = @main_sibling = false
56
+
57
+ @options = Options.new(options)
58
+ @type = initialize_type(uri, @options)
59
+ @origins = [uri.origin]
60
+ @origin = Utils.to_uri(uri.origin)
61
+ @window_size = @options.window_size
62
+ @read_buffer = Buffer.new(@options.buffer_size)
63
+ @write_buffer = Buffer.new(@options.buffer_size)
64
+ @pending = []
65
+ @inflight = 0
66
+ @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
67
+
68
+ on(:error, &method(:on_error))
69
+ if @options.io
70
+ # if there's an already open IO, get its
71
+ # peer address, and force-initiate the parser
72
+ transition(:already_open)
73
+ @io = build_socket
74
+ parser
75
+ else
76
+ transition(:idle)
77
+ end
78
+ on(:close) do
79
+ next if @exhausted # it'll reset
80
+
81
+ # may be called after ":close" above, so after the connection has been checked back in.
82
+ # next unless @current_session
83
+
84
+ next unless @current_session
85
+
86
+ @current_session.deselect_connection(self, @current_selector, @cloned)
87
+ end
88
+ on(:terminate) do
89
+ next if @exhausted # it'll reset
90
+
91
+ current_session = @current_session
92
+ current_selector = @current_selector
93
+
94
+ # may be called after ":close" above, so after the connection has been checked back in.
95
+ next unless current_session && current_selector
96
+
97
+ current_session.deselect_connection(self, current_selector)
98
+ end
99
+
100
+ on(:altsvc) do |alt_origin, origin, alt_params|
101
+ build_altsvc_connection(alt_origin, origin, alt_params)
102
+ end
103
+
104
+ self.addresses = @options.addresses if @options.addresses
105
+ end
106
+
107
+ def peer
108
+ @origin
109
+ end
110
+
111
+ # this is a semi-private method, to be used by the resolver
112
+ # to initiate the io object.
113
+ def addresses=(addrs)
114
+ if @io
115
+ @io.add_addresses(addrs)
116
+ else
117
+ @io = build_socket(addrs)
118
+ end
119
+ end
120
+
121
+ def addresses
122
+ @io && @io.addresses
123
+ end
124
+
125
+ def addresses?
126
+ @io && @io.addresses?
127
+ end
128
+
129
+ def match?(uri, options)
130
+ return false if !used? && (@state == :closing || @state == :closed)
131
+
132
+ (
133
+ @origins.include?(uri.origin) &&
134
+ # if there is more than one origin to match, it means that this connection
135
+ # was the result of coalescing. To prevent blind trust in the case where the
136
+ # origin came from an ORIGIN frame, we're going to verify the hostname with the
137
+ # SSL certificate
138
+ (@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
139
+ ) && @options == options
140
+ end
141
+
142
+ def mergeable?(connection)
143
+ return false if @state == :closing || @state == :closed || !@io
144
+
145
+ return false unless connection.addresses
146
+
147
+ (
148
+ (open? && @origin == connection.origin) ||
149
+ !(@io.addresses & (connection.addresses || [])).empty?
150
+ ) && @options == connection.options
151
+ end
152
+
153
+ # coalesces +self+ into +connection+.
154
+ def coalesce!(connection)
155
+ @coalesced_connection = connection
156
+
157
+ close_sibling
158
+ connection.merge(self)
159
+ end
160
+
161
+ # coalescable connections need to be mergeable!
162
+ # but internally, #mergeable? is called before #coalescable?
163
+ def coalescable?(connection)
164
+ if @io.protocol == "h2" &&
165
+ @origin.scheme == "https" &&
166
+ connection.origin.scheme == "https" &&
167
+ @io.can_verify_peer?
168
+ @io.verify_hostname(connection.origin.host)
169
+ else
170
+ @origin == connection.origin
171
+ end
172
+ end
173
+
174
+ def create_idle(options = {})
175
+ self.class.new(@origin, @options.merge(options))
176
+ end
177
+
178
+ def merge(connection)
179
+ @origins |= connection.instance_variable_get(:@origins)
180
+ if @ssl_session.nil? && connection.ssl_session
181
+ @ssl_session = connection.ssl_session
182
+ @io.session_new_cb do |sess|
183
+ @ssl_session = sess
184
+ end if @io
185
+ end
186
+ connection.purge_pending do |req|
187
+ send(req)
188
+ end
189
+ end
190
+
191
+ def purge_pending(&block)
192
+ pendings = []
193
+ if @parser
194
+ @inflight -= @parser.pending.size
195
+ pendings << @parser.pending
196
+ end
197
+ pendings << @pending
198
+ pendings.each do |pending|
199
+ pending.reject!(&block)
200
+ end
201
+ end
202
+
203
+ def io_connected?
204
+ return @coalesced_connection.io_connected? if @coalesced_connection
205
+
206
+ @io && @io.state == :connected
207
+ end
208
+
209
+ def connecting?
210
+ @state == :idle
211
+ end
212
+
213
+ def inflight?
214
+ @parser && (
215
+ # parser may be dealing with other requests (possibly started from a different fiber)
216
+ !@parser.empty? ||
217
+ # connection may be doing connection termination handshake
218
+ !@write_buffer.empty?
219
+ )
220
+ end
221
+
222
+ def interests
223
+ # connecting
224
+ if connecting?
225
+ connect
226
+
227
+ return @io.interests if connecting?
228
+ end
229
+
230
+ return @parser.interests if @parser
231
+
232
+ nil
233
+ rescue StandardError => e
234
+ emit(:error, e)
235
+ nil
236
+ end
237
+
238
+ def to_io
239
+ @io.to_io
240
+ end
241
+
242
+ def call
243
+ case @state
244
+ when :idle
245
+ connect
246
+
247
+ # when opening the tcp or ssl socket fails
248
+ return if @state == :closed
249
+
250
+ consume
251
+ when :closed
252
+ return
253
+ when :closing
254
+ consume
255
+ transition(:closed)
256
+ when :open
257
+ consume
258
+ end
259
+ nil
260
+ rescue StandardError => e
261
+ @write_buffer.clear
262
+ emit(:error, e)
263
+ raise e
264
+ end
265
+
266
+ def close
267
+ transition(:active) if @state == :inactive
268
+
269
+ @parser.close if @parser
270
+ end
271
+
272
+ def terminate
273
+ case @state
274
+ when :idle
275
+ purge_after_closed
276
+ emit(:terminate)
277
+ when :closed
278
+ @connected_at = nil
279
+ end
280
+
281
+ close
282
+ end
283
+
284
+ # bypasses the state machine to force closing of connections still connecting.
285
+ # **only** used for Happy Eyeballs v2.
286
+ def force_reset(cloned = false)
287
+ @state = :closing
288
+ @cloned = cloned
289
+ transition(:closed)
290
+ end
291
+
292
+ def reset
293
+ return if @state == :closing || @state == :closed
294
+
295
+ transition(:closing)
296
+
297
+ transition(:closed)
298
+ end
299
+
300
+ def send(request)
301
+ return @coalesced_connection.send(request) if @coalesced_connection
302
+
303
+ if @parser && !@write_buffer.full?
304
+ if @response_received_at && @keep_alive_timeout &&
305
+ Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
306
+ # when pushing a request into an existing connection, we have to check whether there
307
+ # is the possibility that the connection might have extended the keep alive timeout.
308
+ # for such cases, we want to ping for availability before deciding to shovel requests.
309
+ log(level: 3) { "keep alive timeout expired, pinging connection..." }
310
+ @pending << request
311
+ transition(:active) if @state == :inactive
312
+ parser.ping
313
+ request.ping!
314
+ return
315
+ end
316
+
317
+ send_request_to_parser(request)
318
+ else
319
+ @pending << request
320
+ end
321
+ end
322
+
323
+ def timeout
324
+ return if @state == :closed || @state == :inactive
325
+
326
+ return @timeout if @timeout
327
+
328
+ return @options.timeout[:connect_timeout] if @state == :idle
329
+
330
+ @options.timeout[:operation_timeout]
331
+ end
332
+
333
+ def idling
334
+ purge_after_closed
335
+ @write_buffer.clear
336
+ transition(:idle)
337
+ @parser = nil if @parser
338
+ end
339
+
340
+ def used?
341
+ @connected_at
342
+ end
343
+
344
+ def deactivate
345
+ transition(:inactive)
346
+ end
347
+
348
+ def open?
349
+ @state == :open || @state == :inactive
350
+ end
351
+
352
+ def handle_socket_timeout(interval)
353
+ error = OperationTimeoutError.new(interval, "timed out while waiting on select")
354
+ error.set_backtrace(caller)
355
+ on_error(error)
356
+ end
357
+
358
+ def sibling=(connection)
359
+ @sibling = connection
360
+
361
+ return unless connection
362
+
363
+ @main_sibling = connection.sibling.nil?
364
+
365
+ return unless @main_sibling
366
+
367
+ connection.sibling = self
368
+ end
369
+
370
+ def handle_connect_error(error)
371
+ return handle_error(error) unless @sibling && @sibling.connecting?
372
+
373
+ @sibling.merge(self)
374
+
375
+ force_reset(true)
376
+ end
377
+
378
+ def disconnect
379
+ return unless @current_session && @current_selector
380
+
381
+ emit(:close)
382
+ @current_session = @current_selector = nil
383
+ end
384
+
385
+ # :nocov:
386
+ def inspect
387
+ "#<#{self.class}:#{object_id} " \
388
+ "@origin=#{@origin} " \
389
+ "@state=#{@state} " \
390
+ "@pending=#{@pending.size} " \
391
+ "@io=#{@io}>"
392
+ end
393
+ # :nocov:
394
+
395
+ private
396
+
397
+ def connect
398
+ transition(:open)
399
+ end
400
+
401
+ def consume
402
+ return unless @io
403
+
404
+ catch(:called) do
405
+ epiped = false
406
+ loop do
407
+ # connection may have
408
+ return if @state == :idle
409
+
410
+ parser.consume
411
+
412
+ # we exit if there's no more requests to process
413
+ #
414
+ # this condition takes into account:
415
+ #
416
+ # * the number of inflight requests
417
+ # * the number of pending requests
418
+ # * whether the write buffer has bytes (i.e. for close handshake)
419
+ if @pending.empty? && @inflight.zero? && @write_buffer.empty?
420
+ log(level: 3) { "NO MORE REQUESTS..." }
421
+ return
422
+ end
423
+
424
+ @timeout = @current_timeout
425
+
426
+ read_drained = false
427
+ write_drained = nil
428
+
429
+ #
430
+ # tight read loop.
431
+ #
432
+ # read as much of the socket as possible.
433
+ #
434
+ # this tight loop reads all the data it can from the socket and pipes it to
435
+ # its parser.
436
+ #
437
+ loop do
438
+ siz = @io.read(@window_size, @read_buffer)
439
+ log(level: 3, color: :cyan) { "IO READ: #{siz} bytes... (wsize: #{@window_size}, rbuffer: #{@read_buffer.bytesize})" }
440
+ unless siz
441
+ @write_buffer.clear
442
+
443
+ ex = EOFError.new("descriptor closed")
444
+ ex.set_backtrace(caller)
445
+ on_error(ex)
446
+ return
447
+ end
448
+
449
+ # socket has been drained. mark and exit the read loop.
450
+ if siz.zero?
451
+ read_drained = @read_buffer.empty?
452
+ epiped = false
453
+ break
454
+ end
455
+
456
+ parser << @read_buffer.to_s
457
+
458
+ # continue reading if possible.
459
+ break if interests == :w && !epiped
460
+
461
+ # exit the read loop if connection is preparing to be closed
462
+ break if @state == :closing || @state == :closed
463
+
464
+ # exit #consume altogether if all outstanding requests have been dealt with
465
+ return if @pending.empty? && @inflight.zero?
466
+ end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
467
+
468
+ #
469
+ # tight write loop.
470
+ #
471
+ # flush as many bytes as the sockets allow.
472
+ #
473
+ loop do
474
+ # buffer has been drainned, mark and exit the write loop.
475
+ if @write_buffer.empty?
476
+ # we only mark as drained on the first loop
477
+ write_drained = write_drained.nil? && @inflight.positive?
478
+
479
+ break
480
+ end
481
+
482
+ begin
483
+ siz = @io.write(@write_buffer)
484
+ rescue Errno::EPIPE
485
+ # this can happen if we still have bytes in the buffer to send to the server, but
486
+ # the server wants to respond immediately with some message, or an error. An example is
487
+ # when one's uploading a big file to an unintended endpoint, and the server stops the
488
+ # consumption, and responds immediately with an authorization of even method not allowed error.
489
+ # at this point, we have to let the connection switch to read-mode.
490
+ log(level: 2) { "pipe broken, could not flush buffer..." }
491
+ epiped = true
492
+ read_drained = false
493
+ break
494
+ end
495
+ log(level: 3, color: :cyan) { "IO WRITE: #{siz} bytes..." }
496
+ unless siz
497
+ @write_buffer.clear
498
+
499
+ ex = EOFError.new("descriptor closed")
500
+ ex.set_backtrace(caller)
501
+ on_error(ex)
502
+ return
503
+ end
504
+
505
+ # socket closed for writing. mark and exit the write loop.
506
+ if siz.zero?
507
+ write_drained = !@write_buffer.empty?
508
+ break
509
+ end
510
+
511
+ # exit write loop if marked to consume from peer, or is closing.
512
+ break if interests == :r || @state == :closing || @state == :closed
513
+
514
+ write_drained = false
515
+ end unless (ints = interests) == :r
516
+
517
+ send_pending if @state == :open
518
+
519
+ # return if socket is drained
520
+ next unless (ints != :r || read_drained) && (ints != :w || write_drained)
521
+
522
+ # gotta go back to the event loop. It happens when:
523
+ #
524
+ # * the socket is drained of bytes or it's not the interest of the conn to read;
525
+ # * theres nothing more to write, or it's not in the interest of the conn to write;
526
+ log(level: 3) { "(#{ints}): WAITING FOR EVENTS..." }
527
+ return
528
+ end
529
+ end
530
+ end
531
+
532
+ def send_pending
533
+ while !@write_buffer.full? && (request = @pending.shift)
534
+ send_request_to_parser(request)
535
+ end
536
+ end
537
+
538
+ def parser
539
+ @parser ||= build_parser
540
+ end
541
+
542
+ def send_request_to_parser(request)
543
+ @inflight += 1
544
+ request.peer_address = @io.ip && @io.ip.address
545
+ set_request_timeouts(request)
546
+
547
+ parser.send(request)
548
+
549
+ return unless @state == :inactive
550
+
551
+ transition(:active)
552
+ # mark request as ping, as this inactive connection may have been
553
+ # closed by the server, and we don't want that to influence retry
554
+ # bookkeeping.
555
+ request.ping!
556
+ end
557
+
558
+ def build_parser(protocol = @io.protocol)
559
+ parser = parser_type(protocol).new(@write_buffer, @options)
560
+ set_parser_callbacks(parser)
561
+ parser
562
+ end
563
+
564
+ def set_parser_callbacks(parser)
565
+ parser.on(:response) do |request, response|
566
+ AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
567
+ emit(:altsvc, alt_origin, origin, alt_params)
568
+ end
569
+ @response_received_at = Utils.now
570
+ @inflight -= 1
571
+ response.finish!
572
+ request.emit(:response, response)
573
+ end
574
+ parser.on(:altsvc) do |alt_origin, origin, alt_params|
575
+ emit(:altsvc, alt_origin, origin, alt_params)
576
+ end
577
+
578
+ parser.on(:pong, &method(:send_pending))
579
+
580
+ parser.on(:promise) do |request, stream|
581
+ request.emit(:promise, parser, stream)
582
+ end
583
+ parser.on(:exhausted) do
584
+ @exhausted = true
585
+ current_session = @current_session
586
+ current_selector = @current_selector
587
+ begin
588
+ parser.close
589
+ @pending.concat(parser.pending)
590
+ ensure
591
+ @current_session = current_session
592
+ @current_selector = current_selector
593
+ end
594
+
595
+ case @state
596
+ when :closed
597
+ idling
598
+ @exhausted = false
599
+ when :closing
600
+ once(:closed) do
601
+ idling
602
+ @exhausted = false
603
+ end
604
+ end
605
+ end
606
+ parser.on(:origin) do |origin|
607
+ @origins |= [origin]
608
+ end
609
+ parser.on(:close) do |force|
610
+ if force
611
+ reset
612
+ emit(:terminate)
613
+ end
614
+ end
615
+ parser.on(:close_handshake) do
616
+ consume
617
+ end
618
+ parser.on(:reset) do
619
+ @pending.concat(parser.pending) unless parser.empty?
620
+ current_session = @current_session
621
+ current_selector = @current_selector
622
+ reset
623
+ unless @pending.empty?
624
+ idling
625
+ @current_session = current_session
626
+ @current_selector = current_selector
627
+ end
628
+ end
629
+ parser.on(:current_timeout) do
630
+ @current_timeout = @timeout = parser.timeout
631
+ end
632
+ parser.on(:timeout) do |tout|
633
+ @timeout = tout
634
+ end
635
+ parser.on(:error) do |request, error|
636
+ case error
637
+ when :http_1_1_required
638
+ current_session = @current_session
639
+ current_selector = @current_selector
640
+ parser.close
641
+
642
+ other_connection = current_session.find_connection(@origin, current_selector,
643
+ @options.merge(ssl: { alpn_protocols: %w[http/1.1] }))
644
+ other_connection.merge(self)
645
+ request.transition(:idle)
646
+ other_connection.send(request)
647
+ next
648
+ when OperationTimeoutError
649
+ # request level timeouts should take precedence
650
+ next unless request.active_timeouts.empty?
651
+ end
652
+
653
+ @inflight -= 1
654
+ response = ErrorResponse.new(request, error)
655
+ request.response = response
656
+ request.emit(:response, response)
657
+ end
658
+ end
659
+
660
+ def transition(nextstate)
661
+ handle_transition(nextstate)
662
+ rescue Errno::ECONNABORTED,
663
+ Errno::ECONNREFUSED,
664
+ Errno::ECONNRESET,
665
+ Errno::EADDRNOTAVAIL,
666
+ Errno::EHOSTUNREACH,
667
+ Errno::EINVAL,
668
+ Errno::ENETUNREACH,
669
+ Errno::EPIPE,
670
+ Errno::ENOENT,
671
+ SocketError,
672
+ IOError => e
673
+ # connect errors, exit gracefully
674
+ error = ConnectionError.new(e.message)
675
+ error.set_backtrace(e.backtrace)
676
+ handle_connect_error(error) if connecting?
677
+ @state = :closed
678
+ purge_after_closed
679
+ disconnect
680
+ rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
681
+ # connect errors, exit gracefully
682
+ handle_error(e)
683
+ handle_connect_error(e) if connecting?
684
+ @state = :closed
685
+ purge_after_closed
686
+ disconnect
687
+ end
688
+
689
+ def handle_transition(nextstate)
690
+ case nextstate
691
+ when :idle
692
+ @timeout = @current_timeout = @options.timeout[:connect_timeout]
693
+
694
+ @connected_at = @response_received_at = nil
695
+ when :open
696
+ return if @state == :closed
697
+
698
+ @io.connect
699
+ close_sibling if @io.state == :connected
700
+
701
+ return unless @io.connected?
702
+
703
+ @connected_at = Utils.now
704
+
705
+ send_pending
706
+
707
+ @timeout = @current_timeout = parser.timeout
708
+ emit(:open)
709
+ when :inactive
710
+ return unless @state == :open
711
+
712
+ # do not deactivate connection in use
713
+ return if @inflight.positive? || @parser.waiting_for_ping?
714
+ when :closing
715
+ return unless @state == :idle || @state == :open
716
+
717
+ unless @write_buffer.empty?
718
+ # preset state before handshake, as error callbacks
719
+ # may take it back here.
720
+ @state = nextstate
721
+ # handshakes, try sending
722
+ consume
723
+ @write_buffer.clear
724
+ return
725
+ end
726
+ when :closed
727
+ return unless @state == :closing
728
+ return unless @write_buffer.empty?
729
+
730
+ purge_after_closed
731
+ disconnect if @pending.empty?
732
+
733
+ when :already_open
734
+ nextstate = :open
735
+ # the first check for given io readiness must still use a timeout.
736
+ # connect is the reasonable choice in such a case.
737
+ @timeout = @options.timeout[:connect_timeout]
738
+ send_pending
739
+ when :active
740
+ return unless @state == :inactive
741
+
742
+ nextstate = :open
743
+
744
+ # activate
745
+ @current_session.select_connection(self, @current_selector)
746
+ end
747
+ log(level: 3) { "#{@state} -> #{nextstate}" }
748
+ @state = nextstate
749
+ end
750
+
751
+ def close_sibling
752
+ return unless @sibling
753
+
754
+ if @sibling.io_connected?
755
+ reset
756
+ # TODO: transition connection to closed
757
+ end
758
+
759
+ unless @sibling.state == :closed
760
+ merge(@sibling) unless @main_sibling
761
+ @sibling.force_reset(true)
762
+ end
763
+
764
+ @sibling = nil
765
+ end
766
+
767
+ def purge_after_closed
768
+ @io.close if @io
769
+ @read_buffer.clear
770
+ @timeout = nil
771
+ end
772
+
773
+ def initialize_type(uri, options)
774
+ options.transport || begin
775
+ case uri.scheme
776
+ when "http"
777
+ "tcp"
778
+ when "https"
779
+ "ssl"
780
+ else
781
+ raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
782
+ end
783
+ end
784
+ end
785
+
786
+ # returns an HTTPX::Connection for the negotiated Alternative Service (or none).
787
+ def build_altsvc_connection(alt_origin, origin, alt_params)
788
+ # do not allow security downgrades on altsvc negotiation
789
+ return if @origin.scheme == "https" && alt_origin.scheme != "https"
790
+
791
+ altsvc = AltSvc.cached_altsvc_set(origin, alt_params.merge("origin" => alt_origin))
792
+
793
+ # altsvc already exists, somehow it wasn't advertised, probably noop
794
+ return unless altsvc
795
+
796
+ alt_options = @options.merge(ssl: @options.ssl.merge(hostname: URI(origin).host))
797
+
798
+ connection = @current_session.find_connection(alt_origin, @current_selector, alt_options)
799
+
800
+ # advertised altsvc is the same origin being used, ignore
801
+ return if connection == self
802
+
803
+ connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
804
+
805
+ log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
806
+
807
+ connection.merge(self)
808
+ terminate
809
+ rescue UnsupportedSchemeError
810
+ altsvc["noop"] = true
811
+ nil
812
+ end
813
+
814
+ def build_socket(addrs = nil)
815
+ case @type
816
+ when "tcp"
817
+ TCP.new(peer, addrs, @options)
818
+ when "ssl"
819
+ SSL.new(peer, addrs, @options) do |sock|
820
+ sock.ssl_session = @ssl_session
821
+ sock.session_new_cb do |sess|
822
+ @ssl_session = sess
823
+
824
+ sock.ssl_session = sess
825
+ end
826
+ end
827
+ when "unix"
828
+ path = Array(addrs).first
829
+
830
+ path = String(path) if path
831
+
832
+ UNIX.new(peer, path, @options)
833
+ else
834
+ raise Error, "unsupported transport (#{@type})"
835
+ end
836
+ end
837
+
838
+ def on_error(error, request = nil)
839
+ if error.is_a?(OperationTimeoutError)
840
+
841
+ # inactive connections do not contribute to the select loop, therefore
842
+ # they should not fail due to such errors.
843
+ return if @state == :inactive
844
+
845
+ if @timeout
846
+ @timeout -= error.timeout
847
+ return unless @timeout <= 0
848
+ end
849
+
850
+ error = error.to_connection_error if connecting?
851
+ end
852
+ handle_error(error, request)
853
+ reset
854
+ end
855
+
856
+ def handle_error(error, request = nil)
857
+ parser.handle_error(error, request) if @parser && parser.respond_to?(:handle_error)
858
+ while (req = @pending.shift)
859
+ next if request && req == request
860
+
861
+ response = ErrorResponse.new(req, error)
862
+ req.response = response
863
+ req.emit(:response, response)
864
+ end
865
+
866
+ return unless request
867
+
868
+ @inflight -= 1
869
+ response = ErrorResponse.new(request, error)
870
+ request.response = response
871
+ request.emit(:response, response)
872
+ end
873
+
874
+ def set_request_timeouts(request)
875
+ set_request_write_timeout(request)
876
+ set_request_read_timeout(request)
877
+ set_request_request_timeout(request)
878
+ end
879
+
880
+ def set_request_read_timeout(request)
881
+ read_timeout = request.read_timeout
882
+
883
+ return if read_timeout.nil? || read_timeout.infinite?
884
+
885
+ set_request_timeout(:read_timeout, request, read_timeout, :done, :response) do
886
+ read_timeout_callback(request, read_timeout)
887
+ end
888
+ end
889
+
890
+ def set_request_write_timeout(request)
891
+ write_timeout = request.write_timeout
892
+
893
+ return if write_timeout.nil? || write_timeout.infinite?
894
+
895
+ set_request_timeout(:write_timeout, request, write_timeout, :headers, %i[done response]) do
896
+ write_timeout_callback(request, write_timeout)
897
+ end
898
+ end
899
+
900
+ def set_request_request_timeout(request)
901
+ request_timeout = request.request_timeout
902
+
903
+ return if request_timeout.nil? || request_timeout.infinite?
904
+
905
+ set_request_timeout(:request_timeout, request, request_timeout, :headers, :complete) do
906
+ read_timeout_callback(request, request_timeout, RequestTimeoutError)
907
+ end
908
+ end
909
+
910
+ def write_timeout_callback(request, write_timeout)
911
+ return if request.state == :done
912
+
913
+ @write_buffer.clear
914
+ error = WriteTimeoutError.new(request, nil, write_timeout)
915
+
916
+ on_error(error, request)
917
+ end
918
+
919
+ def read_timeout_callback(request, read_timeout, error_type = ReadTimeoutError)
920
+ response = request.response
921
+
922
+ return if response && response.finished?
923
+
924
+ @write_buffer.clear
925
+ error = error_type.new(request, request.response, read_timeout)
926
+
927
+ on_error(error, request)
928
+ end
929
+
930
+ def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
931
+ request.set_timeout_callback(start_event) do
932
+ timer = @current_selector.after(timeout, callback)
933
+ request.active_timeouts << label
934
+
935
+ Array(finish_events).each do |event|
936
+ # clean up request timeouts if the connection errors out
937
+ request.set_timeout_callback(event) do
938
+ timer.cancel
939
+ request.active_timeouts.delete(label)
940
+ end
941
+ end
942
+ end
943
+ end
944
+
945
+ def parser_type(protocol)
946
+ case protocol
947
+ when "h2" then @options.http2_class
948
+ when "http/1.1" then @options.http1_class
949
+ else
950
+ raise Error, "unsupported protocol (##{protocol})"
951
+ end
952
+ end
953
+ end
954
+ end