use bytes::Bytes;
use http::header;
use pingora_core::protocols::http::HttpTask;
use pingora_core::Error;
use pingora_http::{ResponseHeader, StatusCode};
use pingora_proxy::Session;
use std::path::{Path, PathBuf};
use crate::compression_algorithm::{find_matches, CompressionAlgorithm};
pub(crate) struct Compression<'a> {
precompressed: &'a [CompressionAlgorithm],
precompressed_active: Option<CompressionAlgorithm>,
dynamic: bool,
dynamic_active: bool,
}
impl<'a> Compression<'a> {
pub(crate) fn new(session: &Session, precompressed: &'a [CompressionAlgorithm]) -> Self {
Self {
precompressed,
precompressed_active: None,
dynamic: session.downstream_compression.is_enabled(),
dynamic_active: false,
}
}
pub(crate) fn rewrite_path(&mut self, session: &Session, path: &Path) -> Option<PathBuf> {
if self.precompressed.is_empty() {
return None;
}
let filename = path.file_name()?;
let requested = session.req_header().headers.get(header::ACCEPT_ENCODING)?;
let overlap = find_matches(requested.to_str().ok()?, self.precompressed);
for algorithm in overlap {
let mut candidate_name = filename.to_os_string();
candidate_name.push(".");
candidate_name.push(algorithm.ext());
let mut candidate_path = path.to_path_buf();
candidate_path.set_file_name(candidate_name);
if candidate_path.is_file() {
self.precompressed_active = Some(algorithm);
return Some(candidate_path);
}
}
None
}
pub(crate) fn transform_header(
&mut self,
session: &mut Session,
mut header: Box<ResponseHeader>,
) -> Result<Box<ResponseHeader>, Box<Error>> {
let mut header = if header.status != StatusCode::OK
&& header.status != StatusCode::PARTIAL_CONTENT
{
header
} else if let Some(algorithm) = self.precompressed_active {
header.insert_header(header::CONTENT_ENCODING, algorithm.name())?;
header
} else if header.status == StatusCode::OK {
self.dynamic_active = true;
session
.downstream_compression
.request_filter(session.downstream_session.req_header());
let mut task = HttpTask::Header(header, false);
session.downstream_compression.response_filter(&mut task);
if let HttpTask::Header(mut header, false) = task {
if header.headers.get(header::CONTENT_ENCODING).is_some() {
let _ = header.insert_header(header::ACCEPT_RANGES, "none");
}
header
} else {
panic!("Unexpected: compression response filter replaced header task by {task:?}");
}
} else {
header
};
if !self.precompressed.is_empty() || self.dynamic {
header.insert_header(header::VARY, "Accept-Encoding")?;
}
Ok(header)
}
pub(crate) fn transform_body(
&self,
session: &mut Session,
bytes: Option<Bytes>,
) -> Option<Bytes> {
if !self.dynamic_active {
return bytes;
}
let mut task = if let Some(bytes) = bytes {
HttpTask::Body(Some(bytes), false)
} else {
HttpTask::Done
};
session.downstream_compression.response_filter(&mut task);
if let HttpTask::Body(Some(bytes), _) = task {
if bytes.is_empty() {
None
} else {
Some(bytes)
}
} else {
None
}
}
}