فهرست منبع

add salvo intigration

Evan Almloff 2 سال پیش
والد
کامیت
83d513ef36

+ 0 - 0
packages/server/examples/hello-world/.gitignore → packages/server/examples/axum-hello-world/.gitignore


+ 1 - 1
packages/server/examples/hello-world/Cargo.toml → packages/server/examples/axum-hello-world/Cargo.toml

@@ -1,5 +1,5 @@
 [package]
-name = "hello-world"
+name = "axum-hello-world"
 version = "0.1.0"
 edition = "2021"
 

+ 2 - 2
packages/server/examples/hello-world/src/main.rs → packages/server/examples/axum-hello-world/src/main.rs

@@ -1,5 +1,5 @@
 //! Run with:
-//! 
+//!
 //! ```sh
 //! dioxus build --features web
 //! cargo run --features ssr
@@ -24,7 +24,7 @@ fn main() {
                     .serve(
                         axum::Router::new()
                             .serve_dioxus_application(
-                                ServeConfig::new(app).head(r#"<title>Hello World!</title>"#),
+                                ServeConfig::new(app, ()).head(r#"<title>Hello World!</title>"#),
                             )
                             .into_make_service(),
                     )

+ 2 - 0
packages/server/examples/salvo-hello-world/.gitignore

@@ -0,0 +1,2 @@
+dist
+target

+ 20 - 0
packages/server/examples/salvo-hello-world/Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "salvo-hello-world"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dioxus-web = { path = "../../../web", features=["hydrate"], optional = true }
+dioxus = { path = "../../../dioxus" }
+dioxus-server = { path = "../../" }
+tokio = { version = "1.27.0", features = ["full"], optional = true }
+serde = "1.0.159"
+tracing-subscriber = "0.3.16"
+tracing = "0.1.37"
+salvo = { version = "0.37.9", optional = true }
+
+[features]
+ssr = ["salvo", "tokio", "dioxus-server/ssr", "dioxus-server/salvo"]
+web = ["dioxus-web"]

+ 68 - 0
packages/server/examples/salvo-hello-world/src/main.rs

@@ -0,0 +1,68 @@
+//! Run with:
+//!
+//! ```sh
+//! dioxus build --features web
+//! cargo run --features ssr
+//! ```
+
+#![allow(non_snake_case)]
+use dioxus::prelude::*;
+use dioxus_server::prelude::*;
+
+fn main() {
+    #[cfg(feature = "web")]
+    dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
+    #[cfg(feature = "ssr")]
+    {
+        use salvo::prelude::*;
+        PostServerData::register().unwrap();
+        GetServerData::register().unwrap();
+        tokio::runtime::Runtime::new()
+            .unwrap()
+            .block_on(async move {
+                let router = Router::new().serve_dioxus_application(
+                    ServeConfig::new(app, ()).head(r#"<title>Hello World!</title>"#),
+                );
+                Server::new(TcpListener::bind("127.0.0.1:8080"))
+                    .serve(router)
+                    .await;
+            });
+    }
+}
+
+fn app(cx: Scope) -> Element {
+    let mut count = use_state(cx, || 0);
+    let text = use_state(cx, || "...".to_string());
+
+    cx.render(rsx! {
+        h1 { "High-Five counter: {count}" }
+        button { onclick: move |_| count += 1, "Up high!" }
+        button { onclick: move |_| count -= 1, "Down low!" }
+        button {
+            onclick: move |_| {
+                to_owned![text];
+                async move {
+                    if let Ok(data) = get_server_data().await {
+                        println!("Client received: {}", data);
+                        text.set(data.clone());
+                        post_server_data(data).await.unwrap();
+                    }
+                }
+            },
+            "Run a server function"
+        }
+        "Server said: {text}"
+    })
+}
+
+#[server(PostServerData)]
+async fn post_server_data(data: String) -> Result<(), ServerFnError> {
+    println!("Server received: {}", data);
+
+    Ok(())
+}
+
+#[server(GetServerData)]
+async fn get_server_data() -> Result<String, ServerFnError> {
+    Ok("Hello from the server!".to_string())
+}

+ 147 - 18
packages/server/src/adapters/salvo_adapter.rs

@@ -1,25 +1,154 @@
-use futures_util::{SinkExt, StreamExt};
-use salvo::ws::{Message, WebSocket};
-
-use crate::{LiveViewError, LiveViewSocket};
-
-/// Convert a salvo websocket into a LiveViewSocket
-///
-/// This is required to launch a LiveView app using the warp web framework
-pub fn salvo_socket(ws: WebSocket) -> impl LiveViewSocket {
-    ws.map(transform_rx)
-        .with(transform_tx)
-        .sink_map_err(|_| LiveViewError::SendingFailed)
+use std::{error::Error, sync::Arc};
+
+use hyper::{http::HeaderValue, StatusCode};
+use salvo::{
+    async_trait, handler, serve_static::StaticDir, Depot, FlowCtrl, Handler, Request, Response,
+    Router,
+};
+use server_fn::{Payload, ServerFunctionRegistry};
+use tokio::task::spawn_blocking;
+
+use crate::{
+    dioxus_ssr_html,
+    serve::ServeConfig,
+    server_fn::{DioxusServerContext, DioxusServerFnRegistry, ServerFnTraitObj},
+};
+
+pub trait DioxusRouterExt {
+    fn register_server_fns(self, server_fn_route: &'static str) -> Self;
+    fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
+        self,
+        cfg: ServeConfig<P>,
+    ) -> Self;
+}
+
+impl DioxusRouterExt for Router {
+    fn register_server_fns(self, server_fn_route: &'static str) -> Self {
+        let mut router = self;
+        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}");
+            router = router.push(Router::with_path(&full_route).post(ServerFnHandler {
+                server_context: DioxusServerContext {},
+                function: func,
+            }));
+        }
+        router
+    }
+
+    fn serve_dioxus_application<P: Clone + Send + Sync + 'static>(
+        self,
+        cfg: ServeConfig<P>,
+    ) -> Self {
+        // Serve the dist folder and the index.html file
+        let serve_dir = StaticDir::new(["dist"]);
+
+        self.register_server_fns(cfg.server_fn_route.unwrap_or_default())
+            .push(Router::with_path("/").get(SSRHandler { cfg }))
+            .push(Router::with_path("<**path>").get(serve_dir))
+    }
+}
+
+struct SSRHandler<P: Clone> {
+    cfg: ServeConfig<P>,
+}
+
+#[async_trait]
+impl<P: Clone + Send + Sync + 'static> Handler for SSRHandler<P> {
+    async fn handle(
+        &self,
+        _req: &mut Request,
+        _depot: &mut Depot,
+        res: &mut Response,
+        _flow: &mut FlowCtrl,
+    ) {
+        res.write_body(dioxus_ssr_html(&self.cfg)).unwrap();
+    }
+}
+
+struct ServerFnHandler {
+    server_context: DioxusServerContext,
+    function: Arc<ServerFnTraitObj>,
 }
 
-fn transform_rx(message: Result<Message, salvo::Error>) -> Result<String, LiveViewError> {
-    let as_bytes = message.map_err(|_| LiveViewError::SendingFailed)?;
+#[handler]
+impl ServerFnHandler {
+    async fn handle(&self, req: &mut Request, _depot: &mut Depot, res: &mut Response) {
+        let Self {
+            server_context,
+            function,
+        } = self;
+
+        let body = hyper::body::to_bytes(req.body_mut().unwrap()).await;
+        let Ok(body)=body else {
+            handle_error(body.err().unwrap(), res);
+            return;
+        };
+        let headers = req.headers();
+
+        // 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();
+        let function = function.clone();
+        let server_context = server_context.clone();
+        spawn_blocking({
+            move || {
+                tokio::runtime::Runtime::new()
+                    .expect("couldn't spawn runtime")
+                    .block_on(async move {
+                        let resp = function(server_context, &body).await;
+
+                        resp_tx.send(resp).unwrap();
+                    })
+            }
+        });
+        let result = resp_rx.await.unwrap();
 
-    let msg = String::from_utf8(as_bytes.into_bytes()).map_err(|_| LiveViewError::SendingFailed)?;
+        match result {
+            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());
+                if accept_header == Some("application/json")
+                    || accept_header
+                        == Some(
+                            "application/\
+                                x-www-form-urlencoded",
+                        )
+                    || accept_header == Some("application/cbor")
+                {
+                    res.set_status_code(StatusCode::OK);
+                }
 
-    Ok(msg)
+                match serialized {
+                    Payload::Binary(data) => {
+                        res.headers_mut()
+                            .insert("Content-Type", HeaderValue::from_static("application/cbor"));
+                        res.write_body(data).unwrap();
+                    }
+                    Payload::Url(data) => {
+                        res.headers_mut().insert(
+                            "Content-Type",
+                            HeaderValue::from_static(
+                                "application/\
+                                    x-www-form-urlencoded",
+                            ),
+                        );
+                        res.render(data);
+                    }
+                    Payload::Json(data) => {
+                        res.headers_mut()
+                            .insert("Content-Type", HeaderValue::from_static("application/json"));
+                        res.render(data);
+                    }
+                }
+            }
+            Err(err) => handle_error(err, res),
+        }
+    }
 }
 
-async fn transform_tx(message: String) -> Result<Message, salvo::Error> {
-    Ok(Message::text(message))
+fn handle_error(error: impl Error + Send + Sync, res: &mut Response) {
+    let mut resp_err = Response::new();
+    resp_err.set_status_code(StatusCode::INTERNAL_SERVER_ERROR);
+    resp_err.render(format!("Internal Server Error: {}", error));
+    *res = resp_err;
 }