Skip to content

Commit bfda390

Browse files
euclidrseanmonstar
authored andcommitted
docs(server): http_proxy example
1 parent 703ac34 commit bfda390

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ name = "hello"
110110
path = "examples/hello.rs"
111111
required-features = ["runtime"]
112112

113+
[[example]]
114+
name = "http_proxy"
115+
path = "examples/http_proxy.rs"
116+
required-features = ["runtime"]
117+
113118
[[example]]
114119
name = "multi_server"
115120
path = "examples/multi_server.rs"

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ parses it with serde and outputs the result.
1313

1414
* [`hello`](hello.rs) - A simple server that returns "Hello World!" using a closure wrapped to provide a [`Service`](../src/service/service.rs).
1515

16+
* [`http_proxy`](http_proxy.rs) - A simple HTTP(S) proxy that handle and upgrade CONNECT request and then proxy data between client and remote server.
17+
1618
* [`multi_server`](multi_server.rs) - A server that listens to two different ports, a different [`Service`](../src/service/service.rs) by port, spawning two [`futures`](../src/rt.rs).
1719

1820
* [`params`](params.rs) - A webserver that accept a form, with a name and a number, checks the parameters are presents and validates the input.

examples/http_proxy.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#![deny(warnings)]
2+
3+
use std::convert::Infallible;
4+
use std::net::SocketAddr;
5+
6+
use futures_util::future::try_join;
7+
8+
use hyper::service::{make_service_fn, service_fn};
9+
use hyper::upgrade::Upgraded;
10+
use hyper::{Body, Client, Method, Request, Response, Server};
11+
12+
use tokio::net::TcpStream;
13+
14+
type HttpClient = Client<hyper::client::HttpConnector>;
15+
16+
// To try this example:
17+
// 1. cargo run --example http_proxy
18+
// 2. config http_proxy in command line
19+
// $ export http_proxy=http://127.0.0.1:8100
20+
// $ export https_proxy=http://127.0.0.1:8100
21+
// 3. send requests
22+
// $ curl -i https://www.some_domain.com/
23+
#[tokio::main]
24+
async fn main() {
25+
let addr = SocketAddr::from(([127, 0, 0, 1], 8100));
26+
let client = HttpClient::new();
27+
28+
let make_service = make_service_fn(move |_| {
29+
let client = client.clone();
30+
async move { Ok::<_, Infallible>(service_fn(move |req| proxy(client.clone(), req))) }
31+
});
32+
33+
let server = Server::bind(&addr).serve(make_service);
34+
35+
println!("Listening on http://{}", addr);
36+
37+
if let Err(e) = server.await {
38+
eprintln!("server error: {}", e);
39+
}
40+
}
41+
42+
async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
43+
println!("req: {:?}", req);
44+
45+
if Method::CONNECT == req.method() {
46+
// Recieved an HTTP request like:
47+
// ```
48+
// CONNECT www.domain.com:443 HTTP/1.1
49+
// Host: www.domain.com:443
50+
// Proxy-Connection: Keep-Alive
51+
// ```
52+
//
53+
// When HTTP method is CONNECT we should return an empty body
54+
// then we can eventually upgrade the connection and talk a new protocol.
55+
//
56+
// Note: only after client recieved an empty body with STATUS_OK can the
57+
// connection be upgraded, so we can't return a response inside
58+
// `on_upgrade` future.
59+
if let Some(addr) = host_addr(req.uri()) {
60+
tokio::task::spawn(async move {
61+
match req.into_body().on_upgrade().await {
62+
Ok(upgraded) => {
63+
if let Err(e) = tunnel(upgraded, addr).await {
64+
eprintln!("server io error: {}", e);
65+
};
66+
}
67+
Err(e) => eprintln!("upgrade error: {}", e),
68+
}
69+
});
70+
71+
Ok(Response::new(Body::empty()))
72+
} else {
73+
eprintln!("CONNECT host is not socket addr: {:?}", req.uri());
74+
let mut resp = Response::new(Body::from("CONNECT must be to a socket address"));
75+
*resp.status_mut() = http::StatusCode::BAD_REQUEST;
76+
77+
Ok(resp)
78+
}
79+
} else {
80+
client.request(req).await
81+
}
82+
}
83+
84+
fn host_addr(uri: &http::Uri) -> Option<SocketAddr> {
85+
uri.authority().and_then(|auth| auth.as_str().parse().ok())
86+
}
87+
88+
// Create a TCP connection to host:port, build a tunnel between the connection and
89+
// the upgraded connection
90+
async fn tunnel(upgraded: Upgraded, addr: SocketAddr) -> std::io::Result<()> {
91+
// Connect to remote server
92+
let mut server = TcpStream::connect(addr).await?;
93+
94+
// Proxying data
95+
let amounts = {
96+
let (mut server_rd, mut server_wr) = server.split();
97+
let (mut client_rd, mut client_wr) = tokio::io::split(upgraded);
98+
99+
let client_to_server = tokio::io::copy(&mut client_rd, &mut server_wr);
100+
let server_to_client = tokio::io::copy(&mut server_rd, &mut client_wr);
101+
102+
try_join(client_to_server, server_to_client).await
103+
};
104+
105+
// Print message when done
106+
match amounts {
107+
Ok((from_client, from_server)) => {
108+
println!(
109+
"client wrote {} bytes and received {} bytes",
110+
from_client, from_server
111+
);
112+
}
113+
Err(e) => {
114+
println!("tunnel error: {}", e);
115+
}
116+
};
117+
Ok(())
118+
}

0 commit comments

Comments
 (0)