|
@@ -1,28 +1,136 @@
|
|
|
-use crate::{LiveViewError, LiveViewSocket};
|
|
|
-use futures_util::{SinkExt, StreamExt};
|
|
|
-use warp::ws::{Message, WebSocket};
|
|
|
-
|
|
|
-/// Convert a warp websocket into a LiveViewSocket
|
|
|
-///
|
|
|
-/// This is required to launch a LiveView app using the warp web framework
|
|
|
-pub fn warp_socket(ws: WebSocket) -> impl LiveViewSocket {
|
|
|
- ws.map(transform_rx)
|
|
|
- .with(transform_tx)
|
|
|
- .sink_map_err(|_| LiveViewError::SendingFailed)
|
|
|
+use std::{error::Error, sync::Arc};
|
|
|
+
|
|
|
+use server_fn::{Payload, ServerFunctionRegistry};
|
|
|
+use tokio::task::spawn_blocking;
|
|
|
+use warp::{
|
|
|
+ filters::BoxedFilter,
|
|
|
+ http::{Response, StatusCode},
|
|
|
+ hyper::{body::Bytes, HeaderMap},
|
|
|
+ path, Filter, Reply,
|
|
|
+};
|
|
|
+
|
|
|
+use crate::{
|
|
|
+ dioxus_ssr_html,
|
|
|
+ serve::ServeConfig,
|
|
|
+ server_fn::{DioxusServerContext, DioxusServerFnRegistry, ServerFnTraitObj},
|
|
|
+};
|
|
|
+
|
|
|
+pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl Reply,)> {
|
|
|
+ let mut filter: Option<BoxedFilter<(_,)>> = None;
|
|
|
+ for server_fn_path in DioxusServerFnRegistry::paths_registered() {
|
|
|
+ let func = DioxusServerFnRegistry::get(server_fn_path).unwrap();
|
|
|
+ let full_route = format!("{server_fn_route}/{server_fn_path}")
|
|
|
+ .trim_start_matches('/')
|
|
|
+ .to_string();
|
|
|
+ let route = path(full_route)
|
|
|
+ .and(warp::post())
|
|
|
+ .and(warp::header::headers_cloned())
|
|
|
+ .and(warp::body::bytes())
|
|
|
+ .and_then(move |headers: HeaderMap, body| {
|
|
|
+ let func = func.clone();
|
|
|
+ async move { server_fn_handler(DioxusServerContext {}, func, headers, body).await }
|
|
|
+ })
|
|
|
+ .boxed();
|
|
|
+ if let Some(boxed_filter) = filter.take() {
|
|
|
+ filter = Some(boxed_filter.or(route).unify().boxed());
|
|
|
+ } else {
|
|
|
+ filter = Some(route.boxed());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ filter.expect("No server functions found")
|
|
|
+}
|
|
|
+
|
|
|
+pub fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
|
|
|
+ cfg: ServeConfig<P>,
|
|
|
+) -> BoxedFilter<(impl Reply,)> {
|
|
|
+ // Serve the dist folder and the index.html file
|
|
|
+ let serve_dir = warp::fs::dir("./dist");
|
|
|
+
|
|
|
+ register_server_fns(cfg.server_fn_route.unwrap_or_default())
|
|
|
+ .or(warp::path::end()
|
|
|
+ .and(warp::get())
|
|
|
+ .map(move || warp::reply::html(dioxus_ssr_html(&cfg))))
|
|
|
+ .or(serve_dir)
|
|
|
+ .boxed()
|
|
|
}
|
|
|
|
|
|
-fn transform_rx(message: Result<Message, warp::Error>) -> Result<String, LiveViewError> {
|
|
|
- // destructure the message into the buffer we got from warp
|
|
|
- let msg = message
|
|
|
- .map_err(|_| LiveViewError::SendingFailed)?
|
|
|
- .into_bytes();
|
|
|
+#[derive(Debug)]
|
|
|
+struct FailedToReadBody(String);
|
|
|
+
|
|
|
+impl warp::reject::Reject for FailedToReadBody {}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
+struct RecieveFailed(String);
|
|
|
+
|
|
|
+impl warp::reject::Reject for RecieveFailed {}
|
|
|
+
|
|
|
+async fn server_fn_handler(
|
|
|
+ server_context: DioxusServerContext,
|
|
|
+ function: Arc<ServerFnTraitObj>,
|
|
|
+ headers: HeaderMap,
|
|
|
+ body: Bytes,
|
|
|
+) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
|
|
+ // Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime
|
|
|
+ let (resp_tx, resp_rx) = tokio::sync::oneshot::channel();
|
|
|
+ spawn_blocking({
|
|
|
+ move || {
|
|
|
+ tokio::runtime::Runtime::new()
|
|
|
+ .expect("couldn't spawn runtime")
|
|
|
+ .block_on(async {
|
|
|
+ let resp = match function(server_context, &body).await {
|
|
|
+ Ok(serialized) => {
|
|
|
+ // if this is Accept: application/json then send a serialized JSON response
|
|
|
+ let accept_header =
|
|
|
+ headers.get("Accept").and_then(|value| value.to_str().ok());
|
|
|
+ let mut res = Response::builder();
|
|
|
+ if accept_header == Some("application/json")
|
|
|
+ || accept_header
|
|
|
+ == Some(
|
|
|
+ "application/\
|
|
|
+ x-www-form-urlencoded",
|
|
|
+ )
|
|
|
+ || accept_header == Some("application/cbor")
|
|
|
+ {
|
|
|
+ res = res.status(StatusCode::OK);
|
|
|
+ }
|
|
|
+
|
|
|
+ let resp = match serialized {
|
|
|
+ Payload::Binary(data) => res
|
|
|
+ .header("Content-Type", "application/cbor")
|
|
|
+ .body(Bytes::from(data)),
|
|
|
+ Payload::Url(data) => res
|
|
|
+ .header(
|
|
|
+ "Content-Type",
|
|
|
+ "application/\
|
|
|
+ x-www-form-urlencoded",
|
|
|
+ )
|
|
|
+ .body(Bytes::from(data)),
|
|
|
+ Payload::Json(data) => res
|
|
|
+ .header("Content-Type", "application/json")
|
|
|
+ .body(Bytes::from(data)),
|
|
|
+ };
|
|
|
|
|
|
- // transform it back into a string, saving us the allocation
|
|
|
- let msg = String::from_utf8(msg).map_err(|_| LiveViewError::SendingFailed)?;
|
|
|
+ Box::new(resp.unwrap())
|
|
|
+ }
|
|
|
+ Err(e) => report_err(e),
|
|
|
+ };
|
|
|
|
|
|
- Ok(msg)
|
|
|
+ if resp_tx.send(resp).is_err() {
|
|
|
+ eprintln!("Error sending response");
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ resp_rx.await.map_err(|err| {
|
|
|
+ warp::reject::custom(RecieveFailed(format!("Failed to recieve response {err}")))
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
-async fn transform_tx(message: String) -> Result<Message, warp::Error> {
|
|
|
- Ok(Message::text(message))
|
|
|
+fn report_err<E: Error>(e: E) -> Box<dyn warp::Reply> {
|
|
|
+ Box::new(
|
|
|
+ Response::builder()
|
|
|
+ .status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
|
+ .body(format!("Error: {}", e))
|
|
|
+ .unwrap(),
|
|
|
+ ) as Box<dyn warp::Reply>
|
|
|
}
|