use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "compio")]
use compio::fs;
use http::StatusCode;
use http::header;
use tako_rs_core::body::TakoBody;
use tako_rs_core::responder::Responder;
use tako_rs_core::types::Request;
use tako_rs_core::types::Response;
#[cfg(not(feature = "compio"))]
use tokio::fs;
#[cfg(not(feature = "compio"))]
use tokio::io::AsyncReadExt;
use super::dir::ServeDir;
impl ServeDir {
async fn resolve_existing(
&self,
file_path: PathBuf,
headers: &http::HeaderMap,
) -> Option<(Response, &'static str)> {
let target = if file_path.is_dir() {
let mut chosen: Option<PathBuf> = None;
for idx in &self.index_files {
let cand = file_path.join(idx);
if !cand.is_file() {
continue;
}
if let Some(canonical) = self.canonical_within_base(&cand) {
chosen = Some(canonical);
break;
}
}
chosen?
} else {
file_path
};
if let Some((compressed, encoding)) = self.precompressed_variant(&target, headers) {
if let Some(resp) = Self::serve_file_with_encoding(&compressed, &target, encoding).await {
return Some((resp, encoding));
}
tracing::debug!(
target = %target.display(),
encoding,
"precompressed sidecar read failed, falling back to identity"
);
}
Some((Self::serve_file(&target).await?, "identity"))
}
#[cfg(not(feature = "compio"))]
async fn open_and_read_regular(path: &Path) -> Option<Vec<u8>> {
let mut file = fs::File::open(path).await.ok()?;
let meta = file.metadata().await.ok()?;
if !meta.is_file() {
return None;
}
let mut contents = Vec::with_capacity(meta.len() as usize);
file.read_to_end(&mut contents).await.ok()?;
Some(contents)
}
#[cfg(feature = "compio")]
async fn open_and_read_regular(path: &Path) -> Option<Vec<u8>> {
let meta = fs::metadata(path).await.ok()?;
if !meta.is_file() {
return None;
}
fs::read(path).await.ok()
}
async fn serve_file(file_path: &Path) -> Option<Response> {
let contents = Self::open_and_read_regular(file_path).await?;
let mime = mime_guess::from_path(file_path).first_or_octet_stream();
Some(
http::Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime.to_string())
.body(TakoBody::from(contents))
.unwrap(),
)
}
async fn serve_file_with_encoding(
compressed: &Path,
original: &Path,
encoding: &'static str,
) -> Option<Response> {
let contents = Self::open_and_read_regular(compressed).await?;
let mime = mime_guess::from_path(original).first_or_octet_stream();
Some(
http::Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, mime.to_string())
.header(header::CONTENT_ENCODING, encoding)
.header(header::VARY, "Accept-Encoding")
.body(TakoBody::from(contents))
.unwrap(),
)
}
pub async fn handle(&self, req: Request) -> impl Responder {
let path = req.uri().path();
let headers = req.headers().clone();
if let Some(file_path) = self.sanitize_path(path)
&& let Some((resp, _enc)) = self.resolve_existing(file_path, &headers).await
{
return resp;
}
if let Some(fallback) = &self.fallback
&& let Some((resp, _)) = self.resolve_existing(fallback.clone(), &headers).await
{
return resp;
}
http::Response::builder()
.status(StatusCode::NOT_FOUND)
.body(TakoBody::from("File not found"))
.unwrap()
}
}