Co-authored-by: Danny Browning <danny.browning@protectwise.com> Co-authored-by: Daniel Eades <danieleades@hotmail.com>
		
			
				
	
	
		
			101 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			101 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| #![deny(warnings)]
 | |
| use std::io::{self, Cursor};
 | |
| use std::mem;
 | |
| use std::path::Path;
 | |
| use std::pin::Pin;
 | |
| use std::task::{Context, Poll};
 | |
| 
 | |
| use futures::{Stream, TryStreamExt};
 | |
| use reqwest::r#async::{Body, Client, Decoder};
 | |
| use tokio_fs::File;
 | |
| use tokio::io::AsyncRead;
 | |
| 
 | |
| use failure::Fail;
 | |
| 
 | |
| #[derive(Debug, Fail)]
 | |
| pub enum Error {
 | |
|     #[fail(display = "Io Error")]
 | |
|     Io(#[fail(cause)] std::io::Error),
 | |
|     #[fail(display = "Reqwest error")]
 | |
|     Reqwest(#[fail(cause)] reqwest::Error),
 | |
| }
 | |
| 
 | |
| unsafe impl Send for Error {}
 | |
| unsafe impl Sync for Error {}
 | |
| 
 | |
| struct AsyncReadWrapper<T> {
 | |
|     inner: T,
 | |
| }
 | |
| 
 | |
| impl<T> AsyncReadWrapper<T> {
 | |
|     fn inner(self: Pin<&mut Self>) -> Pin<&mut T> {
 | |
|         unsafe {
 | |
|             Pin::map_unchecked_mut(self, |x| &mut x.inner)
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<T> Stream for AsyncReadWrapper<T>
 | |
|     where T: AsyncRead
 | |
| {
 | |
|     type Item = Result<hyper::Chunk, failure::Compat<Error>>;
 | |
|     fn poll_next(
 | |
|         mut self: Pin<&mut Self>,
 | |
|         cx: &mut Context<'_>,
 | |
|     ) -> Poll<Option<Self::Item>> {
 | |
|         let mut buf = vec![];
 | |
|         loop {
 | |
|             let mut read_buf = vec![];
 | |
|             match self.as_mut().inner().as_mut().poll_read(cx, &mut read_buf) {
 | |
|                 Poll::Pending => {
 | |
|                     if buf.is_empty() {
 | |
|                         return Poll::Pending;
 | |
|                     } else {
 | |
|                         return Poll::Ready(Some(Ok(buf.into())));
 | |
|                     }
 | |
|                 }
 | |
|                 Poll::Ready(Err(e)) => {
 | |
|                     return Poll::Ready(Some(Err(Error::Io(e).compat())))
 | |
|                 },
 | |
|                 Poll::Ready(Ok(n)) => {
 | |
|                     buf.extend_from_slice(&read_buf[..n]);
 | |
|                     if buf.is_empty() && n == 0 {
 | |
|                         return Poll::Ready(None);
 | |
|                     } else {
 | |
|                         return Poll::Ready(Some(Ok(buf.into())));
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| async fn post<P>(path: P) -> Result<(), Error>
 | |
| where
 | |
|     P: AsRef<Path> + Send + Unpin + 'static,
 | |
| {
 | |
|     let source = File::open(path)
 | |
|         .await.map_err(Error::Io)?;
 | |
|     let wrapper = AsyncReadWrapper { inner: source };
 | |
|     let mut res = Client::new()
 | |
|         .post("https://httpbin.org/post")
 | |
|         .body(Body::wrap_stream(wrapper))
 | |
|         .send()
 | |
|         .await.map_err(Error::Reqwest)?;
 | |
| 
 | |
|     println!("{}", res.status());
 | |
| 
 | |
|     let body = mem::replace(res.body_mut(), Decoder::empty());
 | |
|     let body: Result<_, _> = body.try_concat().await;
 | |
| 
 | |
|     let mut body = Cursor::new(body.map_err(Error::Reqwest)?);
 | |
|     io::copy(&mut body, &mut io::stdout()).map_err(Error::Io)?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 | |
| 
 | |
| #[tokio::main]
 | |
| async fn main() -> Result<(), Error> {
 | |
|     let path = concat!(env!("CARGO_MANIFEST_DIR"), "/LICENSE-APACHE");
 | |
|     post(path).await
 | |
| } |