httproxide 0.2.0

Rusted HTTP router reverse-proxy
Documentation
use anyhow::Context;
use client_util::{get_client, ClientConfig};
use http::Request;
use hyper::header::{Entry, HeaderValue, ToStrError, COOKIE};
use serde::{Deserialize, Serialize};

use crate::client_ip::GetClientIp;
use crate::target::{IntoTarget, ReqBody};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct ProxyPassConfig {
    target: String,
    #[serde(default)]
    client: ClientConfig,
}

pub fn from_config(config: ProxyPassConfig) -> anyhow::Result<impl IntoTarget> {
    let client = get_client(config.client.clone())?;
    Ok(tower::service_fn(move |mut request: Request<ReqBody>| {
        let client = client.clone();
        let config = config.clone();
        async move {
            let client_ip = request.client_ip();
            *request.version_mut() = http::Version::default();

            if let Entry::Occupied(mut entry) = request.headers_mut().entry(COOKIE) {
                if let Ok(new_val) = entry
                    .iter()
                    .map(|x| x.to_str())
                    .collect::<Result<Vec<_>, ToStrError>>()
                    .map(|x| x.join("; "))
                {
                    entry.insert(HeaderValue::from_str(&new_val).unwrap());
                }
            }

            let request_uri = request.uri().clone();
            Ok::<_, anyhow::Error>(
                crate::reverse_proxy::call(client_ip, &config.target, request, &client).await
                    .context(format!("proxying from {} to {}", &request_uri, &config.target))?
            )
        }
    }))
}