wreq 1.1.0 → 1.2.1
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 +216 -142
- data/Cargo.toml +5 -5
- data/README.md +5 -5
- data/examples/cookie.rb +24 -0
- data/examples/{emulation_request.rb → emulate_request.rb} +8 -8
- data/lib/wreq.rb +54 -64
- data/lib/wreq_ruby/client.rb +82 -72
- data/lib/wreq_ruby/cookie.rb +21 -9
- data/lib/wreq_ruby/{emulation.rb → emulate.rb} +57 -29
- data/lib/wreq_ruby/header.rb +8 -0
- data/lib/wreq_ruby/http.rb +28 -0
- data/lib/wreq_ruby/response.rb +22 -21
- data/src/client/body/stream.rs +9 -14
- data/src/client/req.rs +53 -42
- data/src/client/resp.rs +46 -40
- data/src/client.rs +44 -35
- data/src/cookie.rs +46 -11
- data/src/emulate.rs +183 -168
- data/src/error.rs +16 -0
- data/src/extractor.rs +3 -60
- data/src/header.rs +61 -7
- data/src/http.rs +16 -1
- data/src/macros.rs +5 -0
- data/src/rt.rs +0 -18
- data/test/client_cookie_test.rb +1 -1
- data/test/cookie_test.rb +30 -16
- data/test/emulation_test.rb +8 -8
- data/test/error_handling_test.rb +4 -1
- data/test/inspect_test.rb +125 -0
- data/test/orig_header_test.rb +115 -0
- data/test/request_test.rb +10 -0
- data/test/stream_test.rb +292 -2
- metadata +6 -3
data/lib/wreq_ruby/response.rb
CHANGED
|
@@ -88,26 +88,16 @@ unless defined?(Wreq)
|
|
|
88
88
|
def bytes
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# Get the response body as text.
|
|
92
|
-
#
|
|
93
|
-
# @return [String] Response body decoded as UTF-8 text
|
|
94
|
-
# @example
|
|
95
|
-
# html = response.text
|
|
96
|
-
# puts html
|
|
97
|
-
# @raise [Wreq::DecodingError] if body cannot be decoded as binary
|
|
98
|
-
def text
|
|
99
|
-
end
|
|
100
|
-
|
|
101
91
|
# Get the response body as text with a specific charset.
|
|
102
92
|
# This method allows you to specify a default encoding
|
|
103
93
|
# to use when decoding the response body.
|
|
104
94
|
# # @param default_encoding [String] Default encoding to use (e.g., "UTF-8")
|
|
105
95
|
# # @return [String] Response body decoded as text using the specified encoding
|
|
106
96
|
# @example
|
|
107
|
-
# html = response.
|
|
97
|
+
# html = response.text("ISO-8859-1")
|
|
108
98
|
# puts html
|
|
109
99
|
# @raise [Wreq::DecodingError] if body cannot be decoded with the specified encoding
|
|
110
|
-
def
|
|
100
|
+
def text(default_encoding: "UTF-8")
|
|
111
101
|
end
|
|
112
102
|
|
|
113
103
|
# Parse the response body as JSON.
|
|
@@ -120,14 +110,17 @@ unless defined?(Wreq)
|
|
|
120
110
|
def json
|
|
121
111
|
end
|
|
122
112
|
|
|
123
|
-
#
|
|
113
|
+
# Stream the response body, yielding each chunk to the given block.
|
|
124
114
|
#
|
|
125
115
|
# This method allows you to process large HTTP responses efficiently,
|
|
126
116
|
# by yielding each chunk of the body as it arrives, without loading
|
|
127
117
|
# the entire response into memory.
|
|
128
118
|
#
|
|
129
|
-
# @return
|
|
119
|
+
# @return [nil]
|
|
130
120
|
# @yield [chunk] Each chunk of the response body as a binary String
|
|
121
|
+
# @raise [LocalJumpError] if called without a block
|
|
122
|
+
# @raise [Wreq::TimeoutError, Wreq::BodyError, Wreq::ConnectionResetError, Wreq::RequestError]
|
|
123
|
+
# if streaming fails while reading the response body
|
|
131
124
|
# @example Save response to file
|
|
132
125
|
# File.open("output.bin", "wb") do |f|
|
|
133
126
|
# response.chunks { |chunk| f.write(chunk) }
|
|
@@ -137,7 +130,7 @@ unless defined?(Wreq)
|
|
|
137
130
|
# response.chunks { |chunk| total += chunk.bytesize }
|
|
138
131
|
# puts "Downloaded #{total} bytes"
|
|
139
132
|
#
|
|
140
|
-
#
|
|
133
|
+
# Exceptions raised inside the block are propagated to the caller.
|
|
141
134
|
def chunks
|
|
142
135
|
end
|
|
143
136
|
|
|
@@ -156,27 +149,35 @@ end
|
|
|
156
149
|
|
|
157
150
|
module Wreq
|
|
158
151
|
class Response
|
|
159
|
-
# Returns
|
|
152
|
+
# Returns the response body as a string.
|
|
153
|
+
#
|
|
154
|
+
# @return [String] Response body text
|
|
155
|
+
# @example
|
|
156
|
+
# puts response.to_s
|
|
157
|
+
# puts response
|
|
158
|
+
# File.write("page.html", response)
|
|
159
|
+
def to_s
|
|
160
|
+
text
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns a compact string representation for debugging.
|
|
160
164
|
#
|
|
161
165
|
# Format: #<Wreq::Response STATUS content-type="..." body=SIZE>
|
|
162
166
|
#
|
|
163
167
|
# @return [String] Compact formatted response information
|
|
164
168
|
# @example
|
|
165
|
-
#
|
|
169
|
+
# p response
|
|
166
170
|
# # => #<Wreq::Response 200 content-type="application/json" body=456B>
|
|
167
|
-
def
|
|
171
|
+
def inspect
|
|
168
172
|
parts = ["#<Wreq::Response"]
|
|
169
173
|
|
|
170
|
-
# Status code
|
|
171
174
|
parts << code.to_s
|
|
172
175
|
|
|
173
|
-
# Content-Type header if present
|
|
174
176
|
if headers.respond_to?(:get)
|
|
175
177
|
content_type = headers.get("content-type")
|
|
176
178
|
parts << "content-type=#{content_type.inspect}" if content_type
|
|
177
179
|
end
|
|
178
180
|
|
|
179
|
-
# Body size
|
|
180
181
|
if content_length
|
|
181
182
|
parts << "body=#{format_bytes(content_length)}"
|
|
182
183
|
end
|
data/src/client/body/stream.rs
CHANGED
|
@@ -13,7 +13,7 @@ use tokio::sync::{
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
use crate::{
|
|
16
|
-
error::{memory_error, mpsc_send_error_to_magnus},
|
|
16
|
+
error::{memory_error, mpsc_send_error_to_magnus, wreq_error_to_magnus},
|
|
17
17
|
rt,
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -37,20 +37,15 @@ impl BodyReceiver {
|
|
|
37
37
|
pub fn new(stream: impl Stream<Item = wreq::Result<Bytes>> + Send + 'static) -> BodyReceiver {
|
|
38
38
|
BodyReceiver(Mutex::new(Box::pin(stream)))
|
|
39
39
|
}
|
|
40
|
-
}
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.as_mut()
|
|
51
|
-
.next()
|
|
52
|
-
.await
|
|
53
|
-
.and_then(|r| r.ok())
|
|
41
|
+
/// Read the next body chunk, converting stream errors into Ruby errors.
|
|
42
|
+
pub fn next(&self) -> Result<Option<Bytes>, Error> {
|
|
43
|
+
rt::try_block_on(async {
|
|
44
|
+
match self.0.lock().await.as_mut().next().await {
|
|
45
|
+
Some(Ok(data)) => Ok(Some(data)),
|
|
46
|
+
Some(Err(err)) => Err(wreq_error_to_magnus(err)),
|
|
47
|
+
None => Ok(None),
|
|
48
|
+
}
|
|
54
49
|
})
|
|
55
50
|
}
|
|
56
51
|
}
|
data/src/client/req.rs
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
use std::{net::IpAddr, time::Duration};
|
|
2
2
|
|
|
3
|
-
use http::
|
|
3
|
+
use http::header;
|
|
4
4
|
use magnus::{RHash, TryConvert, typed_data::Obj, value::ReprValue};
|
|
5
5
|
use serde::Deserialize;
|
|
6
|
-
use wreq::{
|
|
7
|
-
Client, Proxy, Version,
|
|
8
|
-
header::{HeaderMap, OrigHeaderMap},
|
|
9
|
-
};
|
|
6
|
+
use wreq::{Client, Proxy};
|
|
10
7
|
|
|
11
8
|
use super::body::{Body, Form, Json};
|
|
12
9
|
use crate::{
|
|
13
10
|
client::{query::Query, resp::Response},
|
|
11
|
+
cookie::Cookies,
|
|
14
12
|
emulate::Emulation,
|
|
15
13
|
error::wreq_error_to_magnus,
|
|
16
14
|
extractor::Extractor,
|
|
17
|
-
|
|
15
|
+
header::{Headers, OrigHeaders},
|
|
16
|
+
http::{Method, Version},
|
|
18
17
|
rt,
|
|
19
18
|
};
|
|
20
19
|
|
|
@@ -46,20 +45,20 @@ pub struct Request {
|
|
|
46
45
|
#[serde(skip)]
|
|
47
46
|
version: Option<Version>,
|
|
48
47
|
|
|
48
|
+
/// The option enables default headers.
|
|
49
|
+
default_headers: Option<bool>,
|
|
50
|
+
|
|
49
51
|
/// The headers to use for the request.
|
|
50
52
|
#[serde(skip)]
|
|
51
|
-
headers: Option<
|
|
53
|
+
headers: Option<Headers>,
|
|
52
54
|
|
|
53
55
|
/// The original headers to use for the request.
|
|
54
56
|
#[serde(skip)]
|
|
55
|
-
orig_headers: Option<
|
|
56
|
-
|
|
57
|
-
/// The option enables default headers.
|
|
58
|
-
default_headers: Option<bool>,
|
|
57
|
+
orig_headers: Option<OrigHeaders>,
|
|
59
58
|
|
|
60
59
|
/// The cookies to use for the request.
|
|
61
60
|
#[serde(skip)]
|
|
62
|
-
cookies: Option<
|
|
61
|
+
cookies: Option<Cookies>,
|
|
63
62
|
|
|
64
63
|
/// Whether to allow redirects.
|
|
65
64
|
allow_redirects: Option<bool>,
|
|
@@ -105,35 +104,36 @@ pub struct Request {
|
|
|
105
104
|
impl Request {
|
|
106
105
|
/// Create a new [`Request`] from Ruby keyword arguments.
|
|
107
106
|
pub fn new(ruby: &magnus::Ruby, hash: RHash) -> Result<Self, magnus::Error> {
|
|
108
|
-
let
|
|
109
|
-
let mut builder: Self = serde_magnus::deserialize(ruby,
|
|
107
|
+
let keyword = hash.as_value();
|
|
108
|
+
let mut builder: Self = serde_magnus::deserialize(ruby, keyword)?;
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
builder.emulation = Some((*emulation_obj).clone());
|
|
110
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(emulation))) {
|
|
111
|
+
let obj = Obj::<Emulation>::try_convert(v)?;
|
|
112
|
+
builder.emulation = Some((*obj).clone());
|
|
115
113
|
}
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// extra headers handling
|
|
121
|
-
builder.headers = Extractor::<HeaderMap>::try_convert(kwargs)?.into_inner();
|
|
115
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(version))) {
|
|
116
|
+
builder.version = Some(Version::try_convert(v)?);
|
|
117
|
+
}
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(headers))) {
|
|
120
|
+
builder.headers = Some(Headers::try_convert(v)?);
|
|
121
|
+
}
|
|
125
122
|
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(orig_headers))) {
|
|
124
|
+
builder.orig_headers = Some(OrigHeaders::try_convert(v)?);
|
|
125
|
+
}
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookies))) {
|
|
128
|
+
builder.cookies = Some(Cookies::try_convert(v)?);
|
|
129
|
+
}
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
builder.body = Some(Body::try_convert(body)?);
|
|
131
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(body))) {
|
|
132
|
+
builder.body = Some(Body::try_convert(v)?);
|
|
135
133
|
}
|
|
136
134
|
|
|
135
|
+
builder.proxy = Extractor::<Proxy>::try_convert(keyword)?.into_inner();
|
|
136
|
+
|
|
137
137
|
Ok(builder)
|
|
138
138
|
}
|
|
139
139
|
}
|
|
@@ -151,7 +151,13 @@ pub fn execute_request<U: AsRef<str>>(
|
|
|
151
151
|
apply_option!(set_if_some_inner, builder, request.emulation, emulation);
|
|
152
152
|
|
|
153
153
|
// Version options.
|
|
154
|
-
apply_option!(
|
|
154
|
+
apply_option!(
|
|
155
|
+
set_if_some_map,
|
|
156
|
+
builder,
|
|
157
|
+
request.version,
|
|
158
|
+
version,
|
|
159
|
+
Version::into_ffi
|
|
160
|
+
);
|
|
155
161
|
|
|
156
162
|
// Timeout options.
|
|
157
163
|
apply_option!(
|
|
@@ -175,8 +181,13 @@ pub fn execute_request<U: AsRef<str>>(
|
|
|
175
181
|
apply_option!(set_if_some, builder, request.interface, interface);
|
|
176
182
|
|
|
177
183
|
// Headers options.
|
|
178
|
-
apply_option!(
|
|
179
|
-
apply_option!(
|
|
184
|
+
apply_option!(set_if_some_into_inner, builder, request.headers, headers);
|
|
185
|
+
apply_option!(
|
|
186
|
+
set_if_some_inner,
|
|
187
|
+
builder,
|
|
188
|
+
request.orig_headers,
|
|
189
|
+
orig_headers
|
|
190
|
+
);
|
|
180
191
|
apply_option!(
|
|
181
192
|
set_if_some,
|
|
182
193
|
builder,
|
|
@@ -184,6 +195,13 @@ pub fn execute_request<U: AsRef<str>>(
|
|
|
184
195
|
default_headers
|
|
185
196
|
);
|
|
186
197
|
|
|
198
|
+
// Cookies options.
|
|
199
|
+
if let Some(cookies) = request.cookies.take() {
|
|
200
|
+
for cookie in cookies.0 {
|
|
201
|
+
builder = builder.header(header::COOKIE, cookie);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
187
205
|
// Authentication options.
|
|
188
206
|
apply_option!(
|
|
189
207
|
set_if_some_map_ref,
|
|
@@ -197,13 +215,6 @@ pub fn execute_request<U: AsRef<str>>(
|
|
|
197
215
|
builder = builder.basic_auth(basic_auth.0, basic_auth.1);
|
|
198
216
|
}
|
|
199
217
|
|
|
200
|
-
// Cookies options.
|
|
201
|
-
if let Some(cookies) = request.cookies.take() {
|
|
202
|
-
for cookie in cookies {
|
|
203
|
-
builder = builder.header(header::COOKIE, cookie);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
218
|
// Allow redirects options.
|
|
208
219
|
match request.allow_redirects {
|
|
209
220
|
Some(false) => {
|
data/src/client/resp.rs
CHANGED
|
@@ -5,14 +5,14 @@ use bytes::Bytes;
|
|
|
5
5
|
use futures_util::TryFutureExt;
|
|
6
6
|
use http::{Extensions, HeaderMap, response::Response as HttpResponse};
|
|
7
7
|
use http_body_util::BodyExt;
|
|
8
|
-
use magnus::{Error, Module, RArray, RModule, Ruby, Value,
|
|
8
|
+
use magnus::{Error, Module, RArray, RModule, Ruby, Value, scan_args::scan_args};
|
|
9
9
|
use wreq::Uri;
|
|
10
10
|
|
|
11
11
|
use crate::{
|
|
12
12
|
client::body::{BodyReceiver, Json},
|
|
13
13
|
cookie::Cookie,
|
|
14
|
-
error::{memory_error, wreq_error_to_magnus},
|
|
15
|
-
gvl,
|
|
14
|
+
error::{memory_error, no_block_given_error, wreq_error_to_magnus},
|
|
15
|
+
gvl::{self, nogvl},
|
|
16
16
|
header::Headers,
|
|
17
17
|
http::{StatusCode, Version},
|
|
18
18
|
rt,
|
|
@@ -173,20 +173,18 @@ impl Response {
|
|
|
173
173
|
rt::try_block_on(response.bytes().map_err(wreq_error_to_magnus))
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
/// Get the response body as a UTF-8 string.
|
|
177
|
-
pub fn text(&self) -> Result<String, Error> {
|
|
178
|
-
let response = self.response(false)?;
|
|
179
|
-
rt::try_block_on(response.text().map_err(wreq_error_to_magnus))
|
|
180
|
-
}
|
|
181
|
-
|
|
182
176
|
/// Get the full response text given a specific encoding.
|
|
183
|
-
pub fn
|
|
177
|
+
pub fn text(&self, args: &[Value]) -> Result<String, Error> {
|
|
178
|
+
let args = scan_args::<(), (Option<String>,), (), (), (), ()>(args)?;
|
|
184
179
|
let response = self.response(false)?;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
180
|
+
match args.optional.0 {
|
|
181
|
+
Some(encoding) => rt::try_block_on(
|
|
182
|
+
response
|
|
183
|
+
.text_with_charset(encoding)
|
|
184
|
+
.map_err(wreq_error_to_magnus),
|
|
185
|
+
),
|
|
186
|
+
None => rt::try_block_on(response.text().map_err(wreq_error_to_magnus)),
|
|
187
|
+
}
|
|
190
188
|
}
|
|
191
189
|
|
|
192
190
|
/// Get the response body as JSON.
|
|
@@ -201,12 +199,24 @@ impl Response {
|
|
|
201
199
|
})
|
|
202
200
|
}
|
|
203
201
|
|
|
204
|
-
///
|
|
205
|
-
pub fn chunks(&
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
202
|
+
/// Yield response body chunks to the given Ruby block.
|
|
203
|
+
pub fn chunks(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
204
|
+
if !ruby.block_given() {
|
|
205
|
+
return Err(no_block_given_error());
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let receiver = nogvl(|| {
|
|
209
|
+
rb_self
|
|
210
|
+
.response(true)
|
|
211
|
+
.map(wreq::Response::bytes_stream)
|
|
212
|
+
.map(BodyReceiver::new)
|
|
213
|
+
})?;
|
|
214
|
+
|
|
215
|
+
while let Some(chunk) = receiver.next()? {
|
|
216
|
+
let _: Value = ruby.yield_value(chunk)?;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
Ok(())
|
|
210
220
|
}
|
|
211
221
|
|
|
212
222
|
/// Close the response body, dropping any resources.
|
|
@@ -224,27 +234,23 @@ impl Drop for Response {
|
|
|
224
234
|
}
|
|
225
235
|
|
|
226
236
|
pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
227
|
-
let
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
237
|
+
let response = gem_module.define_class("Response", ruby.class_object())?;
|
|
238
|
+
response.define_method("code", magnus::method!(Response::code, 0))?;
|
|
239
|
+
response.define_method("status", magnus::method!(Response::status, 0))?;
|
|
240
|
+
response.define_method("version", magnus::method!(Response::version, 0))?;
|
|
241
|
+
response.define_method("url", magnus::method!(Response::url, 0))?;
|
|
242
|
+
response.define_method(
|
|
233
243
|
"content_length",
|
|
234
244
|
magnus::method!(Response::content_length, 0),
|
|
235
245
|
)?;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
)?;
|
|
246
|
-
response_class.define_method("json", magnus::method!(Response::json, 0))?;
|
|
247
|
-
response_class.define_method("chunks", magnus::method!(Response::chunks, 0))?;
|
|
248
|
-
response_class.define_method("close", magnus::method!(Response::close, 0))?;
|
|
246
|
+
response.define_method("cookies", magnus::method!(Response::cookies, 0))?;
|
|
247
|
+
response.define_method("headers", magnus::method!(Response::headers, 0))?;
|
|
248
|
+
response.define_method("local_addr", magnus::method!(Response::local_addr, 0))?;
|
|
249
|
+
response.define_method("remote_addr", magnus::method!(Response::remote_addr, 0))?;
|
|
250
|
+
response.define_method("bytes", magnus::method!(Response::bytes, 0))?;
|
|
251
|
+
response.define_method("text", magnus::method!(Response::text, -1))?;
|
|
252
|
+
response.define_method("json", magnus::method!(Response::json, 0))?;
|
|
253
|
+
response.define_method("chunks", magnus::method!(Response::chunks, 0))?;
|
|
254
|
+
response.define_method("close", magnus::method!(Response::close, 0))?;
|
|
249
255
|
Ok(())
|
|
250
256
|
}
|
data/src/client.rs
CHANGED
|
@@ -10,10 +10,7 @@ use magnus::{
|
|
|
10
10
|
Module, Object, RHash, RModule, Ruby, TryConvert, Value, function, method, typed_data::Obj,
|
|
11
11
|
};
|
|
12
12
|
use serde::Deserialize;
|
|
13
|
-
use wreq::{
|
|
14
|
-
Proxy,
|
|
15
|
-
header::{HeaderMap, HeaderValue, OrigHeaderMap},
|
|
16
|
-
};
|
|
13
|
+
use wreq::{Proxy, header::HeaderValue};
|
|
17
14
|
|
|
18
15
|
use crate::{
|
|
19
16
|
client::{req::execute_request, resp::Response},
|
|
@@ -22,6 +19,7 @@ use crate::{
|
|
|
22
19
|
error::wreq_error_to_magnus,
|
|
23
20
|
extractor::Extractor,
|
|
24
21
|
gvl,
|
|
22
|
+
header::{Headers, OrigHeaders},
|
|
25
23
|
http::Method,
|
|
26
24
|
};
|
|
27
25
|
|
|
@@ -36,10 +34,10 @@ struct Builder {
|
|
|
36
34
|
user_agent: Option<HeaderValue>,
|
|
37
35
|
/// The headers to use for the client.
|
|
38
36
|
#[serde(skip)]
|
|
39
|
-
headers: Option<
|
|
37
|
+
headers: Option<Headers>,
|
|
40
38
|
/// The original headers to use for the client.
|
|
41
39
|
#[serde(skip)]
|
|
42
|
-
orig_headers: Option<
|
|
40
|
+
orig_headers: Option<OrigHeaders>,
|
|
43
41
|
/// Whether to use referer.
|
|
44
42
|
referer: Option<bool>,
|
|
45
43
|
/// Whether to allow redirects.
|
|
@@ -83,7 +81,7 @@ struct Builder {
|
|
|
83
81
|
/// Sets the maximum idle connection per host allowed in the pool.
|
|
84
82
|
pool_max_idle_per_host: Option<usize>,
|
|
85
83
|
/// Sets the maximum number of connections in the pool.
|
|
86
|
-
pool_max_size: Option<
|
|
84
|
+
pool_max_size: Option<usize>,
|
|
87
85
|
|
|
88
86
|
// ========= Protocol options =========
|
|
89
87
|
/// Whether to use the HTTP/1 protocol only.
|
|
@@ -128,35 +126,36 @@ pub struct Client(wreq::Client);
|
|
|
128
126
|
impl Builder {
|
|
129
127
|
/// Create a new [`Builder`] from Ruby keyword arguments.
|
|
130
128
|
fn new(ruby: &magnus::Ruby, keyword: &Value) -> Result<Self, magnus::Error> {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if let Some(v) = hash.get(ruby.to_symbol("emulation")) {
|
|
135
|
-
let emulation_obj = Obj::<Emulation>::try_convert(v)?;
|
|
136
|
-
builder.emulation = Some((*emulation_obj).clone());
|
|
137
|
-
}
|
|
129
|
+
let Ok(hash) = RHash::try_convert(*keyword) else {
|
|
130
|
+
return Ok(Default::default());
|
|
131
|
+
};
|
|
138
132
|
|
|
139
|
-
|
|
140
|
-
builder.user_agent = Extractor::<HeaderValue>::try_convert(*keyword)?.into_inner();
|
|
133
|
+
let mut builder: Self = serde_magnus::deserialize(ruby, hash)?;
|
|
141
134
|
|
|
142
|
-
|
|
143
|
-
builder.
|
|
135
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(emulation))) {
|
|
136
|
+
builder.emulation = Some((*Obj::<Emulation>::try_convert(v)?).clone());
|
|
137
|
+
}
|
|
144
138
|
|
|
145
|
-
|
|
146
|
-
builder.
|
|
139
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(headers))) {
|
|
140
|
+
builder.headers = Some(Headers::try_convert(v)?);
|
|
141
|
+
}
|
|
147
142
|
|
|
148
|
-
|
|
149
|
-
builder.
|
|
143
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(orig_headers))) {
|
|
144
|
+
builder.orig_headers = Some(OrigHeaders::try_convert(v)?);
|
|
145
|
+
}
|
|
150
146
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
147
|
+
if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) {
|
|
148
|
+
builder.cookie_provider = Some((*Obj::<Jar>::try_convert(v)?).clone());
|
|
149
|
+
}
|
|
155
150
|
|
|
156
|
-
|
|
151
|
+
if let Some(jar) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) {
|
|
152
|
+
builder.cookie_provider = Some((*Obj::<Jar>::try_convert(jar)?).clone());
|
|
157
153
|
}
|
|
158
154
|
|
|
159
|
-
|
|
155
|
+
builder.user_agent = Extractor::<HeaderValue>::try_convert(*keyword)?.into_inner();
|
|
156
|
+
builder.proxy = Extractor::<Proxy>::try_convert(*keyword)?.into_inner();
|
|
157
|
+
|
|
158
|
+
Ok(builder)
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
161
|
|
|
@@ -164,9 +163,9 @@ impl Builder {
|
|
|
164
163
|
|
|
165
164
|
impl Client {
|
|
166
165
|
/// Create a new [`Client`] with the given keyword arguments.
|
|
167
|
-
pub fn new(ruby: &Ruby,
|
|
168
|
-
if let Some(
|
|
169
|
-
let mut params = Builder::new(ruby,
|
|
166
|
+
pub fn new(ruby: &Ruby, keyword: &[Value]) -> Result<Self, magnus::Error> {
|
|
167
|
+
if let Some(keyword) = keyword.first() {
|
|
168
|
+
let mut params = Builder::new(ruby, keyword)?;
|
|
170
169
|
gvl::nogvl(|| {
|
|
171
170
|
let mut builder = wreq::Client::builder();
|
|
172
171
|
|
|
@@ -176,9 +175,19 @@ impl Client {
|
|
|
176
175
|
// User agent options.
|
|
177
176
|
apply_option!(set_if_some, builder, params.user_agent, user_agent);
|
|
178
177
|
|
|
179
|
-
//
|
|
180
|
-
apply_option!(
|
|
181
|
-
|
|
178
|
+
// Headers options.
|
|
179
|
+
apply_option!(
|
|
180
|
+
set_if_some_into_inner,
|
|
181
|
+
builder,
|
|
182
|
+
params.headers,
|
|
183
|
+
default_headers
|
|
184
|
+
);
|
|
185
|
+
apply_option!(
|
|
186
|
+
set_if_some_inner,
|
|
187
|
+
builder,
|
|
188
|
+
params.orig_headers,
|
|
189
|
+
orig_headers
|
|
190
|
+
);
|
|
182
191
|
|
|
183
192
|
// Allow redirects options.
|
|
184
193
|
apply_option!(set_if_some, builder, params.referer, referer);
|
|
@@ -286,7 +295,7 @@ impl Client {
|
|
|
286
295
|
apply_option!(set_if_some, builder, params.https_only, https_only);
|
|
287
296
|
|
|
288
297
|
// TLS options.
|
|
289
|
-
apply_option!(set_if_some, builder, params.verify,
|
|
298
|
+
apply_option!(set_if_some, builder, params.verify, tls_cert_verification);
|
|
290
299
|
|
|
291
300
|
// Network options.
|
|
292
301
|
apply_option!(set_if_some, builder, params.proxy, proxy);
|
data/src/cookie.rs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
use std::{fmt, sync::Arc, time::SystemTime};
|
|
2
2
|
|
|
3
|
+
use bytes::Bytes;
|
|
3
4
|
use cookie::{Cookie as RawCookie, Expiration, ParseError, time::Duration};
|
|
4
5
|
use magnus::{
|
|
5
|
-
Error, Module, Object, RModule, Ruby, Value, function, method,
|
|
6
|
-
value::ReprValue,
|
|
6
|
+
Error, Module, Object, RHash, RModule, RString, Ruby, TryConvert, Value, function, method,
|
|
7
|
+
r_hash::ForEach, typed_data::Obj, value::ReprValue,
|
|
7
8
|
};
|
|
8
9
|
use wreq::header::{self, HeaderMap, HeaderValue};
|
|
9
10
|
|
|
10
|
-
use crate::gvl;
|
|
11
|
+
use crate::{error::header_value_error_to_magnus, gvl};
|
|
11
12
|
|
|
12
13
|
define_ruby_enum!(
|
|
13
14
|
/// The Cookie SameSite attribute.
|
|
@@ -25,6 +26,10 @@ define_ruby_enum!(
|
|
|
25
26
|
#[magnus::wrap(class = "Wreq::Cookie", free_immediately, size)]
|
|
26
27
|
pub struct Cookie(RawCookie<'static>);
|
|
27
28
|
|
|
29
|
+
/// A collection of HTTP cookies.
|
|
30
|
+
#[derive(Default)]
|
|
31
|
+
pub struct Cookies(pub Vec<HeaderValue>);
|
|
32
|
+
|
|
28
33
|
/// A good default `CookieStore` implementation.
|
|
29
34
|
///
|
|
30
35
|
/// This is the implementation used when simply calling `cookie_store(true)`.
|
|
@@ -197,6 +202,36 @@ impl fmt::Display for Cookie {
|
|
|
197
202
|
}
|
|
198
203
|
}
|
|
199
204
|
|
|
205
|
+
// ===== impl Cookies =====
|
|
206
|
+
|
|
207
|
+
impl TryConvert for Cookies {
|
|
208
|
+
fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
|
|
209
|
+
// try extract uncompressed cookies
|
|
210
|
+
if let Some(rhash) = RHash::from_value(value) {
|
|
211
|
+
let mut cookies = Vec::new();
|
|
212
|
+
rhash.foreach(|name: RString, value: RString| {
|
|
213
|
+
let cookie = format!("{name}={value}");
|
|
214
|
+
let header_value = HeaderValue::from_maybe_shared(Bytes::from(cookie))
|
|
215
|
+
.map_err(header_value_error_to_magnus)?;
|
|
216
|
+
cookies.push(header_value);
|
|
217
|
+
Ok(ForEach::Continue)
|
|
218
|
+
})?;
|
|
219
|
+
|
|
220
|
+
return Ok(Self(cookies));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// try extract compressed cookies
|
|
224
|
+
if let Some(cookies) = RString::from_value(value) {
|
|
225
|
+
return Ok(Self(vec![
|
|
226
|
+
HeaderValue::from_maybe_shared(cookies.to_bytes())
|
|
227
|
+
.map_err(header_value_error_to_magnus)?,
|
|
228
|
+
]));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Ok(Self::default())
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
200
235
|
// ===== impl Jar =====
|
|
201
236
|
|
|
202
237
|
impl Jar {
|
|
@@ -221,13 +256,14 @@ impl Jar {
|
|
|
221
256
|
}
|
|
222
257
|
|
|
223
258
|
/// Add a cookie to this jar.
|
|
224
|
-
pub fn
|
|
225
|
-
|
|
226
|
-
|
|
259
|
+
pub fn add(&self, cookie: Value, url: String) {
|
|
260
|
+
if let Ok(cookie) = Obj::<Cookie>::try_convert(cookie) {
|
|
261
|
+
gvl::nogvl(|| self.0.add(cookie.0.clone(), &url))
|
|
262
|
+
}
|
|
227
263
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
264
|
+
if let Ok(cookie_str) = String::try_convert(cookie) {
|
|
265
|
+
gvl::nogvl(|| self.0.add(cookie_str.as_ref(), &url))
|
|
266
|
+
}
|
|
231
267
|
}
|
|
232
268
|
|
|
233
269
|
/// Remove a cookie from this jar by name and URL.
|
|
@@ -268,8 +304,7 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
|
268
304
|
let jar_class = gem_module.define_class("Jar", ruby.class_object())?;
|
|
269
305
|
jar_class.define_singleton_method("new", function!(Jar::new, 0))?;
|
|
270
306
|
jar_class.define_method("get_all", method!(Jar::get_all, 0))?;
|
|
271
|
-
jar_class.define_method("
|
|
272
|
-
jar_class.define_method("add_cookie_str", method!(Jar::add_cookie_str, 2))?;
|
|
307
|
+
jar_class.define_method("add", method!(Jar::add, 2))?;
|
|
273
308
|
jar_class.define_method("remove", method!(Jar::remove, 2))?;
|
|
274
309
|
jar_class.define_method("clear", method!(Jar::clear, 0))?;
|
|
275
310
|
|