wreq-rb 0.3.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 +7 -0
- data/Cargo.lock +2688 -0
- data/Cargo.toml +6 -0
- data/README.md +179 -0
- data/ext/wreq_rb/Cargo.toml +39 -0
- data/ext/wreq_rb/extconf.rb +22 -0
- data/ext/wreq_rb/src/client.rs +565 -0
- data/ext/wreq_rb/src/error.rs +25 -0
- data/ext/wreq_rb/src/lib.rs +20 -0
- data/ext/wreq_rb/src/response.rs +132 -0
- data/lib/wreq-rb/version.rb +5 -0
- data/lib/wreq-rb.rb +17 -0
- data/patches/0001-add-transfer-size-tracking.patch +292 -0
- data/vendor/wreq/Cargo.toml +306 -0
- data/vendor/wreq/LICENSE +202 -0
- data/vendor/wreq/README.md +122 -0
- data/vendor/wreq/examples/cert_store.rs +77 -0
- data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
- data/vendor/wreq/examples/emulation.rs +118 -0
- data/vendor/wreq/examples/form.rs +14 -0
- data/vendor/wreq/examples/http1_websocket.rs +37 -0
- data/vendor/wreq/examples/http2_websocket.rs +45 -0
- data/vendor/wreq/examples/json_dynamic.rs +41 -0
- data/vendor/wreq/examples/json_typed.rs +47 -0
- data/vendor/wreq/examples/keylog.rs +16 -0
- data/vendor/wreq/examples/request_with_emulation.rs +115 -0
- data/vendor/wreq/examples/request_with_interface.rs +37 -0
- data/vendor/wreq/examples/request_with_local_address.rs +16 -0
- data/vendor/wreq/examples/request_with_proxy.rs +13 -0
- data/vendor/wreq/examples/request_with_redirect.rs +22 -0
- data/vendor/wreq/examples/request_with_version.rs +15 -0
- data/vendor/wreq/examples/tor_socks.rs +24 -0
- data/vendor/wreq/examples/unix_socket.rs +33 -0
- data/vendor/wreq/src/client/body.rs +304 -0
- data/vendor/wreq/src/client/conn/conn.rs +231 -0
- data/vendor/wreq/src/client/conn/connector.rs +549 -0
- data/vendor/wreq/src/client/conn/http.rs +1023 -0
- data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
- data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
- data/vendor/wreq/src/client/conn/proxy.rs +39 -0
- data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
- data/vendor/wreq/src/client/conn/uds.rs +44 -0
- data/vendor/wreq/src/client/conn/verbose.rs +149 -0
- data/vendor/wreq/src/client/conn.rs +323 -0
- data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
- data/vendor/wreq/src/client/core/body/length.rs +118 -0
- data/vendor/wreq/src/client/core/body.rs +34 -0
- data/vendor/wreq/src/client/core/common/buf.rs +149 -0
- data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
- data/vendor/wreq/src/client/core/common/watch.rs +76 -0
- data/vendor/wreq/src/client/core/common.rs +3 -0
- data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
- data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
- data/vendor/wreq/src/client/core/conn.rs +11 -0
- data/vendor/wreq/src/client/core/dispatch.rs +299 -0
- data/vendor/wreq/src/client/core/error.rs +435 -0
- data/vendor/wreq/src/client/core/ext.rs +201 -0
- data/vendor/wreq/src/client/core/http1.rs +178 -0
- data/vendor/wreq/src/client/core/http2.rs +483 -0
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
- data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
- data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
- data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
- data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
- data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
- data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
- data/vendor/wreq/src/client/core/proto.rs +58 -0
- data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
- data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
- data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
- data/vendor/wreq/src/client/core/rt.rs +25 -0
- data/vendor/wreq/src/client/core/upgrade.rs +267 -0
- data/vendor/wreq/src/client/core.rs +16 -0
- data/vendor/wreq/src/client/emulation.rs +161 -0
- data/vendor/wreq/src/client/http/client/error.rs +142 -0
- data/vendor/wreq/src/client/http/client/exec.rs +29 -0
- data/vendor/wreq/src/client/http/client/extra.rs +77 -0
- data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
- data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
- data/vendor/wreq/src/client/http/client/util.rs +104 -0
- data/vendor/wreq/src/client/http/client.rs +1003 -0
- data/vendor/wreq/src/client/http/future.rs +99 -0
- data/vendor/wreq/src/client/http.rs +1629 -0
- data/vendor/wreq/src/client/layer/config/options.rs +156 -0
- data/vendor/wreq/src/client/layer/config.rs +116 -0
- data/vendor/wreq/src/client/layer/cookie.rs +161 -0
- data/vendor/wreq/src/client/layer/decoder.rs +139 -0
- data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
- data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
- data/vendor/wreq/src/client/layer/redirect.rs +145 -0
- data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
- data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
- data/vendor/wreq/src/client/layer/retry.rs +151 -0
- data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
- data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
- data/vendor/wreq/src/client/layer/timeout.rs +177 -0
- data/vendor/wreq/src/client/layer.rs +15 -0
- data/vendor/wreq/src/client/multipart.rs +717 -0
- data/vendor/wreq/src/client/request.rs +818 -0
- data/vendor/wreq/src/client/response.rs +534 -0
- data/vendor/wreq/src/client/ws/json.rs +99 -0
- data/vendor/wreq/src/client/ws/message.rs +453 -0
- data/vendor/wreq/src/client/ws.rs +714 -0
- data/vendor/wreq/src/client.rs +27 -0
- data/vendor/wreq/src/config.rs +140 -0
- data/vendor/wreq/src/cookie.rs +579 -0
- data/vendor/wreq/src/dns/gai.rs +249 -0
- data/vendor/wreq/src/dns/hickory.rs +78 -0
- data/vendor/wreq/src/dns/resolve.rs +180 -0
- data/vendor/wreq/src/dns.rs +69 -0
- data/vendor/wreq/src/error.rs +502 -0
- data/vendor/wreq/src/ext.rs +398 -0
- data/vendor/wreq/src/hash.rs +143 -0
- data/vendor/wreq/src/header.rs +506 -0
- data/vendor/wreq/src/into_uri.rs +187 -0
- data/vendor/wreq/src/lib.rs +586 -0
- data/vendor/wreq/src/proxy/mac.rs +82 -0
- data/vendor/wreq/src/proxy/matcher.rs +806 -0
- data/vendor/wreq/src/proxy/uds.rs +66 -0
- data/vendor/wreq/src/proxy/win.rs +31 -0
- data/vendor/wreq/src/proxy.rs +569 -0
- data/vendor/wreq/src/redirect.rs +575 -0
- data/vendor/wreq/src/retry.rs +198 -0
- data/vendor/wreq/src/sync.rs +129 -0
- data/vendor/wreq/src/tls/conn/cache.rs +123 -0
- data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
- data/vendor/wreq/src/tls/conn/ext.rs +82 -0
- data/vendor/wreq/src/tls/conn/macros.rs +34 -0
- data/vendor/wreq/src/tls/conn/service.rs +138 -0
- data/vendor/wreq/src/tls/conn.rs +681 -0
- data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
- data/vendor/wreq/src/tls/keylog.rs +99 -0
- data/vendor/wreq/src/tls/options.rs +464 -0
- data/vendor/wreq/src/tls/x509/identity.rs +122 -0
- data/vendor/wreq/src/tls/x509/parser.rs +71 -0
- data/vendor/wreq/src/tls/x509/store.rs +228 -0
- data/vendor/wreq/src/tls/x509.rs +68 -0
- data/vendor/wreq/src/tls.rs +154 -0
- data/vendor/wreq/src/trace.rs +55 -0
- data/vendor/wreq/src/util.rs +122 -0
- data/vendor/wreq/tests/badssl.rs +228 -0
- data/vendor/wreq/tests/brotli.rs +350 -0
- data/vendor/wreq/tests/client.rs +1098 -0
- data/vendor/wreq/tests/connector_layers.rs +227 -0
- data/vendor/wreq/tests/cookie.rs +306 -0
- data/vendor/wreq/tests/deflate.rs +347 -0
- data/vendor/wreq/tests/emulation.rs +260 -0
- data/vendor/wreq/tests/gzip.rs +347 -0
- data/vendor/wreq/tests/layers.rs +261 -0
- data/vendor/wreq/tests/multipart.rs +165 -0
- data/vendor/wreq/tests/proxy.rs +438 -0
- data/vendor/wreq/tests/redirect.rs +629 -0
- data/vendor/wreq/tests/retry.rs +135 -0
- data/vendor/wreq/tests/support/delay_server.rs +117 -0
- data/vendor/wreq/tests/support/error.rs +16 -0
- data/vendor/wreq/tests/support/layer.rs +183 -0
- data/vendor/wreq/tests/support/mod.rs +9 -0
- data/vendor/wreq/tests/support/server.rs +232 -0
- data/vendor/wreq/tests/timeouts.rs +281 -0
- data/vendor/wreq/tests/unix_socket.rs +135 -0
- data/vendor/wreq/tests/upgrade.rs +98 -0
- data/vendor/wreq/tests/zstd.rs +559 -0
- metadata +225 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
use magnus::{
|
|
2
|
+
method, prelude::*, Module, RHash, Ruby, Value,
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
use crate::error::generic_error;
|
|
6
|
+
|
|
7
|
+
/// Wraps a wreq::Response in a Ruby-accessible type.
|
|
8
|
+
#[magnus::wrap(class = "Wreq::Response", free_immediately)]
|
|
9
|
+
pub struct Response {
|
|
10
|
+
status: u16,
|
|
11
|
+
headers: Vec<(String, String)>,
|
|
12
|
+
body: Vec<u8>,
|
|
13
|
+
url: String,
|
|
14
|
+
version: String,
|
|
15
|
+
content_length: Option<u64>,
|
|
16
|
+
transfer_size: Option<u64>,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl Response {
|
|
20
|
+
pub fn new(
|
|
21
|
+
status: u16,
|
|
22
|
+
headers: Vec<(String, String)>,
|
|
23
|
+
body: Vec<u8>,
|
|
24
|
+
url: String,
|
|
25
|
+
version: String,
|
|
26
|
+
content_length: Option<u64>,
|
|
27
|
+
transfer_size: Option<u64>,
|
|
28
|
+
) -> Self {
|
|
29
|
+
Self {
|
|
30
|
+
status,
|
|
31
|
+
headers,
|
|
32
|
+
body,
|
|
33
|
+
url,
|
|
34
|
+
version,
|
|
35
|
+
content_length,
|
|
36
|
+
transfer_size,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn status(&self) -> u16 {
|
|
41
|
+
self.status
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn text(&self) -> Result<String, magnus::Error> {
|
|
45
|
+
String::from_utf8(self.body.clone()).map_err(|e| generic_error(e))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn body_bytes(&self) -> Vec<u8> {
|
|
49
|
+
self.body.clone()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn headers(&self) -> Result<RHash, magnus::Error> {
|
|
53
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
54
|
+
let hash = ruby.hash_new();
|
|
55
|
+
for (k, v) in &self.headers {
|
|
56
|
+
hash.aset(k.as_str(), v.as_str())?;
|
|
57
|
+
}
|
|
58
|
+
Ok(hash)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn url(&self) -> String {
|
|
62
|
+
self.url.clone()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn http_version(&self) -> String {
|
|
66
|
+
self.version.clone()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn content_length(&self) -> Option<u64> {
|
|
70
|
+
self.content_length
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn transfer_size(&self) -> Option<u64> {
|
|
74
|
+
self.transfer_size
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn is_success(&self) -> bool {
|
|
78
|
+
(200..300).contains(&self.status)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fn is_redirect(&self) -> bool {
|
|
82
|
+
(300..400).contains(&self.status)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn is_client_error(&self) -> bool {
|
|
86
|
+
(400..500).contains(&self.status)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn is_server_error(&self) -> bool {
|
|
90
|
+
(500..600).contains(&self.status)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn json(&self) -> Result<Value, magnus::Error> {
|
|
94
|
+
let ruby = unsafe { Ruby::get_unchecked() };
|
|
95
|
+
let text = self.text()?;
|
|
96
|
+
let json_module: Value = ruby.class_object().const_get("JSON")?;
|
|
97
|
+
json_module.funcall("parse", (text,))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn inspect(&self) -> String {
|
|
101
|
+
format!(
|
|
102
|
+
"#<Wreq::Response status={} url={:?}>",
|
|
103
|
+
self.status, self.url
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn to_s(&self) -> Result<String, magnus::Error> {
|
|
108
|
+
self.text()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub fn init(ruby: &magnus::Ruby, module: &magnus::RModule) -> Result<(), magnus::Error> {
|
|
113
|
+
let class = module.define_class("Response", ruby.class_object())?;
|
|
114
|
+
class.define_method("status", method!(Response::status, 0))?;
|
|
115
|
+
class.define_method("code", method!(Response::status, 0))?;
|
|
116
|
+
class.define_method("text", method!(Response::text, 0))?;
|
|
117
|
+
class.define_method("body", method!(Response::text, 0))?;
|
|
118
|
+
class.define_method("body_bytes", method!(Response::body_bytes, 0))?;
|
|
119
|
+
class.define_method("headers", method!(Response::headers, 0))?;
|
|
120
|
+
class.define_method("url", method!(Response::url, 0))?;
|
|
121
|
+
class.define_method("version", method!(Response::http_version, 0))?;
|
|
122
|
+
class.define_method("content_length", method!(Response::content_length, 0))?;
|
|
123
|
+
class.define_method("transfer_size", method!(Response::transfer_size, 0))?;
|
|
124
|
+
class.define_method("success?", method!(Response::is_success, 0))?;
|
|
125
|
+
class.define_method("redirect?", method!(Response::is_redirect, 0))?;
|
|
126
|
+
class.define_method("client_error?", method!(Response::is_client_error, 0))?;
|
|
127
|
+
class.define_method("server_error?", method!(Response::is_server_error, 0))?;
|
|
128
|
+
class.define_method("json", method!(Response::json, 0))?;
|
|
129
|
+
class.define_method("inspect", method!(Response::inspect, 0))?;
|
|
130
|
+
class.define_method("to_s", method!(Response::to_s, 0))?;
|
|
131
|
+
Ok(())
|
|
132
|
+
}
|
data/lib/wreq-rb.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
# pre-compiled extension by rake-compiler is located inside lib/wreq_rb/<ruby_version>/
|
|
7
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
8
|
+
require_relative "wreq_rb/#{Regexp.last_match(1)}/wreq_rb"
|
|
9
|
+
rescue LoadError => e
|
|
10
|
+
# fallback to the locally built extension
|
|
11
|
+
require_relative 'wreq_rb/wreq_rb'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require_relative "wreq-rb/version"
|
|
15
|
+
|
|
16
|
+
module Wreq
|
|
17
|
+
end
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
diff --git a/src/client/http.rs b/src/client/http.rs
|
|
2
|
+
index 10be0c83..462c77d0 100644
|
|
3
|
+
--- a/src/client/http.rs
|
|
4
|
+
+++ b/src/client/http.rs
|
|
5
|
+
@@ -33,6 +33,7 @@ use self::future::Pending;
|
|
6
|
+
feature = "deflate",
|
|
7
|
+
))]
|
|
8
|
+
use super::layer::decoder::{AcceptEncoding, DecompressionLayer};
|
|
9
|
+
+use super::layer::transfer_size::{CountingBody, TransferSizeLayer, TransferSizeService};
|
|
10
|
+
#[cfg(feature = "ws")]
|
|
11
|
+
use super::ws::WebSocketRequestBuilder;
|
|
12
|
+
use super::{
|
|
13
|
+
@@ -104,7 +105,7 @@ type Decompression<T> = super::layer::decoder::Decompression<T>;
|
|
14
|
+
feature = "brotli",
|
|
15
|
+
feature = "deflate"
|
|
16
|
+
))]
|
|
17
|
+
-type ResponseBody = TimeoutBody<tower_http::decompression::DecompressionBody<Incoming>>;
|
|
18
|
+
+type ResponseBody = TimeoutBody<tower_http::decompression::DecompressionBody<CountingBody<Incoming>>>;
|
|
19
|
+
|
|
20
|
+
/// Response body type with timeout only (no compression features).
|
|
21
|
+
#[cfg(not(any(
|
|
22
|
+
@@ -113,23 +114,25 @@ type ResponseBody = TimeoutBody<tower_http::decompression::DecompressionBody<Inc
|
|
23
|
+
feature = "brotli",
|
|
24
|
+
feature = "deflate"
|
|
25
|
+
)))]
|
|
26
|
+
-type ResponseBody = TimeoutBody<Incoming>;
|
|
27
|
+
+type ResponseBody = TimeoutBody<CountingBody<Incoming>>;
|
|
28
|
+
|
|
29
|
+
/// The complete HTTP client service stack with all middleware layers.
|
|
30
|
+
type ClientService = Timeout<
|
|
31
|
+
ResponseBodyTimeout<
|
|
32
|
+
ConfigService<
|
|
33
|
+
Decompression<
|
|
34
|
+
- Retry<
|
|
35
|
+
- RetryPolicy,
|
|
36
|
+
- FollowRedirect<
|
|
37
|
+
- CookieService<
|
|
38
|
+
- MapErr<
|
|
39
|
+
- HttpClient<Connector, Body>,
|
|
40
|
+
- fn(client::error::Error) -> BoxError,
|
|
41
|
+
+ TransferSizeService<
|
|
42
|
+
+ Retry<
|
|
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 {
|
|
58
|
+
})
|
|
59
|
+
.service(service);
|
|
60
|
+
|
|
61
|
+
+ let service = ServiceBuilder::new()
|
|
62
|
+
+ .layer(TransferSizeLayer::new())
|
|
63
|
+
+ .service(service);
|
|
64
|
+
+
|
|
65
|
+
#[cfg(any(
|
|
66
|
+
feature = "gzip",
|
|
67
|
+
feature = "zstd",
|
|
68
|
+
diff --git a/src/client/layer.rs b/src/client/layer.rs
|
|
69
|
+
index 05bb533f..cb0b0866 100644
|
|
70
|
+
--- a/src/client/layer.rs
|
|
71
|
+
+++ b/src/client/layer.rs
|
|
72
|
+
@@ -13,3 +13,4 @@ pub mod decoder;
|
|
73
|
+
pub mod redirect;
|
|
74
|
+
pub mod retry;
|
|
75
|
+
pub mod timeout;
|
|
76
|
+
+pub mod transfer_size;
|
|
77
|
+
diff --git a/src/client/layer/transfer_size.rs b/src/client/layer/transfer_size.rs
|
|
78
|
+
new file mode 100644
|
|
79
|
+
index 00000000..7e8a4390
|
|
80
|
+
--- /dev/null
|
|
81
|
+
+++ b/src/client/layer/transfer_size.rs
|
|
82
|
+
@@ -0,0 +1,176 @@
|
|
83
|
+
+//! Middleware that records the network transfer size (pre-decompression bytes).
|
|
84
|
+
+//!
|
|
85
|
+
+//! This layer sits **below** the decompression layer so that it sees the raw
|
|
86
|
+
+//! compressed bytes flowing through the body. It wraps the response body in
|
|
87
|
+
+//! [`CountingBody`] which counts bytes as they flow through `poll_frame()`.
|
|
88
|
+
+//! The running total is accessible via a shared [`TransferSizeHandle`] stored
|
|
89
|
+
+//! in the response extensions.
|
|
90
|
+
+
|
|
91
|
+
+use std::{
|
|
92
|
+
+ pin::Pin,
|
|
93
|
+
+ sync::{
|
|
94
|
+
+ atomic::{AtomicU64, Ordering},
|
|
95
|
+
+ Arc,
|
|
96
|
+
+ },
|
|
97
|
+
+ task::{Context, Poll},
|
|
98
|
+
+};
|
|
99
|
+
+
|
|
100
|
+
+use bytes::Bytes;
|
|
101
|
+
+use http::{Request, Response};
|
|
102
|
+
+use http_body::{Body, Frame};
|
|
103
|
+
+use tower::{Layer, Service};
|
|
104
|
+
+
|
|
105
|
+
+/// A shared handle to the running byte count of the response body on the wire.
|
|
106
|
+
+///
|
|
107
|
+
+/// Call [`TransferSizeHandle::get()`] **after** the body has been fully
|
|
108
|
+
+/// consumed to obtain the total transferred bytes.
|
|
109
|
+
+#[derive(Debug, Clone)]
|
|
110
|
+
+pub struct TransferSizeHandle(Arc<AtomicU64>);
|
|
111
|
+
+
|
|
112
|
+
+impl TransferSizeHandle {
|
|
113
|
+
+ /// Returns the number of bytes that have flowed through the body so far.
|
|
114
|
+
+ #[inline]
|
|
115
|
+
+ pub fn get(&self) -> u64 {
|
|
116
|
+
+ self.0.load(Ordering::Relaxed)
|
|
117
|
+
+ }
|
|
118
|
+
+}
|
|
119
|
+
+
|
|
120
|
+
+// ===== CountingBody =====
|
|
121
|
+
+
|
|
122
|
+
+pin_project_lite::pin_project! {
|
|
123
|
+
+ /// A body wrapper that counts raw bytes flowing through `poll_frame()`.
|
|
124
|
+
+ pub struct CountingBody<B> {
|
|
125
|
+
+ #[pin]
|
|
126
|
+
+ inner: B,
|
|
127
|
+
+ counter: Arc<AtomicU64>,
|
|
128
|
+
+ }
|
|
129
|
+
+}
|
|
130
|
+
+
|
|
131
|
+
+impl<B> Body for CountingBody<B>
|
|
132
|
+
+where
|
|
133
|
+
+ B: Body<Data = Bytes>,
|
|
134
|
+
+ B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
|
135
|
+
+{
|
|
136
|
+
+ type Data = Bytes;
|
|
137
|
+
+ type Error = B::Error;
|
|
138
|
+
+
|
|
139
|
+
+ fn poll_frame(
|
|
140
|
+
+ self: Pin<&mut Self>,
|
|
141
|
+
+ cx: &mut Context<'_>,
|
|
142
|
+
+ ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
|
|
143
|
+
+ let this = self.project();
|
|
144
|
+
+ match this.inner.poll_frame(cx) {
|
|
145
|
+
+ Poll::Ready(Some(Ok(frame))) => {
|
|
146
|
+
+ if let Some(data) = frame.data_ref() {
|
|
147
|
+
+ this.counter
|
|
148
|
+
+ .fetch_add(data.len() as u64, Ordering::Relaxed);
|
|
149
|
+
+ }
|
|
150
|
+
+ Poll::Ready(Some(Ok(frame)))
|
|
151
|
+
+ }
|
|
152
|
+
+ other => other,
|
|
153
|
+
+ }
|
|
154
|
+
+ }
|
|
155
|
+
+
|
|
156
|
+
+ #[inline]
|
|
157
|
+
+ fn is_end_stream(&self) -> bool {
|
|
158
|
+
+ self.inner.is_end_stream()
|
|
159
|
+
+ }
|
|
160
|
+
+
|
|
161
|
+
+ #[inline]
|
|
162
|
+
+ fn size_hint(&self) -> http_body::SizeHint {
|
|
163
|
+
+ self.inner.size_hint()
|
|
164
|
+
+ }
|
|
165
|
+
+}
|
|
166
|
+
+
|
|
167
|
+
+// ===== TransferSizeLayer / TransferSizeService =====
|
|
168
|
+
+
|
|
169
|
+
+/// A [`Layer`] that wraps responses with transfer-size tracking.
|
|
170
|
+
+#[derive(Debug, Clone, Copy, Default)]
|
|
171
|
+
+pub struct TransferSizeLayer;
|
|
172
|
+
+
|
|
173
|
+
+impl TransferSizeLayer {
|
|
174
|
+
+ /// Create a new [`TransferSizeLayer`].
|
|
175
|
+
+ #[inline]
|
|
176
|
+
+ pub const fn new() -> Self {
|
|
177
|
+
+ Self
|
|
178
|
+
+ }
|
|
179
|
+
+}
|
|
180
|
+
+
|
|
181
|
+
+impl<S> Layer<S> for TransferSizeLayer {
|
|
182
|
+
+ type Service = TransferSizeService<S>;
|
|
183
|
+
+
|
|
184
|
+
+ #[inline]
|
|
185
|
+
+ fn layer(&self, inner: S) -> Self::Service {
|
|
186
|
+
+ TransferSizeService { inner }
|
|
187
|
+
+ }
|
|
188
|
+
+}
|
|
189
|
+
+
|
|
190
|
+
+/// The [`Service`] created by [`TransferSizeLayer`].
|
|
191
|
+
+#[derive(Debug, Clone)]
|
|
192
|
+
+pub struct TransferSizeService<S> {
|
|
193
|
+
+ inner: S,
|
|
194
|
+
+}
|
|
195
|
+
+
|
|
196
|
+
+impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for TransferSizeService<S>
|
|
197
|
+
+where
|
|
198
|
+
+ S: Service<Request<ReqBody>, Response = Response<ResBody>>,
|
|
199
|
+
+ ResBody: Body<Data = Bytes>,
|
|
200
|
+
+ ResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
|
201
|
+
+{
|
|
202
|
+
+ type Response = Response<CountingBody<ResBody>>;
|
|
203
|
+
+ type Error = S::Error;
|
|
204
|
+
+ type Future = TransferSizeFuture<S::Future>;
|
|
205
|
+
+
|
|
206
|
+
+ #[inline]
|
|
207
|
+
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
208
|
+
+ self.inner.poll_ready(cx)
|
|
209
|
+
+ }
|
|
210
|
+
+
|
|
211
|
+
+ #[inline]
|
|
212
|
+
+ fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
|
213
|
+
+ TransferSizeFuture {
|
|
214
|
+
+ inner: self.inner.call(req),
|
|
215
|
+
+ }
|
|
216
|
+
+ }
|
|
217
|
+
+}
|
|
218
|
+
+
|
|
219
|
+
+// ===== TransferSizeFuture =====
|
|
220
|
+
+
|
|
221
|
+
+pin_project_lite::pin_project! {
|
|
222
|
+
+ /// Future returned by [`TransferSizeService`].
|
|
223
|
+
+ pub struct TransferSizeFuture<F> {
|
|
224
|
+
+ #[pin]
|
|
225
|
+
+ inner: F,
|
|
226
|
+
+ }
|
|
227
|
+
+}
|
|
228
|
+
+
|
|
229
|
+
+impl<F, ResBody, E> std::future::Future for TransferSizeFuture<F>
|
|
230
|
+
+where
|
|
231
|
+
+ F: std::future::Future<Output = Result<Response<ResBody>, E>>,
|
|
232
|
+
+ ResBody: Body<Data = Bytes>,
|
|
233
|
+
+ ResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
|
234
|
+
+{
|
|
235
|
+
+ type Output = Result<Response<CountingBody<ResBody>>, E>;
|
|
236
|
+
+
|
|
237
|
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
238
|
+
+ let this = self.project();
|
|
239
|
+
+ match this.inner.poll(cx) {
|
|
240
|
+
+ Poll::Ready(Ok(response)) => {
|
|
241
|
+
+ let counter = Arc::new(AtomicU64::new(0));
|
|
242
|
+
+ let handle = TransferSizeHandle(counter.clone());
|
|
243
|
+
+
|
|
244
|
+
+ let (mut parts, body) = response.into_parts();
|
|
245
|
+
+ parts.extensions.insert(handle);
|
|
246
|
+
+
|
|
247
|
+
+ let counting_body = CountingBody {
|
|
248
|
+
+ inner: body,
|
|
249
|
+
+ counter,
|
|
250
|
+
+ };
|
|
251
|
+
+
|
|
252
|
+
+ Poll::Ready(Ok(Response::from_parts(parts, counting_body)))
|
|
253
|
+
+ }
|
|
254
|
+
+ Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
|
|
255
|
+
+ Poll::Pending => Poll::Pending,
|
|
256
|
+
+ }
|
|
257
|
+
+ }
|
|
258
|
+
+}
|
|
259
|
+
diff --git a/src/client/response.rs b/src/client/response.rs
|
|
260
|
+
index d98859b9..16ed35c3 100644
|
|
261
|
+
--- a/src/client/response.rs
|
|
262
|
+
+++ b/src/client/response.rs
|
|
263
|
+
@@ -20,6 +20,7 @@ use serde::de::DeserializeOwned;
|
|
264
|
+
use super::{
|
|
265
|
+
conn::HttpInfo,
|
|
266
|
+
core::{ext::ReasonPhrase, upgrade},
|
|
267
|
+
+ layer::transfer_size::TransferSizeHandle,
|
|
268
|
+
};
|
|
269
|
+
#[cfg(feature = "cookies")]
|
|
270
|
+
use crate::cookie;
|
|
271
|
+
@@ -91,6 +92,21 @@ impl Response {
|
|
272
|
+
HttpBody::size_hint(self.res.body()).exact()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
+ /// Get a handle to the transfer size counter for this response.
|
|
276
|
+
+ ///
|
|
277
|
+
+ /// The handle tracks the number of raw (pre-decompression) bytes received
|
|
278
|
+
+ /// from the network as the body is consumed. Call
|
|
279
|
+
+ /// [`TransferSizeHandle::get()`] **after** the body has been fully read
|
|
280
|
+
+ /// (e.g. after [`bytes()`](Self::bytes) or [`text()`](Self::text)) to
|
|
281
|
+
+ /// obtain the total network transfer size.
|
|
282
|
+
+ ///
|
|
283
|
+
+ /// Returns `None` only if the response was constructed outside the normal
|
|
284
|
+
+ /// client pipeline (e.g. via `From<http::Response>`).
|
|
285
|
+
+ #[inline]
|
|
286
|
+
+ pub fn transfer_size_handle(&self) -> Option<&TransferSizeHandle> {
|
|
287
|
+
+ self.res.extensions().get::<TransferSizeHandle>()
|
|
288
|
+
+ }
|
|
289
|
+
+
|
|
290
|
+
/// Retrieve the cookies contained in the [`Response`].
|
|
291
|
+
///
|
|
292
|
+
/// Note that invalid 'Set-Cookie' headers will be ignored.
|