httpx 0.21.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -48
  3. data/README.md +54 -45
  4. data/doc/release_notes/0_10_0.md +2 -2
  5. data/doc/release_notes/0_11_0.md +3 -5
  6. data/doc/release_notes/0_12_0.md +5 -5
  7. data/doc/release_notes/0_13_0.md +4 -4
  8. data/doc/release_notes/0_14_0.md +2 -2
  9. data/doc/release_notes/0_16_0.md +3 -3
  10. data/doc/release_notes/0_17_0.md +1 -1
  11. data/doc/release_notes/0_18_0.md +4 -4
  12. data/doc/release_notes/0_18_2.md +1 -1
  13. data/doc/release_notes/0_19_0.md +1 -1
  14. data/doc/release_notes/0_20_0.md +1 -1
  15. data/doc/release_notes/0_21_0.md +7 -5
  16. data/doc/release_notes/0_21_1.md +12 -0
  17. data/doc/release_notes/0_22_0.md +13 -0
  18. data/doc/release_notes/0_22_1.md +11 -0
  19. data/doc/release_notes/0_22_2.md +5 -0
  20. data/doc/release_notes/0_22_3.md +55 -0
  21. data/doc/release_notes/0_22_4.md +6 -0
  22. data/doc/release_notes/0_22_5.md +6 -0
  23. data/doc/release_notes/0_23_0.md +42 -0
  24. data/doc/release_notes/0_23_1.md +5 -0
  25. data/doc/release_notes/0_23_2.md +5 -0
  26. data/doc/release_notes/0_23_3.md +6 -0
  27. data/doc/release_notes/0_23_4.md +5 -0
  28. data/doc/release_notes/0_24_0.md +48 -0
  29. data/doc/release_notes/0_24_1.md +12 -0
  30. data/doc/release_notes/0_24_2.md +12 -0
  31. data/doc/release_notes/0_24_3.md +12 -0
  32. data/doc/release_notes/0_24_4.md +18 -0
  33. data/doc/release_notes/0_24_5.md +6 -0
  34. data/doc/release_notes/0_24_6.md +5 -0
  35. data/doc/release_notes/0_24_7.md +10 -0
  36. data/doc/release_notes/1_0_0.md +60 -0
  37. data/doc/release_notes/1_0_1.md +5 -0
  38. data/doc/release_notes/1_0_2.md +7 -0
  39. data/doc/release_notes/1_1_0.md +32 -0
  40. data/doc/release_notes/1_1_1.md +17 -0
  41. data/doc/release_notes/1_1_2.md +12 -0
  42. data/doc/release_notes/1_1_3.md +18 -0
  43. data/doc/release_notes/1_1_4.md +6 -0
  44. data/doc/release_notes/1_1_5.md +12 -0
  45. data/doc/release_notes/1_2_0.md +49 -0
  46. data/doc/release_notes/1_2_1.md +6 -0
  47. data/lib/httpx/adapters/datadog.rb +100 -106
  48. data/lib/httpx/adapters/faraday.rb +143 -107
  49. data/lib/httpx/adapters/sentry.rb +26 -7
  50. data/lib/httpx/adapters/webmock.rb +33 -17
  51. data/lib/httpx/altsvc.rb +61 -24
  52. data/lib/httpx/base64.rb +27 -0
  53. data/lib/httpx/buffer.rb +12 -0
  54. data/lib/httpx/callbacks.rb +5 -3
  55. data/lib/httpx/chainable.rb +54 -39
  56. data/lib/httpx/connection/http1.rb +62 -37
  57. data/lib/httpx/connection/http2.rb +16 -27
  58. data/lib/httpx/connection.rb +213 -120
  59. data/lib/httpx/domain_name.rb +10 -13
  60. data/lib/httpx/errors.rb +34 -2
  61. data/lib/httpx/extensions.rb +4 -134
  62. data/lib/httpx/io/ssl.rb +77 -71
  63. data/lib/httpx/io/tcp.rb +46 -70
  64. data/lib/httpx/io/udp.rb +18 -52
  65. data/lib/httpx/io/unix.rb +6 -13
  66. data/lib/httpx/io.rb +3 -9
  67. data/lib/httpx/loggable.rb +4 -19
  68. data/lib/httpx/options.rb +168 -110
  69. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  70. data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -14
  71. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  72. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  73. data/lib/httpx/plugins/auth.rb +25 -0
  74. data/lib/httpx/plugins/aws_sdk_authentication.rb +1 -3
  75. data/lib/httpx/plugins/aws_sigv4.rb +5 -6
  76. data/lib/httpx/plugins/basic_auth.rb +29 -0
  77. data/lib/httpx/plugins/brotli.rb +50 -0
  78. data/lib/httpx/plugins/callbacks.rb +91 -0
  79. data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
  80. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
  81. data/lib/httpx/plugins/circuit_breaker.rb +30 -7
  82. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  83. data/lib/httpx/plugins/cookies.rb +20 -10
  84. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
  85. data/lib/httpx/plugins/expect.rb +15 -13
  86. data/lib/httpx/plugins/follow_redirects.rb +71 -29
  87. data/lib/httpx/plugins/grpc/call.rb +2 -3
  88. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  89. data/lib/httpx/plugins/grpc/message.rb +7 -37
  90. data/lib/httpx/plugins/grpc.rb +35 -29
  91. data/lib/httpx/plugins/h2c.rb +25 -18
  92. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  93. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  94. data/lib/httpx/plugins/oauth.rb +170 -0
  95. data/lib/httpx/plugins/persistent.rb +1 -1
  96. data/lib/httpx/plugins/proxy/http.rb +15 -10
  97. data/lib/httpx/plugins/proxy/socks4.rb +8 -6
  98. data/lib/httpx/plugins/proxy/socks5.rb +10 -8
  99. data/lib/httpx/plugins/proxy.rb +69 -67
  100. data/lib/httpx/plugins/push_promise.rb +1 -1
  101. data/lib/httpx/plugins/rate_limiter.rb +3 -1
  102. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  103. data/lib/httpx/plugins/response_cache/store.rb +34 -17
  104. data/lib/httpx/plugins/response_cache.rb +6 -6
  105. data/lib/httpx/plugins/retries.rb +61 -12
  106. data/lib/httpx/plugins/ssrf_filter.rb +142 -0
  107. data/lib/httpx/plugins/stream.rb +27 -32
  108. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  109. data/lib/httpx/plugins/upgrade.rb +8 -10
  110. data/lib/httpx/plugins/webdav.rb +10 -8
  111. data/lib/httpx/pool.rb +85 -23
  112. data/lib/httpx/punycode.rb +9 -291
  113. data/lib/httpx/request/body.rb +158 -0
  114. data/lib/httpx/request.rb +86 -121
  115. data/lib/httpx/resolver/https.rb +54 -17
  116. data/lib/httpx/resolver/multi.rb +8 -12
  117. data/lib/httpx/resolver/native.rb +163 -70
  118. data/lib/httpx/resolver/resolver.rb +28 -13
  119. data/lib/httpx/resolver/system.rb +15 -10
  120. data/lib/httpx/resolver.rb +38 -16
  121. data/lib/httpx/response/body.rb +242 -0
  122. data/lib/httpx/response/buffer.rb +96 -0
  123. data/lib/httpx/response.rb +113 -211
  124. data/lib/httpx/selector.rb +2 -4
  125. data/lib/httpx/session.rb +91 -64
  126. data/lib/httpx/session_extensions.rb +4 -1
  127. data/lib/httpx/timers.rb +28 -8
  128. data/lib/httpx/transcoder/body.rb +0 -2
  129. data/lib/httpx/transcoder/chunker.rb +0 -1
  130. data/lib/httpx/transcoder/deflate.rb +37 -0
  131. data/lib/httpx/transcoder/form.rb +52 -33
  132. data/lib/httpx/transcoder/gzip.rb +74 -0
  133. data/lib/httpx/transcoder/json.rb +2 -5
  134. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  135. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
  136. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  137. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  138. data/lib/httpx/transcoder/multipart.rb +17 -0
  139. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  140. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  141. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  142. data/lib/httpx/transcoder/xml.rb +0 -5
  143. data/lib/httpx/transcoder.rb +4 -6
  144. data/lib/httpx/utils.rb +36 -16
  145. data/lib/httpx/version.rb +1 -1
  146. data/lib/httpx.rb +12 -14
  147. data/sig/altsvc.rbs +33 -0
  148. data/sig/buffer.rbs +1 -0
  149. data/sig/callbacks.rbs +3 -3
  150. data/sig/chainable.rbs +10 -9
  151. data/sig/connection/http1.rbs +5 -4
  152. data/sig/connection/http2.rbs +1 -1
  153. data/sig/connection.rbs +46 -24
  154. data/sig/errors.rbs +9 -3
  155. data/sig/httpx.rbs +5 -4
  156. data/sig/io/ssl.rbs +26 -0
  157. data/sig/io/tcp.rbs +60 -0
  158. data/sig/io/udp.rbs +20 -0
  159. data/sig/io/unix.rbs +10 -0
  160. data/sig/options.rbs +28 -12
  161. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  162. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  163. data/sig/plugins/auth.rbs +13 -0
  164. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  165. data/sig/plugins/brotli.rbs +22 -0
  166. data/sig/plugins/callbacks.rbs +38 -0
  167. data/sig/plugins/circuit_breaker.rbs +13 -3
  168. data/sig/plugins/compression.rbs +6 -4
  169. data/sig/plugins/cookies/jar.rbs +2 -2
  170. data/sig/plugins/cookies.rbs +2 -0
  171. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  172. data/sig/plugins/follow_redirects.rbs +11 -2
  173. data/sig/plugins/grpc/call.rbs +19 -0
  174. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  175. data/sig/plugins/grpc/message.rbs +17 -0
  176. data/sig/plugins/grpc.rbs +2 -32
  177. data/sig/plugins/h2c.rbs +1 -1
  178. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  179. data/sig/plugins/oauth.rbs +54 -0
  180. data/sig/plugins/proxy/socks4.rbs +4 -4
  181. data/sig/plugins/proxy/socks5.rbs +2 -2
  182. data/sig/plugins/proxy/ssh.rbs +1 -1
  183. data/sig/plugins/proxy.rbs +10 -4
  184. data/sig/plugins/response_cache.rbs +12 -3
  185. data/sig/plugins/retries.rbs +28 -8
  186. data/sig/plugins/stream.rbs +24 -17
  187. data/sig/plugins/upgrade.rbs +5 -3
  188. data/sig/pool.rbs +5 -4
  189. data/sig/request/body.rbs +40 -0
  190. data/sig/request.rbs +12 -28
  191. data/sig/resolver/https.rbs +7 -2
  192. data/sig/resolver/native.rbs +10 -4
  193. data/sig/resolver/resolver.rbs +6 -4
  194. data/sig/resolver/system.rbs +2 -0
  195. data/sig/resolver.rbs +9 -5
  196. data/sig/response/body.rbs +53 -0
  197. data/sig/response/buffer.rbs +24 -0
  198. data/sig/response.rbs +17 -38
  199. data/sig/session.rbs +24 -18
  200. data/sig/timers.rbs +17 -7
  201. data/sig/transcoder/body.rbs +4 -3
  202. data/sig/transcoder/deflate.rbs +11 -0
  203. data/sig/transcoder/form.rbs +5 -3
  204. data/sig/transcoder/gzip.rbs +24 -0
  205. data/sig/transcoder/json.rbs +4 -2
  206. data/sig/{plugins → transcoder}/multipart.rbs +3 -12
  207. data/sig/transcoder/utils/body_reader.rbs +15 -0
  208. data/sig/transcoder/utils/deflater.rbs +29 -0
  209. data/sig/transcoder/utils/inflater.rbs +12 -0
  210. data/sig/transcoder/xml.rbs +1 -1
  211. data/sig/transcoder.rbs +22 -7
  212. data/sig/utils.rbs +2 -0
  213. metadata +127 -40
  214. data/lib/httpx/plugins/authentication.rb +0 -20
  215. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  216. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  217. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  218. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  219. data/lib/httpx/plugins/compression.rb +0 -164
  220. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  221. data/lib/httpx/plugins/multipart.rb +0 -84
  222. data/lib/httpx/registry.rb +0 -85
  223. data/sig/plugins/authentication.rbs +0 -11
  224. data/sig/plugins/compression/brotli.rbs +0 -21
  225. data/sig/plugins/compression/deflate.rbs +0 -17
  226. data/sig/plugins/compression/gzip.rbs +0 -29
  227. data/sig/registry.rbs +0 -13
  228. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  229. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/session.rb CHANGED
@@ -1,12 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
+ # Class implementing the APIs being used publicly.
5
+ #
6
+ # HTTPX.get(..) #=> delegating to an internal HTTPX::Session object.
7
+ # HTTPX.plugin(..).get(..) #=> creating an intermediate HTTPX::Session with plugin, then sending the GET request
4
8
  class Session
5
9
  include Loggable
6
10
  include Chainable
7
11
 
8
12
  EMPTY_HASH = {}.freeze
9
13
 
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,6 +22,11 @@ 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
31
  begin
19
32
  prev_persistent = @persistent
@@ -25,10 +38,31 @@ module HTTPX
25
38
  end
26
39
  end
27
40
 
41
+ # closes all the active connections from the session
28
42
  def close(*args)
29
43
  pool.close(*args)
30
44
  end
31
45
 
46
+ # performs one, or multple requests; it accepts:
47
+ #
48
+ # 1. one or multiple HTTPX::Request objects;
49
+ # 2. an HTTP verb, then a sequence of URIs or URI/options tuples;
50
+ # 3. one or multiple HTTP verb / uri / (optional) options tuples;
51
+ #
52
+ # when present, the set of +options+ kwargs is applied to all of the
53
+ # sent requests.
54
+ #
55
+ # respectively returns a single HTTPX::Response response, or all of them in an Array, in the same order.
56
+ #
57
+ # resp1 = session.request(req1)
58
+ # resp1, resp2 = session.request(req1, req2)
59
+ # resp1 = session.request("GET", "https://server.org/a")
60
+ # resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"])
61
+ # resp1, resp2 = session.request(["GET", "https://server.org/a"], ["GET", "https://server.org/b"])
62
+ # resp1 = session.request("POST", "https://server.org/a", form: { "foo" => "bar" })
63
+ # resp1, resp2 = session.request(["POST", "https://server.org/a", form: { "foo" => "bar" }], ["GET", "https://server.org/b"])
64
+ # resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"], headers: { "x-api-token" => "TOKEN" })
65
+ #
32
66
  def request(*args, **options)
33
67
  raise ArgumentError, "must perform at least one request" if args.empty?
34
68
 
@@ -39,38 +73,50 @@ module HTTPX
39
73
  responses
40
74
  end
41
75
 
76
+ # returns a HTTP::Request instance built from the HTTP +verb+, the request +uri+, and
77
+ # the optional set of request-specific +options+. This request **must** be sent through
78
+ # the same session it was built from.
79
+ #
80
+ # req = session.build_request("GET", "https://server.com")
81
+ # resp = session.request(req)
42
82
  def build_request(verb, uri, options = EMPTY_HASH)
43
83
  rklass = @options.request_class
44
84
  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))
85
+ request = rklass.new(verb, uri, options)
86
+ request.persistent = @persistent
87
+ set_request_callbacks(request)
48
88
  request
49
89
  end
50
90
 
51
91
  private
52
92
 
93
+ # returns the HTTPX::Pool object which manages the networking required to
94
+ # perform requests.
53
95
  def pool
54
96
  Thread.current[:httpx_connection_pool] ||= Pool.new
55
97
  end
56
98
 
99
+ # callback executed when a response for a given request has been received.
57
100
  def on_response(request, response)
58
101
  @responses[request] = response
59
102
  end
60
103
 
104
+ # callback executed when an HTTP/2 promise frame has been received.
61
105
  def on_promise(_, stream)
62
106
  log(level: 2) { "#{stream.id}: refusing stream!" }
63
107
  stream.refuse
64
108
  end
65
109
 
110
+ # returns the corresponding HTTP::Response to the given +request+ if it has been received.
66
111
  def fetch_response(request, _, _)
67
112
  @responses.delete(request)
68
113
  end
69
114
 
115
+ # returns the HTTPX::Connection through which the +request+ should be sent through.
70
116
  def find_connection(request, connections, options)
71
117
  uri = request.uri
72
118
 
73
- connection = pool.find_connection(uri, options) || build_connection(uri, options)
119
+ connection = pool.find_connection(uri, options) || init_connection(uri, options)
74
120
  unless connections.nil? || connections.include?(connection)
75
121
  connections << connection
76
122
  set_connection_callbacks(connection, connections, options)
@@ -78,6 +124,18 @@ module HTTPX
78
124
  connection
79
125
  end
80
126
 
127
+ def send_request(request, connections, options = request.options)
128
+ error = catch(:resolve_error) do
129
+ connection = find_connection(request, connections, options)
130
+ connection.send(request)
131
+ end
132
+ return unless error.is_a?(Error)
133
+
134
+ request.emit(:response, ErrorResponse.new(request, error, options))
135
+ end
136
+
137
+ # sets the callbacks on the +connection+ required to process certain specific
138
+ # connection lifecycle events which deal with request rerouting.
81
139
  def set_connection_callbacks(connection, connections, options)
82
140
  connection.only(:misdirected) do |misdirected_request|
83
141
  other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
@@ -94,17 +152,9 @@ module HTTPX
94
152
  other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
95
153
  connections << other_connection if other_connection
96
154
  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
106
155
  end
107
156
 
157
+ # returns an HTTPX::Connection for the negotiated Alternative Service (or none).
108
158
  def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
109
159
  # do not allow security downgrades on altsvc negotiation
110
160
  return if existing_connection.origin.scheme == "https" && alt_origin.scheme != "https"
@@ -116,30 +166,26 @@ module HTTPX
116
166
 
117
167
  alt_options = options.merge(ssl: options.ssl.merge(hostname: URI(origin).host))
118
168
 
119
- connection = pool.find_connection(alt_origin, alt_options) || build_connection(alt_origin, alt_options)
169
+ connection = pool.find_connection(alt_origin, alt_options) || init_connection(alt_origin, alt_options)
170
+
120
171
  # advertised altsvc is the same origin being used, ignore
121
172
  return if connection == existing_connection
122
173
 
174
+ connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
175
+
123
176
  set_connection_callbacks(connection, connections, alt_options)
124
177
 
125
178
  log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
126
179
 
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
180
  connection.merge(existing_connection)
181
+ existing_connection.terminate
137
182
  connection
138
183
  rescue UnsupportedSchemeError
139
184
  altsvc["noop"] = true
140
185
  nil
141
186
  end
142
187
 
188
+ # returns a set of HTTPX::Request objects built from the given +args+ and +options+.
143
189
  def build_requests(*args, options)
144
190
  request_options = @options.merge(options)
145
191
 
@@ -163,46 +209,39 @@ module HTTPX
163
209
  requests
164
210
  end
165
211
 
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)
212
+ def set_request_callbacks(request)
213
+ request.on(:response, &method(:on_response).curry(2)[request])
214
+ request.on(:promise, &method(:on_promise))
215
+ end
216
+
217
+ def init_connection(uri, options)
218
+ connection = options.connection_class.new(uri, options)
178
219
  catch(:coalesced) do
179
220
  pool.init_connection(connection, options)
180
221
  connection
181
222
  end
182
223
  end
183
224
 
225
+ # sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
184
226
  def send_requests(*requests)
185
227
  connections = _send_requests(requests)
186
228
  receive_requests(requests, connections)
187
229
  end
188
230
 
231
+ # sends an array of HTTPX::Request objects
189
232
  def _send_requests(requests)
190
233
  connections = []
191
234
 
192
235
  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))
236
+ send_request(request, connections)
200
237
  end
201
238
 
202
239
  connections
203
240
  end
204
241
 
242
+ # returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
205
243
  def receive_requests(requests, connections)
244
+ # @type var responses: Array[response]
206
245
  responses = []
207
246
 
208
247
  begin
@@ -251,8 +290,14 @@ module HTTPX
251
290
  super
252
291
  klass.instance_variable_set(:@default_options, @default_options)
253
292
  klass.instance_variable_set(:@plugins, @plugins.dup)
293
+ klass.instance_variable_set(:@callbacks, @callbacks.dup)
254
294
  end
255
295
 
296
+ # returns a new HTTPX::Session instance, with the plugin pointed by +pl+ loaded.
297
+ #
298
+ # session_with_retries = session.plugin(:retries)
299
+ # session_with_custom = session.plugin(CustomPlugin)
300
+ #
256
301
  def plugin(pl, options = nil, &block)
257
302
  # raise Error, "Cannot add a plugin to a frozen config" if frozen?
258
303
  pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
@@ -266,19 +311,8 @@ module HTTPX
266
311
  extend(pl::ClassMethods) if defined?(pl::ClassMethods)
267
312
 
268
313
  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)
314
+ opts.extend_with_plugin_classes(pl)
280
315
  if defined?(pl::OptionsMethods)
281
- opts.options_class.__send__(:include, pl::OptionsMethods)
282
316
 
283
317
  (pl::OptionsMethods.instance_methods - Object.instance_methods).each do |meth|
284
318
  opts.options_class.method_added(meth)
@@ -302,16 +336,9 @@ module HTTPX
302
336
  end
303
337
  self
304
338
  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
339
  end
316
340
  end
341
+
342
+ # session may be overridden by certain adapters.
343
+ S = Session
317
344
  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
@@ -41,11 +48,6 @@ module HTTPX
41
48
  @next_interval_at = nil if @intervals.empty?
42
49
  end
43
50
 
44
- def cancel
45
- @next_interval_at = nil
46
- @intervals.clear
47
- end
48
-
49
51
  class Interval
50
52
  include Comparable
51
53
 
@@ -54,6 +56,11 @@ module HTTPX
54
56
  def initialize(interval)
55
57
  @interval = interval
56
58
  @callbacks = []
59
+ @on_empty = nil
60
+ end
61
+
62
+ def on_empty(&blk)
63
+ @on_empty = blk
57
64
  end
58
65
 
59
66
  def <=>(other)
@@ -74,6 +81,19 @@ module HTTPX
74
81
  @callbacks << callback
75
82
  end
76
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
+
77
97
  def elapse(elapsed)
78
98
  @interval -= elapsed
79
99
 
@@ -9,7 +9,6 @@ module HTTPX::Transcoder
9
9
  module_function
10
10
 
11
11
  class Encoder
12
- using HTTPX::ArrayExtensions::Sum
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
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "uri"
5
+ require "stringio"
6
+ require "zlib"
7
+
8
+ module HTTPX
9
+ module Transcoder
10
+ module GZIP
11
+ class Deflater < Transcoder::Deflater
12
+ def initialize(body)
13
+ @compressed_chunk = "".b
14
+ super
15
+ end
16
+
17
+ def deflate(chunk)
18
+ @deflater ||= Zlib::GzipWriter.new(self)
19
+
20
+ if chunk.nil?
21
+ unless @deflater.closed?
22
+ @deflater.flush
23
+ @deflater.close
24
+ compressed_chunk
25
+ end
26
+ else
27
+ @deflater.write(chunk)
28
+ compressed_chunk
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def write(chunk)
35
+ @compressed_chunk << chunk
36
+ end
37
+
38
+ def compressed_chunk
39
+ @compressed_chunk.dup
40
+ ensure
41
+ @compressed_chunk.clear
42
+ end
43
+ end
44
+
45
+ class Inflater
46
+ def initialize(bytesize)
47
+ @inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
48
+ @bytesize = bytesize
49
+ end
50
+
51
+ def call(chunk)
52
+ buffer = @inflater.inflate(chunk)
53
+ @bytesize -= chunk.bytesize
54
+ if @bytesize <= 0
55
+ buffer << @inflater.finish
56
+ @inflater.close
57
+ end
58
+ buffer
59
+ end
60
+ end
61
+
62
+ module_function
63
+
64
+ def encode(body)
65
+ Deflater.new(body)
66
+ end
67
+
68
+ def decode(response, bytesize: nil)
69
+ bytesize ||= response.headers.key?("content-length") ? response.headers["content-length"].to_i : Float::INFINITY
70
+ Inflater.new(bytesize)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -4,12 +4,10 @@ require "forwardable"
4
4
 
5
5
  module HTTPX::Transcoder
6
6
  module JSON
7
- JSON_REGEX = %r{\bapplication/(?:vnd\.api\+)?json\b}i.freeze
8
-
9
- using HTTPX::RegexpExtensions unless Regexp.method_defined?(:match?)
10
-
11
7
  module_function
12
8
 
9
+ JSON_REGEX = %r{\bapplication/(?:vnd\.api\+|hal\+)?json\b}i.freeze
10
+
13
11
  class Encoder
14
12
  extend Forwardable
15
13
 
@@ -56,5 +54,4 @@ module HTTPX::Transcoder
56
54
  end
57
55
  # rubocop:enable Style/SingleLineMethods
58
56
  end
59
- register "json", JSON
60
57
  end