wreq-rb 0.6.0 → 0.6.2

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +257 -102
  3. data/ext/wreq_rb/Cargo.toml +7 -4
  4. data/ext/wreq_rb/src/client.rs +105 -15
  5. data/ext/wreq_rb/src/response.rs +28 -3
  6. data/lib/wreq-rb/version.rb +1 -1
  7. data/patches/0001-add-transfer-size-tracking.patch +11 -15
  8. data/vendor/wreq/Cargo.toml +9 -8
  9. data/vendor/wreq/README.md +5 -5
  10. data/vendor/wreq/bench/support/bench.rs +6 -2
  11. data/vendor/wreq/bench/support/client.rs +88 -1
  12. data/vendor/wreq/bench/support/exec.rs +0 -0
  13. data/vendor/wreq/bench/support/rt.rs +34 -0
  14. data/vendor/wreq/bench/support/server.rs +1 -1
  15. data/vendor/wreq/bench/support.rs +1 -15
  16. data/vendor/wreq/examples/cert_store.rs +13 -13
  17. data/vendor/wreq/examples/request_with_emulate.rs +1 -1
  18. data/vendor/wreq/examples/tcp_linger.rs +22 -0
  19. data/vendor/wreq/src/client/layer/client/pool.rs +17 -17
  20. data/vendor/wreq/src/client/layer/client.rs +2 -0
  21. data/vendor/wreq/src/client/layer/decoder.rs +71 -17
  22. data/vendor/wreq/src/client/layer/redirect/future.rs +49 -63
  23. data/vendor/wreq/src/client/layer/redirect/policy.rs +2 -26
  24. data/vendor/wreq/src/client/layer/redirect.rs +48 -60
  25. data/vendor/wreq/src/client/layer/retry.rs +12 -15
  26. data/vendor/wreq/src/client/layer/timeout/body.rs +27 -21
  27. data/vendor/wreq/src/client/layer/timeout/future.rs +33 -58
  28. data/vendor/wreq/src/client/layer/timeout.rs +8 -14
  29. data/vendor/wreq/src/client/request.rs +4 -0
  30. data/vendor/wreq/src/client.rs +53 -31
  31. data/vendor/wreq/src/conn/connector.rs +99 -129
  32. data/vendor/wreq/src/conn/http.rs +25 -18
  33. data/vendor/wreq/src/conn/net/tcp.rs +601 -107
  34. data/vendor/wreq/src/conn/proxy/socks.rs +6 -6
  35. data/vendor/wreq/src/conn/timeout.rs +166 -0
  36. data/vendor/wreq/src/conn.rs +5 -4
  37. data/vendor/wreq/src/cookie/jar.rs +1225 -0
  38. data/vendor/wreq/src/cookie/store.rs +321 -0
  39. data/vendor/wreq/src/cookie.rs +108 -612
  40. data/vendor/wreq/src/dns/resolve.rs +8 -2
  41. data/vendor/wreq/src/dns.rs +4 -4
  42. data/vendor/wreq/src/error.rs +53 -20
  43. data/vendor/wreq/src/lib.rs +1 -0
  44. data/vendor/wreq/src/proxy/matcher.rs +26 -12
  45. data/vendor/wreq/src/proxy/win.rs +39 -9
  46. data/vendor/wreq/src/redirect.rs +515 -100
  47. data/vendor/wreq/src/tls/conn.rs +3 -11
  48. data/vendor/wreq/src/tls/session.rs +7 -8
  49. data/vendor/wreq/src/tls/trust/store.rs +4 -4
  50. data/vendor/wreq/src/util.rs +23 -0
  51. data/vendor/wreq/tests/badssl.rs +72 -7
  52. data/vendor/wreq/tests/brotli.rs +1 -1
  53. data/vendor/wreq/tests/client.rs +24 -0
  54. data/vendor/wreq/tests/connector_layers.rs +8 -4
  55. data/vendor/wreq/tests/cookie.rs +59 -0
  56. data/vendor/wreq/tests/deflate.rs +1 -1
  57. data/vendor/wreq/tests/gzip.rs +53 -1
  58. data/vendor/wreq/tests/layers.rs +8 -4
  59. data/vendor/wreq/tests/redirect.rs +180 -97
  60. data/vendor/wreq/tests/timeouts.rs +47 -12
  61. data/vendor/wreq/tests/zstd.rs +1 -1
  62. metadata +8 -2
@@ -10,11 +10,12 @@ use std::{
10
10
 
11
11
  use futures_util::future::Either;
12
12
  use http::{Request, Response};
13
- use http_body::Body;
13
+ use http_body::Body as HttpBody;
14
14
  use tower::{BoxError, Layer, Service};
15
15
 
16
16
  use self::future::ResponseFuture;
17
- pub use self::policy::{Action, Attempt, Policy};
17
+ pub use self::policy::{Action, Attempt};
18
+ use crate::{client::body::Body, redirect::FollowRedirectPolicy};
18
19
 
19
20
  enum BodyRepr<B> {
20
21
  Some(B),
@@ -22,31 +23,25 @@ enum BodyRepr<B> {
22
23
  None,
23
24
  }
24
25
 
25
- impl<B> BodyRepr<B>
26
- where
27
- B: Body + Default,
28
- {
29
- fn take(&mut self) -> Option<B> {
26
+ impl BodyRepr<Body> {
27
+ fn take(&mut self) -> Option<Body> {
30
28
  match mem::replace(self, BodyRepr::None) {
31
29
  BodyRepr::Some(body) => Some(body),
32
30
  BodyRepr::Empty => {
33
31
  *self = BodyRepr::Empty;
34
- Some(B::default())
32
+ Some(Body::default())
35
33
  }
36
34
  BodyRepr::None => None,
37
35
  }
38
36
  }
39
37
 
40
- fn try_clone_from<P, E>(&mut self, body: &B, policy: &P)
41
- where
42
- P: Policy<B, E>,
43
- {
38
+ fn try_clone_from(&mut self, body: &Body) {
44
39
  match self {
45
40
  BodyRepr::Some(_) | BodyRepr::Empty => {}
46
41
  BodyRepr::None => {
47
42
  if body.size_hint().exact() == Some(0) {
48
- *self = BodyRepr::Some(B::default());
49
- } else if let Some(cloned) = policy.clone_body(body) {
43
+ *self = BodyRepr::Some(Body::default());
44
+ } else if let Some(cloned) = body.try_clone() {
50
45
  *self = BodyRepr::Some(cloned);
51
46
  }
52
47
  }
@@ -55,25 +50,24 @@ where
55
50
  }
56
51
 
57
52
  /// [`Layer`] for retrying requests with a [`Service`] to follow redirection responses.
58
- #[derive(Clone, Copy, Default)]
59
- pub struct FollowRedirectLayer<P> {
60
- policy: P,
53
+ #[derive(Clone)]
54
+ pub struct FollowRedirectLayer {
55
+ policy: FollowRedirectPolicy,
61
56
  }
62
57
 
63
- impl<P> FollowRedirectLayer<P> {
64
- /// Create a new [`FollowRedirectLayer`] with the given redirection [`Policy`].
58
+ impl FollowRedirectLayer {
59
+ /// Create a new [`FollowRedirectLayer`] with the given redirection policy.
65
60
  #[inline(always)]
66
- pub fn with_policy(policy: P) -> Self {
61
+ pub(crate) fn with_policy(policy: FollowRedirectPolicy) -> Self {
67
62
  FollowRedirectLayer { policy }
68
63
  }
69
64
  }
70
65
 
71
- impl<S, P> Layer<S> for FollowRedirectLayer<P>
66
+ impl<S> Layer<S> for FollowRedirectLayer
72
67
  where
73
68
  S: Clone,
74
- P: Clone,
75
69
  {
76
- type Service = FollowRedirect<S, P>;
70
+ type Service = FollowRedirect<S>;
77
71
 
78
72
  #[inline(always)]
79
73
  fn layer(&self, inner: S) -> Self::Service {
@@ -82,63 +76,57 @@ where
82
76
  }
83
77
 
84
78
  /// Middleware that retries requests with a [`Service`] to follow redirection responses.
85
- #[derive(Clone, Copy)]
86
- pub struct FollowRedirect<S, P> {
79
+ #[derive(Clone)]
80
+ pub struct FollowRedirect<S> {
87
81
  inner: S,
88
- policy: P,
82
+ policy: FollowRedirectPolicy,
89
83
  }
90
84
 
91
- impl<S, P> FollowRedirect<S, P>
92
- where
93
- P: Clone,
94
- {
95
- /// Create a new [`FollowRedirect`] with the given redirection [`Policy`].
85
+ impl<S> FollowRedirect<S> {
86
+ /// Create a new [`FollowRedirect`] with the given redirection policy.
96
87
  #[inline(always)]
97
- pub fn with_policy(inner: S, policy: P) -> Self {
88
+ fn with_policy(inner: S, policy: FollowRedirectPolicy) -> Self {
98
89
  FollowRedirect { inner, policy }
99
90
  }
100
91
  }
101
92
 
102
- impl<ReqBody, ResBody, S, P> Service<Request<ReqBody>> for FollowRedirect<S, P>
93
+ impl<ResBody, S> Service<Request<Body>> for FollowRedirect<S>
103
94
  where
104
- S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
95
+ S: Service<Request<Body>, Response = Response<ResBody>> + Clone,
105
96
  S::Error: From<BoxError>,
106
- P: Policy<ReqBody, S::Error> + Clone,
107
- ReqBody: Body + Default,
108
97
  {
109
98
  type Response = Response<ResBody>;
110
99
  type Error = S::Error;
111
- type Future = ResponseFuture<S, ReqBody, P>;
100
+ type Future = ResponseFuture<S>;
112
101
 
113
102
  #[inline(always)]
114
103
  fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
115
104
  self.inner.poll_ready(cx)
116
105
  }
117
106
 
118
- fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
119
- if self.policy.follow_redirects(&mut req) {
120
- let service = self.inner.clone();
121
- let mut service = mem::replace(&mut self.inner, service);
122
- let mut policy = self.policy.clone();
123
-
124
- let mut body_repr = BodyRepr::None;
125
- body_repr.try_clone_from(req.body(), &policy);
126
- policy.on_request(&mut req);
127
-
128
- let (parts, body) = req.into_parts();
129
- let req = Request::from_parts(parts.clone(), body);
130
- ResponseFuture::Redirect {
131
- future: Either::Left(service.call(req)),
132
- pending_future: None,
133
- service,
134
- policy,
135
- parts,
136
- body_repr,
137
- }
138
- } else {
139
- ResponseFuture::Direct {
107
+ fn call(&mut self, mut req: Request<Body>) -> Self::Future {
108
+ let Some(mut policy) = self.policy.for_request(&mut req) else {
109
+ return ResponseFuture::Direct {
140
110
  future: self.inner.call(req),
141
- }
111
+ };
112
+ };
113
+
114
+ let service = self.inner.clone();
115
+ let mut service = mem::replace(&mut self.inner, service);
116
+
117
+ let mut body_repr = BodyRepr::None;
118
+ body_repr.try_clone_from(req.body());
119
+
120
+ policy.on_request(&mut req);
121
+ let (parts, body) = req.into_parts();
122
+ let request = Request::from_parts(parts, ());
123
+ ResponseFuture::Redirect {
124
+ future: Either::Left(service.call(request.clone().map(|_| body))),
125
+ pending_future: None,
126
+ service,
127
+ policy,
128
+ request,
129
+ body_repr,
142
130
  }
143
131
  }
144
132
  }
@@ -132,22 +132,19 @@ fn is_retryable_error(err: &(dyn StdError + 'static)) -> bool {
132
132
  return false;
133
133
  };
134
134
 
135
- if let Some(cause) = err.source() {
136
- if let Some(err) = cause.downcast_ref::<http2::Error>() {
137
- // They sent us a graceful shutdown, try with a new connection!
138
- if err.is_go_away() && err.is_remote() && err.reason() == Some(http2::Reason::NO_ERROR)
139
- {
140
- return true;
141
- }
135
+ if let Some(cause) = err.source()
136
+ && let Some(err) = cause.downcast_ref::<http2::Error>()
137
+ {
138
+ // They sent us a graceful shutdown, try with a new connection!
139
+ if err.is_go_away() && err.is_remote() && err.reason() == Some(http2::Reason::NO_ERROR) {
140
+ return true;
141
+ }
142
142
 
143
- // REFUSED_STREAM was sent from the server, which is safe to retry.
144
- // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2
145
- if err.is_reset()
146
- && err.is_remote()
147
- && err.reason() == Some(http2::Reason::REFUSED_STREAM)
148
- {
149
- return true;
150
- }
143
+ // REFUSED_STREAM was sent from the server, which is safe to retry.
144
+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2
145
+ if err.is_reset() && err.is_remote() && err.reason() == Some(http2::Reason::REFUSED_STREAM)
146
+ {
147
+ return true;
151
148
  }
152
149
  }
153
150
  false
@@ -57,33 +57,33 @@ pin_project! {
57
57
  /// The timeout resets after every successful read. If a single read
58
58
  /// takes longer than the specified duration, an error is returned.
59
59
  pub struct ReadTimeoutBody<B> {
60
- timeout: Duration,
61
- #[pin]
62
- sleep: Option<Pin<Box<dyn Sleep>>>,
63
60
  #[pin]
64
61
  body: B,
62
+ #[pin]
63
+ sleep: Option<Pin<Box<dyn Sleep>>>,
64
+ timeout: Duration,
65
65
  timer: Timer,
66
66
  }
67
67
  }
68
68
 
69
- /// ==== impl TimeoutBody ====
69
+ // ===== impl TimeoutBody =====
70
+
70
71
  impl<B> TimeoutBody<B> {
71
- /// Creates a new [`TimeoutBody`] with no timeout.
72
+ /// Wraps a body with the active total timeout and an optional read timeout.
72
73
  pub fn new(
74
+ body: B,
73
75
  timer: Timer,
74
- deadline: Option<Duration>,
75
76
  read_timeout: Option<Duration>,
76
- body: B,
77
+ total_timeout: Option<Pin<Box<dyn Sleep>>>,
77
78
  ) -> Self {
78
- let deadline = deadline.map(|deadline| timer.sleep(deadline));
79
- match (deadline, read_timeout) {
79
+ match (total_timeout, read_timeout) {
80
80
  (Some(total_timeout), Some(read_timeout)) => TimeoutBody::CombinedTimeout {
81
81
  body: TotalTimeoutBody {
82
82
  timeout: total_timeout,
83
83
  body: ReadTimeoutBody {
84
- timeout: read_timeout,
85
- sleep: None,
86
84
  body,
85
+ sleep: None,
86
+ timeout: read_timeout,
87
87
  timer,
88
88
  },
89
89
  },
@@ -160,7 +160,8 @@ where
160
160
  )
161
161
  }
162
162
 
163
- // ==== impl TotalTimeoutBody ====
163
+ // ===== impl TotalTimeoutBody =====
164
+
164
165
  impl<B> Body for TotalTimeoutBody<B>
165
166
  where
166
167
  B: Body,
@@ -191,7 +192,8 @@ where
191
192
  }
192
193
  }
193
194
 
194
- /// ==== impl ReadTimeoutBody ====
195
+ // ===== impl ReadTimeoutBody =====
196
+
195
197
  impl<B> Body for ReadTimeoutBody<B>
196
198
  where
197
199
  B: Body,
@@ -206,23 +208,27 @@ where
206
208
  ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
207
209
  let mut this = self.project();
208
210
 
209
- // Error if the timeout has expired.
211
+ // Start the timeout on the first poll.
210
212
  if this.sleep.is_none() {
211
- this.sleep.set(Some(this.timer.sleep(*this.timeout)));
213
+ let deadline = this.timer.now() + *this.timeout;
214
+ this.sleep.set(Some(this.timer.sleep_until(deadline)));
212
215
  }
213
216
 
214
217
  // Error if the timeout has expired.
215
- if let Some(sleep) = this.sleep.as_mut().as_pin_mut() {
216
- if sleep.poll(cx).is_ready() {
217
- return Poll::Ready(Some(Err(Box::new(TimedOut))));
218
- }
218
+ if let Some(sleep) = this.sleep.as_mut().as_pin_mut()
219
+ && sleep.poll(cx).is_ready()
220
+ {
221
+ return Poll::Ready(Some(Err(Error::body(TimedOut).into())));
219
222
  }
220
223
 
221
224
  // Poll the actual body
222
225
  match ready!(this.body.poll_frame(cx)) {
223
226
  Some(Ok(frame)) => {
224
- // Reset timeout on successful read
225
- this.sleep.set(None);
227
+ // Reuse the sleep to avoid allocating and registering a new timer for every frame.
228
+ if let Some(sleep) = this.sleep.as_mut().as_pin_mut() {
229
+ let deadline = this.timer.now() + *this.timeout;
230
+ this.timer.reset(sleep.get_mut(), deadline);
231
+ }
226
232
  Poll::Ready(Some(Ok(frame)))
227
233
  }
228
234
  Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
@@ -16,80 +16,55 @@ use crate::{
16
16
  };
17
17
 
18
18
  pin_project! {
19
- /// [`Timeout`] response future
19
+ /// Waits for response headers, then moves the total timeout into the response body.
20
20
  pub struct ResponseFuture<Fut> {
21
21
  #[pin]
22
- pub(crate) fut: Fut,
23
- pub(crate) total_timeout: Option<Pin<Box<dyn Sleep>>>,
24
- pub(crate) read_timeout: Option<Pin<Box<dyn Sleep>>>,
22
+ pub(super) fut: Fut,
23
+ pub(super) timer: Timer,
24
+ pub(super) read_timeout: Option<Duration>,
25
+ pub(super) read_timeout_fut: Option<Pin<Box<dyn Sleep>>>,
26
+ pub(super) total_timeout_fut: Option<Pin<Box<dyn Sleep>>>,
25
27
  }
26
28
  }
27
29
 
28
- impl<F, T, E> Future for ResponseFuture<F>
30
+ impl<Fut, ResBody, E> Future for ResponseFuture<Fut>
29
31
  where
30
- F: Future<Output = Result<T, E>>,
32
+ Fut: Future<Output = Result<Response<ResBody>, E>>,
31
33
  E: Into<BoxError>,
32
34
  {
33
- type Output = Result<T, BoxError>;
35
+ type Output = Result<Response<TimeoutBody<ResBody>>, BoxError>;
34
36
 
35
37
  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
36
38
  let this = self.project();
37
39
 
38
- // First, try polling the future
39
- match this.fut.poll(cx) {
40
- Poll::Ready(v) => return Poll::Ready(v.map_err(Into::into)),
41
- Poll::Pending => {}
42
- }
43
-
44
- // Helper closure for polling a timeout and returning a TimedOut error
45
- let mut check_timeout = |sleep: Option<&mut Pin<Box<dyn Sleep>>>| {
46
- if let Some(sleep) = sleep {
47
- if sleep.as_mut().poll(cx).is_ready() {
48
- return Some(Poll::Ready(Err(Error::request(TimedOut).into())));
49
- }
50
- }
51
- None
52
- };
53
-
54
- // Check total timeout first
55
- if let Some(poll) = check_timeout(this.total_timeout.as_mut()) {
56
- return poll;
40
+ // The total timer covers response headers and body. Poll it first so an
41
+ // expired timeout wins, then move it into `TimeoutBody` below.
42
+ if let Some(timeout) = this.total_timeout_fut.as_mut()
43
+ && timeout.as_mut().poll(cx).is_ready()
44
+ {
45
+ return Poll::Ready(Err(Error::request(TimedOut).into()));
57
46
  }
58
47
 
59
- // Check read timeout
60
- if let Some(poll) = check_timeout(this.read_timeout.as_mut()) {
61
- return poll;
48
+ // Before headers arrive, the read timer limits that wait. The body starts
49
+ // and resets its own read timer after each successful frame.
50
+ if let Some(timeout) = this.read_timeout_fut.as_mut()
51
+ && timeout.as_mut().poll(cx).is_ready()
52
+ {
53
+ return Poll::Ready(Err(Error::request(TimedOut).into()));
62
54
  }
63
55
 
64
- Poll::Pending
65
- }
66
- }
56
+ // Poll the request after both timers so every pending future registers the
57
+ // current waker before `ready!` returns.
58
+ let response = ready!(this.fut.poll(cx)).map_err(Into::into)?;
67
59
 
68
- pin_project! {
69
- /// Response future for wrapping the response body in [`TimeoutBody`].
70
- pub struct ResponseBodyTimeoutFuture<Fut> {
71
- #[pin]
72
- pub(super) fut: Fut,
73
- pub(super) timer: Timer,
74
- pub(super) total_timeout: Option<Duration>,
75
- pub(super) read_timeout: Option<Duration>,
76
-
77
- }
78
- }
79
-
80
- impl<Fut, ResBody, E> Future for ResponseBodyTimeoutFuture<Fut>
81
- where
82
- Fut: Future<Output = Result<Response<ResBody>, E>>,
83
- {
84
- type Output = Result<Response<TimeoutBody<ResBody>>, E>;
85
-
86
- #[inline(always)]
87
- fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
88
- let timer = self.timer.clone();
89
- let total_timeout = self.total_timeout;
90
- let read_timeout = self.read_timeout;
91
- let res = ready!(self.project().fut.poll(cx))?
92
- .map(|body| TimeoutBody::new(timer, total_timeout, read_timeout, body));
93
- Poll::Ready(Ok(res))
60
+ // Moving the running total timer preserves the original deadline.
61
+ Poll::Ready(Ok(response.map(|body| {
62
+ TimeoutBody::new(
63
+ body,
64
+ this.timer.clone(),
65
+ *this.read_timeout,
66
+ this.total_timeout_fut.take(),
67
+ )
68
+ })))
94
69
  }
95
70
  }
@@ -12,17 +12,14 @@ use http::{Request, Response};
12
12
  use tower::{BoxError, Layer, Service};
13
13
  use wreq_proto::rt::Timer as _;
14
14
 
15
- use self::{
16
- body::TimeoutBody,
17
- future::{ResponseBodyTimeoutFuture, ResponseFuture},
18
- };
15
+ use self::{body::TimeoutBody, future::ResponseFuture};
19
16
  use crate::{config::RequestConfig, rt::Timer};
20
17
 
21
18
  /// Options for configuring timeouts.
22
19
  #[derive(Clone, Copy, Default)]
23
20
  pub struct TimeoutOptions {
24
- total_timeout: Option<Duration>,
25
21
  read_timeout: Option<Duration>,
22
+ total_timeout: Option<Duration>,
26
23
  }
27
24
 
28
25
  impl TimeoutOptions {
@@ -88,7 +85,7 @@ where
88
85
  {
89
86
  type Response = Response<TimeoutBody<ResBody>>;
90
87
  type Error = BoxError;
91
- type Future = ResponseFuture<ResponseBodyTimeoutFuture<S::Future>>;
88
+ type Future = ResponseFuture<S::Future>;
92
89
 
93
90
  #[inline(always)]
94
91
  fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -99,14 +96,11 @@ where
99
96
  fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
100
97
  let (total_timeout, read_timeout) = fetch_timeout_options(&self.timeout, req.extensions());
101
98
  ResponseFuture {
102
- fut: ResponseBodyTimeoutFuture {
103
- fut: self.inner.call(req),
104
- timer: self.timer.clone(),
105
- total_timeout,
106
- read_timeout,
107
- },
108
- total_timeout: total_timeout.map(|timeout| self.timer.sleep(timeout)),
109
- read_timeout: read_timeout.map(|timeout| self.timer.sleep(timeout)),
99
+ fut: self.inner.call(req),
100
+ timer: self.timer.clone(),
101
+ read_timeout,
102
+ read_timeout_fut: read_timeout.map(|timeout| self.timer.sleep(timeout)),
103
+ total_timeout_fut: total_timeout.map(|timeout| self.timer.sleep(timeout)),
110
104
  }
111
105
  }
112
106
  }
@@ -473,6 +473,10 @@ impl RequestBuilder {
473
473
 
474
474
  /// Send a JSON body.
475
475
  ///
476
+ /// Serializes the value as JSON and sets the resulting bytes as the request body.
477
+ ///
478
+ /// Sets `Content-Type` to `application/json` unless it is already set.
479
+ ///
476
480
  /// # Optional
477
481
  ///
478
482
  /// This requires the optional `json` feature enabled.