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,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds support for retrying requests when errors happen.
7
+ #
8
+ # It has a default max number of retries (see *MAX_RETRIES* and the *max_retries* option),
9
+ # after which it will return the last response, error or not. It will **not** raise an exception.
10
+ #
11
+ # It does not retry which are not considered idempotent (see *retry_change_requests* to override).
12
+ #
13
+ # https://gitlab.com/os85/httpx/wikis/Retries
14
+ #
15
+ module Retries
16
+ MAX_RETRIES = 3
17
+ # TODO: pass max_retries in a configure/load block
18
+
19
+ IDEMPOTENT_METHODS = %w[GET OPTIONS HEAD PUT DELETE].freeze
20
+
21
+ # subset of retryable errors which are safe to retry when reconnecting
22
+ RECONNECTABLE_ERRORS = [
23
+ IOError,
24
+ EOFError,
25
+ Errno::ECONNRESET,
26
+ Errno::ECONNABORTED,
27
+ Errno::EPIPE,
28
+ Errno::EINVAL,
29
+ Errno::ETIMEDOUT,
30
+ ConnectionError,
31
+ TLSError,
32
+ Connection::HTTP2::Error,
33
+ ].freeze
34
+
35
+ RETRYABLE_ERRORS = (RECONNECTABLE_ERRORS + [
36
+ Parser::Error,
37
+ TimeoutError,
38
+ ]).freeze
39
+ DEFAULT_JITTER = ->(interval) { interval * ((rand + 1) * 0.5) }
40
+
41
+ if ENV.key?("HTTPX_NO_JITTER")
42
+ def self.extra_options(options)
43
+ options.merge(max_retries: MAX_RETRIES)
44
+ end
45
+ else
46
+ def self.extra_options(options)
47
+ options.merge(max_retries: MAX_RETRIES, retry_jitter: DEFAULT_JITTER)
48
+ end
49
+ end
50
+
51
+ # adds support for the following options:
52
+ #
53
+ # :max_retries :: max number of times a request will be retried (defaults to <tt>3</tt>).
54
+ # :retry_change_requests :: whether idempotent requests are retried (defaults to <tt>false</tt>).
55
+ # :retry_after:: seconds after which a request is retried; can also be a callable object (i.e. <tt>->(req, res) { ... } </tt>)
56
+ # :retry_jitter :: number of seconds applied to *:retry_after* (must be a callable, i.e. <tt>->(retry_after) { ... } </tt>).
57
+ # :retry_on :: callable which alternatively defines a different rule for when a response is to be retried
58
+ # (i.e. <tt>->(res) { ... }</tt>).
59
+ module OptionsMethods
60
+ private
61
+
62
+ def option_retry_after(value)
63
+ # return early if callable
64
+ unless value.respond_to?(:call)
65
+ value = Float(value)
66
+ raise TypeError, ":retry_after must be positive" unless value.positive?
67
+ end
68
+
69
+ value
70
+ end
71
+
72
+ def option_retry_jitter(value)
73
+ # return early if callable
74
+ raise TypeError, ":retry_jitter must be callable" unless value.respond_to?(:call)
75
+
76
+ value
77
+ end
78
+
79
+ def option_max_retries(value)
80
+ num = Integer(value)
81
+ raise TypeError, ":max_retries must be positive" unless num >= 0
82
+
83
+ num
84
+ end
85
+
86
+ def option_retry_change_requests(v)
87
+ v
88
+ end
89
+
90
+ def option_retry_on(value)
91
+ raise TypeError, ":retry_on must be called with the response" unless value.respond_to?(:call)
92
+
93
+ value
94
+ end
95
+ end
96
+
97
+ module InstanceMethods
98
+ # returns a `:retries` plugin enabled session with +n+ maximum retries per request setting.
99
+ def max_retries(n)
100
+ with(max_retries: n)
101
+ end
102
+
103
+ private
104
+
105
+ def fetch_response(request, selector, options)
106
+ response = super
107
+
108
+ if response &&
109
+ request.retries.positive? &&
110
+ repeatable_request?(request, options) &&
111
+ (
112
+ (
113
+ response.is_a?(ErrorResponse) && retryable_error?(response.error)
114
+ ) ||
115
+ (
116
+ options.retry_on && options.retry_on.call(response)
117
+ )
118
+ )
119
+ try_partial_retry(request, response)
120
+ log { "failed to get response, #{request.retries} tries to go..." }
121
+ request.retries -= 1 unless request.ping? # do not exhaust retries on connection liveness probes
122
+ request.transition(:idle)
123
+
124
+ retry_after = options.retry_after
125
+ retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
126
+
127
+ if retry_after
128
+ # apply jitter
129
+ if (jitter = request.options.retry_jitter)
130
+ retry_after = jitter.call(retry_after)
131
+ end
132
+
133
+ retry_start = Utils.now
134
+ log { "retrying after #{retry_after} secs..." }
135
+ selector.after(retry_after) do
136
+ if (response = request.response)
137
+ response.finish!
138
+ # request has terminated abruptly meanwhile
139
+ request.emit(:response, response)
140
+ else
141
+ log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
142
+ send_request(request, selector, options)
143
+ end
144
+ end
145
+ else
146
+ send_request(request, selector, options)
147
+ end
148
+
149
+ return
150
+ end
151
+ response
152
+ end
153
+
154
+ # returns whether +request+ can be retried.
155
+ def repeatable_request?(request, options)
156
+ IDEMPOTENT_METHODS.include?(request.verb) || options.retry_change_requests
157
+ end
158
+
159
+ # returns whether the +ex+ exception happend for a retriable request.
160
+ def retryable_error?(ex)
161
+ RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
162
+ end
163
+
164
+ def proxy_error?(request, response, _)
165
+ super && !request.retries.positive?
166
+ end
167
+
168
+ #
169
+ # Attempt to set the request to perform a partial range request.
170
+ # This happens if the peer server accepts byte-range requests, and
171
+ # the last response contains some body payload.
172
+ #
173
+ def try_partial_retry(request, response)
174
+ response = response.response if response.is_a?(ErrorResponse)
175
+
176
+ return unless response
177
+
178
+ unless response.headers.key?("accept-ranges") &&
179
+ response.headers["accept-ranges"] == "bytes" && # there's nothing else supported though...
180
+ (original_body = response.body)
181
+ response.body.close
182
+ return
183
+ end
184
+
185
+ request.partial_response = response
186
+
187
+ size = original_body.bytesize
188
+
189
+ request.headers["range"] = "bytes=#{size}-"
190
+ end
191
+ end
192
+
193
+ module RequestMethods
194
+ # number of retries left.
195
+ attr_accessor :retries
196
+
197
+ # a response partially received before.
198
+ attr_writer :partial_response
199
+
200
+ # initializes the request instance, sets the number of retries for the request.
201
+ def initialize(*args)
202
+ super
203
+ @retries = @options.max_retries
204
+ end
205
+
206
+ def response=(response)
207
+ if @partial_response
208
+ if response.is_a?(Response) && response.status == 206
209
+ response.from_partial_response(@partial_response)
210
+ else
211
+ @partial_response.close
212
+ end
213
+ @partial_response = nil
214
+ end
215
+
216
+ super
217
+ end
218
+ end
219
+
220
+ module ResponseMethods
221
+ def from_partial_response(response)
222
+ @status = response.status
223
+ @headers = response.headers
224
+ @body = response.body
225
+ end
226
+ end
227
+ end
228
+ register_plugin :retries, Retries
229
+ end
230
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ class ServerSideRequestForgeryError < Error; end
5
+
6
+ module Plugins
7
+ #
8
+ # This plugin adds support for preventing Server-Side Request Forgery attacks.
9
+ #
10
+ # https://gitlab.com/os85/httpx/wikis/Server-Side-Request-Forgery-Filter
11
+ #
12
+ module SsrfFilter
13
+ module IPAddrExtensions
14
+ refine IPAddr do
15
+ def prefixlen
16
+ mask_addr = @mask_addr
17
+ raise "Invalid mask" if mask_addr.zero?
18
+
19
+ mask_addr >>= 1 while (mask_addr & 0x1).zero?
20
+
21
+ length = 0
22
+ while mask_addr & 0x1 == 0x1
23
+ length += 1
24
+ mask_addr >>= 1
25
+ end
26
+
27
+ length
28
+ end
29
+ end
30
+ end
31
+
32
+ using IPAddrExtensions
33
+
34
+ # https://en.wikipedia.org/wiki/Reserved_IP_addresses
35
+ IPV4_BLACKLIST = [
36
+ IPAddr.new("0.0.0.0/8"), # Current network (only valid as source address)
37
+ IPAddr.new("10.0.0.0/8"), # Private network
38
+ IPAddr.new("100.64.0.0/10"), # Shared Address Space
39
+ IPAddr.new("127.0.0.0/8"), # Loopback
40
+ IPAddr.new("169.254.0.0/16"), # Link-local
41
+ IPAddr.new("172.16.0.0/12"), # Private network
42
+ IPAddr.new("192.0.0.0/24"), # IETF Protocol Assignments
43
+ IPAddr.new("192.0.2.0/24"), # TEST-NET-1, documentation and examples
44
+ IPAddr.new("192.88.99.0/24"), # IPv6 to IPv4 relay (includes 2002::/16)
45
+ IPAddr.new("192.168.0.0/16"), # Private network
46
+ IPAddr.new("198.18.0.0/15"), # Network benchmark tests
47
+ IPAddr.new("198.51.100.0/24"), # TEST-NET-2, documentation and examples
48
+ IPAddr.new("203.0.113.0/24"), # TEST-NET-3, documentation and examples
49
+ IPAddr.new("224.0.0.0/4"), # IP multicast (former Class D network)
50
+ IPAddr.new("240.0.0.0/4"), # Reserved (former Class E network)
51
+ IPAddr.new("255.255.255.255"), # Broadcast
52
+ ].freeze
53
+
54
+ IPV6_BLACKLIST = ([
55
+ IPAddr.new("::1/128"), # Loopback
56
+ IPAddr.new("64:ff9b::/96"), # IPv4/IPv6 translation (RFC 6052)
57
+ IPAddr.new("100::/64"), # Discard prefix (RFC 6666)
58
+ IPAddr.new("2001::/32"), # Teredo tunneling
59
+ IPAddr.new("2001:10::/28"), # Deprecated (previously ORCHID)
60
+ IPAddr.new("2001:20::/28"), # ORCHIDv2
61
+ IPAddr.new("2001:db8::/32"), # Addresses used in documentation and example source code
62
+ IPAddr.new("2002::/16"), # 6to4
63
+ IPAddr.new("fc00::/7"), # Unique local address
64
+ IPAddr.new("fe80::/10"), # Link-local address
65
+ IPAddr.new("ff00::/8"), # Multicast
66
+ ] + IPV4_BLACKLIST.flat_map do |ipaddr|
67
+ prefixlen = ipaddr.prefixlen
68
+
69
+ ipv4_compatible = ipaddr.ipv4_compat.mask(96 + prefixlen)
70
+ ipv4_mapped = ipaddr.ipv4_mapped.mask(80 + prefixlen)
71
+
72
+ [ipv4_compatible, ipv4_mapped]
73
+ end).freeze
74
+
75
+ class << self
76
+ def extra_options(options)
77
+ options.merge(allowed_schemes: %w[https http])
78
+ end
79
+
80
+ def unsafe_ip_address?(ipaddr)
81
+ range = ipaddr.to_range
82
+ return true if range.first != range.last
83
+
84
+ return IPV6_BLACKLIST.any? { |r| r.include?(ipaddr) } if ipaddr.ipv6?
85
+
86
+ IPV4_BLACKLIST.any? { |r| r.include?(ipaddr) } # then it's IPv4
87
+ end
88
+ end
89
+
90
+ # adds support for the following options:
91
+ #
92
+ # :allowed_schemes :: list of URI schemes allowed (defaults to <tt>["https", "http"]</tt>)
93
+ module OptionsMethods
94
+ private
95
+
96
+ def option_allowed_schemes(value)
97
+ Array(value)
98
+ end
99
+ end
100
+
101
+ module InstanceMethods
102
+ def send_requests(*requests)
103
+ responses = requests.map do |request|
104
+ next if @options.allowed_schemes.include?(request.uri.scheme)
105
+
106
+ error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
107
+ error.set_backtrace(caller)
108
+ response = ErrorResponse.new(request, error)
109
+ request.emit(:response, response)
110
+ response
111
+ end
112
+ allowed_requests = requests.select { |req| responses[requests.index(req)].nil? }
113
+ allowed_responses = super(*allowed_requests)
114
+ allowed_responses.each_with_index do |res, idx|
115
+ req = allowed_requests[idx]
116
+ responses[requests.index(req)] = res
117
+ end
118
+
119
+ responses
120
+ end
121
+ end
122
+
123
+ module ConnectionMethods
124
+ def initialize(*)
125
+ begin
126
+ super
127
+ rescue ServerSideRequestForgeryError => e
128
+ # may raise when IPs are passed as options via :addresses
129
+ throw(:resolve_error, e)
130
+ end
131
+ end
132
+
133
+ def addresses=(addrs)
134
+ addrs.reject!(&SsrfFilter.method(:unsafe_ip_address?))
135
+
136
+ raise ServerSideRequestForgeryError, "#{@origin.host} has no public IP addresses" if addrs.empty?
137
+
138
+ super
139
+ end
140
+ end
141
+ end
142
+
143
+ register_plugin :ssrf_filter, SsrfFilter
144
+ end
145
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ class StreamResponse
5
+ attr_reader :request
6
+
7
+ def initialize(request, session)
8
+ @request = request
9
+ @options = @request.options
10
+ @session = session
11
+ @response_enum = nil
12
+ @buffered_chunks = []
13
+ end
14
+
15
+ def each(&block)
16
+ return enum_for(__method__) unless block
17
+
18
+ if (response_enum = @response_enum)
19
+ @response_enum = nil
20
+ # streaming already started, let's finish it
21
+
22
+ while (chunk = @buffered_chunks.shift)
23
+ block.call(chunk)
24
+ end
25
+
26
+ # consume enum til the end
27
+ begin
28
+ while (chunk = response_enum.next)
29
+ block.call(chunk)
30
+ end
31
+ rescue StopIteration
32
+ return
33
+ end
34
+ end
35
+
36
+ @request.stream = self
37
+
38
+ begin
39
+ @on_chunk = block
40
+
41
+ response = @session.request(@request)
42
+
43
+ response.raise_for_status
44
+ ensure
45
+ @on_chunk = nil
46
+ end
47
+ end
48
+
49
+ def each_line
50
+ return enum_for(__method__) unless block_given?
51
+
52
+ line = "".b
53
+
54
+ each do |chunk|
55
+ line << chunk
56
+
57
+ while (idx = line.index("\n"))
58
+ yield line.byteslice(0..idx - 1)
59
+
60
+ line = line.byteslice(idx + 1..-1)
61
+ end
62
+ end
63
+
64
+ yield line unless line.empty?
65
+ end
66
+
67
+ # This is a ghost method. It's to be used ONLY internally, when processing streams
68
+ def on_chunk(chunk)
69
+ raise NoMethodError unless @on_chunk
70
+
71
+ @on_chunk.call(chunk)
72
+ end
73
+
74
+ # :nocov:
75
+ def inspect
76
+ "#<#{self.class}:#{object_id}>"
77
+ end
78
+ # :nocov:
79
+
80
+ def to_s
81
+ if @request.response
82
+ @request.response.to_s
83
+ else
84
+ @buffered_chunks.join
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def response
91
+ @request.response || begin
92
+ response_enum = each
93
+ while (chunk = response_enum.next)
94
+ @buffered_chunks << chunk
95
+ break if @request.response
96
+ end
97
+ @response_enum = response_enum
98
+ @request.response
99
+ end
100
+ end
101
+
102
+ def respond_to_missing?(meth, include_private)
103
+ if (response = @request.response)
104
+ response.respond_to_missing?(meth, include_private)
105
+ else
106
+ @options.response_class.method_defined?(meth) || (include_private && @options.response_class.private_method_defined?(meth))
107
+ end || super
108
+ end
109
+
110
+ def method_missing(meth, *args, **kwargs, &block)
111
+ return super unless response.respond_to?(meth)
112
+
113
+ response.__send__(meth, *args, **kwargs, &block)
114
+ end
115
+ end
116
+
117
+ module Plugins
118
+ #
119
+ # This plugin adds support for streaming a response (useful for i.e. "text/event-stream" payloads).
120
+ #
121
+ # https://gitlab.com/os85/httpx/wikis/Stream
122
+ #
123
+ module Stream
124
+ def self.extra_options(options)
125
+ options.merge(timeout: { read_timeout: Float::INFINITY, operation_timeout: 60 })
126
+ end
127
+
128
+ module InstanceMethods
129
+ def request(*args, stream: false, **options)
130
+ return super(*args, **options) unless stream
131
+
132
+ requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
133
+ raise Error, "only 1 response at a time is supported for streaming requests" unless requests.size == 1
134
+
135
+ request = requests.first
136
+
137
+ StreamResponse.new(request, self)
138
+ end
139
+ end
140
+
141
+ module RequestMethods
142
+ attr_accessor :stream
143
+ end
144
+
145
+ module ResponseMethods
146
+ def stream
147
+ request = @request.root_request if @request.respond_to?(:root_request)
148
+ request ||= @request
149
+
150
+ request.stream
151
+ end
152
+ end
153
+
154
+ module ResponseBodyMethods
155
+ def initialize(*)
156
+ super
157
+ @stream = @response.stream
158
+ end
159
+
160
+ def write(chunk)
161
+ return super unless @stream
162
+
163
+ return 0 if chunk.empty?
164
+
165
+ chunk = decode_chunk(chunk)
166
+
167
+ @stream.on_chunk(chunk.dup)
168
+
169
+ chunk.size
170
+ end
171
+
172
+ private
173
+
174
+ def transition(*)
175
+ return if @stream
176
+
177
+ super
178
+ end
179
+ end
180
+ end
181
+ register_plugin :stream, Stream
182
+ end
183
+ end