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
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