RPM build fix (reverted CI changes which will need to be un-reverted or made conditional) and vendor Rust dependencies to make builds much faster in any CI system.
This commit is contained in:
370
zeroidc/vendor/reqwest/src/async_impl/body.rs
vendored
Normal file
370
zeroidc/vendor/reqwest/src/async_impl/body.rs
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use http_body::Body as HttpBody;
|
||||
use pin_project_lite::pin_project;
|
||||
#[cfg(feature = "stream")]
|
||||
use tokio::fs::File;
|
||||
use tokio::time::Sleep;
|
||||
#[cfg(feature = "stream")]
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
/// An asynchronous request body.
|
||||
pub struct Body {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
// The `Stream` trait isn't stable, so the impl isn't public.
|
||||
pub(crate) struct ImplStream(Body);
|
||||
|
||||
enum Inner {
|
||||
Reusable(Bytes),
|
||||
Streaming {
|
||||
body: Pin<
|
||||
Box<
|
||||
dyn HttpBody<Data = Bytes, Error = Box<dyn std::error::Error + Send + Sync>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
>,
|
||||
timeout: Option<Pin<Box<Sleep>>>,
|
||||
},
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
struct WrapStream<S> {
|
||||
#[pin]
|
||||
inner: S,
|
||||
}
|
||||
}
|
||||
|
||||
struct WrapHyper(hyper::Body);
|
||||
|
||||
impl Body {
|
||||
/// Returns a reference to the internal data of the `Body`.
|
||||
///
|
||||
/// `None` is returned, if the underlying data is a stream.
|
||||
pub fn as_bytes(&self) -> Option<&[u8]> {
|
||||
match &self.inner {
|
||||
Inner::Reusable(bytes) => Some(bytes.as_ref()),
|
||||
Inner::Streaming { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a futures `Stream` in a box inside `Body`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use reqwest::Body;
|
||||
/// # use futures_util;
|
||||
/// # fn main() {
|
||||
/// let chunks: Vec<Result<_, ::std::io::Error>> = vec![
|
||||
/// Ok("hello"),
|
||||
/// Ok(" "),
|
||||
/// Ok("world"),
|
||||
/// ];
|
||||
///
|
||||
/// let stream = futures_util::stream::iter(chunks);
|
||||
///
|
||||
/// let body = Body::wrap_stream(stream);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This requires the `stream` feature to be enabled.
|
||||
#[cfg(feature = "stream")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
|
||||
pub fn wrap_stream<S>(stream: S) -> Body
|
||||
where
|
||||
S: futures_core::stream::TryStream + Send + Sync + 'static,
|
||||
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
Bytes: From<S::Ok>,
|
||||
{
|
||||
Body::stream(stream)
|
||||
}
|
||||
|
||||
pub(crate) fn stream<S>(stream: S) -> Body
|
||||
where
|
||||
S: futures_core::stream::TryStream + Send + Sync + 'static,
|
||||
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
Bytes: From<S::Ok>,
|
||||
{
|
||||
use futures_util::TryStreamExt;
|
||||
|
||||
let body = Box::pin(WrapStream {
|
||||
inner: stream.map_ok(Bytes::from).map_err(Into::into),
|
||||
});
|
||||
Body {
|
||||
inner: Inner::Streaming {
|
||||
body,
|
||||
timeout: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn response(body: hyper::Body, timeout: Option<Pin<Box<Sleep>>>) -> Body {
|
||||
Body {
|
||||
inner: Inner::Streaming {
|
||||
body: Box::pin(WrapHyper(body)),
|
||||
timeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
pub(crate) fn wrap(body: hyper::Body) -> Body {
|
||||
Body {
|
||||
inner: Inner::Streaming {
|
||||
body: Box::pin(WrapHyper(body)),
|
||||
timeout: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty() -> Body {
|
||||
Body::reusable(Bytes::new())
|
||||
}
|
||||
|
||||
pub(crate) fn reusable(chunk: Bytes) -> Body {
|
||||
Body {
|
||||
inner: Inner::Reusable(chunk),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_reuse(self) -> (Option<Bytes>, Self) {
|
||||
let reuse = match self.inner {
|
||||
Inner::Reusable(ref chunk) => Some(chunk.clone()),
|
||||
Inner::Streaming { .. } => None,
|
||||
};
|
||||
|
||||
(reuse, self)
|
||||
}
|
||||
|
||||
pub(crate) fn try_clone(&self) -> Option<Body> {
|
||||
match self.inner {
|
||||
Inner::Reusable(ref chunk) => Some(Body::reusable(chunk.clone())),
|
||||
Inner::Streaming { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_stream(self) -> ImplStream {
|
||||
ImplStream(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "multipart")]
|
||||
pub(crate) fn content_length(&self) -> Option<u64> {
|
||||
match self.inner {
|
||||
Inner::Reusable(ref bytes) => Some(bytes.len() as u64),
|
||||
Inner::Streaming { ref body, .. } => body.size_hint().exact(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<hyper::Body> for Body {
|
||||
#[inline]
|
||||
fn from(body: hyper::Body) -> Body {
|
||||
Self {
|
||||
inner: Inner::Streaming {
|
||||
body: Box::pin(WrapHyper(body)),
|
||||
timeout: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Body {
|
||||
#[inline]
|
||||
fn from(bytes: Bytes) -> Body {
|
||||
Body::reusable(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Body {
|
||||
#[inline]
|
||||
fn from(vec: Vec<u8>) -> Body {
|
||||
Body::reusable(vec.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static [u8]> for Body {
|
||||
#[inline]
|
||||
fn from(s: &'static [u8]) -> Body {
|
||||
Body::reusable(Bytes::from_static(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Body {
|
||||
#[inline]
|
||||
fn from(s: String) -> Body {
|
||||
Body::reusable(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Body {
|
||||
#[inline]
|
||||
fn from(s: &'static str) -> Body {
|
||||
s.as_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
|
||||
impl From<File> for Body {
|
||||
#[inline]
|
||||
fn from(file: File) -> Body {
|
||||
Body::wrap_stream(ReaderStream::new(file))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Body {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Body").finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl ImplStream =====
|
||||
|
||||
impl HttpBody for ImplStream {
|
||||
type Data = Bytes;
|
||||
type Error = crate::Error;
|
||||
|
||||
fn poll_data(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
let opt_try_chunk = match self.0.inner {
|
||||
Inner::Streaming {
|
||||
ref mut body,
|
||||
ref mut timeout,
|
||||
} => {
|
||||
if let Some(ref mut timeout) = timeout {
|
||||
if let Poll::Ready(()) = timeout.as_mut().poll(cx) {
|
||||
return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut))));
|
||||
}
|
||||
}
|
||||
futures_core::ready!(Pin::new(body).poll_data(cx))
|
||||
.map(|opt_chunk| opt_chunk.map(Into::into).map_err(crate::error::body))
|
||||
}
|
||||
Inner::Reusable(ref mut bytes) => {
|
||||
if bytes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Ok(std::mem::replace(bytes, Bytes::new())))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Poll::Ready(opt_try_chunk)
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context,
|
||||
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
|
||||
Poll::Ready(Ok(None))
|
||||
}
|
||||
|
||||
fn is_end_stream(&self) -> bool {
|
||||
match self.0.inner {
|
||||
Inner::Streaming { ref body, .. } => body.is_end_stream(),
|
||||
Inner::Reusable(ref bytes) => bytes.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> http_body::SizeHint {
|
||||
match self.0.inner {
|
||||
Inner::Streaming { ref body, .. } => body.size_hint(),
|
||||
Inner::Reusable(ref bytes) => {
|
||||
let mut hint = http_body::SizeHint::default();
|
||||
hint.set_exact(bytes.len() as u64);
|
||||
hint
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for ImplStream {
|
||||
type Item = Result<Bytes, crate::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
self.poll_data(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl WrapStream =====
|
||||
|
||||
impl<S, D, E> HttpBody for WrapStream<S>
|
||||
where
|
||||
S: Stream<Item = Result<D, E>>,
|
||||
D: Into<Bytes>,
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
type Data = Bytes;
|
||||
type Error = E;
|
||||
|
||||
fn poll_data(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
let item = futures_core::ready!(self.project().inner.poll_next(cx)?);
|
||||
|
||||
Poll::Ready(item.map(|val| Ok(val.into())))
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context,
|
||||
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
|
||||
Poll::Ready(Ok(None))
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl WrapHyper =====
|
||||
|
||||
impl HttpBody for WrapHyper {
|
||||
type Data = Bytes;
|
||||
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
fn poll_data(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
// safe pin projection
|
||||
Pin::new(&mut self.0)
|
||||
.poll_data(cx)
|
||||
.map(|opt| opt.map(|res| res.map_err(Into::into)))
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context,
|
||||
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
|
||||
Poll::Ready(Ok(None))
|
||||
}
|
||||
|
||||
fn is_end_stream(&self) -> bool {
|
||||
self.0.is_end_stream()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> http_body::SizeHint {
|
||||
HttpBody::size_hint(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Body;
|
||||
|
||||
#[test]
|
||||
fn test_as_bytes() {
|
||||
let test_data = b"Test body";
|
||||
let body = Body::from(&test_data[..]);
|
||||
assert_eq!(body.as_bytes(), Some(&test_data[..]));
|
||||
}
|
||||
}
|
||||
1974
zeroidc/vendor/reqwest/src/async_impl/client.rs
vendored
Normal file
1974
zeroidc/vendor/reqwest/src/async_impl/client.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
418
zeroidc/vendor/reqwest/src/async_impl/decoder.rs
vendored
Normal file
418
zeroidc/vendor/reqwest/src/async_impl/decoder.rs
vendored
Normal file
@@ -0,0 +1,418 @@
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[cfg(feature = "gzip")]
|
||||
use async_compression::tokio::bufread::GzipDecoder;
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
use async_compression::tokio::bufread::BrotliDecoder;
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
use async_compression::tokio::bufread::ZlibDecoder;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_core::Stream;
|
||||
use futures_util::stream::Peekable;
|
||||
use http::HeaderMap;
|
||||
use hyper::body::HttpBody;
|
||||
|
||||
#[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))]
|
||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||
#[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))]
|
||||
use tokio_util::io::StreamReader;
|
||||
|
||||
use super::super::Body;
|
||||
use crate::error;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) struct Accepts {
|
||||
#[cfg(feature = "gzip")]
|
||||
pub(super) gzip: bool,
|
||||
#[cfg(feature = "brotli")]
|
||||
pub(super) brotli: bool,
|
||||
#[cfg(feature = "deflate")]
|
||||
pub(super) deflate: bool,
|
||||
}
|
||||
|
||||
/// A response decompressor over a non-blocking stream of chunks.
|
||||
///
|
||||
/// The inner decoder may be constructed asynchronously.
|
||||
pub(crate) struct Decoder {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
enum Inner {
|
||||
/// A `PlainText` decoder just returns the response content as is.
|
||||
PlainText(super::body::ImplStream),
|
||||
|
||||
/// A `Gzip` decoder will uncompress the gzipped response content before returning it.
|
||||
#[cfg(feature = "gzip")]
|
||||
Gzip(FramedRead<GzipDecoder<StreamReader<Peekable<IoStream>, Bytes>>, BytesCodec>),
|
||||
|
||||
/// A `Brotli` decoder will uncompress the brotlied response content before returning it.
|
||||
#[cfg(feature = "brotli")]
|
||||
Brotli(FramedRead<BrotliDecoder<StreamReader<Peekable<IoStream>, Bytes>>, BytesCodec>),
|
||||
|
||||
/// A `Deflate` decoder will uncompress the deflated response content before returning it.
|
||||
#[cfg(feature = "deflate")]
|
||||
Deflate(FramedRead<ZlibDecoder<StreamReader<Peekable<IoStream>, Bytes>>, BytesCodec>),
|
||||
|
||||
/// A decoder that doesn't have a value yet.
|
||||
#[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
|
||||
Pending(Pending),
|
||||
}
|
||||
|
||||
/// A future attempt to poll the response body for EOF so we know whether to use gzip or not.
|
||||
struct Pending(Peekable<IoStream>, DecoderType);
|
||||
|
||||
struct IoStream(super::body::ImplStream);
|
||||
|
||||
enum DecoderType {
|
||||
#[cfg(feature = "gzip")]
|
||||
Gzip,
|
||||
#[cfg(feature = "brotli")]
|
||||
Brotli,
|
||||
#[cfg(feature = "deflate")]
|
||||
Deflate,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Decoder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Decoder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decoder {
|
||||
#[cfg(feature = "blocking")]
|
||||
pub(crate) fn empty() -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::PlainText(Body::empty().into_stream()),
|
||||
}
|
||||
}
|
||||
|
||||
/// A plain text decoder.
|
||||
///
|
||||
/// This decoder will emit the underlying chunks as-is.
|
||||
fn plain_text(body: Body) -> Decoder {
|
||||
Decoder {
|
||||
inner: Inner::PlainText(body.into_stream()),
|
||||
}
|
||||
}
|
||||
|
||||
/// A gzip decoder.
|
||||
///
|
||||
/// This decoder will buffer and decompress chunks that are gzipped.
|
||||
#[cfg(feature = "gzip")]
|
||||
fn gzip(body: Body) -> Decoder {
|
||||
use futures_util::StreamExt;
|
||||
|
||||
Decoder {
|
||||
inner: Inner::Pending(Pending(
|
||||
IoStream(body.into_stream()).peekable(),
|
||||
DecoderType::Gzip,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// A brotli decoder.
|
||||
///
|
||||
/// This decoder will buffer and decompress chunks that are brotlied.
|
||||
#[cfg(feature = "brotli")]
|
||||
fn brotli(body: Body) -> Decoder {
|
||||
use futures_util::StreamExt;
|
||||
|
||||
Decoder {
|
||||
inner: Inner::Pending(Pending(
|
||||
IoStream(body.into_stream()).peekable(),
|
||||
DecoderType::Brotli,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// A deflate decoder.
|
||||
///
|
||||
/// This decoder will buffer and decompress chunks that are deflated.
|
||||
#[cfg(feature = "deflate")]
|
||||
fn deflate(body: Body) -> Decoder {
|
||||
use futures_util::StreamExt;
|
||||
|
||||
Decoder {
|
||||
inner: Inner::Pending(Pending(
|
||||
IoStream(body.into_stream()).peekable(),
|
||||
DecoderType::Deflate,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
|
||||
fn detect_encoding(headers: &mut HeaderMap, encoding_str: &str) -> bool {
|
||||
use http::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
use log::warn;
|
||||
|
||||
let mut is_content_encoded = {
|
||||
headers
|
||||
.get_all(CONTENT_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == encoding_str)
|
||||
|| headers
|
||||
.get_all(TRANSFER_ENCODING)
|
||||
.iter()
|
||||
.any(|enc| enc == encoding_str)
|
||||
};
|
||||
if is_content_encoded {
|
||||
if let Some(content_length) = headers.get(CONTENT_LENGTH) {
|
||||
if content_length == "0" {
|
||||
warn!("{} response with content-length of 0", encoding_str);
|
||||
is_content_encoded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_content_encoded {
|
||||
headers.remove(CONTENT_ENCODING);
|
||||
headers.remove(CONTENT_LENGTH);
|
||||
}
|
||||
is_content_encoded
|
||||
}
|
||||
|
||||
/// Constructs a Decoder from a hyper request.
|
||||
///
|
||||
/// A decoder is just a wrapper around the hyper request that knows
|
||||
/// how to decode the content body of the request.
|
||||
///
|
||||
/// Uses the correct variant by inspecting the Content-Encoding header.
|
||||
pub(super) fn detect(_headers: &mut HeaderMap, body: Body, _accepts: Accepts) -> Decoder {
|
||||
#[cfg(feature = "gzip")]
|
||||
{
|
||||
if _accepts.gzip && Decoder::detect_encoding(_headers, "gzip") {
|
||||
return Decoder::gzip(body);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "brotli")]
|
||||
{
|
||||
if _accepts.brotli && Decoder::detect_encoding(_headers, "br") {
|
||||
return Decoder::brotli(body);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
{
|
||||
if _accepts.deflate && Decoder::detect_encoding(_headers, "deflate") {
|
||||
return Decoder::deflate(body);
|
||||
}
|
||||
}
|
||||
|
||||
Decoder::plain_text(body)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Decoder {
|
||||
type Item = Result<Bytes, error::Error>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
// Do a read or poll for a pending decoder value.
|
||||
match self.inner {
|
||||
#[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
|
||||
Inner::Pending(ref mut future) => match Pin::new(future).poll(cx) {
|
||||
Poll::Ready(Ok(inner)) => {
|
||||
self.inner = inner;
|
||||
return self.poll_next(cx);
|
||||
}
|
||||
Poll::Ready(Err(e)) => {
|
||||
return Poll::Ready(Some(Err(crate::error::decode_io(e))));
|
||||
}
|
||||
Poll::Pending => return Poll::Pending,
|
||||
},
|
||||
Inner::PlainText(ref mut body) => Pin::new(body).poll_next(cx),
|
||||
#[cfg(feature = "gzip")]
|
||||
Inner::Gzip(ref mut decoder) => {
|
||||
return match futures_core::ready!(Pin::new(decoder).poll_next(cx)) {
|
||||
Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))),
|
||||
Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))),
|
||||
None => Poll::Ready(None),
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "brotli")]
|
||||
Inner::Brotli(ref mut decoder) => {
|
||||
return match futures_core::ready!(Pin::new(decoder).poll_next(cx)) {
|
||||
Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))),
|
||||
Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))),
|
||||
None => Poll::Ready(None),
|
||||
};
|
||||
}
|
||||
#[cfg(feature = "deflate")]
|
||||
Inner::Deflate(ref mut decoder) => {
|
||||
return match futures_core::ready!(Pin::new(decoder).poll_next(cx)) {
|
||||
Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))),
|
||||
Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))),
|
||||
None => Poll::Ready(None),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpBody for Decoder {
|
||||
type Data = Bytes;
|
||||
type Error = crate::Error;
|
||||
|
||||
fn poll_data(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
self.poll_next(cx)
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context,
|
||||
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
|
||||
Poll::Ready(Ok(None))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> http_body::SizeHint {
|
||||
match self.inner {
|
||||
Inner::PlainText(ref body) => HttpBody::size_hint(body),
|
||||
// the rest are "unknown", so default
|
||||
#[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
|
||||
_ => http_body::SizeHint::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Pending {
|
||||
type Output = Result<Inner, std::io::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
use futures_util::StreamExt;
|
||||
|
||||
match futures_core::ready!(Pin::new(&mut self.0).poll_peek(cx)) {
|
||||
Some(Ok(_)) => {
|
||||
// fallthrough
|
||||
}
|
||||
Some(Err(_e)) => {
|
||||
// error was just a ref, so we need to really poll to move it
|
||||
return Poll::Ready(Err(futures_core::ready!(
|
||||
Pin::new(&mut self.0).poll_next(cx)
|
||||
)
|
||||
.expect("just peeked Some")
|
||||
.unwrap_err()));
|
||||
}
|
||||
None => return Poll::Ready(Ok(Inner::PlainText(Body::empty().into_stream()))),
|
||||
};
|
||||
|
||||
let _body = std::mem::replace(
|
||||
&mut self.0,
|
||||
IoStream(Body::empty().into_stream()).peekable(),
|
||||
);
|
||||
|
||||
match self.1 {
|
||||
#[cfg(feature = "brotli")]
|
||||
DecoderType::Brotli => Poll::Ready(Ok(Inner::Brotli(FramedRead::new(
|
||||
BrotliDecoder::new(StreamReader::new(_body)),
|
||||
BytesCodec::new(),
|
||||
)))),
|
||||
#[cfg(feature = "gzip")]
|
||||
DecoderType::Gzip => Poll::Ready(Ok(Inner::Gzip(FramedRead::new(
|
||||
GzipDecoder::new(StreamReader::new(_body)),
|
||||
BytesCodec::new(),
|
||||
)))),
|
||||
#[cfg(feature = "deflate")]
|
||||
DecoderType::Deflate => Poll::Ready(Ok(Inner::Deflate(FramedRead::new(
|
||||
ZlibDecoder::new(StreamReader::new(_body)),
|
||||
BytesCodec::new(),
|
||||
)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for IoStream {
|
||||
type Item = Result<Bytes, std::io::Error>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
match futures_core::ready!(Pin::new(&mut self.0).poll_next(cx)) {
|
||||
Some(Ok(chunk)) => Poll::Ready(Some(Ok(chunk))),
|
||||
Some(Err(err)) => Poll::Ready(Some(Err(err.into_io()))),
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Accepts =====
|
||||
|
||||
impl Accepts {
|
||||
pub(super) fn none() -> Self {
|
||||
Accepts {
|
||||
#[cfg(feature = "gzip")]
|
||||
gzip: false,
|
||||
#[cfg(feature = "brotli")]
|
||||
brotli: false,
|
||||
#[cfg(feature = "deflate")]
|
||||
deflate: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn as_str(&self) -> Option<&'static str> {
|
||||
match (self.is_gzip(), self.is_brotli(), self.is_deflate()) {
|
||||
(true, true, true) => Some("gzip, br, deflate"),
|
||||
(true, true, false) => Some("gzip, br"),
|
||||
(true, false, true) => Some("gzip, deflate"),
|
||||
(false, true, true) => Some("br, deflate"),
|
||||
(true, false, false) => Some("gzip"),
|
||||
(false, true, false) => Some("br"),
|
||||
(false, false, true) => Some("deflate"),
|
||||
(false, false, false) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_gzip(&self) -> bool {
|
||||
#[cfg(feature = "gzip")]
|
||||
{
|
||||
self.gzip
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "gzip"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_brotli(&self) -> bool {
|
||||
#[cfg(feature = "brotli")]
|
||||
{
|
||||
self.brotli
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "brotli"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_deflate(&self) -> bool {
|
||||
#[cfg(feature = "deflate")]
|
||||
{
|
||||
self.deflate
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "deflate"))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Accepts {
|
||||
fn default() -> Accepts {
|
||||
Accepts {
|
||||
#[cfg(feature = "gzip")]
|
||||
gzip: true,
|
||||
#[cfg(feature = "brotli")]
|
||||
brotli: true,
|
||||
#[cfg(feature = "deflate")]
|
||||
deflate: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
15
zeroidc/vendor/reqwest/src/async_impl/mod.rs
vendored
Normal file
15
zeroidc/vendor/reqwest/src/async_impl/mod.rs
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
pub use self::body::Body;
|
||||
pub use self::client::{Client, ClientBuilder};
|
||||
pub use self::request::{Request, RequestBuilder};
|
||||
pub use self::response::Response;
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
pub(crate) use self::decoder::Decoder;
|
||||
|
||||
pub mod body;
|
||||
pub mod client;
|
||||
pub mod decoder;
|
||||
#[cfg(feature = "multipart")]
|
||||
pub mod multipart;
|
||||
pub(crate) mod request;
|
||||
mod response;
|
||||
659
zeroidc/vendor/reqwest/src/async_impl/multipart.rs
vendored
Normal file
659
zeroidc/vendor/reqwest/src/async_impl/multipart.rs
vendored
Normal file
@@ -0,0 +1,659 @@
|
||||
//! multipart/form-data
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::HeaderMap;
|
||||
use mime_guess::Mime;
|
||||
use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
|
||||
|
||||
use futures_core::Stream;
|
||||
use futures_util::{future, stream, StreamExt};
|
||||
|
||||
use super::Body;
|
||||
|
||||
/// An async multipart/form-data request.
|
||||
pub struct Form {
|
||||
inner: FormParts<Part>,
|
||||
}
|
||||
|
||||
/// A field in a multipart form.
|
||||
pub struct Part {
|
||||
meta: PartMetadata,
|
||||
value: Body,
|
||||
body_length: Option<u64>,
|
||||
}
|
||||
|
||||
pub(crate) struct FormParts<P> {
|
||||
pub(crate) boundary: String,
|
||||
pub(crate) computed_headers: Vec<Vec<u8>>,
|
||||
pub(crate) fields: Vec<(Cow<'static, str>, P)>,
|
||||
pub(crate) percent_encoding: PercentEncoding,
|
||||
}
|
||||
|
||||
pub(crate) struct PartMetadata {
|
||||
mime: Option<Mime>,
|
||||
file_name: Option<Cow<'static, str>>,
|
||||
pub(crate) headers: HeaderMap,
|
||||
}
|
||||
|
||||
pub(crate) trait PartProps {
|
||||
fn value_len(&self) -> Option<u64>;
|
||||
fn metadata(&self) -> &PartMetadata;
|
||||
}
|
||||
|
||||
// ===== impl Form =====
|
||||
|
||||
impl Default for Form {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Form {
|
||||
/// Creates a new async 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::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 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())
|
||||
}
|
||||
|
||||
/// Consume this instance and transform into an instance of Body for use in a request.
|
||||
pub(crate) fn stream(mut self) -> Body {
|
||||
if self.inner.fields.is_empty() {
|
||||
return Body::empty();
|
||||
}
|
||||
|
||||
// create initial part to init reduce chain
|
||||
let (name, part) = self.inner.fields.remove(0);
|
||||
let start = Box::pin(self.part_stream(name, part))
|
||||
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
|
||||
|
||||
let fields = self.inner.take_fields();
|
||||
// for each field, chain an additional stream
|
||||
let stream = fields.into_iter().fold(start, |memo, (name, part)| {
|
||||
let part_stream = self.part_stream(name, part);
|
||||
Box::pin(memo.chain(part_stream))
|
||||
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
|
||||
});
|
||||
// append special ending boundary
|
||||
let last = stream::once(future::ready(Ok(
|
||||
format!("--{}--\r\n", self.boundary()).into()
|
||||
)));
|
||||
Body::stream(stream.chain(last))
|
||||
}
|
||||
|
||||
/// Generate a hyper::Body stream for a single Part instance of a Form request.
|
||||
pub(crate) fn part_stream<T>(
|
||||
&mut self,
|
||||
name: T,
|
||||
part: Part,
|
||||
) -> impl Stream<Item = Result<Bytes, crate::Error>>
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
{
|
||||
// start with boundary
|
||||
let boundary = stream::once(future::ready(Ok(
|
||||
format!("--{}\r\n", self.boundary()).into()
|
||||
)));
|
||||
// append headers
|
||||
let header = stream::once(future::ready(Ok({
|
||||
let mut h = self
|
||||
.inner
|
||||
.percent_encoding
|
||||
.encode_headers(&name.into(), &part.meta);
|
||||
h.extend_from_slice(b"\r\n\r\n");
|
||||
h.into()
|
||||
})));
|
||||
// then append form data followed by terminating CRLF
|
||||
boundary
|
||||
.chain(header)
|
||||
.chain(part.value.into_stream())
|
||||
.chain(stream::once(future::ready(Ok("\r\n".into()))))
|
||||
}
|
||||
|
||||
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 =====
|
||||
|
||||
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, None)
|
||||
}
|
||||
|
||||
/// 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, None)
|
||||
}
|
||||
|
||||
/// Makes a new parameter from an arbitrary stream.
|
||||
pub fn stream<T: Into<Body>>(value: T) -> Part {
|
||||
Part::new(value.into(), None)
|
||||
}
|
||||
|
||||
/// Makes a new parameter from an arbitrary stream with a known length. This is particularly
|
||||
/// useful when adding something like file contents as a stream, where you can know the content
|
||||
/// length beforehand.
|
||||
pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
|
||||
Part::new(value.into(), Some(length))
|
||||
}
|
||||
|
||||
fn new(value: Body, body_length: Option<u64>) -> Part {
|
||||
Part {
|
||||
meta: PartMetadata::new(),
|
||||
value,
|
||||
body_length,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
||||
fn with_inner<F>(self, func: F) -> Self
|
||||
where
|
||||
F: FnOnce(PartMetadata) -> PartMetadata,
|
||||
{
|
||||
Part {
|
||||
meta: func(self.meta),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
if self.body_length.is_some() {
|
||||
self.body_length
|
||||
} else {
|
||||
self.value.content_length()
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&self) -> &PartMetadata {
|
||||
&self.meta
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl FormParts =====
|
||||
|
||||
impl<P: PartProps> FormParts<P> {
|
||||
pub(crate) fn new() -> Self {
|
||||
FormParts {
|
||||
boundary: gen_boundary(),
|
||||
computed_headers: Vec::new(),
|
||||
fields: Vec::new(),
|
||||
percent_encoding: PercentEncoding::PathSegment,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn boundary(&self) -> &str {
|
||||
&self.boundary
|
||||
}
|
||||
|
||||
/// Adds a customized Part.
|
||||
pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
{
|
||||
self.fields.push((name.into(), part));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure this `Form` to percent-encode using the `path-segment` rules.
|
||||
pub(crate) fn percent_encode_path_segment(mut self) -> Self {
|
||||
self.percent_encoding = PercentEncoding::PathSegment;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure this `Form` to percent-encode using the `attr-char` rules.
|
||||
pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
|
||||
self.percent_encoding = PercentEncoding::AttrChar;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure this `Form` to skip percent-encoding
|
||||
pub(crate) fn percent_encode_noop(mut self) -> Self {
|
||||
self.percent_encoding = PercentEncoding::NoOp;
|
||||
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> {
|
||||
let mut length = 0u64;
|
||||
for &(ref name, ref field) in self.fields.iter() {
|
||||
match field.value_len() {
|
||||
Some(value_length) => {
|
||||
// We are constructing the header just to get its length. To not have to
|
||||
// construct it again when the request is sent we cache these headers.
|
||||
let header = self.percent_encoding.encode_headers(name, field.metadata());
|
||||
let header_length = header.len();
|
||||
self.computed_headers.push(header);
|
||||
// The additions mimic the format string out of which the field is constructed
|
||||
// in Reader. Not the cleanest solution because if that format string is
|
||||
// ever changed then this formula needs to be changed too which is not an
|
||||
// obvious dependency in the code.
|
||||
length += 2
|
||||
+ self.boundary().len() as u64
|
||||
+ 2
|
||||
+ header_length as u64
|
||||
+ 4
|
||||
+ value_length
|
||||
+ 2
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
// If there is a at least one field there is a special boundary for the very last field.
|
||||
if !self.fields.is_empty() {
|
||||
length += 2 + self.boundary().len() as u64 + 4
|
||||
}
|
||||
Some(length)
|
||||
}
|
||||
|
||||
/// Take the fields vector of this instance, replacing with an empty vector.
|
||||
fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
|
||||
std::mem::replace(&mut self.fields, Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: fmt::Debug> FormParts<P> {
|
||||
pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct(ty_name)
|
||||
.field("boundary", &self.boundary)
|
||||
.field("parts", &self.fields)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl PartMetadata =====
|
||||
|
||||
impl PartMetadata {
|
||||
pub(crate) fn new() -> Self {
|
||||
PartMetadata {
|
||||
mime: None,
|
||||
file_name: None,
|
||||
headers: HeaderMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mime(mut self, mime: Mime) -> Self {
|
||||
self.mime = Some(mime);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn file_name<T>(mut self, filename: T) -> Self
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
{
|
||||
self.file_name = Some(filename.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartMetadata {
|
||||
pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
|
||||
&self,
|
||||
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
|
||||
) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
|
||||
debug_struct
|
||||
.field("mime", &self.mime)
|
||||
.field("file_name", &self.file_name)
|
||||
.field("headers", &self.headers)
|
||||
}
|
||||
}
|
||||
|
||||
// https://url.spec.whatwg.org/#fragment-percent-encode-set
|
||||
const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'"')
|
||||
.add(b'<')
|
||||
.add(b'>')
|
||||
.add(b'`');
|
||||
|
||||
// https://url.spec.whatwg.org/#path-percent-encode-set
|
||||
const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
|
||||
|
||||
const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
|
||||
|
||||
// https://tools.ietf.org/html/rfc8187#section-3.2.1
|
||||
const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
|
||||
.remove(b'!')
|
||||
.remove(b'#')
|
||||
.remove(b'$')
|
||||
.remove(b'&')
|
||||
.remove(b'+')
|
||||
.remove(b'-')
|
||||
.remove(b'.')
|
||||
.remove(b'^')
|
||||
.remove(b'_')
|
||||
.remove(b'`')
|
||||
.remove(b'|')
|
||||
.remove(b'~');
|
||||
|
||||
pub(crate) enum PercentEncoding {
|
||||
PathSegment,
|
||||
AttrChar,
|
||||
NoOp,
|
||||
}
|
||||
|
||||
impl PercentEncoding {
|
||||
pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
|
||||
let s = format!(
|
||||
"Content-Disposition: form-data; {}{}{}",
|
||||
self.format_parameter("name", name),
|
||||
match field.file_name {
|
||||
Some(ref file_name) => format!("; {}", self.format_filename(file_name)),
|
||||
None => String::new(),
|
||||
},
|
||||
match field.mime {
|
||||
Some(ref mime) => format!("\r\nContent-Type: {}", mime),
|
||||
None => "".to_string(),
|
||||
},
|
||||
);
|
||||
field
|
||||
.headers
|
||||
.iter()
|
||||
.fold(s.into_bytes(), |mut header, (k, v)| {
|
||||
header.extend_from_slice(b"\r\n");
|
||||
header.extend_from_slice(k.as_str().as_bytes());
|
||||
header.extend_from_slice(b": ");
|
||||
header.extend_from_slice(v.as_bytes());
|
||||
header
|
||||
})
|
||||
}
|
||||
|
||||
// According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
|
||||
// See https://github.com/seanmonstar/reqwest/issues/419.
|
||||
fn format_filename(&self, filename: &str) -> String {
|
||||
let legal_filename = filename
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\r", "\\\r")
|
||||
.replace("\n", "\\\n");
|
||||
format!("filename=\"{}\"", legal_filename)
|
||||
}
|
||||
|
||||
fn format_parameter(&self, name: &str, value: &str) -> String {
|
||||
let legal_value = match *self {
|
||||
PercentEncoding::PathSegment => {
|
||||
percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
|
||||
}
|
||||
PercentEncoding::AttrChar => {
|
||||
percent_encoding::utf8_percent_encode(value, ATTR_CHAR_ENCODE_SET).to_string()
|
||||
}
|
||||
PercentEncoding::NoOp => value.to_string(),
|
||||
};
|
||||
if value.len() == legal_value.len() {
|
||||
// nothing has been percent encoded
|
||||
format!("{}=\"{}\"", name, value)
|
||||
} else {
|
||||
// something has been percent encoded
|
||||
format!("{}*=utf-8''{}", name, legal_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_boundary() -> String {
|
||||
use crate::util::fast_random as random;
|
||||
|
||||
let a = random();
|
||||
let b = random();
|
||||
let c = random();
|
||||
let d = random();
|
||||
|
||||
format!("{:016x}-{:016x}-{:016x}-{:016x}", a, b, c, d)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures_util::TryStreamExt;
|
||||
use futures_util::{future, stream};
|
||||
use tokio::{self, runtime};
|
||||
|
||||
#[test]
|
||||
fn form_empty() {
|
||||
let form = Form::new();
|
||||
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("new rt");
|
||||
let body = form.stream().into_stream();
|
||||
let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
|
||||
|
||||
let out = rt.block_on(s);
|
||||
assert!(out.unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_to_end() {
|
||||
let mut form = Form::new()
|
||||
.part(
|
||||
"reader1",
|
||||
Part::stream(Body::stream(stream::once(future::ready::<
|
||||
Result<String, crate::Error>,
|
||||
>(Ok(
|
||||
"part1".to_owned()
|
||||
))))),
|
||||
)
|
||||
.part("key1", Part::text("value1"))
|
||||
.part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
|
||||
.part(
|
||||
"reader2",
|
||||
Part::stream(Body::stream(stream::once(future::ready::<
|
||||
Result<String, crate::Error>,
|
||||
>(Ok(
|
||||
"part2".to_owned()
|
||||
))))),
|
||||
)
|
||||
.part("key3", Part::text("value3").file_name("filename"));
|
||||
form.inner.boundary = "boundary".to_string();
|
||||
let expected = "--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
part1\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\
|
||||
part2\r\n\
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
|
||||
value3\r\n--boundary--\r\n";
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("new rt");
|
||||
let body = form.stream().into_stream();
|
||||
let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
|
||||
|
||||
let out = rt.block_on(s).unwrap();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
"START REAL\n{}\nEND REAL",
|
||||
std::str::from_utf8(&out).unwrap()
|
||||
);
|
||||
println!("START EXPECTED\n{}\nEND EXPECTED", expected);
|
||||
assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_to_end_with_header() {
|
||||
let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
|
||||
part.meta.headers.insert("Hdr3", "/a/b/c".parse().unwrap());
|
||||
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";
|
||||
let rt = runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("new rt");
|
||||
let body = form.stream().into_stream();
|
||||
let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
|
||||
|
||||
let out = rt.block_on(s).unwrap();
|
||||
// These prints are for debug purposes in case the test fails
|
||||
println!(
|
||||
"START REAL\n{}\nEND REAL",
|
||||
std::str::from_utf8(&out).unwrap()
|
||||
);
|
||||
println!("START EXPECTED\n{}\nEND EXPECTED", expected);
|
||||
assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_content_length() {
|
||||
// Setup an arbitrary data stream
|
||||
let stream_data = b"just some stream data";
|
||||
let stream_len = stream_data.len();
|
||||
let stream_data = stream_data
|
||||
.chunks(3)
|
||||
.map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
|
||||
let the_stream = futures_util::stream::iter(stream_data);
|
||||
|
||||
let bytes_data = b"some bytes data".to_vec();
|
||||
let bytes_len = bytes_data.len();
|
||||
|
||||
let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
|
||||
let body_part = Part::bytes(bytes_data);
|
||||
|
||||
// A simple check to make sure we get the configured body length
|
||||
assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
|
||||
|
||||
// Make sure it delegates to the underlying body if length is not specified
|
||||
assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_percent_encoding() {
|
||||
let name = "start%'\"\r\nßend";
|
||||
let field = Part::text("");
|
||||
|
||||
assert_eq!(
|
||||
PercentEncoding::PathSegment.encode_headers(name, &field.meta),
|
||||
&b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PercentEncoding::AttrChar.encode_headers(name, &field.meta),
|
||||
&b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
|
||||
);
|
||||
}
|
||||
}
|
||||
1085
zeroidc/vendor/reqwest/src/async_impl/request.rs
vendored
Normal file
1085
zeroidc/vendor/reqwest/src/async_impl/request.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
460
zeroidc/vendor/reqwest/src/async_impl/response.rs
vendored
Normal file
460
zeroidc/vendor/reqwest/src/async_impl/response.rs
vendored
Normal file
@@ -0,0 +1,460 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
|
||||
use bytes::Bytes;
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures_util::stream::StreamExt;
|
||||
use hyper::client::connect::HttpInfo;
|
||||
use hyper::{HeaderMap, StatusCode, Version};
|
||||
use mime::Mime;
|
||||
#[cfg(feature = "json")]
|
||||
use serde::de::DeserializeOwned;
|
||||
#[cfg(feature = "json")]
|
||||
use serde_json;
|
||||
use tokio::time::Sleep;
|
||||
use url::Url;
|
||||
|
||||
use super::body::Body;
|
||||
use super::decoder::{Accepts, Decoder};
|
||||
#[cfg(feature = "cookies")]
|
||||
use crate::cookie;
|
||||
use crate::response::ResponseUrl;
|
||||
|
||||
/// A Response to a submitted `Request`.
|
||||
pub struct Response {
|
||||
status: StatusCode,
|
||||
headers: HeaderMap,
|
||||
// Boxed to save space (11 words to 1 word), and it's not accessed
|
||||
// frequently internally.
|
||||
url: Box<Url>,
|
||||
body: Decoder,
|
||||
version: Version,
|
||||
extensions: http::Extensions,
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub(super) fn new(
|
||||
res: hyper::Response<hyper::Body>,
|
||||
url: Url,
|
||||
accepts: Accepts,
|
||||
timeout: Option<Pin<Box<Sleep>>>,
|
||||
) -> Response {
|
||||
let (parts, body) = res.into_parts();
|
||||
let status = parts.status;
|
||||
let version = parts.version;
|
||||
let extensions = parts.extensions;
|
||||
|
||||
let mut headers = parts.headers;
|
||||
let decoder = Decoder::detect(&mut headers, Body::response(body, timeout), accepts);
|
||||
|
||||
Response {
|
||||
status,
|
||||
headers,
|
||||
url: Box::new(url),
|
||||
body: decoder,
|
||||
version,
|
||||
extensions,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `StatusCode` of this `Response`.
|
||||
#[inline]
|
||||
pub fn status(&self) -> StatusCode {
|
||||
self.status
|
||||
}
|
||||
|
||||
/// Get the HTTP `Version` of this `Response`.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Get the `Headers` of this `Response`.
|
||||
#[inline]
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the `Headers` of this `Response`.
|
||||
#[inline]
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
|
||||
/// Get the content-length of this response, if known.
|
||||
///
|
||||
/// Reasons it may not be known:
|
||||
///
|
||||
/// - The server didn't send a `content-length` header.
|
||||
/// - The response is compressed and automatically decoded (thus changing
|
||||
/// the actual decoded length).
|
||||
pub fn content_length(&self) -> Option<u64> {
|
||||
use hyper::body::HttpBody;
|
||||
|
||||
HttpBody::size_hint(&self.body).exact()
|
||||
}
|
||||
|
||||
/// 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 final `Url` of this `Response`.
|
||||
#[inline]
|
||||
pub fn url(&self) -> &Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Get the remote address used to get this `Response`.
|
||||
pub fn remote_addr(&self) -> Option<SocketAddr> {
|
||||
self.extensions
|
||||
.get::<HttpInfo>()
|
||||
.map(|info| info.remote_addr())
|
||||
}
|
||||
|
||||
/// Returns a reference to the associated extensions.
|
||||
pub fn extensions(&self) -> &http::Extensions {
|
||||
&self.extensions
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the associated extensions.
|
||||
pub fn extensions_mut(&mut self) -> &mut http::Extensions {
|
||||
&mut self.extensions
|
||||
}
|
||||
|
||||
// body methods
|
||||
|
||||
/// Get the full response text.
|
||||
///
|
||||
/// This method decodes the response body with BOM sniffing
|
||||
/// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
|
||||
/// Encoding is determinated from the `charset` parameter of `Content-Type` header,
|
||||
/// and defaults to `utf-8` if not presented.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let content = reqwest::get("http://httpbin.org/range/26")
|
||||
/// .await?
|
||||
/// .text()
|
||||
/// .await?;
|
||||
///
|
||||
/// println!("text: {:?}", content);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn text(self) -> crate::Result<String> {
|
||||
self.text_with_charset("utf-8").await
|
||||
}
|
||||
|
||||
/// Get the full 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
|
||||
///
|
||||
/// ```
|
||||
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let content = reqwest::get("http://httpbin.org/range/26")
|
||||
/// .await?
|
||||
/// .text_with_charset("utf-8")
|
||||
/// .await?;
|
||||
///
|
||||
/// println!("text: {:?}", content);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
|
||||
let content_type = self
|
||||
.headers
|
||||
.get(crate::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
let encoding_name = content_type
|
||||
.as_ref()
|
||||
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
|
||||
.unwrap_or(default_encoding);
|
||||
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
|
||||
|
||||
let full = self.bytes().await?;
|
||||
|
||||
let (text, _, _) = encoding.decode(&full);
|
||||
if let Cow::Owned(s) = text {
|
||||
return Ok(s);
|
||||
}
|
||||
unsafe {
|
||||
// decoding returned Cow::Borrowed, meaning these bytes
|
||||
// are already valid utf8
|
||||
Ok(String::from_utf8_unchecked(full.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to deserialize the response body as JSON.
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This requires the optional `json` feature enabled.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # extern crate reqwest;
|
||||
/// # extern crate serde;
|
||||
/// #
|
||||
/// # use reqwest::Error;
|
||||
/// # use serde::Deserialize;
|
||||
/// #
|
||||
/// // This `derive` requires the `serde` dependency.
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Ip {
|
||||
/// origin: String,
|
||||
/// }
|
||||
///
|
||||
/// # async fn run() -> Result<(), Error> {
|
||||
/// let ip = reqwest::get("http://httpbin.org/ip")
|
||||
/// .await?
|
||||
/// .json::<Ip>()
|
||||
/// .await?;
|
||||
///
|
||||
/// println!("ip: {}", ip.origin);
|
||||
/// # 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 async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
|
||||
let full = self.bytes().await?;
|
||||
|
||||
serde_json::from_slice(&full).map_err(crate::error::decode)
|
||||
}
|
||||
|
||||
/// Get the full response body as `Bytes`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let bytes = reqwest::get("http://httpbin.org/ip")
|
||||
/// .await?
|
||||
/// .bytes()
|
||||
/// .await?;
|
||||
///
|
||||
/// println!("bytes: {:?}", bytes);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn bytes(self) -> crate::Result<Bytes> {
|
||||
hyper::body::to_bytes(self.body).await
|
||||
}
|
||||
|
||||
/// Stream a chunk of the response body.
|
||||
///
|
||||
/// When the response body has been exhausted, this will return `None`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut res = reqwest::get("https://hyper.rs").await?;
|
||||
///
|
||||
/// while let Some(chunk) = res.chunk().await? {
|
||||
/// println!("Chunk: {:?}", chunk);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> {
|
||||
if let Some(item) = self.body.next().await {
|
||||
Ok(Some(item?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the response into a `Stream` of `Bytes` from the body.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use futures_util::StreamExt;
|
||||
///
|
||||
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut stream = reqwest::get("http://httpbin.org/ip")
|
||||
/// .await?
|
||||
/// .bytes_stream();
|
||||
///
|
||||
/// while let Some(item) = stream.next().await {
|
||||
/// println!("Chunk: {:?}", item?);
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This requires the optional `stream` feature to be enabled.
|
||||
#[cfg(feature = "stream")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
|
||||
pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
|
||||
self.body
|
||||
}
|
||||
|
||||
// util methods
|
||||
|
||||
/// Turn a response into an error if the server returned an error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use reqwest::Response;
|
||||
/// fn on_response(res: Response) {
|
||||
/// match res.error_for_status() {
|
||||
/// Ok(_res) => (),
|
||||
/// Err(err) => {
|
||||
/// // asserting a 400 as an example
|
||||
/// // it could be any status between 400...599
|
||||
/// assert_eq!(
|
||||
/// err.status(),
|
||||
/// Some(reqwest::StatusCode::BAD_REQUEST)
|
||||
/// );
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn error_for_status(self) -> crate::Result<Self> {
|
||||
if self.status.is_client_error() || self.status.is_server_error() {
|
||||
Err(crate::error::status_code(*self.url, self.status))
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn a reference to a response into an error if the server returned an error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use reqwest::Response;
|
||||
/// fn on_response(res: &Response) {
|
||||
/// match res.error_for_status_ref() {
|
||||
/// Ok(_res) => (),
|
||||
/// Err(err) => {
|
||||
/// // asserting a 400 as an example
|
||||
/// // it could be any status between 400...599
|
||||
/// assert_eq!(
|
||||
/// err.status(),
|
||||
/// Some(reqwest::StatusCode::BAD_REQUEST)
|
||||
/// );
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
|
||||
if self.status.is_client_error() || self.status.is_server_error() {
|
||||
Err(crate::error::status_code(*self.url.clone(), self.status))
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
// The Response's body is an implementation detail.
|
||||
// You no longer need to get a reference to it, there are async methods
|
||||
// on the `Response` itself.
|
||||
//
|
||||
// This method is just used by the blocking API.
|
||||
#[cfg(feature = "blocking")]
|
||||
pub(crate) fn body_mut(&mut self) -> &mut Decoder {
|
||||
&mut self.body
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Response {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Response")
|
||||
.field("url", self.url())
|
||||
.field("status", &self.status())
|
||||
.field("headers", self.headers())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Body>> From<http::Response<T>> for Response {
|
||||
fn from(r: http::Response<T>) -> Response {
|
||||
let (mut parts, body) = r.into_parts();
|
||||
let body = body.into();
|
||||
let body = Decoder::detect(&mut parts.headers, body, Accepts::none());
|
||||
let url = parts
|
||||
.extensions
|
||||
.remove::<ResponseUrl>()
|
||||
.unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap()));
|
||||
let url = url.0;
|
||||
Response {
|
||||
status: parts.status,
|
||||
headers: parts.headers,
|
||||
url: Box::new(url),
|
||||
body,
|
||||
version: parts.version,
|
||||
extensions: parts.extensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Response` can be piped as the `Body` of another request.
|
||||
impl From<Response> for Body {
|
||||
fn from(r: Response) -> Body {
|
||||
Body::stream(r.body)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Response;
|
||||
use crate::ResponseBuilderExt;
|
||||
use http::response::Builder;
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn test_from_http_response() {
|
||||
let url = Url::parse("http://example.com").unwrap();
|
||||
let response = Builder::new()
|
||||
.status(200)
|
||||
.url(url.clone())
|
||||
.body("foo")
|
||||
.unwrap();
|
||||
let response = Response::from(response);
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
assert_eq!(*response.url(), url);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user