aboutsummaryrefslogtreecommitdiff
path: root/src/reverse_proxy.rs
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2021-12-07 15:20:45 +0100
committerAlex Auvolat <alex@adnab.me>2021-12-07 15:20:45 +0100
commitcd7e5ad034b75d659d4d87a752ab7b11cf75de12 (patch)
tree32773f9758b33188402e137d435bdd61ce01b280 /src/reverse_proxy.rs
parent5535c4951a832d65755afa53822a36e96681320f (diff)
downloadtricot-cd7e5ad034b75d659d4d87a752ab7b11cf75de12.tar.gz
tricot-cd7e5ad034b75d659d4d87a752ab7b11cf75de12.zip
Got a reverse proxy
Diffstat (limited to 'src/reverse_proxy.rs')
-rw-r--r--src/reverse_proxy.rs114
1 files changed, 114 insertions, 0 deletions
diff --git a/src/reverse_proxy.rs b/src/reverse_proxy.rs
new file mode 100644
index 0000000..82533d8
--- /dev/null
+++ b/src/reverse_proxy.rs
@@ -0,0 +1,114 @@
+//! 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)
+}