httpx 0.21.0 → 1.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 (229) 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 +4 -4
  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_20_0.md +1 -1
  15. data/doc/release_notes/0_21_0.md +7 -5
  16. data/doc/release_notes/0_21_1.md +12 -0
  17. data/doc/release_notes/0_22_0.md +13 -0
  18. data/doc/release_notes/0_22_1.md +11 -0
  19. data/doc/release_notes/0_22_2.md +5 -0
  20. data/doc/release_notes/0_22_3.md +55 -0
  21. data/doc/release_notes/0_22_4.md +6 -0
  22. data/doc/release_notes/0_22_5.md +6 -0
  23. data/doc/release_notes/0_23_0.md +42 -0
  24. data/doc/release_notes/0_23_1.md +5 -0
  25. data/doc/release_notes/0_23_2.md +5 -0
  26. data/doc/release_notes/0_23_3.md +6 -0
  27. data/doc/release_notes/0_23_4.md +5 -0
  28. data/doc/release_notes/0_24_0.md +48 -0
  29. data/doc/release_notes/0_24_1.md +12 -0
  30. data/doc/release_notes/0_24_2.md +12 -0
  31. data/doc/release_notes/0_24_3.md +12 -0
  32. data/doc/release_notes/0_24_4.md +18 -0
  33. data/doc/release_notes/0_24_5.md +6 -0
  34. data/doc/release_notes/0_24_6.md +5 -0
  35. data/doc/release_notes/0_24_7.md +10 -0
  36. data/doc/release_notes/1_0_0.md +60 -0
  37. data/doc/release_notes/1_0_1.md +5 -0
  38. data/doc/release_notes/1_0_2.md +7 -0
  39. data/doc/release_notes/1_1_0.md +32 -0
  40. data/doc/release_notes/1_1_1.md +17 -0
  41. data/doc/release_notes/1_1_2.md +12 -0
  42. data/doc/release_notes/1_1_3.md +18 -0
  43. data/doc/release_notes/1_1_4.md +6 -0
  44. data/doc/release_notes/1_1_5.md +12 -0
  45. data/doc/release_notes/1_2_0.md +49 -0
  46. data/doc/release_notes/1_2_1.md +6 -0
  47. data/lib/httpx/adapters/datadog.rb +100 -106
  48. data/lib/httpx/adapters/faraday.rb +143 -107
  49. data/lib/httpx/adapters/sentry.rb +26 -7
  50. data/lib/httpx/adapters/webmock.rb +33 -17
  51. data/lib/httpx/altsvc.rb +61 -24
  52. data/lib/httpx/base64.rb +27 -0
  53. data/lib/httpx/buffer.rb +12 -0
  54. data/lib/httpx/callbacks.rb +5 -3
  55. data/lib/httpx/chainable.rb +54 -39
  56. data/lib/httpx/connection/http1.rb +62 -37
  57. data/lib/httpx/connection/http2.rb +16 -27
  58. data/lib/httpx/connection.rb +213 -120
  59. data/lib/httpx/domain_name.rb +10 -13
  60. data/lib/httpx/errors.rb +34 -2
  61. data/lib/httpx/extensions.rb +4 -134
  62. data/lib/httpx/io/ssl.rb +77 -71
  63. data/lib/httpx/io/tcp.rb +46 -70
  64. data/lib/httpx/io/udp.rb +18 -52
  65. data/lib/httpx/io/unix.rb +6 -13
  66. data/lib/httpx/io.rb +3 -9
  67. data/lib/httpx/loggable.rb +4 -19
  68. data/lib/httpx/options.rb +168 -110
  69. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  70. data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -14
  71. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  72. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  73. data/lib/httpx/plugins/auth.rb +25 -0
  74. data/lib/httpx/plugins/aws_sdk_authentication.rb +1 -3
  75. data/lib/httpx/plugins/aws_sigv4.rb +5 -6
  76. data/lib/httpx/plugins/basic_auth.rb +29 -0
  77. data/lib/httpx/plugins/brotli.rb +50 -0
  78. data/lib/httpx/plugins/callbacks.rb +91 -0
  79. data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
  80. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
  81. data/lib/httpx/plugins/circuit_breaker.rb +30 -7
  82. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  83. data/lib/httpx/plugins/cookies.rb +20 -10
  84. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
  85. data/lib/httpx/plugins/expect.rb +15 -13
  86. data/lib/httpx/plugins/follow_redirects.rb +71 -29
  87. data/lib/httpx/plugins/grpc/call.rb +2 -3
  88. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  89. data/lib/httpx/plugins/grpc/message.rb +7 -37
  90. data/lib/httpx/plugins/grpc.rb +35 -29
  91. data/lib/httpx/plugins/h2c.rb +25 -18
  92. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  93. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  94. data/lib/httpx/plugins/oauth.rb +170 -0
  95. data/lib/httpx/plugins/persistent.rb +1 -1
  96. data/lib/httpx/plugins/proxy/http.rb +15 -10
  97. data/lib/httpx/plugins/proxy/socks4.rb +8 -6
  98. data/lib/httpx/plugins/proxy/socks5.rb +10 -8
  99. data/lib/httpx/plugins/proxy.rb +69 -67
  100. data/lib/httpx/plugins/push_promise.rb +1 -1
  101. data/lib/httpx/plugins/rate_limiter.rb +3 -1
  102. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  103. data/lib/httpx/plugins/response_cache/store.rb +34 -17
  104. data/lib/httpx/plugins/response_cache.rb +6 -6
  105. data/lib/httpx/plugins/retries.rb +61 -12
  106. data/lib/httpx/plugins/ssrf_filter.rb +142 -0
  107. data/lib/httpx/plugins/stream.rb +27 -32
  108. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  109. data/lib/httpx/plugins/upgrade.rb +8 -10
  110. data/lib/httpx/plugins/webdav.rb +10 -8
  111. data/lib/httpx/pool.rb +85 -23
  112. data/lib/httpx/punycode.rb +9 -291
  113. data/lib/httpx/request/body.rb +158 -0
  114. data/lib/httpx/request.rb +86 -121
  115. data/lib/httpx/resolver/https.rb +54 -17
  116. data/lib/httpx/resolver/multi.rb +8 -12
  117. data/lib/httpx/resolver/native.rb +163 -70
  118. data/lib/httpx/resolver/resolver.rb +28 -13
  119. data/lib/httpx/resolver/system.rb +15 -10
  120. data/lib/httpx/resolver.rb +38 -16
  121. data/lib/httpx/response/body.rb +242 -0
  122. data/lib/httpx/response/buffer.rb +96 -0
  123. data/lib/httpx/response.rb +113 -211
  124. data/lib/httpx/selector.rb +2 -4
  125. data/lib/httpx/session.rb +91 -64
  126. data/lib/httpx/session_extensions.rb +4 -1
  127. data/lib/httpx/timers.rb +28 -8
  128. data/lib/httpx/transcoder/body.rb +0 -2
  129. data/lib/httpx/transcoder/chunker.rb +0 -1
  130. data/lib/httpx/transcoder/deflate.rb +37 -0
  131. data/lib/httpx/transcoder/form.rb +52 -33
  132. data/lib/httpx/transcoder/gzip.rb +74 -0
  133. data/lib/httpx/transcoder/json.rb +2 -5
  134. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  135. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
  136. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  137. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  138. data/lib/httpx/transcoder/multipart.rb +17 -0
  139. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  140. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  141. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  142. data/lib/httpx/transcoder/xml.rb +0 -5
  143. data/lib/httpx/transcoder.rb +4 -6
  144. data/lib/httpx/utils.rb +36 -16
  145. data/lib/httpx/version.rb +1 -1
  146. data/lib/httpx.rb +12 -14
  147. data/sig/altsvc.rbs +33 -0
  148. data/sig/buffer.rbs +1 -0
  149. data/sig/callbacks.rbs +3 -3
  150. data/sig/chainable.rbs +10 -9
  151. data/sig/connection/http1.rbs +5 -4
  152. data/sig/connection/http2.rbs +1 -1
  153. data/sig/connection.rbs +46 -24
  154. data/sig/errors.rbs +9 -3
  155. data/sig/httpx.rbs +5 -4
  156. data/sig/io/ssl.rbs +26 -0
  157. data/sig/io/tcp.rbs +60 -0
  158. data/sig/io/udp.rbs +20 -0
  159. data/sig/io/unix.rbs +10 -0
  160. data/sig/options.rbs +28 -12
  161. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  162. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  163. data/sig/plugins/auth.rbs +13 -0
  164. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  165. data/sig/plugins/brotli.rbs +22 -0
  166. data/sig/plugins/callbacks.rbs +38 -0
  167. data/sig/plugins/circuit_breaker.rbs +13 -3
  168. data/sig/plugins/compression.rbs +6 -4
  169. data/sig/plugins/cookies/jar.rbs +2 -2
  170. data/sig/plugins/cookies.rbs +2 -0
  171. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  172. data/sig/plugins/follow_redirects.rbs +11 -2
  173. data/sig/plugins/grpc/call.rbs +19 -0
  174. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  175. data/sig/plugins/grpc/message.rbs +17 -0
  176. data/sig/plugins/grpc.rbs +2 -32
  177. data/sig/plugins/h2c.rbs +1 -1
  178. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  179. data/sig/plugins/oauth.rbs +54 -0
  180. data/sig/plugins/proxy/socks4.rbs +4 -4
  181. data/sig/plugins/proxy/socks5.rbs +2 -2
  182. data/sig/plugins/proxy/ssh.rbs +1 -1
  183. data/sig/plugins/proxy.rbs +10 -4
  184. data/sig/plugins/response_cache.rbs +12 -3
  185. data/sig/plugins/retries.rbs +28 -8
  186. data/sig/plugins/stream.rbs +24 -17
  187. data/sig/plugins/upgrade.rbs +5 -3
  188. data/sig/pool.rbs +5 -4
  189. data/sig/request/body.rbs +40 -0
  190. data/sig/request.rbs +12 -28
  191. data/sig/resolver/https.rbs +7 -2
  192. data/sig/resolver/native.rbs +10 -4
  193. data/sig/resolver/resolver.rbs +6 -4
  194. data/sig/resolver/system.rbs +2 -0
  195. data/sig/resolver.rbs +9 -5
  196. data/sig/response/body.rbs +53 -0
  197. data/sig/response/buffer.rbs +24 -0
  198. data/sig/response.rbs +17 -38
  199. data/sig/session.rbs +24 -18
  200. data/sig/timers.rbs +17 -7
  201. data/sig/transcoder/body.rbs +4 -3
  202. data/sig/transcoder/deflate.rbs +11 -0
  203. data/sig/transcoder/form.rbs +5 -3
  204. data/sig/transcoder/gzip.rbs +24 -0
  205. data/sig/transcoder/json.rbs +4 -2
  206. data/sig/{plugins → transcoder}/multipart.rbs +3 -12
  207. data/sig/transcoder/utils/body_reader.rbs +15 -0
  208. data/sig/transcoder/utils/deflater.rbs +29 -0
  209. data/sig/transcoder/utils/inflater.rbs +12 -0
  210. data/sig/transcoder/xml.rbs +1 -1
  211. data/sig/transcoder.rbs +22 -7
  212. data/sig/utils.rbs +2 -0
  213. metadata +127 -40
  214. data/lib/httpx/plugins/authentication.rb +0 -20
  215. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  216. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  217. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  218. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  219. data/lib/httpx/plugins/compression.rb +0 -164
  220. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  221. data/lib/httpx/plugins/multipart.rb +0 -84
  222. data/lib/httpx/registry.rb +0 -85
  223. data/sig/plugins/authentication.rbs +0 -11
  224. data/sig/plugins/compression/brotli.rbs +0 -21
  225. data/sig/plugins/compression/deflate.rbs +0 -17
  226. data/sig/plugins/compression/gzip.rbs +0 -29
  227. data/sig/registry.rbs +0 -13
  228. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  229. /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,35 @@ 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
23
  module OptionsMethods
24
24
  def option_digest(value)
25
- raise TypeError, ":digest must be a Digest" unless value.is_a?(Authentication::Digest)
25
+ raise TypeError, ":digest must be a #{Authentication::Digest}" unless value.is_a?(Authentication::Digest)
26
26
 
27
27
  value
28
28
  end
29
29
  end
30
30
 
31
31
  module InstanceMethods
32
- def digest_authentication(user, password)
33
- with(digest: Authentication::Digest.new(user, password))
32
+ def digest_auth(user, password, hashed: false)
33
+ with(digest: Authentication::Digest.new(user, password, hashed: hashed))
34
34
  end
35
35
 
36
- alias_method :digest_auth, :digest_authentication
36
+ private
37
37
 
38
38
  def send_requests(*requests)
39
39
  requests.flat_map do |request|
40
40
  digest = request.options.digest
41
41
 
42
- unless digest
43
- super(request)
44
- next
45
- end
42
+ next super(request) unless digest
46
43
 
47
44
  probe_response = wrap { super(request).first }
48
45
 
46
+ return probe_response unless probe_response.is_a?(Response)
47
+
49
48
  if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
50
49
  request.transition(:idle)
51
50
  request.headers["authorization"] = digest.authenticate(request, probe_response.headers["www-authenticate"])
@@ -58,6 +57,6 @@ module HTTPX
58
57
  end
59
58
  end
60
59
 
61
- register_plugin :digest_authentication, DigestAuth
60
+ register_plugin :digest_auth, DigestAuth
62
61
  end
63
62
  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
@@ -50,7 +50,8 @@ module HTTPX
50
50
  end
51
51
 
52
52
  def response=(response)
53
- if response && response.status == 100 &&
53
+ if response.is_a?(Response) &&
54
+ response.status == 100 &&
54
55
  !@headers.key?("expect") &&
55
56
  (@state == :body || @state == :done)
56
57
 
@@ -74,14 +75,16 @@ module HTTPX
74
75
 
75
76
  return unless request.headers["expect"] == "100-continue"
76
77
 
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
78
+ expect_timeout = request.options.expect_timeout
79
+
80
+ return if expect_timeout.nil? || expect_timeout.infinite?
81
+
82
+ set_request_timeout(request, expect_timeout, :expect, %i[body response]) do
83
+ # expect timeout expired
84
+ if request.state == :expect && !request.expects?
85
+ Expect.no_expect_store << request.origin
86
+ request.headers.delete("expect")
87
+ consume
85
88
  end
86
89
  end
87
90
  end
@@ -92,12 +95,11 @@ module HTTPX
92
95
  response = @responses.delete(request)
93
96
  return unless response
94
97
 
95
- if response.status == 417 && request.headers.key?("expect")
98
+ if response.is_a?(Response) && response.status == 417 && request.headers.key?("expect")
96
99
  response.close
97
100
  request.headers.delete("expect")
98
101
  request.transition(:idle)
99
- connection = find_connection(request, connections, options)
100
- connection.send(request)
102
+ send_request(request, connections, options)
101
103
  return
102
104
  end
103
105
 
@@ -11,12 +11,14 @@ module HTTPX
11
11
  #
12
12
  # It also 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
+ # https://gitlab.com/os85/httpx/wikis/Follow-Redirects
15
15
  #
16
16
  module FollowRedirects
17
17
  MAX_REDIRECTS = 3
18
18
  REDIRECT_STATUS = (300..399).freeze
19
19
 
20
+ using URIExtensions
21
+
20
22
  module OptionsMethods
21
23
  def option_max_redirects(value)
22
24
  num = Integer(value)
@@ -28,6 +30,16 @@ module HTTPX
28
30
  def option_follow_insecure_redirects(value)
29
31
  value
30
32
  end
33
+
34
+ def option_allow_auth_to_other_origins(value)
35
+ value
36
+ end
37
+
38
+ def option_redirect_on(value)
39
+ raise TypeError, ":redirect_on must be callable" unless value.respond_to?(:call)
40
+
41
+ value
42
+ end
31
43
  end
32
44
 
33
45
  module InstanceMethods
@@ -44,21 +56,53 @@ module HTTPX
44
56
 
45
57
  max_redirects = redirect_request.max_redirects
46
58
 
59
+ return response unless response.is_a?(Response)
47
60
  return response unless REDIRECT_STATUS.include?(response.status) && response.headers.key?("location")
48
61
  return response unless max_redirects.positive?
49
62
 
50
- retry_request = build_redirect_request(redirect_request, response, options)
63
+ # build redirect request
64
+ redirect_uri = __get_location_from_response(response)
51
65
 
52
- request.redirect_request = retry_request
66
+ if options.redirect_on
67
+ redirect_allowed = options.redirect_on.call(redirect_uri)
68
+ return response unless redirect_allowed
69
+ end
70
+
71
+ if response.status == 305 && options.respond_to?(:proxy)
72
+ # The requested resource MUST be accessed through the proxy given by
73
+ # the Location field. The Location field gives the URI of the proxy.
74
+ retry_options = options.merge(headers: redirect_request.headers,
75
+ proxy: { uri: redirect_uri },
76
+ body: redirect_request.body,
77
+ max_redirects: max_redirects - 1)
78
+ redirect_uri = redirect_request.uri
79
+ options = retry_options
80
+ else
81
+ redirect_headers = redirect_request_headers(redirect_request.uri, redirect_uri, request.headers, options)
82
+
83
+ # redirects are **ALWAYS** GET
84
+ retry_opts = Hash[options].merge(
85
+ headers: redirect_headers.to_h,
86
+ body: redirect_request.body,
87
+ max_redirects: max_redirects - 1
88
+ )
89
+ retry_options = options.class.new(retry_opts)
90
+ end
91
+
92
+ redirect_uri = Utils.to_uri(redirect_uri)
53
93
 
54
94
  if !options.follow_insecure_redirects &&
55
95
  response.uri.scheme == "https" &&
56
- retry_request.uri.scheme == "http"
57
- error = InsecureRedirectError.new(retry_request.uri.to_s)
96
+ redirect_uri.scheme == "http"
97
+ error = InsecureRedirectError.new(redirect_uri.to_s)
58
98
  error.set_backtrace(caller)
59
99
  return ErrorResponse.new(request, error, options)
60
100
  end
61
101
 
102
+ retry_request = build_request("GET", redirect_uri, retry_options)
103
+
104
+ request.redirect_request = retry_request
105
+
62
106
  retry_after = response.headers["retry-after"]
63
107
 
64
108
  if retry_after
@@ -72,37 +116,25 @@ module HTTPX
72
116
 
73
117
  log { "redirecting after #{retry_after} secs..." }
74
118
  pool.after(retry_after) do
75
- connection = find_connection(retry_request, connections, options)
76
- connection.send(retry_request)
119
+ send_request(retry_request, connections, options)
77
120
  end
78
121
  else
79
- connection = find_connection(retry_request, connections, options)
80
- connection.send(retry_request)
122
+ send_request(retry_request, connections, options)
81
123
  end
82
124
  nil
83
125
  end
84
126
 
85
- def build_redirect_request(request, response, options)
86
- redirect_uri = __get_location_from_response(response)
87
- max_redirects = request.max_redirects
127
+ def redirect_request_headers(original_uri, redirect_uri, headers, options)
128
+ return headers if options.allow_auth_to_other_origins
88
129
 
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
130
+ return headers unless headers.key?("authorization")
98
131
 
99
- # redirects are **ALWAYS** GET
100
- retry_options = options.merge(headers: request.headers,
101
- body: request.body,
102
- max_redirects: max_redirects - 1)
132
+ unless original_uri.origin == redirect_uri.origin
133
+ headers = headers.dup
134
+ headers.delete("authorization")
103
135
  end
104
136
 
105
- build_request(:get, redirect_uri, retry_options)
137
+ headers
106
138
  end
107
139
 
108
140
  def __get_location_from_response(response)
@@ -113,14 +145,24 @@ module HTTPX
113
145
  end
114
146
 
115
147
  module RequestMethods
116
- def self.included(klass)
117
- klass.__send__(:attr_writer, :redirect_request)
118
- end
148
+ attr_accessor :root_request
119
149
 
120
150
  def redirect_request
121
151
  @redirect_request || self
122
152
  end
123
153
 
154
+ def redirect_request=(req)
155
+ @redirect_request = req
156
+ req.root_request = @root_request || self
157
+ @response = nil
158
+ end
159
+
160
+ def response
161
+ return super unless @redirect_request
162
+
163
+ @redirect_request.response
164
+ end
165
+
124
166
  def max_redirects
125
167
  @options.max_redirects || MAX_REDIRECTS
126
168
  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
@@ -16,7 +16,7 @@ module HTTPX
16
16
  #
17
17
  # This plugin adds DSL to build GRPC interfaces.
18
18
  #
19
- # https://gitlab.com/honeyryderchuck/httpx/wikis/GRPC
19
+ # https://gitlab.com/os85/httpx/wikis/GRPC
20
20
  #
21
21
  module GRPC
22
22
  unless String.method_defined?(:underscore)
@@ -49,20 +49,19 @@ module HTTPX
49
49
  class << self
50
50
  def load_dependencies(*)
51
51
  require "stringio"
52
+ require "httpx/plugins/grpc/grpc_encoding"
52
53
  require "httpx/plugins/grpc/message"
53
54
  require "httpx/plugins/grpc/call"
54
55
  end
55
56
 
56
57
  def configure(klass)
57
58
  klass.plugin(:persistent)
58
- klass.plugin(:compression)
59
59
  klass.plugin(:stream)
60
60
  end
61
61
 
62
62
  def extra_options(options)
63
63
  options.merge(
64
64
  fallback_protocol: "h2",
65
- http2_settings: { wait_for_handshake: false },
66
65
  grpc_rpcs: {}.freeze,
67
66
  grpc_compression: false,
68
67
  grpc_deadline: DEADLINE
@@ -108,9 +107,18 @@ module HTTPX
108
107
  @trailing_metadata = Hash[trailers]
109
108
  super
110
109
  end
110
+ end
111
+
112
+ module RequestBodyMethods
113
+ def initialize(headers, _)
114
+ super
111
115
 
112
- def encoders
113
- @options.encodings
116
+ if (compression = headers["grpc-encoding"])
117
+ deflater_body = self.class.initialize_deflater_body(@body, compression)
118
+ @body = Transcoder::GRPCEncoding.encode(deflater_body || @body, compressed: !deflater_body.nil?)
119
+ else
120
+ @body = Transcoder::GRPCEncoding.encode(@body, compressed: false)
121
+ end
114
122
  end
115
123
  end
116
124
 
@@ -141,17 +149,29 @@ module HTTPX
141
149
  deadline: @options.grpc_deadline,
142
150
  }.merge(opts)
143
151
 
152
+ local_rpc_name = rpc_name.underscore
153
+
144
154
  session_class = Class.new(self.class) do
155
+ # define rpc method with ruby style name
145
156
  class_eval(<<-OUT, __FILE__, __LINE__ + 1)
146
- def #{rpc_name}(input, **opts) # def grpc_action(input, **opts)
147
- rpc_execute("#{rpc_name}", input, **opts) # rpc_execute("grpc_action", input, **opts)
148
- end # end
157
+ def #{local_rpc_name}(input, **opts) # def grpc_action(input, **opts)
158
+ rpc_execute("#{local_rpc_name}", input, **opts) # rpc_execute("grpc_action", input, **opts)
159
+ end # end
149
160
  OUT
161
+
162
+ # define rpc method with original name
163
+ unless local_rpc_name == rpc_name
164
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
165
+ def #{rpc_name}(input, **opts) # def grpcAction(input, **opts)
166
+ rpc_execute("#{local_rpc_name}", input, **opts) # rpc_execute("grpc_action", input, **opts)
167
+ end # end
168
+ OUT
169
+ end
150
170
  end
151
171
 
152
172
  session_class.new(@options.merge(
153
173
  grpc_rpcs: @options.grpc_rpcs.merge(
154
- rpc_name.underscore => [rpc_name, input, output, rpc_opts]
174
+ local_rpc_name => [rpc_name, input, output, rpc_opts]
155
175
  ).freeze
156
176
  ))
157
177
  end
@@ -195,7 +215,7 @@ module HTTPX
195
215
  **opts)
196
216
  grpc_request = build_grpc_request(rpc_method, input, deadline: deadline, metadata: metadata, **opts)
197
217
  response = request(grpc_request, **opts)
198
- response.raise_for_status
218
+ response.raise_for_status unless opts[:stream]
199
219
  GRPC::Call.new(response)
200
220
  end
201
221
 
@@ -233,7 +253,7 @@ module HTTPX
233
253
  uri.path = rpc_method
234
254
 
235
255
  headers = HEADERS.merge(
236
- "grpc-accept-encoding" => ["identity", *@options.encodings.registry.keys]
256
+ "grpc-accept-encoding" => ["identity", *@options.supported_compression_formats]
237
257
  )
238
258
  unless deadline == Float::INFINITY
239
259
  # convert to milliseconds
@@ -241,30 +261,16 @@ module HTTPX
241
261
  headers["grpc-timeout"] = "#{deadline}m"
242
262
  end
243
263
 
244
- headers = headers.merge(metadata) if metadata
264
+ headers = headers.merge(metadata.transform_keys(&:to_s)) if metadata
245
265
 
246
266
  # prepare compressor
247
- deflater = nil
248
267
  compression = @options.grpc_compression == true ? "gzip" : @options.grpc_compression
249
268
 
250
- if compression
251
- headers["grpc-encoding"] = compression
252
- deflater = @options.encodings.registry(compression).deflater
253
- end
254
-
255
- headers.merge!(@options.call_credentials.call) if @options.call_credentials
269
+ headers["grpc-encoding"] = compression if compression
256
270
 
257
- body = if input.respond_to?(:each)
258
- Enumerator.new do |y|
259
- input.each do |message|
260
- y << Message.encode(message, deflater: deflater)
261
- end
262
- end
263
- else
264
- Message.encode(input, deflater: deflater)
265
- end
271
+ headers.merge!(@options.call_credentials.call.transform_keys(&:to_s)) if @options.call_credentials
266
272
 
267
- build_request(:post, uri, headers: headers, body: body)
273
+ build_request("POST", uri, headers: headers, body: input)
268
274
  end
269
275
  end
270
276
  end
@@ -4,21 +4,16 @@ module HTTPX
4
4
  module Plugins
5
5
  #
6
6
  # This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
7
- # (https://tools.ietf.org/html/rfc7540#section-3.2)
7
+ # (https://datatracker.ietf.org/doc/html/rfc7540#section-3.2)
8
8
  #
9
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Upgrade#h2c
9
+ # https://gitlab.com/os85/httpx/wikis/Connection-Upgrade#h2c
10
10
  #
11
11
  module H2C
12
- VALID_H2C_VERBS = %i[get options head].freeze
12
+ VALID_H2C_VERBS = %w[GET OPTIONS HEAD].freeze
13
13
 
14
14
  class << self
15
- def load_dependencies(*)
16
- require "base64"
17
- end
18
-
19
- def configure(klass)
15
+ def load_dependencies(klass)
20
16
  klass.plugin(:upgrade)
21
- klass.default_options.upgrade_handlers.register "h2c", self
22
17
  end
23
18
 
24
19
  def call(connection, request, response)
@@ -26,7 +21,7 @@ module HTTPX
26
21
  end
27
22
 
28
23
  def extra_options(options)
29
- options.merge(max_concurrent_requests: 1)
24
+ options.merge(max_concurrent_requests: 1, upgrade_handlers: options.upgrade_handlers.merge("h2c" => self))
30
25
  end
31
26
  end
32
27
 
@@ -38,7 +33,7 @@ module HTTPX
38
33
 
39
34
  connection = pool.find_connection(upgrade_request.uri, upgrade_request.options)
40
35
 
41
- return super if connection && connection.upgrade_protocol == :h2c
36
+ return super if connection && connection.upgrade_protocol == "h2c"
42
37
 
43
38
  # build upgrade request
44
39
  upgrade_request.headers.add("connection", "upgrade")
@@ -78,22 +73,34 @@ module HTTPX
78
73
  @inflight -= prev_parser.requests.size
79
74
  end
80
75
 
81
- parser_options = @options.merge(max_concurrent_requests: request.options.max_concurrent_requests)
82
- @parser = H2CParser.new(@write_buffer, parser_options)
76
+ @parser = H2CParser.new(@write_buffer, @options)
83
77
  set_parser_callbacks(@parser)
84
78
  @inflight += 1
85
79
  @parser.upgrade(request, response)
86
- @upgrade_protocol = :h2c
87
-
88
- if request.options.max_concurrent_requests != @options.max_concurrent_requests
89
- @options = @options.merge(max_concurrent_requests: nil)
90
- end
80
+ @upgrade_protocol = "h2c"
91
81
 
92
82
  prev_parser.requests.each do |req|
93
83
  req.transition(:idle)
94
84
  send(req)
95
85
  end
96
86
  end
87
+
88
+ private
89
+
90
+ def send_request_to_parser(request)
91
+ super
92
+
93
+ return unless request.headers["upgrade"] == "h2c" && parser.is_a?(Connection::HTTP1)
94
+
95
+ max_concurrent_requests = parser.max_concurrent_requests
96
+
97
+ return if max_concurrent_requests == 1
98
+
99
+ parser.max_concurrent_requests = 1
100
+ request.once(:response) do
101
+ parser.max_concurrent_requests = max_concurrent_requests
102
+ end
103
+ end
97
104
  end
98
105
  end
99
106
  register_plugin(:h2c, H2C)