wreq-rb 0.4.0 → 0.5.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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1922 -397
  3. data/LICENSE +203 -0
  4. data/README.md +47 -16
  5. data/exe/wreq +211 -0
  6. data/ext/wreq_rb/Cargo.toml +4 -6
  7. data/ext/wreq_rb/src/client.rs +145 -41
  8. data/lib/wreq-rb/version.rb +1 -1
  9. data/patches/0001-add-transfer-size-tracking.patch +76 -67
  10. data/vendor/wreq/Cargo.toml +119 -71
  11. data/vendor/wreq/README.md +25 -20
  12. data/vendor/wreq/bench/http1.rs +25 -0
  13. data/vendor/wreq/bench/http1_over_tls.rs +25 -0
  14. data/vendor/wreq/bench/http2.rs +25 -0
  15. data/vendor/wreq/bench/http2_over_tls.rs +25 -0
  16. data/vendor/wreq/bench/support/bench.rs +91 -0
  17. data/vendor/wreq/bench/support/client.rs +217 -0
  18. data/vendor/wreq/bench/support/server.rs +188 -0
  19. data/vendor/wreq/bench/support.rs +56 -0
  20. data/vendor/wreq/examples/cert_store.rs +4 -4
  21. data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
  22. data/vendor/wreq/examples/http2_websocket.rs +2 -2
  23. data/vendor/wreq/examples/keylog.rs +3 -3
  24. data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
  25. data/vendor/wreq/examples/rt.rs +23 -0
  26. data/vendor/wreq/src/client/body.rs +23 -61
  27. data/vendor/wreq/src/client/emulate.rs +119 -0
  28. data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
  29. data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
  30. data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
  31. data/vendor/wreq/src/client/layer/config.rs +27 -6
  32. data/vendor/wreq/src/client/layer/decoder.rs +9 -4
  33. data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
  34. data/vendor/wreq/src/client/layer/redirect.rs +4 -5
  35. data/vendor/wreq/src/client/layer/retry.rs +8 -5
  36. data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
  37. data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
  38. data/vendor/wreq/src/client/layer/timeout.rs +24 -74
  39. data/vendor/wreq/src/client/layer.rs +1 -2
  40. data/vendor/wreq/src/client/multipart.rs +137 -154
  41. data/vendor/wreq/src/client/request.rs +202 -118
  42. data/vendor/wreq/src/client/response.rs +46 -45
  43. data/vendor/wreq/src/client/upgrade.rs +15 -0
  44. data/vendor/wreq/src/client/ws.rs +73 -25
  45. data/vendor/wreq/src/client.rs +1655 -17
  46. data/vendor/wreq/src/config.rs +11 -11
  47. data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
  48. data/vendor/wreq/src/conn/descriptor.rs +143 -0
  49. data/vendor/wreq/src/conn/http.rs +484 -0
  50. data/vendor/wreq/src/conn/net/io.rs +75 -0
  51. data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
  52. data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
  53. data/vendor/wreq/src/conn/net/tcp.rs +561 -0
  54. data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
  55. data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
  56. data/vendor/wreq/src/conn/net/uds.rs +11 -0
  57. data/vendor/wreq/src/conn/net.rs +130 -0
  58. data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
  59. data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
  60. data/vendor/wreq/src/conn/tls_info.rs +47 -0
  61. data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
  62. data/vendor/wreq/src/cookie.rs +302 -142
  63. data/vendor/wreq/src/dns/gai/compio.rs +77 -0
  64. data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
  65. data/vendor/wreq/src/dns/gai.rs +14 -164
  66. data/vendor/wreq/src/dns/hickory.rs +16 -23
  67. data/vendor/wreq/src/dns/resolve.rs +7 -41
  68. data/vendor/wreq/src/dns.rs +90 -7
  69. data/vendor/wreq/src/error.rs +57 -31
  70. data/vendor/wreq/src/ext.rs +25 -0
  71. data/vendor/wreq/src/group.rs +211 -0
  72. data/vendor/wreq/src/header.rs +100 -112
  73. data/vendor/wreq/src/lib.rs +124 -73
  74. data/vendor/wreq/src/proxy.rs +6 -20
  75. data/vendor/wreq/src/redirect.rs +1 -1
  76. data/vendor/wreq/src/rt.rs +208 -0
  77. data/vendor/wreq/src/sync.rs +97 -98
  78. data/vendor/wreq/src/tls/compress.rs +124 -0
  79. data/vendor/wreq/src/tls/conn/ext.rs +54 -45
  80. data/vendor/wreq/src/tls/conn/service.rs +14 -18
  81. data/vendor/wreq/src/tls/conn.rs +169 -241
  82. data/vendor/wreq/src/tls/keylog.rs +68 -5
  83. data/vendor/wreq/src/tls/session.rs +205 -0
  84. data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
  85. data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
  86. data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
  87. data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
  88. data/vendor/wreq/src/tls.rs +489 -25
  89. data/vendor/wreq/src/trace.rs +0 -12
  90. data/vendor/wreq/src/util.rs +1 -1
  91. data/vendor/wreq/tests/badssl.rs +10 -10
  92. data/vendor/wreq/tests/client.rs +3 -9
  93. data/vendor/wreq/tests/cookie.rs +6 -8
  94. data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
  95. data/vendor/wreq/tests/multipart.rs +43 -1
  96. data/vendor/wreq/tests/proxy.rs +1 -1
  97. data/vendor/wreq/tests/support/layer.rs +1 -0
  98. metadata +53 -72
  99. data/vendor/wreq/src/client/conn/conn.rs +0 -231
  100. data/vendor/wreq/src/client/conn/http.rs +0 -1023
  101. data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
  102. data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
  103. data/vendor/wreq/src/client/core/body/length.rs +0 -118
  104. data/vendor/wreq/src/client/core/body.rs +0 -34
  105. data/vendor/wreq/src/client/core/common/buf.rs +0 -149
  106. data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
  107. data/vendor/wreq/src/client/core/common/watch.rs +0 -76
  108. data/vendor/wreq/src/client/core/common.rs +0 -3
  109. data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
  110. data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
  111. data/vendor/wreq/src/client/core/conn.rs +0 -11
  112. data/vendor/wreq/src/client/core/dispatch.rs +0 -299
  113. data/vendor/wreq/src/client/core/error.rs +0 -435
  114. data/vendor/wreq/src/client/core/ext.rs +0 -201
  115. data/vendor/wreq/src/client/core/http1.rs +0 -178
  116. data/vendor/wreq/src/client/core/http2.rs +0 -483
  117. data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
  118. data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
  119. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
  120. data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
  121. data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
  122. data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
  123. data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
  124. data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
  125. data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
  126. data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
  127. data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
  128. data/vendor/wreq/src/client/core/proto.rs +0 -58
  129. data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
  130. data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
  131. data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
  132. data/vendor/wreq/src/client/core/rt.rs +0 -25
  133. data/vendor/wreq/src/client/core/upgrade.rs +0 -267
  134. data/vendor/wreq/src/client/core.rs +0 -16
  135. data/vendor/wreq/src/client/emulation.rs +0 -161
  136. data/vendor/wreq/src/client/http/client/error.rs +0 -142
  137. data/vendor/wreq/src/client/http/client/exec.rs +0 -29
  138. data/vendor/wreq/src/client/http/client/extra.rs +0 -77
  139. data/vendor/wreq/src/client/http/client/util.rs +0 -104
  140. data/vendor/wreq/src/client/http.rs +0 -1629
  141. data/vendor/wreq/src/client/layer/config/options.rs +0 -156
  142. data/vendor/wreq/src/client/layer/cookie.rs +0 -161
  143. data/vendor/wreq/src/hash.rs +0 -143
  144. data/vendor/wreq/src/tls/conn/cache.rs +0 -123
  145. data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
  146. data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
  147. data/vendor/wreq/src/tls/options.rs +0 -464
  148. /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
  149. /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
  150. /data/vendor/wreq/src/{client/conn → conn}/verbose.rs +0 -0
@@ -10,8 +10,10 @@ use magnus::{
10
10
  };
11
11
  use tokio::runtime::Runtime;
12
12
  use tokio_util::sync::CancellationToken;
13
- use wreq::header::{HeaderMap, HeaderName, HeaderValue};
14
- use wreq_util::{Emulation as BrowserEmulation, EmulationOS, EmulationOption};
13
+ use std::net::IpAddr;
14
+ use wreq::header::{HeaderMap, HeaderName, HeaderValue, OrigHeaderMap};
15
+ use wreq::tls::TlsVersion;
16
+ use wreq_util::{Emulation as BrowserEmulation, Platform as EmulationPlatform, Profile as BrowserProfile};
15
17
 
16
18
  use crate::error::{generic_error, to_magnus_error};
17
19
  use crate::response::Response;
@@ -139,35 +141,35 @@ async fn execute_request(req: wreq::RequestBuilder) -> Result<ResponseData, wreq
139
141
  // Emulation helpers
140
142
  // --------------------------------------------------------------------------
141
143
 
142
- /// The default emulation to apply when none is specified.
143
- const DEFAULT_EMULATION: BrowserEmulation = BrowserEmulation::Chrome145;
144
+ /// The default browser profile to apply when none is specified.
145
+ const DEFAULT_EMULATION: BrowserProfile = BrowserProfile::Chrome148;
144
146
 
145
- /// Parse a Ruby string like "chrome_143" into a BrowserEmulation variant.
146
- fn parse_emulation(name: &str) -> Result<BrowserEmulation, magnus::Error> {
147
+ /// Parse a Ruby string like "chrome_143" into a BrowserProfile variant.
148
+ fn parse_emulation(name: &str) -> Result<BrowserProfile, magnus::Error> {
147
149
  let json_val = serde_json::Value::String(name.to_string());
148
- serde_json::from_value::<BrowserEmulation>(json_val)
149
- .map_err(|_| generic_error(format!("unknown emulation: '{}'. Use names like 'chrome_145', 'firefox_147', 'safari_18.5', etc.", name)))
150
+ serde_json::from_value::<BrowserProfile>(json_val)
151
+ .map_err(|_| generic_error(format!("unknown emulation: '{}'. Use names like 'chrome_148', 'firefox_151', 'safari_18.5', etc.", name)))
150
152
  }
151
153
 
152
- /// Parse a Ruby string like "windows" into an EmulationOS variant.
153
- fn parse_emulation_os(name: &str) -> Result<EmulationOS, magnus::Error> {
154
+ /// Parse a Ruby string like "windows" into an EmulationPlatform variant.
155
+ fn parse_emulation_os(name: &str) -> Result<EmulationPlatform, magnus::Error> {
154
156
  let json_val = serde_json::Value::String(name.to_string());
155
- serde_json::from_value::<EmulationOS>(json_val)
157
+ serde_json::from_value::<EmulationPlatform>(json_val)
156
158
  .map_err(|_| generic_error("unknown emulation_os. Use: 'windows', 'macos', 'linux', 'android', 'ios'"))
157
159
  }
158
160
 
159
- /// Build an EmulationOption from an Emulation and an optional OS from the opts hash.
161
+ /// Build a BrowserEmulation from a profile and an optional platform from the opts hash.
160
162
  fn build_emulation_option(
161
- emu: BrowserEmulation,
163
+ profile: BrowserProfile,
162
164
  opts: &RHash,
163
- ) -> Result<EmulationOption, magnus::Error> {
164
- let os = match hash_get_string(opts, "emulation_os")? {
165
- Some(os_name) => parse_emulation_os(&os_name)?,
166
- None => EmulationOS::default(),
165
+ ) -> Result<BrowserEmulation, magnus::Error> {
166
+ let platform = match hash_get_string(opts, "emulation_os")? {
167
+ Some(platform_name) => parse_emulation_os(&platform_name)?,
168
+ None => EmulationPlatform::default(),
167
169
  };
168
- Ok(EmulationOption::builder()
169
- .emulation(emu)
170
- .emulation_os(os)
170
+ Ok(BrowserEmulation::builder()
171
+ .profile(profile)
172
+ .platform(platform)
171
173
  .build())
172
174
  }
173
175
 
@@ -178,6 +180,7 @@ fn build_emulation_option(
178
180
  #[magnus::wrap(class = "Wreq::Client", free_immediately)]
179
181
  struct Client {
180
182
  inner: wreq::Client,
183
+ cancel_token: std::sync::Mutex<CancellationToken>,
181
184
  }
182
185
 
183
186
  impl Client {
@@ -189,9 +192,16 @@ impl Client {
189
192
  Some(RHash::try_convert(args[0])?)
190
193
  };
191
194
 
192
- let mut builder = wreq::Client::builder();
195
+ let mut builder = wreq::Client::builder()
196
+ .retry(wreq::retry::Policy::never());
193
197
 
194
198
  if let Some(opts) = opts {
199
+ // Apply header_order BEFORE emulation so the user's ordering takes precedence
200
+ if let Some(ary) = hash_get_array(&opts, "header_order")? {
201
+ let orig = array_to_orig_header_map(ary)?;
202
+ builder = builder.orig_headers(orig);
203
+ }
204
+
195
205
  if let Some(val) = hash_get_value(&opts, "emulation")? {
196
206
  let ruby = unsafe { Ruby::get_unchecked() };
197
207
  if val.is_kind_of(ruby.class_false_class()) {
@@ -267,11 +277,11 @@ impl Client {
267
277
  }
268
278
 
269
279
  if let Some(v) = hash_get_bool(&opts, "verify_host")? {
270
- builder = builder.verify_hostname(v);
280
+ builder = builder.tls_verify_hostname(v);
271
281
  }
272
282
 
273
283
  if let Some(v) = hash_get_bool(&opts, "verify_cert")? {
274
- builder = builder.cert_verification(v);
284
+ builder = builder.tls_cert_verification(v);
275
285
  }
276
286
 
277
287
  if let Some(true) = hash_get_bool(&opts, "http1_only")? {
@@ -293,12 +303,50 @@ impl Client {
293
303
  if let Some(v) = hash_get_bool(&opts, "zstd")? {
294
304
  builder = builder.zstd(v);
295
305
  }
306
+
307
+ if let Some(v) = hash_get_bool(&opts, "referer")? {
308
+ builder = builder.referer(v);
309
+ }
310
+
311
+ if let Some(n) = hash_get_usize(&opts, "pool_max_idle_per_host")? {
312
+ builder = builder.pool_max_idle_per_host(n);
313
+ }
314
+
315
+ if let Some(n) = hash_get_usize(&opts, "pool_max_size")? {
316
+ builder = builder.pool_max_size(n);
317
+ }
318
+
319
+ if let Some(v) = hash_get_bool(&opts, "tcp_nodelay")? {
320
+ builder = builder.tcp_nodelay(v);
321
+ }
322
+
323
+ if let Some(t) = hash_get_float(&opts, "tcp_keepalive")? {
324
+ builder = builder.tcp_keepalive(Duration::from_secs_f64(t));
325
+ }
326
+
327
+ if let Some(addr_str) = hash_get_string(&opts, "local_address")? {
328
+ let addr: IpAddr = addr_str.parse()
329
+ .map_err(|_| generic_error(format!("invalid IP address: '{}'", addr_str)))?;
330
+ builder = builder.local_address(addr);
331
+ }
332
+
333
+ if let Some(v) = hash_get_bool(&opts, "tls_sni")? {
334
+ builder = builder.tls_sni(v);
335
+ }
336
+
337
+ if let Some(s) = hash_get_string(&opts, "min_tls_version")? {
338
+ builder = builder.tls_min_version(parse_tls_version(&s)?);
339
+ }
340
+
341
+ if let Some(s) = hash_get_string(&opts, "max_tls_version")? {
342
+ builder = builder.tls_max_version(parse_tls_version(&s)?);
343
+ }
296
344
  } else {
297
345
  builder = builder.emulation(DEFAULT_EMULATION);
298
346
  }
299
347
 
300
348
  let client = builder.build().map_err(to_magnus_error)?;
301
- Ok(Client { inner: client })
349
+ Ok(Client { inner: client, cancel_token: std::sync::Mutex::new(CancellationToken::new()) })
302
350
  }
303
351
 
304
352
  /// client.get(url) or client.get(url, opts)
@@ -330,6 +378,18 @@ impl Client {
330
378
  self.execute_method("OPTIONS", args)
331
379
  }
332
380
 
381
+ fn cancel(&self) {
382
+ // Replace the cancel token first so new requests use a fresh token,
383
+ // then cancel the old one to unblock all current in-flight select!s.
384
+ let old_token = {
385
+ let mut guard = self.cancel_token.lock().unwrap_or_else(|e| e.into_inner());
386
+ let old = guard.clone();
387
+ *guard = CancellationToken::new();
388
+ old
389
+ };
390
+ old_token.cancel();
391
+ }
392
+
333
393
  fn execute_method(&self, method_str: &str, args: &[Value]) -> Result<Response, magnus::Error> {
334
394
  let url: String = if args.is_empty() {
335
395
  return Err(generic_error("url is required"));
@@ -353,13 +413,16 @@ impl Client {
353
413
  req = apply_request_options(req, &opts)?;
354
414
  }
355
415
 
416
+ let client_token = self.cancel_token.lock().unwrap_or_else(|e| e.into_inner()).clone();
417
+
356
418
  // Release the GVL so other Ruby threads can run during I/O.
357
419
  let outcome: RequestOutcome = unsafe {
358
- without_gvl(|cancel| {
420
+ without_gvl(|thread_token| {
359
421
  runtime().block_on(async {
360
422
  tokio::select! {
361
423
  biased;
362
- _ = cancel.cancelled() => RequestOutcome::Interrupted,
424
+ _ = thread_token.cancelled() => RequestOutcome::Interrupted,
425
+ _ = client_token.cancelled() => RequestOutcome::Interrupted,
363
426
  res = execute_request(req) => match res {
364
427
  Ok(data) => RequestOutcome::Ok(data),
365
428
  Err(e) => RequestOutcome::Err(e),
@@ -382,6 +445,21 @@ fn apply_request_options(
382
445
  mut req: wreq::RequestBuilder,
383
446
  opts: &RHash,
384
447
  ) -> Result<wreq::RequestBuilder, magnus::Error> {
448
+ if let Some(val) = hash_get_value(opts, "emulation")? {
449
+ let ruby = unsafe { Ruby::get_unchecked() };
450
+ if val.is_kind_of(ruby.class_false_class()) {
451
+ // emulation: false — no per-request emulation override
452
+ } else if val.is_kind_of(ruby.class_true_class()) {
453
+ let opt = build_emulation_option(DEFAULT_EMULATION, opts)?;
454
+ req = req.emulation(opt);
455
+ } else {
456
+ let name: String = TryConvert::try_convert(val)?;
457
+ let emu = parse_emulation(&name)?;
458
+ let opt = build_emulation_option(emu, opts)?;
459
+ req = req.emulation(opt);
460
+ }
461
+ }
462
+
385
463
  if let Some(hdr_hash) = hash_get_hash(opts, "headers")? {
386
464
  let hmap = hash_to_header_map(&hdr_hash)?;
387
465
  req = req.headers(hmap);
@@ -436,21 +514,6 @@ fn apply_request_options(
436
514
  req = req.proxy(proxy);
437
515
  }
438
516
 
439
- if let Some(val) = hash_get_value(opts, "emulation")? {
440
- let ruby = unsafe { Ruby::get_unchecked() };
441
- if val.is_kind_of(ruby.class_false_class()) {
442
- // emulation: false — no per-request emulation override
443
- } else if val.is_kind_of(ruby.class_true_class()) {
444
- let opt = build_emulation_option(DEFAULT_EMULATION, opts)?;
445
- req = req.emulation(opt);
446
- } else {
447
- let name: String = TryConvert::try_convert(val)?;
448
- let emu = parse_emulation(&name)?;
449
- let opt = build_emulation_option(emu, opts)?;
450
- req = req.emulation(opt);
451
- }
452
- }
453
-
454
517
  Ok(req)
455
518
  }
456
519
 
@@ -529,6 +592,25 @@ fn hash_get_bool(hash: &RHash, key: &str) -> Result<Option<bool>, magnus::Error>
529
592
  }
530
593
  }
531
594
 
595
+ fn hash_get_usize(hash: &RHash, key: &str) -> Result<Option<usize>, magnus::Error> {
596
+ match hash_get_value(hash, key)? {
597
+ Some(v) => Ok(Some(TryConvert::try_convert(v)?)),
598
+ None => Ok(None),
599
+ }
600
+ }
601
+
602
+ fn parse_tls_version(s: &str) -> Result<TlsVersion, magnus::Error> {
603
+ match s {
604
+ "tls1.0" | "tls_1_0" | "1.0" => Ok(TlsVersion::TLS_1_0),
605
+ "tls1.1" | "tls_1_1" | "1.1" => Ok(TlsVersion::TLS_1_1),
606
+ "tls1.2" | "tls_1_2" | "1.2" => Ok(TlsVersion::TLS_1_2),
607
+ "tls1.3" | "tls_1_3" | "1.3" => Ok(TlsVersion::TLS_1_3),
608
+ _ => Err(generic_error(format!(
609
+ "unknown TLS version '{}'. Use: 'tls1.2', 'tls1.3'", s
610
+ ))),
611
+ }
612
+ }
613
+
532
614
  fn hash_get_hash(hash: &RHash, key: &str) -> Result<Option<RHash>, magnus::Error> {
533
615
  match hash_get_value(hash, key)? {
534
616
  Some(v) => Ok(Some(RHash::try_convert(v)?)),
@@ -536,6 +618,27 @@ fn hash_get_hash(hash: &RHash, key: &str) -> Result<Option<RHash>, magnus::Error
536
618
  }
537
619
  }
538
620
 
621
+ fn hash_get_array(hash: &RHash, key: &str) -> Result<Option<RArray>, magnus::Error> {
622
+ match hash_get_value(hash, key)? {
623
+ Some(v) => Ok(Some(RArray::try_convert(v)?)),
624
+ None => Ok(None),
625
+ }
626
+ }
627
+
628
+ fn array_to_orig_header_map(ary: RArray) -> Result<OrigHeaderMap, magnus::Error> {
629
+ let mut orig = OrigHeaderMap::with_capacity(ary.len());
630
+ for elem in ary.into_iter() {
631
+ let ruby = unsafe { Ruby::get_unchecked() };
632
+ let name_str: String = if elem.is_kind_of(ruby.class_symbol()) {
633
+ elem.funcall("to_s", ())?
634
+ } else {
635
+ TryConvert::try_convert(elem)?
636
+ };
637
+ orig.insert(name_str);
638
+ }
639
+ Ok(orig)
640
+ }
641
+
539
642
  fn hash_to_header_map(hash: &RHash) -> Result<HeaderMap, magnus::Error> {
540
643
  let mut hmap = HeaderMap::new();
541
644
  hash.foreach(|k: Value, v: Value| {
@@ -589,6 +692,7 @@ pub fn init(_ruby: &magnus::Ruby, module: &magnus::RModule) -> Result<(), magnus
589
692
  client_class.define_method("delete", method!(Client::delete, -1))?;
590
693
  client_class.define_method("head", method!(Client::head, -1))?;
591
694
  client_class.define_method("options", method!(Client::options, -1))?;
695
+ client_class.define_method("cancel", method!(Client::cancel, 0))?;
592
696
 
593
697
  module.define_module_function("get", function!(wreq_get, -1))?;
594
698
  module.define_module_function("post", function!(wreq_post, -1))?;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wreq
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.1"
5
5
  end
@@ -1,60 +1,51 @@
1
- diff --git a/src/client/http.rs b/src/client/http.rs
2
- index 10be0c83..462c77d0 100644
3
- --- a/src/client/http.rs
4
- +++ b/src/client/http.rs
5
- @@ -33,6 +33,7 @@ use self::future::Pending;
6
- feature = "deflate",
7
- ))]
8
- use super::layer::decoder::{AcceptEncoding, DecompressionLayer};
9
- +use super::layer::transfer_size::{CountingBody, TransferSizeLayer, TransferSizeService};
10
- #[cfg(feature = "ws")]
11
- use super::ws::WebSocketRequestBuilder;
12
- use super::{
13
- @@ -104,7 +105,7 @@ type Decompression<T> = super::layer::decoder::Decompression<T>;
14
- feature = "brotli",
15
- feature = "deflate"
16
- ))]
17
- -type ResponseBody = TimeoutBody<tower_http::decompression::DecompressionBody<Incoming>>;
18
- +type ResponseBody = TimeoutBody<tower_http::decompression::DecompressionBody<CountingBody<Incoming>>>;
1
+ diff --git a/src/client.rs b/src/client.rs
2
+ index 4abe25d8..76e709a8 100644
3
+ --- a/src/client.rs
4
+ +++ b/src/client.rs
5
+ @@ -49,6 +49,7 @@ use self::{
6
+ redirect::{FollowRedirect, FollowRedirectLayer},
7
+ retry::RetryPolicy,
8
+ timeout::{Timeout, TimeoutLayer, TimeoutOptions, body::TimeoutBody},
9
+ + transfer_size::{CountingBody, TransferSizeLayer, TransferSizeService},
10
+ },
11
+ request::{Request, RequestBuilder},
12
+ response::Response,
13
+ @@ -115,25 +116,30 @@ type MaybeDecompressionBody<T> = tower_http::decompression::DecompressionBody<T>
14
+ type ClientService = Timeout<
15
+ ConfigService<
16
+ MaybeDecompression<
17
+ - Retry<RetryPolicy, FollowRedirect<HttpClient<Connector, Body>, FollowRedirectPolicy>>,
18
+ + TransferSizeService<
19
+ + Retry<
20
+ + RetryPolicy,
21
+ + FollowRedirect<HttpClient<Connector, Body>, FollowRedirectPolicy>,
22
+ + >,
23
+ + >,
24
+ >,
25
+ >,
26
+ >;
19
27
 
20
- /// Response body type with timeout only (no compression features).
21
- #[cfg(not(any(
22
- @@ -113,23 +114,25 @@ type ResponseBody = TimeoutBody<tower_http::decompression::DecompressionBody<Inc
23
- feature = "brotli",
24
- feature = "deflate"
25
- )))]
26
- -type ResponseBody = TimeoutBody<Incoming>;
27
- +type ResponseBody = TimeoutBody<CountingBody<Incoming>>;
28
+ type BoxedClientService = BoxCloneSyncService<
29
+ http::Request<Body>,
30
+ - http::Response<TimeoutBody<MaybeDecompressionBody<Incoming>>>,
31
+ + http::Response<TimeoutBody<MaybeDecompressionBody<CountingBody<Incoming>>>>,
32
+ BoxError,
33
+ >;
28
34
 
29
- /// The complete HTTP client service stack with all middleware layers.
30
- type ClientService = Timeout<
31
- ResponseBodyTimeout<
32
- ConfigService<
33
- Decompression<
34
- - Retry<
35
- - RetryPolicy,
36
- - FollowRedirect<
37
- - CookieService<
38
- - MapErr<
39
- - HttpClient<Connector, Body>,
40
- - fn(client::error::Error) -> BoxError,
41
- + TransferSizeService<
42
- + Retry<
43
- + RetryPolicy,
44
- + FollowRedirect<
45
- + CookieService<
46
- + MapErr<
47
- + HttpClient<Connector, Body>,
48
- + fn(client::error::Error) -> BoxError,
49
- + >,
50
- >,
51
- + FollowRedirectPolicy,
52
- >,
53
- - FollowRedirectPolicy,
54
- >,
55
- >,
56
- >,
57
- @@ -582,6 +585,10 @@ impl ClientBuilder {
35
+ type BoxedClientServiceLayer = BoxCloneSyncServiceLayer<
36
+ BoxCloneSyncService<
37
+ http::Request<Body>,
38
+ - http::Response<MaybeDecompressionBody<Incoming>>,
39
+ + http::Response<MaybeDecompressionBody<CountingBody<Incoming>>>,
40
+ BoxError,
41
+ >,
42
+ http::Request<Body>,
43
+ - http::Response<MaybeDecompressionBody<Incoming>>,
44
+ + http::Response<MaybeDecompressionBody<CountingBody<Incoming>>>,
45
+ BoxError,
46
+ >;
47
+
48
+ @@ -593,6 +599,10 @@ impl ClientBuilder {
58
49
  })
59
50
  .service(service);
60
51
 
@@ -65,18 +56,36 @@ index 10be0c83..462c77d0 100644
65
56
  #[cfg(any(
66
57
  feature = "gzip",
67
58
  feature = "zstd",
59
+ @@ -1585,7 +1595,7 @@ impl ClientBuilder {
60
+ L: Layer<
61
+ BoxCloneSyncService<
62
+ http::Request<Body>,
63
+ - http::Response<MaybeDecompressionBody<Incoming>>,
64
+ + http::Response<MaybeDecompressionBody<CountingBody<Incoming>>>,
65
+ BoxError,
66
+ >,
67
+ > + Clone
68
+ @@ -1594,7 +1604,7 @@ impl ClientBuilder {
69
+ + 'static,
70
+ L::Service: Service<
71
+ http::Request<Body>,
72
+ - Response = http::Response<MaybeDecompressionBody<Incoming>>,
73
+ + Response = http::Response<MaybeDecompressionBody<CountingBody<Incoming>>>,
74
+ Error = BoxError,
75
+ > + Clone
76
+ + Send
68
77
  diff --git a/src/client/layer.rs b/src/client/layer.rs
69
- index 05bb533f..cb0b0866 100644
78
+ index 5c03ef4d..fe37cff0 100644
70
79
  --- a/src/client/layer.rs
71
80
  +++ b/src/client/layer.rs
72
- @@ -13,3 +13,4 @@ pub mod decoder;
81
+ @@ -12,3 +12,4 @@ pub mod decoder;
73
82
  pub mod redirect;
74
83
  pub mod retry;
75
84
  pub mod timeout;
76
85
  +pub mod transfer_size;
77
86
  diff --git a/src/client/layer/transfer_size.rs b/src/client/layer/transfer_size.rs
78
87
  new file mode 100644
79
- index 00000000..7e8a4390
88
+ index 00000000..f155ec8d
80
89
  --- /dev/null
81
90
  +++ b/src/client/layer/transfer_size.rs
82
91
  @@ -0,0 +1,176 @@
@@ -91,8 +100,8 @@ index 00000000..7e8a4390
91
100
  +use std::{
92
101
  + pin::Pin,
93
102
  + sync::{
94
- + atomic::{AtomicU64, Ordering},
95
103
  + Arc,
104
+ + atomic::{AtomicU64, Ordering},
96
105
  + },
97
106
  + task::{Context, Poll},
98
107
  +};
@@ -257,18 +266,18 @@ index 00000000..7e8a4390
257
266
  + }
258
267
  +}
259
268
  diff --git a/src/client/response.rs b/src/client/response.rs
260
- index d98859b9..16ed35c3 100644
269
+ index 3d3532af..ed478804 100644
261
270
  --- a/src/client/response.rs
262
271
  +++ b/src/client/response.rs
263
- @@ -20,6 +20,7 @@ use serde::de::DeserializeOwned;
264
- use super::{
265
- conn::HttpInfo,
266
- core::{ext::ReasonPhrase, upgrade},
267
- + layer::transfer_size::TransferSizeHandle,
268
- };
272
+ @@ -18,6 +18,7 @@ use mime::Mime;
273
+ use serde::de::DeserializeOwned;
274
+ use wreq_proto::ext::ReasonPhrase;
275
+
276
+ +use super::layer::transfer_size::TransferSizeHandle;
269
277
  #[cfg(feature = "cookies")]
270
278
  use crate::cookie;
271
- @@ -91,6 +92,21 @@ impl Response {
279
+ use crate::{
280
+ @@ -97,6 +98,21 @@ impl Response {
272
281
  HttpBody::size_hint(self.res.body()).exact()
273
282
  }
274
283