use std::sync::Arc;
use claviron::http::{RouteBody, ServiceResult, err_status};
use claviron_pipeline::factory::{BackendContext, BackendPlacement, factory};
use claviron_pipeline::{
BackendContract, BackendHandler, BackendIdentity, BackendReply,
DispatchCall, DispatchStep, HostContext, SiteRegistry,
};
use claviron_transport::owner::RequestBody;
use http::{Request, StatusCode};
use serde::Deserialize;
use tower_service::Service;
use crate::routing::PythonService;
use crate::service::PythonService as Application;
use crate::websocket::Upgrades;
pub fn register(
registry: &SiteRegistry
) -> claviron::runtime::registry::Result<()> {
registry.register_backend(factory(
"python",
BackendPlacement::AfterFiles,
prepare,
))
}
#[derive(Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct Route {
#[serde(default)]
pub(crate) webtransport: Vec<claviron::http::ext::PathPrefix>,
}
#[cfg(feature = "webtransport")]
pub(crate) fn declared(
scope: &claviron_pipeline::Site
) -> Result<Option<Route>, serde_yaml::Error> {
match scope.directive("python") {
None => Ok(None),
Some(serde_yaml::Value::Null) => Ok(Some(Route::default())),
Some(value) => serde_yaml::from_value(value.clone()).map(Some),
}
}
fn prepare(
context: &BackendContext<'_>,
route: Route,
) -> claviron::runtime::registry::Result<Vec<Arc<dyn BackendHandler>>> {
if !cfg!(feature = "webtransport") && !route.webtransport.is_empty() {
return Err(claviron::runtime::registry::Error::msg(format!(
"backend 'python' at '{}': webtransport requires the webtransport feature",
context.prefix
)));
}
let application = context
.state
.registry_ref()
.resolve::<Application>()
.ok_or_else(|| {
claviron::runtime::registry::Error::msg(format!(
"backend 'python' at '{}': the application is not registered",
context.prefix
))
})?;
let handle = application.handle();
Ok(vec![Arc::new(PythonBackend {
http: PythonService::new(Arc::clone(&handle)),
upgrades: Upgrades::new(handle),
h1_upgrade: context.transport.h1.upgrade,
})])
}
struct PythonBackend {
http: PythonService,
upgrades: Upgrades,
h1_upgrade: bool,
}
impl BackendHandler for PythonBackend {
fn contract(&self) -> BackendContract {
BackendContract {
execution: claviron::ExecutorSpec::Dedicated(
claviron::ExecutorKind::CurrentThread,
),
identity: BackendIdentity::Request,
reply: BackendReply::Owner,
}
}
}
impl DispatchStep for PythonBackend {
fn call(
&self,
request: Request<RouteBody>,
_: Option<HostContext<'_>>,
) -> DispatchCall {
if let Some(offer) =
request.extensions().get::<claviron::http::socket::Offer>()
{
if offer.info().kind
== claviron::http::socket::SocketKind::WebTransport
&& offer.info().carrier == claviron::http::socket::Carrier::H3
{
return DispatchCall::ready(ServiceResult::Handled(
match offer.claim() {
Some(claim) => claim.reach(),
None => err_status(StatusCode::FORBIDDEN),
},
));
}
if offer.info().carrier == claviron::http::socket::Carrier::H1
&& !self.h1_upgrade
{
tracing::debug!(
path = request.uri().path(),
"a WebSocket upgrade was refused: this transport has no h1.upgrade"
);
return DispatchCall::ready(ServiceResult::Handled(
err_status(StatusCode::FORBIDDEN),
));
}
let mut upgrades = self.upgrades.clone();
let request = request.map(RequestBody::new);
return DispatchCall::response(Box::pin(async move {
Ok(match upgrades.call(request).await {
Ok(response) => response,
Err(never) => match never {},
})
}));
}
let http = self.http.clone();
DispatchCall::response(Box::pin(async move {
Ok(http.handle(request).await)
}))
}
}