Skip to content

Commit

Permalink
wip: implemented backend
Browse files Browse the repository at this point in the history
  • Loading branch information
junkurihara committed Nov 24, 2023
1 parent de91c7a commit 3c6e4e5
Show file tree
Hide file tree
Showing 16 changed files with 1,173 additions and 23 deletions.
26 changes: 13 additions & 13 deletions rpxy-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ publish = false
default = ["http3-s2n", "sticky-cookie", "cache"]
http3-quinn = ["socket2"] #"quinn", "h3", "h3-quinn", ]
http3-s2n = [] #"h3", "s2n-quic", "s2n-quic-rustls", "s2n-quic-h3"]
sticky-cookie = [] #"base64", "sha2", "chrono"]
sticky-cookie = ["base64", "sha2", "chrono"]
cache = [] #"http-cache-semantics", "lru"]
native-roots = [] #"hyper-rustls/native-tokio"]

[dependencies]
# rand = "0.8.5"
# rustc-hash = "1.1.0"
rand = "0.8.5"
rustc-hash = "1.1.0"
# bytes = "1.5.0"
# derive_builder = "0.12.0"
derive_builder = "0.12.0"
futures = { version = "0.3.29", features = ["alloc", "async-await"] }
tokio = { version = "1.34.0", default-features = false, features = [
"net",
Expand All @@ -34,13 +34,13 @@ tokio = { version = "1.34.0", default-features = false, features = [
"fs",
] }
async-trait = "0.1.74"
# hot_reload = "0.1.4" # reloading certs

# Error handling
anyhow = "1.0.75"
thiserror = "1.0.50"

# http and tls
hot_reload = "0.1.4" # reloading certs
http = "1.0.0"
# http-body-util = "0.1.0"
hyper = { version = "1.0.1", default-features = false }
Expand Down Expand Up @@ -75,14 +75,14 @@ socket2 = { version = "0.5.5", features = ["all"], optional = true }
# http-cache-semantics = { path = "../submodules/rusty-http-cache-semantics/", optional = true }
# lru = { version = "0.12.0", optional = true }

# # cookie handling for sticky cookie
# chrono = { version = "0.4.31", default-features = false, features = [
# "unstable-locales",
# "alloc",
# "clock",
# ], optional = true }
# base64 = { version = "0.21.5", optional = true }
# sha2 = { version = "0.10.8", default-features = false, optional = true }
# cookie handling for sticky cookie
chrono = { version = "0.4.31", default-features = false, features = [
"unstable-locales",
"alloc",
"clock",
], optional = true }
base64 = { version = "0.21.5", optional = true }
sha2 = { version = "0.10.8", default-features = false, optional = true }


# [dev-dependencies]
Expand Down
136 changes: 136 additions & 0 deletions rpxy-lib/src/backend/backend_main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::{
certs::CryptoSource,
error::*,
log::*,
name_exp::{ByteName, ServerName},
AppConfig, AppConfigList,
};
use derive_builder::Builder;
use rustc_hash::FxHashMap as HashMap;
use std::borrow::Cow;

use super::upstream::PathManager;

/// Struct serving information to route incoming connections, like server name to be handled and tls certs/keys settings.
#[derive(Builder)]
pub struct BackendApp<T>
where
T: CryptoSource,
{
#[builder(setter(into))]
/// backend application name, e.g., app1
pub app_name: String,
#[builder(setter(custom))]
/// server name, e.g., example.com, in [[ServerName]] object
pub server_name: ServerName,
/// struct of reverse proxy serving incoming request
pub path_manager: PathManager,
/// tls settings: https redirection with 30x
#[builder(default)]
pub https_redirection: Option<bool>,
/// TLS settings: source meta for server cert, key, client ca cert
#[builder(default)]
pub crypto_source: Option<T>,
}
impl<'a, T> BackendAppBuilder<T>
where
T: CryptoSource,
{
pub fn server_name(&mut self, server_name: impl Into<Cow<'a, str>>) -> &mut Self {
self.server_name = Some(server_name.to_server_name());
self
}
}

/// HashMap and some meta information for multiple Backend structs.
pub struct BackendAppManager<T>
where
T: CryptoSource,
{
/// HashMap of Backend structs, key is server name
pub apps: HashMap<ServerName, BackendApp<T>>,
/// for plaintext http
pub default_server_name: Option<ServerName>,
}

impl<T> Default for BackendAppManager<T>
where
T: CryptoSource,
{
fn default() -> Self {
Self {
apps: HashMap::<ServerName, BackendApp<T>>::default(),
default_server_name: None,
}
}
}

impl<T> TryFrom<&AppConfig<T>> for BackendApp<T>
where
T: CryptoSource + Clone,
{
type Error = RpxyError;

fn try_from(app_config: &AppConfig<T>) -> Result<Self, Self::Error> {
let mut backend_builder = BackendAppBuilder::default();
let path_manager = PathManager::try_from(app_config)?;
backend_builder
.app_name(app_config.app_name.clone())
.server_name(app_config.server_name.clone())
.path_manager(path_manager);
// TLS settings and build backend instance
let backend = if app_config.tls.is_none() {
backend_builder.build()?
} else {
let tls = app_config.tls.as_ref().unwrap();
backend_builder
.https_redirection(Some(tls.https_redirection))
.crypto_source(Some(tls.inner.clone()))
.build()?
};
Ok(backend)
}
}

impl<T> TryFrom<&AppConfigList<T>> for BackendAppManager<T>
where
T: CryptoSource + Clone,
{
type Error = RpxyError;

fn try_from(config_list: &AppConfigList<T>) -> Result<Self, Self::Error> {
let mut manager = Self::default();
for app_config in config_list.inner.iter() {
let backend: BackendApp<T> = BackendApp::try_from(app_config)?;
manager
.apps
.insert(app_config.server_name.clone().to_server_name(), backend);

info!(
"Registering application {} ({})",
&app_config.server_name, &app_config.app_name
);
}

// default backend application for plaintext http requests
if let Some(default_app_name) = &config_list.default_app {
let default_server_name = manager
.apps
.iter()
.filter(|(_k, v)| &v.app_name == default_app_name)
.map(|(_, v)| v.server_name.clone())
.collect::<Vec<_>>();

if !default_server_name.is_empty() {
info!(
"Serving plaintext http for requests to unconfigured server_name by app {} (server_name: {}).",
&default_app_name,
(&default_server_name[0]).try_into().unwrap_or_else(|_| "".to_string())
);

manager.default_server_name = Some(default_server_name[0].clone());
}
}
Ok(manager)
}
}
135 changes: 135 additions & 0 deletions rpxy-lib/src/backend/load_balance/load_balance_main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#[cfg(feature = "sticky-cookie")]
pub use super::{
load_balance_sticky::{LoadBalanceSticky, LoadBalanceStickyBuilder},
sticky_cookie::StickyCookie,
};
use derive_builder::Builder;
use rand::Rng;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};

/// Constants to specify a load balance option
pub mod load_balance_options {
pub const FIX_TO_FIRST: &str = "none";
pub const ROUND_ROBIN: &str = "round_robin";
pub const RANDOM: &str = "random";
#[cfg(feature = "sticky-cookie")]
pub const STICKY_ROUND_ROBIN: &str = "sticky";
}

#[derive(Debug, Clone)]
/// Pointer to upstream serving the incoming request.
/// If 'sticky cookie'-based LB is enabled and cookie must be updated/created, the new cookie is also given.
pub struct PointerToUpstream {
pub ptr: usize,
pub context: Option<LoadBalanceContext>,
}
/// Trait for LB
pub(super) trait LoadBalanceWithPointer {
fn get_ptr(&self, req_info: Option<&LoadBalanceContext>) -> PointerToUpstream;
}

#[derive(Debug, Clone, Builder)]
/// Round Robin LB object as a pointer to the current serving upstream destination
pub struct LoadBalanceRoundRobin {
#[builder(default)]
/// Pointer to the index of the last served upstream destination
ptr: Arc<AtomicUsize>,
#[builder(setter(custom), default)]
/// Number of upstream destinations
num_upstreams: usize,
}
impl LoadBalanceRoundRobinBuilder {
pub fn num_upstreams(&mut self, v: &usize) -> &mut Self {
self.num_upstreams = Some(*v);
self
}
}
impl LoadBalanceWithPointer for LoadBalanceRoundRobin {
/// Increment the count of upstream served up to the max value
fn get_ptr(&self, _info: Option<&LoadBalanceContext>) -> PointerToUpstream {
// Get a current count of upstream served
let current_ptr = self.ptr.load(Ordering::Relaxed);

let ptr = if current_ptr < self.num_upstreams - 1 {
self.ptr.fetch_add(1, Ordering::Relaxed)
} else {
// Clear the counter
self.ptr.fetch_and(0, Ordering::Relaxed)
};
PointerToUpstream { ptr, context: None }
}
}

#[derive(Debug, Clone, Builder)]
/// Random LB object to keep the object of random pools
pub struct LoadBalanceRandom {
#[builder(setter(custom), default)]
/// Number of upstream destinations
num_upstreams: usize,
}
impl LoadBalanceRandomBuilder {
pub fn num_upstreams(&mut self, v: &usize) -> &mut Self {
self.num_upstreams = Some(*v);
self
}
}
impl LoadBalanceWithPointer for LoadBalanceRandom {
/// Returns the random index within the range
fn get_ptr(&self, _info: Option<&LoadBalanceContext>) -> PointerToUpstream {
let mut rng = rand::thread_rng();
let ptr = rng.gen_range(0..self.num_upstreams);
PointerToUpstream { ptr, context: None }
}
}

#[derive(Debug, Clone)]
/// Load Balancing Option
pub enum LoadBalance {
/// Fix to the first upstream. Use if only one upstream destination is specified
FixToFirst,
/// Randomly chose one upstream server
Random(LoadBalanceRandom),
/// Simple round robin without session persistance
RoundRobin(LoadBalanceRoundRobin),
#[cfg(feature = "sticky-cookie")]
/// Round robin with session persistance using cookie
StickyRoundRobin(LoadBalanceSticky),
}
impl Default for LoadBalance {
fn default() -> Self {
Self::FixToFirst
}
}

impl LoadBalance {
/// Get the index of the upstream serving the incoming request
pub fn get_context(&self, _context_to_lb: &Option<LoadBalanceContext>) -> PointerToUpstream {
match self {
LoadBalance::FixToFirst => PointerToUpstream {
ptr: 0usize,
context: None,
},
LoadBalance::RoundRobin(ptr) => ptr.get_ptr(None),
LoadBalance::Random(ptr) => ptr.get_ptr(None),
#[cfg(feature = "sticky-cookie")]
LoadBalance::StickyRoundRobin(ptr) => {
// Generate new context if sticky round robin is enabled.
ptr.get_ptr(_context_to_lb.as_ref())
}
}
}
}

#[derive(Debug, Clone)]
/// Struct to handle the sticky cookie string,
/// - passed from Rp module (http handler) to LB module, manipulated from req, only StickyCookieValue exists.
/// - passed from LB module to Rp module (http handler), will be inserted into res, StickyCookieValue and Info exist.
pub struct LoadBalanceContext {
#[cfg(feature = "sticky-cookie")]
pub sticky_cookie: StickyCookie,
#[cfg(not(feature = "sticky-cookie"))]
pub sticky_cookie: (),
}
Loading

0 comments on commit 3c6e4e5

Please sign in to comment.