Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update reverse proxy example to support both http and https #2696

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/reverse-proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ edition = "2021"

[dependencies]
axum = { path = "../../axum" }
hyper = { version = "1.0.0", features = ["full"] }
hyper-tls = { version = "0.6.0", features = ["vendored"] }
hyper-util = { version = "0.1.1", features = ["client-legacy"] }
tokio = { version = "1", features = ["full"] }
85 changes: 66 additions & 19 deletions examples/reverse-proxy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,63 +1,110 @@
//! Reverse proxy listening in "localhost:4000" will proxy all requests to "localhost:3000"
//! endpoint.
//! Reverse proxy listening in "localhost:4000" will proxy all `GET` requests to "localhost:3000"
//! except for path /https is example.com endpoint.
//!
//! On unix like OS: make sure `ca-certificates` is installed.
//!
//! Run with
//!
//! ```not_rust
//! cargo run -p example-reverse-proxy
//! ```

use axum::extract::ConnectInfo;
use axum::http::header::FORWARDED;
use axum::http::{header::HOST, StatusCode};
use axum::{
body::Body,
extract::{Request, State},
http::uri::Uri,
response::{IntoResponse, Response},
routing::get,
Router,
routing, Router,
};
use hyper::StatusCode;
use hyper_tls::HttpsConnector;
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
use std::net::SocketAddr;

type Client = hyper_util::client::legacy::Client<HttpConnector, Body>;
type Client = hyper_util::client::legacy::Client<HttpsConnector<HttpConnector>, Body>;

#[tokio::main]
async fn main() {
tokio::spawn(server());

let client: Client =
hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new())
.build(HttpConnector::new());
.build(HttpsConnector::new());

let app = Router::new().route("/", get(handler)).with_state(client);
let app = Router::new()
.fallback(routing::get(handler))
.with_state(client);

let listener = tokio::net::TcpListener::bind("127.0.0.1:4000")
.await
.unwrap();
println!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>(),
)
.await
.unwrap();
}

async fn handler(State(client): State<Client>, mut req: Request) -> Result<Response, StatusCode> {
let path = req.uri().path();
let path_query = req
.uri()
.path_and_query()
.map(|v| v.as_str())
.unwrap_or(path);
async fn handler(
State(client): State<Client>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
mut req: Request,
) -> Result<Response, StatusCode> {
let uri = req.uri();

let ip = addr.ip().to_string();
let host = uri
.authority()
.map(|a| a.as_str())
.unwrap_or("127.0.0.1:4000")
.to_string();
let proto = uri.scheme_str().unwrap_or("http").to_string();

let path = uri.path();
let path_query = uri.path_and_query().map(|v| v.as_str()).unwrap_or(path);

let uri = format!("http://127.0.0.1:3000{}", path_query);
let mut uri = format!("http://127.0.0.1:3000{}", path_query);
if path == "/https" {
uri = String::from("https://example.com");
}

*req.uri_mut() = Uri::try_from(uri).unwrap();

// Remove incorrect header host, hyper will add automatically for you.
req.headers_mut().remove(HOST);

// Add some informative header (de-facto)
req.headers_mut()
.insert("X-Forwarded-For", ip.parse().unwrap());
req.headers_mut()
.insert("X-Forwarded-Host", host.parse().unwrap());
req.headers_mut()
.insert("X-Forwarded-Proto", proto.parse().unwrap());

// a standardized
req.headers_mut().insert(
FORWARDED,
format!("for={ip};host={host};proto={proto};")
.parse()
.unwrap(),
);

Ok(client
.request(req)
.await
.map_err(|_| StatusCode::BAD_REQUEST)?
.map_err(|err| {
eprintln!("{:?}", err);
StatusCode::BAD_REQUEST
})?
.into_response())
}

async fn server() {
let app = Router::new().route("/", get(|| async { "Hello, world!" }));
let app = Router::new().fallback(routing::get(|| async { "Hello, world!" }));

let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
Expand Down