use std::ops::ControlFlow;
use bytes::Bytes;
use futures::FutureExt;
use futures::future::BoxFuture;
use http::HeaderMap;
use http::HeaderValue;
use http::Method;
use http::header::CONTENT_TYPE;
use mediatype::MediaType;
use mediatype::MediaTypeList;
use mediatype::names::HTML;
use mediatype::names::TEXT;
use tower::BoxError;
use tower::Layer;
use tower::Service;
use tower::ServiceBuilder;
use crate::Configuration;
use crate::configuration::Homepage;
use crate::layers::ServiceBuilderExt;
use crate::layers::async_checkpoint::AsyncCheckpointService;
use crate::services::router;
#[derive(Clone)]
pub(crate) struct StaticPageLayer {
static_page: Option<Bytes>,
}
impl StaticPageLayer {
pub(crate) fn new(configuration: &Configuration) -> Self {
let static_page = if configuration.sandbox.enabled {
Some(Bytes::from(sandbox_page_content()))
} else if configuration.homepage.enabled {
Some(Bytes::from(home_page_content(&configuration.homepage)))
} else {
None
};
Self { static_page }
}
}
impl<S> Layer<S> for StaticPageLayer
where
S: Service<router::Request, Response = router::Response, Error = BoxError>
+ Send
+ Clone
+ 'static,
<S as Service<router::Request>>::Future: Send + 'static,
{
type Service = AsyncCheckpointService<
S,
BoxFuture<'static, Result<ControlFlow<router::Response, router::Request>, BoxError>>,
router::Request,
>;
fn layer(&self, service: S) -> Self::Service {
if let Some(static_page) = &self.static_page {
let page = static_page.clone();
ServiceBuilder::new()
.checkpoint_async(move |req: router::Request| {
let page = page.clone();
async move {
let res = if req.router_request.method() == Method::GET
&& accepts_html(req.router_request.headers())
{
ControlFlow::Break(
router::Response::http_response_builder()
.response(
http::Response::builder()
.header(
CONTENT_TYPE,
HeaderValue::from_static(
mime::TEXT_HTML_UTF_8.as_ref(),
),
)
.body(router::body::from_bytes(page))
.unwrap(),
)
.context(req.context)
.build()
.unwrap(),
)
} else {
ControlFlow::Continue(req)
};
Ok(res)
}
.boxed()
})
.service(service)
} else {
ServiceBuilder::new()
.checkpoint_async(move |req: router::Request| {
async move { Ok(ControlFlow::Continue(req)) }.boxed()
})
.service(service)
}
}
}
fn accepts_html(headers: &HeaderMap) -> bool {
let text_html = MediaType::new(TEXT, HTML);
headers.get_all(&http::header::ACCEPT).iter().any(|value| {
value
.to_str()
.map(|accept_str| {
let mut list = MediaTypeList::new(accept_str);
list.any(|mime| mime.as_ref() == Ok(&text_html))
})
.unwrap_or(false)
})
}
pub(crate) fn sandbox_page_content() -> Vec<u8> {
const TEMPLATE: &str = include_str!("../../../templates/sandbox_index.html");
TEMPLATE
.replace("{{APOLLO_ROUTER_VERSION}}", std::env!("CARGO_PKG_VERSION"))
.into_bytes()
}
pub(crate) fn home_page_content(homepage_config: &Homepage) -> Vec<u8> {
const TEMPLATE: &str = include_str!("../../../templates/homepage_index.html");
let graph_ref = serde_json::to_string(&homepage_config.graph_ref).expect("cannot fail");
TEMPLATE.replace("{{GRAPH_REF}}", &graph_ref).into_bytes()
}