forked from Deuxfleurs/tricot
115 lines
3.1 KiB
Rust
115 lines
3.1 KiB
Rust
|
//! Copied from https://github.com/felipenoris/hyper-reverse-proxy
|
||
|
//! See there for original Copyright notice
|
||
|
|
||
|
use anyhow::Result;
|
||
|
|
||
|
use hyper::header::{HeaderMap, HeaderValue};
|
||
|
use hyper::{Body, Client, Request, Response, Uri};
|
||
|
use lazy_static::lazy_static;
|
||
|
use std::net::IpAddr;
|
||
|
use std::str::FromStr;
|
||
|
|
||
|
fn is_hop_header(name: &str) -> bool {
|
||
|
use unicase::Ascii;
|
||
|
|
||
|
// A list of the headers, using `unicase` to help us compare without
|
||
|
// worrying about the case, and `lazy_static!` to prevent reallocation
|
||
|
// of the vector.
|
||
|
lazy_static! {
|
||
|
static ref HOP_HEADERS: Vec<Ascii<&'static str>> = vec![
|
||
|
Ascii::new("Connection"),
|
||
|
Ascii::new("Keep-Alive"),
|
||
|
Ascii::new("Proxy-Authenticate"),
|
||
|
Ascii::new("Proxy-Authorization"),
|
||
|
Ascii::new("Te"),
|
||
|
Ascii::new("Trailers"),
|
||
|
Ascii::new("Transfer-Encoding"),
|
||
|
Ascii::new("Upgrade"),
|
||
|
];
|
||
|
}
|
||
|
|
||
|
HOP_HEADERS.iter().any(|h| h == &name)
|
||
|
}
|
||
|
|
||
|
/// Returns a clone of the headers without the [hop-by-hop headers].
|
||
|
///
|
||
|
/// [hop-by-hop headers]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
||
|
fn remove_hop_headers(headers: &HeaderMap<HeaderValue>) -> HeaderMap<HeaderValue> {
|
||
|
let mut result = HeaderMap::new();
|
||
|
for (k, v) in headers.iter() {
|
||
|
if !is_hop_header(k.as_str()) {
|
||
|
result.insert(k.clone(), v.clone());
|
||
|
}
|
||
|
}
|
||
|
result
|
||
|
}
|
||
|
|
||
|
fn create_proxied_response<B>(mut response: Response<B>) -> Response<B> {
|
||
|
*response.headers_mut() = remove_hop_headers(response.headers());
|
||
|
response
|
||
|
}
|
||
|
|
||
|
fn forward_uri<B>(forward_url: &str, req: &Request<B>) -> Result<Uri> {
|
||
|
let forward_uri = match req.uri().query() {
|
||
|
Some(query) => format!("{}{}?{}", forward_url, req.uri().path(), query),
|
||
|
None => format!("{}{}", forward_url, req.uri().path()),
|
||
|
};
|
||
|
|
||
|
Ok(Uri::from_str(forward_uri.as_str())?)
|
||
|
}
|
||
|
|
||
|
fn create_proxied_request<B>(
|
||
|
client_ip: IpAddr,
|
||
|
forward_url: &str,
|
||
|
request: Request<B>,
|
||
|
) -> Result<Request<B>> {
|
||
|
let mut builder = Request::builder().uri(forward_uri(forward_url, &request)?);
|
||
|
|
||
|
*builder.headers_mut().unwrap() = remove_hop_headers(request.headers());
|
||
|
|
||
|
let host_header_name = "host";
|
||
|
let x_forwarded_for_header_name = "x-forwarded-for";
|
||
|
|
||
|
// If request does not have host header, add it from original URI authority
|
||
|
if let Some(authority) = request.uri().authority() {
|
||
|
if let hyper::header::Entry::Vacant(entry) = builder
|
||
|
.headers_mut()
|
||
|
.unwrap()
|
||
|
.entry(host_header_name)
|
||
|
{
|
||
|
entry.insert(authority.as_str().parse()?);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add forwarding information in the headers
|
||
|
match builder
|
||
|
.headers_mut()
|
||
|
.unwrap()
|
||
|
.entry(x_forwarded_for_header_name)
|
||
|
{
|
||
|
hyper::header::Entry::Vacant(entry) => {
|
||
|
entry.insert(client_ip.to_string().parse()?);
|
||
|
}
|
||
|
|
||
|
hyper::header::Entry::Occupied(mut entry) => {
|
||
|
let addr = format!("{}, {}", entry.get().to_str()?, client_ip);
|
||
|
entry.insert(addr.parse()?);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ok(builder.body(request.into_body())?)
|
||
|
}
|
||
|
|
||
|
pub async fn call(
|
||
|
client_ip: IpAddr,
|
||
|
forward_uri: &str,
|
||
|
request: Request<Body>,
|
||
|
) -> Result<Response<Body>> {
|
||
|
let proxied_request = create_proxied_request(client_ip, &forward_uri, request)?;
|
||
|
|
||
|
let client = Client::new();
|
||
|
let response = client.request(proxied_request).await?;
|
||
|
let proxied_response = create_proxied_response(response);
|
||
|
Ok(proxied_response)
|
||
|
}
|