add lb. todo: toml

This commit is contained in:
Jun Kurihara 2022-06-25 10:33:55 -04:00
commit e2ebb304c1
No known key found for this signature in database
GPG key ID: 48ADFD173ED22B03
5 changed files with 104 additions and 24 deletions

View file

@ -47,6 +47,7 @@ hyper-trust-dns = { version = "0.4.2", default-features = false, features = [
"rustls-webpki", "rustls-webpki",
] } ] }
rustls = "0.20.6" rustls = "0.20.6"
rand = "0.8.5"
[dev-dependencies] [dev-dependencies]

View file

@ -14,13 +14,20 @@ https_port = 8443
################################### ###################################
[[application]] [[application]]
app_name = 'localhost' # this should be option, if null then same as hostname app_name = 'localhost' # this should be option, if null then same as server_name
hostname = 'localhost' hostname = 'localhost'
https_redirection = true https_redirection = true
reverse_proxy = [ reverse_proxy = [
# default destination if path is not specified # default destination if path is not specified
{ destination = 'www.google.com', tls = true }, # TODO: Array for load balancing
{ destination = 'www.bing.com', path = '/maps', tls = true }, { upstream = [
{ location = 'www.google.com', tls = true },
{ location = 'www.google.co.jp', tls = true },
] },
{ path = '/maps', upstream = [
{ location = 'www.bing.com', tls = true },
{ location = 'www.bing.co.jp', tls = true },
] },
] ]
## List of destinations to send data to. ## List of destinations to send data to.
## At this point, round-robin is used for load-balancing if multiple URLs are specified. ## At this point, round-robin is used for load-balancing if multiple URLs are specified.
@ -34,6 +41,6 @@ tls_cert_key_path = 'localhost.pem'
app_name = 'locahost_application' app_name = 'locahost_application'
hostname = 'localhost.localdomain' hostname = 'localhost.localdomain'
https_redirection = true https_redirection = true
reverse_proxy = [{ destination = 'www.google.com', tls = true }] reverse_proxy = [{ upstream = [{ location = 'www.google.com', tls = true }] }]
tls_cert_path = 'localhost.pem' tls_cert_path = 'localhost.pem'
tls_cert_key_path = 'localhost.pem' tls_cert_key_path = 'localhost.pem'

View file

@ -1,10 +1,14 @@
use crate::log::*; use crate::log::*;
use rand::Rng;
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::File, fs::File,
io::{self, BufReader, Cursor, Read}, io::{self, BufReader, Cursor, Read},
path::PathBuf, path::PathBuf,
sync::Mutex, sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex,
},
}; };
use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig}; use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig};
@ -20,8 +24,58 @@ pub struct Backend {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ReverseProxy { pub struct ReverseProxy {
pub default_destination_uri: hyper::Uri, pub default_upstream: Upstream,
pub destination_uris: HashMap<String, hyper::Uri>, // TODO: url pathで引っ掛ける。 pub upstream: HashMap<String, Upstream>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum LoadBalance {
RoundRobin,
Random,
}
impl Default for LoadBalance {
fn default() -> Self {
Self::RoundRobin
}
}
#[derive(Debug, Clone)]
pub struct Upstream {
pub uri: Vec<hyper::Uri>,
pub lb: LoadBalance,
pub cnt: UpstreamCount, // counter for load balancing
}
#[derive(Debug, Clone, Default)]
pub struct UpstreamCount(Arc<AtomicUsize>);
impl Upstream {
pub fn get(&self) -> Option<&hyper::Uri> {
match self.lb {
LoadBalance::RoundRobin => {
let idx = self.increment_cnt();
self.uri.get(idx)
}
LoadBalance::Random => {
let mut rng = rand::thread_rng();
let max = self.uri.len() - 1;
self.uri.get(rng.gen_range(0..max))
}
}
}
fn current_cnt(&self) -> usize {
self.cnt.0.load(Ordering::Relaxed)
}
fn increment_cnt(&self) -> usize {
if self.current_cnt() < self.uri.len() - 1 {
self.cnt.0.fetch_add(1, Ordering::Relaxed)
} else {
self.cnt.0.fetch_and(0, Ordering::Relaxed)
}
}
} }
impl Backend { impl Backend {

View file

@ -21,10 +21,17 @@ pub fn parse_opts(globals: &mut Globals, backends: &mut HashMap<String, Backend>
globals.https_port = Some(HTTPS_LISTEN_PORT); globals.https_port = Some(HTTPS_LISTEN_PORT);
// TODO: // TODO:
let mut map_example: HashMap<String, Uri> = HashMap::new(); let mut map_example: HashMap<String, Upstream> = HashMap::new();
map_example.insert( map_example.insert(
"/maps".to_string(), "/maps".to_string(),
"https://www.bing.com".parse::<Uri>().unwrap(), Upstream {
uri: vec![
"https://www.bing.com".parse::<Uri>().unwrap(),
"https://www.bing.co.jp".parse::<Uri>().unwrap(),
],
cnt: Default::default(),
lb: Default::default(),
},
); );
backends.insert( backends.insert(
"localhost".to_string(), "localhost".to_string(),
@ -32,9 +39,16 @@ pub fn parse_opts(globals: &mut Globals, backends: &mut HashMap<String, Backend>
app_name: "Localhost to Google except for maps".to_string(), app_name: "Localhost to Google except for maps".to_string(),
hostname: "localhost".to_string(), hostname: "localhost".to_string(),
reverse_proxy: ReverseProxy { reverse_proxy: ReverseProxy {
// default_destination_uri: "https://www.google.com".parse::<Uri>().unwrap(), default_upstream: Upstream {
default_destination_uri: "http://abehiroshi.la.coocan.jp/".parse::<Uri>().unwrap(), // httpのみの場合の好例 uri: vec![
destination_uris: map_example, "https://www.google.com".parse::<Uri>().unwrap(),
"https://www.google.co.jp".parse::<Uri>().unwrap(),
],
cnt: Default::default(),
lb: Default::default(),
},
// default_upstream_uri: vec!["http://abehiroshi.la.coocan.jp/".parse::<Uri>().unwrap()], // httpのみの場合の好例
upstream: map_example,
}, },
https_redirection: Some(false), // TODO: ここはtlsが存在する時はSomeにすべき。Noneはtlsがないときのみのはず https_redirection: Some(false), // TODO: ここはtlsが存在する時はSomeにすべき。Noneはtlsがないときのみのはず

View file

@ -52,14 +52,18 @@ where
return secure_redirection(&hostname, self.globals.https_port, &path_and_query); return secure_redirection(&hostname, self.globals.https_port, &path_and_query);
} }
// Find reverse proxy for given path // Find reverse proxy for given path and choose one of upstream host
let path = req.uri().path(); let path = req.uri().path();
let destination_scheme_host = let upstream_uri = if let Some(upstream) = backend.reverse_proxy.upstream.get(path) {
if let Some(uri) = backend.reverse_proxy.destination_uris.get(path) { upstream.get()
uri.to_owned() } else {
} else { backend.reverse_proxy.default_upstream.get()
backend.reverse_proxy.default_destination_uri.clone() };
}; let upstream_scheme_host = if let Some(u) = upstream_uri {
u
} else {
return http_error(StatusCode::INTERNAL_SERVER_ERROR);
};
// Upgrade in request header // Upgrade in request header
let upgrade_in_request = extract_upgrade(req.headers()); let upgrade_in_request = extract_upgrade(req.headers());
@ -69,7 +73,7 @@ where
let req_forwarded = if let Ok(req) = generate_request_forwarded( let req_forwarded = if let Ok(req) = generate_request_forwarded(
client_addr, client_addr,
req, req,
destination_scheme_host, upstream_scheme_host,
path_and_query, path_and_query,
&upgrade_in_request, &upgrade_in_request,
) { ) {
@ -139,7 +143,7 @@ fn generate_response_forwarded<B: core::fmt::Debug>(response: &mut Response<B>)
fn generate_request_forwarded<B: core::fmt::Debug>( fn generate_request_forwarded<B: core::fmt::Debug>(
client_addr: SocketAddr, client_addr: SocketAddr,
mut req: Request<B>, mut req: Request<B>,
destination_scheme_host: Uri, upstream_scheme_host: &Uri,
path_and_query: String, path_and_query: String,
upgrade: &Option<String>, upgrade: &Option<String>,
) -> Result<Request<B>> { ) -> Result<Request<B>> {
@ -174,8 +178,8 @@ fn generate_request_forwarded<B: core::fmt::Debug>(
// update uri in request // update uri in request
*req.uri_mut() = Uri::builder() *req.uri_mut() = Uri::builder()
.scheme(destination_scheme_host.scheme().unwrap().as_str()) .scheme(upstream_scheme_host.scheme().unwrap().as_str())
.authority(destination_scheme_host.authority().unwrap().as_str()) .authority(upstream_scheme_host.authority().unwrap().as_str())
.path_and_query(&path_and_query) .path_and_query(&path_and_query)
.build()?; .build()?;
@ -188,7 +192,7 @@ fn generate_request_forwarded<B: core::fmt::Debug>(
} }
// Change version to http/1.1 when destination scheme is http // Change version to http/1.1 when destination scheme is http
if req.version() != Version::HTTP_11 && destination_scheme_host.scheme() == Some(&Scheme::HTTP) { if req.version() != Version::HTTP_11 && upstream_scheme_host.scheme() == Some(&Scheme::HTTP) {
*req.version_mut() = Version::HTTP_11; *req.version_mut() = Version::HTTP_11;
} }