wreq-rb 0.5.0 → 0.6.0
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 +1922 -397
- data/LICENSE +203 -0
- data/README.md +47 -15
- data/ext/wreq_rb/Cargo.toml +4 -6
- data/ext/wreq_rb/src/client.rs +216 -49
- data/lib/wreq-rb/version.rb +1 -1
- data/patches/0001-add-transfer-size-tracking.patch +76 -67
- data/vendor/wreq/Cargo.toml +119 -71
- data/vendor/wreq/README.md +25 -20
- data/vendor/wreq/bench/http1.rs +25 -0
- data/vendor/wreq/bench/http1_over_tls.rs +25 -0
- data/vendor/wreq/bench/http2.rs +25 -0
- data/vendor/wreq/bench/http2_over_tls.rs +25 -0
- data/vendor/wreq/bench/support/bench.rs +91 -0
- data/vendor/wreq/bench/support/client.rs +217 -0
- data/vendor/wreq/bench/support/server.rs +188 -0
- data/vendor/wreq/bench/support.rs +56 -0
- data/vendor/wreq/examples/cert_store.rs +4 -4
- data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
- data/vendor/wreq/examples/http2_websocket.rs +2 -2
- data/vendor/wreq/examples/keylog.rs +3 -3
- data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
- data/vendor/wreq/examples/rt.rs +23 -0
- data/vendor/wreq/src/client/body.rs +23 -61
- data/vendor/wreq/src/client/emulate.rs +119 -0
- data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
- data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
- data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
- data/vendor/wreq/src/client/layer/config.rs +27 -6
- data/vendor/wreq/src/client/layer/decoder.rs +9 -4
- data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
- data/vendor/wreq/src/client/layer/redirect.rs +4 -5
- data/vendor/wreq/src/client/layer/retry.rs +8 -5
- data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
- data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
- data/vendor/wreq/src/client/layer/timeout.rs +24 -74
- data/vendor/wreq/src/client/layer.rs +1 -2
- data/vendor/wreq/src/client/multipart.rs +137 -154
- data/vendor/wreq/src/client/request.rs +202 -118
- data/vendor/wreq/src/client/response.rs +46 -45
- data/vendor/wreq/src/client/upgrade.rs +15 -0
- data/vendor/wreq/src/client/ws.rs +73 -25
- data/vendor/wreq/src/client.rs +1655 -17
- data/vendor/wreq/src/config.rs +11 -11
- data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
- data/vendor/wreq/src/conn/descriptor.rs +143 -0
- data/vendor/wreq/src/conn/http.rs +484 -0
- data/vendor/wreq/src/conn/net/io.rs +75 -0
- data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
- data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
- data/vendor/wreq/src/conn/net/tcp.rs +561 -0
- data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
- data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
- data/vendor/wreq/src/conn/net/uds.rs +11 -0
- data/vendor/wreq/src/conn/net.rs +130 -0
- data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
- data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
- data/vendor/wreq/src/conn/tls_info.rs +47 -0
- data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
- data/vendor/wreq/src/cookie.rs +302 -142
- data/vendor/wreq/src/dns/gai/compio.rs +77 -0
- data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
- data/vendor/wreq/src/dns/gai.rs +14 -164
- data/vendor/wreq/src/dns/hickory.rs +16 -23
- data/vendor/wreq/src/dns/resolve.rs +7 -41
- data/vendor/wreq/src/dns.rs +90 -7
- data/vendor/wreq/src/error.rs +57 -31
- data/vendor/wreq/src/ext.rs +25 -0
- data/vendor/wreq/src/group.rs +211 -0
- data/vendor/wreq/src/header.rs +100 -112
- data/vendor/wreq/src/lib.rs +124 -73
- data/vendor/wreq/src/proxy.rs +6 -20
- data/vendor/wreq/src/redirect.rs +1 -1
- data/vendor/wreq/src/rt.rs +208 -0
- data/vendor/wreq/src/sync.rs +97 -98
- data/vendor/wreq/src/tls/compress.rs +124 -0
- data/vendor/wreq/src/tls/conn/ext.rs +54 -45
- data/vendor/wreq/src/tls/conn/service.rs +14 -18
- data/vendor/wreq/src/tls/conn.rs +169 -241
- data/vendor/wreq/src/tls/keylog.rs +68 -5
- data/vendor/wreq/src/tls/session.rs +205 -0
- data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
- data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
- data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
- data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
- data/vendor/wreq/src/tls.rs +489 -25
- data/vendor/wreq/src/trace.rs +0 -12
- data/vendor/wreq/src/util.rs +1 -1
- data/vendor/wreq/tests/badssl.rs +10 -10
- data/vendor/wreq/tests/client.rs +3 -9
- data/vendor/wreq/tests/cookie.rs +6 -8
- data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
- data/vendor/wreq/tests/multipart.rs +43 -1
- data/vendor/wreq/tests/proxy.rs +1 -1
- data/vendor/wreq/tests/support/layer.rs +1 -0
- metadata +49 -71
- data/patches/0002-add-cancel-connections.patch +0 -181
- data/vendor/wreq/src/client/conn/conn.rs +0 -231
- data/vendor/wreq/src/client/conn/http.rs +0 -1023
- data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
- data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
- data/vendor/wreq/src/client/core/body/length.rs +0 -118
- data/vendor/wreq/src/client/core/body.rs +0 -34
- data/vendor/wreq/src/client/core/common/buf.rs +0 -149
- data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
- data/vendor/wreq/src/client/core/common/watch.rs +0 -76
- data/vendor/wreq/src/client/core/common.rs +0 -3
- data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
- data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
- data/vendor/wreq/src/client/core/conn.rs +0 -11
- data/vendor/wreq/src/client/core/dispatch.rs +0 -299
- data/vendor/wreq/src/client/core/error.rs +0 -435
- data/vendor/wreq/src/client/core/ext.rs +0 -201
- data/vendor/wreq/src/client/core/http1.rs +0 -178
- data/vendor/wreq/src/client/core/http2.rs +0 -483
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
- data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
- data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
- data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
- data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
- data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
- data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
- data/vendor/wreq/src/client/core/proto.rs +0 -58
- data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
- data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
- data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
- data/vendor/wreq/src/client/core/rt.rs +0 -25
- data/vendor/wreq/src/client/core/upgrade.rs +0 -267
- data/vendor/wreq/src/client/core.rs +0 -16
- data/vendor/wreq/src/client/emulation.rs +0 -161
- data/vendor/wreq/src/client/http/client/error.rs +0 -142
- data/vendor/wreq/src/client/http/client/exec.rs +0 -29
- data/vendor/wreq/src/client/http/client/extra.rs +0 -77
- data/vendor/wreq/src/client/http/client/util.rs +0 -104
- data/vendor/wreq/src/client/http.rs +0 -1629
- data/vendor/wreq/src/client/layer/config/options.rs +0 -156
- data/vendor/wreq/src/client/layer/cookie.rs +0 -161
- data/vendor/wreq/src/hash.rs +0 -143
- data/vendor/wreq/src/tls/conn/cache.rs +0 -123
- data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
- data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
- data/vendor/wreq/src/tls/options.rs +0 -464
- /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
- /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
- /data/vendor/wreq/src/{client/conn → conn}/verbose.rs +0 -0
data/ext/wreq_rb/src/client.rs
CHANGED
|
@@ -2,6 +2,7 @@ use std::ffi::c_void;
|
|
|
2
2
|
use std::panic::{self, AssertUnwindSafe};
|
|
3
3
|
use std::ptr;
|
|
4
4
|
use std::any::Any;
|
|
5
|
+
use std::sync::Arc;
|
|
5
6
|
use std::time::Duration;
|
|
6
7
|
|
|
7
8
|
use magnus::{
|
|
@@ -9,13 +10,15 @@ use magnus::{
|
|
|
9
10
|
try_convert::TryConvert, Value,
|
|
10
11
|
};
|
|
11
12
|
use tokio::runtime::Runtime;
|
|
13
|
+
use tokio::sync::Semaphore;
|
|
14
|
+
use tokio::task::JoinSet;
|
|
12
15
|
use tokio_util::sync::CancellationToken;
|
|
13
16
|
use std::net::IpAddr;
|
|
14
17
|
use wreq::header::{HeaderMap, HeaderName, HeaderValue, OrigHeaderMap};
|
|
15
18
|
use wreq::tls::TlsVersion;
|
|
16
|
-
use wreq_util::{Emulation as BrowserEmulation,
|
|
19
|
+
use wreq_util::{Emulation as BrowserEmulation, Platform as EmulationPlatform, Profile as BrowserProfile};
|
|
17
20
|
|
|
18
|
-
use crate::error::{generic_error, to_magnus_error};
|
|
21
|
+
use crate::error::{generic_error, to_magnus_error, wreq_error};
|
|
19
22
|
use crate::response::Response;
|
|
20
23
|
|
|
21
24
|
// --------------------------------------------------------------------------
|
|
@@ -137,39 +140,95 @@ async fn execute_request(req: wreq::RequestBuilder) -> Result<ResponseData, wreq
|
|
|
137
140
|
Ok(ResponseData { status, headers, body, url, version, content_length, transfer_size })
|
|
138
141
|
}
|
|
139
142
|
|
|
143
|
+
// --------------------------------------------------------------------------
|
|
144
|
+
// Batch execution
|
|
145
|
+
// --------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
const DEFAULT_BATCH_CONCURRENCY: usize = 16;
|
|
148
|
+
|
|
149
|
+
/// Per-item batch result as pure Rust types (no Ruby objects). Errors are kept
|
|
150
|
+
/// as messages so one failure never discards the rest of the batch.
|
|
151
|
+
enum BatchItem {
|
|
152
|
+
Ok(ResponseData),
|
|
153
|
+
Err(String),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Outcome of a whole batch performed outside the GVL.
|
|
157
|
+
enum BatchOutcome {
|
|
158
|
+
Done(Vec<BatchItem>),
|
|
159
|
+
Interrupted,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Run every request concurrently with at most `concurrency` in flight,
|
|
163
|
+
/// returning results in input order.
|
|
164
|
+
async fn execute_batch(reqs: Vec<wreq::RequestBuilder>, concurrency: usize) -> Vec<BatchItem> {
|
|
165
|
+
let permits = Arc::new(Semaphore::new(concurrency));
|
|
166
|
+
let mut set: JoinSet<(usize, BatchItem)> = JoinSet::new();
|
|
167
|
+
|
|
168
|
+
for (idx, req) in reqs.into_iter().enumerate() {
|
|
169
|
+
let permits = Arc::clone(&permits);
|
|
170
|
+
set.spawn(async move {
|
|
171
|
+
let _permit = match permits.acquire_owned().await {
|
|
172
|
+
Ok(p) => p,
|
|
173
|
+
Err(_) => return (idx, BatchItem::Err("batch semaphore closed".to_owned())),
|
|
174
|
+
};
|
|
175
|
+
let item = match execute_request(req).await {
|
|
176
|
+
Ok(data) => BatchItem::Ok(data),
|
|
177
|
+
Err(e) => BatchItem::Err(e.to_string()),
|
|
178
|
+
};
|
|
179
|
+
(idx, item)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let mut slots: Vec<Option<BatchItem>> = Vec::new();
|
|
184
|
+
slots.resize_with(set.len(), || None);
|
|
185
|
+
while let Some(joined) = set.join_next().await {
|
|
186
|
+
// A JoinError means the task panicked or was aborted; its slot is left
|
|
187
|
+
// empty and filled with a generic error below.
|
|
188
|
+
if let Ok((idx, item)) = joined {
|
|
189
|
+
slots[idx] = Some(item);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
slots
|
|
194
|
+
.into_iter()
|
|
195
|
+
.map(|slot| slot.unwrap_or_else(|| BatchItem::Err("request task failed".to_owned())))
|
|
196
|
+
.collect()
|
|
197
|
+
}
|
|
198
|
+
|
|
140
199
|
// --------------------------------------------------------------------------
|
|
141
200
|
// Emulation helpers
|
|
142
201
|
// --------------------------------------------------------------------------
|
|
143
202
|
|
|
144
|
-
/// The default
|
|
145
|
-
const DEFAULT_EMULATION:
|
|
203
|
+
/// The default browser profile to apply when none is specified.
|
|
204
|
+
const DEFAULT_EMULATION: BrowserProfile = BrowserProfile::Chrome148;
|
|
146
205
|
|
|
147
|
-
/// Parse a Ruby string like "chrome_143" into a
|
|
148
|
-
fn parse_emulation(name: &str) -> Result<
|
|
206
|
+
/// Parse a Ruby string like "chrome_143" into a BrowserProfile variant.
|
|
207
|
+
fn parse_emulation(name: &str) -> Result<BrowserProfile, magnus::Error> {
|
|
149
208
|
let json_val = serde_json::Value::String(name.to_string());
|
|
150
|
-
serde_json::from_value::<
|
|
151
|
-
.map_err(|_| generic_error(format!("unknown emulation: '{}'. Use names like '
|
|
209
|
+
serde_json::from_value::<BrowserProfile>(json_val)
|
|
210
|
+
.map_err(|_| generic_error(format!("unknown emulation: '{}'. Use names like 'chrome_148', 'firefox_151', 'safari_18.5', etc.", name)))
|
|
152
211
|
}
|
|
153
212
|
|
|
154
|
-
/// Parse a Ruby string like "windows" into an
|
|
155
|
-
fn parse_emulation_os(name: &str) -> Result<
|
|
213
|
+
/// Parse a Ruby string like "windows" into an EmulationPlatform variant.
|
|
214
|
+
fn parse_emulation_os(name: &str) -> Result<EmulationPlatform, magnus::Error> {
|
|
156
215
|
let json_val = serde_json::Value::String(name.to_string());
|
|
157
|
-
serde_json::from_value::<
|
|
216
|
+
serde_json::from_value::<EmulationPlatform>(json_val)
|
|
158
217
|
.map_err(|_| generic_error("unknown emulation_os. Use: 'windows', 'macos', 'linux', 'android', 'ios'"))
|
|
159
218
|
}
|
|
160
219
|
|
|
161
|
-
/// Build
|
|
220
|
+
/// Build a BrowserEmulation from a profile and an optional platform from the opts hash.
|
|
162
221
|
fn build_emulation_option(
|
|
163
|
-
|
|
222
|
+
profile: BrowserProfile,
|
|
164
223
|
opts: &RHash,
|
|
165
|
-
) -> Result<
|
|
166
|
-
let
|
|
167
|
-
Some(
|
|
168
|
-
None =>
|
|
224
|
+
) -> Result<BrowserEmulation, magnus::Error> {
|
|
225
|
+
let platform = match hash_get_string(opts, "emulation_os")? {
|
|
226
|
+
Some(platform_name) => parse_emulation_os(&platform_name)?,
|
|
227
|
+
None => EmulationPlatform::default(),
|
|
169
228
|
};
|
|
170
|
-
Ok(
|
|
171
|
-
.
|
|
172
|
-
.
|
|
229
|
+
Ok(BrowserEmulation::builder()
|
|
230
|
+
.profile(profile)
|
|
231
|
+
.platform(platform)
|
|
173
232
|
.build())
|
|
174
233
|
}
|
|
175
234
|
|
|
@@ -192,7 +251,8 @@ impl Client {
|
|
|
192
251
|
Some(RHash::try_convert(args[0])?)
|
|
193
252
|
};
|
|
194
253
|
|
|
195
|
-
let mut builder = wreq::Client::builder()
|
|
254
|
+
let mut builder = wreq::Client::builder()
|
|
255
|
+
.retry(wreq::retry::Policy::never());
|
|
196
256
|
|
|
197
257
|
if let Some(opts) = opts {
|
|
198
258
|
// Apply header_order BEFORE emulation so the user's ordering takes precedence
|
|
@@ -276,11 +336,11 @@ impl Client {
|
|
|
276
336
|
}
|
|
277
337
|
|
|
278
338
|
if let Some(v) = hash_get_bool(&opts, "verify_host")? {
|
|
279
|
-
builder = builder.
|
|
339
|
+
builder = builder.tls_verify_hostname(v);
|
|
280
340
|
}
|
|
281
341
|
|
|
282
342
|
if let Some(v) = hash_get_bool(&opts, "verify_cert")? {
|
|
283
|
-
builder = builder.
|
|
343
|
+
builder = builder.tls_cert_verification(v);
|
|
284
344
|
}
|
|
285
345
|
|
|
286
346
|
if let Some(true) = hash_get_bool(&opts, "http1_only")? {
|
|
@@ -311,7 +371,7 @@ impl Client {
|
|
|
311
371
|
builder = builder.pool_max_idle_per_host(n);
|
|
312
372
|
}
|
|
313
373
|
|
|
314
|
-
if let Some(n) =
|
|
374
|
+
if let Some(n) = hash_get_usize(&opts, "pool_max_size")? {
|
|
315
375
|
builder = builder.pool_max_size(n);
|
|
316
376
|
}
|
|
317
377
|
|
|
@@ -334,11 +394,11 @@ impl Client {
|
|
|
334
394
|
}
|
|
335
395
|
|
|
336
396
|
if let Some(s) = hash_get_string(&opts, "min_tls_version")? {
|
|
337
|
-
builder = builder.
|
|
397
|
+
builder = builder.tls_min_version(parse_tls_version(&s)?);
|
|
338
398
|
}
|
|
339
399
|
|
|
340
400
|
if let Some(s) = hash_get_string(&opts, "max_tls_version")? {
|
|
341
|
-
builder = builder.
|
|
401
|
+
builder = builder.tls_max_version(parse_tls_version(&s)?);
|
|
342
402
|
}
|
|
343
403
|
} else {
|
|
344
404
|
builder = builder.emulation(DEFAULT_EMULATION);
|
|
@@ -387,7 +447,6 @@ impl Client {
|
|
|
387
447
|
old
|
|
388
448
|
};
|
|
389
449
|
old_token.cancel();
|
|
390
|
-
self.inner.cancel_connections();
|
|
391
450
|
}
|
|
392
451
|
|
|
393
452
|
fn execute_method(&self, method_str: &str, args: &[Value]) -> Result<Response, magnus::Error> {
|
|
@@ -439,12 +498,141 @@ impl Client {
|
|
|
439
498
|
};
|
|
440
499
|
Ok(Response::new(data.status, data.headers, data.body, data.url, data.version, data.content_length, data.transfer_size))
|
|
441
500
|
}
|
|
501
|
+
|
|
502
|
+
/// Wreq::Client#request_batch(specs) or #request_batch(specs, options)
|
|
503
|
+
fn request_batch(&self, args: &[Value]) -> Result<RArray, magnus::Error> {
|
|
504
|
+
if args.is_empty() {
|
|
505
|
+
return Err(generic_error("an array of requests is required"));
|
|
506
|
+
}
|
|
507
|
+
let specs = RArray::try_convert(args[0])?;
|
|
508
|
+
|
|
509
|
+
let opts: Option<RHash> = if args.len() > 1 {
|
|
510
|
+
Some(RHash::try_convert(args[1])?)
|
|
511
|
+
} else {
|
|
512
|
+
None
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
let concurrency = match opts.as_ref() {
|
|
516
|
+
Some(o) => hash_get_usize(o, "concurrency")?.unwrap_or(DEFAULT_BATCH_CONCURRENCY),
|
|
517
|
+
None => DEFAULT_BATCH_CONCURRENCY,
|
|
518
|
+
};
|
|
519
|
+
if concurrency == 0 {
|
|
520
|
+
return Err(generic_error("concurrency must be >= 1"));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// All Ruby -> Rust conversion happens here, while we still hold the GVL.
|
|
524
|
+
let mut reqs: Vec<wreq::RequestBuilder> = Vec::with_capacity(specs.len());
|
|
525
|
+
for spec in specs.into_iter() {
|
|
526
|
+
reqs.push(self.build_request(spec, opts.as_ref())?);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
530
|
+
if reqs.is_empty() {
|
|
531
|
+
return Ok(ruby.ary_new());
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
let client_token = self.cancel_token.lock().unwrap_or_else(|e| e.into_inner()).clone();
|
|
535
|
+
|
|
536
|
+
// One GVL release covering the whole batch.
|
|
537
|
+
let outcome: BatchOutcome = unsafe {
|
|
538
|
+
without_gvl(|thread_token| {
|
|
539
|
+
runtime().block_on(async {
|
|
540
|
+
tokio::select! {
|
|
541
|
+
biased;
|
|
542
|
+
_ = thread_token.cancelled() => BatchOutcome::Interrupted,
|
|
543
|
+
_ = client_token.cancelled() => BatchOutcome::Interrupted,
|
|
544
|
+
items = execute_batch(reqs, concurrency) => BatchOutcome::Done(items),
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
})
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
let items = match outcome {
|
|
551
|
+
BatchOutcome::Done(items) => items,
|
|
552
|
+
BatchOutcome::Interrupted => return Err(generic_error("request interrupted")),
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// Back under the GVL: Ruby objects may be created again.
|
|
556
|
+
let results = ruby.ary_new_capa(items.len());
|
|
557
|
+
for item in items {
|
|
558
|
+
match item {
|
|
559
|
+
BatchItem::Ok(d) => {
|
|
560
|
+
let resp = Response::new(
|
|
561
|
+
d.status, d.headers, d.body, d.url, d.version, d.content_length,
|
|
562
|
+
d.transfer_size,
|
|
563
|
+
);
|
|
564
|
+
results.push(ruby.obj_wrap(resp))?;
|
|
565
|
+
}
|
|
566
|
+
BatchItem::Err(msg) => {
|
|
567
|
+
let err: Value = wreq_error().funcall("new", (msg,))?;
|
|
568
|
+
results.push(err)?;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
Ok(results)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/// Convert a single batch spec into a RequestBuilder. Accepted forms:
|
|
576
|
+
/// "https://example.com" -> GET
|
|
577
|
+
/// { method:, url:, **opts }
|
|
578
|
+
fn build_request(
|
|
579
|
+
&self,
|
|
580
|
+
spec: Value,
|
|
581
|
+
shared: Option<&RHash>,
|
|
582
|
+
) -> Result<wreq::RequestBuilder, magnus::Error> {
|
|
583
|
+
let mut method = wreq::Method::GET;
|
|
584
|
+
let url: String;
|
|
585
|
+
let mut item_opts: Option<RHash> = None;
|
|
586
|
+
|
|
587
|
+
if let Some(hash) = RHash::from_value(spec) {
|
|
588
|
+
url = hash_get_string(&hash, "url")?
|
|
589
|
+
.ok_or_else(|| generic_error("each request hash requires a :url"))?;
|
|
590
|
+
if let Some(val) = hash_get_value(&hash, "method")? {
|
|
591
|
+
method = value_to_method(val)?;
|
|
592
|
+
}
|
|
593
|
+
item_opts = Some(hash);
|
|
594
|
+
} else {
|
|
595
|
+
url = TryConvert::try_convert(spec)?;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
let mut req = self.inner.request(method, &url);
|
|
599
|
+
if let Some(shared) = shared {
|
|
600
|
+
req = apply_request_options(req, shared)?;
|
|
601
|
+
}
|
|
602
|
+
if let Some(item_opts) = item_opts {
|
|
603
|
+
req = apply_request_options(req, &item_opts)?;
|
|
604
|
+
}
|
|
605
|
+
Ok(req)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/// Parse a String or Symbol like "post" / :post into an HTTP method.
|
|
610
|
+
fn value_to_method(val: Value) -> Result<wreq::Method, magnus::Error> {
|
|
611
|
+
let name: String = val.funcall("to_s", ())?;
|
|
612
|
+
name.to_uppercase()
|
|
613
|
+
.parse()
|
|
614
|
+
.map_err(|_| generic_error(format!("invalid HTTP method: {}", name)))
|
|
442
615
|
}
|
|
443
616
|
|
|
444
617
|
fn apply_request_options(
|
|
445
618
|
mut req: wreq::RequestBuilder,
|
|
446
619
|
opts: &RHash,
|
|
447
620
|
) -> Result<wreq::RequestBuilder, magnus::Error> {
|
|
621
|
+
if let Some(val) = hash_get_value(opts, "emulation")? {
|
|
622
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
623
|
+
if val.is_kind_of(ruby.class_false_class()) {
|
|
624
|
+
// emulation: false — no per-request emulation override
|
|
625
|
+
} else if val.is_kind_of(ruby.class_true_class()) {
|
|
626
|
+
let opt = build_emulation_option(DEFAULT_EMULATION, opts)?;
|
|
627
|
+
req = req.emulation(opt);
|
|
628
|
+
} else {
|
|
629
|
+
let name: String = TryConvert::try_convert(val)?;
|
|
630
|
+
let emu = parse_emulation(&name)?;
|
|
631
|
+
let opt = build_emulation_option(emu, opts)?;
|
|
632
|
+
req = req.emulation(opt);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
448
636
|
if let Some(hdr_hash) = hash_get_hash(opts, "headers")? {
|
|
449
637
|
let hmap = hash_to_header_map(&hdr_hash)?;
|
|
450
638
|
req = req.headers(hmap);
|
|
@@ -499,21 +687,6 @@ fn apply_request_options(
|
|
|
499
687
|
req = req.proxy(proxy);
|
|
500
688
|
}
|
|
501
689
|
|
|
502
|
-
if let Some(val) = hash_get_value(opts, "emulation")? {
|
|
503
|
-
let ruby = unsafe { Ruby::get_unchecked() };
|
|
504
|
-
if val.is_kind_of(ruby.class_false_class()) {
|
|
505
|
-
// emulation: false — no per-request emulation override
|
|
506
|
-
} else if val.is_kind_of(ruby.class_true_class()) {
|
|
507
|
-
let opt = build_emulation_option(DEFAULT_EMULATION, opts)?;
|
|
508
|
-
req = req.emulation(opt);
|
|
509
|
-
} else {
|
|
510
|
-
let name: String = TryConvert::try_convert(val)?;
|
|
511
|
-
let emu = parse_emulation(&name)?;
|
|
512
|
-
let opt = build_emulation_option(emu, opts)?;
|
|
513
|
-
req = req.emulation(opt);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
690
|
Ok(req)
|
|
518
691
|
}
|
|
519
692
|
|
|
@@ -599,13 +772,6 @@ fn hash_get_usize(hash: &RHash, key: &str) -> Result<Option<usize>, magnus::Erro
|
|
|
599
772
|
}
|
|
600
773
|
}
|
|
601
774
|
|
|
602
|
-
fn hash_get_u32(hash: &RHash, key: &str) -> Result<Option<u32>, magnus::Error> {
|
|
603
|
-
match hash_get_value(hash, key)? {
|
|
604
|
-
Some(v) => Ok(Some(TryConvert::try_convert(v)?)),
|
|
605
|
-
None => Ok(None),
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
775
|
fn parse_tls_version(s: &str) -> Result<TlsVersion, magnus::Error> {
|
|
610
776
|
match s {
|
|
611
777
|
"tls1.0" | "tls_1_0" | "1.0" => Ok(TlsVersion::TLS_1_0),
|
|
@@ -699,6 +865,7 @@ pub fn init(_ruby: &magnus::Ruby, module: &magnus::RModule) -> Result<(), magnus
|
|
|
699
865
|
client_class.define_method("delete", method!(Client::delete, -1))?;
|
|
700
866
|
client_class.define_method("head", method!(Client::head, -1))?;
|
|
701
867
|
client_class.define_method("options", method!(Client::options, -1))?;
|
|
868
|
+
client_class.define_method("request_batch", method!(Client::request_batch, -1))?;
|
|
702
869
|
client_class.define_method("cancel", method!(Client::cancel, 0))?;
|
|
703
870
|
|
|
704
871
|
module.define_module_function("get", function!(wreq_get, -1))?;
|
data/lib/wreq-rb/version.rb
CHANGED
|
@@ -1,60 +1,51 @@
|
|
|
1
|
-
diff --git a/src/client
|
|
2
|
-
index
|
|
3
|
-
--- a/src/client
|
|
4
|
-
+++ b/src/client
|
|
5
|
-
@@ -
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
+
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@@ -
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
+
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
+
|
|
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
|
|
78
|
+
index 5c03ef4d..fe37cff0 100644
|
|
70
79
|
--- a/src/client/layer.rs
|
|
71
80
|
+++ b/src/client/layer.rs
|
|
72
|
-
@@ -
|
|
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..
|
|
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
|
|
269
|
+
index 3d3532af..ed478804 100644
|
|
261
270
|
--- a/src/client/response.rs
|
|
262
271
|
+++ b/src/client/response.rs
|
|
263
|
-
@@ -
|
|
264
|
-
use
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
+
|
|
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
|
-
|
|
279
|
+
use crate::{
|
|
280
|
+
@@ -97,6 +98,21 @@ impl Response {
|
|
272
281
|
HttpBody::size_hint(self.res.body()).exact()
|
|
273
282
|
}
|
|
274
283
|
|