mod body;
mod build;
mod checks;
mod clusters;
mod http;
mod http_utils;
mod tcp;
#[cfg(test)]
mod tests;
use praxis_core::health::HealthRegistry;
use tracing::warn;
use crate::{
FilterError,
body::{BodyCapabilities, BodyMode},
builtins::http::payload_processing::compression_config::CompressionConfig,
};
pub(crate) type ConditionalFilter = (
crate::any_filter::AnyFilter,
Vec<praxis_core::config::Condition>,
Vec<praxis_core::config::ResponseCondition>,
);
pub struct FilterPipeline {
body_capabilities: BodyCapabilities,
compression: Option<CompressionConfig>,
pub(crate) filters: Vec<ConditionalFilter>,
health_registry: Option<HealthRegistry>,
}
impl FilterPipeline {
pub fn apply_body_limits(
&mut self,
max_request: Option<usize>,
max_response: Option<usize>,
allow_unbounded: bool,
) -> Result<(), FilterError> {
if let Some(ceiling) = max_request {
self.body_capabilities.request_body_mode =
clamp_body_mode(self.body_capabilities.request_body_mode, ceiling);
self.body_capabilities.needs_request_body = true;
}
if let Some(ceiling) = max_response {
self.body_capabilities.response_body_mode =
clamp_body_mode(self.body_capabilities.response_body_mode, ceiling);
self.body_capabilities.needs_response_body = true;
}
check_unbounded_stream_buffer("request", self.body_capabilities.request_body_mode, allow_unbounded)?;
check_unbounded_stream_buffer("response", self.body_capabilities.response_body_mode, allow_unbounded)?;
Ok(())
}
pub fn body_capabilities(&self) -> &BodyCapabilities {
&self.body_capabilities
}
pub fn needs_body_filters(&self) -> bool {
self.body_capabilities.needs_request_body || self.body_capabilities.needs_response_body
}
pub fn len(&self) -> usize {
self.filters.len()
}
pub fn is_empty(&self) -> bool {
self.filters.is_empty()
}
pub fn compression_config(&self) -> Option<&CompressionConfig> {
self.compression.as_ref()
}
pub fn set_health_registry(&mut self, registry: HealthRegistry) {
self.health_registry = Some(registry);
}
pub fn health_registry(&self) -> Option<&HealthRegistry> {
self.health_registry.as_ref()
}
}
fn clamp_body_mode(mode: BodyMode, ceiling: usize) -> BodyMode {
match mode {
BodyMode::Buffer { max_bytes } => BodyMode::Buffer {
max_bytes: max_bytes.min(ceiling),
},
BodyMode::StreamBuffer { max_bytes } => BodyMode::StreamBuffer {
max_bytes: Some(max_bytes.map_or(ceiling, |m| m.min(ceiling))),
},
BodyMode::SizeLimit { max_bytes } => BodyMode::SizeLimit {
max_bytes: max_bytes.min(ceiling),
},
BodyMode::Stream => BodyMode::SizeLimit { max_bytes: ceiling },
}
}
fn check_unbounded_stream_buffer(direction: &str, mode: BodyMode, allow_unbounded: bool) -> Result<(), FilterError> {
if matches!(mode, BodyMode::StreamBuffer { max_bytes: None }) {
if allow_unbounded {
warn!(
direction = direction,
"StreamBuffer body mode has no size limit; \
allowed by insecure_options.allow_unbounded_body"
);
} else {
return Err(format!(
"StreamBuffer {direction} body mode has no size limit; \
set max_{direction}_body_bytes or set \
insecure_options.allow_unbounded_body: true to allow"
)
.into());
}
}
Ok(())
}