wreq 1.2.10 → 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.
- checksums.yaml +4 -4
- data/Cargo.lock +88 -147
- data/Cargo.toml +10 -6
- data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +1 -1
- data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +2 -2
- data/crates/wreq-util/src/emulate/profile/opera/http2.rs +1 -1
- data/docs/fork-safety.md +34 -0
- data/docs/interrupt-handling.md +133 -0
- data/examples/tls_info.rb +27 -0
- data/lib/wreq.rb +13 -0
- data/lib/wreq_ruby/body.rb +6 -0
- data/lib/wreq_ruby/client.rb +19 -0
- data/lib/wreq_ruby/error.rb +13 -0
- data/lib/wreq_ruby/response.rb +28 -0
- data/lib/wreq_ruby/tls.rb +73 -0
- data/src/arch.rs +128 -0
- data/src/client/body/stream.rs +24 -9
- data/src/client/resp.rs +45 -18
- data/src/client.rs +45 -28
- data/src/error.rs +28 -0
- data/src/lib.rs +5 -1
- data/src/macros.rs +3 -3
- data/src/rt.rs +46 -9
- data/src/tls.rs +51 -0
- data/test/fork_test.rb +82 -0
- data/test/scripts/fork_safety.rb +110 -0
- data/test/support/tls_server.rb +95 -0
- data/test/tls_info_test.rb +59 -0
- metadata +10 -1
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
|
+
}
|
data/src/client/body/stream.rs
CHANGED
|
@@ -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(
|
|
104
|
-
|
|
105
|
-
|
|
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::
|
|
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::
|
|
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
|
-
|
|
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
|
-
|
|
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() =
|
|
85
|
+
*response.extensions_mut() = state.extensions.clone();
|
|
74
86
|
wreq::Response::from(response)
|
|
75
87
|
};
|
|
76
88
|
|
|
77
|
-
if let Some(arc) =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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<
|
|
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
|
-
///
|
|
218
|
-
|
|
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()
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|