wreq 1.2.11 → 1.2.12

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/arch.rs CHANGED
@@ -6,6 +6,49 @@
6
6
  //! do not leak into the rest of the binding.
7
7
  #![allow(unsafe_code)]
8
8
 
9
+ use std::mem::ManuallyDrop;
10
+
11
+ /// Native state that belongs to the process where the extension was loaded.
12
+ ///
13
+ /// A forked child must not destroy inherited clients, channels, or response
14
+ /// bodies because their synchronization state may belong to threads that no
15
+ /// longer exist. The child intentionally leaks the value and lets the operating
16
+ /// system reclaim it when the process exits.
17
+ ///
18
+ /// This wrapper only controls destruction. Call [`crate::rt::ensure_current`]
19
+ /// before using process-bound state stored inside it.
20
+ #[derive(Clone)]
21
+ pub(crate) struct ProcessLocal<T>(ManuallyDrop<T>);
22
+
23
+ impl<T> ProcessLocal<T> {
24
+ /// Wrap native state created by the current process.
25
+ pub(crate) fn new(value: T) -> Self {
26
+ Self(ManuallyDrop::new(value))
27
+ }
28
+ }
29
+
30
+ impl<T> AsRef<T> for ProcessLocal<T> {
31
+ fn as_ref(&self) -> &T {
32
+ &self.0
33
+ }
34
+ }
35
+
36
+ impl<T> Drop for ProcessLocal<T> {
37
+ fn drop(&mut self) {
38
+ #[cfg(unix)]
39
+ if forked_process_ids().is_some() {
40
+ return;
41
+ }
42
+
43
+ // SAFETY: `new` initializes the value exactly once, `ManuallyDrop`
44
+ // prevents an automatic second drop, and this wrapper's `Drop`
45
+ // implementation runs at most once.
46
+ unsafe {
47
+ ManuallyDrop::drop(&mut self.0);
48
+ }
49
+ }
50
+ }
51
+
9
52
  /// Whether the native client exposes TCP user-timeout configuration.
10
53
  pub(crate) const SUPPORTS_TCP_USER_TIMEOUT: bool = cfg!(any(
11
54
  target_os = "android",
@@ -27,6 +70,64 @@ pub(crate) const SUPPORTS_INTERFACE: bool = cfg!(any(
27
70
  target_os = "watchos",
28
71
  ));
29
72
 
73
+ #[cfg(unix)]
74
+ mod unix {
75
+ use std::{io, process, sync::OnceLock};
76
+
77
+ /// Process state captured when the extension initializes.
78
+ ///
79
+ /// The atfork guard uses a POSIX child handler to advance an atomic fork
80
+ /// generation without running Ruby code.
81
+ /// https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_atfork.html
82
+ struct ForkGuard {
83
+ detector: forkguard::Guard,
84
+ owner_pid: u32,
85
+ }
86
+
87
+ impl ForkGuard {
88
+ /// Create a guard and register fork detection with the process.
89
+ fn new() -> io::Result<Self> {
90
+ forkguard::Guard::try_new()
91
+ .map(|detector| Self {
92
+ detector,
93
+ owner_pid: process::id(),
94
+ })
95
+ .map_err(|error| io::Error::from_raw_os_error(error.code().get()))
96
+ }
97
+
98
+ /// Return process IDs when this guard was inherited through a fork.
99
+ fn forked_process_ids(&self) -> Option<(u32, u32)> {
100
+ // Keep the stored generation unchanged so every runtime access in
101
+ // the child remains rejected. Cloning the detector copies one usize.
102
+ let mut detector = self.detector.clone();
103
+ detector
104
+ .detected_fork()
105
+ .then(|| (self.owner_pid, process::id()))
106
+ }
107
+ }
108
+
109
+ static FORK_GUARD: OnceLock<ForkGuard> = OnceLock::new();
110
+
111
+ /// Register process fork tracking before the extension exposes its API.
112
+ pub(crate) fn initialize_fork_tracking() -> io::Result<()> {
113
+ if FORK_GUARD.get().is_some() {
114
+ return Ok(());
115
+ }
116
+
117
+ let guard = ForkGuard::new()?;
118
+ let _ = FORK_GUARD.set(guard);
119
+ Ok(())
120
+ }
121
+
122
+ /// Return process IDs only when this process inherited the extension.
123
+ pub(crate) fn forked_process_ids() -> Option<(u32, u32)> {
124
+ FORK_GUARD.get().and_then(ForkGuard::forked_process_ids)
125
+ }
126
+ }
127
+
128
+ #[cfg(unix)]
129
+ pub(crate) use unix::{forked_process_ids, initialize_fork_tracking};
130
+
30
131
  #[cfg(all(target_os = "windows", target_env = "gnu"))]
31
132
  mod windows_gnu {
32
133
  //! Windows GNU support.
@@ -53,3 +154,30 @@ mod windows_gnu {
53
154
  }
54
155
  }
55
156
  }
157
+
158
+ #[cfg(test)]
159
+ mod tests {
160
+ use std::cell::Cell;
161
+
162
+ use super::ProcessLocal;
163
+
164
+ struct DropCounter<'a>(&'a Cell<usize>);
165
+
166
+ impl Drop for DropCounter<'_> {
167
+ fn drop(&mut self) {
168
+ self.0.set(self.0.get() + 1);
169
+ }
170
+ }
171
+
172
+ #[test]
173
+ fn process_local_drops_in_its_owner_process() {
174
+ let drops = Cell::new(0);
175
+
176
+ {
177
+ let value = ProcessLocal::new(DropCounter(&drops));
178
+ assert_eq!(value.as_ref().0.get(), 0);
179
+ }
180
+
181
+ assert_eq!(drops.get(), 1);
182
+ }
183
+ }
@@ -13,6 +13,7 @@ use magnus::{Error, Integer, RString, Ruby, Value, scan_args::scan_args};
13
13
  use tokio::sync::{Mutex, Semaphore, mpsc};
14
14
 
15
15
  use crate::{
16
+ arch::ProcessLocal,
16
17
  error::{
17
18
  argument_error, body_sender_borrow_error, body_sender_borrow_mut_error,
18
19
  body_sender_send_error, closed_body_sender_error, memory_error, type_error, wreq_error,
@@ -33,7 +34,7 @@ pub struct BodyReceiver(Mutex<Pin<Box<dyn Stream<Item = wreq::Result<Bytes>> + S
33
34
  /// receiver. Ruby's GVL protects state access; no [`RefCell`] borrow is kept
34
35
  /// while request backpressure waits without the GVL.
35
36
  #[magnus::wrap(class = "Wreq::BodySender", free_immediately, size)]
36
- pub struct BodySender(RefCell<InnerBodySender>);
37
+ pub struct BodySender(ProcessLocal<RefCell<InnerBodySender>>);
37
38
 
38
39
  /// Mutable ownership state for both halves of the body channel.
39
40
  struct InnerBodySender {
@@ -89,8 +90,10 @@ impl BodySender {
89
90
  /// # Errors
90
91
  ///
91
92
  /// Returns `TypeError` for a non-Integer capacity and `ArgumentError` for
92
- /// an invalid range or argument count.
93
+ /// an invalid range or argument count. Returns `Wreq::ForkError` before
94
+ /// creating a channel in a child that inherited the extension.
93
95
  pub fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
96
+ rt::ensure_current(ruby)?;
94
97
  let capacity = parse_capacity(ruby, args)?;
95
98
 
96
99
  // Create the Tokio channel without allowing an unwind to cross the Ruby FFI boundary.
@@ -100,10 +103,12 @@ impl BodySender {
100
103
  let (tx, rx) =
101
104
  catch_unwind(|| mpsc::channel(capacity)).map_err(|_| invalid_capacity_error(ruby))?;
102
105
 
103
- Ok(BodySender(RefCell::new(InnerBodySender {
104
- tx: Some(tx),
105
- rx: Some(rx),
106
- })))
106
+ Ok(BodySender(ProcessLocal::new(RefCell::new(
107
+ InnerBodySender {
108
+ tx: Some(tx),
109
+ rx: Some(rx),
110
+ },
111
+ ))))
107
112
  }
108
113
 
109
114
  /// Push a binary chunk, waiting for capacity when the channel is full.
@@ -113,8 +118,11 @@ impl BodySender {
113
118
  /// # Errors
114
119
  ///
115
120
  /// Returns `IOError` after either channel side has closed. An interrupted
116
- /// wait raises `Wreq::InterruptError`.
121
+ /// wait raises `Wreq::InterruptError`. Returns `Wreq::ForkError` before
122
+ /// reading an inherited channel.
117
123
  pub fn push(ruby: &Ruby, rb_self: &Self, data: RString) -> Result<(), Error> {
124
+ rt::ensure_current(ruby)?;
125
+
118
126
  // Clone during the shared borrow, then release it before waiting
119
127
  // for capacity. Request attachment needs a mutable borrow.
120
128
  let tx = match &rb_self.read_inner(ruby)?.tx {
@@ -131,8 +139,10 @@ impl BodySender {
131
139
  ///
132
140
  /// # Errors
133
141
  ///
134
- /// Returns `Wreq::BodyError` if the internal state is already borrowed.
142
+ /// Returns `Wreq::ForkError` before reading an inherited channel, or
143
+ /// `Wreq::BodyError` if the internal state is already borrowed.
135
144
  pub fn close(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
145
+ rt::ensure_current(ruby)?;
136
146
  let mut inner = rb_self.write_inner(ruby)?;
137
147
  inner.tx.take();
138
148
  Ok(())
@@ -142,14 +152,17 @@ impl BodySender {
142
152
  ///
143
153
  /// # Errors
144
154
  ///
145
- /// Returns `Wreq::BodyError` if the internal state is already borrowed.
155
+ /// Returns `Wreq::ForkError` before reading an inherited channel, or
156
+ /// `Wreq::BodyError` if the internal state is already borrowed.
146
157
  pub fn is_closed(ruby: &Ruby, rb_self: &Self) -> Result<bool, Error> {
158
+ rt::ensure_current(ruby)?;
147
159
  rb_self.read_inner(ruby).map(|r| r.is_closed())
148
160
  }
149
161
 
150
162
  /// Borrow the channel state without panicking on accidental re-entry.
151
163
  fn read_inner(&self, ruby: &Ruby) -> Result<Ref<'_, InnerBodySender>, Error> {
152
164
  self.0
165
+ .as_ref()
153
166
  .try_borrow()
154
167
  .map_err(|err| body_sender_borrow_error(ruby, err))
155
168
  }
@@ -157,6 +170,7 @@ impl BodySender {
157
170
  /// Mutably borrow the channel state without panicking on accidental re-entry.
158
171
  fn write_inner(&self, ruby: &Ruby) -> Result<RefMut<'_, InnerBodySender>, Error> {
159
172
  self.0
173
+ .as_ref()
160
174
  .try_borrow_mut()
161
175
  .map_err(|err| body_sender_borrow_mut_error(ruby, err))
162
176
  }
@@ -168,6 +182,7 @@ impl BodySender {
168
182
  /// Returns `Wreq::MemoryError` if the receiver was already consumed, or
169
183
  /// `Wreq::BodyError` if Ruby re-enters while the state is borrowed.
170
184
  pub(super) fn take_receiver(&self, ruby: &Ruby) -> Result<ReceiverStream<Bytes>, Error> {
185
+ rt::ensure_current(ruby)?;
171
186
  self.write_inner(ruby)?
172
187
  .rx
173
188
  .take()
data/src/client/resp.rs CHANGED
@@ -9,6 +9,7 @@ use magnus::{Error, Module, RArray, RModule, Ruby, Value, scan_args::scan_args};
9
9
  use wreq::Uri;
10
10
 
11
11
  use crate::{
12
+ arch::ProcessLocal,
12
13
  client::body::{json::Json, stream::BodyReceiver},
13
14
  cookie::Cookie,
14
15
  error::{memory_error, no_block_given_error, wreq_error},
@@ -16,6 +17,7 @@ use crate::{
16
17
  header::Headers,
17
18
  http::{StatusCode, Version},
18
19
  rt,
20
+ tls::TlsInfo,
19
21
  };
20
22
 
21
23
  /// A response from a request.
@@ -28,8 +30,7 @@ pub struct Response {
28
30
  headers: HeaderMap,
29
31
  local_addr: Option<SocketAddr>,
30
32
  remote_addr: Option<SocketAddr>,
31
- body: ArcSwapOption<Body>,
32
- extensions: Extensions,
33
+ state: ProcessLocal<NativeResponseState>,
33
34
  }
34
35
 
35
36
  /// Represents the state of the HTTP response body.
@@ -40,6 +41,12 @@ enum Body {
40
41
  Reusable(Bytes),
41
42
  }
42
43
 
44
+ /// Response state that may contain handles owned by the native runtime.
45
+ struct NativeResponseState {
46
+ body: ArcSwapOption<Body>,
47
+ extensions: Extensions,
48
+ }
49
+
43
50
  impl Response {
44
51
  /// Create a new [`Response`] instance.
45
52
  pub fn new(response: wreq::Response) -> Self {
@@ -55,26 +62,31 @@ impl Response {
55
62
  local_addr,
56
63
  remote_addr,
57
64
  content_length,
58
- extensions: parts.extensions,
59
65
  version: Version::from_ffi(parts.version),
60
66
  status: StatusCode::from(parts.status),
61
67
  headers: parts.headers,
62
- body: ArcSwapOption::from_pointee(Body::Streamable(body)),
68
+ state: ProcessLocal::new(NativeResponseState {
69
+ body: ArcSwapOption::from_pointee(Body::Streamable(body)),
70
+ extensions: parts.extensions,
71
+ }),
63
72
  }
64
73
  }
65
74
 
66
75
  /// Internal method to get the wreq::Response, optionally streaming the body.
67
76
  fn response(&self, ruby: &Ruby, stream: bool) -> Result<wreq::Response, Error> {
77
+ rt::ensure_current(ruby)?;
78
+ let state = self.state.as_ref();
79
+
68
80
  let build_response = |body: wreq::Body| -> wreq::Response {
69
81
  let mut response = HttpResponse::new(body);
70
82
  *response.version_mut() = self.version.into_ffi();
71
83
  *response.status_mut() = self.status.0;
72
84
  *response.headers_mut() = self.headers.clone();
73
- *response.extensions_mut() = self.extensions.clone();
85
+ *response.extensions_mut() = state.extensions.clone();
74
86
  wreq::Response::from(response)
75
87
  };
76
88
 
77
- if let Some(arc) = self.body.swap(None) {
89
+ if let Some(arc) = state.body.swap(None) {
78
90
  match Arc::try_unwrap(arc) {
79
91
  Ok(Body::Streamable(body)) => {
80
92
  return if stream {
@@ -86,14 +98,16 @@ impl Response {
86
98
  wreq_error,
87
99
  )?;
88
100
 
89
- self.body
101
+ state
102
+ .body
90
103
  .store(Some(Arc::new(Body::Reusable(bytes.clone()))));
91
104
 
92
105
  Ok(build_response(wreq::Body::from(bytes)))
93
106
  };
94
107
  }
95
108
  Ok(Body::Reusable(bytes)) => {
96
- self.body
109
+ state
110
+ .body
97
111
  .store(Some(Arc::new(Body::Reusable(bytes.clone()))));
98
112
 
99
113
  if !stream {
@@ -167,6 +181,16 @@ impl Response {
167
181
  self.remote_addr.map(|addr| addr.to_string())
168
182
  }
169
183
 
184
+ /// Return peer certificate data retained for this response.
185
+ fn tls_info(&self) -> Option<TlsInfo> {
186
+ self.state
187
+ .as_ref()
188
+ .extensions
189
+ .get::<wreq::tls::TlsInfo>()
190
+ .cloned()
191
+ .map(TlsInfo)
192
+ }
193
+
170
194
  /// Get the response body as bytes.
171
195
  pub fn bytes(ruby: &Ruby, rb_self: &Self) -> Result<Bytes, Error> {
172
196
  let response = rb_self.response(ruby, false)?;
@@ -175,6 +199,7 @@ impl Response {
175
199
 
176
200
  /// Get the full response text given a specific encoding.
177
201
  pub fn text(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<String, Error> {
202
+ rt::ensure_current(ruby)?;
178
203
  let args = scan_args::<(), (Option<String>,), (), (), (), ()>(args)?;
179
204
  let response = rb_self.response(ruby, false)?;
180
205
  match args.optional.0 {
@@ -194,6 +219,8 @@ impl Response {
194
219
 
195
220
  /// Yield response body chunks to the given Ruby block.
196
221
  pub fn chunks(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
222
+ rt::ensure_current(ruby)?;
223
+
197
224
  if !ruby.block_given() {
198
225
  return Err(no_block_given_error(ruby));
199
226
  }
@@ -211,16 +238,15 @@ impl Response {
211
238
  }
212
239
 
213
240
  /// Close the response body, dropping any resources.
214
- #[inline]
215
- pub fn close(&self) {
216
- gvl::nogvl(|| self.body.swap(None));
217
- }
218
- }
219
-
220
- impl Drop for Response {
221
- fn drop(&mut self) {
222
- // Ensure body is dropped in GVL
223
- self.body.swap(None);
241
+ ///
242
+ /// # Errors
243
+ ///
244
+ /// Returns `Wreq::ForkError` before touching a body inherited from the
245
+ /// parent process.
246
+ pub fn close(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
247
+ rt::ensure_current(ruby)?;
248
+ gvl::nogvl(|| rb_self.state.as_ref().body.swap(None));
249
+ Ok(())
224
250
  }
225
251
  }
226
252
 
@@ -243,5 +269,6 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
243
269
  response.define_method("json", magnus::method!(Response::json, 0))?;
244
270
  response.define_method("chunks", magnus::method!(Response::chunks, 0))?;
245
271
  response.define_method("close", magnus::method!(Response::close, 0))?;
272
+ response.define_method("tls_info", magnus::method!(Response::tls_info, 0))?;
246
273
  Ok(())
247
274
  }
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,
@@ -21,6 +21,7 @@ use crate::{
21
21
  header::{Headers, OrigHeaders, UserAgent},
22
22
  http::Method,
23
23
  options::{NativeOption, Options},
24
+ rt,
24
25
  };
25
26
 
26
27
  /// A builder for `Client`.
@@ -94,6 +95,8 @@ struct Builder {
94
95
  // ========= TLS options =========
95
96
  /// Whether to verify TLS certificates.
96
97
  verify: Option<bool>,
98
+ /// Whether to retain peer certificate data on responses.
99
+ tls_info: Option<bool>,
97
100
 
98
101
  // ========= Network options =========
99
102
  /// Whether to disable the proxy for the client.
@@ -120,7 +123,7 @@ struct Builder {
120
123
 
121
124
  #[derive(Clone)]
122
125
  #[magnus::wrap(class = "Wreq::Client", free_immediately, size)]
123
- pub struct Client(wreq::Client);
126
+ pub struct Client(ProcessLocal<wreq::Client>);
124
127
 
125
128
  // ===== impl Builder =====
126
129
 
@@ -193,11 +196,15 @@ impl Client {
193
196
  /// native fallible client builder. Extra positional arguments return
194
197
  /// `ArgumentError`.
195
198
  pub fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, magnus::Error> {
199
+ rt::ensure_current(ruby)?;
200
+
196
201
  Options::from_args(ruby, args, "client")?
197
202
  .map(Builder::from_options)
198
203
  .transpose()
199
204
  .map(Option::unwrap_or_default)
200
205
  .and_then(|params| Self::build(ruby, params))
206
+ .map(ProcessLocal::new)
207
+ .map(Self)
201
208
  }
202
209
 
203
210
  /// Build the default client through the same fallible path as `new`.
@@ -206,7 +213,7 @@ impl Client {
206
213
  ///
207
214
  /// Returns `Wreq::BuilderError`, `Wreq::TlsError`, or another mapped native
208
215
  /// initialization error without unwinding through Ruby.
209
- pub(crate) fn default_client(ruby: &Ruby) -> Result<Self, magnus::Error> {
216
+ pub(crate) fn default_client(ruby: &Ruby) -> Result<wreq::Client, magnus::Error> {
210
217
  Self::build(ruby, Builder::default())
211
218
  }
212
219
 
@@ -214,8 +221,12 @@ impl Client {
214
221
  ///
215
222
  /// # Errors
216
223
  ///
217
- /// Maps native build failures only after the GVL has been reacquired.
218
- fn build(ruby: &Ruby, mut params: Builder) -> Result<Self, magnus::Error> {
224
+ /// Returns `Wreq::ForkError` before touching native client state when the
225
+ /// extension was inherited from a parent process. Maps native build
226
+ /// failures only after the GVL has been reacquired.
227
+ fn build(ruby: &Ruby, mut params: Builder) -> Result<wreq::Client, magnus::Error> {
228
+ rt::ensure_current(ruby)?;
229
+
219
230
  let result = gvl::nogvl(|| {
220
231
  let mut builder = wreq::Client::builder();
221
232
 
@@ -349,6 +360,7 @@ impl Client {
349
360
 
350
361
  // TLS options.
351
362
  apply_option!(set_if_some, builder, params.verify, tls_cert_verification);
363
+ apply_option!(set_if_some, builder, params.tls_info, tls_info);
352
364
 
353
365
  // Network options.
354
366
  apply_option!(set_if_some, builder, params.proxy, proxy);
@@ -374,12 +386,17 @@ impl Client {
374
386
  apply_option!(set_if_some, builder, params.deflate, deflate);
375
387
  apply_option!(set_if_some, builder, params.zstd, zstd);
376
388
 
377
- builder.build().map(Client)
389
+ builder.build()
378
390
  });
379
391
 
380
392
  // Ruby exceptions must be created after the GVL has been reacquired.
381
393
  result.map_err(|err| wreq_error(ruby, err))
382
394
  }
395
+
396
+ /// Clone the native client handle for a request future.
397
+ fn native_client(&self) -> wreq::Client {
398
+ self.0.as_ref().clone()
399
+ }
383
400
  }
384
401
 
385
402
  impl Client {
@@ -391,9 +408,9 @@ impl Client {
391
408
  ruby: &Ruby,
392
409
  args: &[Value],
393
410
  ) -> Result<Response, magnus::Error> {
394
- let ((method, url), request) = extract_request!(args, (Obj<Method>, String));
411
+ let ((method, url), request) = extract_request!(ruby, args, (Obj<Method>, String));
395
412
  let client = Self::default_client(ruby)?;
396
- execute_request(ruby, client.0, *method, url, request)
413
+ execute_request(ruby, client, *method, url, request)
397
414
  }
398
415
 
399
416
  /// Send a request with `method` through a newly built default client.
@@ -405,72 +422,72 @@ impl Client {
405
422
  method: Method,
406
423
  args: &[Value],
407
424
  ) -> Result<Response, magnus::Error> {
408
- let ((url,), request) = extract_request!(args, (String,));
425
+ let ((url,), request) = extract_request!(ruby, args, (String,));
409
426
  let client = Self::default_client(ruby)?;
410
- execute_request(ruby, client.0, method, url, request)
427
+ execute_request(ruby, client, method, url, request)
411
428
  }
412
429
 
413
430
  /// Send a HTTP request.
414
431
  #[inline]
415
432
  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)
433
+ let ((method, url), request) = extract_request!(ruby, args, (Obj<Method>, String));
434
+ execute_request(ruby, rb_self.native_client(), *method, url, request)
418
435
  }
419
436
 
420
437
  /// Send a GET request.
421
438
  #[inline]
422
439
  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)
440
+ let ((url,), request) = extract_request!(ruby, args, (String,));
441
+ execute_request(ruby, rb_self.native_client(), Method::GET, url, request)
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(ruby, rb_self.native_client(), Method::POST, url, request)
432
449
  }
433
450
 
434
451
  /// Send a PUT request.
435
452
  #[inline]
436
453
  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)
454
+ let ((url,), request) = extract_request!(ruby, args, (String,));
455
+ execute_request(ruby, rb_self.native_client(), Method::PUT, url, request)
439
456
  }
440
457
 
441
458
  /// Send a DELETE request.
442
459
  #[inline]
443
460
  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)
461
+ let ((url,), request) = extract_request!(ruby, args, (String,));
462
+ execute_request(ruby, rb_self.native_client(), Method::DELETE, url, request)
446
463
  }
447
464
 
448
465
  /// Send a HEAD request.
449
466
  #[inline]
450
467
  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)
468
+ let ((url,), request) = extract_request!(ruby, args, (String,));
469
+ execute_request(ruby, rb_self.native_client(), Method::HEAD, url, request)
453
470
  }
454
471
 
455
472
  /// Send an OPTIONS request.
456
473
  #[inline]
457
474
  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)
475
+ let ((url,), request) = extract_request!(ruby, args, (String,));
476
+ execute_request(ruby, rb_self.native_client(), Method::OPTIONS, url, request)
460
477
  }
461
478
 
462
479
  /// Send a TRACE request.
463
480
  #[inline]
464
481
  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)
482
+ let ((url,), request) = extract_request!(ruby, args, (String,));
483
+ execute_request(ruby, rb_self.native_client(), Method::TRACE, url, request)
467
484
  }
468
485
 
469
486
  /// Send a PATCH request.
470
487
  #[inline]
471
488
  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)
489
+ let ((url,), request) = extract_request!(ruby, args, (String,));
490
+ execute_request(ruby, rb_self.native_client(), Method::PATCH, url, request)
474
491
  }
475
492
  }
476
493
 
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 was loaded 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,