summaryrefslogtreecommitdiffstats
path: root/vendor/reqwest/src/blocking
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
commitd1b2d29528b7794b41e66fc2136e395a02f8529b (patch)
treea4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/reqwest/src/blocking
parentReleasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz
rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/reqwest/src/blocking')
-rw-r--r--vendor/reqwest/src/blocking/body.rs352
-rw-r--r--vendor/reqwest/src/blocking/client.rs1151
-rw-r--r--vendor/reqwest/src/blocking/mod.rs109
-rw-r--r--vendor/reqwest/src/blocking/multipart.rs483
-rw-r--r--vendor/reqwest/src/blocking/request.rs1063
-rw-r--r--vendor/reqwest/src/blocking/response.rs425
-rw-r--r--vendor/reqwest/src/blocking/wait.rs78
7 files changed, 3661 insertions, 0 deletions
diff --git a/vendor/reqwest/src/blocking/body.rs b/vendor/reqwest/src/blocking/body.rs
new file mode 100644
index 000000000..1179a5485
--- /dev/null
+++ b/vendor/reqwest/src/blocking/body.rs
@@ -0,0 +1,352 @@
+use std::fmt;
+use std::fs::File;
+use std::future::Future;
+#[cfg(feature = "multipart")]
+use std::io::Cursor;
+use std::io::{self, Read};
+use std::mem;
+use std::ptr;
+
+use bytes::buf::UninitSlice;
+use bytes::Bytes;
+
+use crate::async_impl;
+
+/// The body of a `Request`.
+///
+/// In most cases, this is not needed directly, as the
+/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
+/// passing many things (like a string or vector of bytes).
+///
+/// [builder]: ./struct.RequestBuilder.html#method.body
+#[derive(Debug)]
+pub struct Body {
+ kind: Kind,
+}
+
+impl Body {
+ /// Instantiate a `Body` from a reader.
+ ///
+ /// # Note
+ ///
+ /// While allowing for many types to be used, these bodies do not have
+ /// a way to reset to the beginning and be reused. This means that when
+ /// encountering a 307 or 308 status code, instead of repeating the
+ /// request at the new location, the `Response` will be returned with
+ /// the redirect status code set.
+ ///
+ /// ```rust
+ /// # use std::fs::File;
+ /// # use reqwest::blocking::Body;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = File::open("national_secrets.txt")?;
+ /// let body = Body::new(file);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// If you have a set of bytes, like `String` or `Vec<u8>`, using the
+ /// `From` implementations for `Body` will store the data in a manner
+ /// it can be reused.
+ ///
+ /// ```rust
+ /// # use reqwest::blocking::Body;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let s = "A stringy body";
+ /// let body = Body::from(s);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn new<R: Read + Send + 'static>(reader: R) -> Body {
+ Body {
+ kind: Kind::Reader(Box::from(reader), None),
+ }
+ }
+
+ /// Create a `Body` from a `Read` where the size is known in advance
+ /// but the data should not be fully loaded into memory. This will
+ /// set the `Content-Length` header and stream from the `Read`.
+ ///
+ /// ```rust
+ /// # use std::fs::File;
+ /// # use reqwest::blocking::Body;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = File::open("a_large_file.txt")?;
+ /// let file_size = file.metadata()?.len();
+ /// let body = Body::sized(file, file_size);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn sized<R: Read + Send + 'static>(reader: R, len: u64) -> Body {
+ Body {
+ kind: Kind::Reader(Box::from(reader), Some(len)),
+ }
+ }
+
+ /// Returns the body as a byte slice if the body is already buffered in
+ /// memory. For streamed requests this method returns `None`.
+ pub fn as_bytes(&self) -> Option<&[u8]> {
+ match self.kind {
+ Kind::Reader(_, _) => None,
+ Kind::Bytes(ref bytes) => Some(bytes.as_ref()),
+ }
+ }
+
+ /// Converts streamed requests to their buffered equivalent and
+ /// returns a reference to the buffer. If the request is already
+ /// buffered, this has no effect.
+ ///
+ /// Be aware that for large requests this method is expensive
+ /// and may cause your program to run out of memory.
+ pub fn buffer(&mut self) -> Result<&[u8], crate::Error> {
+ match self.kind {
+ Kind::Reader(ref mut reader, maybe_len) => {
+ let mut bytes = if let Some(len) = maybe_len {
+ Vec::with_capacity(len as usize)
+ } else {
+ Vec::new()
+ };
+ io::copy(reader, &mut bytes).map_err(crate::error::builder)?;
+ self.kind = Kind::Bytes(bytes.into());
+ self.buffer()
+ }
+ Kind::Bytes(ref bytes) => Ok(bytes.as_ref()),
+ }
+ }
+
+ #[cfg(feature = "multipart")]
+ pub(crate) fn len(&self) -> Option<u64> {
+ match self.kind {
+ Kind::Reader(_, len) => len,
+ Kind::Bytes(ref bytes) => Some(bytes.len() as u64),
+ }
+ }
+
+ #[cfg(feature = "multipart")]
+ pub(crate) fn into_reader(self) -> Reader {
+ match self.kind {
+ Kind::Reader(r, _) => Reader::Reader(r),
+ Kind::Bytes(b) => Reader::Bytes(Cursor::new(b)),
+ }
+ }
+
+ pub(crate) fn into_async(self) -> (Option<Sender>, async_impl::Body, Option<u64>) {
+ match self.kind {
+ Kind::Reader(read, len) => {
+ let (tx, rx) = hyper::Body::channel();
+ let tx = Sender {
+ body: (read, len),
+ tx,
+ };
+ (Some(tx), async_impl::Body::wrap(rx), len)
+ }
+ Kind::Bytes(chunk) => {
+ let len = chunk.len() as u64;
+ (None, async_impl::Body::reusable(chunk), Some(len))
+ }
+ }
+ }
+
+ pub(crate) fn try_clone(&self) -> Option<Body> {
+ self.kind.try_clone().map(|kind| Body { kind })
+ }
+}
+
+enum Kind {
+ Reader(Box<dyn Read + Send>, Option<u64>),
+ Bytes(Bytes),
+}
+
+impl Kind {
+ fn try_clone(&self) -> Option<Kind> {
+ match self {
+ Kind::Reader(..) => None,
+ Kind::Bytes(v) => Some(Kind::Bytes(v.clone())),
+ }
+ }
+}
+
+impl From<Vec<u8>> for Body {
+ #[inline]
+ fn from(v: Vec<u8>) -> Body {
+ Body {
+ kind: Kind::Bytes(v.into()),
+ }
+ }
+}
+
+impl From<String> for Body {
+ #[inline]
+ fn from(s: String) -> Body {
+ s.into_bytes().into()
+ }
+}
+
+impl From<&'static [u8]> for Body {
+ #[inline]
+ fn from(s: &'static [u8]) -> Body {
+ Body {
+ kind: Kind::Bytes(Bytes::from_static(s)),
+ }
+ }
+}
+
+impl From<&'static str> for Body {
+ #[inline]
+ fn from(s: &'static str) -> Body {
+ s.as_bytes().into()
+ }
+}
+
+impl From<File> for Body {
+ #[inline]
+ fn from(f: File) -> Body {
+ let len = f.metadata().map(|m| m.len()).ok();
+ Body {
+ kind: Kind::Reader(Box::new(f), len),
+ }
+ }
+}
+impl From<Bytes> for Body {
+ #[inline]
+ fn from(b: Bytes) -> Body {
+ Body {
+ kind: Kind::Bytes(b),
+ }
+ }
+}
+
+impl fmt::Debug for Kind {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Kind::Reader(_, ref v) => f
+ .debug_struct("Reader")
+ .field("length", &DebugLength(v))
+ .finish(),
+ Kind::Bytes(ref v) => fmt::Debug::fmt(v, f),
+ }
+ }
+}
+
+struct DebugLength<'a>(&'a Option<u64>);
+
+impl<'a> fmt::Debug for DebugLength<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self.0 {
+ Some(ref len) => fmt::Debug::fmt(len, f),
+ None => f.write_str("Unknown"),
+ }
+ }
+}
+
+#[cfg(feature = "multipart")]
+pub(crate) enum Reader {
+ Reader(Box<dyn Read + Send>),
+ Bytes(Cursor<Bytes>),
+}
+
+#[cfg(feature = "multipart")]
+impl Read for Reader {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ match *self {
+ Reader::Reader(ref mut rdr) => rdr.read(buf),
+ Reader::Bytes(ref mut rdr) => rdr.read(buf),
+ }
+ }
+}
+
+pub(crate) struct Sender {
+ body: (Box<dyn Read + Send>, Option<u64>),
+ tx: hyper::body::Sender,
+}
+
+async fn send_future(sender: Sender) -> Result<(), crate::Error> {
+ use bytes::{BufMut, BytesMut};
+ use std::cmp;
+
+ let con_len = sender.body.1;
+ let cap = cmp::min(sender.body.1.unwrap_or(8192), 8192);
+ let mut written = 0;
+ let mut buf = BytesMut::with_capacity(cap as usize);
+ let mut body = sender.body.0;
+ // Put in an option so that it can be consumed on error to call abort()
+ let mut tx = Some(sender.tx);
+
+ loop {
+ if Some(written) == con_len {
+ // Written up to content-length, so stop.
+ return Ok(());
+ }
+
+ // The input stream is read only if the buffer is empty so
+ // that there is only one read in the buffer at any time.
+ //
+ // We need to know whether there is any data to send before
+ // we check the transmission channel (with poll_ready below)
+ // because somestimes the receiver disappears as soon as is
+ // considers the data is completely transmitted, which may
+ // be true.
+ //
+ // The use case is a web server that closes its
+ // input stream as soon as the data received is valid JSON.
+ // This behaviour is questionable, but it exists and the
+ // fact is that there is actually no remaining data to read.
+ if buf.is_empty() {
+ if buf.remaining_mut() == 0 {
+ buf.reserve(8192);
+ // zero out the reserved memory
+ let uninit = buf.chunk_mut();
+ unsafe {
+ ptr::write_bytes(uninit.as_mut_ptr(), 0, uninit.len());
+ }
+ }
+
+ let bytes = unsafe { mem::transmute::<&mut UninitSlice, &mut [u8]>(buf.chunk_mut()) };
+ match body.read(bytes) {
+ Ok(0) => {
+ // The buffer was empty and nothing's left to
+ // read. Return.
+ return Ok(());
+ }
+ Ok(n) => unsafe {
+ buf.advance_mut(n);
+ },
+ Err(e) => {
+ tx.take().expect("tx only taken on error").abort();
+ return Err(crate::error::body(e));
+ }
+ }
+ }
+
+ // The only way to get here is when the buffer is not empty.
+ // We can check the transmission channel
+
+ let buf_len = buf.len() as u64;
+ tx.as_mut()
+ .expect("tx only taken on error")
+ .send_data(buf.split().freeze())
+ .await
+ .map_err(crate::error::body)?;
+
+ written += buf_len;
+ }
+}
+
+impl Sender {
+ // A `Future` that may do blocking read calls.
+ // As a `Future`, this integrates easily with `wait::timeout`.
+ pub(crate) fn send(self) -> impl Future<Output = Result<(), crate::Error>> {
+ send_future(self)
+ }
+}
+
+// useful for tests, but not publicly exposed
+#[cfg(test)]
+pub(crate) fn read_to_string(mut body: Body) -> io::Result<String> {
+ let mut s = String::new();
+ match body.kind {
+ Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s),
+ Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s),
+ }
+ .map(|_| s)
+}
diff --git a/vendor/reqwest/src/blocking/client.rs b/vendor/reqwest/src/blocking/client.rs
new file mode 100644
index 000000000..e6ec6735a
--- /dev/null
+++ b/vendor/reqwest/src/blocking/client.rs
@@ -0,0 +1,1151 @@
+#[cfg(any(feature = "native-tls", feature = "__rustls",))]
+use std::any::Any;
+use std::convert::TryInto;
+use std::fmt;
+use std::future::Future;
+use std::net::IpAddr;
+use std::net::SocketAddr;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+
+use http::header::HeaderValue;
+use log::{error, trace};
+use tokio::sync::{mpsc, oneshot};
+
+use super::request::{Request, RequestBuilder};
+use super::response::Response;
+use super::wait;
+#[cfg(feature = "__tls")]
+use crate::tls;
+#[cfg(feature = "__tls")]
+use crate::Certificate;
+#[cfg(any(feature = "native-tls", feature = "__rustls"))]
+use crate::Identity;
+use crate::{async_impl, header, redirect, IntoUrl, Method, Proxy};
+
+/// A `Client` to make Requests with.
+///
+/// The Client has various configuration values to tweak, but the defaults
+/// are set to what is usually the most commonly desired value. To configure a
+/// `Client`, use `Client::builder()`.
+///
+/// The `Client` holds a connection pool internally, so it is advised that
+/// you create one and **reuse** it.
+///
+/// # Examples
+///
+/// ```rust
+/// use reqwest::blocking::Client;
+/// #
+/// # fn run() -> Result<(), reqwest::Error> {
+/// let client = Client::new();
+/// let resp = client.get("http://httpbin.org/").send()?;
+/// # drop(resp);
+/// # Ok(())
+/// # }
+///
+/// ```
+#[derive(Clone)]
+pub struct Client {
+ inner: ClientHandle,
+}
+
+/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
+///
+/// # Example
+///
+/// ```
+/// # fn run() -> Result<(), reqwest::Error> {
+/// use std::time::Duration;
+///
+/// let client = reqwest::blocking::Client::builder()
+/// .timeout(Duration::from_secs(10))
+/// .build()?;
+/// # Ok(())
+/// # }
+/// ```
+#[must_use]
+pub struct ClientBuilder {
+ inner: async_impl::ClientBuilder,
+ timeout: Timeout,
+}
+
+impl Default for ClientBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl ClientBuilder {
+ /// Constructs a new `ClientBuilder`.
+ ///
+ /// This is the same as `Client::builder()`.
+ pub fn new() -> ClientBuilder {
+ ClientBuilder {
+ inner: async_impl::ClientBuilder::new(),
+ timeout: Timeout::default(),
+ }
+ }
+
+ /// Returns a `Client` that uses this `ClientBuilder` configuration.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ ///
+ /// # Panics
+ ///
+ /// This method panics if called from within an async runtime. See docs on
+ /// [`reqwest::blocking`][crate::blocking] for details.
+ pub fn build(self) -> crate::Result<Client> {
+ ClientHandle::new(self).map(|handle| Client { inner: handle })
+ }
+
+ // Higher-level options
+
+ /// Sets the `User-Agent` header to be used by this client.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn doc() -> Result<(), reqwest::Error> {
+ /// // Name your user agent after your app?
+ /// static APP_USER_AGENT: &str = concat!(
+ /// env!("CARGO_PKG_NAME"),
+ /// "/",
+ /// env!("CARGO_PKG_VERSION"),
+ /// );
+ ///
+ /// let client = reqwest::blocking::Client::builder()
+ /// .user_agent(APP_USER_AGENT)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn user_agent<V>(self, value: V) -> ClientBuilder
+ where
+ V: TryInto<HeaderValue>,
+ V::Error: Into<http::Error>,
+ {
+ self.with_inner(move |inner| inner.user_agent(value))
+ }
+
+ /// Sets the default headers for every request.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # fn build_client() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ /// headers.insert(header::AUTHORIZATION, header::HeaderValue::from_static("secret"));
+ ///
+ /// // Consider marking security-sensitive headers with `set_sensitive`.
+ /// let mut auth_value = header::HeaderValue::from_static("secret");
+ /// auth_value.set_sensitive(true);
+ /// headers.insert(header::AUTHORIZATION, auth_value);
+ ///
+ /// // get a client builder
+ /// let client = reqwest::blocking::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Override the default headers:
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # fn build_client() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ ///
+ /// // get a client builder
+ /// let client = reqwest::blocking::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client
+ /// .get("https://www.rust-lang.org")
+ /// .header("X-MY-HEADER", "new_value")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn default_headers(self, headers: header::HeaderMap) -> ClientBuilder {
+ self.with_inner(move |inner| inner.default_headers(headers))
+ }
+
+ /// Enable a persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be preserved and included in
+ /// additional requests.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_store(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.cookie_store(enable))
+ }
+
+ /// Set the persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be passed to this store, and
+ /// additional requests will query this store for cookies.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_provider<C: crate::cookie::CookieStore + 'static>(
+ self,
+ cookie_store: Arc<C>,
+ ) -> ClientBuilder {
+ self.with_inner(|inner| inner.cookie_provider(cookie_store))
+ }
+
+ /// Enable auto gzip decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto gzip decompresson is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `gzip` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `gzip` feature to be enabled
+ #[cfg(feature = "gzip")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
+ pub fn gzip(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.gzip(enable))
+ }
+
+ /// Enable auto brotli decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto brotli decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `br`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `br`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `brotli` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `brotli` feature to be enabled
+ #[cfg(feature = "brotli")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))]
+ pub fn brotli(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.brotli(enable))
+ }
+
+ /// Enable auto deflate decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto deflate decompresson is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `deflate`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `deflate`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `deflate` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `deflate` feature to be enabled
+ #[cfg(feature = "deflate")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "deflate")))]
+ pub fn deflate(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.deflate(enable))
+ }
+
+ /// Disable auto response body gzip decompression.
+ ///
+ /// This method exists even if the optional `gzip` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use gzip decompression
+ /// even if another dependency were to enable the optional `gzip` feature.
+ pub fn no_gzip(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_gzip())
+ }
+
+ /// Disable auto response body brotli decompression.
+ ///
+ /// This method exists even if the optional `brotli` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use brotli decompression
+ /// even if another dependency were to enable the optional `brotli` feature.
+ pub fn no_brotli(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_brotli())
+ }
+
+ /// Disable auto response body deflate decompression.
+ ///
+ /// This method exists even if the optional `deflate` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use deflate decompression
+ /// even if another dependency were to enable the optional `deflate` feature.
+ pub fn no_deflate(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_deflate())
+ }
+
+ // Redirect options
+
+ /// Set a `redirect::Policy` for this client.
+ ///
+ /// Default will follow redirects up to a maximum of 10.
+ pub fn redirect(self, policy: redirect::Policy) -> ClientBuilder {
+ self.with_inner(move |inner| inner.redirect(policy))
+ }
+
+ /// Enable or disable automatic setting of the `Referer` header.
+ ///
+ /// Default is `true`.
+ pub fn referer(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.referer(enable))
+ }
+
+ // Proxy options
+
+ /// Add a `Proxy` to the list of proxies the `Client` will use.
+ ///
+ /// # Note
+ ///
+ /// Adding a proxy will disable the automatic usage of the "system" proxy.
+ pub fn proxy(self, proxy: Proxy) -> ClientBuilder {
+ self.with_inner(move |inner| inner.proxy(proxy))
+ }
+
+ /// Clear all `Proxies`, so `Client` will use no proxy anymore.
+ ///
+ /// # Note
+ /// To add a proxy exclusion list, use [crate::proxy::Proxy::no_proxy()]
+ /// on all desired proxies instead.
+ ///
+ /// This also disables the automatic usage of the "system" proxy.
+ pub fn no_proxy(self) -> ClientBuilder {
+ self.with_inner(move |inner| inner.no_proxy())
+ }
+
+ // Timeout options
+
+ /// Set a timeout for connect, read and write operations of a `Client`.
+ ///
+ /// Default is 30 seconds.
+ ///
+ /// Pass `None` to disable timeout.
+ pub fn timeout<T>(mut self, timeout: T) -> ClientBuilder
+ where
+ T: Into<Option<Duration>>,
+ {
+ self.timeout = Timeout(timeout.into());
+ self
+ }
+
+ /// Set a timeout for only the connect phase of a `Client`.
+ ///
+ /// Default is `None`.
+ pub fn connect_timeout<T>(self, timeout: T) -> ClientBuilder
+ where
+ T: Into<Option<Duration>>,
+ {
+ let timeout = timeout.into();
+ if let Some(dur) = timeout {
+ self.with_inner(|inner| inner.connect_timeout(dur))
+ } else {
+ self
+ }
+ }
+
+ /// Set whether connections should emit verbose logs.
+ ///
+ /// Enabling this option will emit [log][] messages at the `TRACE` level
+ /// for read and write operations on connections.
+ ///
+ /// [log]: https://crates.io/crates/log
+ pub fn connection_verbose(self, verbose: bool) -> ClientBuilder {
+ self.with_inner(move |inner| inner.connection_verbose(verbose))
+ }
+
+ // HTTP options
+
+ /// Set an optional timeout for idle sockets being kept-alive.
+ ///
+ /// Pass `None` to disable timeout.
+ ///
+ /// Default is 90 seconds.
+ pub fn pool_idle_timeout<D>(self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.with_inner(|inner| inner.pool_idle_timeout(val))
+ }
+
+ /// Sets the maximum idle connection per host allowed in the pool.
+ pub fn pool_max_idle_per_host(self, max: usize) -> ClientBuilder {
+ self.with_inner(move |inner| inner.pool_max_idle_per_host(max))
+ }
+
+ /// Send headers as title case instead of lowercase.
+ pub fn http1_title_case_headers(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http1_title_case_headers())
+ }
+
+ /// Set whether HTTP/1 connections will accept obsolete line folding for
+ /// header values.
+ ///
+ /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
+ /// parsing.
+ pub fn http1_allow_obsolete_multiline_headers_in_responses(self, value: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.http1_allow_obsolete_multiline_headers_in_responses(value))
+ }
+
+ /// Only use HTTP/1.
+ pub fn http1_only(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http1_only())
+ }
+
+ /// Allow HTTP/0.9 responses
+ pub fn http09_responses(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http09_responses())
+ }
+
+ /// Only use HTTP/2.
+ pub fn http2_prior_knowledge(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_prior_knowledge())
+ }
+
+ /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control.
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_stream_window_size(self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_initial_stream_window_size(sz))
+ }
+
+ /// Sets the max connection-level flow control for HTTP2
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_connection_window_size(self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_initial_connection_window_size(sz))
+ }
+
+ /// Sets whether to use an adaptive flow control.
+ ///
+ /// Enabling this will override the limits set in `http2_initial_stream_window_size` and
+ /// `http2_initial_connection_window_size`.
+ pub fn http2_adaptive_window(self, enabled: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_adaptive_window(enabled))
+ }
+
+ /// Sets the maximum frame size to use for HTTP2.
+ ///
+ /// Default is currently 16,384 but may change internally to optimize for common uses.
+ pub fn http2_max_frame_size(self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_max_frame_size(sz))
+ }
+
+ // TCP options
+
+ /// Set whether sockets have `TCP_NODELAY` enabled.
+ ///
+ /// Default is `true`.
+ pub fn tcp_nodelay(self, enabled: bool) -> ClientBuilder {
+ self.with_inner(move |inner| inner.tcp_nodelay(enabled))
+ }
+
+ /// Bind to a local IP Address.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::net::IpAddr;
+ /// let local_addr = IpAddr::from([12, 4, 1, 8]);
+ /// let client = reqwest::blocking::Client::builder()
+ /// .local_address(local_addr)
+ /// .build().unwrap();
+ /// ```
+ pub fn local_address<T>(self, addr: T) -> ClientBuilder
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ self.with_inner(move |inner| inner.local_address(addr))
+ }
+
+ /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration.
+ ///
+ /// If `None`, the option will not be set.
+ pub fn tcp_keepalive<D>(self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.with_inner(move |inner| inner.tcp_keepalive(val))
+ }
+
+ // TLS options
+
+ /// Add a custom root certificate.
+ ///
+ /// This allows connecting to a server that has a self-signed
+ /// certificate for example. This **does not** replace the existing
+ /// trusted store.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use std::fs::File;
+ /// # use std::io::Read;
+ /// # fn build_client() -> Result<(), Box<dyn std::error::Error>> {
+ /// // read a local binary DER encoded certificate
+ /// let der = std::fs::read("my-cert.der")?;
+ ///
+ /// // create a certificate
+ /// let cert = reqwest::Certificate::from_der(&der)?;
+ ///
+ /// // get a client builder
+ /// let client = reqwest::blocking::Client::builder()
+ /// .add_root_certificate(cert)
+ /// .build()?;
+ /// # drop(client);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder {
+ self.with_inner(move |inner| inner.add_root_certificate(cert))
+ }
+
+ /// Controls the use of built-in system certificates during certificate validation.
+ ///
+ /// Defaults to `true` -- built-in system certs will be used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_built_in_root_certs(self, tls_built_in_root_certs: bool) -> ClientBuilder {
+ self.with_inner(move |inner| inner.tls_built_in_root_certs(tls_built_in_root_certs))
+ }
+
+ /// Sets the identity to be used for client certificate authentication.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` or `rustls-tls(-...)` feature to be
+ /// enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn identity(self, identity: Identity) -> ClientBuilder {
+ self.with_inner(move |inner| inner.identity(identity))
+ }
+
+ /// Controls the use of hostname verification.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before you use this method. If
+ /// hostname verification is not used, any valid certificate for any
+ /// site will be trusted for use from any other. This introduces a
+ /// significant vulnerability to man-in-the-middle attacks.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostname: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname))
+ }
+
+ /// Controls the use of certificate validation.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before using this method. If
+ /// invalid certificates are trusted, *any* certificate for *any* site
+ /// will be trusted for use. This includes expired certificates. This
+ /// introduces significant vulnerabilities, and should only be used
+ /// as a last resort.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs))
+ }
+
+ /// Controls the use of TLS server name indication.
+ ///
+ /// Defaults to `true`.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_sni(self, tls_sni: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.tls_sni(tls_sni))
+ }
+
+ /// Set the minimum required TLS version for connections.
+ ///
+ /// By default the TLS backend's own default is used.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a minimum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn min_tls_version(self, version: tls::Version) -> ClientBuilder {
+ self.with_inner(|inner| inner.min_tls_version(version))
+ }
+
+ /// Set the maximum allowed TLS version for connections.
+ ///
+ /// By default there's no maximum.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a maximum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn max_tls_version(self, version: tls::Version) -> ClientBuilder {
+ self.with_inner(|inner| inner.max_tls_version(version))
+ }
+
+ /// Force using the native TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `native-tls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn use_native_tls(self) -> ClientBuilder {
+ self.with_inner(move |inner| inner.use_native_tls())
+ }
+
+ /// Force using the Rustls TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `rustls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `rustls-tls(-...)` feature to be enabled.
+ #[cfg(feature = "__rustls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
+ pub fn use_rustls_tls(self) -> ClientBuilder {
+ self.with_inner(move |inner| inner.use_rustls_tls())
+ }
+
+ /// Use a preconfigured TLS backend.
+ ///
+ /// If the passed `Any` argument is not a TLS backend that reqwest
+ /// understands, the `ClientBuilder` will error when calling `build`.
+ ///
+ /// # Advanced
+ ///
+ /// This is an advanced option, and can be somewhat brittle. Usage requires
+ /// keeping the preconfigured TLS argument version in sync with reqwest,
+ /// since version mismatches will result in an "unknown" TLS backend.
+ ///
+ /// If possible, it's preferable to use the methods on `ClientBuilder`
+ /// to configure reqwest's TLS.
+ ///
+ /// # Optional
+ ///
+ /// This requires one of the optional features `native-tls` or
+ /// `rustls-tls(-...)` to be enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn use_preconfigured_tls(self, tls: impl Any) -> ClientBuilder {
+ self.with_inner(move |inner| inner.use_preconfigured_tls(tls))
+ }
+
+ /// Enables the [trust-dns](trust_dns_resolver) async resolver instead of a default threadpool using `getaddrinfo`.
+ ///
+ /// If the `trust-dns` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `trust-dns` feature to be enabled
+ #[cfg(feature = "trust-dns")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "trust-dns")))]
+ pub fn trust_dns(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.trust_dns(enable))
+ }
+
+ /// Disables the trust-dns async resolver.
+ ///
+ /// This method exists even if the optional `trust-dns` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use the trust-dns async resolver
+ /// even if another dependency were to enable the optional `trust-dns` feature.
+ pub fn no_trust_dns(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_trust_dns())
+ }
+
+ /// Restrict the Client to be used with HTTPS only requests.
+ ///
+ /// Defaults to false.
+ pub fn https_only(self, enabled: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.https_only(enabled))
+ }
+
+ /// Override DNS resolution for specific domains to a particular IP address.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addr will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder {
+ self.resolve_to_addrs(domain, &[addr])
+ }
+
+ /// Override DNS resolution for specific domains to particular IP addresses.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addresses will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve_to_addrs(self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder {
+ self.with_inner(|inner| inner.resolve_to_addrs(domain, addrs))
+ }
+
+ // private
+
+ fn with_inner<F>(mut self, func: F) -> ClientBuilder
+ where
+ F: FnOnce(async_impl::ClientBuilder) -> async_impl::ClientBuilder,
+ {
+ self.inner = func(self.inner);
+ self
+ }
+}
+
+impl From<async_impl::ClientBuilder> for ClientBuilder {
+ fn from(builder: async_impl::ClientBuilder) -> Self {
+ Self {
+ inner: builder,
+ timeout: Timeout::default(),
+ }
+ }
+}
+
+impl Default for Client {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Client {
+ /// Constructs a new `Client`.
+ ///
+ /// # Panic
+ ///
+ /// This method panics if TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ ///
+ /// Use `Client::builder()` if you wish to handle the failure as an `Error`
+ /// instead of panicking.
+ ///
+ /// This method also panics if called from within an async runtime. See docs
+ /// on [`reqwest::blocking`][crate::blocking] for details.
+ pub fn new() -> Client {
+ ClientBuilder::new().build().expect("Client::new()")
+ }
+
+ /// Creates a `ClientBuilder` to configure a `Client`.
+ ///
+ /// This is the same as `ClientBuilder::new()`.
+ pub fn builder() -> ClientBuilder {
+ ClientBuilder::new()
+ }
+
+ /// Convenience method to make a `GET` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::GET, url)
+ }
+
+ /// Convenience method to make a `POST` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::POST, url)
+ }
+
+ /// Convenience method to make a `PUT` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PUT, url)
+ }
+
+ /// Convenience method to make a `PATCH` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PATCH, url)
+ }
+
+ /// Convenience method to make a `DELETE` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::DELETE, url)
+ }
+
+ /// Convenience method to make a `HEAD` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::HEAD, url)
+ }
+
+ /// Start building a `Request` with the `Method` and `Url`.
+ ///
+ /// Returns a `RequestBuilder`, which will allow setting headers and
+ /// request body before sending.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
+ let req = url.into_url().map(move |url| Request::new(method, url));
+ RequestBuilder::new(self.clone(), req)
+ }
+
+ /// Executes a `Request`.
+ ///
+ /// A `Request` can be built manually with `Request::new()` or obtained
+ /// from a RequestBuilder with `RequestBuilder::build()`.
+ ///
+ /// You should prefer to use the `RequestBuilder` and
+ /// `RequestBuilder::send()`.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// or redirect limit was exhausted.
+ pub fn execute(&self, request: Request) -> crate::Result<Response> {
+ self.inner.execute_request(request)
+ }
+}
+
+impl fmt::Debug for Client {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Client")
+ //.field("gzip", &self.inner.gzip)
+ //.field("redirect_policy", &self.inner.redirect_policy)
+ //.field("referer", &self.inner.referer)
+ .finish()
+ }
+}
+
+impl fmt::Debug for ClientBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.inner.fmt(f)
+ }
+}
+
+#[derive(Clone)]
+struct ClientHandle {
+ timeout: Timeout,
+ inner: Arc<InnerClientHandle>,
+}
+
+type OneshotResponse = oneshot::Sender<crate::Result<async_impl::Response>>;
+type ThreadSender = mpsc::UnboundedSender<(async_impl::Request, OneshotResponse)>;
+
+struct InnerClientHandle {
+ tx: Option<ThreadSender>,
+ thread: Option<thread::JoinHandle<()>>,
+}
+
+impl Drop for InnerClientHandle {
+ fn drop(&mut self) {
+ let id = self
+ .thread
+ .as_ref()
+ .map(|h| h.thread().id())
+ .expect("thread not dropped yet");
+
+ trace!("closing runtime thread ({:?})", id);
+ self.tx.take();
+ trace!("signaled close for runtime thread ({:?})", id);
+ self.thread.take().map(|h| h.join());
+ trace!("closed runtime thread ({:?})", id);
+ }
+}
+
+impl ClientHandle {
+ fn new(builder: ClientBuilder) -> crate::Result<ClientHandle> {
+ let timeout = builder.timeout;
+ let builder = builder.inner;
+ let (tx, rx) = mpsc::unbounded_channel::<(async_impl::Request, OneshotResponse)>();
+ let (spawn_tx, spawn_rx) = oneshot::channel::<crate::Result<()>>();
+ let handle = thread::Builder::new()
+ .name("reqwest-internal-sync-runtime".into())
+ .spawn(move || {
+ use tokio::runtime;
+ let rt = match runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .map_err(crate::error::builder)
+ {
+ Err(e) => {
+ if let Err(e) = spawn_tx.send(Err(e)) {
+ error!("Failed to communicate runtime creation failure: {:?}", e);
+ }
+ return;
+ }
+ Ok(v) => v,
+ };
+
+ let f = async move {
+ let client = match builder.build() {
+ Err(e) => {
+ if let Err(e) = spawn_tx.send(Err(e)) {
+ error!("Failed to communicate client creation failure: {:?}", e);
+ }
+ return;
+ }
+ Ok(v) => v,
+ };
+ if let Err(e) = spawn_tx.send(Ok(())) {
+ error!("Failed to communicate successful startup: {:?}", e);
+ return;
+ }
+
+ let mut rx = rx;
+
+ while let Some((req, req_tx)) = rx.recv().await {
+ let req_fut = client.execute(req);
+ tokio::spawn(forward(req_fut, req_tx));
+ }
+
+ trace!("({:?}) Receiver is shutdown", thread::current().id());
+ };
+
+ trace!("({:?}) start runtime::block_on", thread::current().id());
+ rt.block_on(f);
+ trace!("({:?}) end runtime::block_on", thread::current().id());
+ drop(rt);
+ trace!("({:?}) finished", thread::current().id());
+ })
+ .map_err(crate::error::builder)?;
+
+ // Wait for the runtime thread to start up...
+ match wait::timeout(spawn_rx, None) {
+ Ok(Ok(())) => (),
+ Ok(Err(err)) => return Err(err),
+ Err(_canceled) => event_loop_panicked(),
+ }
+
+ let inner_handle = Arc::new(InnerClientHandle {
+ tx: Some(tx),
+ thread: Some(handle),
+ });
+
+ Ok(ClientHandle {
+ timeout,
+ inner: inner_handle,
+ })
+ }
+
+ fn execute_request(&self, req: Request) -> crate::Result<Response> {
+ let (tx, rx) = oneshot::channel();
+ let (req, body) = req.into_async();
+ let url = req.url().clone();
+ let timeout = req.timeout().copied().or(self.timeout.0);
+
+ self.inner
+ .tx
+ .as_ref()
+ .expect("core thread exited early")
+ .send((req, tx))
+ .expect("core thread panicked");
+
+ let result: Result<crate::Result<async_impl::Response>, wait::Waited<crate::Error>> =
+ if let Some(body) = body {
+ let f = async move {
+ body.send().await?;
+ rx.await.map_err(|_canceled| event_loop_panicked())
+ };
+ wait::timeout(f, timeout)
+ } else {
+ let f = async move { rx.await.map_err(|_canceled| event_loop_panicked()) };
+ wait::timeout(f, timeout)
+ };
+
+ match result {
+ Ok(Err(err)) => Err(err.with_url(url)),
+ Ok(Ok(res)) => Ok(Response::new(
+ res,
+ timeout,
+ KeepCoreThreadAlive(Some(self.inner.clone())),
+ )),
+ Err(wait::Waited::TimedOut(e)) => Err(crate::error::request(e).with_url(url)),
+ Err(wait::Waited::Inner(err)) => Err(err.with_url(url)),
+ }
+ }
+}
+
+async fn forward<F>(fut: F, mut tx: OneshotResponse)
+where
+ F: Future<Output = crate::Result<async_impl::Response>>,
+{
+ use std::task::Poll;
+
+ futures_util::pin_mut!(fut);
+
+ // "select" on the sender being canceled, and the future completing
+ let res = futures_util::future::poll_fn(|cx| {
+ match fut.as_mut().poll(cx) {
+ Poll::Ready(val) => Poll::Ready(Some(val)),
+ Poll::Pending => {
+ // check if the callback is canceled
+ futures_core::ready!(tx.poll_closed(cx));
+ Poll::Ready(None)
+ }
+ }
+ })
+ .await;
+
+ if let Some(res) = res {
+ let _ = tx.send(res);
+ }
+ // else request is canceled
+}
+
+#[derive(Clone, Copy)]
+struct Timeout(Option<Duration>);
+
+impl Default for Timeout {
+ fn default() -> Timeout {
+ // default mentioned in ClientBuilder::timeout() doc comment
+ Timeout(Some(Duration::from_secs(30)))
+ }
+}
+
+pub(crate) struct KeepCoreThreadAlive(Option<Arc<InnerClientHandle>>);
+
+impl KeepCoreThreadAlive {
+ pub(crate) fn empty() -> KeepCoreThreadAlive {
+ KeepCoreThreadAlive(None)
+ }
+}
+
+#[cold]
+#[inline(never)]
+fn event_loop_panicked() -> ! {
+ // The only possible reason there would be a Canceled error
+ // is if the thread running the event loop panicked. We could return
+ // an Err here, like a BrokenPipe, but the Client is not
+ // recoverable. Additionally, the panic in the other thread
+ // is not normal, and should likely be propagated.
+ panic!("event loop thread panicked");
+}
diff --git a/vendor/reqwest/src/blocking/mod.rs b/vendor/reqwest/src/blocking/mod.rs
new file mode 100644
index 000000000..487387545
--- /dev/null
+++ b/vendor/reqwest/src/blocking/mod.rs
@@ -0,0 +1,109 @@
+//! A blocking Client API.
+//!
+//! The blocking `Client` will block the current thread to execute, instead
+//! of returning futures that need to be executed on a runtime.
+//!
+//! Conversely, the functionality in `reqwest::blocking` must *not* be executed
+//! within an async runtime, or it will panic when attempting to block. If
+//! calling directly from an async function, consider using an async
+//! [`reqwest::Client`][crate::Client] instead. If the immediate context is only
+//! synchronous, but a transitive caller is async, consider changing that caller
+//! to use [`tokio::task::spawn_blocking`] around the calls that need to block.
+//!
+//! # Optional
+//!
+//! This requires the optional `blocking` feature to be enabled.
+//!
+//! # Making a GET request
+//!
+//! For a single request, you can use the [`get`](get) shortcut method.
+//!
+//! ```rust
+//! # use reqwest::{Error, Response};
+//!
+//! # fn run() -> Result<(), Error> {
+//! let body = reqwest::blocking::get("https://www.rust-lang.org")?
+//! .text()?;
+//!
+//! println!("body = {:?}", body);
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! Additionally, the blocking [`Response`](Response) struct implements Rust's
+//! `Read` trait, so many useful standard library and third party crates will
+//! have convenience methods that take a `Response` anywhere `T: Read` is
+//! acceptable.
+//!
+//! **NOTE**: If you plan to perform multiple requests, it is best to create a
+//! [`Client`](Client) and reuse it, taking advantage of keep-alive connection
+//! pooling.
+//!
+//! # Making POST requests (or setting request bodies)
+//!
+//! There are several ways you can set the body of a request. The basic one is
+//! by using the `body()` method of a [`RequestBuilder`](RequestBuilder). This lets you set the
+//! exact raw bytes of what the body should be. It accepts various types,
+//! including `String`, `Vec<u8>`, and `File`. If you wish to pass a custom
+//! Reader, you can use the `reqwest::blocking::Body::new()` constructor.
+//!
+//! ```rust
+//! # use reqwest::Error;
+//! #
+//! # fn run() -> Result<(), Error> {
+//! let client = reqwest::blocking::Client::new();
+//! let res = client.post("http://httpbin.org/post")
+//! .body("the exact body that is sent")
+//! .send()?;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! ## And More
+//!
+//! Most features available to the asynchronous `Client` are also available,
+//! on the blocking `Client`, see those docs for more.
+
+mod body;
+mod client;
+#[cfg(feature = "multipart")]
+pub mod multipart;
+mod request;
+mod response;
+mod wait;
+
+pub use self::body::Body;
+pub use self::client::{Client, ClientBuilder};
+pub use self::request::{Request, RequestBuilder};
+pub use self::response::Response;
+
+/// Shortcut method to quickly make a *blocking* `GET` request.
+///
+/// **NOTE**: This function creates a new internal `Client` on each call,
+/// and so should not be used if making many requests. Create a
+/// [`Client`](./struct.Client.html) instead.
+///
+/// # Examples
+///
+/// ```rust
+/// # fn run() -> Result<(), reqwest::Error> {
+/// let body = reqwest::blocking::get("https://www.rust-lang.org")?
+/// .text()?;
+/// # Ok(())
+/// # }
+/// # fn main() { }
+/// ```
+///
+/// # Errors
+///
+/// This function fails if:
+///
+/// - the native TLS backend cannot be initialized,
+/// - the supplied `Url` cannot be parsed,
+/// - there was an error while sending request,
+/// - a redirect loop was detected,
+/// - the redirect limit was exhausted, or
+/// - the total download time exceeds 30 seconds.
+pub fn get<T: crate::IntoUrl>(url: T) -> crate::Result<Response> {
+ Client::builder().build()?.get(url).send()
+}
diff --git a/vendor/reqwest/src/blocking/multipart.rs b/vendor/reqwest/src/blocking/multipart.rs
new file mode 100644
index 000000000..9e7dfd3c7
--- /dev/null
+++ b/vendor/reqwest/src/blocking/multipart.rs
@@ -0,0 +1,483 @@
+//! multipart/form-data
+//!
+//! To send a `multipart/form-data` body, a [`Form`](crate::blocking::multipart::Form) is built up, adding
+//! fields or customized [`Part`](crate::blocking::multipart::Part)s, and then calling the
+//! [`multipart`][builder] method on the `RequestBuilder`.
+//!
+//! # Example
+//!
+//! ```
+//! use reqwest::blocking::multipart;
+//!
+//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
+//! let form = multipart::Form::new()
+//! // Adding just a simple text field...
+//! .text("username", "seanmonstar")
+//! // And a file...
+//! .file("photo", "/path/to/photo.png")?;
+//!
+//! // Customize all the details of a Part if needed...
+//! let bio = multipart::Part::text("hallo peeps")
+//! .file_name("bio.txt")
+//! .mime_str("text/plain")?;
+//!
+//! // Add the custom part to our form...
+//! let form = form.part("biography", bio);
+//!
+//! // And finally, send the form
+//! let client = reqwest::blocking::Client::new();
+//! let resp = client
+//! .post("http://localhost:8080/user")
+//! .multipart(form)
+//! .send()?;
+//! # Ok(())
+//! # }
+//! # fn main() {}
+//! ```
+//!
+//! [builder]: ../struct.RequestBuilder.html#method.multipart
+use std::borrow::Cow;
+use std::fmt;
+use std::fs::File;
+use std::io::{self, Cursor, Read};
+use std::path::Path;
+
+use mime_guess::{self, Mime};
+
+use super::Body;
+use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
+use crate::header::HeaderMap;
+
+/// A multipart/form-data request.
+pub struct Form {
+ inner: FormParts<Part>,
+}
+
+/// A field in a multipart form.
+pub struct Part {
+ meta: PartMetadata,
+ value: Body,
+}
+
+impl Default for Form {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Form {
+ /// Creates a new Form without any content.
+ pub fn new() -> Form {
+ Form {
+ inner: FormParts::new(),
+ }
+ }
+
+ /// Get the boundary that this form will use.
+ #[inline]
+ pub fn boundary(&self) -> &str {
+ self.inner.boundary()
+ }
+
+ /// Add a data field with supplied name and value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// let form = reqwest::blocking::multipart::Form::new()
+ /// .text("username", "seanmonstar")
+ /// .text("password", "secret");
+ /// ```
+ pub fn text<T, U>(self, name: T, value: U) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ U: Into<Cow<'static, str>>,
+ {
+ self.part(name, Part::text(value))
+ }
+
+ /// Adds a file field.
+ ///
+ /// The path will be used to try to guess the filename and mime.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// # fn run() -> std::io::Result<()> {
+ /// let files = reqwest::blocking::multipart::Form::new()
+ /// .file("key", "/path/to/file")?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Errors when the file cannot be opened.
+ pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
+ where
+ T: Into<Cow<'static, str>>,
+ U: AsRef<Path>,
+ {
+ Ok(self.part(name, Part::file(path)?))
+ }
+
+ /// Adds a customized Part.
+ pub fn part<T>(self, name: T, part: Part) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.part(name, part))
+ }
+
+ /// Configure this `Form` to percent-encode using the `path-segment` rules.
+ pub fn percent_encode_path_segment(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_path_segment())
+ }
+
+ /// Configure this `Form` to percent-encode using the `attr-char` rules.
+ pub fn percent_encode_attr_chars(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_attr_chars())
+ }
+
+ /// Configure this `Form` to skip percent-encoding
+ pub fn percent_encode_noop(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_noop())
+ }
+
+ pub(crate) fn reader(self) -> Reader {
+ Reader::new(self)
+ }
+
+ // If predictable, computes the length the request will have
+ // The length should be preditable if only String and file fields have been added,
+ // but not if a generic reader has been added;
+ pub(crate) fn compute_length(&mut self) -> Option<u64> {
+ self.inner.compute_length()
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(FormParts<Part>) -> FormParts<Part>,
+ {
+ Form {
+ inner: func(self.inner),
+ }
+ }
+}
+
+impl fmt::Debug for Form {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.inner.fmt_fields("Form", f)
+ }
+}
+
+impl Part {
+ /// Makes a text parameter.
+ pub fn text<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(string) => Body::from(string),
+ };
+ Part::new(body)
+ }
+
+ /// Makes a new parameter from arbitrary bytes.
+ pub fn bytes<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, [u8]>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(vec) => Body::from(vec),
+ };
+ Part::new(body)
+ }
+
+ /// Adds a generic reader.
+ ///
+ /// Does not set filename or mime.
+ pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
+ Part::new(Body::new(value))
+ }
+
+ /// Adds a generic reader with known length.
+ ///
+ /// Does not set filename or mime.
+ pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
+ Part::new(Body::sized(value, length))
+ }
+
+ /// Makes a file parameter.
+ ///
+ /// # Errors
+ ///
+ /// Errors when the file cannot be opened.
+ pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
+ let path = path.as_ref();
+ let file_name = path
+ .file_name()
+ .map(|filename| filename.to_string_lossy().into_owned());
+ let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
+ let mime = mime_guess::from_ext(ext).first_or_octet_stream();
+ let file = File::open(path)?;
+ let field = Part::new(Body::from(file)).mime(mime);
+
+ Ok(if let Some(file_name) = file_name {
+ field.file_name(file_name)
+ } else {
+ field
+ })
+ }
+
+ fn new(value: Body) -> Part {
+ Part {
+ meta: PartMetadata::new(),
+ value,
+ }
+ }
+
+ /// Tries to set the mime of this part.
+ pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
+ Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
+ }
+
+ // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
+ fn mime(self, mime: Mime) -> Part {
+ self.with_inner(move |inner| inner.mime(mime))
+ }
+
+ /// Sets the filename, builder style.
+ pub fn file_name<T>(self, filename: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.file_name(filename))
+ }
+
+ /// Sets custom headers for the part.
+ pub fn headers(self, headers: HeaderMap) -> Part {
+ self.with_inner(move |inner| inner.headers(headers))
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(PartMetadata) -> PartMetadata,
+ {
+ Part {
+ meta: func(self.meta),
+ value: self.value,
+ }
+ }
+}
+
+impl fmt::Debug for Part {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut dbg = f.debug_struct("Part");
+ dbg.field("value", &self.value);
+ self.meta.fmt_fields(&mut dbg);
+ dbg.finish()
+ }
+}
+
+impl PartProps for Part {
+ fn value_len(&self) -> Option<u64> {
+ self.value.len()
+ }
+
+ fn metadata(&self) -> &PartMetadata {
+ &self.meta
+ }
+}
+
+pub(crate) struct Reader {
+ form: Form,
+ active_reader: Option<Box<dyn Read + Send>>,
+}
+
+impl fmt::Debug for Reader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Reader").field("form", &self.form).finish()
+ }
+}
+
+impl Reader {
+ fn new(form: Form) -> Reader {
+ let mut reader = Reader {
+ form,
+ active_reader: None,
+ };
+ reader.next_reader();
+ reader
+ }
+
+ fn next_reader(&mut self) {
+ self.active_reader = if !self.form.inner.fields.is_empty() {
+ // We need to move out of the vector here because we are consuming the field's reader
+ let (name, field) = self.form.inner.fields.remove(0);
+ let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
+ let header = Cursor::new({
+ // Try to use cached headers created by compute_length
+ let mut h = if !self.form.inner.computed_headers.is_empty() {
+ self.form.inner.computed_headers.remove(0)
+ } else {
+ self.form
+ .inner
+ .percent_encoding
+ .encode_headers(&name, field.metadata())
+ };
+ h.extend_from_slice(b"\r\n\r\n");
+ h
+ });
+ let reader = boundary
+ .chain(header)
+ .chain(field.value.into_reader())
+ .chain(Cursor::new("\r\n"));
+ // According to https://tools.ietf.org/html/rfc2046#section-5.1.1
+ // the very last field has a special boundary
+ if !self.form.inner.fields.is_empty() {
+ Some(Box::new(reader))
+ } else {
+ Some(Box::new(reader.chain(Cursor::new(format!(
+ "--{}--\r\n",
+ self.form.boundary()
+ )))))
+ }
+ } else {
+ None
+ }
+ }
+}
+
+impl Read for Reader {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let mut total_bytes_read = 0usize;
+ let mut last_read_bytes;
+ loop {
+ match self.active_reader {
+ Some(ref mut reader) => {
+ last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
+ total_bytes_read += last_read_bytes;
+ if total_bytes_read == buf.len() {
+ return Ok(total_bytes_read);
+ }
+ }
+ None => return Ok(total_bytes_read),
+ };
+ if last_read_bytes == 0 && !buf.is_empty() {
+ self.next_reader();
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn form_empty() {
+ let mut output = Vec::new();
+ let mut form = Form::new();
+ let length = form.compute_length();
+ form.reader().read_to_end(&mut output).unwrap();
+ assert_eq!(output, b"");
+ assert_eq!(length.unwrap(), 0);
+ }
+
+ #[test]
+ fn read_to_end() {
+ let mut output = Vec::new();
+ let mut form = Form::new()
+ .part("reader1", Part::reader(std::io::empty()))
+ .part("key1", Part::text("value1"))
+ .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
+ .part("reader2", Part::reader(std::io::empty()))
+ .part("key3", Part::text("value3").file_name("filename"));
+ form.inner.boundary = "boundary".to_string();
+ let length = form.compute_length();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
+ \r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
+ value1\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\r\n\
+ value2\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
+ \r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
+ value3\r\n--boundary--\r\n";
+ form.reader().read_to_end(&mut output).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&output).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
+ assert!(length.is_none());
+ }
+
+ #[test]
+ fn read_to_end_with_length() {
+ let mut output = Vec::new();
+ let mut form = Form::new()
+ .text("key1", "value1")
+ .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
+ .part("key3", Part::text("value3").file_name("filename"));
+ form.inner.boundary = "boundary".to_string();
+ let length = form.compute_length();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
+ value1\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\r\n\
+ value2\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
+ value3\r\n--boundary--\r\n";
+ form.reader().read_to_end(&mut output).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&output).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
+ assert_eq!(length.unwrap(), expected.len() as u64);
+ }
+
+ #[test]
+ fn read_to_end_with_header() {
+ let mut output = Vec::new();
+ let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
+ let mut headers = HeaderMap::new();
+ headers.insert("Hdr3", "/a/b/c".parse().unwrap());
+ part = part.headers(headers);
+ let mut form = Form::new().part("key2", part);
+ form.inner.boundary = "boundary".to_string();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\
+ hdr3: /a/b/c\r\n\
+ \r\n\
+ value2\r\n\
+ --boundary--\r\n";
+ form.reader().read_to_end(&mut output).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&output).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
+ }
+}
diff --git a/vendor/reqwest/src/blocking/request.rs b/vendor/reqwest/src/blocking/request.rs
new file mode 100644
index 000000000..71bbdc932
--- /dev/null
+++ b/vendor/reqwest/src/blocking/request.rs
@@ -0,0 +1,1063 @@
+use std::convert::TryFrom;
+use std::fmt;
+use std::time::Duration;
+
+use http::{request::Parts, Request as HttpRequest, Version};
+use serde::Serialize;
+#[cfg(feature = "json")]
+use serde_json;
+use serde_urlencoded;
+
+use super::body::{self, Body};
+#[cfg(feature = "multipart")]
+use super::multipart;
+use super::Client;
+use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
+use crate::{async_impl, Method, Url};
+
+/// A request which can be executed with `Client::execute()`.
+pub struct Request {
+ body: Option<Body>,
+ inner: async_impl::Request,
+}
+
+/// A builder to construct the properties of a `Request`.
+///
+/// To construct a `RequestBuilder`, refer to the `Client` documentation.
+#[derive(Debug)]
+#[must_use = "RequestBuilder does nothing until you 'send' it"]
+pub struct RequestBuilder {
+ client: Client,
+ request: crate::Result<Request>,
+}
+
+impl Request {
+ /// Constructs a new request.
+ #[inline]
+ pub fn new(method: Method, url: Url) -> Self {
+ Request {
+ body: None,
+ inner: async_impl::Request::new(method, url),
+ }
+ }
+
+ /// Get the method.
+ #[inline]
+ pub fn method(&self) -> &Method {
+ self.inner.method()
+ }
+
+ /// Get a mutable reference to the method.
+ #[inline]
+ pub fn method_mut(&mut self) -> &mut Method {
+ self.inner.method_mut()
+ }
+
+ /// Get the url.
+ #[inline]
+ pub fn url(&self) -> &Url {
+ self.inner.url()
+ }
+
+ /// Get a mutable reference to the url.
+ #[inline]
+ pub fn url_mut(&mut self) -> &mut Url {
+ self.inner.url_mut()
+ }
+
+ /// Get the headers.
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ self.inner.headers()
+ }
+
+ /// Get a mutable reference to the headers.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ self.inner.headers_mut()
+ }
+
+ /// Get the http version.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.inner.version()
+ }
+
+ /// Get a mutable reference to the http version.
+ #[inline]
+ pub fn version_mut(&mut self) -> &mut Version {
+ self.inner.version_mut()
+ }
+
+ /// Get the body.
+ #[inline]
+ pub fn body(&self) -> Option<&Body> {
+ self.body.as_ref()
+ }
+
+ /// Get a mutable reference to the body.
+ #[inline]
+ pub fn body_mut(&mut self) -> &mut Option<Body> {
+ &mut self.body
+ }
+
+ /// Get the timeout.
+ #[inline]
+ pub fn timeout(&self) -> Option<&Duration> {
+ self.inner.timeout()
+ }
+
+ /// Get a mutable reference to the timeout.
+ #[inline]
+ pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
+ self.inner.timeout_mut()
+ }
+
+ /// Attempts to clone the `Request`.
+ ///
+ /// None is returned if a body is which can not be cloned. This can be because the body is a
+ /// stream.
+ pub fn try_clone(&self) -> Option<Request> {
+ let body = if let Some(ref body) = self.body.as_ref() {
+ if let Some(body) = body.try_clone() {
+ Some(body)
+ } else {
+ return None;
+ }
+ } else {
+ None
+ };
+ let mut req = Request::new(self.method().clone(), self.url().clone());
+ *req.headers_mut() = self.headers().clone();
+ *req.version_mut() = self.version().clone();
+ req.body = body;
+ Some(req)
+ }
+
+ pub(crate) fn into_async(self) -> (async_impl::Request, Option<body::Sender>) {
+ use crate::header::CONTENT_LENGTH;
+
+ let mut req_async = self.inner;
+ let body = self.body.and_then(|body| {
+ let (tx, body, len) = body.into_async();
+ if let Some(len) = len {
+ req_async.headers_mut().insert(CONTENT_LENGTH, len.into());
+ }
+ *req_async.body_mut() = Some(body);
+ tx
+ });
+ (req_async, body)
+ }
+}
+
+impl RequestBuilder {
+ pub(crate) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
+ let mut builder = RequestBuilder { client, request };
+
+ let auth = builder
+ .request
+ .as_mut()
+ .ok()
+ .and_then(|req| async_impl::request::extract_authority(req.url_mut()));
+
+ if let Some((username, password)) = auth {
+ builder.basic_auth(username, password)
+ } else {
+ builder
+ }
+ }
+
+ /// Add a `Header` to this Request.
+ ///
+ /// ```rust
+ /// use reqwest::header::USER_AGENT;
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.get("https://www.rust-lang.org")
+ /// .header(USER_AGENT, "foo")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
+ where
+ HeaderName: TryFrom<K>,
+ HeaderValue: TryFrom<V>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+ {
+ self.header_sensitive(key, value, false)
+ }
+
+ /// Add a `Header` to this Request with ability to define if header_value is sensitive.
+ fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
+ where
+ HeaderName: TryFrom<K>,
+ HeaderValue: TryFrom<V>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+ {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match <HeaderName as TryFrom<K>>::try_from(key) {
+ Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
+ Ok(mut value) => {
+ value.set_sensitive(sensitive);
+ req.headers_mut().append(key, value);
+ }
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ },
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ };
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Add a set of Headers to the existing ones on this Request.
+ ///
+ /// The headers will be merged in to any already set.
+ ///
+ /// ```rust
+ /// use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE};
+ /// # use std::fs;
+ ///
+ /// fn construct_headers() -> HeaderMap {
+ /// let mut headers = HeaderMap::new();
+ /// headers.insert(USER_AGENT, HeaderValue::from_static("reqwest"));
+ /// headers.insert(CONTENT_TYPE, HeaderValue::from_static("image/png"));
+ /// headers
+ /// }
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = fs::File::open("much_beauty.png")?;
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .headers(construct_headers())
+ /// .body(file)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ crate::util::replace_headers(req.headers_mut(), headers);
+ }
+ self
+ }
+
+ /// Enable HTTP basic authentication.
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let resp = client.delete("http://httpbin.org/delete")
+ /// .basic_auth("admin", Some("good password"))
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
+ where
+ U: fmt::Display,
+ P: fmt::Display,
+ {
+ let header_value = crate::util::basic_auth(username, password);
+ self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
+ }
+
+ /// Enable HTTP bearer authentication.
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let resp = client.delete("http://httpbin.org/delete")
+ /// .bearer_auth("token")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
+ where
+ T: fmt::Display,
+ {
+ let header_value = format!("Bearer {}", token);
+ self.header_sensitive(crate::header::AUTHORIZATION, &*header_value, true)
+ }
+
+ /// Set the request body.
+ ///
+ /// # Examples
+ ///
+ /// Using a string:
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .body("from a &str!")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Using a `File`:
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = std::fs::File::open("from_a_file.txt")?;
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .body(file)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Using arbitrary bytes:
+ ///
+ /// ```rust
+ /// # use std::fs;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// // from bytes!
+ /// let bytes: Vec<u8> = vec![1, 10, 100];
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .body(bytes)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.body_mut() = Some(body.into());
+ }
+ self
+ }
+
+ /// Enables a request timeout.
+ ///
+ /// The timeout is applied from when the request starts connecting until the
+ /// response body has finished. It affects only this request and overrides
+ /// the timeout configured using `ClientBuilder::timeout()`.
+ pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.timeout_mut() = Some(timeout);
+ }
+ self
+ }
+
+ /// Modify the query string of the URL.
+ ///
+ /// Modifies the URL of this request, adding the parameters provided.
+ /// This method appends and does not overwrite. This means that it can
+ /// be called multiple times and that existing query parameters are not
+ /// overwritten if the same key is used. The key will simply show up
+ /// twice in the query string.
+ /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.get("http://httpbin.org")
+ /// .query(&[("lang", "rust")])
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Note
+ /// This method does not support serializing a single key-value
+ /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
+ /// as `.query(&[("key", "val")])`. It's also possible to serialize structs
+ /// and maps into a key-value pair.
+ ///
+ /// # Errors
+ /// This method will fail if the object you provide cannot be serialized
+ /// into a query string.
+ pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ let url = req.url_mut();
+ let mut pairs = url.query_pairs_mut();
+ let serializer = serde_urlencoded::Serializer::new(&mut pairs);
+
+ if let Err(err) = query.serialize(serializer) {
+ error = Some(crate::error::builder(err));
+ }
+ }
+ if let Ok(ref mut req) = self.request {
+ if let Some("") = req.url().query() {
+ req.url_mut().set_query(None);
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Set HTTP version
+ pub fn version(mut self, version: Version) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.version_mut() = version;
+ }
+ self
+ }
+
+ /// Send a form body.
+ ///
+ /// Sets the body to the url encoded serialization of the passed value,
+ /// and also sets the `Content-Type: application/x-www-form-urlencoded`
+ /// header.
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ /// # use std::collections::HashMap;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let mut params = HashMap::new();
+ /// params.insert("lang", "rust");
+ ///
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org")
+ /// .form(&params)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// This method fails if the passed value cannot be serialized into
+ /// url encoded format
+ pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_urlencoded::to_string(form) {
+ Ok(body) => {
+ req.headers_mut().insert(
+ CONTENT_TYPE,
+ HeaderValue::from_static("application/x-www-form-urlencoded"),
+ );
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Send a JSON body.
+ ///
+ /// Sets the body to the JSON serialization of the passed value, and
+ /// also sets the `Content-Type: application/json` header.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `json` feature enabled.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ /// # use std::collections::HashMap;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let mut map = HashMap::new();
+ /// map.insert("lang", "rust");
+ ///
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org")
+ /// .json(&map)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Serialization can fail if `T`'s implementation of `Serialize` decides to
+ /// fail, or if `T` contains a map with non-string keys.
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_json::to_vec(json) {
+ Ok(body) => {
+ if !req.headers().contains_key(CONTENT_TYPE) {
+ req.headers_mut()
+ .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ }
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Sends a multipart/form-data body.
+ ///
+ /// ```
+ /// # use reqwest::Error;
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let form = reqwest::blocking::multipart::Form::new()
+ /// .text("key3", "value3")
+ /// .file("file", "/path/to/field")?;
+ ///
+ /// let response = client.post("your url")
+ /// .multipart(form)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// See [`multipart`](multipart/) for more examples.
+ #[cfg(feature = "multipart")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
+ pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
+ let mut builder = self.header(
+ CONTENT_TYPE,
+ format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
+ );
+ if let Ok(ref mut req) = builder.request {
+ *req.body_mut() = Some(match multipart.compute_length() {
+ Some(length) => Body::sized(multipart.reader(), length),
+ None => Body::new(multipart.reader()),
+ })
+ }
+ builder
+ }
+
+ /// Build a `Request`, which can be inspected, modified and executed with
+ /// `Client::execute()`.
+ pub fn build(self) -> crate::Result<Request> {
+ self.request
+ }
+
+ /// Constructs the Request and sends it the target URL, returning a Response.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// redirect loop was detected or redirect limit was exhausted.
+ pub fn send(self) -> crate::Result<super::Response> {
+ self.client.execute(self.request?)
+ }
+
+ /// Attempts to clone the `RequestBuilder`.
+ ///
+ /// None is returned if a body is which can not be cloned. This can be because the body is a
+ /// stream.
+ ///
+ /// # Examples
+ ///
+ /// With a static body
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let builder = client.post("http://httpbin.org/post")
+ /// .body("from a &str!");
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_some());
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Without a body
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let builder = client.get("http://httpbin.org/get");
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_some());
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// With a non-clonable body
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let builder = client.get("http://httpbin.org/get")
+ /// .body(reqwest::blocking::Body::new(std::io::empty()));
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_none());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn try_clone(&self) -> Option<RequestBuilder> {
+ self.request
+ .as_ref()
+ .ok()
+ .and_then(|req| req.try_clone())
+ .map(|req| RequestBuilder {
+ client: self.client.clone(),
+ request: Ok(req),
+ })
+ }
+}
+
+impl<T> TryFrom<HttpRequest<T>> for Request
+where
+ T: Into<Body>,
+{
+ type Error = crate::Error;
+
+ fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
+ let (parts, body) = req.into_parts();
+ let Parts {
+ method,
+ uri,
+ headers,
+ ..
+ } = parts;
+ let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
+ let mut inner = async_impl::Request::new(method, url);
+ crate::util::replace_headers(inner.headers_mut(), headers);
+ Ok(Request {
+ body: Some(body.into()),
+ inner,
+ })
+ }
+}
+
+impl fmt::Debug for Request {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
+ }
+}
+
+fn fmt_request_fields<'a, 'b>(
+ f: &'a mut fmt::DebugStruct<'a, 'b>,
+ req: &Request,
+) -> &'a mut fmt::DebugStruct<'a, 'b> {
+ f.field("method", req.method())
+ .field("url", req.url())
+ .field("headers", req.headers())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::{body, Client};
+ use super::{HttpRequest, Request, Version};
+ use crate::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST};
+ use crate::Method;
+ use serde::Serialize;
+ #[cfg(feature = "json")]
+ use serde_json;
+ use serde_urlencoded;
+ use std::collections::{BTreeMap, HashMap};
+ use std::convert::TryFrom;
+
+ #[test]
+ fn basic_get_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.get(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::GET);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_head_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.head(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::HEAD);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_post_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::POST);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_put_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.put(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::PUT);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_patch_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.patch(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::PATCH);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_delete_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.delete(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::DELETE);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn add_header() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let header = HeaderValue::from_static("google.com");
+
+ // Add a copy of the header to the request builder
+ let r = r.header(HOST, header.clone()).build().unwrap();
+
+ // then check it was actually added
+ assert_eq!(r.headers().get(HOST), Some(&header));
+ }
+
+ #[test]
+ fn add_headers() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let header = HeaderValue::from_static("google.com");
+
+ let mut headers = HeaderMap::new();
+ headers.insert(HOST, header);
+
+ // Add a copy of the headers to the request builder
+ let r = r.headers(headers.clone()).build().unwrap();
+
+ // then make sure they were added correctly
+ assert_eq!(r.headers(), &headers);
+ }
+
+ #[test]
+ fn add_headers_multi() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let header_json = HeaderValue::from_static("application/json");
+ let header_xml = HeaderValue::from_static("application/xml");
+
+ let mut headers = HeaderMap::new();
+ headers.append(ACCEPT, header_json);
+ headers.append(ACCEPT, header_xml);
+
+ // Add a copy of the headers to the request builder
+ let r = r.headers(headers.clone()).build().unwrap();
+
+ // then make sure they were added correctly
+ assert_eq!(r.headers(), &headers);
+ let mut all_values = r.headers().get_all(ACCEPT).iter();
+ assert_eq!(all_values.next().unwrap(), &"application/json");
+ assert_eq!(all_values.next().unwrap(), &"application/xml");
+ assert_eq!(all_values.next(), None);
+ }
+
+ #[test]
+ fn add_body() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let body = "Some interesting content";
+
+ let mut r = r.body(body).build().unwrap();
+
+ let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
+
+ assert_eq!(buf, body);
+ }
+
+ #[test]
+ fn add_query_append() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ r = r.query(&[("foo", "bar")]);
+ r = r.query(&[("qux", 3)]);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
+ }
+
+ #[test]
+ fn add_query_append_same() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ r = r.query(&[("foo", "a"), ("foo", "b")]);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=a&foo=b"));
+ }
+
+ #[test]
+ fn add_query_struct() {
+ #[derive(Serialize)]
+ struct Params {
+ foo: String,
+ qux: i32,
+ }
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ let params = Params {
+ foo: "bar".into(),
+ qux: 3,
+ };
+
+ r = r.query(&params);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
+ }
+
+ #[test]
+ fn add_query_map() {
+ let mut params = BTreeMap::new();
+ params.insert("foo", "bar");
+ params.insert("qux", "three");
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ r = r.query(&params);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=three"));
+ }
+
+ #[test]
+ fn add_form() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let mut form_data = HashMap::new();
+ form_data.insert("foo", "bar");
+
+ let mut r = r.form(&form_data).build().unwrap();
+
+ // Make sure the content type was set
+ assert_eq!(
+ r.headers().get(CONTENT_TYPE).unwrap(),
+ &"application/x-www-form-urlencoded"
+ );
+
+ let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
+
+ let body_should_be = serde_urlencoded::to_string(&form_data).unwrap();
+ assert_eq!(buf, body_should_be);
+ }
+
+ #[test]
+ #[cfg(feature = "json")]
+ fn add_json() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let mut json_data = HashMap::new();
+ json_data.insert("foo", "bar");
+
+ let mut r = r.json(&json_data).build().unwrap();
+
+ // Make sure the content type was set
+ assert_eq!(r.headers().get(CONTENT_TYPE).unwrap(), &"application/json");
+
+ let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
+
+ let body_should_be = serde_json::to_string(&json_data).unwrap();
+ assert_eq!(buf, body_should_be);
+ }
+
+ #[test]
+ #[cfg(feature = "json")]
+ fn add_json_fail() {
+ use serde::ser::Error as _;
+ use serde::{Serialize, Serializer};
+ use std::error::Error as _;
+ struct MyStruct;
+ impl Serialize for MyStruct {
+ fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ Err(S::Error::custom("nope"))
+ }
+ }
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+ let json_data = MyStruct;
+ let err = r.json(&json_data).build().unwrap_err();
+ assert!(err.is_builder()); // well, duh ;)
+ assert!(err.source().unwrap().is::<serde_json::Error>());
+ }
+
+ #[test]
+ fn test_replace_headers() {
+ use http::HeaderMap;
+
+ let mut headers = HeaderMap::new();
+ headers.insert("foo", "bar".parse().unwrap());
+ headers.append("foo", "baz".parse().unwrap());
+
+ let client = Client::new();
+ let req = client
+ .get("https://hyper.rs")
+ .header("im-a", "keeper")
+ .header("foo", "pop me")
+ .headers(headers)
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.headers()["im-a"], "keeper");
+
+ let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
+ assert_eq!(foo.len(), 2);
+ assert_eq!(foo[0], "bar");
+ assert_eq!(foo[1], "baz");
+ }
+
+ #[test]
+ fn normalize_empty_query() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let empty_query: &[(&str, &str)] = &[];
+
+ let req = client
+ .get(some_url)
+ .query(empty_query)
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().query(), None);
+ assert_eq!(req.url().as_str(), "https://google.com/");
+ }
+
+ #[test]
+ fn convert_url_authority_into_basic_auth() {
+ let client = Client::new();
+ let some_url = "https://Aladdin:open sesame@localhost/";
+
+ let req = client.get(some_url).build().expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(
+ req.headers()["authorization"],
+ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ );
+ }
+
+ #[test]
+ fn convert_from_http_request() {
+ let http_request = HttpRequest::builder()
+ .method("GET")
+ .uri("http://localhost/")
+ .header("User-Agent", "my-awesome-agent/1.0")
+ .body("test test test")
+ .unwrap();
+ let req: Request = Request::try_from(http_request).unwrap();
+ assert_eq!(req.body().is_none(), false);
+ let test_data = b"test test test";
+ assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
+ let headers = req.headers();
+ assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
+ assert_eq!(req.method(), Method::GET);
+ assert_eq!(req.url().as_str(), "http://localhost/");
+ }
+
+ #[test]
+ fn set_http_request_version() {
+ let http_request = HttpRequest::builder()
+ .method("GET")
+ .uri("http://localhost/")
+ .header("User-Agent", "my-awesome-agent/1.0")
+ .version(Version::HTTP_11)
+ .body("test test test")
+ .unwrap();
+ let req: Request = Request::try_from(http_request).unwrap();
+ assert_eq!(req.body().is_none(), false);
+ let test_data = b"test test test";
+ assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
+ let headers = req.headers();
+ assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
+ assert_eq!(req.method(), Method::GET);
+ assert_eq!(req.url().as_str(), "http://localhost/");
+ assert_eq!(req.version(), Version::HTTP_11);
+ }
+
+ #[test]
+ fn test_basic_auth_sensitive_header() {
+ let client = Client::new();
+ let some_url = "https://localhost/";
+
+ let req = client
+ .get(some_url)
+ .basic_auth("Aladdin", Some("open sesame"))
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(
+ req.headers()["authorization"],
+ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ );
+ assert_eq!(req.headers()["authorization"].is_sensitive(), true);
+ }
+
+ #[test]
+ fn test_bearer_auth_sensitive_header() {
+ let client = Client::new();
+ let some_url = "https://localhost/";
+
+ let req = client
+ .get(some_url)
+ .bearer_auth("Hold my bear")
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
+ assert_eq!(req.headers()["authorization"].is_sensitive(), true);
+ }
+}
diff --git a/vendor/reqwest/src/blocking/response.rs b/vendor/reqwest/src/blocking/response.rs
new file mode 100644
index 000000000..8d0e9b9cf
--- /dev/null
+++ b/vendor/reqwest/src/blocking/response.rs
@@ -0,0 +1,425 @@
+use std::fmt;
+use std::io::{self, Read};
+use std::mem;
+use std::net::SocketAddr;
+use std::pin::Pin;
+use std::time::Duration;
+
+use bytes::Bytes;
+use http;
+use hyper::header::HeaderMap;
+#[cfg(feature = "json")]
+use serde::de::DeserializeOwned;
+
+use super::client::KeepCoreThreadAlive;
+use super::wait;
+#[cfg(feature = "cookies")]
+use crate::cookie;
+use crate::{async_impl, StatusCode, Url, Version};
+
+/// A Response to a submitted `Request`.
+pub struct Response {
+ inner: async_impl::Response,
+ body: Option<Pin<Box<dyn futures_util::io::AsyncRead + Send + Sync>>>,
+ timeout: Option<Duration>,
+ _thread_handle: KeepCoreThreadAlive,
+}
+
+impl fmt::Debug for Response {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner, f)
+ }
+}
+
+impl Response {
+ pub(crate) fn new(
+ res: async_impl::Response,
+ timeout: Option<Duration>,
+ thread: KeepCoreThreadAlive,
+ ) -> Response {
+ Response {
+ inner: res,
+ body: None,
+ timeout,
+ _thread_handle: thread,
+ }
+ }
+
+ /// Get the `StatusCode` of this `Response`.
+ ///
+ /// # Examples
+ ///
+ /// Checking for general status class:
+ ///
+ /// ```rust
+ /// # #[cfg(feature = "json")]
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let resp = reqwest::blocking::get("http://httpbin.org/get")?;
+ /// if resp.status().is_success() {
+ /// println!("success!");
+ /// } else if resp.status().is_server_error() {
+ /// println!("server error!");
+ /// } else {
+ /// println!("Something else happened. Status: {:?}", resp.status());
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Checking for specific status codes:
+ ///
+ /// ```rust
+ /// use reqwest::blocking::Client;
+ /// use reqwest::StatusCode;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = Client::new();
+ ///
+ /// let resp = client.post("http://httpbin.org/post")
+ /// .body("possibly too large")
+ /// .send()?;
+ ///
+ /// match resp.status() {
+ /// StatusCode::OK => println!("success!"),
+ /// StatusCode::PAYLOAD_TOO_LARGE => {
+ /// println!("Request payload is too large!");
+ /// }
+ /// s => println!("Received response status: {:?}", s),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ pub fn status(&self) -> StatusCode {
+ self.inner.status()
+ }
+
+ /// Get the `Headers` of this `Response`.
+ ///
+ /// # Example
+ ///
+ /// Saving an etag when caching a file:
+ ///
+ /// ```
+ /// use reqwest::blocking::Client;
+ /// use reqwest::header::ETAG;
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = Client::new();
+ ///
+ /// let mut resp = client.get("http://httpbin.org/cache").send()?;
+ /// if resp.status().is_success() {
+ /// if let Some(etag) = resp.headers().get(ETAG) {
+ /// std::fs::write("etag", etag.as_bytes());
+ /// }
+ /// let mut file = std::fs::File::create("file")?;
+ /// resp.copy_to(&mut file)?;
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ self.inner.headers()
+ }
+
+ /// Get a mutable reference to the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ self.inner.headers_mut()
+ }
+
+ /// Retrieve the cookies contained in the response.
+ ///
+ /// Note that invalid 'Set-Cookie' headers will be ignored.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
+ cookie::extract_response_cookies(self.headers()).filter_map(Result::ok)
+ }
+
+ /// Get the HTTP `Version` of this `Response`.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.inner.version()
+ }
+
+ /// Get the final `Url` of this `Response`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
+ /// assert_eq!(resp.url().as_str(), "http://httpbin.org/get");
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ pub fn url(&self) -> &Url {
+ self.inner.url()
+ }
+
+ /// Get the remote address used to get this `Response`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
+ /// println!("httpbin.org address: {:?}", resp.remote_addr());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn remote_addr(&self) -> Option<SocketAddr> {
+ self.inner.remote_addr()
+ }
+
+ /// Returns a reference to the associated extensions.
+ pub fn extensions(&self) -> &http::Extensions {
+ self.inner.extensions()
+ }
+
+ /// Returns a mutable reference to the associated extensions.
+ pub fn extensions_mut(&mut self) -> &mut http::Extensions {
+ self.inner.extensions_mut()
+ }
+
+ /// Get the content-length of the response, if it is known.
+ ///
+ /// Reasons it may not be known:
+ ///
+ /// - The server didn't send a `content-length` header.
+ /// - The response is gzipped and automatically decoded (thus changing
+ /// the actual decoded length).
+ pub fn content_length(&self) -> Option<u64> {
+ self.inner.content_length()
+ }
+
+ /// Try and deserialize the response body as JSON using `serde`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `json` feature enabled.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # extern crate reqwest;
+ /// # extern crate serde;
+ /// #
+ /// # use reqwest::Error;
+ /// # use serde::Deserialize;
+ /// #
+ /// // This `derive` requires the `serde` dependency.
+ /// #[derive(Deserialize)]
+ /// struct Ip {
+ /// origin: String,
+ /// }
+ ///
+ /// # fn run() -> Result<(), Error> {
+ /// let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() { }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the response body is not in JSON format
+ /// or it cannot be properly deserialized to target type `T`. For more
+ /// details please see [`serde_json::from_reader`].
+ ///
+ /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
+ wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e),
+ wait::Waited::Inner(e) => e,
+ })
+ }
+
+ /// Get the full response body as `Bytes`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
+ /// let bytes = reqwest::blocking::get("http://httpbin.org/ip")?.bytes()?;
+ ///
+ /// println!("bytes: {:?}", bytes);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn bytes(self) -> crate::Result<Bytes> {
+ wait::timeout(self.inner.bytes(), self.timeout).map_err(|e| match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e),
+ wait::Waited::Inner(e) => e,
+ })
+ }
+
+ /// Get the response text.
+ ///
+ /// This method decodes the response body with BOM sniffing
+ /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
+ /// Encoding is determined from the `charset` parameter of `Content-Type` header,
+ /// and defaults to `utf-8` if not presented.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?.text()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn text(self) -> crate::Result<String> {
+ self.text_with_charset("utf-8")
+ }
+
+ /// Get the response text given a specific encoding.
+ ///
+ /// This method decodes the response body with BOM sniffing
+ /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
+ /// You can provide a default encoding for decoding the raw message, while the
+ /// `charset` parameter of `Content-Type` header is still prioritized. For more information
+ /// about the possible encoding name, please go to [`encoding_rs`] docs.
+ ///
+ /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?
+ /// .text_with_charset("utf-8")?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
+ wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| {
+ match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e),
+ wait::Waited::Inner(e) => e,
+ }
+ })
+ }
+
+ /// Copy the response body into a writer.
+ ///
+ /// This function internally uses [`std::io::copy`] and hence will continuously read data from
+ /// the body and then write it into writer in a streaming fashion until EOF is met.
+ ///
+ /// On success, the total number of bytes that were copied to `writer` is returned.
+ ///
+ /// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let mut resp = reqwest::blocking::get("http://httpbin.org/range/5")?;
+ /// let mut buf: Vec<u8> = vec![];
+ /// resp.copy_to(&mut buf)?;
+ /// assert_eq!(b"abcde", buf.as_slice());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64>
+ where
+ W: io::Write,
+ {
+ io::copy(self, w).map_err(crate::error::decode_io)
+ }
+
+ /// Turn a response into an error if the server returned an error.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?
+ /// .error_for_status();
+ /// if let Err(err) = res {
+ /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
+ /// }
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn error_for_status(self) -> crate::Result<Self> {
+ let Response {
+ body,
+ inner,
+ timeout,
+ _thread_handle,
+ } = self;
+ inner.error_for_status().map(move |inner| Response {
+ inner,
+ body,
+ timeout,
+ _thread_handle,
+ })
+ }
+
+ /// Turn a reference to a response into an error if the server returned an error.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?;
+ /// let res = res.error_for_status_ref();
+ /// if let Err(err) = res {
+ /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
+ /// }
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
+ self.inner.error_for_status_ref().and_then(|_| Ok(self))
+ }
+
+ // private
+
+ fn body_mut(&mut self) -> Pin<&mut dyn futures_util::io::AsyncRead> {
+ use futures_util::TryStreamExt;
+ if self.body.is_none() {
+ let body = mem::replace(self.inner.body_mut(), async_impl::Decoder::empty());
+
+ let body = body.map_err(crate::error::into_io).into_async_read();
+
+ self.body = Some(Box::pin(body));
+ }
+ self.body.as_mut().expect("body was init").as_mut()
+ }
+}
+
+impl Read for Response {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ use futures_util::io::AsyncReadExt;
+
+ let timeout = self.timeout;
+ wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e).into_io(),
+ wait::Waited::Inner(e) => e,
+ })
+ }
+}
+
+impl<T: Into<async_impl::body::Body>> From<http::Response<T>> for Response {
+ fn from(r: http::Response<T>) -> Response {
+ let response = async_impl::Response::from(r);
+ Response::new(response, None, KeepCoreThreadAlive::empty())
+ }
+}
diff --git a/vendor/reqwest/src/blocking/wait.rs b/vendor/reqwest/src/blocking/wait.rs
new file mode 100644
index 000000000..3c903f8bf
--- /dev/null
+++ b/vendor/reqwest/src/blocking/wait.rs
@@ -0,0 +1,78 @@
+use std::future::Future;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+use std::thread::{self, Thread};
+use std::time::Duration;
+
+use tokio::time::Instant;
+
+pub(crate) fn timeout<F, I, E>(fut: F, timeout: Option<Duration>) -> Result<I, Waited<E>>
+where
+ F: Future<Output = Result<I, E>>,
+{
+ enter();
+
+ let deadline = timeout.map(|d| {
+ log::trace!("wait at most {:?}", d);
+ Instant::now() + d
+ });
+
+ let thread = ThreadWaker(thread::current());
+ // Arc shouldn't be necessary, since `Thread` is reference counted internally,
+ // but let's just stay safe for now.
+ let waker = futures_util::task::waker(Arc::new(thread));
+ let mut cx = Context::from_waker(&waker);
+
+ futures_util::pin_mut!(fut);
+
+ loop {
+ match fut.as_mut().poll(&mut cx) {
+ Poll::Ready(Ok(val)) => return Ok(val),
+ Poll::Ready(Err(err)) => return Err(Waited::Inner(err)),
+ Poll::Pending => (), // fallthrough
+ }
+
+ if let Some(deadline) = deadline {
+ let now = Instant::now();
+ if now >= deadline {
+ log::trace!("wait timeout exceeded");
+ return Err(Waited::TimedOut(crate::error::TimedOut));
+ }
+
+ log::trace!(
+ "({:?}) park timeout {:?}",
+ thread::current().id(),
+ deadline - now
+ );
+ thread::park_timeout(deadline - now);
+ } else {
+ log::trace!("({:?}) park without timeout", thread::current().id());
+ thread::park();
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) enum Waited<E> {
+ TimedOut(crate::error::TimedOut),
+ Inner(E),
+}
+
+struct ThreadWaker(Thread);
+
+impl futures_util::task::ArcWake for ThreadWaker {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ arc_self.0.unpark();
+ }
+}
+
+fn enter() {
+ // Check we aren't already in a runtime
+ #[cfg(debug_assertions)]
+ {
+ let _enter = tokio::runtime::Builder::new_current_thread()
+ .build()
+ .expect("build shell runtime")
+ .enter();
+ }
+}