mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
perf(app-proxy): skip disk reads for absent static prefixes (#3115)
This commit is contained in:
@@ -6,7 +6,7 @@ use fluxer_app_proxy::{
|
|||||||
csp::CompiledCspPolicy,
|
csp::CompiledCspPolicy,
|
||||||
discovery_cache::DiscoveryCache,
|
discovery_cache::DiscoveryCache,
|
||||||
geoip,
|
geoip,
|
||||||
routes::build_router,
|
routes::{build_router, present_local_asset_prefixes},
|
||||||
state::{
|
state::{
|
||||||
AppProxyBudgets, AppState, MAX_SPA_INDEX_BYTES, build_http_client, read_bounded_text_file,
|
AppProxyBudgets, AppState, MAX_SPA_INDEX_BYTES, build_http_client, read_bounded_text_file,
|
||||||
},
|
},
|
||||||
@@ -67,6 +67,11 @@ fn main() -> anyhow::Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let local_asset_prefixes = config
|
||||||
|
.index_upstream_url
|
||||||
|
.is_none()
|
||||||
|
.then(|| present_local_asset_prefixes(&config.static_dir));
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
config,
|
config,
|
||||||
csp,
|
csp,
|
||||||
@@ -74,6 +79,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
discovery_cache,
|
discovery_cache,
|
||||||
geoip,
|
geoip,
|
||||||
index_html,
|
index_html,
|
||||||
|
local_asset_prefixes,
|
||||||
budgets: AppProxyBudgets::default(),
|
budgets: AppProxyBudgets::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -510,6 +510,7 @@ mod tests {
|
|||||||
client_ip_header_name: "x-forwarded-for".to_owned(),
|
client_ip_header_name: "x-forwarded-for".to_owned(),
|
||||||
})),
|
})),
|
||||||
index_html: None,
|
index_html: None,
|
||||||
|
local_asset_prefixes: None,
|
||||||
budgets: crate::state::AppProxyBudgets::default(),
|
budgets: crate::state::AppProxyBudgets::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ mod tests {
|
|||||||
client_ip_header_name: "x-forwarded-for".to_owned(),
|
client_ip_header_name: "x-forwarded-for".to_owned(),
|
||||||
})),
|
})),
|
||||||
index_html: Some(Arc::from("<html><head></head><body></body></html>")),
|
index_html: Some(Arc::from("<html><head></head><body></body></html>")),
|
||||||
|
local_asset_prefixes: None,
|
||||||
budgets: crate::state::AppProxyBudgets::default(),
|
budgets: crate::state::AppProxyBudgets::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ mod tests {
|
|||||||
client_ip_header_name: "x-forwarded-for".to_owned(),
|
client_ip_header_name: "x-forwarded-for".to_owned(),
|
||||||
})),
|
})),
|
||||||
index_html: None,
|
index_html: None,
|
||||||
|
local_asset_prefixes: None,
|
||||||
budgets: crate::state::AppProxyBudgets::default(),
|
budgets: crate::state::AppProxyBudgets::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ mod health;
|
|||||||
mod spa_index;
|
mod spa_index;
|
||||||
mod spa_static;
|
mod spa_static;
|
||||||
|
|
||||||
|
pub use spa_index::present_local_asset_prefixes;
|
||||||
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use axum::{
|
|||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
};
|
};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use super::assets_proxy::serve_local_asset;
|
use super::assets_proxy::serve_local_asset;
|
||||||
@@ -45,7 +46,14 @@ pub async fn spa_catch_all(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
if is_static_asset_path(request_path) {
|
if let Some(prefix) = static_asset_prefix(request_path) {
|
||||||
|
if state
|
||||||
|
.local_asset_prefixes
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|present| !present.contains(&prefix))
|
||||||
|
{
|
||||||
|
return StatusCode::NOT_FOUND.into_response();
|
||||||
|
}
|
||||||
return serve_local_asset(
|
return serve_local_asset(
|
||||||
&state.budgets,
|
&state.budgets,
|
||||||
&state.config.static_dir,
|
&state.config.static_dir,
|
||||||
@@ -80,10 +88,23 @@ fn static_root_file_cache_control(request_path: &str) -> Option<&'static str> {
|
|||||||
.map(|(_, cache_control)| *cache_control)
|
.map(|(_, cache_control)| *cache_control)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_static_asset_path(request_path: &str) -> bool {
|
fn static_asset_prefix(request_path: &str) -> Option<&'static str> {
|
||||||
STATIC_ASSET_PREFIXES
|
STATIC_ASSET_PREFIXES
|
||||||
.iter()
|
.iter()
|
||||||
.any(|prefix| request_path.starts_with(prefix))
|
.copied()
|
||||||
|
.find(|prefix| request_path.starts_with(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn present_local_asset_prefixes(static_dir: &str) -> Arc<[&'static str]> {
|
||||||
|
STATIC_ASSET_PREFIXES
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|prefix| {
|
||||||
|
Path::new(static_dir)
|
||||||
|
.join(prefix.trim_matches('/'))
|
||||||
|
.is_dir()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_static_file(
|
async fn serve_static_file(
|
||||||
@@ -609,11 +630,11 @@ mod tests {
|
|||||||
|
|
||||||
use crate::config::{AppProxyConfig, ReleaseChannel};
|
use crate::config::{AppProxyConfig, ReleaseChannel};
|
||||||
use crate::discovery_cache::DiscoveryCache;
|
use crate::discovery_cache::DiscoveryCache;
|
||||||
|
use crate::state::LOCAL_FILE_READS_IN_FLIGHT_MAX;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use fluxer_common::config::GeoipSourceConfig;
|
use fluxer_common::config::GeoipSourceConfig;
|
||||||
use fluxer_common::geoip::{GeoipConfig, GeoipResolver};
|
use fluxer_common::geoip::{GeoipConfig, GeoipResolver};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dev_asset_cache_buster_rewrites_script_and_link_assets() {
|
fn dev_asset_cache_buster_rewrites_script_and_link_assets() {
|
||||||
@@ -823,6 +844,79 @@ mod tests {
|
|||||||
assert!(served.contains("window.__FLUXER_BOOTSTRAP__"));
|
assert!(served.contains("window.__FLUXER_BOOTSTRAP__"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn static_dir_with(prefix_dirs: &[&str]) -> std::path::PathBuf {
|
||||||
|
static NEXT_DIR: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||||
|
let unique = NEXT_DIR.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let root = std::env::temp_dir().join(format!(
|
||||||
|
"fluxer-static-prefixes-{}-{unique}",
|
||||||
|
std::process::id()
|
||||||
|
));
|
||||||
|
std::fs::create_dir_all(&root).unwrap();
|
||||||
|
for dir in prefix_dirs {
|
||||||
|
std::fs::create_dir_all(root.join(dir)).unwrap();
|
||||||
|
}
|
||||||
|
root
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_prefixes_with_a_directory_on_disk_are_present() {
|
||||||
|
let root = static_dir_with(&["emoji", "web"]);
|
||||||
|
std::fs::write(root.join("badges"), b"a file, not a directory").unwrap();
|
||||||
|
|
||||||
|
let present = present_local_asset_prefixes(root.to_str().unwrap());
|
||||||
|
|
||||||
|
assert_eq!(&*present, &["/emoji/", "/web/"]);
|
||||||
|
std::fs::remove_dir_all(root).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn a_prefix_missing_at_startup_is_refused_without_a_file_read_slot() {
|
||||||
|
let mut state = spa_state_serving(ReleaseChannel::Stable, Some(SHIPPED_APP_SHELL)).await;
|
||||||
|
state.local_asset_prefixes = Some(Arc::from([] as [&str; 0]));
|
||||||
|
let _every_slot = state
|
||||||
|
.budgets
|
||||||
|
.local_read_slots
|
||||||
|
.clone()
|
||||||
|
.try_acquire_many_owned(LOCAL_FILE_READS_IN_FLIGHT_MAX as u32)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = spa_catch_all(
|
||||||
|
State(state),
|
||||||
|
HeaderMap::new(),
|
||||||
|
Request::builder()
|
||||||
|
.uri("/emoji/1f600.svg")
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn a_prefix_present_at_startup_is_still_served_from_disk() {
|
||||||
|
let root = static_dir_with(&["emoji"]);
|
||||||
|
std::fs::write(root.join("emoji").join("1f600.svg"), b"<svg/>").unwrap();
|
||||||
|
let mut state = spa_state_serving(ReleaseChannel::Stable, Some(SHIPPED_APP_SHELL)).await;
|
||||||
|
let mut config = (*state.config).clone();
|
||||||
|
config.static_dir = root.to_str().unwrap().to_owned();
|
||||||
|
state.local_asset_prefixes = Some(present_local_asset_prefixes(&config.static_dir));
|
||||||
|
state.config = Arc::new(config);
|
||||||
|
|
||||||
|
let response = spa_catch_all(
|
||||||
|
State(state),
|
||||||
|
HeaderMap::new(),
|
||||||
|
Request::builder()
|
||||||
|
.uri("/emoji/1f600.svg")
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
|
std::fs::remove_dir_all(root).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn the_official_instance_keeps_its_link_preview_metadata() {
|
async fn the_official_instance_keeps_its_link_preview_metadata() {
|
||||||
let state = spa_state_serving(ReleaseChannel::Stable, Some(SHIPPED_APP_SHELL)).await;
|
let state = spa_state_serving(ReleaseChannel::Stable, Some(SHIPPED_APP_SHELL)).await;
|
||||||
@@ -923,6 +1017,7 @@ mod tests {
|
|||||||
client_ip_header_name: "x-forwarded-for".to_owned(),
|
client_ip_header_name: "x-forwarded-for".to_owned(),
|
||||||
})),
|
})),
|
||||||
index_html: cached_shell.map(Arc::from),
|
index_html: cached_shell.map(Arc::from),
|
||||||
|
local_asset_prefixes: None,
|
||||||
budgets: crate::state::AppProxyBudgets::default(),
|
budgets: crate::state::AppProxyBudgets::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ pub struct AppState {
|
|||||||
pub discovery_cache: Arc<DiscoveryCache>,
|
pub discovery_cache: Arc<DiscoveryCache>,
|
||||||
pub geoip: Arc<GeoipResolver>,
|
pub geoip: Arc<GeoipResolver>,
|
||||||
pub index_html: Option<Arc<str>>,
|
pub index_html: Option<Arc<str>>,
|
||||||
|
pub local_asset_prefixes: Option<Arc<[&'static str]>>,
|
||||||
pub budgets: AppProxyBudgets,
|
pub budgets: AppProxyBudgets,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user