feat: separated feature cache

This commit is contained in:
Jun Kurihara 2023-08-18 18:15:36 +09:00
commit 32b173966c
No known key found for this signature in database
GPG key ID: D992B3E3DE1DED23
11 changed files with 76 additions and 36 deletions

View file

@ -42,7 +42,7 @@ jobs:
- target: "s2n" - target: "s2n"
dockerfile: ./docker/Dockerfile dockerfile: ./docker/Dockerfile
build-args: | build-args: |
"CARGO_FEATURES=--no-default-features --features http3-s2n" "CARGO_FEATURES=--no-default-features --features http3-s2n cache"
"ADDITIONAL_DEPS=pkg-config libssl-dev cmake libclang1 gcc g++" "ADDITIONAL_DEPS=pkg-config libssl-dev cmake libclang1 gcc g++"
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags-suffix: "-s2n" tags-suffix: "-s2n"

View file

@ -12,9 +12,10 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["http3-quinn"] default = ["http3-quinn", "cache"]
http3-quinn = ["rpxy-lib/http3-quinn"] http3-quinn = ["rpxy-lib/http3-quinn"]
http3-s2n = ["rpxy-lib/http3-s2n"] http3-s2n = ["rpxy-lib/http3-s2n"]
cache = ["rpxy-lib/cache"]
[dependencies] [dependencies]
rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [ rpxy-lib = { path = "../rpxy-lib/", default-features = false, features = [

View file

@ -6,7 +6,7 @@ use crate::{
use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri}; use rpxy_lib::{reexports::Uri, AppConfig, ProxyConfig, ReverseProxyConfig, TlsConfig, UpstreamUri};
use rustc_hash::FxHashMap as HashMap; use rustc_hash::FxHashMap as HashMap;
use serde::Deserialize; use serde::Deserialize;
use std::{fs, net::SocketAddr, path::PathBuf}; use std::{fs, net::SocketAddr};
#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] #[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)]
pub struct ConfigToml { pub struct ConfigToml {
@ -32,6 +32,7 @@ pub struct Http3Option {
pub max_idle_timeout: Option<u64>, pub max_idle_timeout: Option<u64>,
} }
#[cfg(feature = "cache")]
#[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)] #[derive(Deserialize, Debug, Default, PartialEq, Eq, Clone)]
pub struct CacheOption { pub struct CacheOption {
pub cache_dir: Option<String>, pub cache_dir: Option<String>,
@ -43,6 +44,7 @@ pub struct CacheOption {
pub struct Experimental { pub struct Experimental {
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))] #[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
pub h3: Option<Http3Option>, pub h3: Option<Http3Option>,
#[cfg(feature = "cache")]
pub cache: Option<CacheOption>, pub cache: Option<CacheOption>,
pub ignore_sni_consistency: Option<bool>, pub ignore_sni_consistency: Option<bool>,
} }
@ -169,20 +171,19 @@ impl TryInto<ProxyConfig> for &ConfigToml {
proxy_config.sni_consistency = !ignore; proxy_config.sni_consistency = !ignore;
} }
#[cfg(feature = "cache")]
if let Some(cache_option) = &exp.cache { if let Some(cache_option) = &exp.cache {
proxy_config.cache_enabled = true; proxy_config.cache_enabled = true;
proxy_config.cache_dir = match &cache_option.cache_dir { proxy_config.cache_dir = match &cache_option.cache_dir {
Some(cache_dir) => Some(PathBuf::from(cache_dir)), Some(cache_dir) => Some(std::path::PathBuf::from(cache_dir)),
None => Some(PathBuf::from(CACHE_DIR)), None => Some(std::path::PathBuf::from(CACHE_DIR)),
};
proxy_config.cache_max_entry = match &cache_option.max_cache_entry {
Some(num) => Some(*num),
None => Some(MAX_CACHE_ENTRY),
};
proxy_config.cache_max_each_size = match &cache_option.max_cache_each_size {
Some(num) => Some(*num),
None => Some(MAX_CACHE_EACH_SIZE),
}; };
if let Some(num) = cache_option.max_cache_entry {
proxy_config.cache_max_entry = num;
}
if let Some(num) = cache_option.max_cache_each_size {
proxy_config.cache_max_each_size = num;
}
} }
} }

View file

@ -2,11 +2,6 @@ pub const LISTEN_ADDRESSES_V4: &[&str] = &["0.0.0.0"];
pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"]; pub const LISTEN_ADDRESSES_V6: &[&str] = &["[::]"];
pub const CONFIG_WATCH_DELAY_SECS: u32 = 20; pub const CONFIG_WATCH_DELAY_SECS: u32 = 20;
#[cfg(feature = "cache")]
// Cache directory // Cache directory
pub const CACHE_DIR: &str = "./cache"; pub const CACHE_DIR: &str = "./cache";
// # of entries in cache
pub const MAX_CACHE_ENTRY: usize = 1_000;
// max size for each file in bytes
pub const MAX_CACHE_EACH_SIZE: usize = 65_535;
// TODO: max cache size in total

View file

@ -12,10 +12,11 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["http3-quinn", "sticky-cookie"] default = ["http3-quinn", "sticky-cookie", "cache"]
http3-quinn = ["quinn", "h3", "h3-quinn", "socket2"] http3-quinn = ["quinn", "h3", "h3-quinn", "socket2"]
http3-s2n = ["h3", "s2n-quic", "s2n-quic-rustls", "s2n-quic-h3"] 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"]
[dependencies] [dependencies]
rand = "0.8.5" rand = "0.8.5"
@ -74,8 +75,8 @@ s2n-quic-h3 = { path = "../submodules/s2n-quic/quic/s2n-quic-h3/", optional = tr
s2n-quic-rustls = { path = "../submodules/s2n-quic/quic/s2n-quic-rustls/", optional = true } s2n-quic-rustls = { path = "../submodules/s2n-quic/quic/s2n-quic-rustls/", optional = true }
# cache # cache
http-cache-semantics = { path = "../submodules/rusty-http-cache-semantics/" } http-cache-semantics = { path = "../submodules/rusty-http-cache-semantics/", optional = true }
lru = { version = "0.11.0" } lru = { version = "0.11.0", optional = true }
# cookie handling for sticky cookie # cookie handling for sticky cookie
chrono = { version = "0.4.26", default-features = false, features = [ chrono = { version = "0.4.26", default-features = false, features = [

View file

@ -31,3 +31,12 @@ pub mod H3 {
#[cfg(feature = "sticky-cookie")] #[cfg(feature = "sticky-cookie")]
/// For load-balancing with sticky cookie /// For load-balancing with sticky cookie
pub const STICKY_COOKIE_NAME: &str = "rpxy_srv_id"; pub const STICKY_COOKIE_NAME: &str = "rpxy_srv_id";
#[cfg(feature = "cache")]
// # of entries in cache
pub const MAX_CACHE_ENTRY: usize = 1_000;
#[cfg(feature = "cache")]
// max size for each file in bytes
pub const MAX_CACHE_EACH_SIZE: usize = 65_535;
// TODO: max cache size in total

View file

@ -9,11 +9,11 @@ use crate::{
utils::{BytesName, PathNameBytesExp}, utils::{BytesName, PathNameBytesExp},
}; };
use rustc_hash::FxHashMap as HashMap; use rustc_hash::FxHashMap as HashMap;
use std::net::SocketAddr;
use std::sync::{ use std::sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Arc, Arc,
}; };
use std::{net::SocketAddr, path::PathBuf};
use tokio::time::Duration; use tokio::time::Duration;
/// Global object containing proxy configurations and shared object like counters. /// Global object containing proxy configurations and shared object like counters.
@ -53,10 +53,14 @@ pub struct ProxyConfig {
// experimentals // experimentals
pub sni_consistency: bool, // Handler pub sni_consistency: bool, // Handler
#[cfg(feature = "cache")]
pub cache_enabled: bool, pub cache_enabled: bool,
pub cache_dir: Option<PathBuf>, #[cfg(feature = "cache")]
pub cache_max_entry: Option<usize>, pub cache_dir: Option<std::path::PathBuf>,
pub cache_max_each_size: Option<usize>, #[cfg(feature = "cache")]
pub cache_max_entry: usize,
#[cfg(feature = "cache")]
pub cache_max_each_size: usize,
// All need to make packet acceptor // All need to make packet acceptor
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))] #[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
@ -93,10 +97,14 @@ impl Default for ProxyConfig {
sni_consistency: true, sni_consistency: true,
#[cfg(feature = "cache")]
cache_enabled: false, cache_enabled: false,
#[cfg(feature = "cache")]
cache_dir: None, cache_dir: None,
cache_max_entry: None, #[cfg(feature = "cache")]
cache_max_each_size: None, cache_max_entry: MAX_CACHE_ENTRY,
#[cfg(feature = "cache")]
cache_max_each_size: MAX_CACHE_EACH_SIZE,
#[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))] #[cfg(any(feature = "http3-quinn", feature = "http3-s2n"))]
http3: false, http3: false,

View file

@ -121,14 +121,14 @@ impl RpxyCache {
let path = globals.proxy_config.cache_dir.as_ref().unwrap(); let path = globals.proxy_config.cache_dir.as_ref().unwrap();
let cache_file_manager = Arc::new(RwLock::new(CacheFileManager::new(path, &globals.runtime_handle).await)); let cache_file_manager = Arc::new(RwLock::new(CacheFileManager::new(path, &globals.runtime_handle).await));
let inner = Arc::new(Mutex::new(LruCache::new( let inner = Arc::new(Mutex::new(LruCache::new(
std::num::NonZeroUsize::new(globals.proxy_config.cache_max_entry.unwrap()).unwrap(), std::num::NonZeroUsize::new(globals.proxy_config.cache_max_entry).unwrap(),
))); )));
Some(Self { Some(Self {
cache_file_manager, cache_file_manager,
inner, inner,
runtime_handle: globals.runtime_handle.clone(), runtime_handle: globals.runtime_handle.clone(),
max_each_size: globals.proxy_config.cache_max_each_size.unwrap(), max_each_size: globals.proxy_config.cache_max_each_size,
}) })
} }

View file

@ -1,8 +1,11 @@
#[cfg(feature = "cache")]
use super::cache::{get_policy_if_cacheable, RpxyCache}; use super::cache::{get_policy_if_cacheable, RpxyCache};
use crate::{error::RpxyError, globals::Globals, log::*, CryptoSource}; #[cfg(feature = "cache")]
use crate::log::*;
use crate::{error::RpxyError, globals::Globals, CryptoSource};
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(feature = "cache")]
use bytes::Buf; use bytes::Buf;
use derive_builder::Builder;
use hyper::{ use hyper::{
body::{Body, HttpBody}, body::{Body, HttpBody},
client::{connect::Connect, HttpConnector}, client::{connect::Connect, HttpConnector},
@ -11,6 +14,8 @@ use hyper::{
}; };
use hyper_rustls::HttpsConnector; use hyper_rustls::HttpsConnector;
#[cfg(feature = "cache")]
/// Build synthetic request to cache
fn build_synth_req_for_cache<T>(req: &Request<T>) -> Request<()> { fn build_synth_req_for_cache<T>(req: &Request<T>) -> Request<()> {
let mut builder = Request::builder() let mut builder = Request::builder()
.method(req.method()) .method(req.method())
@ -30,12 +35,12 @@ pub trait ForwardRequest<B> {
async fn request(&self, req: Request<B>) -> Result<Response<Body>, Self::Error>; async fn request(&self, req: Request<B>) -> Result<Response<Body>, Self::Error>;
} }
#[derive(Builder, Clone)]
/// Forwarder struct responsible to cache handling /// Forwarder struct responsible to cache handling
pub struct Forwarder<C, B = Body> pub struct Forwarder<C, B = Body>
where where
C: Connect + Clone + Sync + Send + 'static, C: Connect + Clone + Sync + Send + 'static,
{ {
#[cfg(feature = "cache")]
cache: Option<RpxyCache>, cache: Option<RpxyCache>,
inner: Client<C, B>, inner: Client<C, B>,
inner_h2: Client<C, B>, // `h2c` or http/2-only client is defined separately inner_h2: Client<C, B>, // `h2c` or http/2-only client is defined separately
@ -50,6 +55,8 @@ where
C: Connect + Clone + Sync + Send + 'static, C: Connect + Clone + Sync + Send + 'static,
{ {
type Error = RpxyError; type Error = RpxyError;
#[cfg(feature = "cache")]
async fn request(&self, req: Request<B>) -> Result<Response<Body>, Self::Error> { async fn request(&self, req: Request<B>) -> Result<Response<Body>, Self::Error> {
let mut synth_req = None; let mut synth_req = None;
if self.cache.is_some() { if self.cache.is_some() {
@ -99,10 +106,19 @@ where
// res // res
Ok(Response::from_parts(parts, Body::from(aggregated))) Ok(Response::from_parts(parts, Body::from(aggregated)))
} }
#[cfg(not(feature = "cache"))]
async fn request(&self, req: Request<B>) -> Result<Response<Body>, Self::Error> {
match req.version() {
Version::HTTP_2 => self.inner_h2.request(req).await.map_err(RpxyError::Hyper), // handles `h2c` requests
_ => self.inner.request(req).await.map_err(RpxyError::Hyper),
}
}
} }
impl Forwarder<HttpsConnector<HttpConnector>, Body> { impl Forwarder<HttpsConnector<HttpConnector>, Body> {
pub async fn new<T: CryptoSource>(globals: &std::sync::Arc<Globals<T>>) -> Self { /// Build forwarder
pub async fn new<T: CryptoSource>(_globals: &std::sync::Arc<Globals<T>>) -> Self {
// let connector = TrustDnsResolver::default().into_rustls_webpki_https_connector(); // let connector = TrustDnsResolver::default().into_rustls_webpki_https_connector();
let connector = hyper_rustls::HttpsConnectorBuilder::new() let connector = hyper_rustls::HttpsConnectorBuilder::new()
.with_webpki_roots() .with_webpki_roots()
@ -119,7 +135,12 @@ impl Forwarder<HttpsConnector<HttpConnector>, Body> {
let inner = Client::builder().build::<_, Body>(connector); let inner = Client::builder().build::<_, Body>(connector);
let inner_h2 = Client::builder().http2_only(true).build::<_, Body>(connector_h2); let inner_h2 = Client::builder().http2_only(true).build::<_, Body>(connector_h2);
let cache = RpxyCache::new(globals).await; #[cfg(feature = "cache")]
{
let cache = RpxyCache::new(_globals).await;
Self { inner, inner_h2, cache } Self { inner, inner_h2, cache }
} }
#[cfg(not(feature = "cache"))]
Self { inner, inner_h2 }
}
} }

View file

@ -1,3 +1,4 @@
#[cfg(feature = "cache")]
mod cache; mod cache;
mod forwarder; mod forwarder;
mod handler_main; mod handler_main;

View file

@ -60,11 +60,14 @@ where
if !proxy_config.sni_consistency { if !proxy_config.sni_consistency {
info!("Ignore consistency between TLS SNI and Host header (or Request line). Note it violates RFC."); info!("Ignore consistency between TLS SNI and Host header (or Request line). Note it violates RFC.");
} }
#[cfg(feature = "cache")]
if proxy_config.cache_enabled { if proxy_config.cache_enabled {
info!( info!(
"Cache is enabled: cache dir = {:?}", "Cache is enabled: cache dir = {:?}",
proxy_config.cache_dir.as_ref().unwrap() proxy_config.cache_dir.as_ref().unwrap()
); );
} else {
info!("Cache is disabled")
} }
// build global // build global