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
@@ -7,69 +7,110 @@ require "faraday"
7
7
  module Faraday
8
8
  class Adapter
9
9
  class HTTPX < Faraday::Adapter
10
- # :nocov:
11
- SSL_ERROR = if defined?(Faraday::SSLError)
12
- Faraday::SSLError
13
- else
14
- Faraday::Error::SSLError
15
- end
10
+ module RequestMixin
11
+ def build_connection(env)
12
+ return @connection if defined?(@connection)
16
13
 
17
- CONNECTION_FAILED_ERROR = if defined?(Faraday::ConnectionFailed)
18
- Faraday::ConnectionFailed
19
- else
20
- Faraday::Error::ConnectionFailed
21
- end
22
- # :nocov:
14
+ @connection = ::HTTPX.plugin(:persistent).plugin(ReasonPlugin)
15
+ @connection = @connection.with(@connection_options) unless @connection_options.empty?
16
+ connection_opts = options_from_env(env)
23
17
 
24
- unless Faraday::RequestOptions.method_defined?(:stream_response?)
25
- module RequestOptionsExtensions
26
- refine Faraday::RequestOptions do
27
- def stream_response?
28
- false
29
- end
18
+ if (bind = env.request.bind)
19
+ @bind = TCPSocket.new(bind[:host], bind[:port])
20
+ connection_opts[:io] = @bind
21
+ end
22
+ @connection = @connection.with(connection_opts)
23
+
24
+ if (proxy = env.request.proxy)
25
+ proxy_options = { uri: proxy.uri }
26
+ proxy_options[:username] = proxy.user if proxy.user
27
+ proxy_options[:password] = proxy.password if proxy.password
28
+
29
+ @connection = @connection.plugin(:proxy).with(proxy: proxy_options)
30
30
  end
31
+ @connection = @connection.plugin(OnDataPlugin) if env.request.stream_response?
32
+
33
+ @connection = @config_block.call(@connection) || @connection if @config_block
34
+ @connection
31
35
  end
32
- using RequestOptionsExtensions
33
- end
34
36
 
35
- module RequestMixin
36
- using ::HTTPX::HashExtensions
37
+ def close
38
+ @connection.close if @connection
39
+ @bind.close if @bind
40
+ end
37
41
 
38
42
  private
39
43
 
44
+ def connect(env, &blk)
45
+ connection(env, &blk)
46
+ rescue ::HTTPX::TLSError => e
47
+ raise Faraday::SSLError, e
48
+ rescue Errno::ECONNABORTED,
49
+ Errno::ECONNREFUSED,
50
+ Errno::ECONNRESET,
51
+ Errno::EHOSTUNREACH,
52
+ Errno::EINVAL,
53
+ Errno::ENETUNREACH,
54
+ Errno::EPIPE,
55
+ ::HTTPX::ConnectionError => e
56
+ raise Faraday::ConnectionFailed, e
57
+ end
58
+
40
59
  def build_request(env)
41
60
  meth = env[:method]
42
61
 
43
62
  request_options = {
44
63
  headers: env.request_headers,
45
64
  body: env.body,
65
+ **options_from_env(env),
46
66
  }
47
- [meth, env.url, request_options]
67
+ [meth.to_s.upcase, env.url, request_options]
48
68
  end
49
69
 
50
70
  def options_from_env(env)
51
- timeout_options = {
52
- connect_timeout: env.request.open_timeout,
53
- operation_timeout: env.request.timeout,
54
- }.compact
71
+ timeout_options = {}
72
+ req_opts = env.request
73
+ if (sec = request_timeout(:read, req_opts))
74
+ timeout_options[:read_timeout] = sec
75
+ end
55
76
 
56
- options = {
57
- ssl: {},
77
+ if (sec = request_timeout(:write, req_opts))
78
+ timeout_options[:write_timeout] = sec
79
+ end
80
+
81
+ if (sec = request_timeout(:open, req_opts))
82
+ timeout_options[:connect_timeout] = sec
83
+ end
84
+
85
+ {
86
+ ssl: ssl_options_from_env(env),
58
87
  timeout: timeout_options,
59
88
  }
89
+ end
90
+
91
+ if defined?(::OpenSSL)
92
+ def ssl_options_from_env(env)
93
+ ssl_options = {}
94
+
95
+ unless env.ssl.verify.nil?
96
+ ssl_options[:verify_mode] = env.ssl.verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
97
+ end
60
98
 
61
- options[:ssl][:verify_mode] = OpenSSL::SSL::VERIFY_PEER if env.ssl.verify
62
- options[:ssl][:ca_file] = env.ssl.ca_file if env.ssl.ca_file
63
- options[:ssl][:ca_path] = env.ssl.ca_path if env.ssl.ca_path
64
- options[:ssl][:cert_store] = env.ssl.cert_store if env.ssl.cert_store
65
- options[:ssl][:cert] = env.ssl.client_cert if env.ssl.client_cert
66
- options[:ssl][:key] = env.ssl.client_key if env.ssl.client_key
67
- options[:ssl][:ssl_version] = env.ssl.version if env.ssl.version
68
- options[:ssl][:verify_depth] = env.ssl.verify_depth if env.ssl.verify_depth
69
- options[:ssl][:min_version] = env.ssl.min_version if env.ssl.min_version
70
- options[:ssl][:max_version] = env.ssl.max_version if env.ssl.max_version
71
-
72
- options
99
+ ssl_options[:ca_file] = env.ssl.ca_file if env.ssl.ca_file
100
+ ssl_options[:ca_path] = env.ssl.ca_path if env.ssl.ca_path
101
+ ssl_options[:cert_store] = env.ssl.cert_store if env.ssl.cert_store
102
+ ssl_options[:cert] = env.ssl.client_cert if env.ssl.client_cert
103
+ ssl_options[:key] = env.ssl.client_key if env.ssl.client_key
104
+ ssl_options[:ssl_version] = env.ssl.version if env.ssl.version
105
+ ssl_options[:verify_depth] = env.ssl.verify_depth if env.ssl.verify_depth
106
+ ssl_options[:min_version] = env.ssl.min_version if env.ssl.min_version
107
+ ssl_options[:max_version] = env.ssl.max_version if env.ssl.max_version
108
+ ssl_options
109
+ end
110
+ else
111
+ def ssl_options_from_env(*)
112
+ {}
113
+ end
73
114
  end
74
115
  end
75
116
 
@@ -100,32 +141,17 @@ module Faraday
100
141
  end
101
142
 
102
143
  module ReasonPlugin
103
- if RUBY_VERSION < "2.5"
104
- def self.load_dependencies(*)
105
- require "webrick"
106
- end
107
- else
108
- def self.load_dependencies(*)
109
- require "net/http/status"
110
- end
144
+ def self.load_dependencies(*)
145
+ require "net/http/status"
111
146
  end
147
+
112
148
  module ResponseMethods
113
- if RUBY_VERSION < "2.5"
114
- def reason
115
- WEBrick::HTTPStatus::StatusMessage.fetch(@status)
116
- end
117
- else
118
- def reason
119
- Net::HTTP::STATUS_CODES.fetch(@status)
120
- end
149
+ def reason
150
+ Net::HTTP::STATUS_CODES.fetch(@status)
121
151
  end
122
152
  end
123
153
  end
124
154
 
125
- def self.session
126
- @session ||= ::HTTPX.plugin(:compression).plugin(:persistent).plugin(ReasonPlugin)
127
- end
128
-
129
155
  class ParallelManager
130
156
  class ResponseHandler < SimpleDelegator
131
157
  attr_reader :env
@@ -158,8 +184,9 @@ module Faraday
158
184
 
159
185
  include RequestMixin
160
186
 
161
- def initialize
187
+ def initialize(options)
162
188
  @handlers = []
189
+ @connection_options = options
163
190
  end
164
191
 
165
192
  def enqueue(request)
@@ -169,85 +196,96 @@ module Faraday
169
196
  end
170
197
 
171
198
  def run
199
+ return unless @handlers.last
200
+
172
201
  env = @handlers.last.env
173
202
 
174
- session = HTTPX.session.with(options_from_env(env))
175
- session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
176
- session = session.plugin(OnDataPlugin) if env.request.stream_response?
203
+ connect(env) do |session|
204
+ requests = @handlers.map { |handler| session.build_request(*build_request(handler.env)) }
177
205
 
178
- requests = @handlers.map { |handler| session.build_request(*build_request(handler.env)) }
206
+ if env.request.stream_response?
207
+ requests.each do |request|
208
+ request.response_on_data = env.request.on_data
209
+ end
210
+ end
179
211
 
180
- if env.request.stream_response?
181
- requests.each do |request|
182
- request.response_on_data = env.request.on_data
212
+ responses = session.request(*requests)
213
+ Array(responses).each_with_index do |response, index|
214
+ handler = @handlers[index]
215
+ handler.on_response.call(response)
216
+ handler.on_complete.call(handler.env) if handler.on_complete
183
217
  end
184
218
  end
219
+ rescue ::HTTPX::TimeoutError => e
220
+ raise Faraday::TimeoutError, e
221
+ end
185
222
 
186
- responses = session.request(*requests)
187
- Array(responses).each_with_index do |response, index|
188
- handler = @handlers[index]
189
- handler.on_response.call(response)
190
- handler.on_complete.call(handler.env)
191
- end
223
+ # from Faraday::Adapter#connection
224
+ def connection(env)
225
+ conn = build_connection(env)
226
+ return conn unless block_given?
227
+
228
+ yield conn
229
+ end
230
+
231
+ private
232
+
233
+ # from Faraday::Adapter#request_timeout
234
+ def request_timeout(type, options)
235
+ key = Faraday::Adapter::TIMEOUT_KEYS[type]
236
+ options[key] || options[:timeout]
192
237
  end
193
238
  end
194
239
 
195
240
  self.supports_parallel = true
196
241
 
197
242
  class << self
198
- def setup_parallel_manager
199
- ParallelManager.new
243
+ def setup_parallel_manager(options = {})
244
+ ParallelManager.new(options)
200
245
  end
201
246
  end
202
247
 
203
- def initialize(app, options = {})
204
- super(app)
205
- @session_options = options
206
- end
207
-
208
248
  def call(env)
209
249
  super
210
250
  if parallel?(env)
211
251
  handler = env[:parallel_manager].enqueue(env)
212
252
  handler.on_response do |response|
213
- response.raise_for_status
214
- save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
215
- response_headers.merge!(response.headers)
253
+ if response.is_a?(::HTTPX::Response)
254
+ save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
255
+ response_headers.merge!(response.headers)
256
+ end
257
+ else
258
+ env[:error] = response.error
259
+ save_response(env, 0, "", {}, nil)
216
260
  end
217
261
  end
218
262
  return handler
219
263
  end
220
264
 
221
- session = HTTPX.session
222
- session = session.with(@session_options) unless @session_options.empty?
223
- session = session.with(options_from_env(env))
224
- session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
225
- session = session.plugin(OnDataPlugin) if env.request.stream_response?
226
-
227
- request = session.build_request(*build_request(env))
228
-
229
- request.response_on_data = env.request.on_data if env.request.stream_response?
230
-
231
- response = session.request(request)
232
- response.raise_for_status unless response.is_a?(::HTTPX::Response)
265
+ response = connect_and_request(env)
233
266
  save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
234
267
  response_headers.merge!(response.headers)
235
268
  end
236
269
  @app.call(env)
237
- rescue ::HTTPX::TLSError => e
238
- raise SSL_ERROR, e
239
- rescue Errno::ECONNABORTED,
240
- Errno::ECONNREFUSED,
241
- Errno::ECONNRESET,
242
- Errno::EHOSTUNREACH,
243
- Errno::EINVAL,
244
- Errno::ENETUNREACH,
245
- Errno::EPIPE => e
246
- raise CONNECTION_FAILED_ERROR, e
247
270
  end
248
271
 
249
272
  private
250
273
 
274
+ def connect_and_request(env)
275
+ connect(env) do |session|
276
+ request = session.build_request(*build_request(env))
277
+
278
+ request.response_on_data = env.request.on_data if env.request.stream_response?
279
+
280
+ response = session.request(request)
281
+ # do not call #raise_for_status for HTTP 4xx or 5xx, as faraday has a middleware for that.
282
+ response.raise_for_status unless response.is_a?(::HTTPX::Response)
283
+ response
284
+ end
285
+ rescue ::HTTPX::TimeoutError => e
286
+ raise Faraday::TimeoutError, e
287
+ end
288
+
251
289
  def parallel?(env)
252
290
  env[:parallel_manager]
253
291
  end
@@ -27,6 +27,11 @@ module HTTPX::Plugins
27
27
  def set_sentry_trace_header(request, sentry_span)
28
28
  return unless sentry_span
29
29
 
30
+ config = ::Sentry.configuration
31
+ url = request.uri.to_s
32
+
33
+ return unless config.propagate_traces && config.trace_propagation_targets.any? { |target| url.match?(target) }
34
+
30
35
  trace = ::Sentry.get_current_client.generate_sentry_trace(sentry_span)
31
36
  request.headers[::Sentry::SENTRY_TRACE_HEADER_NAME] = trace if trace
32
37
  end
@@ -43,8 +48,8 @@ module HTTPX::Plugins
43
48
 
44
49
  request_info = extract_request_info(req)
45
50
 
46
- data = if response.is_a?(HTTPX::ErrorResponse)
47
- { error: res.message, **request_info }
51
+ data = if res.is_a?(HTTPX::ErrorResponse)
52
+ { error: res.error.message, **request_info }
48
53
  else
49
54
  { status: res.status, **request_info }
50
55
  end
@@ -63,7 +68,11 @@ module HTTPX::Plugins
63
68
 
64
69
  request_info = extract_request_info(req)
65
70
  sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
66
- sentry_span.set_data(:status, res.status)
71
+ if res.is_a?(HTTPX::ErrorResponse)
72
+ sentry_span.set_data(:error, res.error.message)
73
+ else
74
+ sentry_span.set_data(:status, res.status)
75
+ end
67
76
  sentry_span.set_timestamp(::Sentry.utc_now.to_f)
68
77
  end
69
78
 
@@ -71,7 +80,7 @@ module HTTPX::Plugins
71
80
  uri = req.uri
72
81
 
73
82
  result = {
74
- method: req.verb.to_s.upcase,
83
+ method: req.verb,
75
84
  }
76
85
 
77
86
  if ::Sentry.configuration.send_default_pii
@@ -85,17 +94,27 @@ module HTTPX::Plugins
85
94
  end
86
95
  end
87
96
 
97
+ module RequestMethods
98
+ def __sentry_enable_trace!
99
+ return if @__sentry_enable_trace
100
+
101
+ Tracer.call(self)
102
+ @__sentry_enable_trace = true
103
+ end
104
+ end
105
+
88
106
  module ConnectionMethods
89
107
  def send(request)
90
- Tracer.call(request)
108
+ request.__sentry_enable_trace!
109
+
91
110
  super
92
111
  end
93
112
  end
94
113
  end
95
114
  end
96
115
 
97
- Sentry.register_patch do
98
- sentry_session = ::HTTPX.plugin(HTTPX::Plugins::Sentry)
116
+ Sentry.register_patch(:httpx) do
117
+ sentry_session = HTTPX.plugin(HTTPX::Plugins::Sentry)
99
118
 
100
119
  HTTPX.send(:remove_const, :Session)
101
120
  HTTPX.send(:const_set, :Session, sentry_session.class)
@@ -2,13 +2,8 @@
2
2
 
3
3
  module WebMock
4
4
  module HttpLibAdapters
5
- if RUBY_VERSION < "2.5"
6
- require "webrick/httpstatus"
7
- HTTP_REASONS = WEBrick::HTTPStatus::StatusMessage
8
- else
9
- require "net/http/status"
10
- HTTP_REASONS = Net::HTTP::STATUS_CODES
11
- end
5
+ require "net/http/status"
6
+ HTTP_REASONS = Net::HTTP::STATUS_CODES
12
7
 
13
8
  #
14
9
  # HTTPX plugin for webmock.
@@ -23,7 +18,7 @@ module WebMock
23
18
  uri.path = uri.normalized_path.gsub("[^:]//", "/")
24
19
 
25
20
  WebMock::RequestSignature.new(
26
- request.verb,
21
+ request.verb.downcase.to_sym,
27
22
  uri.to_s,
28
23
  body: request.body.each.to_a.join,
29
24
  headers: request.headers.to_h
@@ -43,30 +38,50 @@ module WebMock
43
38
 
44
39
  return build_error_response(request, webmock_response.exception) if webmock_response.exception
45
40
 
46
- response = request.options.response_class.new(request,
47
- webmock_response.status[0],
48
- "2.0",
49
- webmock_response.headers)
50
- response << webmock_response.body.dup
51
- response
41
+ request.options.response_class.new(request,
42
+ webmock_response.status[0],
43
+ "2.0",
44
+ webmock_response.headers).tap do |res|
45
+ res.mocked = true
46
+ end
52
47
  end
53
48
 
54
49
  def build_error_response(request, exception)
55
- HTTPX::ErrorResponse.new(request, exception, request.options)
50
+ HTTPX::ErrorResponse.new(request, exception)
56
51
  end
57
52
  end
58
53
 
59
54
  module InstanceMethods
60
- def build_connection(*)
55
+ def init_connection(*)
61
56
  connection = super
62
57
  connection.once(:unmock_connection) do
58
+ unless connection.addresses
59
+ connection.__send__(:callbacks)[:connect_error].clear
60
+ pool.__send__(:unregister_connection, connection)
61
+ end
63
62
  pool.__send__(:resolve_connection, connection)
64
- pool.__send__(:unregister_connection, connection) unless connection.addresses
65
63
  end
66
64
  connection
67
65
  end
68
66
  end
69
67
 
68
+ module ResponseMethods
69
+ attr_accessor :mocked
70
+
71
+ def initialize(*)
72
+ super
73
+ @mocked = false
74
+ end
75
+ end
76
+
77
+ module ResponseBodyMethods
78
+ def decode_chunk(chunk)
79
+ return chunk if @response.mocked
80
+
81
+ super
82
+ end
83
+ end
84
+
70
85
  module ConnectionMethods
71
86
  def initialize(*)
72
87
  super
@@ -95,6 +110,7 @@ module WebMock
95
110
  log { "mocking #{request.uri} with #{mock_response.inspect}" }
96
111
  request.response = response
97
112
  request.emit(:response, response)
113
+ response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
98
114
  elsif WebMock.net_connect_allowed?(request_signature.uri)
99
115
  if WebMock::CallbackRegistry.any_callbacks?
100
116
  request.on(:response) do |resp|
@@ -122,7 +138,7 @@ module WebMock
122
138
 
123
139
  class << self
124
140
  def enable!
125
- @original_session = HTTPX::Session
141
+ @original_session ||= HTTPX::Session
126
142
 
127
143
  webmock_session = HTTPX.plugin(Plugin)
128
144
 
data/lib/httpx/altsvc.rb CHANGED
@@ -4,7 +4,59 @@ require "strscan"
4
4
 
5
5
  module HTTPX
6
6
  module AltSvc
7
- @altsvc_mutex = Mutex.new
7
+ # makes connections able to accept requests destined to primary service.
8
+ module ConnectionMixin
9
+ using URIExtensions
10
+
11
+ def send(request)
12
+ request.headers["alt-used"] = @origin.authority if @parser && !@write_buffer.full? && match_altsvcs?(request.uri)
13
+
14
+ super
15
+ end
16
+
17
+ def match?(uri, options)
18
+ return false if !used? && (@state == :closing || @state == :closed)
19
+
20
+ match_altsvcs?(uri) && match_altsvc_options?(uri, options)
21
+ end
22
+
23
+ private
24
+
25
+ # checks if this is connection is an alternative service of
26
+ # +uri+
27
+ def match_altsvcs?(uri)
28
+ @origins.any? { |origin| altsvc_match?(uri, origin) } ||
29
+ AltSvc.cached_altsvc(@origin).any? do |altsvc|
30
+ origin = altsvc["origin"]
31
+ altsvc_match?(origin, uri.origin)
32
+ end
33
+ end
34
+
35
+ def match_altsvc_options?(uri, options)
36
+ return @options == options unless @options.ssl.all? do |k, v|
37
+ v == (k == :hostname ? uri.host : options.ssl[k])
38
+ end
39
+
40
+ @options.options_equals?(options, Options::REQUEST_BODY_IVARS + %i[@ssl])
41
+ end
42
+
43
+ def altsvc_match?(uri, other_uri)
44
+ other_uri = URI(other_uri)
45
+
46
+ uri.origin == other_uri.origin || begin
47
+ case uri.scheme
48
+ when "h2"
49
+ (other_uri.scheme == "https" || other_uri.scheme == "h2") &&
50
+ uri.host == other_uri.host &&
51
+ uri.port == other_uri.port
52
+ else
53
+ false
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ @altsvc_mutex = Thread::Mutex.new
8
60
  @altsvcs = Hash.new { |h, k| h[k] = [] }
9
61
 
10
62
  module_function
@@ -46,7 +98,7 @@ module HTTPX
46
98
 
47
99
  altsvc = response.headers["alt-svc"]
48
100
 
49
- # https://tools.ietf.org/html/rfc7838#section-3
101
+ # https://datatracker.ietf.org/doc/html/rfc7838#section-3
50
102
  # A field value containing the special value "clear" indicates that the
51
103
  # origin requests all alternatives for that origin to be invalidated
52
104
  # (including those specified in the same response, in case of an
@@ -79,9 +131,9 @@ module HTTPX
79
131
  scanner.skip(/;/)
80
132
  break if scanner.eos? || scanner.scan(/ *, */)
81
133
  end
82
- alt_params = Hash[alt_params.map { |field| field.split("=") }]
134
+ alt_params = Hash[alt_params.map { |field| field.split("=", 2) }]
83
135
 
84
- alt_proto, alt_authority = alt_service.split("=")
136
+ alt_proto, alt_authority = alt_service.split("=", 2)
85
137
  alt_origin = parse_altsvc_origin(alt_proto, alt_authority)
86
138
  return unless alt_origin
87
139
 
@@ -98,29 +150,14 @@ module HTTPX
98
150
  end
99
151
  end
100
152
 
101
- # :nocov:
102
- if RUBY_VERSION < "2.2"
103
- def parse_altsvc_origin(alt_proto, alt_origin)
104
- alt_scheme = parse_altsvc_scheme(alt_proto) or return
105
-
106
- alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
107
- if alt_origin.start_with?(":")
108
- alt_origin = "#{alt_scheme}://dummy#{alt_origin}"
109
- uri = URI.parse(alt_origin)
110
- uri.host = nil
111
- uri
112
- else
113
- URI.parse("#{alt_scheme}://#{alt_origin}")
114
- end
115
- end
116
- else
117
- def parse_altsvc_origin(alt_proto, alt_origin)
118
- alt_scheme = parse_altsvc_scheme(alt_proto) or return
119
- alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
153
+ def parse_altsvc_origin(alt_proto, alt_origin)
154
+ alt_scheme = parse_altsvc_scheme(alt_proto)
120
155
 
121
- URI.parse("#{alt_scheme}://#{alt_origin}")
122
- end
156
+ return unless alt_scheme
157
+
158
+ alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
159
+
160
+ URI.parse("#{alt_scheme}://#{alt_origin}")
123
161
  end
124
- # :nocov:
125
162
  end
126
163
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ if RUBY_VERSION < "3.3.0"
4
+ require "base64"
5
+ elsif !defined?(Base64)
6
+ module HTTPX
7
+ # require "base64" will not be a default gem after ruby 3.4.0
8
+ module Base64
9
+ module_function
10
+
11
+ def decode64(str)
12
+ str.unpack1("m")
13
+ end
14
+
15
+ def strict_encode64(bin)
16
+ [bin].pack("m0")
17
+ end
18
+
19
+ def urlsafe_encode64(bin, padding: true)
20
+ str = strict_encode64(bin)
21
+ str.chomp!("==") or str.chomp!("=") unless padding
22
+ str.tr!("+/", "-_")
23
+ str
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/httpx/buffer.rb CHANGED
@@ -3,6 +3,14 @@
3
3
  require "forwardable"
4
4
 
5
5
  module HTTPX
6
+ # Internal class to abstract a string buffer, by wrapping a string and providing the
7
+ # minimum possible API and functionality required.
8
+ #
9
+ # buffer = Buffer.new(640)
10
+ # buffer.full? #=> false
11
+ # buffer << "aa"
12
+ # buffer.capacity #=> 638
13
+ #
6
14
  class Buffer
7
15
  extend Forwardable
8
16
 
@@ -31,6 +39,10 @@ module HTTPX
31
39
  @buffer.bytesize >= @limit
32
40
  end
33
41
 
42
+ def capacity
43
+ @limit - @buffer.bytesize
44
+ end
45
+
34
46
  def shift!(fin)
35
47
  @buffer = @buffer.byteslice(fin..-1) || "".b
36
48
  end