use std::marker::PhantomData;
use poem::{
http::{header, Method, StatusCode},
Endpoint, Error, Request, Response,
};
use rust_embed::RustEmbed;
pub struct EmbeddedFileEndpoint<E: RustEmbed + Send + Sync> {
_embed: PhantomData<E>,
path: String,
}
impl<E: RustEmbed + Send + Sync> EmbeddedFileEndpoint<E> {
pub fn new(path: &str) -> Self {
EmbeddedFileEndpoint {
_embed: PhantomData,
path: path.to_owned(),
}
}
}
impl<E: RustEmbed + Send + Sync> Endpoint for EmbeddedFileEndpoint<E> {
type Output = Response;
async fn call(&self, req: Request) -> Result<Self::Output, Error> {
if req.method() != Method::GET {
return Err(StatusCode::METHOD_NOT_ALLOWED.into());
}
match E::get(&self.path) {
Some(content) => {
let hash = hex::encode(content.metadata.sha256_hash());
if req
.headers()
.get(header::IF_NONE_MATCH)
.map(|etag| etag.to_str().unwrap_or("000000").eq(&hash))
.unwrap_or(false)
{
return Err(StatusCode::NOT_MODIFIED.into());
}
let body: Vec<u8> = content.data.into();
let mime = mime_guess::from_path(&self.path).first_or_octet_stream();
Ok(Response::builder()
.header(header::CONTENT_TYPE, mime.as_ref())
.header(header::ETAG, hash)
.body(body))
}
None => Err(StatusCode::NOT_FOUND.into()),
}
}
}
pub struct EmbeddedFilesEndpoint<E: RustEmbed + Send + Sync> {
_embed: PhantomData<E>,
}
impl<E: RustEmbed + Sync + Send> Default for EmbeddedFilesEndpoint<E> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<E: RustEmbed + Send + Sync> EmbeddedFilesEndpoint<E> {
pub fn new() -> Self {
EmbeddedFilesEndpoint { _embed: PhantomData }
}
}
impl<E: RustEmbed + Send + Sync> Endpoint for EmbeddedFilesEndpoint<E> {
type Output = Response;
async fn call(&self, req: Request) -> Result<Self::Output, Error> {
let path = req.uri().path().trim_start_matches('/');
let original_path = req.original_uri().path();
let original_end_with_slash = original_path.ends_with('/');
use header::LOCATION;
log::info!("path: {}", path);
if path.is_empty() && !original_end_with_slash {
Ok(Response::builder()
.status(StatusCode::FOUND)
.header(LOCATION, format!("{}/", original_path))
.finish())
} else if original_end_with_slash {
let path = format!("{}index.html", path);
EmbeddedFileEndpoint::<E>::new(&path).call(req).await
} else if E::get(path).is_some() {
EmbeddedFileEndpoint::<E>::new(path).call(req).await
} else if E::get(&format!("{}/index.html", path)).is_some() {
Ok(Response::builder()
.status(StatusCode::FOUND)
.header(LOCATION, format!("{}/", original_path))
.finish())
} else {
log::info!("path: {path} not found, fallback to index.html");
EmbeddedFileEndpoint::<E>::new(&format!("index.html")).call(req).await
}
}
}