//! A proxy that forwards data to another server and forwards that server's //! responses back to clients. //! //! Because the Tokio runtime uses a thread pool, each TCP connection is //! processed concurrently with all other TCP connections across multiple //! threads. //! //! You can showcase this by running this in one terminal: //! //! cargo run --example proxy //! //! This in another terminal //! //! cargo run --example echo //! //! And finally this in another terminal //! //! cargo run --example connect 127.0.0.1:8081 //! //! This final terminal will connect to our proxy, which will in turn connect to //! the echo server, and you'll be able to see data flowing between them. #![warn(rust_2018_idioms)] use tokio::io; use tokio::io::AsyncWriteExt; use tokio::net::{TcpListener, TcpStream}; use futures::future::try_join; use futures::FutureExt; use std::env; use std::error::Error; #[tokio::main] async fn main() -> Result<(), Box> { let listen_addr = env::args() .nth(1) .unwrap_or_else(|| "127.0.0.1:8081".to_string()); let server_addr = env::args() .nth(2) .unwrap_or_else(|| "127.0.0.1:8080".to_string()); println!("Listening on: {}", listen_addr); println!("Proxying to: {}", server_addr); let listener = TcpListener::bind(listen_addr).await?; while let Ok((inbound, _)) = listener.accept().await { let transfer = transfer(inbound, server_addr.clone()).map(|r| { if let Err(e) = r { println!("Failed to transfer; error={}", e); } }); tokio::spawn(transfer); } Ok(()) } async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box> { let mut outbound = TcpStream::connect(proxy_addr).await?; let (mut ri, mut wi) = inbound.split(); let (mut ro, mut wo) = outbound.split(); let client_to_server = async { io::copy(&mut ri, &mut wo).await?; wo.shutdown().await }; let server_to_client = async { io::copy(&mut ro, &mut wi).await?; wi.shutdown().await }; try_join(client_to_server, server_to_client).await?; Ok(()) }