Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:hyper-1.0 #115

Merged
merged 43 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b639e79
wip: implemented hyper-1.0 for http/1.1 and http/2. todo: http/3 and …
junkurihara Nov 18, 2023
7bc6e30
chore: deps
junkurihara Nov 21, 2023
f98c778
wip: refactor whole module in lib
junkurihara Nov 21, 2023
de91c7a
wip: refactoring all the structure and improve error messages
junkurihara Nov 22, 2023
3c6e4e5
wip: implemented backend
junkurihara Nov 24, 2023
5576389
wip: implemented crypto reloader, as separated object from proxy itself
junkurihara Nov 24, 2023
1dc88ce
wip: tested with synthetic echo response from h3
junkurihara Nov 24, 2023
4b6f63e
wip: implemented incoming-like body for asynchronous operation in http/3
junkurihara Nov 24, 2023
b8cec68
wip: add stub for message handler
junkurihara Nov 24, 2023
e8d67bf
wip: add tests for incoming-like body
junkurihara Nov 24, 2023
a9ce26a
wip: implementing message handler
junkurihara Nov 27, 2023
c4cf40b
wip: implementing message handler, finished to implement request mani…
junkurihara Nov 27, 2023
f9453fe
wip: fix private type
junkurihara Nov 27, 2023
ab4ac3b
fix private type again
junkurihara Nov 27, 2023
f0b0dbc
wip: manipulate response header
junkurihara Nov 28, 2023
8f77ce9
wip: implement switching protocols (http upgrade)
junkurihara Nov 28, 2023
f020ece
chore: change mod name
junkurihara Nov 28, 2023
a9f5e0e
feat: client (wip), still unstable for http2 due to alpn issues
junkurihara Nov 28, 2023
0741990
wip: fix sync
junkurihara Nov 28, 2023
48a84a7
implement native-tls client
junkurihara Nov 29, 2023
a6f9fc7
remove unneccessary deps
junkurihara Nov 29, 2023
deb4c28
wip: set_reuse_addr for client
junkurihara Nov 30, 2023
2a48c64
deps
junkurihara Dec 4, 2023
f58ce97
chore: empty feature rustls
junkurihara Dec 4, 2023
4aa149a
deps except for rustls family
junkurihara Dec 8, 2023
f714282
chore: prioritize http3-quinn over http3-s2n when both features are e…
junkurihara Dec 8, 2023
6030beb
chore: prioritize rustls-backend while it is not supported (non-default)
junkurihara Dec 8, 2023
f5197d0
wip: refactoring the cache logic
junkurihara Dec 8, 2023
cdcb1b1
wip: chore: fix bug for unused
junkurihara Dec 8, 2023
d473b44
add comment
junkurihara Dec 9, 2023
ed33c5d
wip: implement on-memory cache as is
junkurihara Dec 9, 2023
cc48394
wip: feat: update h3 response reader to use async stream
junkurihara Dec 11, 2023
d526ce6
wip: refactor: reconsider timeouts of connections
junkurihara Dec 11, 2023
b8f3034
wip: fix keep alive timeouts
junkurihara Dec 11, 2023
7cb25a7
add tokio timer
junkurihara Dec 11, 2023
008b62a
wip: feat: define response body enum
junkurihara Dec 12, 2023
1c18f38
wip: feat: change request body from either to explicit enum
junkurihara Dec 12, 2023
8dd6af6
wip: feat: refactored cache implementation for put
junkurihara Dec 12, 2023
bd29c9d
wip: feat: implemented cache
junkurihara Dec 12, 2023
66efa93
Merge pull request #127 from junkurihara/feat/cache-hyper-1.0
junkurihara Dec 12, 2023
92638cc
wip: update changelog and todo
junkurihara Dec 12, 2023
f41a221
preparing nightly-build
junkurihara Dec 15, 2023
db65872
update docs. preparing 0.7.0-alpha.0.
junkurihara Dec 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
[submodule "submodules/h3"]
path = submodules/h3
url = [email protected]:junkurihara/h3.git
[submodule "submodules/quinn"]
path = submodules/quinn
url = [email protected]:junkurihara/quinn.git
[submodule "submodules/s2n-quic"]
path = submodules/s2n-quic
url = [email protected]:junkurihara/s2n-quic.git
[submodule "submodules/rusty-http-cache-semantics"]
path = submodules/rusty-http-cache-semantics
url = [email protected]:junkurihara/rusty-http-cache-semantics.git
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]

members = ["rpxy-bin", "rpxy-lib"]
members = ["rpxy-bin", "rpxy-lib", "legacy-lib"]
exclude = ["submodules"]
resolver = "2"

Expand Down
89 changes: 89 additions & 0 deletions legacy-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
[package]
name = "rpxy-lib-legacy"
version = "0.6.2"
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]
default = ["http3-s2n", "sticky-cookie", "cache"]
http3-quinn = ["quinn", "h3", "h3-quinn", "socket2"]
http3-s2n = ["h3", "s2n-quic", "s2n-quic-rustls", "s2n-quic-h3"]
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"
bytes = "1.5.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",
"rt-multi-thread",
"time",
"sync",
"macros",
"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
http = "1.0.0"
http-body-util = "0.1.0"
hyper = { version = "1.0.1", default-features = false }
hyper-util = { version = "0.1.1", features = ["full"] }
hyper-rustls = { version = "0.24.2", default-features = false, features = [
"tokio-runtime",
"webpki-tokio",
"http1",
"http2",
] }
tokio-rustls = { version = "0.24.1", features = ["early-data"] }
rustls = { version = "0.21.9", default-features = false }
webpki = "0.22.4"
x509-parser = "0.15.1"

# logging
tracing = { version = "0.1.40" }

# http/3
quinn = { version = "0.10.2", optional = true }
h3 = { path = "../submodules/h3/h3/", optional = true }
h3-quinn = { path = "../submodules/h3/h3-quinn/", optional = true }
s2n-quic = { version = "1.31.0", default-features = false, features = [
"provider-tls-rustls",
], optional = true }
s2n-quic-h3 = { path = "../submodules/s2n-quic-h3/", optional = true }
s2n-quic-rustls = { version = "0.31.0", optional = true }
# for UDP socket wit SO_REUSEADDR when h3 with quinn
socket2 = { version = "0.5.5", features = ["all"], optional = true }

# cache
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 }


[dev-dependencies]
# http and tls
77 changes: 77 additions & 0 deletions legacy-lib/src/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
mod load_balance;
#[cfg(feature = "sticky-cookie")]
mod load_balance_sticky;
#[cfg(feature = "sticky-cookie")]
mod sticky_cookie;
mod upstream;
mod upstream_opts;

#[cfg(feature = "sticky-cookie")]
pub use self::sticky_cookie::{StickyCookie, StickyCookieValue};
pub use self::{
load_balance::{LbContext, LoadBalance},
upstream::{ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder},
upstream_opts::UpstreamOption,
};
use crate::{
certs::CryptoSource,
utils::{BytesName, PathNameBytesExp, ServerNameBytesExp},
};
use derive_builder::Builder;
use rustc_hash::FxHashMap as HashMap;
use std::borrow::Cow;

/// Struct serving information to route incoming connections, like server name to be handled and tls certs/keys settings.
#[derive(Builder)]
pub struct Backend<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 String ascii lower case
pub server_name: String,
/// struct of reverse proxy serving incoming request
pub reverse_proxy: ReverseProxy,

/// 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> BackendBuilder<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.into().to_ascii_lowercase());
self
}
}

/// HashMap and some meta information for multiple Backend structs.
pub struct Backends<T>
where
T: CryptoSource,
{
pub apps: HashMap<ServerNameBytesExp, Backend<T>>, // hyper::uriで抜いたhostで引っ掛ける
pub default_server_name_bytes: Option<ServerNameBytesExp>, // for plaintext http
}

impl<T> Backends<T>
where
T: CryptoSource,
{
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Backends {
apps: HashMap::<ServerNameBytesExp, Backend<T>>::default(),
default_server_name_bytes: None,
}
}
}
201 changes: 201 additions & 0 deletions legacy-lib/src/backend/upstream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#[cfg(feature = "sticky-cookie")]
use super::load_balance::LbStickyRoundRobinBuilder;
use super::load_balance::{load_balance_options as lb_opts, LbRandomBuilder, LbRoundRobinBuilder, LoadBalance};
use super::{BytesName, LbContext, PathNameBytesExp, UpstreamOption};
use crate::log::*;
#[cfg(feature = "sticky-cookie")]
use base64::{engine::general_purpose, Engine as _};
use derive_builder::Builder;
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
#[cfg(feature = "sticky-cookie")]
use sha2::{Digest, Sha256};
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub struct ReverseProxy {
pub upstream: HashMap<PathNameBytesExp, UpstreamGroup>, // TODO: HashMapでいいのかは疑問。max_by_keyでlongest prefix matchしてるのも無駄っぽいが。。。
}

impl ReverseProxy {
/// Get an appropriate upstream destination for given path string.
pub fn get<'a>(&self, path_str: impl Into<Cow<'a, str>>) -> Option<&UpstreamGroup> {
// trie使ってlongest prefix match させてもいいけどルート記述は少ないと思われるので、
// コスト的にこの程度で十分
let path_bytes = &path_str.to_path_name_vec();

let matched_upstream = self
.upstream
.iter()
.filter(|(route_bytes, _)| {
match path_bytes.starts_with(route_bytes) {
true => {
route_bytes.len() == 1 // route = '/', i.e., default
|| match path_bytes.get(route_bytes.len()) {
None => true, // exact case
Some(p) => p == &b'/', // sub-path case
}
}
_ => false,
}
})
.max_by_key(|(route_bytes, _)| route_bytes.len());
if let Some((_path, u)) = matched_upstream {
debug!(
"Found upstream: {:?}",
String::from_utf8(_path.0.clone()).unwrap_or_else(|_| "<none>".to_string())
);
Some(u)
} else {
None
}
}
}

#[derive(Debug, Clone)]
/// Upstream struct just containing uri without path
pub struct Upstream {
/// Base uri without specific path
pub uri: hyper::Uri,
}
impl Upstream {
#[cfg(feature = "sticky-cookie")]
/// Hashing uri with index to avoid collision
pub fn calculate_id_with_index(&self, index: usize) -> String {
let mut hasher = Sha256::new();
let uri_string = format!("{}&index={}", self.uri.clone(), index);
hasher.update(uri_string.as_bytes());
let digest = hasher.finalize();
general_purpose::URL_SAFE_NO_PAD.encode(digest)
}
}
#[derive(Debug, Clone, Builder)]
/// Struct serving multiple upstream servers for, e.g., load balancing.
pub struct UpstreamGroup {
#[builder(setter(custom))]
/// Upstream server(s)
pub upstream: Vec<Upstream>,
#[builder(setter(custom), default)]
/// Path like "/path" in [[PathNameBytesExp]] associated with the upstream server(s)
pub path: PathNameBytesExp,
#[builder(setter(custom), default)]
/// Path in [[PathNameBytesExp]] that will be used to replace the "path" part of incoming url
pub replace_path: Option<PathNameBytesExp>,

#[builder(setter(custom), default)]
/// Load balancing option
pub lb: LoadBalance,
#[builder(setter(custom), default)]
/// Activated upstream options defined in [[UpstreamOption]]
pub opts: HashSet<UpstreamOption>,
}

impl UpstreamGroupBuilder {
pub fn upstream(&mut self, upstream_vec: &[Upstream]) -> &mut Self {
self.upstream = Some(upstream_vec.to_vec());
self
}
pub fn path(&mut self, v: &Option<String>) -> &mut Self {
let path = match v {
Some(p) => p.to_path_name_vec(),
None => "/".to_path_name_vec(),
};
self.path = Some(path);
self
}
pub fn replace_path(&mut self, v: &Option<String>) -> &mut Self {
self.replace_path = Some(
v.to_owned()
.as_ref()
.map_or_else(|| None, |v| Some(v.to_path_name_vec())),
);
self
}
pub fn lb(
&mut self,
v: &Option<String>,
// upstream_num: &usize,
upstream_vec: &Vec<Upstream>,
_server_name: &str,
_path_opt: &Option<String>,
) -> &mut Self {
let upstream_num = &upstream_vec.len();
let lb = if let Some(x) = v {
match x.as_str() {
lb_opts::FIX_TO_FIRST => LoadBalance::FixToFirst,
lb_opts::RANDOM => LoadBalance::Random(LbRandomBuilder::default().num_upstreams(upstream_num).build().unwrap()),
lb_opts::ROUND_ROBIN => LoadBalance::RoundRobin(
LbRoundRobinBuilder::default()
.num_upstreams(upstream_num)
.build()
.unwrap(),
),
#[cfg(feature = "sticky-cookie")]
lb_opts::STICKY_ROUND_ROBIN => LoadBalance::StickyRoundRobin(
LbStickyRoundRobinBuilder::default()
.num_upstreams(upstream_num)
.sticky_config(_server_name, _path_opt)
.upstream_maps(upstream_vec) // TODO:
.build()
.unwrap(),
),
_ => {
error!("Specified load balancing option is invalid.");
LoadBalance::default()
}
}
} else {
LoadBalance::default()
};
self.lb = Some(lb);
self
}
pub fn opts(&mut self, v: &Option<Vec<String>>) -> &mut Self {
let opts = if let Some(opts) = v {
opts
.iter()
.filter_map(|str| UpstreamOption::try_from(str.as_str()).ok())
.collect::<HashSet<UpstreamOption>>()
} else {
Default::default()
};
self.opts = Some(opts);
self
}
}

impl UpstreamGroup {
/// Get an enabled option of load balancing [[LoadBalance]]
pub fn get(&self, context_to_lb: &Option<LbContext>) -> (Option<&Upstream>, Option<LbContext>) {
let pointer_to_upstream = self.lb.get_context(context_to_lb);
debug!("Upstream of index {} is chosen.", pointer_to_upstream.ptr);
debug!("Context to LB (Cookie in Req): {:?}", context_to_lb);
debug!(
"Context from LB (Set-Cookie in Res): {:?}",
pointer_to_upstream.context_lb
);
(
self.upstream.get(pointer_to_upstream.ptr),
pointer_to_upstream.context_lb,
)
}
}

#[cfg(test)]
mod test {
#[allow(unused)]
use super::*;

#[cfg(feature = "sticky-cookie")]
#[test]
fn calc_id_works() {
let uri = "https://www.rust-lang.org".parse::<hyper::Uri>().unwrap();
let upstream = Upstream { uri };
assert_eq!(
"eGsjoPbactQ1eUJjafYjPT3ekYZQkaqJnHdA_FMSkgM",
upstream.calculate_id_with_index(0)
);
assert_eq!(
"tNVXFJ9eNCT2mFgKbYq35XgH5q93QZtfU8piUiiDxVA",
upstream.calculate_id_with_index(1)
);
}
}
Loading