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/src/header.rs
CHANGED
|
@@ -3,11 +3,17 @@ use std::cell::RefCell;
|
|
|
3
3
|
use bytes::Bytes;
|
|
4
4
|
use http::{HeaderMap, HeaderName, HeaderValue};
|
|
5
5
|
use magnus::{
|
|
6
|
-
Error, Module, Object, RArray, RModule,
|
|
7
|
-
|
|
6
|
+
Error, Module, Object, RArray, RHash, RModule, RString, Ruby, TryConvert, Value,
|
|
7
|
+
block::Yield,
|
|
8
|
+
function, method,
|
|
9
|
+
r_hash::ForEach,
|
|
10
|
+
typed_data::{Inspect, Obj},
|
|
8
11
|
};
|
|
12
|
+
use wreq::header::OrigHeaderMap;
|
|
9
13
|
|
|
10
|
-
use crate::error::{
|
|
14
|
+
use crate::error::{
|
|
15
|
+
header_name_error_to_magnus, header_value_error_to_magnus, type_value_error_to_magnus,
|
|
16
|
+
};
|
|
11
17
|
|
|
12
18
|
/// HTTP headers collection with read and write operations.
|
|
13
19
|
///
|
|
@@ -15,7 +21,17 @@ use crate::error::{header_name_error_to_magnus, header_value_error_to_magnus};
|
|
|
15
21
|
/// accessing, modifying, and iterating over header name-value pairs.
|
|
16
22
|
#[derive(Clone, Default)]
|
|
17
23
|
#[magnus::wrap(class = "Wreq::Headers", free_immediately, size)]
|
|
18
|
-
pub struct Headers(RefCell<HeaderMap>);
|
|
24
|
+
pub struct Headers(pub RefCell<HeaderMap>);
|
|
25
|
+
|
|
26
|
+
/// A map from header names to their original casing as received in an HTTP message.
|
|
27
|
+
pub struct OrigHeaders(pub OrigHeaderMap);
|
|
28
|
+
|
|
29
|
+
struct HeaderIter {
|
|
30
|
+
inner: http::header::IntoIter<HeaderValue>,
|
|
31
|
+
next_name: Option<HeaderName>,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ===== impl Headers =====
|
|
19
35
|
|
|
20
36
|
impl Headers {
|
|
21
37
|
/// Create a new empty Headers instance.
|
|
@@ -132,11 +148,49 @@ impl From<HeaderMap> for Headers {
|
|
|
132
148
|
}
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
impl TryConvert for Headers {
|
|
152
|
+
fn try_convert(value: Value) -> Result<Self, Error> {
|
|
153
|
+
if let Some(rhash) = RHash::from_value(value) {
|
|
154
|
+
let mut headers = HeaderMap::new();
|
|
155
|
+
|
|
156
|
+
rhash.foreach(|name: RString, value: RString| {
|
|
157
|
+
let name = HeaderName::from_bytes(&name.to_bytes())
|
|
158
|
+
.map_err(header_name_error_to_magnus)?;
|
|
159
|
+
let value = HeaderValue::from_maybe_shared(value.to_bytes())
|
|
160
|
+
.map_err(header_value_error_to_magnus)?;
|
|
161
|
+
headers.insert(name, value);
|
|
162
|
+
|
|
163
|
+
Ok(ForEach::Continue)
|
|
164
|
+
})?;
|
|
165
|
+
|
|
166
|
+
return Ok(Self::from(headers));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
Obj::<Headers>::try_convert(value)
|
|
170
|
+
.map(|headers| headers.0.clone())
|
|
171
|
+
.map(Self)
|
|
172
|
+
}
|
|
138
173
|
}
|
|
139
174
|
|
|
175
|
+
// ===== impl OrigHeaders =====
|
|
176
|
+
|
|
177
|
+
impl TryConvert for OrigHeaders {
|
|
178
|
+
fn try_convert(value: magnus::Value) -> Result<Self, magnus::Error> {
|
|
179
|
+
let mut map = OrigHeaderMap::new();
|
|
180
|
+
|
|
181
|
+
let rarray = RArray::from_value(value)
|
|
182
|
+
.ok_or_else(|| type_value_error_to_magnus("Expected an array of strings"))?;
|
|
183
|
+
|
|
184
|
+
for value in rarray.into_iter().flat_map(RString::from_value) {
|
|
185
|
+
map.insert(value.to_bytes());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Ok(Self(map))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ===== impl HeaderIter =====
|
|
193
|
+
|
|
140
194
|
impl Iterator for HeaderIter {
|
|
141
195
|
type Item = (Bytes, Bytes);
|
|
142
196
|
fn next(&mut self) -> Option<Self::Item> {
|
data/src/http.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use magnus::{Error, Module, RModule, Ruby, method, typed_data::Inspect};
|
|
1
|
+
use magnus::{Error, Module, RModule, Ruby, TryConvert, Value, method, typed_data::Inspect};
|
|
2
2
|
|
|
3
3
|
define_ruby_enum!(
|
|
4
4
|
/// An HTTP version.
|
|
@@ -41,6 +41,20 @@ impl Version {
|
|
|
41
41
|
pub fn to_s(&self) -> String {
|
|
42
42
|
self.into_ffi().inspect()
|
|
43
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
|
+
}
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
// ===== impl StatusCode =====
|
|
@@ -113,6 +127,7 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
|
|
|
113
127
|
version_class.const_set("HTTP_2", Version::HTTP_2)?;
|
|
114
128
|
version_class.const_set("HTTP_3", Version::HTTP_3)?;
|
|
115
129
|
version_class.define_method("to_s", method!(Version::to_s, 0))?;
|
|
130
|
+
version_class.define_method("==", method!(Version::equals, 1))?;
|
|
116
131
|
|
|
117
132
|
let status_code_class = gem_module.define_class("StatusCode", ruby.class_object())?;
|
|
118
133
|
status_code_class.define_method("as_int", method!(StatusCode::as_int, 0))?;
|
data/src/macros.rs
CHANGED
|
@@ -14,6 +14,11 @@ macro_rules! apply_option {
|
|
|
14
14
|
$builder = $builder.$method(value.0);
|
|
15
15
|
}
|
|
16
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
|
+
};
|
|
17
22
|
(set_if_some_map, $builder:expr, $option:expr, $method:ident, $transform:expr) => {
|
|
18
23
|
if let Some(value) = $option.take() {
|
|
19
24
|
$builder = $builder.$method($transform(value));
|
data/src/rt.rs
CHANGED
|
@@ -27,21 +27,3 @@ where
|
|
|
27
27
|
})
|
|
28
28
|
})
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
/// Block on a future to completion on the global Tokio runtime,
|
|
32
|
-
/// returning `None` if cancelled via the provided `CancelFlag`.
|
|
33
|
-
#[inline]
|
|
34
|
-
pub fn maybe_block_on<F, T>(future: F) -> F::Output
|
|
35
|
-
where
|
|
36
|
-
F: Future<Output = Option<T>>,
|
|
37
|
-
{
|
|
38
|
-
gvl::nogvl_cancellable(|flag| {
|
|
39
|
-
RUNTIME.block_on(async move {
|
|
40
|
-
tokio::select! {
|
|
41
|
-
biased;
|
|
42
|
-
_ = flag.cancelled() => None,
|
|
43
|
-
result = future => result,
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
}
|
data/test/client_cookie_test.rb
CHANGED
|
@@ -36,7 +36,7 @@ class ClientCookieProviderTest < Minitest::Test
|
|
|
36
36
|
|
|
37
37
|
def test_prepopulated_jar_is_used_by_client
|
|
38
38
|
# pre-populate jar
|
|
39
|
-
@jar.
|
|
39
|
+
@jar.add("pref=1; Path=/", "#{HOST}/")
|
|
40
40
|
|
|
41
41
|
res = @client.get("#{HOST}/cookies")
|
|
42
42
|
assert_equal 200, res.code
|
data/test/cookie_test.rb
CHANGED
|
@@ -19,9 +19,9 @@ class CookieTest < Minitest::Test
|
|
|
19
19
|
assert_equal 0, cookies.length
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def test_add_and_get_all
|
|
23
23
|
set_cookie = "sid=abc123; Path=/; Domain=example.com; HttpOnly; Secure"
|
|
24
|
-
@jar.
|
|
24
|
+
@jar.add(set_cookie, @base_url)
|
|
25
25
|
|
|
26
26
|
cookies = @jar.get_all
|
|
27
27
|
assert_kind_of Array, cookies
|
|
@@ -42,9 +42,9 @@ class CookieTest < Minitest::Test
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def test_add_multiple_and_remove
|
|
45
|
-
@jar.
|
|
46
|
-
@jar.
|
|
47
|
-
@jar.
|
|
45
|
+
@jar.add("a=1; Path=/", @base_url)
|
|
46
|
+
@jar.add("b=2; Path=/", @base_url)
|
|
47
|
+
@jar.add("c=3; Path=/", @base_url)
|
|
48
48
|
|
|
49
49
|
cookies = @jar.get_all
|
|
50
50
|
assert_equal 3, cookies.length
|
|
@@ -58,8 +58,8 @@ class CookieTest < Minitest::Test
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def test_clear
|
|
61
|
-
@jar.
|
|
62
|
-
@jar.
|
|
61
|
+
@jar.add("x=1; Path=/", @base_url)
|
|
62
|
+
@jar.add("y=2; Path=/", @base_url)
|
|
63
63
|
refute_empty @jar.get_all
|
|
64
64
|
|
|
65
65
|
@jar.clear
|
|
@@ -69,7 +69,7 @@ class CookieTest < Minitest::Test
|
|
|
69
69
|
def test_max_age_and_expires_optional
|
|
70
70
|
# Max-Age only
|
|
71
71
|
@jar.clear
|
|
72
|
-
@jar.
|
|
72
|
+
@jar.add("ma=1; Max-Age=3600; Path=/", @base_url)
|
|
73
73
|
c1 = @jar.get_all.find { |c| c.name == "ma" }
|
|
74
74
|
assert c1
|
|
75
75
|
# can be nil or Integer; just ensure responds and is truthy integer
|
|
@@ -81,7 +81,7 @@ class CookieTest < Minitest::Test
|
|
|
81
81
|
# Expires only
|
|
82
82
|
@jar.clear
|
|
83
83
|
t = Time.now + 3600
|
|
84
|
-
@jar.
|
|
84
|
+
@jar.add("exp=1; Expires=#{t.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")}; Path=/", @base_url)
|
|
85
85
|
c2 = @jar.get_all.find { |c| c.name == "exp" }
|
|
86
86
|
assert c2
|
|
87
87
|
# expires returns Float (unix seconds) or nil
|
|
@@ -144,8 +144,8 @@ class CookieTest < Minitest::Test
|
|
|
144
144
|
|
|
145
145
|
def test_same_site_flags_from_parsed_header
|
|
146
146
|
@jar.clear
|
|
147
|
-
@jar.
|
|
148
|
-
@jar.
|
|
147
|
+
@jar.add("s1=1; Path=/; SameSite=Strict", @base_url)
|
|
148
|
+
@jar.add("s2=1; Path=/; SameSite=Lax", @base_url)
|
|
149
149
|
|
|
150
150
|
cookies = @jar.get_all
|
|
151
151
|
h = cookies.to_h { |ck| [ck.name, [ck.same_site_strict?, ck.same_site_lax?]] }
|
|
@@ -154,13 +154,27 @@ class CookieTest < Minitest::Test
|
|
|
154
154
|
assert_equal [false, true], h["s2"]
|
|
155
155
|
end
|
|
156
156
|
|
|
157
|
-
def
|
|
158
|
-
raw_value = "hello world?"
|
|
157
|
+
def test_request_uncompressed_cookies
|
|
159
158
|
client = Wreq::Client.new
|
|
160
159
|
resp = client.get(
|
|
161
|
-
"
|
|
162
|
-
cookies: {"
|
|
160
|
+
"https://httpbin.io/cookies",
|
|
161
|
+
cookies: {"foo" => "bar", "baz" => "qux"}
|
|
163
162
|
)
|
|
164
|
-
|
|
163
|
+
json = resp.json
|
|
164
|
+
assert_instance_of Hash, json
|
|
165
|
+
assert_equal "bar", json["foo"]
|
|
166
|
+
assert_equal "qux", json["baz"]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_request_compressed_cookies
|
|
170
|
+
client = Wreq::Client.new
|
|
171
|
+
resp = client.get(
|
|
172
|
+
"https://httpbin.io/cookies",
|
|
173
|
+
cookies: "foo=bar; baz=qux"
|
|
174
|
+
)
|
|
175
|
+
json = resp.json
|
|
176
|
+
assert_instance_of Hash, json
|
|
177
|
+
assert_equal "bar", json["foo"]
|
|
178
|
+
assert_equal "qux", json["baz"]
|
|
165
179
|
end
|
|
166
180
|
end
|
data/test/emulation_test.rb
CHANGED
|
@@ -4,18 +4,18 @@ require "test_helper"
|
|
|
4
4
|
|
|
5
5
|
class EmulationTest < Minitest::Test
|
|
6
6
|
def test_all_emulation_device_constants_are_non_nil
|
|
7
|
-
Wreq::
|
|
8
|
-
const = Wreq::
|
|
9
|
-
assert_instance_of Wreq::
|
|
10
|
-
"#{name} should be
|
|
7
|
+
Wreq::Profile.constants.each do |name|
|
|
8
|
+
const = Wreq::Profile.const_get(name)
|
|
9
|
+
assert_instance_of Wreq::Profile, const,
|
|
10
|
+
"#{name} should be Profile, got #{const.inspect}"
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def test_all_emulation_os_constants_are_non_nil
|
|
15
|
-
Wreq::
|
|
16
|
-
const = Wreq::
|
|
17
|
-
assert_instance_of Wreq::
|
|
18
|
-
"#{name} should be
|
|
15
|
+
Wreq::Platform.constants.each do |name|
|
|
16
|
+
const = Wreq::Platform.const_get(name)
|
|
17
|
+
assert_instance_of Wreq::Platform, const,
|
|
18
|
+
"#{name} should be Platform, got #{const.inspect}"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
data/test/error_handling_test.rb
CHANGED
|
@@ -82,7 +82,10 @@ class ErrorHandlingTest < Minitest::Test
|
|
|
82
82
|
Wreq.get(url, proxy: proxy, timeout: 5)
|
|
83
83
|
flunk "Expected proxy connection error but got response"
|
|
84
84
|
rescue => e
|
|
85
|
-
|
|
85
|
+
assert(
|
|
86
|
+
e.is_a?(Wreq::ProxyConnectionError) || e.is_a?(Wreq::RequestError),
|
|
87
|
+
"Expected ProxyConnectionError or RequestError, got #{e.class}: #{e.message}"
|
|
88
|
+
)
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class InspectTest < Minitest::Test
|
|
6
|
+
# ---- Headers ----
|
|
7
|
+
|
|
8
|
+
def test_headers_inspect_empty
|
|
9
|
+
headers = Wreq::Headers.new
|
|
10
|
+
assert_equal "#<Wreq::Headers [0 headers]>", headers.inspect
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_headers_inspect_with_entries
|
|
14
|
+
headers = Wreq::Headers.new
|
|
15
|
+
headers.set("Content-Type", "text/html")
|
|
16
|
+
headers.set("Accept", "application/json")
|
|
17
|
+
assert_equal "#<Wreq::Headers [2 headers]>", headers.inspect
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# ---- Cookie ----
|
|
21
|
+
|
|
22
|
+
def test_cookie_inspect_minimal
|
|
23
|
+
c = Wreq::Cookie.new("sid", "secret123")
|
|
24
|
+
result = c.inspect
|
|
25
|
+
assert_includes result, "#<Wreq::Cookie"
|
|
26
|
+
assert_includes result, "sid"
|
|
27
|
+
refute_includes result, "secret123"
|
|
28
|
+
assert result.end_with?(">")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_cookie_inspect_with_domain_and_path
|
|
32
|
+
c = Wreq::Cookie.new("sid", "val",
|
|
33
|
+
domain: "example.com",
|
|
34
|
+
path: "/app")
|
|
35
|
+
result = c.inspect
|
|
36
|
+
assert_includes result, "domain=example.com"
|
|
37
|
+
assert_includes result, "path=/app"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_cookie_inspect_with_flags
|
|
41
|
+
c = Wreq::Cookie.new("sid", "val",
|
|
42
|
+
secure: true,
|
|
43
|
+
http_only: true)
|
|
44
|
+
result = c.inspect
|
|
45
|
+
assert_includes result, "secure"
|
|
46
|
+
assert_includes result, "http_only"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_cookie_inspect_omits_nil_attributes
|
|
50
|
+
c = Wreq::Cookie.new("sid", "val")
|
|
51
|
+
result = c.inspect
|
|
52
|
+
refute_includes result, "domain="
|
|
53
|
+
refute_includes result, "path="
|
|
54
|
+
refute_includes result, "secure"
|
|
55
|
+
refute_includes result, "http_only"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# ---- Jar ----
|
|
59
|
+
|
|
60
|
+
def test_jar_inspect_empty
|
|
61
|
+
jar = Wreq::Jar.new
|
|
62
|
+
assert_equal "#<Wreq::Jar [0 cookies]>", jar.inspect
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_jar_inspect_with_cookies
|
|
66
|
+
jar = Wreq::Jar.new
|
|
67
|
+
jar.add("a=1; Path=/", "https://example.com")
|
|
68
|
+
jar.add("b=2; Path=/", "https://example.com")
|
|
69
|
+
assert_equal "#<Wreq::Jar [2 cookies]>", jar.inspect
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# ---- Client ----
|
|
73
|
+
|
|
74
|
+
def test_client_inspect
|
|
75
|
+
client = Wreq::Client.new
|
|
76
|
+
assert_equal "#<Wreq::Client>", client.inspect
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_client_inspect_with_options
|
|
80
|
+
client = Wreq::Client.new(timeout: 30, gzip: true)
|
|
81
|
+
assert_equal "#<Wreq::Client>", client.inspect
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# ---- Response ----
|
|
85
|
+
|
|
86
|
+
def test_response_to_s_returns_body
|
|
87
|
+
response = Wreq.get("http://localhost:8080/json")
|
|
88
|
+
assert_equal response.text, response.to_s
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_response_inspect_format
|
|
92
|
+
response = Wreq.get("http://localhost:8080/json")
|
|
93
|
+
result = response.inspect
|
|
94
|
+
assert result.start_with?("#<Wreq::Response")
|
|
95
|
+
assert_includes result, "200"
|
|
96
|
+
assert result.end_with?(">")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# ---- StatusCode ----
|
|
100
|
+
|
|
101
|
+
def test_status_code_inspect
|
|
102
|
+
response = Wreq.get("http://localhost:8080/status/200")
|
|
103
|
+
result = response.status.inspect
|
|
104
|
+
assert result.start_with?("#<Wreq::StatusCode")
|
|
105
|
+
assert_includes result, response.status.to_s
|
|
106
|
+
assert result.end_with?(">")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# ---- Version ----
|
|
110
|
+
|
|
111
|
+
def test_version_inspect_from_constant
|
|
112
|
+
v = Wreq::Version::HTTP_11
|
|
113
|
+
result = v.inspect
|
|
114
|
+
assert result.start_with?("#<Wreq::Version")
|
|
115
|
+
assert_includes result, v.to_s
|
|
116
|
+
assert result.end_with?(">")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_version_inspect_from_response
|
|
120
|
+
response = Wreq.get("http://localhost:8080/get")
|
|
121
|
+
result = response.version.inspect
|
|
122
|
+
assert result.start_with?("#<Wreq::Version")
|
|
123
|
+
assert result.end_with?(">")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class OrigHeaderTest < Minitest::Test
|
|
4
|
+
URL = "https://tls.browserleaks.com/http1"
|
|
5
|
+
|
|
6
|
+
CASES = [
|
|
7
|
+
{
|
|
8
|
+
name: "mixed_case_descending",
|
|
9
|
+
headers: {
|
|
10
|
+
"X-Zeta-Token" => "zeta",
|
|
11
|
+
"x-alpha-key" => "alpha",
|
|
12
|
+
"X-MiXeD-CaSe" => "mixed"
|
|
13
|
+
},
|
|
14
|
+
orig_headers: ["X-Zeta-Token", "x-alpha-key", "X-MiXeD-CaSe"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "reverse_alpha_order",
|
|
18
|
+
headers: {
|
|
19
|
+
"X-Third" => "3",
|
|
20
|
+
"X-Second" => "2",
|
|
21
|
+
"X-First" => "1"
|
|
22
|
+
},
|
|
23
|
+
orig_headers: ["X-Third", "X-Second", "X-First"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "preserve_weird_casing",
|
|
27
|
+
headers: {
|
|
28
|
+
"x-a" => "a",
|
|
29
|
+
"X-B" => "b",
|
|
30
|
+
"x-C" => "c"
|
|
31
|
+
},
|
|
32
|
+
orig_headers: ["x-C", "x-a", "X-B"]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "interleaved_tokens",
|
|
36
|
+
headers: {
|
|
37
|
+
"X-Token-3" => "v3",
|
|
38
|
+
"X-Token-1" => "v1",
|
|
39
|
+
"X-Token-2" => "v2"
|
|
40
|
+
},
|
|
41
|
+
orig_headers: ["X-Token-1", "X-Token-2", "X-Token-3"]
|
|
42
|
+
}
|
|
43
|
+
].freeze
|
|
44
|
+
|
|
45
|
+
def test_client_default_orig_headers_preserves_header_order_in_multiple_shuffled_cases
|
|
46
|
+
CASES.each do |kase|
|
|
47
|
+
client = Wreq::Client.new(
|
|
48
|
+
headers: kase[:headers],
|
|
49
|
+
orig_headers: kase[:orig_headers]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
response = client.get(URL, version: Wreq::Version::HTTP_11)
|
|
53
|
+
assert_equal 200, response.code, "case=#{kase[:name]}"
|
|
54
|
+
|
|
55
|
+
echoed_headers = extract_http1_headers(response.json, kase[:name])
|
|
56
|
+
assert_header_order(echoed_headers, kase[:orig_headers], kase[:name])
|
|
57
|
+
assert_header_values(echoed_headers, kase[:headers], kase[:name])
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_module_request_orig_headers_preserves_header_order_in_multiple_shuffled_cases
|
|
62
|
+
CASES.each do |kase|
|
|
63
|
+
response = Wreq.get(
|
|
64
|
+
URL,
|
|
65
|
+
headers: kase[:headers],
|
|
66
|
+
orig_headers: kase[:orig_headers],
|
|
67
|
+
version: Wreq::Version::HTTP_11
|
|
68
|
+
)
|
|
69
|
+
assert_equal 200, response.code, "case=#{kase[:name]}"
|
|
70
|
+
|
|
71
|
+
echoed_headers = extract_http1_headers(response.json, kase[:name])
|
|
72
|
+
assert_header_order(echoed_headers, kase[:orig_headers], kase[:name])
|
|
73
|
+
assert_header_values(echoed_headers, kase[:headers], kase[:name])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def extract_http1_headers(json, case_name)
|
|
80
|
+
http1 = fetch_by_name(json, "http1")
|
|
81
|
+
refute_nil http1, "case=#{case_name}: expected JSON key 'http1', got #{json.keys.inspect}"
|
|
82
|
+
|
|
83
|
+
headers = fetch_by_name(http1, "headers")
|
|
84
|
+
refute_nil headers, "case=#{case_name}: expected JSON key 'http1.headers'"
|
|
85
|
+
headers
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def fetch_by_name(hash_like, key_name)
|
|
89
|
+
return hash_like[key_name] if hash_like.respond_to?(:key?) && hash_like.key?(key_name)
|
|
90
|
+
return hash_like[key_name.to_sym] if hash_like.respond_to?(:key?) && hash_like.key?(key_name.to_sym)
|
|
91
|
+
|
|
92
|
+
pair = hash_like.find { |k, _| k.to_s == key_name }
|
|
93
|
+
pair&.last
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def assert_header_order(echoed_headers, ordered_names, case_name)
|
|
97
|
+
echoed_keys = echoed_headers.keys
|
|
98
|
+
positions = ordered_names.map do |expected_name|
|
|
99
|
+
index = echoed_keys.index(expected_name)
|
|
100
|
+
refute_nil index, "case=#{case_name}: expected header to exist in echo: #{expected_name}"
|
|
101
|
+
index
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
assert_equal positions.sort, positions,
|
|
105
|
+
"case=#{case_name}: expected header order #{ordered_names.inspect}, got keys #{echoed_keys.inspect}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def assert_header_values(echoed_headers, expected_headers, case_name)
|
|
109
|
+
expected_headers.each do |name, expected_value|
|
|
110
|
+
assert echoed_headers.key?(name),
|
|
111
|
+
"case=#{case_name}: expected exact-case header name #{name}, got #{echoed_headers.keys.inspect}"
|
|
112
|
+
assert_equal expected_value, echoed_headers[name]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/test/request_test.rb
CHANGED
|
@@ -6,6 +6,16 @@ class WreqHttpbinTest < Minitest::Test
|
|
|
6
6
|
@client = Wreq::Client.new(timeout: 30)
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
def test_spec_http1_version
|
|
10
|
+
response = Wreq.get("https://tls.browserleaks.com", version: Wreq::Version::HTTP_11)
|
|
11
|
+
assert_equal response.version, Wreq::Version::HTTP_11
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_spec_http2_version
|
|
15
|
+
response = Wreq.get("https://tls.browserleaks.com", version: Wreq::Version::HTTP_2)
|
|
16
|
+
assert_equal response.version, Wreq::Version::HTTP_2
|
|
17
|
+
end
|
|
18
|
+
|
|
9
19
|
def test_module_get_method
|
|
10
20
|
response = Wreq.get("http://localhost:8080/get")
|
|
11
21
|
assert_equal 200, response.code
|