Implement proxying and redirection
This commit is contained in:
parent
9f8ec29e90
commit
ccb4d96da7
1 changed files with 150 additions and 40 deletions
190
src/main.rs
190
src/main.rs
|
@ -1,7 +1,5 @@
|
||||||
use std::{net::SocketAddr, convert::Infallible};
|
use std::net::SocketAddr;
|
||||||
|
use hyper::{service::{make_service_fn, service_fn}, Client, Error, Server, Response, StatusCode, Body, Request, Uri};
|
||||||
use hyper::{Client, Server, service::{make_service_fn, service_fn}, body::HttpBody, Body, Response, Request, Method, http, upgrade::{self, Upgraded}};
|
|
||||||
use tokio::net::TcpStream;
|
|
||||||
|
|
||||||
// https://github.com/hyperium/hyper/blob/master/examples/gateway.rs
|
// https://github.com/hyperium/hyper/blob/master/examples/gateway.rs
|
||||||
// https://en.wikipedia.org/wiki/Gateway_(telecommunications)
|
// https://en.wikipedia.org/wiki/Gateway_(telecommunications)
|
||||||
|
@ -13,12 +11,12 @@ async fn main() {
|
||||||
let config = {
|
let config = {
|
||||||
|
|
||||||
// Prefer ./config over /etc/proxima
|
// Prefer ./config over /etc/proxima
|
||||||
let file = unsafe {
|
let file = {
|
||||||
["./config", "/etc/proxima"]
|
["./config", "/etc/proxima"]
|
||||||
.iter()
|
.iter()
|
||||||
.map(load)
|
.map(load)
|
||||||
.reduce(Result::or)
|
.reduce(Result::or)
|
||||||
.unwrap_unchecked()
|
.expect("Impossible")
|
||||||
};
|
};
|
||||||
|
|
||||||
file.map(parse)
|
file.map(parse)
|
||||||
|
@ -27,8 +25,68 @@ async fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8100));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 8100));
|
||||||
|
|
||||||
|
let client = Client::new();
|
||||||
|
|
||||||
|
let make_service = make_service_fn(move |_| {
|
||||||
|
let client = client.clone();
|
||||||
|
let config = config.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
// This is the `Service` that will handle the connection.
|
||||||
|
// `service_fn` is a helper to convert a function that
|
||||||
|
// returns a Response into a `Service`.
|
||||||
|
Ok::<_, Error>(service_fn(move |mut req| {
|
||||||
|
let config = config.clone();
|
||||||
|
let client = client.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
for Rule (pattern, effect) in config.rules() {
|
||||||
|
println!("{} {}", req.method(), req.uri());
|
||||||
|
if pattern.matches(&req) {
|
||||||
|
return match effect {
|
||||||
|
Effect::Proxy { port, .. } => {
|
||||||
|
let host = "0.0.0.0"; // Support for custom hosts added later
|
||||||
|
let path = req.uri().path_and_query().map(|x| x.as_str()).unwrap_or("");
|
||||||
|
let target = format!("http://{host}:{port}{path}");
|
||||||
|
|
||||||
|
let uri = target.parse().unwrap();
|
||||||
|
*req.uri_mut() = uri;
|
||||||
|
|
||||||
|
println!("Proxying to {target}");
|
||||||
|
|
||||||
|
client.request(req).await
|
||||||
|
},
|
||||||
|
Effect::Redirect (uri) => Ok ({
|
||||||
|
println!("Redirecting to {uri}");
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::PERMANENT_REDIRECT)
|
||||||
|
.header("Location", uri)
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok (Response::builder()
|
||||||
|
.status(StatusCode::BAD_REQUEST)
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let server = Server::bind(&addr).serve(make_service);
|
||||||
|
|
||||||
|
if let Err(e) = server.await {
|
||||||
|
eprintln!("server error: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct Rule (Pattern, Effect);
|
pub struct Rule (Pattern, Effect);
|
||||||
|
|
||||||
impl Rule {
|
impl Rule {
|
||||||
|
@ -47,11 +105,55 @@ impl Rule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct Pattern {
|
pub struct Pattern {
|
||||||
domain: String,
|
domain: String,
|
||||||
ports: Ports,
|
ports: Ports,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pattern {
|
||||||
|
pub fn matches <T> (&self, req: &Request<T>) -> bool {
|
||||||
|
let uri = req.uri();
|
||||||
|
let (host, port) = {
|
||||||
|
let host = req
|
||||||
|
.headers()
|
||||||
|
.get("host")
|
||||||
|
.and_then(|x| x.to_str().ok())
|
||||||
|
.and_then(|x| x.parse::<Uri>().ok());
|
||||||
|
|
||||||
|
let h = uri
|
||||||
|
.host()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.or_else(|| {
|
||||||
|
host.clone().and_then(|x| {
|
||||||
|
x.host().map(|x| x.to_string())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let p = uri
|
||||||
|
.port_u16()
|
||||||
|
.or_else(|| {
|
||||||
|
host.and_then(|x| x.port_u16())
|
||||||
|
});
|
||||||
|
|
||||||
|
(h, p)
|
||||||
|
};
|
||||||
|
|
||||||
|
match host {
|
||||||
|
Some (h) if &h == &self.domain => match &self.ports {
|
||||||
|
Ports::Any => true,
|
||||||
|
spec => match port {
|
||||||
|
Some (p) => spec.includes(p),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub enum Effect {
|
pub enum Effect {
|
||||||
Redirect (String),
|
Redirect (String),
|
||||||
Proxy {
|
Proxy {
|
||||||
|
@ -61,11 +163,12 @@ pub enum Effect {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Effect {
|
impl Effect {
|
||||||
pub async fn perform (&self) -> std::io::Result<()> {
|
pub async fn perform (&self) -> Response<Body> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub enum Ports {
|
pub enum Ports {
|
||||||
Single (u16),
|
Single (u16),
|
||||||
Either (Vec<u16>),
|
Either (Vec<u16>),
|
||||||
|
@ -83,6 +186,45 @@ impl Ports {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A config consists of a set if [`Rule`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Config (Vec<Rule>);
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn rules (&self) -> impl Iterator <Item = &Rule> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a config from a path.
|
||||||
|
pub fn load (p: impl AsRef<std::path::Path>) -> std::io::Result<String> {
|
||||||
|
std::fs::read_to_string(p.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a config string.
|
||||||
|
///
|
||||||
|
/// Example config string:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// hmt.riley.lgbt : (80 | 443) --> 6000 # --> is proxy_pass
|
||||||
|
/// riley.lgbt : (80 | 443) --> 3000 [ssl] # add [ssl] to automate ssl for this domain
|
||||||
|
/// rly.cx : any ==> riley.lgbt # ==> is HTTP redirect
|
||||||
|
/// ```
|
||||||
|
pub fn parse (data: String) -> Config {
|
||||||
|
let rules = data
|
||||||
|
.lines()
|
||||||
|
.map(parse::rule)
|
||||||
|
.filter_map(|x| match x {
|
||||||
|
Ok ((_, rule)) => Some (rule),
|
||||||
|
Err (e) => {
|
||||||
|
eprintln!("Error parsing rule: {:?}", e);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Config (rules)
|
||||||
|
}
|
||||||
pub mod parse {
|
pub mod parse {
|
||||||
|
|
||||||
use super::{ Ports, Effect, Pattern, Rule };
|
use super::{ Ports, Effect, Pattern, Rule };
|
||||||
|
@ -176,7 +318,7 @@ pub mod parse {
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn pattern (s: &str) -> PResult<'_, Pattern> {
|
pub fn pattern (s: &str) -> PResult<'_, Pattern> {
|
||||||
let spaced = |x| seq::delimited(chr::char(' '), x, chr::char(' '));
|
let spaced = |x| seq::delimited(chr::space1, x, chr::space1);
|
||||||
seq::separated_pair(domain, spaced(chr::char(':')), portspec)
|
seq::separated_pair(domain, spaced(chr::char(':')), portspec)
|
||||||
.map(|(domain, ports)| Pattern { domain, ports })
|
.map(|(domain, ports)| Pattern { domain, ports })
|
||||||
.parse(s)
|
.parse(s)
|
||||||
|
@ -241,35 +383,3 @@ pub mod parse {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A config consists of a set if [`Rule`].
|
|
||||||
pub struct Config (Vec<Rule>);
|
|
||||||
|
|
||||||
/// Load a config from a path.
|
|
||||||
pub fn load (p: impl AsRef<std::path::Path>) -> std::io::Result<String> {
|
|
||||||
std::fs::read_to_string(p.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a config string.
|
|
||||||
///
|
|
||||||
/// Example config string:
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// hmt.riley.lgbt : (80 | 443) --> 6000 # --> is proxy_pass
|
|
||||||
/// riley.lgbt : (80 | 443) --> 3000 [ssl] # add [ssl] to automate ssl for this domain
|
|
||||||
/// rly.cx : any ==> riley.lgbt # ==> is HTTP redirect
|
|
||||||
/// ```
|
|
||||||
pub fn parse (data: String) -> Config {
|
|
||||||
let rules = data
|
|
||||||
.lines()
|
|
||||||
.map(parse::rule)
|
|
||||||
.filter_map(|x| match x {
|
|
||||||
Ok ((_, rule)) => Some (rule),
|
|
||||||
Err (e) => {
|
|
||||||
eprintln!("Error parsing rule: {:?}", e);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Config (rules)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue