httpx 0.20.0 → 1.3.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 (250) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -48
  3. data/README.md +54 -45
  4. data/doc/release_notes/0_10_0.md +2 -2
  5. data/doc/release_notes/0_11_0.md +3 -5
  6. data/doc/release_notes/0_12_0.md +5 -5
  7. data/doc/release_notes/0_13_0.md +5 -5
  8. data/doc/release_notes/0_14_0.md +2 -2
  9. data/doc/release_notes/0_16_0.md +3 -3
  10. data/doc/release_notes/0_17_0.md +1 -1
  11. data/doc/release_notes/0_18_0.md +4 -4
  12. data/doc/release_notes/0_18_2.md +1 -1
  13. data/doc/release_notes/0_19_0.md +1 -1
  14. data/doc/release_notes/0_19_8.md +1 -1
  15. data/doc/release_notes/0_20_0.md +2 -2
  16. data/doc/release_notes/0_20_1.md +5 -0
  17. data/doc/release_notes/0_20_2.md +7 -0
  18. data/doc/release_notes/0_20_3.md +6 -0
  19. data/doc/release_notes/0_20_4.md +17 -0
  20. data/doc/release_notes/0_20_5.md +3 -0
  21. data/doc/release_notes/0_21_0.md +96 -0
  22. data/doc/release_notes/0_21_1.md +12 -0
  23. data/doc/release_notes/0_22_0.md +13 -0
  24. data/doc/release_notes/0_22_1.md +11 -0
  25. data/doc/release_notes/0_22_2.md +5 -0
  26. data/doc/release_notes/0_22_3.md +55 -0
  27. data/doc/release_notes/0_22_4.md +6 -0
  28. data/doc/release_notes/0_22_5.md +6 -0
  29. data/doc/release_notes/0_23_0.md +42 -0
  30. data/doc/release_notes/0_23_1.md +5 -0
  31. data/doc/release_notes/0_23_2.md +5 -0
  32. data/doc/release_notes/0_23_3.md +6 -0
  33. data/doc/release_notes/0_23_4.md +5 -0
  34. data/doc/release_notes/0_24_0.md +48 -0
  35. data/doc/release_notes/0_24_1.md +12 -0
  36. data/doc/release_notes/0_24_2.md +12 -0
  37. data/doc/release_notes/0_24_3.md +12 -0
  38. data/doc/release_notes/0_24_4.md +18 -0
  39. data/doc/release_notes/0_24_5.md +6 -0
  40. data/doc/release_notes/0_24_6.md +5 -0
  41. data/doc/release_notes/0_24_7.md +10 -0
  42. data/doc/release_notes/1_0_0.md +60 -0
  43. data/doc/release_notes/1_0_1.md +5 -0
  44. data/doc/release_notes/1_0_2.md +7 -0
  45. data/doc/release_notes/1_1_0.md +32 -0
  46. data/doc/release_notes/1_1_1.md +17 -0
  47. data/doc/release_notes/1_1_2.md +12 -0
  48. data/doc/release_notes/1_1_3.md +18 -0
  49. data/doc/release_notes/1_1_4.md +6 -0
  50. data/doc/release_notes/1_1_5.md +12 -0
  51. data/doc/release_notes/1_2_0.md +49 -0
  52. data/doc/release_notes/1_2_1.md +6 -0
  53. data/doc/release_notes/1_2_2.md +10 -0
  54. data/doc/release_notes/1_2_3.md +16 -0
  55. data/doc/release_notes/1_2_4.md +8 -0
  56. data/doc/release_notes/1_2_5.md +7 -0
  57. data/doc/release_notes/1_2_6.md +13 -0
  58. data/doc/release_notes/1_3_0.md +18 -0
  59. data/doc/release_notes/1_3_1.md +17 -0
  60. data/lib/httpx/adapters/datadog.rb +215 -122
  61. data/lib/httpx/adapters/faraday.rb +145 -107
  62. data/lib/httpx/adapters/sentry.rb +26 -7
  63. data/lib/httpx/adapters/webmock.rb +34 -18
  64. data/lib/httpx/altsvc.rb +63 -26
  65. data/lib/httpx/base64.rb +27 -0
  66. data/lib/httpx/buffer.rb +12 -0
  67. data/lib/httpx/callbacks.rb +5 -3
  68. data/lib/httpx/chainable.rb +54 -39
  69. data/lib/httpx/connection/http1.rb +75 -44
  70. data/lib/httpx/connection/http2.rb +31 -38
  71. data/lib/httpx/connection.rb +287 -117
  72. data/lib/httpx/domain_name.rb +10 -13
  73. data/lib/httpx/errors.rb +52 -2
  74. data/lib/httpx/extensions.rb +24 -131
  75. data/lib/httpx/io/ssl.rb +83 -77
  76. data/lib/httpx/io/tcp.rb +48 -71
  77. data/lib/httpx/io/udp.rb +18 -52
  78. data/lib/httpx/io/unix.rb +10 -15
  79. data/lib/httpx/io.rb +3 -9
  80. data/lib/httpx/loggable.rb +4 -19
  81. data/lib/httpx/options.rb +176 -118
  82. data/lib/httpx/parser/http1.rb +4 -0
  83. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  84. data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
  85. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  86. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  87. data/lib/httpx/plugins/auth.rb +25 -0
  88. data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
  89. data/lib/httpx/plugins/aws_sigv4.rb +12 -9
  90. data/lib/httpx/plugins/basic_auth.rb +29 -0
  91. data/lib/httpx/plugins/brotli.rb +50 -0
  92. data/lib/httpx/plugins/callbacks.rb +91 -0
  93. data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
  94. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
  95. data/lib/httpx/plugins/circuit_breaker.rb +148 -0
  96. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  97. data/lib/httpx/plugins/cookies.rb +30 -17
  98. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
  99. data/lib/httpx/plugins/expect.rb +21 -14
  100. data/lib/httpx/plugins/follow_redirects.rb +140 -41
  101. data/lib/httpx/plugins/grpc/call.rb +2 -3
  102. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  103. data/lib/httpx/plugins/grpc/message.rb +7 -37
  104. data/lib/httpx/plugins/grpc.rb +36 -29
  105. data/lib/httpx/plugins/h2c.rb +26 -19
  106. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  107. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  108. data/lib/httpx/plugins/oauth.rb +175 -0
  109. data/lib/httpx/plugins/persistent.rb +1 -1
  110. data/lib/httpx/plugins/proxy/http.rb +23 -13
  111. data/lib/httpx/plugins/proxy/socks4.rb +9 -7
  112. data/lib/httpx/plugins/proxy/socks5.rb +11 -9
  113. data/lib/httpx/plugins/proxy.rb +80 -61
  114. data/lib/httpx/plugins/push_promise.rb +1 -1
  115. data/lib/httpx/plugins/rate_limiter.rb +5 -1
  116. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  117. data/lib/httpx/plugins/response_cache/store.rb +62 -25
  118. data/lib/httpx/plugins/response_cache.rb +105 -12
  119. data/lib/httpx/plugins/retries.rb +87 -17
  120. data/lib/httpx/plugins/ssrf_filter.rb +145 -0
  121. data/lib/httpx/plugins/stream.rb +27 -23
  122. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  123. data/lib/httpx/plugins/upgrade.rb +8 -10
  124. data/lib/httpx/plugins/webdav.rb +80 -0
  125. data/lib/httpx/pool/synch_pool.rb +93 -0
  126. data/lib/httpx/pool.rb +102 -27
  127. data/lib/httpx/punycode.rb +9 -291
  128. data/lib/httpx/request/body.rb +154 -0
  129. data/lib/httpx/request.rb +130 -146
  130. data/lib/httpx/resolver/https.rb +62 -27
  131. data/lib/httpx/resolver/multi.rb +9 -13
  132. data/lib/httpx/resolver/native.rb +192 -76
  133. data/lib/httpx/resolver/resolver.rb +34 -9
  134. data/lib/httpx/resolver/system.rb +16 -11
  135. data/lib/httpx/resolver.rb +38 -16
  136. data/lib/httpx/response/body.rb +242 -0
  137. data/lib/httpx/response/buffer.rb +96 -0
  138. data/lib/httpx/response.rb +159 -217
  139. data/lib/httpx/selector.rb +9 -4
  140. data/lib/httpx/session.rb +137 -89
  141. data/lib/httpx/session_extensions.rb +4 -1
  142. data/lib/httpx/timers.rb +34 -8
  143. data/lib/httpx/transcoder/body.rb +0 -2
  144. data/lib/httpx/transcoder/chunker.rb +0 -1
  145. data/lib/httpx/transcoder/deflate.rb +37 -0
  146. data/lib/httpx/transcoder/form.rb +52 -33
  147. data/lib/httpx/transcoder/gzip.rb +74 -0
  148. data/lib/httpx/transcoder/json.rb +21 -8
  149. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  150. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
  151. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  152. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  153. data/lib/httpx/transcoder/multipart.rb +17 -0
  154. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  155. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  156. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  157. data/lib/httpx/transcoder/xml.rb +52 -0
  158. data/lib/httpx/transcoder.rb +5 -6
  159. data/lib/httpx/utils.rb +36 -16
  160. data/lib/httpx/version.rb +1 -1
  161. data/lib/httpx.rb +12 -14
  162. data/sig/altsvc.rbs +33 -0
  163. data/sig/buffer.rbs +2 -1
  164. data/sig/callbacks.rbs +3 -3
  165. data/sig/chainable.rbs +11 -9
  166. data/sig/connection/http1.rbs +8 -7
  167. data/sig/connection/http2.rbs +19 -19
  168. data/sig/connection.rbs +64 -24
  169. data/sig/errors.rbs +22 -3
  170. data/sig/httpx.rbs +5 -4
  171. data/sig/io/ssl.rbs +27 -0
  172. data/sig/io/tcp.rbs +60 -0
  173. data/sig/io/udp.rbs +20 -0
  174. data/sig/io/unix.rbs +27 -0
  175. data/sig/io.rbs +6 -0
  176. data/sig/options.rbs +32 -22
  177. data/sig/parser/http1.rbs +1 -1
  178. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  179. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  180. data/sig/plugins/auth.rbs +13 -0
  181. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  182. data/sig/plugins/brotli.rbs +22 -0
  183. data/sig/plugins/callbacks.rbs +38 -0
  184. data/sig/plugins/circuit_breaker.rbs +71 -0
  185. data/sig/plugins/compression.rbs +7 -5
  186. data/sig/plugins/cookies/jar.rbs +2 -2
  187. data/sig/plugins/cookies.rbs +2 -0
  188. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  189. data/sig/plugins/follow_redirects.rbs +18 -4
  190. data/sig/plugins/grpc/call.rbs +19 -0
  191. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  192. data/sig/plugins/grpc/message.rbs +17 -0
  193. data/sig/plugins/grpc.rbs +7 -32
  194. data/sig/plugins/h2c.rbs +1 -1
  195. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  196. data/sig/plugins/oauth.rbs +54 -0
  197. data/sig/plugins/proxy/http.rbs +3 -0
  198. data/sig/plugins/proxy/socks4.rbs +9 -6
  199. data/sig/plugins/proxy/socks5.rbs +10 -6
  200. data/sig/plugins/proxy/ssh.rbs +1 -1
  201. data/sig/plugins/proxy.rbs +13 -5
  202. data/sig/plugins/push_promise.rbs +3 -3
  203. data/sig/plugins/rate_limiter.rbs +1 -1
  204. data/sig/plugins/response_cache.rbs +36 -7
  205. data/sig/plugins/retries.rbs +30 -8
  206. data/sig/plugins/stream.rbs +24 -17
  207. data/sig/plugins/upgrade.rbs +5 -3
  208. data/sig/pool.rbs +10 -7
  209. data/sig/request/body.rbs +38 -0
  210. data/sig/request.rbs +15 -24
  211. data/sig/resolver/https.rbs +8 -3
  212. data/sig/resolver/native.rbs +17 -4
  213. data/sig/resolver/resolver.rbs +8 -6
  214. data/sig/resolver/system.rbs +2 -0
  215. data/sig/resolver.rbs +9 -5
  216. data/sig/response/body.rbs +53 -0
  217. data/sig/response/buffer.rbs +24 -0
  218. data/sig/response.rbs +24 -39
  219. data/sig/selector.rbs +1 -1
  220. data/sig/session.rbs +29 -18
  221. data/sig/timers.rbs +18 -8
  222. data/sig/transcoder/body.rbs +4 -3
  223. data/sig/transcoder/deflate.rbs +11 -0
  224. data/sig/transcoder/form.rbs +5 -3
  225. data/sig/transcoder/gzip.rbs +24 -0
  226. data/sig/transcoder/json.rbs +8 -3
  227. data/sig/{plugins → transcoder}/multipart.rbs +15 -19
  228. data/sig/transcoder/utils/body_reader.rbs +15 -0
  229. data/sig/transcoder/utils/deflater.rbs +29 -0
  230. data/sig/transcoder/utils/inflater.rbs +12 -0
  231. data/sig/transcoder/xml.rbs +22 -0
  232. data/sig/transcoder.rbs +24 -9
  233. data/sig/utils.rbs +8 -2
  234. metadata +163 -41
  235. data/lib/httpx/plugins/authentication.rb +0 -20
  236. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  237. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  238. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  239. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  240. data/lib/httpx/plugins/compression.rb +0 -164
  241. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  242. data/lib/httpx/plugins/multipart.rb +0 -84
  243. data/lib/httpx/registry.rb +0 -85
  244. data/sig/plugins/authentication.rbs +0 -11
  245. data/sig/plugins/compression/brotli.rbs +0 -21
  246. data/sig/plugins/compression/deflate.rbs +0 -17
  247. data/sig/plugins/compression/gzip.rbs +0 -29
  248. data/sig/registry.rbs +0 -12
  249. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  250. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
@@ -3,9 +3,9 @@
3
3
  module HTTPX
4
4
  module Plugins
5
5
  #
6
- # This plugin adds helper methods to implement HTTP Digest Auth (https://tools.ietf.org/html/rfc7616)
6
+ # This plugin adds helper methods to implement HTTP Digest Auth (https://datatracker.ietf.org/doc/html/rfc7616)
7
7
  #
8
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Authentication#authentication
8
+ # https://gitlab.com/os85/httpx/wikis/Auth#digest-auth
9
9
  #
10
10
  module DigestAuth
11
11
  DigestError = Class.new(Error)
@@ -16,36 +16,38 @@ module HTTPX
16
16
  end
17
17
 
18
18
  def load_dependencies(*)
19
- require_relative "authentication/digest"
19
+ require_relative "auth/digest"
20
20
  end
21
21
  end
22
22
 
23
+ # adds support for the following options:
24
+ #
25
+ # :digest :: instance of HTTPX::Plugins::Authentication::Digest, used to authenticate requests in the session.
23
26
  module OptionsMethods
24
27
  def option_digest(value)
25
- raise TypeError, ":digest must be a Digest" unless value.is_a?(Authentication::Digest)
28
+ raise TypeError, ":digest must be a #{Authentication::Digest}" unless value.is_a?(Authentication::Digest)
26
29
 
27
30
  value
28
31
  end
29
32
  end
30
33
 
31
34
  module InstanceMethods
32
- def digest_authentication(user, password)
33
- with(digest: Authentication::Digest.new(user, password))
35
+ def digest_auth(user, password, hashed: false)
36
+ with(digest: Authentication::Digest.new(user, password, hashed: hashed))
34
37
  end
35
38
 
36
- alias_method :digest_auth, :digest_authentication
39
+ private
37
40
 
38
41
  def send_requests(*requests)
39
42
  requests.flat_map do |request|
40
43
  digest = request.options.digest
41
44
 
42
- unless digest
43
- super(request)
44
- next
45
- end
45
+ next super(request) unless digest
46
46
 
47
47
  probe_response = wrap { super(request).first }
48
48
 
49
+ return probe_response unless probe_response.is_a?(Response)
50
+
49
51
  if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
50
52
  request.transition(:idle)
51
53
  request.headers["authorization"] = digest.authenticate(request, probe_response.headers["www-authenticate"])
@@ -58,6 +60,6 @@ module HTTPX
58
60
  end
59
61
  end
60
62
 
61
- register_plugin :digest_authentication, DigestAuth
63
+ register_plugin :digest_auth, DigestAuth
62
64
  end
63
65
  end
@@ -5,7 +5,7 @@ module HTTPX
5
5
  #
6
6
  # This plugin makes all HTTP/1.1 requests with a body send the "Expect: 100-continue".
7
7
  #
8
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Expect#expect
8
+ # https://gitlab.com/os85/httpx/wikis/Expect#expect
9
9
  #
10
10
  module Expect
11
11
  EXPECT_TIMEOUT = 2
@@ -20,9 +20,14 @@ module HTTPX
20
20
  end
21
21
  end
22
22
 
23
+ # adds support for the following options:
24
+ #
25
+ # :expect_timeout :: time (in seconds) to wait for a 100-expect response,
26
+ # before retrying without the Expect header (defaults to <tt>2</tt>).
27
+ # :expect_threshold_size :: min threshold (in bytes) of the request payload to enable the 100-continue negotiation on.
23
28
  module OptionsMethods
24
29
  def option_expect_timeout(value)
25
- seconds = Integer(value)
30
+ seconds = Float(value)
26
31
  raise TypeError, ":expect_timeout must be positive" unless seconds.positive?
27
32
 
28
33
  seconds
@@ -50,7 +55,8 @@ module HTTPX
50
55
  end
51
56
 
52
57
  def response=(response)
53
- if response && response.status == 100 &&
58
+ if response.is_a?(Response) &&
59
+ response.status == 100 &&
54
60
  !@headers.key?("expect") &&
55
61
  (@state == :body || @state == :done)
56
62
 
@@ -74,14 +80,16 @@ module HTTPX
74
80
 
75
81
  return unless request.headers["expect"] == "100-continue"
76
82
 
77
- request.once(:expect) do
78
- @timers.after(request.options.expect_timeout) do
79
- # expect timeout expired
80
- if request.state == :expect && !request.expects?
81
- Expect.no_expect_store << request.origin
82
- request.headers.delete("expect")
83
- consume
84
- end
83
+ expect_timeout = request.options.expect_timeout
84
+
85
+ return if expect_timeout.nil? || expect_timeout.infinite?
86
+
87
+ set_request_timeout(request, expect_timeout, :expect, %i[body response]) do
88
+ # expect timeout expired
89
+ if request.state == :expect && !request.expects?
90
+ Expect.no_expect_store << request.origin
91
+ request.headers.delete("expect")
92
+ consume
85
93
  end
86
94
  end
87
95
  end
@@ -92,12 +100,11 @@ module HTTPX
92
100
  response = @responses.delete(request)
93
101
  return unless response
94
102
 
95
- if response.status == 417 && request.headers.key?("expect")
103
+ if response.is_a?(Response) && response.status == 417 && request.headers.key?("expect")
96
104
  response.close
97
105
  request.headers.delete("expect")
98
106
  request.transition(:idle)
99
- connection = find_connection(request, connections, options)
100
- connection.send(request)
107
+ send_request(request, connections, options)
101
108
  return
102
109
  end
103
110
 
@@ -4,19 +4,35 @@ module HTTPX
4
4
  InsecureRedirectError = Class.new(Error)
5
5
  module Plugins
6
6
  #
7
- # This plugin adds support for following redirect (status 30X) responses.
7
+ # This plugin adds support for automatically following redirect (status 30X) responses.
8
8
  #
9
- # It has an upper bound of followed redirects (see *MAX_REDIRECTS*), after which it
10
- # will return the last redirect response. It will **not** raise an exception.
9
+ # It has a default upper bound of followed redirects (see *MAX_REDIRECTS* and the *max_redirects* option),
10
+ # after which it will return the last redirect response. It will **not** raise an exception.
11
11
  #
12
- # It also doesn't follow insecure redirects (https -> http) by default (see *follow_insecure_redirects*).
12
+ # It doesn't follow insecure redirects (https -> http) by default (see *follow_insecure_redirects*).
13
13
  #
14
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Follow-Redirects
14
+ # It doesn't propagate authorization related headers to requests redirecting to different origins
15
+ # (see *allow_auth_to_other_origins*) to override.
16
+ #
17
+ # It allows customization of when to redirect via the *redirect_on* callback option).
18
+ #
19
+ # https://gitlab.com/os85/httpx/wikis/Follow-Redirects
15
20
  #
16
21
  module FollowRedirects
17
22
  MAX_REDIRECTS = 3
18
23
  REDIRECT_STATUS = (300..399).freeze
19
-
24
+ REQUEST_BODY_HEADERS = %w[transfer-encoding content-encoding content-type content-length content-language content-md5 trailer].freeze
25
+
26
+ using URIExtensions
27
+
28
+ # adds support for the following options:
29
+ #
30
+ # :max_redirects :: max number of times a request will be redirected (defaults to <tt>3</tt>).
31
+ # :follow_insecure_redirects :: whether redirects to an "http://" URI, when coming from an "https//", are allowed
32
+ # (defaults to <tt>false</tt>).
33
+ # :allow_auth_to_other_origins :: whether auth-related headers, such as "Authorization", are propagated on redirection
34
+ # (defaults to <tt>false</tt>).
35
+ # :redirect_on :: optional callback which receives the redirect location and can halt the redirect chain if it returns <tt>false</tt>.
20
36
  module OptionsMethods
21
37
  def option_max_redirects(value)
22
38
  num = Integer(value)
@@ -28,9 +44,20 @@ module HTTPX
28
44
  def option_follow_insecure_redirects(value)
29
45
  value
30
46
  end
47
+
48
+ def option_allow_auth_to_other_origins(value)
49
+ value
50
+ end
51
+
52
+ def option_redirect_on(value)
53
+ raise TypeError, ":redirect_on must be callable" unless value.respond_to?(:call)
54
+
55
+ value
56
+ end
31
57
  end
32
58
 
33
59
  module InstanceMethods
60
+ # returns a session with the *max_redirects* option set to +n+
34
61
  def max_redirects(n)
35
62
  with(max_redirects: n.to_i)
36
63
  end
@@ -44,68 +71,117 @@ module HTTPX
44
71
 
45
72
  max_redirects = redirect_request.max_redirects
46
73
 
74
+ return response unless response.is_a?(Response)
47
75
  return response unless REDIRECT_STATUS.include?(response.status) && response.headers.key?("location")
48
76
  return response unless max_redirects.positive?
49
77
 
50
- retry_request = build_redirect_request(redirect_request, response, options)
78
+ redirect_uri = __get_location_from_response(response)
51
79
 
52
- request.redirect_request = retry_request
80
+ if options.redirect_on
81
+ redirect_allowed = options.redirect_on.call(redirect_uri)
82
+ return response unless redirect_allowed
83
+ end
84
+
85
+ # build redirect request
86
+ request_body = redirect_request.body
87
+ redirect_method = "GET"
88
+ redirect_params = {}
89
+
90
+ if response.status == 305 && options.respond_to?(:proxy)
91
+ request_body.rewind
92
+ # The requested resource MUST be accessed through the proxy given by
93
+ # the Location field. The Location field gives the URI of the proxy.
94
+ redirect_options = options.merge(headers: redirect_request.headers,
95
+ proxy: { uri: redirect_uri },
96
+ max_redirects: max_redirects - 1)
97
+
98
+ redirect_params[:body] = request_body
99
+ redirect_uri = redirect_request.uri
100
+ options = redirect_options
101
+ else
102
+ redirect_headers = redirect_request_headers(redirect_request.uri, redirect_uri, request.headers, options)
103
+ redirect_opts = Hash[options]
104
+ redirect_params[:max_redirects] = max_redirects - 1
105
+
106
+ unless request_body.empty?
107
+ if response.status == 307
108
+ # The method and the body of the original request are reused to perform the redirected request.
109
+ redirect_method = redirect_request.verb
110
+ request_body.rewind
111
+ redirect_params[:body] = request_body
112
+ else
113
+ # redirects are **ALWAYS** GET, so remove body-related headers
114
+ REQUEST_BODY_HEADERS.each do |h|
115
+ redirect_headers.delete(h)
116
+ end
117
+ redirect_params[:body] = nil
118
+ end
119
+ end
120
+
121
+ options = options.class.new(redirect_opts.merge(headers: redirect_headers.to_h))
122
+ end
123
+
124
+ redirect_uri = Utils.to_uri(redirect_uri)
53
125
 
54
126
  if !options.follow_insecure_redirects &&
55
127
  response.uri.scheme == "https" &&
56
- retry_request.uri.scheme == "http"
57
- error = InsecureRedirectError.new(retry_request.uri.to_s)
128
+ redirect_uri.scheme == "http"
129
+ error = InsecureRedirectError.new(redirect_uri.to_s)
58
130
  error.set_backtrace(caller)
59
- return ErrorResponse.new(request, error, options)
131
+ return ErrorResponse.new(request, error)
60
132
  end
61
133
 
62
- retry_after = response.headers["retry-after"]
134
+ retry_request = build_request(redirect_method, redirect_uri, redirect_params, options)
63
135
 
64
- if retry_after
136
+ request.redirect_request = retry_request
137
+
138
+ redirect_after = response.headers["retry-after"]
139
+
140
+ if redirect_after
65
141
  # Servers send the "Retry-After" header field to indicate how long the
66
142
  # user agent ought to wait before making a follow-up request.
67
143
  # When sent with any 3xx (Redirection) response, Retry-After indicates
68
144
  # the minimum time that the user agent is asked to wait before issuing
69
145
  # the redirected request.
70
146
  #
71
- retry_after = Utils.parse_retry_after(retry_after)
147
+ redirect_after = Utils.parse_retry_after(redirect_after)
148
+
149
+ log { "redirecting after #{redirect_after} secs..." }
72
150
 
73
- log { "redirecting after #{retry_after} secs..." }
74
- pool.after(retry_after) do
75
- connection = find_connection(retry_request, connections, options)
76
- connection.send(retry_request)
151
+ deactivate_connection(request, connections, options)
152
+
153
+ pool.after(redirect_after) do
154
+ if request.response
155
+ # request has terminated abruptly meanwhile
156
+ retry_request.emit(:response, request.response)
157
+ else
158
+ send_request(retry_request, connections, options)
159
+ end
77
160
  end
78
161
  else
79
- connection = find_connection(retry_request, connections, options)
80
- connection.send(retry_request)
162
+ send_request(retry_request, connections, options)
81
163
  end
82
164
  nil
83
165
  end
84
166
 
85
- def build_redirect_request(request, response, options)
86
- redirect_uri = __get_location_from_response(response)
87
- max_redirects = request.max_redirects
167
+ # :nodoc:
168
+ def redirect_request_headers(original_uri, redirect_uri, headers, options)
169
+ headers = headers.dup
88
170
 
89
- if response.status == 305 && options.respond_to?(:proxy)
90
- # The requested resource MUST be accessed through the proxy given by
91
- # the Location field. The Location field gives the URI of the proxy.
92
- retry_options = options.merge(headers: request.headers,
93
- proxy: { uri: redirect_uri },
94
- body: request.body,
95
- max_redirects: max_redirects - 1)
96
- redirect_uri = request.url
97
- else
171
+ return headers if options.allow_auth_to_other_origins
98
172
 
99
- # redirects are **ALWAYS** GET
100
- retry_options = options.merge(headers: request.headers,
101
- body: request.body,
102
- max_redirects: max_redirects - 1)
103
- end
173
+ return headers unless headers.key?("authorization")
174
+
175
+ return headers if original_uri.origin == redirect_uri.origin
176
+
177
+ headers.delete("authorization")
104
178
 
105
- build_request(:get, redirect_uri, retry_options)
179
+ headers
106
180
  end
107
181
 
182
+ # :nodoc:
108
183
  def __get_location_from_response(response)
184
+ # @type var location_uri: http_uri
109
185
  location_uri = URI(response.headers["location"])
110
186
  location_uri = response.uri.merge(location_uri) if location_uri.relative?
111
187
  location_uri
@@ -113,18 +189,41 @@ module HTTPX
113
189
  end
114
190
 
115
191
  module RequestMethods
116
- def self.included(klass)
117
- klass.__send__(:attr_writer, :redirect_request)
118
- end
192
+ # returns the top-most original HTTPX::Request from the redirect chain
193
+ attr_accessor :root_request
119
194
 
195
+ # returns the follow-up redirect request, or itself
120
196
  def redirect_request
121
197
  @redirect_request || self
122
198
  end
123
199
 
200
+ # sets the follow-up redirect request
201
+ def redirect_request=(req)
202
+ @redirect_request = req
203
+ req.root_request = @root_request || self
204
+ @response = nil
205
+ end
206
+
207
+ def response
208
+ return super unless @redirect_request && @response.nil?
209
+
210
+ @redirect_request.response
211
+ end
212
+
124
213
  def max_redirects
125
214
  @options.max_redirects || MAX_REDIRECTS
126
215
  end
127
216
  end
217
+
218
+ module ConnectionMethods
219
+ private
220
+
221
+ def set_request_request_timeout(request)
222
+ return unless request.root_request.nil?
223
+
224
+ super
225
+ end
226
+ end
128
227
  end
129
228
  register_plugin :follow_redirects, FollowRedirects
130
229
  end
@@ -11,6 +11,7 @@ module HTTPX
11
11
  @response = response
12
12
  @decoder = ->(z) { z }
13
13
  @consumed = false
14
+ @grpc_response = nil
14
15
  end
15
16
 
16
17
  def inspect
@@ -34,9 +35,7 @@ module HTTPX
34
35
  private
35
36
 
36
37
  def grpc_response
37
- return @grpc_response if defined?(@grpc_response)
38
-
39
- @grpc_response = if @response.respond_to?(:each)
38
+ @grpc_response ||= if @response.respond_to?(:each)
40
39
  Enumerator.new do |y|
41
40
  Message.stream(@response).each do |message|
42
41
  y << @decoder.call(message)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Transcoder
5
+ module GRPCEncoding
6
+ class Deflater
7
+ extend Forwardable
8
+
9
+ attr_reader :content_type
10
+
11
+ def initialize(body, compressed:)
12
+ @content_type = body.content_type
13
+ @body = BodyReader.new(body)
14
+ @compressed = compressed
15
+ end
16
+
17
+ def bytesize
18
+ return @body.bytesize if @body.respond_to?(:bytesize)
19
+
20
+ Float::INFINITY
21
+ end
22
+
23
+ def read(length = nil, outbuf = nil)
24
+ buf = @body.read(length, outbuf)
25
+
26
+ return unless buf
27
+
28
+ compressed_flag = @compressed ? 1 : 0
29
+
30
+ buf = outbuf if outbuf
31
+
32
+ buf.prepend([compressed_flag, buf.bytesize].pack("CL>"))
33
+ buf
34
+ end
35
+ end
36
+
37
+ class Inflater
38
+ def initialize(response)
39
+ @response = response
40
+ @grpc_encodings = nil
41
+ end
42
+
43
+ def call(message, &blk)
44
+ data = "".b
45
+
46
+ until message.empty?
47
+ compressed, size = message.unpack("CL>")
48
+
49
+ encoded_data = message.byteslice(5..size + 5 - 1)
50
+
51
+ if compressed == 1
52
+ grpc_encodings.reverse_each do |encoding|
53
+ decoder = @response.body.class.initialize_inflater_by_encoding(encoding, @response, bytesize: encoded_data.bytesize)
54
+ encoded_data = decoder.call(encoded_data)
55
+
56
+ blk.call(encoded_data) if blk
57
+
58
+ data << encoded_data
59
+ end
60
+ else
61
+ blk.call(encoded_data) if blk
62
+
63
+ data << encoded_data
64
+ end
65
+
66
+ message = message.byteslice((size + 5)..-1)
67
+ end
68
+
69
+ data
70
+ end
71
+
72
+ private
73
+
74
+ def grpc_encodings
75
+ @grpc_encodings ||= @response.headers.get("grpc-encoding")
76
+ end
77
+ end
78
+
79
+ def self.encode(*args, **kwargs)
80
+ Deflater.new(*args, **kwargs)
81
+ end
82
+
83
+ def self.decode(response)
84
+ Inflater.new(response)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -12,55 +12,25 @@ module HTTPX
12
12
  # decodes a unary grpc response
13
13
  def unary(response)
14
14
  verify_status(response)
15
- decode(response.to_s, encodings: response.headers.get("grpc-encoding"), encoders: response.encoders)
15
+
16
+ decoder = Transcoder::GRPCEncoding.decode(response)
17
+
18
+ decoder.call(response.to_s)
16
19
  end
17
20
 
18
21
  # lazy decodes a grpc stream response
19
22
  def stream(response, &block)
20
23
  return enum_for(__method__, response) unless block
21
24
 
25
+ decoder = Transcoder::GRPCEncoding.decode(response)
26
+
22
27
  response.each do |frame|
23
- decode(frame, encodings: response.headers.get("grpc-encoding"), encoders: response.encoders, &block)
28
+ decoder.call(frame, &block)
24
29
  end
25
30
 
26
31
  verify_status(response)
27
32
  end
28
33
 
29
- # encodes a single grpc message
30
- def encode(bytes, deflater:)
31
- if deflater
32
- compressed_flag = 1
33
- bytes = deflater.deflate(StringIO.new(bytes))
34
- else
35
- compressed_flag = 0
36
- end
37
-
38
- "".b << [compressed_flag, bytes.bytesize].pack("CL>") << bytes.to_s
39
- end
40
-
41
- # decodes a single grpc message
42
- def decode(message, encodings:, encoders:)
43
- until message.empty?
44
-
45
- compressed, size = message.unpack("CL>")
46
-
47
- data = message.byteslice(5..size + 5 - 1)
48
- if compressed == 1
49
- encodings.reverse_each do |algo|
50
- inflater = encoders.registry(algo).inflater(size)
51
- data = inflater.inflate(data)
52
- size = data.bytesize
53
- end
54
- end
55
-
56
- return data unless block_given?
57
-
58
- yield data
59
-
60
- message = message.byteslice((5 + size)..-1)
61
- end
62
- end
63
-
64
34
  def cancel(request)
65
35
  request.emit(:refuse, :client_cancellation)
66
36
  end