-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
703ac34
commit bfda390
Showing
3 changed files
with
125 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
#![deny(warnings)] | ||
|
||
use std::convert::Infallible; | ||
use std::net::SocketAddr; | ||
|
||
use futures_util::future::try_join; | ||
|
||
use hyper::service::{make_service_fn, service_fn}; | ||
use hyper::upgrade::Upgraded; | ||
use hyper::{Body, Client, Method, Request, Response, Server}; | ||
|
||
use tokio::net::TcpStream; | ||
|
||
type HttpClient = Client<hyper::client::HttpConnector>; | ||
|
||
// To try this example: | ||
// 1. cargo run --example http_proxy | ||
// 2. config http_proxy in command line | ||
// $ export http_proxy=http://127.0.0.1:8100 | ||
// $ export https_proxy=http://127.0.0.1:8100 | ||
// 3. send requests | ||
// $ curl -i https://www.some_domain.com/ | ||
#[tokio::main] | ||
async fn main() { | ||
let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); | ||
let client = HttpClient::new(); | ||
|
||
let make_service = make_service_fn(move |_| { | ||
let client = client.clone(); | ||
async move { Ok::<_, Infallible>(service_fn(move |req| proxy(client.clone(), req))) } | ||
}); | ||
|
||
let server = Server::bind(&addr).serve(make_service); | ||
|
||
println!("Listening on http://{}", addr); | ||
|
||
if let Err(e) = server.await { | ||
eprintln!("server error: {}", e); | ||
} | ||
} | ||
|
||
async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||
println!("req: {:?}", req); | ||
|
||
if Method::CONNECT == req.method() { | ||
// Recieved an HTTP request like: | ||
// ``` | ||
// CONNECT www.domain.com:443 HTTP/1.1 | ||
// Host: www.domain.com:443 | ||
// Proxy-Connection: Keep-Alive | ||
// ``` | ||
// | ||
// When HTTP method is CONNECT we should return an empty body | ||
// then we can eventually upgrade the connection and talk a new protocol. | ||
// | ||
// Note: only after client recieved an empty body with STATUS_OK can the | ||
// connection be upgraded, so we can't return a response inside | ||
// `on_upgrade` future. | ||
if let Some(addr) = host_addr(req.uri()) { | ||
tokio::task::spawn(async move { | ||
match req.into_body().on_upgrade().await { | ||
Ok(upgraded) => { | ||
if let Err(e) = tunnel(upgraded, addr).await { | ||
eprintln!("server io error: {}", e); | ||
}; | ||
} | ||
Err(e) => eprintln!("upgrade error: {}", e), | ||
} | ||
}); | ||
|
||
Ok(Response::new(Body::empty())) | ||
} else { | ||
eprintln!("CONNECT host is not socket addr: {:?}", req.uri()); | ||
let mut resp = Response::new(Body::from("CONNECT must be to a socket address")); | ||
*resp.status_mut() = http::StatusCode::BAD_REQUEST; | ||
|
||
Ok(resp) | ||
} | ||
} else { | ||
client.request(req).await | ||
} | ||
} | ||
|
||
fn host_addr(uri: &http::Uri) -> Option<SocketAddr> { | ||
uri.authority().and_then(|auth| auth.as_str().parse().ok()) | ||
} | ||
|
||
// Create a TCP connection to host:port, build a tunnel between the connection and | ||
// the upgraded connection | ||
async fn tunnel(upgraded: Upgraded, addr: SocketAddr) -> std::io::Result<()> { | ||
// Connect to remote server | ||
let mut server = TcpStream::connect(addr).await?; | ||
|
||
// Proxying data | ||
let amounts = { | ||
let (mut server_rd, mut server_wr) = server.split(); | ||
let (mut client_rd, mut client_wr) = tokio::io::split(upgraded); | ||
|
||
let client_to_server = tokio::io::copy(&mut client_rd, &mut server_wr); | ||
let server_to_client = tokio::io::copy(&mut server_rd, &mut client_wr); | ||
|
||
try_join(client_to_server, server_to_client).await | ||
}; | ||
|
||
// Print message when done | ||
match amounts { | ||
Ok((from_client, from_server)) => { | ||
println!( | ||
"client wrote {} bytes and received {} bytes", | ||
from_client, from_server | ||
); | ||
} | ||
Err(e) => { | ||
println!("tunnel error: {}", e); | ||
} | ||
}; | ||
Ok(()) | ||
} |