use std::collections::HashMap;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::task::{Context, Poll};
mod index_list;
use hyper::service::Service;
use hyper::{Body, Method, Request, Response};
use hyper_staticfile::{resolve_path, ResolveResult, ResponseBuilder};
use tracing::{Instrument, Span};
use uuid::Uuid;
use wick_config::config::{BoundIdentifier, StaticRouterConfig, WickRouter};
use wick_runtime::Runtime;
use wick_trigger::error::{Error, ErrorKind};
use wick_trigger::resources::{Resource, ResourceKind};
use self::index_list::StaticError;
use crate::http::middleware::resolve_middleware_components;
use crate::http::{BoxFuture, HttpError, HttpRouter, RawRouter, RawRouterHandler};
#[derive()]
#[must_use]
pub(super) struct StaticRouter {
handler: Static,
}
impl StaticRouter {
pub(super) fn new(root: PathBuf, strip: Option<String>, indexes: bool, fallback: Option<String>) -> Self {
debug!(directory = %root.display(), "http:static:serving");
let handler = Static::new(root, strip, indexes, fallback);
Self { handler }
}
}
impl RawRouter for StaticRouter {
fn handle(
&self,
_tx_id: Uuid,
_remote_addr: SocketAddr,
_runtime: Runtime,
request: Request<Body>,
span: &Span,
) -> BoxFuture<Result<Response<Body>, HttpError>> {
let span = info_span!(parent: span, "static");
let handler = self.handler.clone();
let fut = async move {
let response = handler
.serve(request)
.instrument(span)
.await
.map_err(|e| HttpError::OperationError(e.to_string()))?;
Ok(response)
};
Box::pin(fut)
}
}
fn create_response<B>(request: &Request<B>, result: ResolveResult) -> Result<Response<Body>, StaticError>
where
B: Send + Sync + 'static,
{
#[allow(clippy::expect_used)]
Ok(
ResponseBuilder::new()
.request(request)
.build(result)
.expect("unable to build response"),
)
}
#[derive(Clone)]
struct Static {
root: PathBuf,
strip: Option<String>,
fallback: Option<String>,
indexes: bool,
}
impl Static {
fn new(root: impl Into<PathBuf>, strip: Option<String>, indexes: bool, fallback: Option<String>) -> Self {
let root = root.into();
Static {
root,
strip,
indexes,
fallback,
}
}
async fn serve<B>(self, request: Request<B>) -> Result<Response<Body>, StaticError>
where
B: Send + Sync + 'static,
{
let Self {
root,
strip,
indexes,
fallback,
} = self;
match *request.method() {
Method::HEAD | Method::GET => {}
_ => {
return create_response(&request, ResolveResult::MethodNotMatched);
}
}
let path = strip.map_or_else(
|| request.uri().path(),
|path| {
if path.len() > 1 {
request.uri().path().trim_start_matches(&path)
} else {
request.uri().path()
}
},
);
let result = resolve_path(root.clone(), path).await;
match result {
Ok(ResolveResult::Found(_, _, _)) => create_response(&request, result?),
Ok(ResolveResult::IsDirectory) if indexes => index_list::create(&root, &request).await,
Ok(ResolveResult::NotFound) if (indexes && request.uri().path() == "/") => {
index_list::create(&root, &request).await
}
_ => {
if let Some(fb) = &fallback {
let fallback_result = resolve_path(root.clone(), fb).await;
create_response(&request, fallback_result?)
} else {
create_response(&request, result?)
}
}
}
}
}
impl<B: Send + Sync + 'static> Service<Request<B>> for Static {
type Response = Response<Body>;
type Error = StaticError;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, request: Request<B>) -> Self::Future {
Box::pin(self.clone().serve(request))
}
}
pub(crate) fn register_static_router(
index: usize,
resources: Arc<HashMap<BoundIdentifier, Resource>>,
router_config: &StaticRouterConfig,
) -> Result<HttpRouter, Error> {
trace!(index, "registering static router");
let middleware = resolve_middleware_components(router_config)?;
let volume = resources
.get(router_config.volume())
.ok_or_else(|| Error::new_context("http", ErrorKind::ResourceNotFound(router_config.volume().id().into())))?;
let volume = match volume {
Resource::Volume(s) => s.clone(),
_ => {
return Err(Error::new_context(
"http",
ErrorKind::InvalidResourceType(ResourceKind::Volume, volume.kind()),
))
}
};
let fallback = router_config.fallback().cloned();
let indexes = router_config.indexes();
let router = StaticRouter::new(volume, Some(router_config.path().to_owned()), indexes, fallback);
Ok(HttpRouter::Raw(RawRouterHandler {
path: router_config.path().to_owned(),
component: Arc::new(router),
middleware,
}))
}