refactor: initial implementation of separeted lib and bin
This commit is contained in:
parent
ec85b0bb28
commit
13e82035a8
37 changed files with 225 additions and 157 deletions
49
rpxy-bin/Cargo.toml
Normal file
49
rpxy-bin/Cargo.toml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
[package]
|
||||
name = "rpxy"
|
||||
version = "0.4.0"
|
||||
authors = ["Jun Kurihara"]
|
||||
homepage = "https://github.com/junkurihara/rust-rpxy"
|
||||
repository = "https://github.com/junkurihara/rust-rpxy"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
rpxy-lib = { path = "../rpxy-lib/", features = ["http3", "sticky-cookie"] }
|
||||
|
||||
anyhow = "1.0.72"
|
||||
rustc-hash = "1.1.0"
|
||||
serde = { version = "1.0.174", default-features = false, features = ["derive"] }
|
||||
derive_builder = "0.12.0"
|
||||
tokio = { version = "1.29.1", default-features = false, features = [
|
||||
"net",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
"sync",
|
||||
"macros",
|
||||
] }
|
||||
async-trait = "0.1.72"
|
||||
|
||||
# config
|
||||
clap = { version = "4.3.17", features = ["std", "cargo", "wrap_help"] }
|
||||
toml = { version = "0.7.6", default-features = false, features = ["parse"] }
|
||||
|
||||
# reloading certs
|
||||
hot_reload = "0.1.2"
|
||||
rustls-pemfile = "1.0.3"
|
||||
|
||||
# logging
|
||||
tracing = { version = "0.1.37" }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = "0.5.0"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
185
rpxy-bin/src/cert_file_reader.rs
Normal file
185
rpxy-bin/src/cert_file_reader.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
use crate::log::*;
|
||||
use async_trait::async_trait;
|
||||
use derive_builder::Builder;
|
||||
use rpxy_lib::{
|
||||
reexports::{Certificate, PrivateKey},
|
||||
CertsAndKeys, CryptoSource,
|
||||
};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufReader, Cursor, Read},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[derive(Builder, Debug, Clone)]
|
||||
/// Crypto-related file reader implementing certs::CryptoRead trait
|
||||
pub struct CryptoFileSource {
|
||||
#[builder(setter(custom))]
|
||||
/// Always exist
|
||||
pub tls_cert_path: PathBuf,
|
||||
|
||||
#[builder(setter(custom))]
|
||||
/// Always exist
|
||||
pub tls_cert_key_path: PathBuf,
|
||||
|
||||
#[builder(setter(custom), default)]
|
||||
/// This may not exist
|
||||
pub client_ca_cert_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl CryptoFileSourceBuilder {
|
||||
pub fn tls_cert_path(&mut self, v: &str) -> &mut Self {
|
||||
self.tls_cert_path = Some(PathBuf::from(v));
|
||||
self
|
||||
}
|
||||
pub fn tls_cert_key_path(&mut self, v: &str) -> &mut Self {
|
||||
self.tls_cert_key_path = Some(PathBuf::from(v));
|
||||
self
|
||||
}
|
||||
pub fn client_ca_cert_path(&mut self, v: &Option<String>) -> &mut Self {
|
||||
self.client_ca_cert_path = Some(v.to_owned().as_ref().map(PathBuf::from));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CryptoSource for CryptoFileSource {
|
||||
type Error = io::Error;
|
||||
/// read crypto materials from source
|
||||
async fn read(&self) -> Result<CertsAndKeys, Self::Error> {
|
||||
read_certs_and_keys(
|
||||
&self.tls_cert_path,
|
||||
&self.tls_cert_key_path,
|
||||
self.client_ca_cert_path.as_ref(),
|
||||
)
|
||||
}
|
||||
/// Returns true when mutual tls is enabled
|
||||
fn is_mutual_tls(&self) -> bool {
|
||||
self.client_ca_cert_path.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Read certificates and private keys from file
|
||||
fn read_certs_and_keys(
|
||||
cert_path: &PathBuf,
|
||||
cert_key_path: &PathBuf,
|
||||
client_ca_cert_path: Option<&PathBuf>,
|
||||
) -> Result<CertsAndKeys, io::Error> {
|
||||
debug!("Read TLS server certificates and private key");
|
||||
|
||||
let certs: Vec<_> = {
|
||||
let certs_path_str = cert_path.display().to_string();
|
||||
let mut reader = BufReader::new(File::open(cert_path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the certificates [{certs_path_str}]: {e}"),
|
||||
)
|
||||
})?);
|
||||
rustls_pemfile::certs(&mut reader)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Unable to parse the certificates"))?
|
||||
}
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
|
||||
let cert_keys: Vec<_> = {
|
||||
let cert_key_path_str = cert_key_path.display().to_string();
|
||||
let encoded_keys = {
|
||||
let mut encoded_keys = vec![];
|
||||
File::open(cert_key_path)
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the certificate keys [{cert_key_path_str}]: {e}"),
|
||||
)
|
||||
})?
|
||||
.read_to_end(&mut encoded_keys)?;
|
||||
encoded_keys
|
||||
};
|
||||
let mut reader = Cursor::new(encoded_keys);
|
||||
let pkcs8_keys = rustls_pemfile::pkcs8_private_keys(&mut reader).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unable to parse the certificates private keys (PKCS8)",
|
||||
)
|
||||
})?;
|
||||
reader.set_position(0);
|
||||
let mut rsa_keys = rustls_pemfile::rsa_private_keys(&mut reader)?;
|
||||
let mut keys = pkcs8_keys;
|
||||
keys.append(&mut rsa_keys);
|
||||
if keys.is_empty() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"No private keys found - Make sure that they are in PKCS#8/PEM format",
|
||||
));
|
||||
}
|
||||
keys.drain(..).map(PrivateKey).collect()
|
||||
};
|
||||
|
||||
let client_ca_certs = if let Some(path) = client_ca_cert_path {
|
||||
debug!("Read CA certificates for client authentication");
|
||||
// Reads client certificate and returns client
|
||||
let certs: Vec<_> = {
|
||||
let certs_path_str = path.display().to_string();
|
||||
let mut reader = BufReader::new(File::open(path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the client certificates [{certs_path_str}]: {e}"),
|
||||
)
|
||||
})?);
|
||||
rustls_pemfile::certs(&mut reader)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Unable to parse the client certificates"))?
|
||||
}
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
Some(certs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(CertsAndKeys {
|
||||
certs,
|
||||
cert_keys,
|
||||
client_ca_certs,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn read_server_crt_key_files() {
|
||||
let tls_cert_path = "example-certs/server.crt";
|
||||
let tls_cert_key_path = "example-certs/server.key";
|
||||
let crypto_file_source = CryptoFileSourceBuilder::default()
|
||||
.tls_cert_key_path(tls_cert_key_path)
|
||||
.tls_cert_path(tls_cert_path)
|
||||
.build();
|
||||
assert!(crypto_file_source.is_ok());
|
||||
|
||||
let crypto_file_source = crypto_file_source.unwrap();
|
||||
let crypto_elem = crypto_file_source.read().await;
|
||||
assert!(crypto_elem.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_server_crt_key_files_with_client_ca_crt() {
|
||||
let tls_cert_path = "example-certs/server.crt";
|
||||
let tls_cert_key_path = "example-certs/server.key";
|
||||
let client_ca_cert_path = Some("example-certs/client.ca.crt".to_string());
|
||||
let crypto_file_source = CryptoFileSourceBuilder::default()
|
||||
.tls_cert_key_path(tls_cert_key_path)
|
||||
.tls_cert_path(tls_cert_path)
|
||||
.client_ca_cert_path(&client_ca_cert_path)
|
||||
.build();
|
||||
assert!(crypto_file_source.is_ok());
|
||||
|
||||
let crypto_file_source = crypto_file_source.unwrap();
|
||||
let crypto_elem = crypto_file_source.read().await;
|
||||
assert!(crypto_elem.is_ok());
|
||||
|
||||
let crypto_elem = crypto_elem.unwrap();
|
||||
assert!(crypto_elem.client_ca_certs.is_some());
|
||||
}
|
||||
}
|
||||
4
rpxy-bin/src/config/mod.rs
Normal file
4
rpxy-bin/src/config/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
mod parse;
|
||||
mod toml;
|
||||
|
||||
pub use parse::build_globals;
|
||||
114
rpxy-bin/src/config/parse.rs
Normal file
114
rpxy-bin/src/config/parse.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use super::toml::ConfigToml;
|
||||
use crate::{
|
||||
cert_file_reader::CryptoFileSource,
|
||||
error::{anyhow, ensure},
|
||||
log::*,
|
||||
};
|
||||
use clap::Arg;
|
||||
use rpxy_lib::{Backends, BytesName, Globals, ProxyConfig};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
pub fn build_globals(runtime_handle: Handle) -> std::result::Result<Globals<CryptoFileSource>, anyhow::Error> {
|
||||
let _ = include_str!("../../Cargo.toml");
|
||||
let options = clap::command!().arg(
|
||||
Arg::new("config_file")
|
||||
.long("config")
|
||||
.short('c')
|
||||
.value_name("FILE")
|
||||
.help("Configuration file path like \"./config.toml\""),
|
||||
);
|
||||
let matches = options.get_matches();
|
||||
|
||||
///////////////////////////////////
|
||||
let config = if let Some(config_file_path) = matches.get_one::<String>("config_file") {
|
||||
ConfigToml::new(config_file_path)?
|
||||
} else {
|
||||
// Default config Toml
|
||||
ConfigToml::default()
|
||||
};
|
||||
|
||||
///////////////////////////////////
|
||||
// build proxy config
|
||||
let proxy_config: ProxyConfig = (&config).try_into()?;
|
||||
// For loggings
|
||||
if proxy_config.listen_sockets.iter().any(|addr| addr.is_ipv6()) {
|
||||
info!("Listen both IPv4 and IPv6")
|
||||
} else {
|
||||
info!("Listen IPv4")
|
||||
}
|
||||
if proxy_config.http_port.is_some() {
|
||||
info!("Listen port: {}", proxy_config.http_port.unwrap());
|
||||
}
|
||||
if proxy_config.https_port.is_some() {
|
||||
info!("Listen port: {} (for TLS)", proxy_config.https_port.unwrap());
|
||||
}
|
||||
if proxy_config.http3 {
|
||||
info!("Experimental HTTP/3.0 is enabled. Note it is still very unstable.");
|
||||
}
|
||||
if !proxy_config.sni_consistency {
|
||||
info!("Ignore consistency between TLS SNI and Host header (or Request line). Note it violates RFC.");
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
// backend_apps
|
||||
let apps = config.apps.ok_or(anyhow!("Missing application spec"))?;
|
||||
|
||||
// assertions for all backend apps
|
||||
ensure!(!apps.0.is_empty(), "Wrong application spec.");
|
||||
// if only https_port is specified, tls must be configured for all apps
|
||||
if proxy_config.http_port.is_none() {
|
||||
ensure!(
|
||||
apps.0.iter().all(|(_, app)| app.tls.is_some()),
|
||||
"Some apps serves only plaintext HTTP"
|
||||
);
|
||||
}
|
||||
// https redirection can be configured if both ports are active
|
||||
if !(proxy_config.https_port.is_some() && proxy_config.http_port.is_some()) {
|
||||
ensure!(
|
||||
apps.0.iter().all(|(_, app)| {
|
||||
if let Some(tls) = app.tls.as_ref() {
|
||||
tls.https_redirection.is_none()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}),
|
||||
"https_redirection can be specified only when both http_port and https_port are specified"
|
||||
);
|
||||
}
|
||||
|
||||
// build backends
|
||||
let mut backends = Backends::new();
|
||||
for (app_name, app) in apps.0.iter() {
|
||||
let server_name_string = app.server_name.as_ref().ok_or(anyhow!("No server name"))?;
|
||||
let backend = app.try_into()?;
|
||||
backends.apps.insert(server_name_string.to_server_name_vec(), backend);
|
||||
info!("Registering application: {} ({})", app_name, server_name_string);
|
||||
}
|
||||
|
||||
// default backend application for plaintext http requests
|
||||
if let Some(d) = config.default_app {
|
||||
let d_sn: Vec<&str> = backends
|
||||
.apps
|
||||
.iter()
|
||||
.filter(|(_k, v)| v.app_name == d)
|
||||
.map(|(_, v)| v.server_name.as_ref())
|
||||
.collect();
|
||||
if !d_sn.is_empty() {
|
||||
info!(
|
||||
"Serving plaintext http for requests to unconfigured server_name by app {} (server_name: {}).",
|
||||
d, d_sn[0]
|
||||
);
|
||||
backends.default_server_name_bytes = Some(d_sn[0].to_server_name_vec());
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
let globals = Globals {
|
||||
proxy_config,
|
||||
backends,
|
||||
request_count: Default::default(),
|
||||
runtime_handle,
|
||||
};
|
||||
|
||||
Ok(globals)
|
||||
}
|
||||
272
rpxy-bin/src/config/toml.rs
Normal file
272
rpxy-bin/src/config/toml.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
use crate::{
|
||||
cert_file_reader::{CryptoFileSource, CryptoFileSourceBuilder},
|
||||
constants::*,
|
||||
error::{anyhow, ensure},
|
||||
};
|
||||
use rpxy_lib::{
|
||||
reexports::Uri, Backend, BackendBuilder, PathNameBytesExp, ProxyConfig, ReverseProxy, Upstream, UpstreamGroup,
|
||||
UpstreamGroupBuilder, UpstreamOption,
|
||||
};
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use serde::Deserialize;
|
||||
use std::{fs, net::SocketAddr};
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct ConfigToml {
|
||||
pub listen_port: Option<u16>,
|
||||
pub listen_port_tls: Option<u16>,
|
||||
pub listen_ipv6: Option<bool>,
|
||||
pub max_concurrent_streams: Option<u32>,
|
||||
pub max_clients: Option<u32>,
|
||||
pub apps: Option<Apps>,
|
||||
pub default_app: Option<String>,
|
||||
pub experimental: Option<Experimental>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "http3")]
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Http3Option {
|
||||
pub alt_svc_max_age: Option<u32>,
|
||||
pub request_max_body_size: Option<usize>,
|
||||
pub max_concurrent_connections: Option<u32>,
|
||||
pub max_concurrent_bidistream: Option<u32>,
|
||||
pub max_concurrent_unistream: Option<u32>,
|
||||
pub max_idle_timeout: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Experimental {
|
||||
#[cfg(feature = "http3")]
|
||||
pub h3: Option<Http3Option>,
|
||||
pub ignore_sni_consistency: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Apps(pub HashMap<String, Application>);
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Application {
|
||||
pub server_name: Option<String>,
|
||||
pub reverse_proxy: Option<Vec<ReverseProxyOption>>,
|
||||
pub tls: Option<TlsOption>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct TlsOption {
|
||||
pub tls_cert_path: Option<String>,
|
||||
pub tls_cert_key_path: Option<String>,
|
||||
pub https_redirection: Option<bool>,
|
||||
pub client_ca_cert_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct ReverseProxyOption {
|
||||
pub path: Option<String>,
|
||||
pub replace_path: Option<String>,
|
||||
pub upstream: Vec<UpstreamParams>,
|
||||
pub upstream_options: Option<Vec<String>>,
|
||||
pub load_balance: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct UpstreamParams {
|
||||
pub location: String,
|
||||
pub tls: Option<bool>,
|
||||
}
|
||||
|
||||
impl TryInto<ProxyConfig> for &ConfigToml {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<ProxyConfig, Self::Error> {
|
||||
let mut proxy_config = ProxyConfig {
|
||||
// listen port and socket
|
||||
http_port: self.listen_port,
|
||||
https_port: self.listen_port_tls,
|
||||
..Default::default()
|
||||
};
|
||||
ensure!(
|
||||
proxy_config.http_port.is_some() || proxy_config.https_port.is_some(),
|
||||
anyhow!("Either/Both of http_port or https_port must be specified")
|
||||
);
|
||||
if proxy_config.http_port.is_some() && proxy_config.https_port.is_some() {
|
||||
ensure!(
|
||||
proxy_config.http_port.unwrap() != proxy_config.https_port.unwrap(),
|
||||
anyhow!("http_port and https_port must be different")
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: when [::]:xx is bound, both v4 and v6 listeners are enabled.
|
||||
let listen_addresses: Vec<&str> = if let Some(true) = self.listen_ipv6 {
|
||||
LISTEN_ADDRESSES_V6.to_vec()
|
||||
} else {
|
||||
LISTEN_ADDRESSES_V4.to_vec()
|
||||
};
|
||||
proxy_config.listen_sockets = listen_addresses
|
||||
.iter()
|
||||
.flat_map(|addr| {
|
||||
let mut v: Vec<SocketAddr> = vec![];
|
||||
if let Some(port) = proxy_config.http_port {
|
||||
v.push(format!("{addr}:{port}").parse().unwrap());
|
||||
}
|
||||
if let Some(port) = proxy_config.https_port {
|
||||
v.push(format!("{addr}:{port}").parse().unwrap());
|
||||
}
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
// max values
|
||||
if let Some(c) = self.max_clients {
|
||||
proxy_config.max_clients = c as usize;
|
||||
}
|
||||
if let Some(c) = self.max_concurrent_streams {
|
||||
proxy_config.max_concurrent_streams = c;
|
||||
}
|
||||
|
||||
// experimental
|
||||
if let Some(exp) = &self.experimental {
|
||||
#[cfg(feature = "http3")]
|
||||
{
|
||||
if let Some(h3option) = &exp.h3 {
|
||||
proxy_config.http3 = true;
|
||||
if let Some(x) = h3option.alt_svc_max_age {
|
||||
proxy_config.h3_alt_svc_max_age = x;
|
||||
}
|
||||
if let Some(x) = h3option.request_max_body_size {
|
||||
proxy_config.h3_request_max_body_size = x;
|
||||
}
|
||||
if let Some(x) = h3option.max_concurrent_connections {
|
||||
proxy_config.h3_max_concurrent_connections = x;
|
||||
}
|
||||
if let Some(x) = h3option.max_concurrent_bidistream {
|
||||
proxy_config.h3_max_concurrent_bidistream = x.into();
|
||||
}
|
||||
if let Some(x) = h3option.max_concurrent_unistream {
|
||||
proxy_config.h3_max_concurrent_unistream = x.into();
|
||||
}
|
||||
if let Some(x) = h3option.max_idle_timeout {
|
||||
if x == 0u64 {
|
||||
proxy_config.h3_max_idle_timeout = None;
|
||||
} else {
|
||||
proxy_config.h3_max_idle_timeout =
|
||||
Some(quinn::IdleTimeout::try_from(tokio::time::Duration::from_secs(x)).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ignore) = exp.ignore_sni_consistency {
|
||||
proxy_config.sni_consistency = !ignore;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(proxy_config)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigToml {
|
||||
pub fn new(config_file: &str) -> std::result::Result<Self, anyhow::Error> {
|
||||
let config_str = fs::read_to_string(config_file)?;
|
||||
|
||||
toml::from_str(&config_str).map_err(|e| anyhow!(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<Backend<CryptoFileSource>> for &Application {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<Backend<CryptoFileSource>, Self::Error> {
|
||||
let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?;
|
||||
|
||||
// backend builder
|
||||
let mut backend_builder = BackendBuilder::default();
|
||||
// reverse proxy settings
|
||||
let reverse_proxy = self.try_into()?;
|
||||
|
||||
backend_builder
|
||||
.app_name(server_name_string)
|
||||
.server_name(server_name_string)
|
||||
.reverse_proxy(reverse_proxy);
|
||||
|
||||
// TLS settings and build backend instance
|
||||
let backend = if self.tls.is_none() {
|
||||
backend_builder.build()?
|
||||
} else {
|
||||
let tls = self.tls.as_ref().unwrap();
|
||||
ensure!(tls.tls_cert_key_path.is_some() && tls.tls_cert_path.is_some());
|
||||
|
||||
let https_redirection = if tls.https_redirection.is_none() {
|
||||
Some(true) // Default true
|
||||
} else {
|
||||
tls.https_redirection
|
||||
};
|
||||
|
||||
let crypto_source = CryptoFileSourceBuilder::default()
|
||||
.tls_cert_path(tls.tls_cert_path.as_ref().unwrap())
|
||||
.tls_cert_key_path(tls.tls_cert_key_path.as_ref().unwrap())
|
||||
.client_ca_cert_path(&tls.client_ca_cert_path)
|
||||
.build()?;
|
||||
|
||||
backend_builder
|
||||
.https_redirection(https_redirection)
|
||||
.crypto_source(Some(crypto_source))
|
||||
.build()?
|
||||
};
|
||||
Ok(backend)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<ReverseProxy> for &Application {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<ReverseProxy, Self::Error> {
|
||||
let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?;
|
||||
let rp_settings = self.reverse_proxy.as_ref().ok_or(anyhow!("Missing reverse_proxy"))?;
|
||||
|
||||
let mut upstream: HashMap<PathNameBytesExp, UpstreamGroup> = HashMap::default();
|
||||
|
||||
rp_settings.iter().for_each(|rpo| {
|
||||
let upstream_vec: Vec<Upstream> = rpo.upstream.iter().map(|x| x.try_into().unwrap()).collect();
|
||||
// let upstream_iter = rpo.upstream.iter().map(|x| x.to_upstream().unwrap());
|
||||
// let lb_upstream_num = vec_upstream.len();
|
||||
let elem = UpstreamGroupBuilder::default()
|
||||
.upstream(&upstream_vec)
|
||||
.path(&rpo.path)
|
||||
.replace_path(&rpo.replace_path)
|
||||
.lb(&rpo.load_balance, &upstream_vec, server_name_string, &rpo.path)
|
||||
.opts(&rpo.upstream_options)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
upstream.insert(elem.path.clone(), elem);
|
||||
});
|
||||
ensure!(
|
||||
rp_settings.iter().filter(|rpo| rpo.path.is_none()).count() < 2,
|
||||
"Multiple default reverse proxy setting"
|
||||
);
|
||||
ensure!(
|
||||
upstream
|
||||
.iter()
|
||||
.all(|(_, elem)| !(elem.opts.contains(&UpstreamOption::ConvertHttpsTo11)
|
||||
&& elem.opts.contains(&UpstreamOption::ConvertHttpsTo2))),
|
||||
"either one of force_http11 or force_http2 can be enabled"
|
||||
);
|
||||
|
||||
Ok(ReverseProxy { upstream })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<Upstream> for &UpstreamParams {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<Upstream, Self::Error> {
|
||||
let scheme = match self.tls {
|
||||
Some(true) => "https",
|
||||
_ => "http",
|
||||
};
|
||||
let location = format!("{}://{}", scheme, self.location);
|
||||
Ok(Upstream {
|
||||
uri: location.parse::<Uri>().map_err(|e| anyhow!("{}", e))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
2
rpxy-bin/src/constants.rs
Normal file
2
rpxy-bin/src/constants.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"];
|
||||
pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"];
|
||||
1
rpxy-bin/src/error.rs
Normal file
1
rpxy-bin/src/error.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub use anyhow::{anyhow, bail, ensure, Context};
|
||||
24
rpxy-bin/src/log.rs
Normal file
24
rpxy-bin/src/log.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
pub use tracing::{debug, error, info, warn};
|
||||
|
||||
pub fn init_logger() {
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
let format_layer = fmt::layer()
|
||||
.with_line_number(false)
|
||||
.with_thread_ids(false)
|
||||
.with_target(false)
|
||||
.with_thread_names(true)
|
||||
.with_target(true)
|
||||
.with_level(true)
|
||||
.compact();
|
||||
|
||||
// This limits the logger to emits only rpxy crate
|
||||
let level_string = std::env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_| "info".to_string());
|
||||
let filter_layer = EnvFilter::new(format!("{}={}", env!("CARGO_PKG_NAME"), level_string));
|
||||
// let filter_layer = EnvFilter::from_default_env();
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(format_layer)
|
||||
.with(filter_layer)
|
||||
.init();
|
||||
}
|
||||
38
rpxy-bin/src/main.rs
Normal file
38
rpxy-bin/src/main.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#[cfg(not(target_env = "msvc"))]
|
||||
use tikv_jemallocator::Jemalloc;
|
||||
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
#[global_allocator]
|
||||
static GLOBAL: Jemalloc = Jemalloc;
|
||||
|
||||
mod cert_file_reader;
|
||||
mod config;
|
||||
mod constants;
|
||||
mod error;
|
||||
mod log;
|
||||
|
||||
use crate::{cert_file_reader::CryptoFileSource, config::build_globals, log::*};
|
||||
use rpxy_lib::{entrypoint, Globals};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn main() {
|
||||
init_logger();
|
||||
|
||||
let mut runtime_builder = tokio::runtime::Builder::new_multi_thread();
|
||||
runtime_builder.enable_all();
|
||||
runtime_builder.thread_name("rpxy");
|
||||
let runtime = runtime_builder.build().unwrap();
|
||||
|
||||
runtime.block_on(async {
|
||||
let globals: Globals<CryptoFileSource> = match build_globals(runtime.handle().clone()) {
|
||||
Ok(g) => g,
|
||||
Err(e) => {
|
||||
error!("Invalid configuration: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
entrypoint(Arc::new(globals)).await.unwrap()
|
||||
});
|
||||
warn!("rpxy exited!");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue