wreq 1.2.3-x64-mingw-ucrt
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 +7 -0
- data/Cargo.lock +1687 -0
- data/Cargo.toml +54 -0
- data/Gemfile +18 -0
- data/LICENSE +201 -0
- data/README.md +153 -0
- data/Rakefile +90 -0
- data/build.rs +16 -0
- data/docs/windows-gnu-tokio-crash.md +70 -0
- data/examples/body.rb +42 -0
- data/examples/client.rb +33 -0
- data/examples/cookie.rb +24 -0
- data/examples/emulate_request.rb +37 -0
- data/examples/headers.rb +27 -0
- data/examples/proxy.rb +113 -0
- data/examples/send_stream.rb +85 -0
- data/examples/stream.rb +14 -0
- data/examples/thread_interrupt.rb +83 -0
- data/extconf.rb +7 -0
- data/lib/wreq.rb +303 -0
- data/lib/wreq_ruby/3.3/wreq_ruby.so +0 -0
- data/lib/wreq_ruby/3.4/wreq_ruby.so +0 -0
- data/lib/wreq_ruby/4.0/wreq_ruby.so +0 -0
- data/lib/wreq_ruby/body.rb +36 -0
- data/lib/wreq_ruby/client.rb +526 -0
- data/lib/wreq_ruby/cookie.rb +156 -0
- data/lib/wreq_ruby/emulate.rb +232 -0
- data/lib/wreq_ruby/error.rb +157 -0
- data/lib/wreq_ruby/header.rb +205 -0
- data/lib/wreq_ruby/http.rb +160 -0
- data/lib/wreq_ruby/response.rb +209 -0
- data/script/build_platform_gem.rb +34 -0
- data/script/build_windows_gnu.ps1 +257 -0
- data/src/arch.rs +33 -0
- data/src/client/body/form.rs +2 -0
- data/src/client/body/json.rs +16 -0
- data/src/client/body/stream.rs +146 -0
- data/src/client/body.rs +57 -0
- data/src/client/param.rs +19 -0
- data/src/client/query.rs +2 -0
- data/src/client/req.rs +274 -0
- data/src/client/resp.rs +248 -0
- data/src/client.rs +413 -0
- data/src/cookie.rs +312 -0
- data/src/emulate.rs +376 -0
- data/src/error.rs +163 -0
- data/src/extractor.rs +117 -0
- data/src/gvl.rs +154 -0
- data/src/header.rs +245 -0
- data/src/http.rs +142 -0
- data/src/lib.rs +98 -0
- data/src/macros.rs +123 -0
- data/src/rt.rs +46 -0
- data/test/client_cookie_test.rb +46 -0
- data/test/client_test.rb +136 -0
- data/test/cookie_test.rb +182 -0
- data/test/emulation_test.rb +21 -0
- data/test/error_handling_test.rb +92 -0
- data/test/header_test.rb +290 -0
- data/test/inspect_test.rb +125 -0
- data/test/module_methods_test.rb +75 -0
- data/test/orig_header_test.rb +115 -0
- data/test/request_parameters_test.rb +175 -0
- data/test/request_test.rb +244 -0
- data/test/response_test.rb +69 -0
- data/test/stream_test.rb +397 -0
- data/test/test_helper.rb +52 -0
- data/wreq.gemspec +68 -0
- metadata +123 -0
data/src/http.rs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
use magnus::{Error, Module, RModule, Ruby, TryConvert, Value, method, typed_data::Inspect};
|
|
2
|
+
|
|
3
|
+
define_ruby_enum!(
|
|
4
|
+
/// An HTTP version.
|
|
5
|
+
const,
|
|
6
|
+
Version,
|
|
7
|
+
"Wreq::Version",
|
|
8
|
+
wreq::Version,
|
|
9
|
+
HTTP_09,
|
|
10
|
+
HTTP_10,
|
|
11
|
+
HTTP_11,
|
|
12
|
+
HTTP_2,
|
|
13
|
+
HTTP_3,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
define_ruby_enum!(
|
|
17
|
+
/// An HTTP method.
|
|
18
|
+
Method,
|
|
19
|
+
"Wreq::Method",
|
|
20
|
+
wreq::Method,
|
|
21
|
+
GET,
|
|
22
|
+
HEAD,
|
|
23
|
+
POST,
|
|
24
|
+
PUT,
|
|
25
|
+
DELETE,
|
|
26
|
+
OPTIONS,
|
|
27
|
+
TRACE,
|
|
28
|
+
PATCH,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
/// HTTP status code.
|
|
32
|
+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
|
33
|
+
#[magnus::wrap(class = "Wreq::StatusCode", free_immediately, size)]
|
|
34
|
+
pub struct StatusCode(pub wreq::StatusCode);
|
|
35
|
+
|
|
36
|
+
// ===== impl Version =====
|
|
37
|
+
|
|
38
|
+
impl Version {
|
|
39
|
+
/// Convert version to string.
|
|
40
|
+
#[inline]
|
|
41
|
+
pub fn to_s(&self) -> String {
|
|
42
|
+
self.into_ffi().inspect()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Value-based equality for Ruby (`==`).
|
|
46
|
+
#[inline]
|
|
47
|
+
pub fn equals(&self, other: Value) -> bool {
|
|
48
|
+
<&Version>::try_convert(other)
|
|
49
|
+
.map(|other| *self == *other)
|
|
50
|
+
.unwrap_or(false)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl TryConvert for Version {
|
|
55
|
+
fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
|
|
56
|
+
<&Version>::try_convert(value).cloned()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ===== impl StatusCode =====
|
|
61
|
+
|
|
62
|
+
impl StatusCode {
|
|
63
|
+
/// Return the status code as an integer.
|
|
64
|
+
#[inline]
|
|
65
|
+
pub const fn as_int(&self) -> u16 {
|
|
66
|
+
self.0.as_u16()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Check if status is within 100-199.
|
|
70
|
+
#[inline]
|
|
71
|
+
pub fn is_informational(&self) -> bool {
|
|
72
|
+
self.0.is_informational()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Check if status is within 200-299.
|
|
76
|
+
#[inline]
|
|
77
|
+
pub fn is_success(&self) -> bool {
|
|
78
|
+
self.0.is_success()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Check if status is within 300-399.
|
|
82
|
+
#[inline]
|
|
83
|
+
pub fn is_redirection(&self) -> bool {
|
|
84
|
+
self.0.is_redirection()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Check if status is within 400-499.
|
|
88
|
+
#[inline]
|
|
89
|
+
pub fn is_client_error(&self) -> bool {
|
|
90
|
+
self.0.is_client_error()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Check if status is within 500-599.
|
|
94
|
+
#[inline]
|
|
95
|
+
pub fn is_server_error(&self) -> bool {
|
|
96
|
+
self.0.is_server_error()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Convert status code to string.
|
|
100
|
+
#[inline]
|
|
101
|
+
pub fn to_s(&self) -> String {
|
|
102
|
+
self.0.to_string()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
impl From<wreq::StatusCode> for StatusCode {
|
|
107
|
+
fn from(status: wreq::StatusCode) -> Self {
|
|
108
|
+
Self(status)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
113
|
+
let method_class = gem_module.define_class("Method", ruby.class_object())?;
|
|
114
|
+
method_class.const_set("GET", Method::GET)?;
|
|
115
|
+
method_class.const_set("POST", Method::POST)?;
|
|
116
|
+
method_class.const_set("PUT", Method::PUT)?;
|
|
117
|
+
method_class.const_set("DELETE", Method::DELETE)?;
|
|
118
|
+
method_class.const_set("PATCH", Method::PATCH)?;
|
|
119
|
+
method_class.const_set("HEAD", Method::HEAD)?;
|
|
120
|
+
method_class.const_set("TRACE", Method::TRACE)?;
|
|
121
|
+
method_class.const_set("OPTIONS", Method::OPTIONS)?;
|
|
122
|
+
|
|
123
|
+
let version_class = gem_module.define_class("Version", ruby.class_object())?;
|
|
124
|
+
version_class.const_set("HTTP_09", Version::HTTP_09)?;
|
|
125
|
+
version_class.const_set("HTTP_10", Version::HTTP_10)?;
|
|
126
|
+
version_class.const_set("HTTP_11", Version::HTTP_11)?;
|
|
127
|
+
version_class.const_set("HTTP_2", Version::HTTP_2)?;
|
|
128
|
+
version_class.const_set("HTTP_3", Version::HTTP_3)?;
|
|
129
|
+
version_class.define_method("to_s", method!(Version::to_s, 0))?;
|
|
130
|
+
version_class.define_method("==", method!(Version::equals, 1))?;
|
|
131
|
+
|
|
132
|
+
let status_code_class = gem_module.define_class("StatusCode", ruby.class_object())?;
|
|
133
|
+
status_code_class.define_method("as_int", method!(StatusCode::as_int, 0))?;
|
|
134
|
+
status_code_class.define_method("informational?", method!(StatusCode::is_informational, 0))?;
|
|
135
|
+
status_code_class.define_method("success?", method!(StatusCode::is_success, 0))?;
|
|
136
|
+
status_code_class.define_method("redirection?", method!(StatusCode::is_redirection, 0))?;
|
|
137
|
+
status_code_class.define_method("client_error?", method!(StatusCode::is_client_error, 0))?;
|
|
138
|
+
status_code_class.define_method("server_error?", method!(StatusCode::is_server_error, 0))?;
|
|
139
|
+
status_code_class.define_method("to_s", method!(StatusCode::to_s, 0))?;
|
|
140
|
+
|
|
141
|
+
Ok(())
|
|
142
|
+
}
|
data/src/lib.rs
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#![allow(clippy::wrong_self_convention)]
|
|
2
|
+
|
|
3
|
+
#[macro_use]
|
|
4
|
+
mod macros;
|
|
5
|
+
mod arch;
|
|
6
|
+
mod client;
|
|
7
|
+
mod cookie;
|
|
8
|
+
mod emulate;
|
|
9
|
+
mod error;
|
|
10
|
+
mod extractor;
|
|
11
|
+
mod gvl;
|
|
12
|
+
mod header;
|
|
13
|
+
mod http;
|
|
14
|
+
mod rt;
|
|
15
|
+
|
|
16
|
+
use magnus::{Error, Module, Ruby, Value};
|
|
17
|
+
|
|
18
|
+
use crate::client::{Client, resp::Response};
|
|
19
|
+
|
|
20
|
+
const RUBY_MODULE_NAME: &str = "Wreq";
|
|
21
|
+
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
22
|
+
|
|
23
|
+
/// Send a HTTP request.
|
|
24
|
+
#[inline]
|
|
25
|
+
pub fn request(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
26
|
+
Client::request(&Client::default(), args)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Send a GET request.
|
|
30
|
+
#[inline]
|
|
31
|
+
pub fn get(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
32
|
+
Client::get(&Client::default(), args)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Send a POST request.
|
|
36
|
+
#[inline]
|
|
37
|
+
pub fn post(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
38
|
+
Client::post(&Client::default(), args)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Send a PUT request.
|
|
42
|
+
#[inline]
|
|
43
|
+
pub fn put(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
44
|
+
Client::put(&Client::default(), args)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Send a DELETE request.
|
|
48
|
+
#[inline]
|
|
49
|
+
pub fn delete(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
50
|
+
Client::delete(&Client::default(), args)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Send a HEAD request.
|
|
54
|
+
#[inline]
|
|
55
|
+
pub fn head(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
56
|
+
Client::head(&Client::default(), args)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Send an OPTIONS request.
|
|
60
|
+
#[inline]
|
|
61
|
+
pub fn options(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
62
|
+
Client::options(&Client::default(), args)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Send a TRACE request.
|
|
66
|
+
#[inline]
|
|
67
|
+
pub fn trace(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
68
|
+
Client::trace(&Client::default(), args)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Send a PATCH request.
|
|
72
|
+
#[inline]
|
|
73
|
+
pub fn patch(args: &[Value]) -> Result<Response, magnus::Error> {
|
|
74
|
+
Client::patch(&Client::default(), args)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// wreq ruby binding
|
|
78
|
+
#[magnus::init]
|
|
79
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
80
|
+
let gem_module = ruby.define_module(RUBY_MODULE_NAME)?;
|
|
81
|
+
gem_module.const_set("VERSION", VERSION)?;
|
|
82
|
+
gem_module.define_module_function("request", magnus::function!(request, -1))?;
|
|
83
|
+
gem_module.define_module_function("get", magnus::function!(get, -1))?;
|
|
84
|
+
gem_module.define_module_function("post", magnus::function!(post, -1))?;
|
|
85
|
+
gem_module.define_module_function("put", magnus::function!(put, -1))?;
|
|
86
|
+
gem_module.define_module_function("delete", magnus::function!(delete, -1))?;
|
|
87
|
+
gem_module.define_module_function("head", magnus::function!(head, -1))?;
|
|
88
|
+
gem_module.define_module_function("options", magnus::function!(options, -1))?;
|
|
89
|
+
gem_module.define_module_function("trace", magnus::function!(trace, -1))?;
|
|
90
|
+
gem_module.define_module_function("patch", magnus::function!(patch, -1))?;
|
|
91
|
+
http::include(ruby, &gem_module)?;
|
|
92
|
+
header::include(ruby, &gem_module)?;
|
|
93
|
+
cookie::include(ruby, &gem_module)?;
|
|
94
|
+
client::include(ruby, &gem_module)?;
|
|
95
|
+
emulate::include(ruby, &gem_module)?;
|
|
96
|
+
error::include(ruby);
|
|
97
|
+
Ok(())
|
|
98
|
+
}
|
data/src/macros.rs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
macro_rules! apply_option {
|
|
2
|
+
(set_if_some, $builder:expr, $option:expr, $method:ident) => {
|
|
3
|
+
if let Some(value) = $option.take() {
|
|
4
|
+
$builder = $builder.$method(value);
|
|
5
|
+
}
|
|
6
|
+
};
|
|
7
|
+
(set_if_some_ref, $builder:expr, $option:expr, $method:ident) => {
|
|
8
|
+
if let Some(value) = $option.take() {
|
|
9
|
+
$builder = $builder.$method(&value);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
(set_if_some_inner, $builder:expr, $option:expr, $method:ident) => {
|
|
13
|
+
if let Some(value) = $option.take() {
|
|
14
|
+
$builder = $builder.$method(value.0);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
(set_if_some_into_inner, $builder:expr, $option:expr, $method:ident) => {
|
|
18
|
+
if let Some(value) = $option.take() {
|
|
19
|
+
$builder = $builder.$method(value.0.into_inner());
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
(set_if_some_map, $builder:expr, $option:expr, $method:ident, $transform:expr) => {
|
|
23
|
+
if let Some(value) = $option.take() {
|
|
24
|
+
$builder = $builder.$method($transform(value));
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
(set_if_some_map_ref, $builder:expr, $option:expr, $method:ident, $transform:expr) => {
|
|
28
|
+
if let Some(value) = $option.take() {
|
|
29
|
+
$builder = $builder.$method($transform(&value));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
(set_if_true, $builder:expr, $option:expr, $method:ident, $default:expr) => {
|
|
33
|
+
if $option.unwrap_or($default) {
|
|
34
|
+
$builder = $builder.$method();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
(set_if_true_with, $builder:expr, $option:expr, $method:ident, $default:expr, $value:expr) => {
|
|
38
|
+
if $option.unwrap_or($default) {
|
|
39
|
+
$builder = $builder.$method($value);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
macro_rules! define_ruby_enum {
|
|
45
|
+
($(#[$meta:meta])* $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $($variant:ident),* $(,)?) => {
|
|
46
|
+
define_ruby_enum!($(#[$meta])* $enum_type, $ruby_class, $ffi_type, $( ($variant, $variant) ),*);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
($(#[$meta:meta])* const, $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $($variant:ident),* $(,)?) => {
|
|
50
|
+
define_ruby_enum!($(#[$meta])* const, $enum_type, $ruby_class, $ffi_type, $( ($variant, $variant) ),*);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
($(#[$meta:meta])* $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => {
|
|
54
|
+
$(#[$meta])*
|
|
55
|
+
#[magnus::wrap(class = $ruby_class, free_immediately, size)]
|
|
56
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
57
|
+
#[allow(non_camel_case_types)]
|
|
58
|
+
#[allow(clippy::upper_case_acronyms)]
|
|
59
|
+
pub enum $enum_type {
|
|
60
|
+
$($rust_variant),*
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl $enum_type {
|
|
64
|
+
pub fn into_ffi(self) -> $ffi_type {
|
|
65
|
+
match self {
|
|
66
|
+
$(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)*
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[allow(dead_code)]
|
|
71
|
+
pub fn from_ffi(ffi: $ffi_type) -> Self {
|
|
72
|
+
#[allow(unreachable_patterns)]
|
|
73
|
+
match ffi {
|
|
74
|
+
$(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)*
|
|
75
|
+
_ => unreachable!(),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
($(#[$meta:meta])* const, $enum_type:ident, $ruby_class:expr, $ffi_type:ty, $(($rust_variant:ident, $ffi_variant:ident)),* $(,)?) => {
|
|
82
|
+
$(#[$meta])*
|
|
83
|
+
#[magnus::wrap(class = $ruby_class, free_immediately, size)]
|
|
84
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
85
|
+
#[allow(non_camel_case_types)]
|
|
86
|
+
#[allow(clippy::upper_case_acronyms)]
|
|
87
|
+
pub enum $enum_type {
|
|
88
|
+
$($rust_variant),*
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
impl $enum_type {
|
|
92
|
+
pub const fn into_ffi(self) -> $ffi_type {
|
|
93
|
+
match self {
|
|
94
|
+
$(<$enum_type>::$rust_variant => <$ffi_type>::$ffi_variant,)*
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[allow(dead_code)]
|
|
99
|
+
pub const fn from_ffi(ffi: $ffi_type) -> Self {
|
|
100
|
+
#[allow(unreachable_patterns)]
|
|
101
|
+
match ffi {
|
|
102
|
+
$(<$ffi_type>::$ffi_variant => <$enum_type>::$rust_variant,)*
|
|
103
|
+
_ => unreachable!(),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
macro_rules! ruby {
|
|
111
|
+
() => {
|
|
112
|
+
magnus::Ruby::get().expect("Failed to get Ruby VM instance")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
macro_rules! extract_request {
|
|
117
|
+
($args:expr, $required:ty) => {{
|
|
118
|
+
let args = magnus::scan_args::scan_args::<$required, (), (), (), magnus::RHash, ()>($args)?;
|
|
119
|
+
let required = args.required;
|
|
120
|
+
let request = crate::client::req::Request::new(&ruby!(), args.keywords)?;
|
|
121
|
+
(required, request)
|
|
122
|
+
}};
|
|
123
|
+
}
|
data/src/rt.rs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
use std::sync::LazyLock;
|
|
2
|
+
|
|
3
|
+
use tokio::runtime::{Builder, Runtime};
|
|
4
|
+
|
|
5
|
+
use crate::{error::interrupt_error, gvl};
|
|
6
|
+
|
|
7
|
+
static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
|
|
8
|
+
let mut builder = Builder::new_multi_thread();
|
|
9
|
+
|
|
10
|
+
builder
|
|
11
|
+
.enable_all()
|
|
12
|
+
.build()
|
|
13
|
+
.expect("Failed to initialize Tokio runtime")
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
enum BlockOnError<E> {
|
|
17
|
+
Interrupted,
|
|
18
|
+
Future(E),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Block on a future to completion on the global Tokio runtime.
|
|
22
|
+
///
|
|
23
|
+
/// The future runs without Ruby's GVL, so it must not construct Ruby objects or
|
|
24
|
+
/// Ruby exceptions. Convert Rust errors back into Ruby errors after the GVL has
|
|
25
|
+
/// been reacquired.
|
|
26
|
+
pub fn try_block_on<F, T, E, M>(future: F, map_err: M) -> Result<T, magnus::Error>
|
|
27
|
+
where
|
|
28
|
+
F: Future<Output = Result<T, E>>,
|
|
29
|
+
M: FnOnce(E) -> magnus::Error,
|
|
30
|
+
{
|
|
31
|
+
let result = gvl::nogvl_cancellable(|flag| {
|
|
32
|
+
RUNTIME.block_on(async move {
|
|
33
|
+
tokio::select! {
|
|
34
|
+
biased;
|
|
35
|
+
_ = flag.cancelled() => Err(BlockOnError::Interrupted),
|
|
36
|
+
result = future => result.map_err(BlockOnError::Future),
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
match result {
|
|
42
|
+
Ok(value) => Ok(value),
|
|
43
|
+
Err(BlockOnError::Interrupted) => Err(interrupt_error()),
|
|
44
|
+
Err(BlockOnError::Future(err)) => Err(map_err(err)),
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class ClientCookieProviderTest < Minitest::Test
|
|
6
|
+
HOST = HTTPBIN_URL
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
@jar = Wreq::Jar.new
|
|
10
|
+
@client = Wreq::Client.new(
|
|
11
|
+
cookie_store: true,
|
|
12
|
+
cookie_provider: @jar,
|
|
13
|
+
allow_redirects: true
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_custom_jar_captures_set_cookie_and_sends_back
|
|
18
|
+
# starts empty
|
|
19
|
+
assert_kind_of Array, @jar.get_all
|
|
20
|
+
assert_equal 0, @jar.get_all.length
|
|
21
|
+
|
|
22
|
+
# server sets cookie; client follows redirect; jar should store it
|
|
23
|
+
res1 = @client.get("#{HOST}/cookies/set?foo=bar")
|
|
24
|
+
assert_equal 200, res1.code
|
|
25
|
+
|
|
26
|
+
names = @jar.get_all.map(&:name)
|
|
27
|
+
assert_includes names, "foo"
|
|
28
|
+
|
|
29
|
+
# subsequent request should send the stored cookie automatically
|
|
30
|
+
res2 = @client.get("#{HOST}/cookies")
|
|
31
|
+
assert_equal 200, res2.code
|
|
32
|
+
cookies = httpbin_cookies(res2.json)
|
|
33
|
+
assert_kind_of Hash, cookies
|
|
34
|
+
assert_equal "bar", httpbin_fetch(cookies, "foo")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_prepopulated_jar_is_used_by_client
|
|
38
|
+
# pre-populate jar
|
|
39
|
+
@jar.add("pref=1; Path=/", "#{HOST}/")
|
|
40
|
+
|
|
41
|
+
res = @client.get("#{HOST}/cookies")
|
|
42
|
+
assert_equal 200, res.code
|
|
43
|
+
cookies = httpbin_cookies(res.json)
|
|
44
|
+
assert_equal "1", httpbin_fetch(cookies, "pref")
|
|
45
|
+
end
|
|
46
|
+
end
|
data/test/client_test.rb
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class ClientTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@client = Wreq::Client.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_client_creation
|
|
9
|
+
refute_nil @client
|
|
10
|
+
assert_instance_of Wreq::Client, @client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_client_with_configuration
|
|
14
|
+
client = Wreq::Client.new(
|
|
15
|
+
timeout: 30,
|
|
16
|
+
headers: {"User-Agent" => "wreq-ruby/test"}
|
|
17
|
+
)
|
|
18
|
+
refute_nil client
|
|
19
|
+
assert_instance_of Wreq::Client, client
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_get_request
|
|
23
|
+
response = @client.get("#{HTTPBIN_URL}/get")
|
|
24
|
+
refute_nil response
|
|
25
|
+
assert_equal 200, response.code
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_post_request
|
|
29
|
+
response = @client.post("#{HTTPBIN_URL}/post",
|
|
30
|
+
json: {test: "wreq-ruby"})
|
|
31
|
+
refute_nil response
|
|
32
|
+
assert_equal 200, response.code
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_put_request
|
|
36
|
+
response = @client.put("#{HTTPBIN_URL}/put",
|
|
37
|
+
json: {data: "test"})
|
|
38
|
+
refute_nil response
|
|
39
|
+
assert_equal 200, response.code
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_delete_request
|
|
43
|
+
response = @client.delete("#{HTTPBIN_URL}/delete")
|
|
44
|
+
refute_nil response
|
|
45
|
+
assert_equal 200, response.code
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_patch_request
|
|
49
|
+
response = @client.patch("#{HTTPBIN_URL}/patch",
|
|
50
|
+
json: {update: "field"})
|
|
51
|
+
refute_nil response
|
|
52
|
+
assert_equal 200, response.code
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_request_with_query_params
|
|
56
|
+
response = @client.get("#{HTTPBIN_URL}/get",
|
|
57
|
+
query: {"param" => "value"})
|
|
58
|
+
refute_nil response
|
|
59
|
+
assert_equal 200, response.code
|
|
60
|
+
assert_includes response.text, "param"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_request_with_form_data
|
|
64
|
+
response = @client.post("#{HTTPBIN_URL}/post",
|
|
65
|
+
form: {"field" => "value"})
|
|
66
|
+
refute_nil response
|
|
67
|
+
assert_equal 200, response.code
|
|
68
|
+
assert_includes response.text, "field"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_request_with_raw_body
|
|
72
|
+
response = @client.post("#{HTTPBIN_URL}/post",
|
|
73
|
+
body: "raw content",
|
|
74
|
+
headers: {"Content-Type" => "text/plain"})
|
|
75
|
+
refute_nil response
|
|
76
|
+
assert_equal 200, response.code
|
|
77
|
+
assert_includes response.text, "raw content"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_basic_authentication
|
|
81
|
+
response = @client.get("#{HTTPBIN_URL}/basic-auth/user/pass",
|
|
82
|
+
basic_auth: ["user", "pass"])
|
|
83
|
+
refute_nil response
|
|
84
|
+
assert_equal 200, response.code
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_bearer_authentication
|
|
88
|
+
response = @client.get("#{HTTPBIN_URL}/bearer",
|
|
89
|
+
bearer_auth: "test-token")
|
|
90
|
+
refute_nil response
|
|
91
|
+
assert_equal 200, response.code
|
|
92
|
+
assert_includes response.text, "test-token"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_redirect_following
|
|
96
|
+
response = @client.get("#{HTTPBIN_URL}/redirect/1",
|
|
97
|
+
allow_redirects: true)
|
|
98
|
+
refute_nil response
|
|
99
|
+
assert_equal 200, response.code
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_redirect_blocking
|
|
103
|
+
response = @client.get("#{HTTPBIN_URL}/redirect/1",
|
|
104
|
+
allow_redirects: false)
|
|
105
|
+
refute_nil response
|
|
106
|
+
assert_equal 302, response.code
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_gzip_compression
|
|
110
|
+
response = @client.get("#{HTTPBIN_URL}/gzip", gzip: true)
|
|
111
|
+
refute_nil response
|
|
112
|
+
assert_equal 200, response.code
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_timeout_handling
|
|
116
|
+
# Test that short timeouts properly raise exceptions
|
|
117
|
+
assert_raises(Wreq::TimeoutError) do
|
|
118
|
+
@client.get("#{HTTPBIN_URL}/delay/10", timeout: 1)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Test that reasonable timeouts work normally
|
|
122
|
+
start_time = Time.now
|
|
123
|
+
response = @client.get("#{HTTPBIN_URL}/delay/1", timeout: 5)
|
|
124
|
+
elapsed = Time.now - start_time
|
|
125
|
+
|
|
126
|
+
refute_nil response
|
|
127
|
+
assert_equal 200, response.code
|
|
128
|
+
assert elapsed < 5, "Request should complete within timeout"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_status_codes
|
|
132
|
+
response = @client.get("#{HTTPBIN_URL}/status/404")
|
|
133
|
+
refute_nil response
|
|
134
|
+
assert_equal 404, response.code
|
|
135
|
+
end
|
|
136
|
+
end
|