wreq 1.2.11 → 1.2.13

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.
data/src/client.rs CHANGED
@@ -11,7 +11,7 @@ use magnus::{Module, Object, RModule, Ruby, TryConvert, Value, function, method,
11
11
  use wreq::Proxy;
12
12
 
13
13
  use crate::{
14
- arch::{SUPPORTS_INTERFACE, SUPPORTS_TCP_USER_TIMEOUT},
14
+ arch::{ProcessLocal, SUPPORTS_INTERFACE, SUPPORTS_TCP_USER_TIMEOUT},
15
15
  client::{req::execute_request, resp::Response},
16
16
  cookie::Jar,
17
17
  emulate::Emulation,
@@ -50,7 +50,7 @@ struct Builder {
50
50
  cookie_store: Option<bool>,
51
51
  /// Whether to use cookie store provider.
52
52
  #[serde(default)]
53
- cookie_provider: NativeOption<Jar>,
53
+ cookie_provider: NativeOption<Obj<Jar>>,
54
54
 
55
55
  // ========= Timeout options =========
56
56
  /// The timeout to use for the client. (in seconds)
@@ -94,6 +94,8 @@ struct Builder {
94
94
  // ========= TLS options =========
95
95
  /// Whether to verify TLS certificates.
96
96
  verify: Option<bool>,
97
+ /// Whether to retain peer certificate data on responses.
98
+ tls_info: Option<bool>,
97
99
 
98
100
  // ========= Network options =========
99
101
  /// Whether to disable the proxy for the client.
@@ -118,9 +120,8 @@ struct Builder {
118
120
  zstd: Option<bool>,
119
121
  }
120
122
 
121
- #[derive(Clone)]
122
123
  #[magnus::wrap(class = "Wreq::Client", free_immediately, size)]
123
- pub struct Client(wreq::Client);
124
+ pub struct Client(ProcessLocal<wreq::Client>);
124
125
 
125
126
  // ===== impl Builder =====
126
127
 
@@ -168,12 +169,7 @@ impl Builder {
168
169
  extract_native_option!(options, builder, user_agent);
169
170
  extract_native_option!(options, builder, headers);
170
171
  extract_native_option!(options, builder, orig_headers);
171
- extract_native_option!(
172
- options,
173
- builder,
174
- cookie_provider,
175
- Obj<Jar> => |value| (*value).clone()
176
- );
172
+ extract_native_option!(options, builder, cookie_provider);
177
173
  builder
178
174
  .proxy
179
175
  .set(Extractor::<Proxy>::try_convert(options.as_value())?.into_inner());
@@ -190,14 +186,16 @@ impl Client {
190
186
  /// # Errors
191
187
  ///
192
188
  /// Returns Ruby configuration errors from [`Builder::from_options`] or the
193
- /// native fallible client builder. Extra positional arguments return
194
- /// `ArgumentError`.
189
+ /// native fallible client builder. An inherited cookie provider returns
190
+ /// `Wreq::ForkError`, and extra positional arguments return `ArgumentError`.
195
191
  pub fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, magnus::Error> {
196
192
  Options::from_args(ruby, args, "client")?
197
193
  .map(Builder::from_options)
198
194
  .transpose()
199
195
  .map(Option::unwrap_or_default)
200
196
  .and_then(|params| Self::build(ruby, params))
197
+ .map(ProcessLocal::new)
198
+ .map(Self)
201
199
  }
202
200
 
203
201
  /// Build the default client through the same fallible path as `new`.
@@ -206,7 +204,7 @@ impl Client {
206
204
  ///
207
205
  /// Returns `Wreq::BuilderError`, `Wreq::TlsError`, or another mapped native
208
206
  /// initialization error without unwinding through Ruby.
209
- pub(crate) fn default_client(ruby: &Ruby) -> Result<Self, magnus::Error> {
207
+ pub(crate) fn default_client(ruby: &Ruby) -> Result<wreq::Client, magnus::Error> {
210
208
  Self::build(ruby, Builder::default())
211
209
  }
212
210
 
@@ -214,8 +212,15 @@ impl Client {
214
212
  ///
215
213
  /// # Errors
216
214
  ///
217
- /// Maps native build failures only after the GVL has been reacquired.
218
- fn build(ruby: &Ruby, mut params: Builder) -> Result<Self, magnus::Error> {
215
+ /// Returns `Wreq::ForkError` if the cookie provider belongs to a parent
216
+ /// process. Native build failures are mapped only after the GVL has been
217
+ /// reacquired.
218
+ fn build(ruby: &Ruby, mut params: Builder) -> Result<wreq::Client, magnus::Error> {
219
+ let mut cookie_provider = params
220
+ .cookie_provider
221
+ .take()
222
+ .map(|jar| jar.clone_store(ruby))
223
+ .transpose()?;
219
224
  let result = gvl::nogvl(|| {
220
225
  let mut builder = wreq::Client::builder();
221
226
 
@@ -259,12 +264,7 @@ impl Client {
259
264
 
260
265
  // Cookie options.
261
266
  apply_option!(set_if_some, builder, params.cookie_store, cookie_store);
262
- apply_option!(
263
- set_if_some_inner,
264
- builder,
265
- params.cookie_provider,
266
- cookie_provider
267
- );
267
+ apply_option!(set_if_some, builder, cookie_provider, cookie_provider);
268
268
 
269
269
  // TCP options.
270
270
  apply_option!(
@@ -349,6 +349,7 @@ impl Client {
349
349
 
350
350
  // TLS options.
351
351
  apply_option!(set_if_some, builder, params.verify, tls_cert_verification);
352
+ apply_option!(set_if_some, builder, params.tls_info, tls_info);
352
353
 
353
354
  // Network options.
354
355
  apply_option!(set_if_some, builder, params.proxy, proxy);
@@ -374,12 +375,22 @@ impl Client {
374
375
  apply_option!(set_if_some, builder, params.deflate, deflate);
375
376
  apply_option!(set_if_some, builder, params.zstd, zstd);
376
377
 
377
- builder.build().map(Client)
378
+ builder.build()
378
379
  });
379
380
 
380
381
  // Ruby exceptions must be created after the GVL has been reacquired.
381
382
  result.map_err(|err| wreq_error(ruby, err))
382
383
  }
384
+
385
+ /// Clone the native client handle in the process that created it.
386
+ ///
387
+ /// # Errors
388
+ ///
389
+ /// Returns `Wreq::ForkError` when the client was inherited from a parent
390
+ /// process.
391
+ fn native_client(&self, ruby: &Ruby) -> Result<wreq::Client, magnus::Error> {
392
+ self.0.get(ruby).cloned()
393
+ }
383
394
  }
384
395
 
385
396
  impl Client {
@@ -391,9 +402,9 @@ impl Client {
391
402
  ruby: &Ruby,
392
403
  args: &[Value],
393
404
  ) -> Result<Response, magnus::Error> {
394
- let ((method, url), request) = extract_request!(args, (Obj<Method>, String));
405
+ let ((method, url), request) = extract_request!(ruby, args, (Obj<Method>, String));
395
406
  let client = Self::default_client(ruby)?;
396
- execute_request(ruby, client.0, *method, url, request)
407
+ execute_request(ruby, client, *method, url, request)
397
408
  }
398
409
 
399
410
  /// Send a request with `method` through a newly built default client.
@@ -405,72 +416,120 @@ impl Client {
405
416
  method: Method,
406
417
  args: &[Value],
407
418
  ) -> Result<Response, magnus::Error> {
408
- let ((url,), request) = extract_request!(args, (String,));
419
+ let ((url,), request) = extract_request!(ruby, args, (String,));
409
420
  let client = Self::default_client(ruby)?;
410
- execute_request(ruby, client.0, method, url, request)
421
+ execute_request(ruby, client, method, url, request)
411
422
  }
412
423
 
413
424
  /// Send a HTTP request.
414
425
  #[inline]
415
426
  pub fn request(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
416
- let ((method, url), request) = extract_request!(args, (Obj<Method>, String));
417
- execute_request(ruby, rb_self.0.clone(), *method, url, request)
427
+ let ((method, url), request) = extract_request!(ruby, args, (Obj<Method>, String));
428
+ execute_request(ruby, rb_self.native_client(ruby)?, *method, url, request)
418
429
  }
419
430
 
420
431
  /// Send a GET request.
421
432
  #[inline]
422
433
  pub fn get(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
423
- let ((url,), request) = extract_request!(args, (String,));
424
- execute_request(ruby, rb_self.0.clone(), Method::GET, url, request)
434
+ let ((url,), request) = extract_request!(ruby, args, (String,));
435
+ execute_request(
436
+ ruby,
437
+ rb_self.native_client(ruby)?,
438
+ Method::GET,
439
+ url,
440
+ request,
441
+ )
425
442
  }
426
443
 
427
444
  /// Send a POST request.
428
445
  #[inline]
429
446
  pub fn post(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
430
- let ((url,), request) = extract_request!(args, (String,));
431
- execute_request(ruby, rb_self.0.clone(), Method::POST, url, request)
447
+ let ((url,), request) = extract_request!(ruby, args, (String,));
448
+ execute_request(
449
+ ruby,
450
+ rb_self.native_client(ruby)?,
451
+ Method::POST,
452
+ url,
453
+ request,
454
+ )
432
455
  }
433
456
 
434
457
  /// Send a PUT request.
435
458
  #[inline]
436
459
  pub fn put(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
437
- let ((url,), request) = extract_request!(args, (String,));
438
- execute_request(ruby, rb_self.0.clone(), Method::PUT, url, request)
460
+ let ((url,), request) = extract_request!(ruby, args, (String,));
461
+ execute_request(
462
+ ruby,
463
+ rb_self.native_client(ruby)?,
464
+ Method::PUT,
465
+ url,
466
+ request,
467
+ )
439
468
  }
440
469
 
441
470
  /// Send a DELETE request.
442
471
  #[inline]
443
472
  pub fn delete(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
444
- let ((url,), request) = extract_request!(args, (String,));
445
- execute_request(ruby, rb_self.0.clone(), Method::DELETE, url, request)
473
+ let ((url,), request) = extract_request!(ruby, args, (String,));
474
+ execute_request(
475
+ ruby,
476
+ rb_self.native_client(ruby)?,
477
+ Method::DELETE,
478
+ url,
479
+ request,
480
+ )
446
481
  }
447
482
 
448
483
  /// Send a HEAD request.
449
484
  #[inline]
450
485
  pub fn head(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
451
- let ((url,), request) = extract_request!(args, (String,));
452
- execute_request(ruby, rb_self.0.clone(), Method::HEAD, url, request)
486
+ let ((url,), request) = extract_request!(ruby, args, (String,));
487
+ execute_request(
488
+ ruby,
489
+ rb_self.native_client(ruby)?,
490
+ Method::HEAD,
491
+ url,
492
+ request,
493
+ )
453
494
  }
454
495
 
455
496
  /// Send an OPTIONS request.
456
497
  #[inline]
457
498
  pub fn options(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
458
- let ((url,), request) = extract_request!(args, (String,));
459
- execute_request(ruby, rb_self.0.clone(), Method::OPTIONS, url, request)
499
+ let ((url,), request) = extract_request!(ruby, args, (String,));
500
+ execute_request(
501
+ ruby,
502
+ rb_self.native_client(ruby)?,
503
+ Method::OPTIONS,
504
+ url,
505
+ request,
506
+ )
460
507
  }
461
508
 
462
509
  /// Send a TRACE request.
463
510
  #[inline]
464
511
  pub fn trace(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
465
- let ((url,), request) = extract_request!(args, (String,));
466
- execute_request(ruby, rb_self.0.clone(), Method::TRACE, url, request)
512
+ let ((url,), request) = extract_request!(ruby, args, (String,));
513
+ execute_request(
514
+ ruby,
515
+ rb_self.native_client(ruby)?,
516
+ Method::TRACE,
517
+ url,
518
+ request,
519
+ )
467
520
  }
468
521
 
469
522
  /// Send a PATCH request.
470
523
  #[inline]
471
524
  pub fn patch(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<Response, magnus::Error> {
472
- let ((url,), request) = extract_request!(args, (String,));
473
- execute_request(ruby, rb_self.0.clone(), Method::PATCH, url, request)
525
+ let ((url,), request) = extract_request!(ruby, args, (String,));
526
+ execute_request(
527
+ ruby,
528
+ rb_self.native_client(ruby)?,
529
+ Method::PATCH,
530
+ url,
531
+ request,
532
+ )
474
533
  }
475
534
  }
476
535
 
data/src/cookie.rs CHANGED
@@ -18,6 +18,7 @@ use magnus::{
18
18
  use wreq::header::{self, HeaderMap, HeaderValue};
19
19
 
20
20
  use crate::{
21
+ arch::ProcessLocal,
21
22
  error::{header_value_error, type_error},
22
23
  gvl,
23
24
  options::{NativeOption, Options},
@@ -78,9 +79,14 @@ struct Builder {
78
79
  /// A cookie jar that can be shared with a Ruby `Wreq::Client`.
79
80
  ///
80
81
  /// Pass a populated jar as the client's `cookie_provider` option.
81
- #[derive(Clone, Default)]
82
82
  #[magnus::wrap(class = "Wreq::Jar", free_immediately, size)]
83
- pub struct Jar(pub Arc<wreq::cookie::Jar>);
83
+ pub struct Jar(ProcessLocal<Arc<wreq::cookie::Jar>>);
84
+
85
+ impl Default for Jar {
86
+ fn default() -> Self {
87
+ Self(ProcessLocal::new(Arc::new(wreq::cookie::Jar::default())))
88
+ }
89
+ }
84
90
 
85
91
  // ===== impl Builder =====
86
92
 
@@ -309,13 +315,18 @@ impl TryConvert for Cookies {
309
315
  impl Jar {
310
316
  /// Create a new [`Jar`] with an empty cookie store.
311
317
  pub fn new() -> Self {
312
- Self(Arc::new(wreq::cookie::Jar::default()))
318
+ Self::default()
313
319
  }
314
320
 
315
321
  /// Get all cookies.
322
+ ///
323
+ /// # Errors
324
+ ///
325
+ /// Returns `Wreq::ForkError` when the jar belongs to a parent process.
316
326
  pub fn get_all(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
317
327
  let cookies: Vec<Cookie> = rb_self
318
328
  .0
329
+ .get(ruby)?
319
330
  .get_all()
320
331
  .map(RawCookie::from)
321
332
  .map(Cookie)
@@ -331,28 +342,53 @@ impl Jar {
331
342
  ///
332
343
  /// # Errors
333
344
  ///
334
- /// Returns `TypeError` when `cookie` is neither a [`Cookie`] nor a String.
345
+ /// Returns `Wreq::ForkError` when the jar belongs to a parent process, or
346
+ /// `TypeError` when `cookie` is neither a [`Cookie`] nor a String.
335
347
  pub fn add(&self, cookie: Value, url: String) -> Result<(), Error> {
348
+ let ruby = Ruby::get_with(cookie);
349
+ let jar = self.0.get(&ruby)?;
350
+
336
351
  if let Ok(cookie) = Obj::<Cookie>::try_convert(cookie) {
337
- gvl::nogvl(|| self.0.add(cookie.clone_for_jar(), &url));
352
+ gvl::nogvl(|| jar.add(cookie.clone_for_jar(), &url));
338
353
  return Ok(());
339
354
  }
340
355
 
341
- let ruby = Ruby::get_with(cookie);
342
356
  let cookie = String::try_convert(cookie)
343
357
  .map_err(|_| type_error(&ruby, "cookie must be a Wreq::Cookie or String"))?;
344
- gvl::nogvl(|| self.0.add(cookie.as_ref(), &url));
358
+ gvl::nogvl(|| jar.add(cookie.as_ref(), &url));
345
359
  Ok(())
346
360
  }
347
361
 
348
362
  /// Remove a cookie from this jar by name and URL.
349
- pub fn remove(&self, name: String, url: String) {
350
- gvl::nogvl(|| self.0.remove(name, &url))
363
+ ///
364
+ /// # Errors
365
+ ///
366
+ /// Returns `Wreq::ForkError` when the jar belongs to a parent process.
367
+ pub fn remove(ruby: &Ruby, rb_self: &Self, name: String, url: String) -> Result<(), Error> {
368
+ let jar = rb_self.0.get(ruby)?;
369
+ gvl::nogvl(|| jar.remove(name, &url));
370
+ Ok(())
351
371
  }
352
372
 
353
373
  /// Clear all cookies in this jar.
354
- pub fn clear(&self) {
355
- gvl::nogvl(|| self.0.clear())
374
+ ///
375
+ /// # Errors
376
+ ///
377
+ /// Returns `Wreq::ForkError` when the jar belongs to a parent process.
378
+ pub fn clear(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
379
+ let jar = rb_self.0.get(ruby)?;
380
+ gvl::nogvl(|| jar.clear());
381
+ Ok(())
382
+ }
383
+
384
+ /// Clone the shared native store in the process that created this jar.
385
+ ///
386
+ /// # Errors
387
+ ///
388
+ /// Returns `Wreq::ForkError` when the jar was inherited from a parent
389
+ /// process.
390
+ pub(crate) fn clone_store(&self, ruby: &Ruby) -> Result<Arc<wreq::cookie::Jar>, Error> {
391
+ self.0.get(ruby).cloned()
356
392
  }
357
393
  }
358
394
 
data/src/error.rs CHANGED
@@ -56,6 +56,7 @@ macro_rules! map_wreq_error {
56
56
 
57
57
  // System-level and runtime errors
58
58
  define_exception!(MEMORY, "MemoryError", exception_runtime_error);
59
+ define_exception!(FORK_ERROR, "ForkError", exception_runtime_error);
59
60
 
60
61
  // Network connection errors
61
62
  define_exception!(CONNECTION_ERROR, "ConnectionError", exception_runtime_error);
@@ -98,6 +99,26 @@ pub fn interrupt_error(ruby: &Ruby) -> MagnusError {
98
99
  MagnusError::new(ruby.get_inner(&INTERRUPT_ERROR), "request interrupted")
99
100
  }
100
101
 
102
+ /// Build `Wreq::ForkError` without touching inherited native state.
103
+ #[cfg(unix)]
104
+ pub fn fork_error(ruby: &Ruby, owner_pid: u32, current_pid: u32) -> MagnusError {
105
+ MagnusError::new(
106
+ ruby.get_inner(&FORK_ERROR),
107
+ format!(
108
+ "wreq-ruby native state was created in process {owner_pid} and cannot be used after fork in process {current_pid}"
109
+ ),
110
+ )
111
+ }
112
+
113
+ /// Map a failed process-fork handler registration to `Wreq::ForkError`.
114
+ #[cfg(unix)]
115
+ pub fn fork_handler_error(ruby: &Ruby, err: &std::io::Error) -> MagnusError {
116
+ MagnusError::new(
117
+ ruby.get_inner(&FORK_ERROR),
118
+ format!("failed to initialize process fork tracking: {err}"),
119
+ )
120
+ }
121
+
101
122
  /// Map a Tokio runtime initialization failure to `Wreq::BuilderError`.
102
123
  pub fn runtime_initialization_error(ruby: &Ruby, err: &std::io::Error) -> MagnusError {
103
124
  MagnusError::new(
@@ -239,6 +260,13 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), MagnusError> {
239
260
  "MemoryError",
240
261
  exception_runtime_error
241
262
  );
263
+ initialize_exception!(
264
+ ruby,
265
+ gem_module,
266
+ FORK_ERROR,
267
+ "ForkError",
268
+ exception_runtime_error
269
+ );
242
270
  initialize_exception!(
243
271
  ruby,
244
272
  gem_module,
data/src/lib.rs CHANGED
@@ -15,6 +15,7 @@ mod http;
15
15
  mod options;
16
16
  mod rt;
17
17
  mod serde;
18
+ mod tls;
18
19
 
19
20
  use magnus::{Error, Module, Ruby, Value};
20
21
 
@@ -85,6 +86,7 @@ pub fn patch(ruby: &Ruby, args: &[Value]) -> Result<Response, magnus::Error> {
85
86
  fn init(ruby: &Ruby) -> Result<(), Error> {
86
87
  let gem_module = ruby.define_module(RUBY_MODULE_NAME)?;
87
88
  gem_module.const_set("VERSION", VERSION)?;
89
+ error::include(ruby, &gem_module)?;
88
90
  gem_module.define_module_function("request", magnus::function!(request, -1))?;
89
91
  gem_module.define_module_function("get", magnus::function!(get, -1))?;
90
92
  gem_module.define_module_function("post", magnus::function!(post, -1))?;
@@ -97,8 +99,8 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
97
99
  http::include(ruby, &gem_module)?;
98
100
  header::include(ruby, &gem_module)?;
99
101
  cookie::include(ruby, &gem_module)?;
102
+ tls::include(ruby, &gem_module)?;
100
103
  client::include(ruby, &gem_module)?;
101
104
  emulate::include(ruby, &gem_module)?;
102
- error::include(ruby, &gem_module)?;
103
105
  Ok(())
104
106
  }
data/src/macros.rs CHANGED
@@ -148,11 +148,10 @@ macro_rules! define_ruby_enum {
148
148
  }
149
149
 
150
150
  macro_rules! extract_request {
151
- ($args:expr, $required:ty) => {{
151
+ ($ruby:expr, $args:expr, $required:ty) => {{
152
152
  let args = magnus::scan_args::scan_args::<$required, (), (), (), magnus::RHash, ()>($args)?;
153
153
  let required = args.required;
154
- let ruby = magnus::Ruby::get_with(args.keywords);
155
- let request = crate::client::req::Request::new(&ruby, args.keywords)?;
154
+ let request = crate::client::req::Request::new($ruby, args.keywords)?;
156
155
  (required, request)
157
156
  }};
158
157
  }
data/src/rt.rs CHANGED
@@ -1,57 +1,78 @@
1
- use std::sync::LazyLock;
1
+ use std::{io, sync::OnceLock};
2
2
 
3
3
  use magnus::Ruby;
4
- use tokio::runtime::{Builder, Runtime};
4
+ use tokio::runtime::{Builder, Runtime as TokioRuntime};
5
5
 
6
6
  use crate::{
7
7
  error::{interrupt_error, runtime_initialization_error},
8
8
  gvl,
9
9
  };
10
10
 
11
+ #[cfg(unix)]
12
+ use crate::{
13
+ arch,
14
+ error::{fork_error, fork_handler_error},
15
+ };
16
+
11
17
  /// Initialize the global runtime lazily and preserve failures for Ruby.
12
- static RUNTIME: LazyLock<Result<Runtime, std::io::Error>> = LazyLock::new(|| {
13
- let mut builder = Builder::new_multi_thread();
18
+ static RUNTIME: OnceLock<Result<TokioRuntime, io::Error>> = OnceLock::new();
14
19
 
15
- builder.enable_all().build()
16
- });
20
+ /// Reject a child process that inherited an initialized native runtime.
21
+ ///
22
+ /// # Errors
23
+ ///
24
+ /// Returns `Wreq::ForkError` when the global runtime belongs to the parent
25
+ /// process.
26
+ fn ensure_runtime_owner(ruby: &Ruby) -> Result<(), magnus::Error> {
27
+ #[cfg(unix)]
28
+ if let Some((owner_pid, current_pid)) = arch::forked_process_ids() {
29
+ return Err(fork_error(ruby, owner_pid, current_pid));
30
+ }
31
+
32
+ #[cfg(not(unix))]
33
+ let _ = ruby;
17
34
 
18
- enum BlockOnError<E> {
19
- Interrupted,
20
- Future(E),
35
+ Ok(())
21
36
  }
22
37
 
23
- /// Block on a future to completion on the global Tokio runtime.
38
+ /// Block on a future to completion on the current process's global Tokio runtime.
24
39
  ///
25
40
  /// The future runs without Ruby's GVL, so it must not construct Ruby objects or
26
- /// Ruby exceptions. Convert Rust errors back into Ruby errors after the GVL has
27
- /// been reacquired.
41
+ /// Ruby exceptions. Its output is returned unchanged. If that output is a
42
+ /// `Result`, convert its error after this function returns and reacquires the
43
+ /// GVL.
28
44
  ///
29
45
  /// # Errors
30
46
  ///
31
- /// Returns `Wreq::BuilderError` if the Tokio runtime cannot be initialized,
32
- /// `Wreq::InterruptError` if Ruby interrupts the request, or the error produced
33
- /// by `map_err` if the future fails.
34
- pub fn try_block_on<F, T, E, M>(ruby: &Ruby, future: F, map_err: M) -> Result<T, magnus::Error>
47
+ /// Returns `Wreq::ForkError` if the runtime belongs to a parent process,
48
+ /// `Wreq::BuilderError` if the Tokio runtime cannot be initialized,
49
+ /// or `Wreq::InterruptError` if Ruby interrupts the operation.
50
+ pub(crate) fn block_on<F>(ruby: &Ruby, future: F) -> Result<F::Output, magnus::Error>
35
51
  where
36
- F: Future<Output = Result<T, E>>,
37
- M: FnOnce(&Ruby, E) -> magnus::Error,
52
+ F: Future,
38
53
  {
54
+ // Install fork tracking at the same point as the lazy runtime. Loading the
55
+ // extension alone must not claim the runtime for the parent process.
56
+ #[cfg(unix)]
57
+ arch::initialize_fork_tracking().map_err(|err| fork_handler_error(ruby, &err))?;
58
+
59
+ ensure_runtime_owner(ruby)?;
39
60
  let runtime = RUNTIME
61
+ .get_or_init(|| {
62
+ let mut builder = Builder::new_multi_thread();
63
+ builder.enable_all().build()
64
+ })
40
65
  .as_ref()
41
66
  .map_err(|err| runtime_initialization_error(ruby, err))?;
42
67
  let result = gvl::nogvl_cancellable(|flag| {
43
68
  runtime.block_on(async move {
44
69
  tokio::select! {
45
70
  biased;
46
- _ = flag.cancelled() => Err(BlockOnError::Interrupted),
47
- result = future => result.map_err(BlockOnError::Future),
71
+ _ = flag.cancelled() => None,
72
+ result = future => Some(result),
48
73
  }
49
74
  })
50
75
  });
51
76
 
52
- match result {
53
- Ok(value) => Ok(value),
54
- Err(BlockOnError::Interrupted) => Err(interrupt_error(ruby)),
55
- Err(BlockOnError::Future(err)) => Err(map_err(ruby, err)),
56
- }
77
+ result.ok_or_else(|| interrupt_error(ruby))
57
78
  }
data/src/tls.rs ADDED
@@ -0,0 +1,51 @@
1
+ //! Ruby wrappers for TLS metadata attached to a response.
2
+ //!
3
+ //! Certificates use the DER encoding described by the X.509 profile in
4
+ //! [RFC 5280 section 4.1](https://www.rfc-editor.org/rfc/rfc5280#section-4.1).
5
+
6
+ use magnus::{Error, Module, RArray, RModule, RString, Ruby, value::ReprValue};
7
+
8
+ /// Read-only Ruby wrapper around [`wreq::tls::TlsInfo`].
9
+ ///
10
+ /// The native value keeps certificate bytes alive independently of the response
11
+ /// body. Its `Bytes` buffers are cheap to clone, while accessors copy the data
12
+ /// into Ruby-owned Strings so callers cannot mutate the stored metadata.
13
+ #[derive(Clone)]
14
+ #[magnus::wrap(class = "Wreq::TlsInfo", free_immediately, size)]
15
+ pub(crate) struct TlsInfo(pub(crate) wreq::tls::TlsInfo);
16
+
17
+ impl TlsInfo {
18
+ /// Copy the DER-encoded leaf certificate into a binary Ruby String.
19
+ fn peer_certificate(ruby: &Ruby, rb_self: &Self) -> Option<RString> {
20
+ rb_self
21
+ .0
22
+ .peer_certificate()
23
+ .map(|der| ruby.str_from_slice(der))
24
+ }
25
+
26
+ /// Copy the certificate chain into a frozen Array of binary Ruby Strings.
27
+ ///
28
+ /// Only the Array is frozen. Its Strings are independent copies and remain
29
+ /// mutable in Ruby.
30
+ fn peer_certificate_chain(ruby: &Ruby, rb_self: &Self) -> Option<RArray> {
31
+ rb_self.0.peer_certificate_chain().map(|chain| {
32
+ let certificates = ruby.ary_from_iter(chain.map(|cert| ruby.str_from_slice(cert)));
33
+ certificates.freeze();
34
+ certificates
35
+ })
36
+ }
37
+ }
38
+
39
+ /// Define the `Wreq::TlsInfo` Ruby class and its readers.
40
+ pub(crate) fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
41
+ let tls_info_class = gem_module.define_class("TlsInfo", ruby.class_object())?;
42
+ tls_info_class.define_method(
43
+ "peer_certificate",
44
+ magnus::method!(TlsInfo::peer_certificate, 0),
45
+ )?;
46
+ tls_info_class.define_method(
47
+ "peer_certificate_chain",
48
+ magnus::method!(TlsInfo::peer_certificate_chain, 0),
49
+ )?;
50
+ Ok(())
51
+ }