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
data/lib/httpx/session.rb CHANGED
@@ -1,12 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
+ EMPTY_HASH = {}.freeze
5
+
6
+ # Class implementing the APIs being used publicly.
7
+ #
8
+ # HTTPX.get(..) #=> delegating to an internal HTTPX::Session object.
9
+ # HTTPX.plugin(..).get(..) #=> creating an intermediate HTTPX::Session with plugin, then sending the GET request
4
10
  class Session
5
11
  include Loggable
6
12
  include Chainable
7
13
 
8
- EMPTY_HASH = {}.freeze
9
-
14
+ # initializes the session with a set of +options+, which will be shared by all
15
+ # requests sent from it.
16
+ #
17
+ # When pass a block, it'll yield itself to it, then closes after the block is evaluated.
10
18
  def initialize(options = EMPTY_HASH, &blk)
11
19
  @options = self.class.default_options.merge(options)
12
20
  @responses = {}
@@ -14,63 +22,102 @@ module HTTPX
14
22
  wrap(&blk) if blk
15
23
  end
16
24
 
25
+ # Yields itself the block, then closes it after the block is evaluated.
26
+ #
27
+ # session.wrap do |http|
28
+ # http.get("https://wikipedia.com")
29
+ # end # wikipedia connection closes here
17
30
  def wrap
18
- begin
19
- prev_persistent = @persistent
20
- @persistent = true
21
- yield self
22
- ensure
23
- @persistent = prev_persistent
24
- close unless @persistent
31
+ prev_persistent = @persistent
32
+ @persistent = true
33
+ pool.wrap do
34
+ begin
35
+ yield self
36
+ ensure
37
+ @persistent = prev_persistent
38
+ close unless @persistent
39
+ end
25
40
  end
26
41
  end
27
42
 
43
+ # closes all the active connections from the session
28
44
  def close(*args)
29
45
  pool.close(*args)
30
46
  end
31
47
 
32
- def request(*args, **options)
48
+ # performs one, or multple requests; it accepts:
49
+ #
50
+ # 1. one or multiple HTTPX::Request objects;
51
+ # 2. an HTTP verb, then a sequence of URIs or URI/options tuples;
52
+ # 3. one or multiple HTTP verb / uri / (optional) options tuples;
53
+ #
54
+ # when present, the set of +options+ kwargs is applied to all of the
55
+ # sent requests.
56
+ #
57
+ # respectively returns a single HTTPX::Response response, or all of them in an Array, in the same order.
58
+ #
59
+ # resp1 = session.request(req1)
60
+ # resp1, resp2 = session.request(req1, req2)
61
+ # resp1 = session.request("GET", "https://server.org/a")
62
+ # resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"])
63
+ # resp1, resp2 = session.request(["GET", "https://server.org/a"], ["GET", "https://server.org/b"])
64
+ # resp1 = session.request("POST", "https://server.org/a", form: { "foo" => "bar" })
65
+ # resp1, resp2 = session.request(["POST", "https://server.org/a", form: { "foo" => "bar" }], ["GET", "https://server.org/b"])
66
+ # resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"], headers: { "x-api-token" => "TOKEN" })
67
+ #
68
+ def request(*args, **params)
33
69
  raise ArgumentError, "must perform at least one request" if args.empty?
34
70
 
35
- requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
71
+ requests = args.first.is_a?(Request) ? args : build_requests(*args, params)
36
72
  responses = send_requests(*requests)
37
73
  return responses.first if responses.size == 1
38
74
 
39
75
  responses
40
76
  end
41
77
 
42
- def build_request(verb, uri, options = EMPTY_HASH)
43
- rklass = @options.request_class
44
- options = @options.merge(options) unless options.is_a?(Options)
45
- request = rklass.new(verb, uri, options.merge(persistent: @persistent))
46
- request.on(:response, &method(:on_response).curry(2)[request])
47
- request.on(:promise, &method(:on_promise))
78
+ # returns a HTTP::Request instance built from the HTTP +verb+, the request +uri+, and
79
+ # the optional set of request-specific +options+. This request **must** be sent through
80
+ # the same session it was built from.
81
+ #
82
+ # req = session.build_request("GET", "https://server.com")
83
+ # resp = session.request(req)
84
+ def build_request(verb, uri, params = EMPTY_HASH, options = @options)
85
+ rklass = options.request_class
86
+ request = rklass.new(verb, uri, options, params)
87
+ request.persistent = @persistent
88
+ set_request_callbacks(request)
48
89
  request
49
90
  end
50
91
 
51
92
  private
52
93
 
94
+ # returns the HTTPX::Pool object which manages the networking required to
95
+ # perform requests.
53
96
  def pool
54
97
  Thread.current[:httpx_connection_pool] ||= Pool.new
55
98
  end
56
99
 
100
+ # callback executed when a response for a given request has been received.
57
101
  def on_response(request, response)
58
102
  @responses[request] = response
59
103
  end
60
104
 
105
+ # callback executed when an HTTP/2 promise frame has been received.
61
106
  def on_promise(_, stream)
62
107
  log(level: 2) { "#{stream.id}: refusing stream!" }
63
108
  stream.refuse
64
109
  end
65
110
 
111
+ # returns the corresponding HTTP::Response to the given +request+ if it has been received.
66
112
  def fetch_response(request, _, _)
67
113
  @responses.delete(request)
68
114
  end
69
115
 
116
+ # returns the HTTPX::Connection through which the +request+ should be sent through.
70
117
  def find_connection(request, connections, options)
71
118
  uri = request.uri
72
119
 
73
- connection = pool.find_connection(uri, options) || build_connection(uri, options)
120
+ connection = pool.find_connection(uri, options) || init_connection(uri, options)
74
121
  unless connections.nil? || connections.include?(connection)
75
122
  connections << connection
76
123
  set_connection_callbacks(connection, connections, options)
@@ -78,7 +125,20 @@ module HTTPX
78
125
  connection
79
126
  end
80
127
 
81
- def set_connection_callbacks(connection, connections, options)
128
+ # sends the +request+ to the corresponding HTTPX::Connection
129
+ def send_request(request, connections, options = request.options)
130
+ error = catch(:resolve_error) do
131
+ connection = find_connection(request, connections, options)
132
+ connection.send(request)
133
+ end
134
+ return unless error.is_a?(Error)
135
+
136
+ request.emit(:response, ErrorResponse.new(request, error))
137
+ end
138
+
139
+ # sets the callbacks on the +connection+ required to process certain specific
140
+ # connection lifecycle events which deal with request rerouting.
141
+ def set_connection_callbacks(connection, connections, options, cloned: false)
82
142
  connection.only(:misdirected) do |misdirected_request|
83
143
  other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
84
144
  other_connection.merge(connection)
@@ -94,17 +154,13 @@ module HTTPX
94
154
  other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
95
155
  connections << other_connection if other_connection
96
156
  end
97
- connection.only(:exhausted) do
98
- other_connection = connection.create_idle
99
- other_connection.merge(connection)
100
- catch(:coalesced) do
101
- pool.init_connection(other_connection, options)
102
- end
103
- set_connection_callbacks(other_connection, connections, options)
104
- connections << other_connection
105
- end
157
+ connection.only(:cloned) do |cloned_conn|
158
+ set_connection_callbacks(cloned_conn, connections, options, cloned: true)
159
+ connections << cloned_conn
160
+ end unless cloned
106
161
  end
107
162
 
163
+ # returns an HTTPX::Connection for the negotiated Alternative Service (or none).
108
164
  def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
109
165
  # do not allow security downgrades on altsvc negotiation
110
166
  return if existing_connection.origin.scheme == "https" && alt_origin.scheme != "https"
@@ -116,46 +172,46 @@ module HTTPX
116
172
 
117
173
  alt_options = options.merge(ssl: options.ssl.merge(hostname: URI(origin).host))
118
174
 
119
- connection = pool.find_connection(alt_origin, alt_options) || build_connection(alt_origin, alt_options)
175
+ connection = pool.find_connection(alt_origin, alt_options) || init_connection(alt_origin, alt_options)
176
+
120
177
  # advertised altsvc is the same origin being used, ignore
121
178
  return if connection == existing_connection
122
179
 
180
+ connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
181
+
123
182
  set_connection_callbacks(connection, connections, alt_options)
124
183
 
125
184
  log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
126
185
 
127
- # get uninitialized requests
128
- # incidentally, all requests will be re-routed to the first
129
- # advertised alt-svc, which incidentally follows the spec.
130
- existing_connection.purge_pending do |request|
131
- request.origin == origin &&
132
- request.state == :idle &&
133
- !request.headers.key?("alt-used")
134
- end
135
-
136
186
  connection.merge(existing_connection)
187
+ existing_connection.terminate
137
188
  connection
138
189
  rescue UnsupportedSchemeError
139
190
  altsvc["noop"] = true
140
191
  nil
141
192
  end
142
193
 
143
- def build_requests(*args, options)
144
- request_options = @options.merge(options)
145
-
194
+ # returns a set of HTTPX::Request objects built from the given +args+ and +options+.
195
+ def build_requests(*args, params)
146
196
  requests = if args.size == 1
147
197
  reqs = args.first
148
- reqs.map do |verb, uri, opts = EMPTY_HASH|
149
- build_request(verb, uri, request_options.merge(opts))
198
+ # TODO: find a way to make requests share same options object
199
+ reqs.map do |verb, uri, ps = EMPTY_HASH|
200
+ request_params = params
201
+ request_params = request_params.merge(ps) unless ps.empty?
202
+ build_request(verb, uri, request_params)
150
203
  end
151
204
  else
152
205
  verb, uris = args
153
206
  if uris.respond_to?(:each)
154
- uris.enum_for(:each).map do |uri, opts = EMPTY_HASH|
155
- build_request(verb, uri, request_options.merge(opts))
207
+ # TODO: find a way to make requests share same options object
208
+ uris.enum_for(:each).map do |uri, ps = EMPTY_HASH|
209
+ request_params = params
210
+ request_params = request_params.merge(ps) unless ps.empty?
211
+ build_request(verb, uri, request_params)
156
212
  end
157
213
  else
158
- [build_request(verb, uris, request_options)]
214
+ [build_request(verb, uris, params)]
159
215
  end
160
216
  end
161
217
  raise ArgumentError, "wrong number of URIs (given 0, expect 1..+1)" if requests.empty?
@@ -163,46 +219,47 @@ module HTTPX
163
219
  requests
164
220
  end
165
221
 
166
- def build_connection(uri, options)
167
- type = options.transport || begin
168
- case uri.scheme
169
- when "http"
170
- "tcp"
171
- when "https"
172
- "ssl"
173
- else
174
- raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
175
- end
176
- end
177
- connection = options.connection_class.new(type, uri, options)
222
+ def set_request_callbacks(request)
223
+ request.on(:response, &method(:on_response).curry(2)[request])
224
+ request.on(:promise, &method(:on_promise))
225
+ end
226
+
227
+ def init_connection(uri, options)
228
+ connection = options.connection_class.new(uri, options)
178
229
  catch(:coalesced) do
179
230
  pool.init_connection(connection, options)
180
231
  connection
181
232
  end
182
233
  end
183
234
 
235
+ def deactivate_connection(request, connections, options)
236
+ conn = connections.find do |c|
237
+ c.match?(request.uri, options)
238
+ end
239
+
240
+ pool.deactivate(conn) if conn
241
+ end
242
+
243
+ # sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
184
244
  def send_requests(*requests)
185
245
  connections = _send_requests(requests)
186
246
  receive_requests(requests, connections)
187
247
  end
188
248
 
249
+ # sends an array of HTTPX::Request objects
189
250
  def _send_requests(requests)
190
251
  connections = []
191
252
 
192
253
  requests.each do |request|
193
- error = catch(:resolve_error) do
194
- connection = find_connection(request, connections, request.options)
195
- connection.send(request)
196
- end
197
- next unless error.is_a?(ResolveError)
198
-
199
- request.emit(:response, ErrorResponse.new(request, error, request.options))
254
+ send_request(request, connections)
200
255
  end
201
256
 
202
257
  connections
203
258
  end
204
259
 
260
+ # returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
205
261
  def receive_requests(requests, connections)
262
+ # @type var responses: Array[response]
206
263
  responses = []
207
264
 
208
265
  begin
@@ -213,6 +270,7 @@ module HTTPX
213
270
  return responses unless request
214
271
 
215
272
  catch(:coalesced) { pool.next_tick } until (response = fetch_response(request, connections, request.options))
273
+ request.emit(:complete, response)
216
274
 
217
275
  responses << response
218
276
  requests.shift
@@ -226,14 +284,16 @@ module HTTPX
226
284
  # opportunity to traverse the requests, hence we're returning only a fraction of the errors
227
285
  # we were supposed to. This effectively fetches the existing responses and return them.
228
286
  while (request = requests.shift)
229
- responses << fetch_response(request, connections, request.options)
287
+ response = fetch_response(request, connections, request.options)
288
+ request.emit(:complete, response) if response
289
+ responses << response
230
290
  end
231
291
  break
232
292
  end
233
293
  responses
234
294
  ensure
235
295
  if @persistent
236
- pool.deactivate(connections)
296
+ pool.deactivate(*connections)
237
297
  else
238
298
  close(connections)
239
299
  end
@@ -251,8 +311,14 @@ module HTTPX
251
311
  super
252
312
  klass.instance_variable_set(:@default_options, @default_options)
253
313
  klass.instance_variable_set(:@plugins, @plugins.dup)
314
+ klass.instance_variable_set(:@callbacks, @callbacks.dup)
254
315
  end
255
316
 
317
+ # returns a new HTTPX::Session instance, with the plugin pointed by +pl+ loaded.
318
+ #
319
+ # session_with_retries = session.plugin(:retries)
320
+ # session_with_custom = session.plugin(CustomPlugin)
321
+ #
256
322
  def plugin(pl, options = nil, &block)
257
323
  # raise Error, "Cannot add a plugin to a frozen config" if frozen?
258
324
  pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
@@ -266,19 +332,8 @@ module HTTPX
266
332
  extend(pl::ClassMethods) if defined?(pl::ClassMethods)
267
333
 
268
334
  opts = @default_options
269
- opts.request_class.__send__(:include, pl::RequestMethods) if defined?(pl::RequestMethods)
270
- opts.request_class.extend(pl::RequestClassMethods) if defined?(pl::RequestClassMethods)
271
- opts.response_class.__send__(:include, pl::ResponseMethods) if defined?(pl::ResponseMethods)
272
- opts.response_class.extend(pl::ResponseClassMethods) if defined?(pl::ResponseClassMethods)
273
- opts.headers_class.__send__(:include, pl::HeadersMethods) if defined?(pl::HeadersMethods)
274
- opts.headers_class.extend(pl::HeadersClassMethods) if defined?(pl::HeadersClassMethods)
275
- opts.request_body_class.__send__(:include, pl::RequestBodyMethods) if defined?(pl::RequestBodyMethods)
276
- opts.request_body_class.extend(pl::RequestBodyClassMethods) if defined?(pl::RequestBodyClassMethods)
277
- opts.response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
278
- opts.response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
279
- opts.connection_class.__send__(:include, pl::ConnectionMethods) if defined?(pl::ConnectionMethods)
335
+ opts.extend_with_plugin_classes(pl)
280
336
  if defined?(pl::OptionsMethods)
281
- opts.options_class.__send__(:include, pl::OptionsMethods)
282
337
 
283
338
  (pl::OptionsMethods.instance_methods - Object.instance_methods).each do |meth|
284
339
  opts.options_class.method_added(meth)
@@ -302,16 +357,9 @@ module HTTPX
302
357
  end
303
358
  self
304
359
  end
305
-
306
- # :nocov:
307
- def plugins(pls)
308
- warn ":#{__method__} is deprecated, use :plugin instead"
309
- pls.each do |pl|
310
- plugin(pl)
311
- end
312
- self
313
- end
314
- # :nocov:
315
360
  end
316
361
  end
362
+
363
+ # session may be overridden by certain adapters.
364
+ S = Session
317
365
  end
@@ -9,10 +9,13 @@ module HTTPX
9
9
  # redefine the default options static var, which needs to
10
10
  # refresh options_class
11
11
  options = proxy_session.class.default_options.to_hash
12
- options.freeze
13
12
  original_verbosity = $VERBOSE
14
13
  $VERBOSE = nil
14
+ const_set(:Options, proxy_session.class.default_options.options_class)
15
+ options[:options_class] = Class.new(options[:options_class])
16
+ options.freeze
15
17
  Options.send(:const_set, :DEFAULT_OPTIONS, options)
18
+ Session.instance_variable_set(:@default_options, Options.new(options))
16
19
  $VERBOSE = original_verbosity
17
20
  end
18
21
 
data/lib/httpx/timers.rb CHANGED
@@ -6,20 +6,27 @@ module HTTPX
6
6
  @intervals = []
7
7
  end
8
8
 
9
- def after(interval_in_secs, &blk)
9
+ def after(interval_in_secs, cb = nil, &blk)
10
10
  return unless interval_in_secs
11
11
 
12
+ callback = cb || blk
13
+
12
14
  # I'm assuming here that most requests will have the same
13
15
  # request timeout, as in most cases they share common set of
14
16
  # options. A user setting different request timeouts for 100s of
15
17
  # requests will already have a hard time dealing with that.
16
- unless (interval = @intervals.find { |t| t == interval_in_secs })
18
+ unless (interval = @intervals.find { |t| t.interval == interval_in_secs })
17
19
  interval = Interval.new(interval_in_secs)
20
+ interval.on_empty { @intervals.delete(interval) }
18
21
  @intervals << interval
19
22
  @intervals.sort!
20
23
  end
21
24
 
22
- interval << blk
25
+ interval << callback
26
+
27
+ @next_interval_at = nil
28
+
29
+ interval
23
30
  end
24
31
 
25
32
  def wait_interval
@@ -36,11 +43,9 @@ module HTTPX
36
43
 
37
44
  elapsed_time = Utils.elapsed_time(@next_interval_at)
38
45
 
39
- @intervals.delete_if { |interval| interval.elapse(elapsed_time) <= 0 }
40
- end
46
+ @intervals = @intervals.drop_while { |interval| interval.elapse(elapsed_time) <= 0 }
41
47
 
42
- def cancel
43
- @intervals.clear
48
+ @next_interval_at = nil if @intervals.empty?
44
49
  end
45
50
 
46
51
  class Interval
@@ -51,6 +56,11 @@ module HTTPX
51
56
  def initialize(interval)
52
57
  @interval = interval
53
58
  @callbacks = []
59
+ @on_empty = nil
60
+ end
61
+
62
+ def on_empty(&blk)
63
+ @on_empty = blk
54
64
  end
55
65
 
56
66
  def <=>(other)
@@ -71,10 +81,26 @@ module HTTPX
71
81
  @callbacks << callback
72
82
  end
73
83
 
84
+ def delete(callback)
85
+ @callbacks.delete(callback)
86
+ @on_empty.call if @callbacks.empty?
87
+ end
88
+
89
+ def no_callbacks?
90
+ @callbacks.empty?
91
+ end
92
+
93
+ def elapsed?
94
+ @interval <= 0
95
+ end
96
+
74
97
  def elapse(elapsed)
75
98
  @interval -= elapsed
76
99
 
77
- @callbacks.each(&:call) if @interval <= 0
100
+ if @interval <= 0
101
+ cb = @callbacks.dup
102
+ cb.each(&:call)
103
+ end
78
104
 
79
105
  @interval
80
106
  end
@@ -9,7 +9,6 @@ module HTTPX::Transcoder
9
9
  module_function
10
10
 
11
11
  class Encoder
12
- using HTTPX::ArrayExtensions
13
12
  extend Forwardable
14
13
 
15
14
  def_delegator :@raw, :to_s
@@ -55,5 +54,4 @@ module HTTPX::Transcoder
55
54
  Encoder.new(body)
56
55
  end
57
56
  end
58
- register "body", Body
59
57
  end
@@ -112,5 +112,4 @@ module HTTPX::Transcoder
112
112
  Encoder.new(chunks)
113
113
  end
114
114
  end
115
- register "chunker", Chunker
116
115
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require_relative "utils/deflater"
5
+
6
+ module HTTPX
7
+ module Transcoder
8
+ module Deflate
9
+ class Deflater < Transcoder::Deflater
10
+ def deflate(chunk)
11
+ @deflater ||= Zlib::Deflate.new
12
+
13
+ if chunk.nil?
14
+ unless @deflater.closed?
15
+ last = @deflater.finish
16
+ @deflater.close
17
+ last.empty? ? nil : last
18
+ end
19
+ else
20
+ @deflater.deflate(chunk)
21
+ end
22
+ end
23
+ end
24
+
25
+ module_function
26
+
27
+ def encode(body)
28
+ Deflater.new(body)
29
+ end
30
+
31
+ def decode(response, bytesize: nil)
32
+ bytesize ||= response.headers.key?("content-length") ? response.headers["content-length"].to_i : Float::INFINITY
33
+ GZIP::Inflater.new(bytesize)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,58 +2,77 @@
2
2
 
3
3
  require "forwardable"
4
4
  require "uri"
5
+ require_relative "multipart"
5
6
 
6
- module HTTPX::Transcoder
7
- module Form
8
- module_function
7
+ module HTTPX
8
+ module Transcoder
9
+ module Form
10
+ module_function
9
11
 
10
- PARAM_DEPTH_LIMIT = 32
12
+ PARAM_DEPTH_LIMIT = 32
11
13
 
12
- class Encoder
13
- extend Forwardable
14
+ class Encoder
15
+ extend Forwardable
14
16
 
15
- def_delegator :@raw, :to_s
17
+ def_delegator :@raw, :to_s
16
18
 
17
- def_delegator :@raw, :to_str
19
+ def_delegator :@raw, :to_str
18
20
 
19
- def_delegator :@raw, :bytesize
21
+ def_delegator :@raw, :bytesize
20
22
 
21
- def initialize(form)
22
- @raw = form.each_with_object("".b) do |(key, val), buf|
23
- HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
24
- buf << "&" unless buf.empty?
25
- buf << URI.encode_www_form_component(k)
26
- buf << "=#{URI.encode_www_form_component(v.to_s)}" unless v.nil?
23
+ def initialize(form)
24
+ @raw = form.each_with_object("".b) do |(key, val), buf|
25
+ HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
26
+ buf << "&" unless buf.empty?
27
+ buf << URI.encode_www_form_component(k)
28
+ buf << "=#{URI.encode_www_form_component(v.to_s)}" unless v.nil?
29
+ end
27
30
  end
28
31
  end
29
- end
30
32
 
31
- def content_type
32
- "application/x-www-form-urlencoded"
33
+ def content_type
34
+ "application/x-www-form-urlencoded"
35
+ end
33
36
  end
34
- end
35
37
 
36
- module Decoder
37
- module_function
38
+ module Decoder
39
+ module_function
38
40
 
39
- def call(response, _)
40
- URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
41
- HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
41
+ def call(response, *)
42
+ URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
43
+ HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
44
+ end
42
45
  end
43
46
  end
44
- end
45
47
 
46
- def encode(form)
47
- Encoder.new(form)
48
- end
48
+ def encode(form)
49
+ if multipart?(form)
50
+ Multipart::Encoder.new(form)
51
+ else
52
+ Encoder.new(form)
53
+ end
54
+ end
49
55
 
50
- def decode(response)
51
- content_type = response.content_type.mime_type
56
+ def decode(response)
57
+ content_type = response.content_type.mime_type
52
58
 
53
- raise HTTPX::Error, "invalid form mime type (#{content_type})" unless content_type == "application/x-www-form-urlencoded"
59
+ case content_type
60
+ when "application/x-www-form-urlencoded"
61
+ Decoder
62
+ when "multipart/form-data"
63
+ Multipart::Decoder.new(response)
64
+ else
65
+ raise Error, "invalid form mime type (#{content_type})"
66
+ end
67
+ end
54
68
 
55
- Decoder
69
+ def multipart?(data)
70
+ data.any? do |_, v|
71
+ Multipart::MULTIPART_VALUE_COND.call(v) ||
72
+ (v.respond_to?(:to_ary) && v.to_ary.any?(&Multipart::MULTIPART_VALUE_COND)) ||
73
+ (v.respond_to?(:to_hash) && v.to_hash.any? { |_, e| Multipart::MULTIPART_VALUE_COND.call(e) })
74
+ end
75
+ end
56
76
  end
57
77
  end
58
- register "form", Form
59
78
  end