httpx 0.20.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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