mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-30 16:09:12 +00:00
create server package
This commit is contained in:
parent
c5085496db
commit
7f6f6fb8c8
8 changed files with 258 additions and 0 deletions
|
@ -21,6 +21,7 @@ members = [
|
||||||
"packages/rsx-rosetta",
|
"packages/rsx-rosetta",
|
||||||
"packages/signals",
|
"packages/signals",
|
||||||
"packages/hot-reload",
|
"packages/hot-reload",
|
||||||
|
"packages/server",
|
||||||
"docs/guide",
|
"docs/guide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
21
packages/server/Cargo.toml
Normal file
21
packages/server/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
server_fn = { version = "0.2.4", features = ["stable"] }
|
||||||
|
|
||||||
|
# warp
|
||||||
|
warp = { version = "0.3.3", optional = true }
|
||||||
|
|
||||||
|
# axum
|
||||||
|
axum = { version = "0.6.1", optional = true, features = ["ws"] }
|
||||||
|
|
||||||
|
# salvo
|
||||||
|
salvo = { version = "0.37.7", optional = true, features = ["ws"] }
|
||||||
|
serde = "1.0.159"
|
||||||
|
|
||||||
|
dioxus = { path = "../dioxus", version = "^0.3.0" }
|
8
packages/server/server_macro/Cargo.toml
Normal file
8
packages/server/server_macro/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "server_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
67
packages/server/server_macro/src/lib.rs
Normal file
67
packages/server/server_macro/src/lib.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/// Declares that a function is a [server function](leptos_server). This means that
|
||||||
|
/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
|
||||||
|
///
|
||||||
|
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
|
||||||
|
/// are enabled), it will instead make a network request to the server.
|
||||||
|
///
|
||||||
|
/// You can specify one, two, or three arguments to the server function:
|
||||||
|
/// 1. **Required**: A type name that will be used to identify and register the server function
|
||||||
|
/// (e.g., `MyServerFn`).
|
||||||
|
/// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered
|
||||||
|
/// (e.g., `"/api"`). Defaults to `"/"`.
|
||||||
|
/// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
|
||||||
|
/// serialization) or `"Url"` (specifying that it should be use a URL-encoded form-data string).
|
||||||
|
/// Defaults to `"Url"`. If you want to use this server function to power a `<form>` that will
|
||||||
|
/// work without WebAssembly, the encoding must be `"Url"`.
|
||||||
|
///
|
||||||
|
/// The server function itself can take any number of arguments, each of which should be serializable
|
||||||
|
/// and deserializable with `serde`. Optionally, its first argument can be a Leptos [Scope](leptos_reactive::Scope),
|
||||||
|
/// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
|
||||||
|
/// server-side context into the server function.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// # use leptos::*; use serde::{Serialize, Deserialize};
|
||||||
|
/// # #[derive(Serialize, Deserialize)]
|
||||||
|
/// # pub struct Post { }
|
||||||
|
/// #[server(ReadPosts, "/api")]
|
||||||
|
/// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
|
||||||
|
/// // do some work on the server to access the database
|
||||||
|
/// todo!()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note the following:
|
||||||
|
/// - You must **register** the server function by calling `T::register()` somewhere in your main function.
|
||||||
|
/// - **Server functions must be `async`.** Even if the work being done inside the function body
|
||||||
|
/// can run synchronously on the server, from the client’s perspective it involves an asynchronous
|
||||||
|
/// function call.
|
||||||
|
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
|
||||||
|
/// inside the function body can’t fail, the processes of serialization/deserialization and the
|
||||||
|
/// network call are fallible.
|
||||||
|
/// - **Return types must be [Serializable](leptos_reactive::Serializable).**
|
||||||
|
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
|
||||||
|
/// need to deserialize the result to return it to the client.
|
||||||
|
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
|
||||||
|
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
|
||||||
|
/// They are serialized as an `application/x-www-form-urlencoded`
|
||||||
|
/// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
|
||||||
|
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
|
||||||
|
/// - **The [Scope](leptos_reactive::Scope) comes from the server.** Optionally, the first argument of a server function
|
||||||
|
/// can be a Leptos [Scope](leptos_reactive::Scope). This scope can be used to inject dependencies like the HTTP request
|
||||||
|
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||||
|
let context = ServerContext {
|
||||||
|
ty: syn::parse_quote!(Scope),
|
||||||
|
path: syn::parse_quote!(::leptos::Scope),
|
||||||
|
};
|
||||||
|
match server_macro_impl(
|
||||||
|
args.into(),
|
||||||
|
s.into(),
|
||||||
|
Some(context),
|
||||||
|
Some(syn::parse_quote!(::leptos::server_fn)),
|
||||||
|
) {
|
||||||
|
Err(e) => e.to_compile_error().into(),
|
||||||
|
Ok(s) => s.to_token_stream().into(),
|
||||||
|
}
|
||||||
|
}
|
23
packages/server/src/adapters/axum_adapter.rs
Normal file
23
packages/server/src/adapters/axum_adapter.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::{LiveViewError, LiveViewSocket};
|
||||||
|
use axum::extract::ws::{Message, WebSocket};
|
||||||
|
use futures_util::{SinkExt, StreamExt};
|
||||||
|
|
||||||
|
/// Convert a warp websocket into a LiveViewSocket
|
||||||
|
///
|
||||||
|
/// This is required to launch a LiveView app using the warp web framework
|
||||||
|
pub fn axum_socket(ws: WebSocket) -> impl LiveViewSocket {
|
||||||
|
ws.map(transform_rx)
|
||||||
|
.with(transform_tx)
|
||||||
|
.sink_map_err(|_| LiveViewError::SendingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_rx(message: Result<Message, axum::Error>) -> Result<String, LiveViewError> {
|
||||||
|
message
|
||||||
|
.map_err(|_| LiveViewError::SendingFailed)?
|
||||||
|
.into_text()
|
||||||
|
.map_err(|_| LiveViewError::SendingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transform_tx(message: String) -> Result<Message, axum::Error> {
|
||||||
|
Ok(Message::Text(message))
|
||||||
|
}
|
25
packages/server/src/adapters/salvo_adapter.rs
Normal file
25
packages/server/src/adapters/salvo_adapter.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_rx(message: Result<Message, salvo::Error>) -> Result<String, LiveViewError> {
|
||||||
|
let as_bytes = message.map_err(|_| LiveViewError::SendingFailed)?;
|
||||||
|
|
||||||
|
let msg = String::from_utf8(as_bytes.into_bytes()).map_err(|_| LiveViewError::SendingFailed)?;
|
||||||
|
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transform_tx(message: String) -> Result<Message, salvo::Error> {
|
||||||
|
Ok(Message::text(message))
|
||||||
|
}
|
28
packages/server/src/adapters/warp_adapter.rs
Normal file
28
packages/server/src/adapters/warp_adapter.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// transform it back into a string, saving us the allocation
|
||||||
|
let msg = String::from_utf8(msg).map_err(|_| LiveViewError::SendingFailed)?;
|
||||||
|
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transform_tx(message: String) -> Result<Message, warp::Error> {
|
||||||
|
Ok(Message::text(message))
|
||||||
|
}
|
85
packages/server/src/lib.rs
Normal file
85
packages/server/src/lib.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use serde::{de::DeserializeOwned, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
// We use deref specialization to make it possible to pass either a value that implements
|
||||||
|
pub trait SerializeToRemoteWrapper {
|
||||||
|
fn serialize_to_remote<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Serialize> SerializeToRemoteWrapper for &T {
|
||||||
|
fn serialize_to_remote<S: Serializer>(
|
||||||
|
&self,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> {
|
||||||
|
self.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: SerializeToRemote> SerializeToRemoteWrapper for &mut &S {
|
||||||
|
fn serialize_to_remote<S2: Serializer>(
|
||||||
|
&self,
|
||||||
|
serializer: S2,
|
||||||
|
) -> Result<<S2 as Serializer>::Ok, <S2 as Serializer>::Error> {
|
||||||
|
(**self).serialize_to_remote(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SerializeToRemote {
|
||||||
|
fn serialize_to_remote<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Serialize> SerializeToRemote for UseState<S> {
|
||||||
|
fn serialize_to_remote<S2: Serializer>(
|
||||||
|
&self,
|
||||||
|
serializer: S2,
|
||||||
|
) -> Result<<S2 as Serializer>::Ok, <S2 as Serializer>::Error> {
|
||||||
|
self.current().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use deref specialization to make it possible to pass either a value that implements
|
||||||
|
pub trait DeserializeOnRemoteWrapper {
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
fn deserialize_on_remote<'a, D: Deserializer<'a>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Self::Output, D::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: DeserializeOwned> DeserializeOnRemoteWrapper for &T {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn deserialize_on_remote<'a, D: Deserializer<'a>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Self::Output, D::Error> {
|
||||||
|
T::deserialize(deserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: DeserializeOnRemote> DeserializeOnRemoteWrapper for &mut &D {
|
||||||
|
type Output = D::Output;
|
||||||
|
|
||||||
|
fn deserialize_on_remote<'a, D2: Deserializer<'a>>(
|
||||||
|
deserializer: D2,
|
||||||
|
) -> Result<Self::Output, D2::Error> {
|
||||||
|
D::deserialize_on_remote(deserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DeserializeOnRemote {
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
fn deserialize_on_remote<'a, D: Deserializer<'a>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Self::Output, D::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: DeserializeOwned> DeserializeOnRemote for UseState<D> {
|
||||||
|
type Output = D;
|
||||||
|
|
||||||
|
fn deserialize_on_remote<'a, D2: Deserializer<'a>>(
|
||||||
|
deserializer: D2,
|
||||||
|
) -> Result<Self::Output, D2::Error> {
|
||||||
|
D::deserialize(deserializer)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue