mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Upgrade to axum 0.7, drop 3rd party adapters
This commit is contained in:
parent
5dd33c9dcd
commit
214a907adc
53 changed files with 643 additions and 3260 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -4,5 +4,7 @@
|
|||
"editor.formatOnSave": false
|
||||
},
|
||||
"rust-analyzer.check.workspace": true,
|
||||
"rust-analyzer.checkOnSave.allTargets": false,
|
||||
"rust-analyzer.check.features": "all",
|
||||
"rust-analyzer.cargo.features": "all",
|
||||
"rust-analyzer.check.allTargets": true
|
||||
}
|
||||
|
|
1639
Cargo.lock
generated
1639
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
19
Cargo.toml
19
Cargo.toml
|
@ -37,8 +37,6 @@ members = [
|
|||
"packages/fullstack/examples/axum-router",
|
||||
"packages/fullstack/examples/axum-desktop",
|
||||
"packages/fullstack/examples/axum-auth",
|
||||
"packages/fullstack/examples/salvo-hello-world",
|
||||
"packages/fullstack/examples/warp-hello-world",
|
||||
"packages/fullstack/examples/static-hydrated",
|
||||
# Full project examples
|
||||
"examples/tailwind",
|
||||
|
@ -88,6 +86,7 @@ dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
|
|||
dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
|
||||
tracing = "0.1.37"
|
||||
tracing-futures = "0.2.5"
|
||||
toml = "0.8"
|
||||
tokio = "1.28"
|
||||
slab = "0.4.2"
|
||||
futures-channel = "0.3.21"
|
||||
|
@ -105,6 +104,17 @@ manganis-cli-support = { git = "https://github.com/DioxusLabs/collect-assets", r
|
|||
] }
|
||||
manganis = { git = "https://github.com/DioxusLabs/collect-assets", rev = "f982698" }
|
||||
|
||||
lru = "0.12.2"
|
||||
async-trait = "0.1.77"
|
||||
axum = "0.7.0"
|
||||
tower = "0.4.13"
|
||||
http = "1.0.0"
|
||||
tower-http = "0.5.1"
|
||||
hyper = "1.0.0"
|
||||
hyper-rustls = "0.26.0"
|
||||
serde_json = "1.0.61"
|
||||
serde = "1.0.61"
|
||||
|
||||
# This is a "virtual package"
|
||||
# It is not meant to be published, but is used so "cargo run --example XYZ" works properly
|
||||
[package]
|
||||
|
@ -125,7 +135,6 @@ publish = false
|
|||
manganis = { workspace = true, optional = true}
|
||||
reqwest = { version = "0.11.9", features = ["json"], optional = true}
|
||||
http-range = {version = "0.1.5", optional = true }
|
||||
warp = { version = "0.3.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true, features = ["router"] }
|
||||
|
@ -149,13 +158,11 @@ tokio = { version = "1.16.1", features = ["full"] }
|
|||
liveview = ["dioxus/liveview"]
|
||||
fullstack = ["dioxus/fullstack"]
|
||||
axum = ["dioxus/axum"]
|
||||
salvo = ["dioxus/salvo"]
|
||||
rocket = ["dioxus/rocket"]
|
||||
server = ["dioxus/axum"]
|
||||
default = ["dioxus/desktop"]
|
||||
web = ["dioxus/web"]
|
||||
collect-assets = ["manganis"]
|
||||
http = ["reqwest", "http-range", "warp"]
|
||||
http = ["reqwest", "http-range"]
|
||||
|
||||
[[example]]
|
||||
name = "login_form"
|
||||
|
|
|
@ -12,7 +12,7 @@ keywords = ["react", "gui", "cli", "dioxus", "wasm"]
|
|||
clap = { version = "4.2", features = ["derive"], optional = true }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.79"
|
||||
toml = { version = "0.5.8", optional = true }
|
||||
toml = { workspace = true, optional = true }
|
||||
cargo_toml = { version = "0.16.0", optional = true }
|
||||
once_cell = "1.18.0"
|
||||
tracing = { workspace = true }
|
||||
|
|
|
@ -21,7 +21,7 @@ log = "0.4.14"
|
|||
fern = { version = "0.6.0", features = ["colored"] }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.79"
|
||||
toml = "0.8.8"
|
||||
toml = {workspace = true}
|
||||
fs_extra = "1.2.0"
|
||||
cargo_toml = "0.18.0"
|
||||
futures-util = { workspace = true }
|
||||
|
@ -32,17 +32,17 @@ tokio = { version = "1.16.1", features = ["fs", "sync", "rt", "macros"] }
|
|||
atty = "0.2.14"
|
||||
chrono = "0.4.19"
|
||||
anyhow = "1"
|
||||
hyper = "0.14.17"
|
||||
hyper-rustls = "0.23.2"
|
||||
hyper = {workspace = true}
|
||||
hyper-rustls = {workspace = true}
|
||||
indicatif = "0.17.5"
|
||||
subprocess = "0.2.9"
|
||||
rayon = "1.8.0"
|
||||
|
||||
axum = { version = "0.5.1", features = ["ws", "headers"] }
|
||||
axum = { workspace = true, features = ["ws"] }
|
||||
axum-server = { version = "0.5.1", features = ["tls-rustls"] }
|
||||
tower-http = { version = "0.2.2", features = ["full"] }
|
||||
headers = "0.3.7"
|
||||
tower-http = { workspace = true, features = ["full"] }
|
||||
|
||||
headers = "0.3.7"
|
||||
walkdir = "2"
|
||||
|
||||
# tools download
|
||||
|
@ -56,7 +56,7 @@ reqwest = { version = "0.11", features = [
|
|||
flate2 = "1.0.22"
|
||||
tar = "0.4.38"
|
||||
zip = "0.6.2"
|
||||
tower = "0.4.12"
|
||||
tower = { workspace = true }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
# plugin packages
|
||||
|
|
|
@ -5,9 +5,6 @@ use anyhow::Context;
|
|||
use clap::Parser;
|
||||
use dioxus_cli::*;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
use dioxus_cli::plugin::PluginManager;
|
||||
|
||||
use Commands::*;
|
||||
|
||||
fn get_bin(bin: Option<String>) -> Result<PathBuf> {
|
||||
|
@ -92,6 +89,9 @@ async fn main() -> anyhow::Result<()> {
|
|||
DioxusConfig::default()
|
||||
});
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
use dioxus_cli::plugin::PluginManager;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
PluginManager::init(_dioxus_config.plugin)
|
||||
.context(error_wrapper("Plugin system initialization failed"))?;
|
||||
|
|
|
@ -8,8 +8,8 @@ use crate::{
|
|||
BuildResult, Result,
|
||||
};
|
||||
use axum::{
|
||||
body::{Full, HttpBody},
|
||||
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
|
||||
body::{Body, HttpBody},
|
||||
extract::{ws::Message, Extension, WebSocketUpgrade},
|
||||
http::{
|
||||
self,
|
||||
header::{HeaderName, HeaderValue},
|
||||
|
@ -287,20 +287,18 @@ async fn setup_router(
|
|||
let mut response = if file_service_config.dioxus_config.web.watcher.index_on_404
|
||||
&& response.status() == StatusCode::NOT_FOUND
|
||||
{
|
||||
let body = Full::from(
|
||||
let body = Body::from(
|
||||
// TODO: Cache/memoize this.
|
||||
std::fs::read_to_string(file_service_config.out_dir().join("index.html"))
|
||||
.ok()
|
||||
.unwrap(),
|
||||
)
|
||||
.map_err(|err| match err {})
|
||||
.boxed();
|
||||
);
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.body(body)
|
||||
.unwrap()
|
||||
} else {
|
||||
response.map(|body| body.boxed())
|
||||
response.map(|body| body.into())
|
||||
};
|
||||
let headers = response.headers_mut();
|
||||
headers.insert(
|
||||
|
|
|
@ -5,6 +5,8 @@ use anyhow::Context;
|
|||
use axum::{http::StatusCode, routing::any, Router};
|
||||
use hyper::{Request, Response, Uri};
|
||||
|
||||
use axum::body::Body as MyBody;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ProxyClient {
|
||||
inner: hyper::Client<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>,
|
||||
|
@ -24,10 +26,7 @@ impl ProxyClient {
|
|||
}
|
||||
}
|
||||
|
||||
async fn send(
|
||||
&self,
|
||||
mut req: Request<hyper::body::Body>,
|
||||
) -> Result<Response<hyper::body::Body>> {
|
||||
async fn send(&self, mut req: Request<MyBody>) -> Result<Response<MyBody>> {
|
||||
let mut uri_parts = req.uri().clone().into_parts();
|
||||
uri_parts.authority = self.url.authority().cloned();
|
||||
uri_parts.scheme = self.url.scheme().cloned();
|
||||
|
|
|
@ -50,16 +50,10 @@ web = ["dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web", "dioxus
|
|||
ssr = ["dioxus-ssr", "dioxus-router?/ssr", "dioxus-config-macro/ssr"]
|
||||
liveview = ["dioxus-liveview", "dioxus-config-macro/liveview", "dioxus-router?/liveview"]
|
||||
axum = ["dioxus-fullstack?/axum", "ssr", "dioxus-liveview?/axum"]
|
||||
salvo = ["dioxus-fullstack?/salvo", "ssr", "dioxus-liveview?/salvo"]
|
||||
warp = ["dioxus-fullstack?/warp", "ssr", "dioxus-liveview?/warp"]
|
||||
rocket = ["dioxus-liveview?/rocket"]
|
||||
tui = ["dioxus-tui", "dioxus-config-macro/tui"]
|
||||
# This feature just disables the no-renderer-enabled warning
|
||||
third-party-renderer = []
|
||||
|
||||
# This feature enables some nightly flags that make it more clear what structs/methods are available in each feature
|
||||
nightly-doc = []
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
|
|
@ -5,8 +5,8 @@ fn main() {
|
|||
return;
|
||||
}
|
||||
|
||||
let liveview_renderers = ["liveview", "axum", "salvo", "warp", "rocket"];
|
||||
let fullstack_renderers = ["axum", "salvo", "warp"];
|
||||
let liveview_renderers = ["liveview", "axum"];
|
||||
let fullstack_renderers = ["axum"];
|
||||
let client_renderers = ["desktop", "mobile", "web", "tui"];
|
||||
let client_renderer_selected = client_renderers
|
||||
.iter()
|
||||
|
|
|
@ -38,7 +38,7 @@ impl LaunchBuilder {
|
|||
|
||||
/// Launch your web application.
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "web")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
|
||||
pub fn web() -> LaunchBuilder<dioxus_web::Config, UnsendContext> {
|
||||
LaunchBuilder {
|
||||
launch_fn: dioxus_web::launch::launch,
|
||||
|
@ -49,7 +49,7 @@ impl LaunchBuilder {
|
|||
|
||||
/// Launch your desktop application.
|
||||
#[cfg(feature = "desktop")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "desktop")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
|
||||
pub fn desktop() -> LaunchBuilder<dioxus_desktop::Config, UnsendContext> {
|
||||
LaunchBuilder {
|
||||
launch_fn: dioxus_desktop::launch::launch,
|
||||
|
@ -60,7 +60,7 @@ impl LaunchBuilder {
|
|||
|
||||
/// Launch your fullstack application.
|
||||
#[cfg(feature = "fullstack")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "fullstack")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
|
||||
pub fn fullstack() -> LaunchBuilder<dioxus_fullstack::Config, SendContext> {
|
||||
LaunchBuilder {
|
||||
launch_fn: dioxus_fullstack::launch::launch,
|
||||
|
@ -71,7 +71,7 @@ impl LaunchBuilder {
|
|||
|
||||
/// Launch your fullstack application.
|
||||
#[cfg(feature = "mobile")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "mobile")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
|
||||
pub fn mobile() -> LaunchBuilder<dioxus_mobile::Config, UnsendContext> {
|
||||
LaunchBuilder {
|
||||
launch_fn: dioxus_mobile::launch::launch,
|
||||
|
@ -81,7 +81,7 @@ impl LaunchBuilder {
|
|||
}
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "tui")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tui")))]
|
||||
/// Launch your tui application
|
||||
pub fn tui() -> LaunchBuilder<dioxus_tui::Config, UnsendContext> {
|
||||
LaunchBuilder {
|
||||
|
@ -232,28 +232,28 @@ pub fn launch(app: fn() -> Element) {
|
|||
}
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "web")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
|
||||
/// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options.
|
||||
pub fn launch_web(app: fn() -> Element) {
|
||||
LaunchBuilder::web().launch(app)
|
||||
}
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "desktop")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
|
||||
/// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options.
|
||||
pub fn launch_desktop(app: fn() -> Element) {
|
||||
LaunchBuilder::desktop().launch(app)
|
||||
}
|
||||
|
||||
#[cfg(feature = "fullstack")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "fullstack")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
|
||||
/// Launch your fullstack application without any additional configuration. See [`LaunchBuilder`] for more options.
|
||||
pub fn launch_fullstack(app: fn() -> Element) {
|
||||
LaunchBuilder::fullstack().launch(app)
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "tui")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tui")))]
|
||||
/// Launch your tui application without any additional configuration. See [`LaunchBuilder`] for more options.
|
||||
pub fn launch_tui(app: fn() -> Element) {
|
||||
LaunchBuilder::tui().launch(app)
|
||||
|
|
|
@ -1,114 +1,115 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![cfg_attr(any(docsrs, feature = "nightly-doc"), feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
pub use dioxus_core;
|
||||
|
||||
#[cfg(feature = "launch")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "launch")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
|
||||
mod launch;
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "hooks")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
|
||||
pub use dioxus_hooks as hooks;
|
||||
|
||||
#[cfg(feature = "signals")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "signals")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "signals")))]
|
||||
pub use dioxus_signals as signals;
|
||||
|
||||
pub mod events {
|
||||
#[cfg(feature = "html")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "html")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "html")))]
|
||||
pub use dioxus_html::prelude::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "html")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "html")))]
|
||||
pub use dioxus_html as html;
|
||||
|
||||
#[cfg(feature = "macro")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "macro")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macro")))]
|
||||
pub use dioxus_core_macro as core_macro;
|
||||
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "launch")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "launch")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
|
||||
pub use crate::launch::*;
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "hooks")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "hooks")))]
|
||||
pub use crate::hooks::*;
|
||||
|
||||
#[cfg(feature = "signals")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "signals")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "signals")))]
|
||||
pub use dioxus_signals::*;
|
||||
|
||||
pub use dioxus_core::prelude::*;
|
||||
|
||||
#[cfg(feature = "macro")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "macro")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macro")))]
|
||||
#[allow(deprecated)]
|
||||
pub use dioxus_core_macro::{component, format_args_f, inline_props, render, rsx, Props};
|
||||
|
||||
#[cfg(feature = "launch")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "launch")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
|
||||
pub use dioxus_config_macro::*;
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "html")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "html")))]
|
||||
pub use dioxus_html as dioxus_elements;
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "html")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "html")))]
|
||||
pub use dioxus_elements::{prelude::*, GlobalAttributes, SvgAttributes};
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "hot-reload"))]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "hot-reload")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "hot-reload")))]
|
||||
pub use dioxus_hot_reload::{self, hot_reload_init};
|
||||
|
||||
pub use dioxus_core;
|
||||
|
||||
#[cfg(feature = "fullstack")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "fullstack")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
|
||||
pub use dioxus_fullstack::prelude::*;
|
||||
|
||||
#[cfg(feature = "router")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "router")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "router")))]
|
||||
pub use dioxus_router;
|
||||
|
||||
#[cfg(feature = "router")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "router")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "router")))]
|
||||
pub use dioxus_router::prelude::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "web")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
|
||||
pub use dioxus_web as web;
|
||||
|
||||
#[cfg(feature = "router")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "router")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "router")))]
|
||||
pub use dioxus_router as router;
|
||||
|
||||
#[cfg(feature = "fullstack")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "fullstack")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))]
|
||||
pub use dioxus_fullstack as fullstack;
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "desktop")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
|
||||
pub use dioxus_desktop as desktop;
|
||||
|
||||
#[cfg(feature = "mobile")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "mobile")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
|
||||
pub use dioxus_desktop as mobile;
|
||||
|
||||
#[cfg(feature = "liveview")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "liveview")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "liveview")))]
|
||||
pub use dioxus_liveview as liveview;
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "tui")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tui")))]
|
||||
pub use dioxus_tui as tui;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "ssr")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "ssr")))]
|
||||
pub use dioxus_ssr as ssr;
|
||||
|
|
|
@ -15,23 +15,17 @@ resolver = "2"
|
|||
server_fn = { version = "0.5.2", default-features = false }
|
||||
dioxus_server_macro = { workspace = true }
|
||||
|
||||
# warp
|
||||
warp = { version = "0.3.5", features = ["compression-gzip"], optional = true }
|
||||
|
||||
# axum
|
||||
axum = { version = "0.6.1", features = ["ws", "macros"], default-features = false, optional = true }
|
||||
tower-http = { version = "0.4.0", optional = true, features = ["fs", "compression-gzip"] }
|
||||
|
||||
# salvo
|
||||
salvo = { version = "0.63.0", optional = true, features = ["serve-static", "websocket", "compression"] }
|
||||
http-body-util = { version = "0.1.0-rc.2", optional = true }
|
||||
axum = { workspace = true, features = ["ws", "macros"], default-features = false, optional = true }
|
||||
tower-http = { workspace = true, optional = true, features = ["fs", "compression-gzip"] }
|
||||
|
||||
dioxus-lib = { workspace = true }
|
||||
|
||||
# Dioxus + SSR
|
||||
dioxus-ssr = { workspace = true, optional = true }
|
||||
hyper = { version = "0.14.25", optional = true }
|
||||
http = { version = "0.2.9", optional = true }
|
||||
hyper = { workspace = true, optional = true }
|
||||
http = { workspace = true, optional = true }
|
||||
|
||||
# Web Integration
|
||||
dioxus-web = { workspace = true, features = ["hydrate"], optional = true }
|
||||
|
@ -60,7 +54,7 @@ pin-project = { version = "1.1.2", optional = true }
|
|||
thiserror = { workspace = true, optional = true }
|
||||
async-trait = "0.1.71"
|
||||
bytes = "1.4.0"
|
||||
tower = { version = "0.4.13", features = ["util"], optional = true }
|
||||
tower = { workspace = true, features = ["util"], optional = true }
|
||||
tower-layer = { version = "0.3.2", optional = true }
|
||||
web-sys = { version = "0.3.61", optional = true, features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
||||
|
||||
|
@ -75,11 +69,7 @@ hot-reload = ["serde_json", "futures-util"]
|
|||
web = ["dioxus-web", "web-sys"]
|
||||
desktop = ["dioxus-desktop"]
|
||||
mobile = ["dioxus-mobile"]
|
||||
warp = ["dep:warp", "server"]
|
||||
axum = ["dep:axum", "tower-http", "server"]
|
||||
salvo = ["dep:salvo", "server", "http-body-util"]
|
||||
server = ["server_fn/ssr", "dioxus_server_macro/server", "tokio", "tokio-util", "tokio-stream", "dioxus-ssr", "dioxus-ssr/incremental", "tower", "hyper", "http", "tower-layer", "anymap", "tracing-futures", "pin-project", "thiserror", "dioxus-cli-config"]
|
||||
default-tls = ["server_fn/default-tls"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
# This feature enables some nightly flags that make it more clear what structs/methods are available in each feature
|
||||
nightly-doc = []
|
||||
|
|
|
@ -37,51 +37,43 @@ Full stack Dioxus in under 50 lines of code
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_fullstack::prelude::*;
|
||||
|
||||
// On the web, run our client code
|
||||
#[cfg(feature = "web")]
|
||||
fn main() {
|
||||
#[cfg(feature = "web")]
|
||||
dioxus_web::launch_with_props(
|
||||
app,
|
||||
get_root_props_from_document().unwrap_or_default(),
|
||||
dioxus_web::Config::new().hydrate(true),
|
||||
);
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
warp::serve(
|
||||
// Automatically handles server side rendering, hot reloading intigration, and hosting server functions
|
||||
serve_dioxus_application(
|
||||
"",
|
||||
ServerConfig::new(app, ()),
|
||||
)
|
||||
)
|
||||
.run(([127, 0, 0, 1], 8080))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// On the server, run a simple warp server
|
||||
#[cfg(feature = "server")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Automatically handles server side rendering, hot reloading intigration, and hosting server functions
|
||||
warp::serve(serve_dioxus_application("", ServerConfig::new(app, ())))
|
||||
.run(([127, 0, 0, 1], 8080))
|
||||
.await;
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let meaning = use_signal(|| None);
|
||||
|
||||
rsx! {
|
||||
h1 { "Meaning of life: {meaning:?}" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
to_owned![meaning];
|
||||
async move {
|
||||
if let Ok(data) = get_meaning("life the universe and everything".into()).await {
|
||||
meaning.set(data);
|
||||
}
|
||||
onclick: move |_| async move {
|
||||
if let Ok(data) = get_meaning("life the universe and everything".into()).await {
|
||||
meaning.set(data);
|
||||
}
|
||||
},
|
||||
"Run a server function"
|
||||
}
|
||||
"Server said: {meaning:?}"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This code will only run on the server
|
||||
#[server(GetMeaning)]
|
||||
#[server]
|
||||
async fn get_meaning(of: String) -> Result<Option<u32>, ServerFnError> {
|
||||
Ok(of.contains("life").then(|| 42))
|
||||
}
|
||||
|
|
|
@ -8,13 +8,11 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
dioxus-web = { workspace = true, features = ["hydrate"], optional = true }
|
||||
dioxus = { workspace = true }
|
||||
dioxus = { features = ["fullstack"], workspace = true }
|
||||
dioxus-fullstack = { workspace = true }
|
||||
axum = { version = "0.6.12", optional = true }
|
||||
axum = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["full"], optional = true }
|
||||
serde = "1.0.159"
|
||||
execute = "0.2.12"
|
||||
tower-http = { version = "0.4.1", features = ["auth"], optional = true }
|
||||
tower-http = { workspace = true, features = ["auth"], optional = true }
|
||||
simple_logger = { version = "4.2.0", optional = true }
|
||||
async-trait = { version = "0.1.71", optional = true }
|
||||
sqlx = { version = "0.7.0", features = [
|
||||
|
@ -26,9 +24,12 @@ sqlx = { version = "0.7.0", features = [
|
|||
"tls-rustls",
|
||||
"runtime-tokio",
|
||||
], optional = true }
|
||||
http = { workspace = true, optional = true }
|
||||
tower = { workspace = true, optional = true }
|
||||
|
||||
serde = "1.0.159"
|
||||
execute = "0.2.12"
|
||||
anyhow = "1.0.71"
|
||||
http = { version = "0.2.9", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
|
||||
[dependencies.axum_session]
|
||||
version = "0.3.0"
|
||||
|
@ -36,7 +37,7 @@ features = ["sqlite-rustls"]
|
|||
optional = true
|
||||
|
||||
[dependencies.axum_session_auth]
|
||||
version = "0.3.0"
|
||||
version = "0.12.0"
|
||||
features = ["sqlite-rustls"]
|
||||
optional = true
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
|
|||
fn main() {
|
||||
#[cfg(feature = "web")]
|
||||
// Hydrate the application on the client
|
||||
dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
|
||||
dioxus_web::launch::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
{
|
||||
|
@ -51,7 +51,9 @@ fn main() {
|
|||
// build our application with some routes
|
||||
let app = Router::new()
|
||||
// Server side render the application, serve static assets, and register server functions
|
||||
.serve_dioxus_application("", ServerConfig::new(app, ()))
|
||||
.serve_dioxus_application("", ServeConfig::builder().build(), || {
|
||||
VirtualDom::new(app)
|
||||
})
|
||||
.layer(
|
||||
axum_session_auth::AuthSessionLayer::<
|
||||
crate::auth::User,
|
||||
|
|
|
@ -7,8 +7,8 @@ publish = false
|
|||
[lib]
|
||||
|
||||
[dependencies]
|
||||
dioxus = { workspace = true }
|
||||
axum = { version = "0.6.12", optional = true }
|
||||
dioxus = { workspace = true, features = ["launch"] }
|
||||
axum = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["full"], optional = true }
|
||||
serde = "1.0.159"
|
||||
|
||||
|
|
|
@ -9,5 +9,7 @@ fn main() {
|
|||
// Set the url of the server where server functions are hosted.
|
||||
#[cfg(not(feature = "server"))]
|
||||
dioxus::fullstack::prelude::server_fn::set_server_url("http://127.0.0.1:8080");
|
||||
dioxus::desktop::launch(app)
|
||||
|
||||
#[cfg(feature = "desktop")]
|
||||
dioxus::prelude::launch_desktop(app)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
dioxus = { workspace = true, features = ["router"] }
|
||||
axum = { version = "0.6.12", optional = true }
|
||||
axum = { workspace = true, optional = true }
|
||||
tokio = {workspace = true, features = ["full"], optional = true }
|
||||
serde = { version = "1.0.159", features = ["derive"] }
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
dist
|
||||
target
|
||||
static
|
||||
.dioxus
|
|
@ -1,25 +0,0 @@
|
|||
[package]
|
||||
name = "salvo-hello-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-web = { workspace = true, features=["hydrate"], optional = true }
|
||||
dioxus = { workspace = true }
|
||||
dioxus-fullstack = { workspace = true }
|
||||
serde = "1.0.159"
|
||||
salvo = { version = "0.63.0", optional = true }
|
||||
execute = "0.2.12"
|
||||
reqwest = "0.11.18"
|
||||
simple_logger = "4.2.0"
|
||||
tracing-wasm = "0.2.1"
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = "0.3.17"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["salvo", "dioxus-fullstack/salvo"]
|
||||
web = ["dioxus-web"]
|
|
@ -1,57 +0,0 @@
|
|||
//! Run with:
|
||||
//!
|
||||
//! ```sh
|
||||
//! dx serve --platform fullstack
|
||||
//! ```
|
||||
|
||||
#![allow(non_snake_case, unused)]
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_fullstack::{launch, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn app() -> Element {
|
||||
let state = use_server_future(move || async move { get_server_data().await.unwrap() })?;
|
||||
|
||||
let mut count = use_signal(|| 0);
|
||||
let mut text = use_signal(|| "...".to_string());
|
||||
|
||||
rsx! {
|
||||
div { "Server state: {state.value().unwrap()}" }
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
button {
|
||||
onclick: move |_| 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(reqwest::get("https://httpbin.org/ip").await?.text().await?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "web")]
|
||||
tracing_wasm::set_as_global_default();
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
LaunchBuilder::fullstack().launch(app);
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
dist
|
||||
target
|
||||
static
|
||||
.dioxus
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "warp-hello-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { workspace = true, features = ["fullstack"] }
|
||||
serde = "1.0.159"
|
||||
tracing-wasm = "0.2.1"
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = "0.3.17"
|
||||
reqwest = "0.11.18"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus/warp"]
|
||||
web = ["dioxus/web"]
|
|
@ -1,54 +0,0 @@
|
|||
//! Run with:
|
||||
//!
|
||||
//! ```sh
|
||||
//! dx serve --platform fullstack
|
||||
//! ```
|
||||
|
||||
#![allow(non_snake_case, unused)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let text = use_signal(|| "...".to_string());
|
||||
|
||||
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]
|
||||
async fn post_server_data(data: String) -> Result<(), ServerFnError> {
|
||||
println!("Server received: {}", data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[server]
|
||||
async fn get_server_data() -> Result<String, ServerFnError> {
|
||||
Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "web")]
|
||||
tracing_wasm::set_as_global_default();
|
||||
#[cfg(feature = "server")]
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
launch(app);
|
||||
}
|
|
@ -55,7 +55,7 @@
|
|||
//! ```
|
||||
|
||||
use axum::{
|
||||
body::{self, Body, BoxBody},
|
||||
body::{self, Body},
|
||||
extract::State,
|
||||
handler::Handler,
|
||||
http::{Request, Response, StatusCode},
|
||||
|
@ -234,7 +234,7 @@ where
|
|||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler<T, S, Body>,
|
||||
H: Handler<T, S>,
|
||||
T: 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
|
@ -468,10 +468,10 @@ pub async fn render_handler(
|
|||
.await
|
||||
}
|
||||
|
||||
fn report_err<E: std::fmt::Display>(e: E) -> Response<BoxBody> {
|
||||
fn report_err<E: std::fmt::Display>(e: E) -> Response<axum::body::Body> {
|
||||
Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(body::boxed(format!("Error: {}", e)))
|
||||
.body(body::Body::new(format!("Error: {}", e)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
|
|
|
@ -12,15 +12,13 @@
|
|||
|
||||
#[cfg(feature = "axum")]
|
||||
pub mod axum_adapter;
|
||||
// #[cfg(feature = "salvo")]
|
||||
// pub mod salvo_adapter;
|
||||
#[cfg(feature = "warp")]
|
||||
pub mod warp_adapter;
|
||||
|
||||
use http::StatusCode;
|
||||
use server_fn::{Encoding, Payload};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
type MyBody = axum::body::Body;
|
||||
|
||||
use crate::{
|
||||
layer::{BoxedService, Service},
|
||||
prelude::{DioxusServerContext, ProvideServerContext},
|
||||
|
@ -68,11 +66,11 @@ impl ServerFnHandler {
|
|||
impl Service for ServerFnHandler {
|
||||
fn run(
|
||||
&mut self,
|
||||
req: http::Request<hyper::body::Body>,
|
||||
req: http::Request<MyBody>,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<http::Response<hyper::body::Body>, server_fn::ServerFnError>,
|
||||
Output = Result<http::Response<MyBody>, server_fn::ServerFnError>,
|
||||
> + Send,
|
||||
>,
|
||||
> {
|
||||
|
@ -83,7 +81,7 @@ impl Service for ServerFnHandler {
|
|||
Box::pin(async move {
|
||||
let query = req.uri().query().unwrap_or_default().as_bytes().to_vec();
|
||||
let (parts, body) = req.into_parts();
|
||||
let body = hyper::body::to_bytes(body).await?.to_vec();
|
||||
let body = axum::body::to_bytes(body, usize::MAX).await?.to_vec();
|
||||
let headers = &parts.headers;
|
||||
let accept_header = headers.get("Accept").cloned();
|
||||
let parts = Arc::new(RwLock::new(parts));
|
||||
|
|
|
@ -1,581 +0,0 @@
|
|||
//! Dioxus utilities for the [Salvo](https://salvo.rs) server framework.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```rust
|
||||
//! #![allow(non_snake_case)]
|
||||
//! use dioxus_lib::prelude::*;
|
||||
//! use dioxus_fullstack::prelude::*;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! #[cfg(feature = "web")]
|
||||
//! dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
|
||||
//! #[cfg(feature = "server")]
|
||||
//! {
|
||||
//! use salvo::prelude::*;
|
||||
//! tokio::runtime::Runtime::new()
|
||||
//! .unwrap()
|
||||
//! .block_on(async move {
|
||||
//! let router =
|
||||
//! Router::new().serve_dioxus_application("", ServerConfig::new(app, ()));
|
||||
//! Server::new(TcpListener::bind("127.0.0.1:8080"))
|
||||
//! .serve(router)
|
||||
//! .await;
|
||||
//! });
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn app() -> Element {
|
||||
//! let text = use_signal(|| "...".to_string());
|
||||
//!
|
||||
//! rsx! {
|
||||
//! button {
|
||||
//! onclick: move |_| {
|
||||
//! to_owned![text];
|
||||
//! async move {
|
||||
//! if let Ok(data) = get_server_data().await {
|
||||
//! text.set(data);
|
||||
//! }
|
||||
//! }
|
||||
//! },
|
||||
//! "Run a server function"
|
||||
//! }
|
||||
//! "Server said: {text}"
|
||||
//! })
|
||||
//! }
|
||||
//!
|
||||
//! #[server(GetServerData)]
|
||||
//! async fn get_server_data() -> Result<String, ServerFnError> {
|
||||
//! Ok("Hello from the server!".to_string())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use dioxus_lib::prelude::*;
|
||||
use http_body_util::{BodyExt, Limited};
|
||||
use hyper::body::Body as HyperBody;
|
||||
use hyper::StatusCode;
|
||||
use salvo::{
|
||||
async_trait, handler,
|
||||
http::{
|
||||
cookie::{Cookie, CookieJar},
|
||||
ParseError,
|
||||
},
|
||||
serve_static::{StaticDir, StaticFile},
|
||||
Depot, Error as SalvoError, FlowCtrl, Handler, Request, Response, Router,
|
||||
};
|
||||
use server_fn::{Encoding, ServerFunctionRegistry};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
layer::Service, prelude::*, render::SSRState, serve_config::ServeConfig,
|
||||
server_fn::DioxusServerFnRegistry, server_fn_service,
|
||||
};
|
||||
|
||||
type HyperRequest = hyper::Request<hyper::Body>;
|
||||
type HyperResponse = hyper::Response<HyperBody>;
|
||||
|
||||
/// A extension trait with utilities for integrating Dioxus with your Salvo router.
|
||||
pub trait DioxusRouterExt {
|
||||
/// Registers server functions with a custom handler function. This allows you to pass custom context to your server functions by generating a [`DioxusServerContext`] from the request.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use salvo::prelude::*;
|
||||
/// use std::{net::TcpListener, sync::Arc};
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// struct ServerFunctionHandler {
|
||||
/// server_fn: server_fn::ServerFnTraitObj<()>,
|
||||
/// }
|
||||
///
|
||||
/// #[handler]
|
||||
/// impl ServerFunctionHandler {
|
||||
/// async fn handle(
|
||||
/// &self,
|
||||
/// req: &mut Request,
|
||||
/// depot: &mut Depot,
|
||||
/// res: &mut Response,
|
||||
/// flow: &mut FlowCtrl,
|
||||
/// ) {
|
||||
/// // Add the headers to server context
|
||||
/// ServerFnHandler::new((req.headers().clone(),), self.server_fn.clone())
|
||||
/// .handle(req, depot, res, flow)
|
||||
/// .await
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let router = Router::new()
|
||||
/// .register_server_fns_with_handler("", |func| {
|
||||
/// ServerFnHandler::new(DioxusServerContext::default(), func)
|
||||
/// });
|
||||
/// Server::new(TcpListener::bind("127.0.0.1:8080"))
|
||||
/// .serve(router)
|
||||
/// .await;
|
||||
/// }
|
||||
/// ```
|
||||
fn register_server_fns_with_handler<H>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
handler: impl Fn(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler + 'static;
|
||||
|
||||
/// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use salvo::prelude::*;
|
||||
/// use std::{net::TcpListener, sync::Arc};
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let router = Router::new()
|
||||
/// .register_server_fns("");
|
||||
/// Server::new(TcpListener::bind("127.0.0.1:8080"))
|
||||
/// .serve(router)
|
||||
/// .await;
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
fn register_server_fns(self, server_fn_route: &'static str) -> Self;
|
||||
|
||||
/// Register the web RSX hot reloading endpoint. This will enable hot reloading for your application in debug mode when you call [`dioxus_hot_reload::hot_reload_init`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use salvo::prelude::*;
|
||||
/// use std::{net::TcpListener, sync::Arc};
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let router = Router::new()
|
||||
/// .connect_hot_reload();
|
||||
/// Server::new(TcpListener::bind("127.0.0.1:8080"))
|
||||
/// .serve(router)
|
||||
/// .await;
|
||||
/// }
|
||||
fn connect_hot_reload(self) -> Self;
|
||||
|
||||
/// Serves the static WASM for your Dioxus application (except the generated index.html).
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use salvo::prelude::*;
|
||||
/// use std::{net::TcpListener, sync::Arc};
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let router = Router::new()
|
||||
/// .server_static_assets("/dist");
|
||||
/// Server::new(TcpListener::bind("127.0.0.1:8080"))
|
||||
/// .serve(router)
|
||||
/// .await;
|
||||
/// }
|
||||
/// ```
|
||||
fn serve_static_assets(self, assets_path: impl Into<std::path::PathBuf>) -> Self;
|
||||
|
||||
/// Serves the Dioxus application. This will serve a complete server side rendered application.
|
||||
/// This will serve static assets, server render the application, register server functions, and intigrate with hot reloading.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// #![allow(non_snake_case)]
|
||||
/// use dioxus_lib::prelude::*;
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
/// use salvo::prelude::*;
|
||||
/// use std::{net::TcpListener, sync::Arc};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let router = Router::new().serve_dioxus_application("", ServerConfig::new(app, ()));
|
||||
/// Server::new(TcpListener::bind("127.0.0.1:8080"))
|
||||
/// .serve(router)
|
||||
/// .await;
|
||||
/// }
|
||||
///
|
||||
/// fn app() -> Element {unimplemented!() }
|
||||
/// ```
|
||||
fn serve_dioxus_application(
|
||||
self,
|
||||
server_fn_path: &'static str,
|
||||
cfg: impl Into<ServeConfig>,
|
||||
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
impl DioxusRouterExt for Router {
|
||||
fn register_server_fns_with_handler<H>(
|
||||
self,
|
||||
server_fn_route: &'static str,
|
||||
mut handler: impl FnMut(server_fn::ServerFnTraitObj<()>) -> H,
|
||||
) -> Self
|
||||
where
|
||||
H: Handler + 'static,
|
||||
{
|
||||
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}");
|
||||
match func.encoding() {
|
||||
Encoding::Url | Encoding::Cbor => {
|
||||
router = router.push(Router::with_path(&full_route).post(handler(func)));
|
||||
}
|
||||
Encoding::GetJSON | Encoding::GetCBOR => {
|
||||
router = router.push(Router::with_path(&full_route).get(handler(func)));
|
||||
}
|
||||
}
|
||||
}
|
||||
router
|
||||
}
|
||||
|
||||
fn register_server_fns(self, server_fn_route: &'static str) -> Self {
|
||||
self.register_server_fns_with_handler(server_fn_route, |func| ServerFnHandler {
|
||||
server_context: DioxusServerContext::default(),
|
||||
function: func,
|
||||
})
|
||||
}
|
||||
|
||||
fn serve_static_assets(mut self, assets_path: impl Into<std::path::PathBuf>) -> Self {
|
||||
let assets_path = assets_path.into();
|
||||
|
||||
// Serve all files in dist folder except index.html
|
||||
let dir = std::fs::read_dir(&assets_path).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Couldn't read assets directory at {:?}: {}",
|
||||
&assets_path, e
|
||||
)
|
||||
});
|
||||
|
||||
for entry in dir.flatten() {
|
||||
let path = entry.path();
|
||||
if path.ends_with("index.html") {
|
||||
continue;
|
||||
}
|
||||
let route = path
|
||||
.strip_prefix(&assets_path)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|segment| {
|
||||
segment.to_str().unwrap_or_else(|| {
|
||||
panic!("Failed to convert path segment {:?} to string", segment)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
if path.is_file() {
|
||||
let route = format!("/{}", route);
|
||||
let serve_dir = StaticFile::new(path.clone());
|
||||
self = self.push(Router::with_path(route).get(serve_dir))
|
||||
} else {
|
||||
let route = format!("/{}/<**path>", route);
|
||||
let serve_dir = StaticDir::new([path.clone()]);
|
||||
self = self.push(Router::with_path(route).get(serve_dir))
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn serve_dioxus_application(
|
||||
self,
|
||||
server_fn_path: &'static str,
|
||||
cfg: impl Into<ServeConfig>,
|
||||
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let cfg = cfg.into();
|
||||
|
||||
self.serve_static_assets(cfg.assets_path.clone())
|
||||
.connect_hot_reload()
|
||||
.register_server_fns(server_fn_path)
|
||||
.push(Router::with_path("/<**any_path>").get(SSRHandler {
|
||||
config: cfg,
|
||||
virtual_dom: virtual_dom_factory,
|
||||
}))
|
||||
}
|
||||
|
||||
fn connect_hot_reload(self) -> Self {
|
||||
let mut _dioxus_router = Router::with_path("_dioxus");
|
||||
_dioxus_router =
|
||||
_dioxus_router.push(Router::with_path("hot_reload").handle(HotReloadHandler));
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
{
|
||||
_dioxus_router = _dioxus_router.push(Router::with_path("disconnect").handle(ignore_ws));
|
||||
}
|
||||
self.push(_dioxus_router)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the parts of a request that are needed for server functions. This will take parts of the request and replace them with empty values.
|
||||
pub fn extract_parts(req: &mut Request) -> http::request::Parts {
|
||||
let mut parts = http::request::Request::new(()).into_parts().0;
|
||||
parts.method = std::mem::take(req.method_mut());
|
||||
parts.uri = std::mem::take(req.uri_mut());
|
||||
parts.version = req.version();
|
||||
parts.headers = std::mem::take(req.headers_mut());
|
||||
parts.extensions = std::mem::take(req.extensions_mut());
|
||||
|
||||
parts
|
||||
}
|
||||
|
||||
fn apply_request_parts_to_response(
|
||||
headers: hyper::header::HeaderMap,
|
||||
response: &mut salvo::prelude::Response,
|
||||
) {
|
||||
let mut_headers = response.headers_mut();
|
||||
for (key, value) in headers.iter() {
|
||||
mut_headers.insert(key, value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn convert_request(req: &mut Request) -> Result<HyperRequest, SalvoError> {
|
||||
let forward_url: hyper::Uri = TryFrom::try_from(req.uri()).map_err(SalvoError::other)?;
|
||||
let mut build = hyper::Request::builder()
|
||||
.method(req.method())
|
||||
.uri(&forward_url);
|
||||
for (key, value) in req.headers() {
|
||||
build = build.header(key, value);
|
||||
}
|
||||
static SECURE_MAX_SIZE: usize = 64 * 1024;
|
||||
|
||||
let body = Limited::new(req.take_body(), SECURE_MAX_SIZE)
|
||||
.collect()
|
||||
.await
|
||||
.map_err(ParseError::other)?
|
||||
.to_bytes();
|
||||
build.body(body.into()).map_err(SalvoError::other)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn convert_response(response: HyperResponse, res: &mut Response) {
|
||||
let (parts, body) = response.into_parts();
|
||||
let http::response::Parts {
|
||||
version,
|
||||
headers,
|
||||
status,
|
||||
..
|
||||
} = parts;
|
||||
res.status_code = Some(status);
|
||||
res.version = version;
|
||||
res.cookies = CookieJar::new();
|
||||
for cookie in headers.get_all(http::header::SET_COOKIE).iter() {
|
||||
if let Some(cookie) = cookie
|
||||
.to_str()
|
||||
.ok()
|
||||
.and_then(|s| Cookie::parse(s.to_string()).ok())
|
||||
{
|
||||
res.cookies.add_original(cookie);
|
||||
}
|
||||
}
|
||||
res.headers = headers;
|
||||
res.version = version;
|
||||
if let Ok(bytes) = hyper::body::to_bytes(body).await {
|
||||
res.body = bytes.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler that renders a Dioxus application to HTML using server-side rendering.
|
||||
pub struct SSRHandler {
|
||||
config: ServeConfig,
|
||||
virtual_dom: Box<dyn Fn() -> VirtualDom + Send + Sync>,
|
||||
}
|
||||
|
||||
impl SSRHandler {
|
||||
/// Creates a new SSR handler with the given configuration.
|
||||
pub fn new(
|
||||
config: ServeConfig,
|
||||
virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
virtual_dom: Box::new(virtual_dom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Handler for SSRHandler {
|
||||
async fn handle(
|
||||
&self,
|
||||
req: &mut Request,
|
||||
depot: &mut Depot,
|
||||
res: &mut Response,
|
||||
_flow: &mut FlowCtrl,
|
||||
) {
|
||||
// Get the SSR renderer from the depot or create a new one if it doesn't exist
|
||||
let renderer_pool = if let Some(renderer) = depot.obtain::<SSRState>() {
|
||||
renderer.clone()
|
||||
} else {
|
||||
let renderer = SSRState::new(&self.cfg);
|
||||
depot.inject(renderer.clone());
|
||||
renderer
|
||||
};
|
||||
|
||||
let route = req.uri().path().to_string();
|
||||
let parts: Arc<RwLock<http::request::Parts>> = Arc::new(RwLock::new(extract_parts(req)));
|
||||
let server_context = DioxusServerContext::new(parts);
|
||||
|
||||
match renderer_pool
|
||||
.render(route, &self.cfg, &server_context)
|
||||
.await
|
||||
{
|
||||
Ok(rendered) => {
|
||||
let crate::render::RenderResponse { html, freshness } = rendered;
|
||||
|
||||
res.write_body(html).unwrap();
|
||||
|
||||
let headers = server_context.response_parts().unwrap().headers.clone();
|
||||
apply_request_parts_to_response(headers, res);
|
||||
freshness.write(res.headers_mut());
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Error rendering SSR: {}", err);
|
||||
res.write_body("Error rendering SSR").unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// A default handler for server functions. It will deserialize the request body, call the server function, and serialize the response.
|
||||
pub struct ServerFnHandler {
|
||||
server_context: DioxusServerContext,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
}
|
||||
|
||||
impl ServerFnHandler {
|
||||
/// Create a new server function handler with the given server context and server function.
|
||||
pub fn new(
|
||||
server_context: impl Into<DioxusServerContext>,
|
||||
function: server_fn::ServerFnTraitObj<()>,
|
||||
) -> Self {
|
||||
let server_context = server_context.into();
|
||||
Self {
|
||||
server_context,
|
||||
function,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[handler]
|
||||
impl ServerFnHandler {
|
||||
async fn handle(&self, req: &mut Request, _depot: &mut Depot, res: &mut Response) {
|
||||
match convert_request(req).await {
|
||||
Ok(hyper_req) => {
|
||||
let response =
|
||||
server_fn_service(self.server_context.clone(), self.function.clone())
|
||||
.run(hyper_req)
|
||||
.await
|
||||
.unwrap();
|
||||
convert_response(response, res).await;
|
||||
}
|
||||
Err(err) => handle_error(err, res),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_error(error: impl Error + Send + Sync, res: &mut Response) {
|
||||
let mut resp_err = Response::new();
|
||||
resp_err.status_code(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
resp_err.render(format!("Internal Server Error: {}", error));
|
||||
*res = resp_err;
|
||||
}
|
||||
|
||||
/// A handler for Dioxus web hot reload websocket. This will send the updated static parts of the RSX to the client when they change.
|
||||
#[cfg(not(all(debug_assertions, feature = "hot-reload")))]
|
||||
#[derive(Default)]
|
||||
pub struct HotReloadHandler;
|
||||
|
||||
#[cfg(not(all(debug_assertions, feature = "hot-reload")))]
|
||||
#[handler]
|
||||
impl HotReloadHandler {
|
||||
async fn handle(
|
||||
&self,
|
||||
_req: &mut Request,
|
||||
_depot: &mut Depot,
|
||||
_res: &mut Response,
|
||||
) -> Result<(), salvo::http::StatusError> {
|
||||
Err(salvo::http::StatusError::not_found())
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler for Dioxus web hot reload websocket. This will send the updated static parts of the RSX to the client when they change.
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
#[derive(Default)]
|
||||
pub struct HotReloadHandler;
|
||||
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
#[handler]
|
||||
impl HotReloadHandler {
|
||||
async fn handle(
|
||||
&self,
|
||||
req: &mut Request,
|
||||
_depot: &mut Depot,
|
||||
res: &mut Response,
|
||||
) -> Result<(), salvo::http::StatusError> {
|
||||
use salvo::websocket::Message;
|
||||
use salvo::websocket::WebSocketUpgrade;
|
||||
|
||||
let state = crate::hot_reload::spawn_hot_reload().await;
|
||||
|
||||
WebSocketUpgrade::new()
|
||||
.upgrade(req, res, move |mut websocket| async move {
|
||||
use futures_util::StreamExt;
|
||||
|
||||
println!("🔥 Hot Reload WebSocket connected");
|
||||
{
|
||||
// update any rsx calls that changed before the websocket connected.
|
||||
{
|
||||
println!("🔮 Finding updates since last compile...");
|
||||
let templates_read = state.templates.read().await;
|
||||
|
||||
for template in &*templates_read {
|
||||
if websocket
|
||||
.send(Message::text(serde_json::to_string(&template).unwrap()))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("finished");
|
||||
}
|
||||
|
||||
let mut rx = tokio_stream::wrappers::WatchStream::from_changes(
|
||||
state.message_receiver.clone(),
|
||||
);
|
||||
while let Some(change) = rx.next().await {
|
||||
if let Some(template) = change {
|
||||
let template = { serde_json::to_string(&template).unwrap() };
|
||||
if websocket.send(Message::text(template)).await.is_err() {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
#[handler]
|
||||
async fn ignore_ws(req: &mut Request, res: &mut Response) -> Result<(), salvo::http::StatusError> {
|
||||
use salvo::websocket::WebSocketUpgrade;
|
||||
WebSocketUpgrade::new()
|
||||
.upgrade(req, res, |mut ws| async move {
|
||||
let _ = ws.send(salvo::websocket::Message::text("connected")).await;
|
||||
while let Some(msg) = ws.recv().await {
|
||||
if msg.is_err() {
|
||||
return;
|
||||
};
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
|
@ -1,417 +0,0 @@
|
|||
//! Dioxus utilities for the [Warp](https://docs.rs/warp/latest/warp/index.html) server framework.
|
||||
//!
|
||||
//! # Example
|
||||
//! ```rust
|
||||
//! #![allow(non_snake_case)]
|
||||
//! use dioxus_lib::prelude::*;
|
||||
//! use dioxus_fullstack::prelude::*;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! #[cfg(feature = "web")]
|
||||
//! dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
|
||||
//! #[cfg(feature = "server")]
|
||||
//! {
|
||||
//! tokio::runtime::Runtime::new()
|
||||
//! .unwrap()
|
||||
//! .block_on(async move {
|
||||
//! let routes = serve_dioxus_application("", ServerConfig::new(app, ()));
|
||||
//! warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
||||
//! });
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn app() -> Element {
|
||||
//! let text = use_signal(|| "...".to_string());
|
||||
//!
|
||||
//! rsx! {
|
||||
//! button {
|
||||
//! onclick: move |_| {
|
||||
//! to_owned![text];
|
||||
//! async move {
|
||||
//! if let Ok(data) = get_server_data().await {
|
||||
//! text.set(data);
|
||||
//! }
|
||||
//! }
|
||||
//! },
|
||||
//! "Run a server function"
|
||||
//! }
|
||||
//! "Server said: {text}"
|
||||
//! })
|
||||
//! }
|
||||
//!
|
||||
//! #[server(GetServerData)]
|
||||
//! async fn get_server_data() -> Result<String, ServerFnError> {
|
||||
//! Ok("Hello from the server!".to_string())
|
||||
//! }
|
||||
//!
|
||||
//! ```
|
||||
|
||||
use crate::layer::Service;
|
||||
use crate::{
|
||||
prelude::*, render::SSRState, serve_config::ServeConfig, server_fn::DioxusServerFnRegistry,
|
||||
};
|
||||
|
||||
use crate::server_fn_service;
|
||||
use dioxus_lib::prelude::VirtualDom;
|
||||
use server_fn::{Encoding, Payload, ServerFunctionRegistry};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use tokio::task::spawn_blocking;
|
||||
use warp::path::FullPath;
|
||||
use warp::Rejection;
|
||||
use warp::{
|
||||
filters::BoxedFilter,
|
||||
http::{Response, StatusCode},
|
||||
hyper::body::Bytes,
|
||||
path, Filter, Reply,
|
||||
};
|
||||
|
||||
/// Registers server functions with a custom handler function. This allows you to pass custom context to your server functions by generating a [`DioxusServerContext`] from the request.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use warp::{body, header, hyper::HeaderMap, path, post, Filter};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let routes = register_server_fns_with_handler(server_fn_route, |full_route, func| {
|
||||
/// path(full_route)
|
||||
/// .and(warp::post().or(warp::get()).unify())
|
||||
/// .and(request_parts())
|
||||
/// .and(warp::body::bytes())
|
||||
/// .and_then(move |parts, bytes: bytes::Bytes| {
|
||||
/// let mut service = server_fn_service(DioxusServerContext::default(), func.clone());
|
||||
/// async move {
|
||||
/// let req = warp::hyper::Request::from_parts(parts, bytes.into());
|
||||
/// service.run(req).await.map_err(|err| {
|
||||
/// tracing::error!("Server function error: {}", err);
|
||||
/// warp::reject::reject()
|
||||
/// })
|
||||
/// }
|
||||
/// })
|
||||
/// })
|
||||
/// warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub fn register_server_fns_with_handler<H, F, R>(
|
||||
server_fn_route: &'static str,
|
||||
mut handler: H,
|
||||
) -> BoxedFilter<(R,)>
|
||||
where
|
||||
H: FnMut(String, server_fn::ServerFnTraitObj<()>) -> F,
|
||||
F: Filter<Extract = (R,), Error = warp::Rejection> + Send + Sync + 'static,
|
||||
F::Extract: Send,
|
||||
R: Reply + 'static,
|
||||
{
|
||||
let mut filter: Option<BoxedFilter<F::Extract>> = 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 = handler(full_route, func).boxed();
|
||||
if let Some(boxed_filter) = filter.take() {
|
||||
filter = Some(boxed_filter.or(route).unify().boxed());
|
||||
} else {
|
||||
filter = Some(route);
|
||||
}
|
||||
}
|
||||
filter.expect("No server functions found")
|
||||
}
|
||||
|
||||
/// Registers server functions with the default handler. This handler function will pass an empty [`DioxusServerContext`] to your server functions.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let routes = register_server_fns("");
|
||||
/// warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl Reply,)> {
|
||||
register_server_fns_with_handler(server_fn_route, |full_route, func| {
|
||||
path(full_route)
|
||||
.and(warp::post().or(warp::get()).unify())
|
||||
.and(request_parts())
|
||||
.and(warp::body::bytes())
|
||||
.and_then(move |parts, bytes: bytes::Bytes| {
|
||||
let mut service = server_fn_service(DioxusServerContext::default(), func.clone());
|
||||
async move {
|
||||
let req = warp::hyper::Request::from_parts(parts, bytes.into());
|
||||
service.run(req).await.map_err(|err| {
|
||||
tracing::error!("Server function error: {}", err);
|
||||
|
||||
struct WarpServerFnError(String);
|
||||
impl std::fmt::Debug for WarpServerFnError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl warp::reject::Reject for WarpServerFnError {}
|
||||
|
||||
warp::reject::custom(WarpServerFnError(err.to_string()))
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Serves the Dioxus application. This will serve a complete server side rendered application.
|
||||
/// This will serve static assets, server render the application, register server functions, and intigrate with hot reloading.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// #![allow(non_snake_case)]
|
||||
/// use dioxus_lib::prelude::*;
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let routes = serve_dioxus_application("", ServerConfig::new(app, ()));
|
||||
/// warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
||||
/// }
|
||||
///
|
||||
/// fn app() -> Element {
|
||||
/// None
|
||||
/// }
|
||||
/// ```
|
||||
pub fn serve_dioxus_application(
|
||||
server_fn_route: &'static str,
|
||||
cfg: impl Into<ServeConfig>,
|
||||
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> BoxedFilter<(impl Reply,)> {
|
||||
let cfg = cfg.into();
|
||||
// Serve the dist folder and the index.html file
|
||||
let serve_dir = warp::fs::dir(cfg.assets_path.clone());
|
||||
|
||||
let virtual_dom_factory =
|
||||
Arc::new(virtual_dom_factory) as Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>;
|
||||
|
||||
connect_hot_reload()
|
||||
// First register the server functions
|
||||
.or(register_server_fns(server_fn_route))
|
||||
// Then the index route
|
||||
.or(path::end().and(render_ssr(cfg.clone(), {
|
||||
let virtual_dom_factory = virtual_dom_factory.clone();
|
||||
move || virtual_dom_factory()
|
||||
})))
|
||||
// Then the static assets
|
||||
.or(serve_dir)
|
||||
// Then all other routes
|
||||
.or(render_ssr(cfg, move || virtual_dom_factory()))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
/// Server render the application.
|
||||
pub fn render_ssr(
|
||||
cfg: ServeConfig,
|
||||
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::get()
|
||||
.and(request_parts())
|
||||
.and(with_ssr_state(&cfg, virtual_dom_factory))
|
||||
.then(
|
||||
move |parts: http::request::Parts,
|
||||
(renderer, virtual_dom_factory): (
|
||||
SSRState,
|
||||
Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>,
|
||||
)| {
|
||||
let route = parts.uri.path().to_string();
|
||||
let parts = Arc::new(RwLock::new(parts));
|
||||
let cfg = cfg.clone();
|
||||
async move {
|
||||
let server_context = DioxusServerContext::new(parts);
|
||||
|
||||
match renderer
|
||||
.render(route, &cfg, move || virtual_dom_factory(), &server_context)
|
||||
.await
|
||||
{
|
||||
Ok(rendered) => {
|
||||
let crate::render::RenderResponse { html, freshness } = rendered;
|
||||
|
||||
let mut res = Response::builder()
|
||||
.header("Content-Type", "text/html")
|
||||
.body(html)
|
||||
.unwrap();
|
||||
|
||||
let headers_mut = res.headers_mut();
|
||||
let headers = server_context.response_parts().unwrap().headers.clone();
|
||||
for (key, value) in headers.iter() {
|
||||
headers_mut.insert(key, value.clone());
|
||||
}
|
||||
freshness.write(headers_mut);
|
||||
|
||||
res
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to render ssr: {}", err);
|
||||
Response::builder()
|
||||
.status(500)
|
||||
.body("Failed to render ssr".into())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// An extractor for the request parts (used in [DioxusServerContext]). This will extract the method, uri, query, and headers from the request.
|
||||
pub fn request_parts(
|
||||
) -> impl Filter<Extract = (http::request::Parts,), Error = warp::reject::Rejection> + Clone {
|
||||
warp::method()
|
||||
.and(warp::filters::path::full())
|
||||
.and(
|
||||
warp::filters::query::raw()
|
||||
.or(warp::any().map(String::new))
|
||||
.unify(),
|
||||
)
|
||||
.and(warp::header::headers_cloned())
|
||||
.and_then(move |method, path: FullPath, query, headers| async move {
|
||||
http::uri::Builder::new()
|
||||
.path_and_query(format!("{}?{}", path.as_str(), query))
|
||||
.build()
|
||||
.map_err(|err| {
|
||||
warp::reject::custom(FailedToReadBody(format!("Failed to build uri: {}", err)))
|
||||
})
|
||||
.map(|uri| {
|
||||
let mut req = http::Request::builder()
|
||||
.method(method)
|
||||
.uri(uri)
|
||||
.body(())
|
||||
.unwrap();
|
||||
req.headers_mut().extend(headers);
|
||||
req.into_parts().0
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn with_ssr_state(
|
||||
cfg: &ServeConfig,
|
||||
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
||||
) -> impl Filter<
|
||||
Extract = ((
|
||||
SSRState,
|
||||
Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>,
|
||||
),),
|
||||
Error = std::convert::Infallible,
|
||||
> + Clone {
|
||||
let renderer = SSRState::new(cfg);
|
||||
let virtual_dom_factory =
|
||||
Arc::new(virtual_dom_factory) as Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>;
|
||||
warp::any().map(move || (renderer.clone(), virtual_dom_factory.clone()))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FailedToReadBody(String);
|
||||
|
||||
impl warp::reject::Reject for FailedToReadBody {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RecieveFailed(String);
|
||||
|
||||
impl warp::reject::Reject for RecieveFailed {}
|
||||
|
||||
/// Register the web RSX hot reloading endpoint. This will enable hot reloading for your application in debug mode when you call [`dioxus_hot_reload::hot_reload_init`].
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// #![allow(non_snake_case)]
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let routes = connect_hot_reload();
|
||||
/// warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
|
||||
/// }
|
||||
/// ```
|
||||
pub fn connect_hot_reload() -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone
|
||||
{
|
||||
#[cfg(not(all(debug_assertions, feature = "hot-reload")))]
|
||||
{
|
||||
warp::path!("_dioxus" / "hot_reload")
|
||||
.map(warp::reply)
|
||||
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND))
|
||||
}
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
{
|
||||
use crate::hot_reload::HotReloadState;
|
||||
use futures_util::sink::SinkExt;
|
||||
use futures_util::StreamExt;
|
||||
use warp::ws::Message;
|
||||
|
||||
let hot_reload = warp::path!("_dioxus" / "hot_reload")
|
||||
.and(warp::any().then(crate::hot_reload::spawn_hot_reload))
|
||||
.and(warp::ws())
|
||||
.map(move |state: &'static HotReloadState, ws: warp::ws::Ws| {
|
||||
// #[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
ws.on_upgrade(move |mut websocket| {
|
||||
async move {
|
||||
println!("🔥 Hot Reload WebSocket connected");
|
||||
{
|
||||
// update any rsx calls that changed before the websocket connected.
|
||||
{
|
||||
println!("🔮 Finding updates since last compile...");
|
||||
let templates_read = state.templates.read().await;
|
||||
|
||||
for template in &*templates_read {
|
||||
if websocket
|
||||
.send(Message::text(
|
||||
serde_json::to_string(&template).unwrap(),
|
||||
))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("finished");
|
||||
}
|
||||
|
||||
let mut rx = tokio_stream::wrappers::WatchStream::from_changes(
|
||||
state.message_receiver.clone(),
|
||||
);
|
||||
while let Some(change) = rx.next().await {
|
||||
if let Some(template) = change {
|
||||
let template = { serde_json::to_string(&template).unwrap() };
|
||||
if websocket.send(Message::text(template)).await.is_err() {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let disconnect =
|
||||
warp::path!("_dioxus" / "disconnect")
|
||||
.and(warp::ws())
|
||||
.map(move |ws: warp::ws::Ws| {
|
||||
println!("disconnect");
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload",))]
|
||||
ws.on_upgrade(move |mut websocket| async move {
|
||||
struct DisconnectOnDrop(Option<warp::ws::WebSocket>);
|
||||
impl Drop for DisconnectOnDrop {
|
||||
fn drop(&mut self) {
|
||||
std::mem::drop(self.0.take().unwrap().close());
|
||||
}
|
||||
}
|
||||
|
||||
let _ = websocket.send(Message::text("connected")).await;
|
||||
let mut ws = DisconnectOnDrop(Some(websocket));
|
||||
|
||||
loop {
|
||||
if ws.0.as_mut().unwrap().next().await.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
disconnect.or(hot_reload)
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ impl Config {
|
|||
|
||||
/// Set the address to serve the app on.
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub fn addr(self, addr: impl Into<std::net::SocketAddr>) -> Self {
|
||||
let addr = addr.into();
|
||||
Self { addr, ..self }
|
||||
|
@ -61,7 +61,7 @@ impl Config {
|
|||
|
||||
/// Set the route to the server functions.
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub fn server_fn_route(self, server_fn_route: &'static str) -> Self {
|
||||
Self {
|
||||
server_fn_route,
|
||||
|
@ -71,7 +71,7 @@ impl Config {
|
|||
|
||||
/// Set the incremental renderer config.
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self {
|
||||
Self {
|
||||
server_cfg: self.server_cfg.incremental(cfg),
|
||||
|
@ -81,21 +81,21 @@ impl Config {
|
|||
|
||||
/// Set the server config.
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub fn server_cfg(self, server_cfg: ServeConfigBuilder) -> Self {
|
||||
Self { server_cfg, ..self }
|
||||
}
|
||||
|
||||
/// Set the web config.
|
||||
#[cfg(feature = "web")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "web")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
|
||||
pub fn web_cfg(self, web_cfg: dioxus_web::Config) -> Self {
|
||||
Self { web_cfg, ..self }
|
||||
}
|
||||
|
||||
/// Set the desktop config.
|
||||
#[cfg(feature = "desktop")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "desktop")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
|
||||
pub fn desktop_cfg(self, desktop_cfg: dioxus_desktop::Config) -> Self {
|
||||
Self {
|
||||
desktop_cfg,
|
||||
|
@ -105,13 +105,13 @@ impl Config {
|
|||
|
||||
/// Set the mobile config.
|
||||
#[cfg(feature = "mobile")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "mobile")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
|
||||
pub fn mobile_cfg(self, mobile_cfg: dioxus_mobile::Config) -> Self {
|
||||
Self { mobile_cfg, ..self }
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
/// Launch a server application
|
||||
pub async fn launch_server(
|
||||
self,
|
||||
|
@ -121,7 +121,8 @@ impl Config {
|
|||
println!("Listening on {}", addr);
|
||||
let cfg = self.server_cfg.build();
|
||||
let server_fn_route = self.server_fn_route;
|
||||
#[cfg(all(feature = "axum", not(feature = "warp"), not(feature = "salvo")))]
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
{
|
||||
use crate::adapters::axum_adapter::{render_handler, DioxusRouterExt};
|
||||
use axum::routing::get;
|
||||
|
@ -144,52 +145,8 @@ impl Config {
|
|||
.layer(tower_http::compression::CompressionLayer::new().gzip(true)),
|
||||
)
|
||||
.into_make_service();
|
||||
axum::Server::bind(&addr).serve(router).await.unwrap();
|
||||
}
|
||||
#[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))]
|
||||
{
|
||||
use warp::Filter;
|
||||
// First register the server functions
|
||||
let router = register_server_fns(server_fn_route);
|
||||
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
||||
let router = {
|
||||
// Serve the dist folder and the index.html file
|
||||
let serve_dir = warp::fs::dir(cfg.assets_path);
|
||||
let build_virtual_dom = Arc::new(build_virtual_dom);
|
||||
|
||||
router
|
||||
.or(connect_hot_reload())
|
||||
// Then the index route
|
||||
.or(warp::path::end().and(render_ssr(cfg.clone(), {
|
||||
let build_virtual_dom = build_virtual_dom.clone();
|
||||
move || build_virtual_dom()
|
||||
})))
|
||||
// Then the static assets
|
||||
.or(serve_dir)
|
||||
// Then all other routes
|
||||
.or(render_ssr(cfg, move || build_virtual_dom()))
|
||||
};
|
||||
warp::serve(router.boxed().with(warp::filters::compression::gzip()))
|
||||
.run(addr)
|
||||
.await;
|
||||
}
|
||||
#[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))]
|
||||
{
|
||||
use crate::adapters::salvo_adapter::{DioxusRouterExt, SSRHandler};
|
||||
use salvo::conn::Listener;
|
||||
let router = salvo::Router::new().register_server_fns(server_fn_route);
|
||||
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
||||
let router = router
|
||||
.serve_static_assets(cfg.assets_path)
|
||||
.connect_hot_reload()
|
||||
.push(salvo::Router::with_path("/<**any_path>").get(SSRHandler::new(cfg)));
|
||||
let router = router.hoop(
|
||||
salvo::compression::Compression::new()
|
||||
.enable_gzip(salvo::prelude::CompressionLevel::Default),
|
||||
);
|
||||
salvo::Server::new(salvo::conn::tcp::TcpListener::new(addr).bind().await)
|
||||
.serve(router)
|
||||
.await;
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(listener, router).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,35 +19,35 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
type MyBody = axum::body::Body;
|
||||
|
||||
/// A service is a function that takes a request and returns an async response
|
||||
pub trait Service {
|
||||
/// Run the service and produce a future that resolves to a response
|
||||
fn run(
|
||||
&mut self,
|
||||
req: http::Request<hyper::body::Body>,
|
||||
req: http::Request<MyBody>,
|
||||
) -> Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<Response<hyper::body::Body>, server_fn::ServerFnError>,
|
||||
> + Send,
|
||||
dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
|
||||
+ Send,
|
||||
>,
|
||||
>;
|
||||
}
|
||||
|
||||
impl<S> Service for S
|
||||
where
|
||||
S: tower::Service<http::Request<hyper::body::Body>, Response = Response<hyper::body::Body>>,
|
||||
S: tower::Service<http::Request<MyBody>, Response = Response<MyBody>>,
|
||||
S::Future: Send + 'static,
|
||||
S::Error: Into<server_fn::ServerFnError>,
|
||||
{
|
||||
fn run(
|
||||
&mut self,
|
||||
req: http::Request<hyper::body::Body>,
|
||||
req: http::Request<MyBody>,
|
||||
) -> Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<Response<hyper::body::Body>, server_fn::ServerFnError>,
|
||||
> + Send,
|
||||
dyn std::future::Future<Output = Result<Response<MyBody>, server_fn::ServerFnError>>
|
||||
+ Send,
|
||||
>,
|
||||
> {
|
||||
let fut = self.call(req).instrument(tracing::trace_span!(
|
||||
|
@ -62,13 +62,13 @@ where
|
|||
/// A boxed service is a type-erased service that can be used without knowing the underlying type
|
||||
pub struct BoxedService(pub Box<dyn Service + Send>);
|
||||
|
||||
impl tower::Service<http::Request<hyper::body::Body>> for BoxedService {
|
||||
type Response = http::Response<hyper::body::Body>;
|
||||
impl tower::Service<http::Request<MyBody>> for BoxedService {
|
||||
type Response = http::Response<MyBody>;
|
||||
type Error = server_fn::ServerFnError;
|
||||
type Future = Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<http::Response<hyper::body::Body>, server_fn::ServerFnError>,
|
||||
Output = Result<http::Response<MyBody>, server_fn::ServerFnError>,
|
||||
> + Send,
|
||||
>,
|
||||
>;
|
||||
|
@ -80,7 +80,7 @@ impl tower::Service<http::Request<hyper::body::Body>> for BoxedService {
|
|||
Ok(()).into()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<hyper::body::Body>) -> Self::Future {
|
||||
fn call(&mut self, req: Request<MyBody>) -> Self::Future {
|
||||
self.0.run(req)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,21 +11,17 @@ mod html_storage;
|
|||
#[cfg(feature = "server")]
|
||||
mod adapters;
|
||||
// Splitting up the glob export lets us document features required for each adapter
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "axum")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||
#[cfg(feature = "axum")]
|
||||
pub use adapters::axum_adapter;
|
||||
// TODO: Compilation seems to be broken with the salvo feature enabled. Fix and add more features to checks in CI
|
||||
// #[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "salvo")))]
|
||||
// #[cfg(feature = "salvo")]
|
||||
// pub use adapters::salvo_adapter;
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "warp")))]
|
||||
#[cfg(feature = "warp")]
|
||||
pub use adapters::warp_adapter;
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
#[cfg(feature = "server")]
|
||||
pub use adapters::{server_fn_service, ServerFnHandler};
|
||||
|
||||
mod config;
|
||||
mod hooks;
|
||||
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "server"))]
|
||||
mod hot_reload;
|
||||
pub mod launch;
|
||||
|
@ -43,15 +39,11 @@ mod server_fn;
|
|||
/// A prelude of commonly used items in dioxus-fullstack.
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "axum")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "axum")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||
pub use crate::adapters::axum_adapter::*;
|
||||
// #[cfg(feature = "salvo")]
|
||||
// #[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "salvo")))]
|
||||
// pub use crate::adapters::salvo_adapter::*;
|
||||
#[cfg(feature = "warp")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "warp")))]
|
||||
pub use crate::adapters::warp_adapter::*;
|
||||
|
||||
use crate::hooks;
|
||||
|
||||
#[cfg(not(feature = "server"))]
|
||||
#[cfg_attr(
|
||||
any(docsrs, feature = "nightly-doc"),
|
||||
|
@ -59,8 +51,9 @@ pub mod prelude {
|
|||
)]
|
||||
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::layer::{Layer, Service};
|
||||
|
||||
#[cfg(all(feature = "server", feature = "router"))]
|
||||
#[cfg_attr(
|
||||
any(docsrs, feature = "nightly-doc"),
|
||||
|
@ -68,13 +61,15 @@ pub mod prelude {
|
|||
)]
|
||||
pub use crate::render::pre_cache_static_routes_with_props;
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::render::SSRState;
|
||||
|
||||
#[cfg(feature = "router")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "router")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "router")))]
|
||||
pub use crate::router::FullstackRouterConfig;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
|
||||
#[cfg(all(feature = "server", feature = "axum"))]
|
||||
#[cfg_attr(
|
||||
|
@ -83,44 +78,29 @@ pub mod prelude {
|
|||
)]
|
||||
pub use crate::server_context::Axum;
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::server_context::{
|
||||
extract, server_context, DioxusServerContext, FromServerContext, ProvideServerContext,
|
||||
};
|
||||
pub use crate::server_fn::DioxusServerFn;
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use crate::server_fn::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction};
|
||||
pub use dioxus_server_macro::*;
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
||||
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
||||
|
||||
pub use hooks::{server_cached::server_cached, server_future::use_server_future};
|
||||
}
|
||||
|
||||
// Warn users about overlapping features
|
||||
#[cfg(all(
|
||||
feature = "server",
|
||||
feature = "web",
|
||||
not(doc),
|
||||
not(feature = "nightly-doc")
|
||||
))]
|
||||
compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `web` feature are overlapping. Please choose one or the other.");
|
||||
// // Warn users about overlapping features
|
||||
// #[cfg(all(feature = "server", feature = "web", not(doc)))]
|
||||
// compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `web` feature are overlapping. Please choose one or the other.");
|
||||
|
||||
#[cfg(all(
|
||||
feature = "server",
|
||||
feature = "desktop",
|
||||
not(doc),
|
||||
not(feature = "nightly-doc")
|
||||
))]
|
||||
compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `desktop` feature are overlapping. Please choose one or the other.");
|
||||
// #[cfg(all(feature = "server", feature = "desktop", not(doc)))]
|
||||
// compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `desktop` feature are overlapping. Please choose one or the other.");
|
||||
|
||||
#[cfg(all(
|
||||
feature = "server",
|
||||
feature = "mobile",
|
||||
not(doc),
|
||||
not(feature = "nightly-doc")
|
||||
))]
|
||||
compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `mobile` feature are overlapping. Please choose one or the other.");
|
||||
// #[cfg(all(feature = "server", feature = "mobile", not(doc)))]
|
||||
// compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `mobile` feature are overlapping. Please choose one or the other.");
|
||||
|
|
|
@ -228,7 +228,7 @@ impl<T: Send + Sync + Clone + 'static> FromServerContext for FromContext<T> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "axum")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
|
||||
/// An adapter for axum extractors for the server context
|
||||
pub struct Axum;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
#[derive(Clone)]
|
||||
/// A trait object for a function that be called on serializable arguments and returns a serializable result.
|
||||
pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<()>);
|
||||
|
@ -41,7 +41,7 @@ impl ServerFnTraitObj {
|
|||
server_fn::inventory::collect!(ServerFnTraitObj);
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
/// Middleware for a server function
|
||||
pub struct ServerFnMiddleware {
|
||||
/// The prefix of the server function.
|
||||
|
@ -75,7 +75,7 @@ pub(crate) static MIDDLEWARE: once_cell::sync::Lazy<
|
|||
server_fn::inventory::collect!(ServerFnMiddleware);
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
/// A server function that can be called on serializable arguments and returns a serializable result.
|
||||
pub type ServerFunction = server_fn::SerializedFnTraitObj<()>;
|
||||
|
||||
|
@ -92,7 +92,7 @@ static REGISTERED_SERVER_FUNCTIONS: once_cell::sync::Lazy<
|
|||
});
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
/// The registry of all Dioxus server functions.
|
||||
pub struct DioxusServerFnRegistry;
|
||||
|
||||
|
@ -160,7 +160,7 @@ impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
|
|||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
/// Errors that can occur when registering a server function.
|
||||
#[derive(thiserror::Error, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ServerRegistrationFnError {
|
||||
|
@ -184,7 +184,7 @@ pub enum ServerRegistrationFnError {
|
|||
pub trait DioxusServerFn: server_fn::ServerFn<()> {
|
||||
/// Registers the server function, allowing the client to query it by URL.
|
||||
#[cfg(feature = "server")]
|
||||
#[cfg_attr(any(docsrs, feature = "nightly-doc"), doc(cfg(feature = "server")))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||
fn register_explicit() -> Result<(), server_fn::ServerFnError> {
|
||||
Self::register_in_explicit::<DioxusServerFnRegistry>()
|
||||
}
|
||||
|
|
5
packages/liveview/.vscode/settings.json
vendored
5
packages/liveview/.vscode/settings.json
vendored
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"rust-analyzer.cargo.features": [
|
||||
"warp"
|
||||
]
|
||||
}
|
|
@ -31,42 +31,19 @@ dioxus-hot-reload = { workspace = true, optional = true }
|
|||
dioxus-cli-config = { workspace = true }
|
||||
generational-box = { workspace = true }
|
||||
|
||||
# warp
|
||||
warp = { version = "0.3.3", optional = true }
|
||||
|
||||
# axum
|
||||
axum = { version = "0.6.1", optional = true, features = ["ws"] }
|
||||
|
||||
# salvo
|
||||
salvo = { version = "0.63.0", optional = true, features = ["websocket", "affix"] }
|
||||
once_cell = "1.17.1"
|
||||
async-trait = "0.1.71"
|
||||
|
||||
# rocket
|
||||
rocket = { version = "0.5.0", optional = true }
|
||||
rocket_ws = { version = "0.1.0", optional = true }
|
||||
|
||||
# actix is ... complicated?
|
||||
# actix-files = { version = "0.6.2", optional = true }
|
||||
# actix-web = { version = "4.2.1", optional = true }
|
||||
# actix-ws = { version = "0.2.5", optional = true }
|
||||
axum = { workspace = true, optional = true, features = ["ws"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_env_logger = { version = "0.5.0" }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
warp = "0.3.3"
|
||||
axum = { version = "0.6.1", features = ["ws"] }
|
||||
salvo = { version = "0.63.0", features = ["affix", "websocket"] }
|
||||
rocket = "0.5.0"
|
||||
rocket_ws = "0.1.0"
|
||||
tower = "0.4.13"
|
||||
axum = { workspace = true, features = ["ws"] }
|
||||
tower = { workspace = true }
|
||||
dioxus = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["hot-reload"]
|
||||
# actix = ["actix-files", "actix-web", "actix-ws"]
|
||||
hot-reload = ["dioxus-hot-reload"]
|
||||
rocket = ["dep:rocket", "dep:rocket_ws"]
|
||||
|
||||
[[example]]
|
||||
name = "axum"
|
||||
|
@ -75,15 +52,3 @@ required-features = ["axum"]
|
|||
[[example]]
|
||||
name = "axum_stress"
|
||||
required-features = ["axum"]
|
||||
|
||||
[[example]]
|
||||
name = "salvo"
|
||||
required-features = ["salvo"]
|
||||
|
||||
[[example]]
|
||||
name = "warp"
|
||||
required-features = ["warp"]
|
||||
|
||||
[[example]]
|
||||
name = "rocket"
|
||||
required-features = ["rocket"]
|
||||
|
|
|
@ -26,9 +26,6 @@
|
|||
The current backend frameworks supported include:
|
||||
|
||||
- Axum
|
||||
- Warp
|
||||
- Salvo
|
||||
- Rocket
|
||||
|
||||
Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework.
|
||||
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_liveview::LiveViewPool;
|
||||
use rocket::response::content::RawHtml;
|
||||
use rocket::{Config, Rocket, State};
|
||||
use rocket_ws::{Channel, WebSocket};
|
||||
|
||||
fn app() -> Element {
|
||||
let mut num = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
"hello Rocket! {num}"
|
||||
button { onclick: move |_| num += 1, "Increment" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn index_page_with_glue(glue: &str) -> RawHtml<String> {
|
||||
RawHtml(format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>Dioxus LiveView with Rocket</title> </head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
"#,
|
||||
glue = glue
|
||||
))
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index(config: &Config) -> RawHtml<String> {
|
||||
index_page_with_glue(&dioxus_liveview::interpreter_glue(&format!(
|
||||
"ws://{addr}:{port}/ws",
|
||||
addr = config.address,
|
||||
port = config.port,
|
||||
)))
|
||||
}
|
||||
|
||||
#[get("/as-path")]
|
||||
async fn as_path() -> RawHtml<String> {
|
||||
index_page_with_glue(&dioxus_liveview::interpreter_glue("/ws"))
|
||||
}
|
||||
|
||||
#[get("/ws")]
|
||||
fn ws(ws: WebSocket, pool: &State<LiveViewPool>) -> Channel<'static> {
|
||||
let pool = pool.inner().to_owned();
|
||||
|
||||
ws.channel(move |stream| {
|
||||
Box::pin(async move {
|
||||
let _ = pool
|
||||
.launch(dioxus_liveview::rocket_socket(stream), app)
|
||||
.await;
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let view = dioxus_liveview::LiveViewPool::new();
|
||||
|
||||
Rocket::build()
|
||||
.manage(view)
|
||||
.mount("/", routes![index, as_path, ws])
|
||||
.ignite()
|
||||
.await
|
||||
.expect("Failed to ignite rocket")
|
||||
.launch()
|
||||
.await
|
||||
.expect("Failed to launch rocket");
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_liveview::LiveViewPool;
|
||||
use salvo::affix;
|
||||
use salvo::prelude::*;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn app() -> Element {
|
||||
let mut num = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
"hello salvo! {num}"
|
||||
button { onclick: move |_| num += 1, "Increment" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr = "127.0.0.1:3030";
|
||||
let acceptor = TcpListener::new(addr).bind().await;
|
||||
|
||||
let view = LiveViewPool::new();
|
||||
|
||||
let router = Router::new()
|
||||
.hoop(affix::inject(Arc::new(view)))
|
||||
.get(index)
|
||||
.push(Router::with_path("ws").get(connect));
|
||||
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
Server::new(acceptor).serve(router).await;
|
||||
}
|
||||
|
||||
#[handler]
|
||||
fn index(res: &mut Response) {
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 3030).into();
|
||||
res.render(Text::Html(format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>Dioxus LiveView with Salvo</title> </head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
"#,
|
||||
glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws"))
|
||||
)));
|
||||
}
|
||||
|
||||
#[handler]
|
||||
async fn connect(
|
||||
req: &mut Request,
|
||||
depot: &mut Depot,
|
||||
res: &mut Response,
|
||||
) -> Result<(), StatusError> {
|
||||
let view = depot.obtain::<Arc<LiveViewPool>>().unwrap().clone();
|
||||
|
||||
WebSocketUpgrade::new()
|
||||
.upgrade(req, res, |ws| async move {
|
||||
_ = view.launch(dioxus_liveview::salvo_socket(ws), app).await;
|
||||
})
|
||||
.await
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_liveview::warp_adapter::warp_socket;
|
||||
use dioxus_liveview::LiveViewPool;
|
||||
use std::net::SocketAddr;
|
||||
use warp::ws::Ws;
|
||||
use warp::Filter;
|
||||
|
||||
fn app() -> Element {
|
||||
let mut num = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
"hello warp! {num}"
|
||||
button {
|
||||
onclick: move |_| num += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 3030).into();
|
||||
|
||||
let index = warp::path::end().map(move || {
|
||||
warp::reply::html(format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>Dioxus LiveView with Warp</title> </head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
"#,
|
||||
glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws/"))
|
||||
))
|
||||
});
|
||||
|
||||
let pool = LiveViewPool::new();
|
||||
|
||||
let ws = warp::path("ws")
|
||||
.and(warp::ws())
|
||||
.and(warp::any().map(move || pool.clone()))
|
||||
.map(move |ws: Ws, pool: LiveViewPool| {
|
||||
ws.on_upgrade(|ws| async move {
|
||||
let _ = pool.launch(warp_socket(ws), app).await;
|
||||
})
|
||||
});
|
||||
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
warp::serve(index.or(ws)).run(addr).await;
|
||||
}
|
|
@ -81,10 +81,8 @@ impl LiveviewRouter for Router {
|
|||
}
|
||||
|
||||
async fn start(self, address: impl Into<std::net::SocketAddr>) {
|
||||
if let Err(err) = axum::Server::bind(&address.into())
|
||||
.serve(self.into_make_service())
|
||||
.await
|
||||
{
|
||||
let listener = tokio::net::TcpListener::bind(address.into()).await.unwrap();
|
||||
if let Err(err) = axum::serve(listener, self.into_make_service()).await {
|
||||
eprintln!("Failed to start axum server: {}", err);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,26 +2,11 @@ use std::future::Future;
|
|||
|
||||
use dioxus_core::{Element, VirtualDom};
|
||||
|
||||
#[cfg(feature = "warp")]
|
||||
pub mod warp_adapter;
|
||||
#[cfg(feature = "warp")]
|
||||
pub use warp_adapter::*;
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
pub mod axum_adapter;
|
||||
#[cfg(feature = "axum")]
|
||||
pub use axum_adapter::*;
|
||||
|
||||
#[cfg(feature = "salvo")]
|
||||
pub mod salvo_adapter;
|
||||
#[cfg(feature = "salvo")]
|
||||
pub use salvo_adapter::*;
|
||||
|
||||
#[cfg(feature = "rocket")]
|
||||
pub mod rocket_adapter;
|
||||
#[cfg(feature = "rocket")]
|
||||
pub use rocket_adapter::*;
|
||||
|
||||
/// A trait for servers that can be used to host a LiveView app.
|
||||
pub trait LiveviewRouter {
|
||||
/// Create a new router.
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
use crate::LiveViewPool;
|
||||
use crate::LiveviewRouter;
|
||||
use crate::{LiveViewError, LiveViewSocket};
|
||||
use rocket::futures::{SinkExt, StreamExt};
|
||||
use rocket::response::content::RawHtml;
|
||||
use rocket::{get, routes, State};
|
||||
use rocket_ws::Channel;
|
||||
use rocket_ws::WebSocket;
|
||||
use rocket_ws::{result::Error, stream::DuplexStream, Message};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Convert a Rocket WebSocket into a `LiveViewSocket`.
|
||||
///
|
||||
/// This is required to launch a LiveView app using the Rocket web framework.
|
||||
pub fn rocket_socket(stream: DuplexStream) -> impl LiveViewSocket {
|
||||
stream
|
||||
.map(transform_rx)
|
||||
.with(transform_tx)
|
||||
.sink_map_err(|_| LiveViewError::SendingFailed)
|
||||
}
|
||||
|
||||
fn transform_rx(message: Result<Message, Error>) -> Result<Vec<u8>, LiveViewError> {
|
||||
message
|
||||
.map_err(|_| LiveViewError::SendingFailed)?
|
||||
.into_text()
|
||||
.map(|s| s.into_bytes())
|
||||
.map_err(|_| LiveViewError::SendingFailed)
|
||||
}
|
||||
|
||||
async fn transform_tx(message: Vec<u8>) -> Result<Message, Error> {
|
||||
Ok(Message::Binary(message))
|
||||
}
|
||||
|
||||
impl LiveviewRouter for rocket::Rocket<rocket::Build> {
|
||||
fn create_default_liveview_router() -> Self {
|
||||
Self::build()
|
||||
}
|
||||
|
||||
fn with_virtual_dom(
|
||||
self,
|
||||
route: &str,
|
||||
app: impl Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
#[get("/")]
|
||||
async fn index(request: &rocket::route::Route) -> RawHtml<String> {
|
||||
let route = request.uri.base();
|
||||
|
||||
let glue = crate::interpreter_glue(&format!("{route}/ws",));
|
||||
|
||||
let title = crate::app_title();
|
||||
|
||||
RawHtml(format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>{title}</title> </head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
"#
|
||||
))
|
||||
}
|
||||
|
||||
#[get("/ws")]
|
||||
fn ws(ws: WebSocket, app: &State<LiveviewApp>) -> Channel<'static> {
|
||||
let app = app.inner();
|
||||
let pool = app.pool.clone();
|
||||
let app = app.app.clone();
|
||||
|
||||
ws.channel(move |stream| {
|
||||
Box::pin(async move {
|
||||
let _ = pool
|
||||
.launch_virtualdom(crate::rocket_socket(stream), move || app())
|
||||
.await;
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
struct LiveviewApp {
|
||||
app: Arc<dyn Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static>,
|
||||
pool: LiveViewPool,
|
||||
}
|
||||
|
||||
let app = Arc::new(app);
|
||||
|
||||
let view = crate::LiveViewPool::new();
|
||||
|
||||
self.manage(LiveviewApp {
|
||||
app: app,
|
||||
pool: view,
|
||||
})
|
||||
.mount(route, routes![index, ws])
|
||||
}
|
||||
|
||||
async fn start(self, address: impl Into<std::net::SocketAddr>) {
|
||||
let address = address.into();
|
||||
|
||||
let figment = self
|
||||
.figment()
|
||||
.clone()
|
||||
.merge((rocket::Config::PORT, address.port()))
|
||||
.merge((rocket::Config::ADDRESS, address.ip()));
|
||||
|
||||
self.configure(figment)
|
||||
.ignite()
|
||||
.await
|
||||
.expect("Failed to ignite rocket")
|
||||
.launch()
|
||||
.await
|
||||
.expect("Failed to launch rocket");
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
use crate::LiveViewPool;
|
||||
use crate::LiveviewRouter;
|
||||
use crate::{LiveViewError, LiveViewSocket};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use salvo::conn::TcpListener;
|
||||
use salvo::http::StatusError;
|
||||
use salvo::websocket::WebSocketUpgrade;
|
||||
use salvo::websocket::{Message, WebSocket};
|
||||
use salvo::writing::Text;
|
||||
use salvo::Listener;
|
||||
use salvo::Server;
|
||||
use salvo::{handler, Depot, Request, Response, Router};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Convert a Salvo WebSocket into a `LiveViewSocket`.
|
||||
///
|
||||
/// This is required to launch a LiveView app using the Salvo 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<Vec<u8>, LiveViewError> {
|
||||
let as_bytes = message.map_err(|_| LiveViewError::SendingFailed)?;
|
||||
|
||||
Ok(as_bytes.into())
|
||||
}
|
||||
|
||||
async fn transform_tx(message: Vec<u8>) -> Result<Message, salvo::Error> {
|
||||
Ok(Message::binary(message))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LiveviewApp {
|
||||
app: Arc<dyn Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static>,
|
||||
pool: Arc<LiveViewPool>,
|
||||
}
|
||||
|
||||
impl LiveviewRouter for Router {
|
||||
fn create_default_liveview_router() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
fn with_virtual_dom(
|
||||
self,
|
||||
route: &str,
|
||||
app: impl Fn() -> dioxus_core::prelude::VirtualDom + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let app = Arc::new(app);
|
||||
|
||||
let view = crate::LiveViewPool::new();
|
||||
|
||||
self.push(
|
||||
Router::with_path(route)
|
||||
.hoop(salvo::affix::inject(LiveviewApp {
|
||||
app: app,
|
||||
pool: Arc::new(view),
|
||||
}))
|
||||
.get(index)
|
||||
.push(Router::with_path("ws").get(connect)),
|
||||
)
|
||||
}
|
||||
|
||||
async fn start(self, address: impl Into<std::net::SocketAddr>) {
|
||||
let address = address.into();
|
||||
|
||||
let acceptor = TcpListener::new(address).bind().await;
|
||||
Server::new(acceptor).serve(self).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[handler]
|
||||
fn index(req: &mut Request, res: &mut Response) {
|
||||
let base = req.uri().path();
|
||||
let title = crate::app_title();
|
||||
|
||||
res.render(Text::Html(format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head> <title>{title}</title> </head>
|
||||
<body> <div id="main"></div> </body>
|
||||
{glue}
|
||||
</html>
|
||||
"#,
|
||||
glue = crate::interpreter_glue(&format!("{base}/ws"))
|
||||
)));
|
||||
}
|
||||
|
||||
#[handler]
|
||||
async fn connect(
|
||||
req: &mut Request,
|
||||
depot: &mut Depot,
|
||||
res: &mut Response,
|
||||
) -> Result<(), StatusError> {
|
||||
let app = depot.obtain::<LiveviewApp>().unwrap().clone();
|
||||
let view = app.pool.clone();
|
||||
let app = app.app.clone();
|
||||
|
||||
WebSocketUpgrade::new()
|
||||
.upgrade(req, res, |ws| async move {
|
||||
_ = view
|
||||
.launch_virtualdom(crate::salvo_socket(ws), move || app())
|
||||
.await;
|
||||
})
|
||||
.await
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
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<Vec<u8>, LiveViewError> {
|
||||
// Destructure the `message` into the buffer we got from warp.
|
||||
let msg = message
|
||||
.map_err(|_| LiveViewError::SendingFailed)?
|
||||
.into_bytes();
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
async fn transform_tx(message: Vec<u8>) -> Result<Message, warp::Error> {
|
||||
Ok(Message::binary(message))
|
||||
}
|
|
@ -3,10 +3,6 @@ use std::any::Any;
|
|||
|
||||
#[cfg(feature = "axum")]
|
||||
pub type Config = crate::Config<axum::Router>;
|
||||
#[cfg(all(feature = "salvo", not(feature = "axum")))]
|
||||
pub type Config = crate::Config<salvo::Router>;
|
||||
#[cfg(all(feature = "rocket", not(any(feature = "axum", feature = "salvo"))))]
|
||||
pub type Config = crate::Config<rocket::Rocket<rocket::Build>>;
|
||||
|
||||
/// Launches the WebView and runs the event loop, with configuration and root props.
|
||||
pub fn launch(
|
||||
|
|
|
@ -25,7 +25,7 @@ impl<T> WebsocketRx for T where T: StreamExt<Item = Result<String, LiveViewError
|
|||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LiveViewError {
|
||||
#[error("warp error")]
|
||||
#[error("Sending to client error")]
|
||||
SendingFailed,
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ pub fn interpreter_glue(url_or_path: &str) -> String {
|
|||
// If the url starts with a `/`, generate glue which reuses current host
|
||||
let get_ws_url = if url_or_path.starts_with('/') {
|
||||
r#"
|
||||
let loc = window.location;
|
||||
let loc = window.location;
|
||||
let new_url = "";
|
||||
if (loc.protocol === "https:") {{
|
||||
new_url = "wss:";
|
||||
|
@ -135,7 +135,7 @@ pub fn interpreter_glue(url_or_path: &str) -> String {
|
|||
function __dioxusGetWsUrl(path) {{
|
||||
{get_ws_url}
|
||||
}}
|
||||
|
||||
|
||||
var WS_ADDR = __dioxusGetWsUrl("{url_or_path}");
|
||||
{handle_edits}
|
||||
</script>
|
||||
|
|
|
@ -10,4 +10,4 @@ publish = false
|
|||
dioxus = { workspace = true }
|
||||
dioxus-liveview = { workspace = true, features = ["axum"] }
|
||||
tokio = { version = "1.19.2", features = ["full"] }
|
||||
axum = { version = "0.6.1", features = ["ws"] }
|
||||
axum = { workspace = true, features = ["ws"] }
|
||||
|
|
|
@ -39,7 +39,7 @@ web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils", "js-sys"]
|
|||
fullstack = ["dioxus-fullstack"]
|
||||
|
||||
[dev-dependencies]
|
||||
axum = { version = "0.6.1", features = ["ws"] }
|
||||
axum = { workspace = true, features = ["ws"] }
|
||||
dioxus = { workspace = true, features = ["router" ]}
|
||||
# dioxus-liveview = { workspace = true, features = ["axum"] }
|
||||
dioxus-ssr = { path = "../ssr" }
|
||||
|
|
|
@ -26,9 +26,7 @@ async fn main() {
|
|||
"/ws",
|
||||
get(move |ws: WebSocketUpgrade| async move {
|
||||
ws.on_upgrade(move |socket| async move {
|
||||
_ = view
|
||||
.launch(dioxus_liveview::axum_socket(socket), Route::Home {})
|
||||
.await;
|
||||
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
@ -43,11 +41,13 @@ async fn main() {
|
|||
|
||||
#[cfg(not(feature = "liveview"))]
|
||||
fn main() {
|
||||
launch(|| {
|
||||
rsx! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
})
|
||||
launch(app)
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
|
|
@ -175,10 +175,8 @@ where
|
|||
let eval_provider = consume_context::<Rc<dyn EvalProvider>>();
|
||||
|
||||
let create_eval = Rc::new(move |script: &str| {
|
||||
eval_provider
|
||||
.new_evaluator(script.to_string())
|
||||
.map(UseEval::new)
|
||||
}) as Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>;
|
||||
UseEval::new(eval_provider.new_evaluator(script.to_string()))
|
||||
}) as Rc<dyn Fn(&str) -> UseEval>;
|
||||
|
||||
// Listen to server actions
|
||||
spawn({
|
||||
|
@ -256,7 +254,7 @@ where
|
|||
history.length,
|
||||
];
|
||||
"#,
|
||||
).expect("failed to load state").await.expect("serializable state");
|
||||
).await.expect("serializable state");
|
||||
let (route, state, session, depth) = serde_json::from_value::<(
|
||||
String,
|
||||
Option<State>,
|
||||
|
@ -276,7 +274,8 @@ where
|
|||
// Call the updater callback
|
||||
(updater.read().unwrap())();
|
||||
|
||||
create_eval(&format!(r#"
|
||||
create_eval(&format!(
|
||||
r#"
|
||||
// this does not trigger a PopState event
|
||||
history.replaceState({state}, "", "{route}");
|
||||
sessionStorage.setItem("liveview", '{session}');
|
||||
|
@ -287,7 +286,8 @@ where
|
|||
event.state,
|
||||
]);
|
||||
}});
|
||||
"#)).expect("failed to initialize popstate")
|
||||
"#
|
||||
))
|
||||
};
|
||||
|
||||
loop {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::contexts::router::RoutingCallback;
|
||||
use crate::history::HistoryProvider;
|
||||
use crate::prelude::*;
|
||||
use crate::routable::Routable;
|
||||
use dioxus_lib::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Global configuration options for the router.
|
||||
///
|
||||
|
@ -31,43 +29,6 @@ pub struct RouterConfig<R: Routable> {
|
|||
pub(crate) initial_route: Option<R>,
|
||||
}
|
||||
|
||||
macro_rules! default_history {
|
||||
($initial_route:ident) => {
|
||||
{
|
||||
// If we are on wasm32 and the web feature is enabled, use the web history.
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
return Box::new(AnyHistoryProviderImplWrapper::new(WebHistory::<R>::default()));
|
||||
// If we are using dioxus fullstack and the ssr feature is enabled, use the memory history with the initial path set to the current path in fullstack
|
||||
#[cfg(all(feature = "fullstack", feature = "ssr"))]
|
||||
return Box::new(AnyHistoryProviderImplWrapper::new(MemoryHistory::<R>::with_initial_path(
|
||||
dioxus_fullstack::prelude::server_context()
|
||||
.request_parts()
|
||||
.unwrap()
|
||||
.uri
|
||||
.to_string()
|
||||
.parse()
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::error!("Failed to parse uri: {}", err);
|
||||
"/"
|
||||
.parse()
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("Failed to parse uri: {}", err);
|
||||
})
|
||||
}),
|
||||
)));
|
||||
// If we are not on wasm32 and the liveview feature is enabled, use the liveview history.
|
||||
#[cfg(all(feature = "liveview"))]
|
||||
return Box::new(AnyHistoryProviderImplWrapper::new(LiveviewHistory::new_with_initial_path($initial_route)));
|
||||
// Otherwise use the memory history.
|
||||
#[cfg(all(
|
||||
not(all(target_arch = "wasm32", feature = "web")),
|
||||
not(all(feature = "liveview", not(target_arch = "wasm32"))),
|
||||
))]
|
||||
Box::new(AnyHistoryProviderImplWrapper::new(MemoryHistory::with_initial_path($initial_route)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<R: Routable + Clone> Default for RouterConfig<R>
|
||||
where
|
||||
<R as std::str::FromStr>::Err: std::fmt::Display,
|
||||
|
@ -94,7 +55,7 @@ where
|
|||
));
|
||||
self.history
|
||||
.take()
|
||||
.unwrap_or_else(|| default_history!(initial_route))
|
||||
.unwrap_or_else(|| default_history(initial_route))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,3 +115,49 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the default history provider for the current platform.
|
||||
fn default_history<R: Routable + Clone>(initial_route: R) -> Box<dyn AnyHistoryProvider>
|
||||
where
|
||||
<R as std::str::FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
// If we're on the web and have wasm, use the web history provider
|
||||
if cfg!(all(target_arch = "wasm32", feature = "web")) {
|
||||
return Box::new(AnyHistoryProviderImplWrapper::new(
|
||||
WebHistory::<R>::default(),
|
||||
));
|
||||
}
|
||||
|
||||
// If we're using fullstack and server side rendering, use the memory history provider
|
||||
if cfg!(all(feature = "fullstack", feature = "ssr")) {
|
||||
return Box::new(AnyHistoryProviderImplWrapper::new(
|
||||
MemoryHistory::<R>::with_initial_path(
|
||||
dioxus_fullstack::prelude::server_context()
|
||||
.request_parts()
|
||||
.unwrap()
|
||||
.uri
|
||||
.to_string()
|
||||
.parse()
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::error!("Failed to parse uri: {}", err);
|
||||
"/".parse().unwrap_or_else(|err| {
|
||||
panic!("Failed to parse uri: {}", err);
|
||||
})
|
||||
}),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// If liveview is enabled, use the liveview history provider
|
||||
if cfg!(feature = "liveview") {
|
||||
return Box::new(AnyHistoryProviderImplWrapper::new(
|
||||
LiveviewHistory::new_with_initial_path(initial_route),
|
||||
));
|
||||
}
|
||||
|
||||
// If none of the above, use the memory history provider, which is a decent enough fallback
|
||||
// Eventually we want to integrate with the mobile history provider, and other platform providers
|
||||
Box::new(AnyHistoryProviderImplWrapper::new(
|
||||
MemoryHistory::with_initial_path(initial_route),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -15,15 +15,17 @@ generational-box = { workspace = true }
|
|||
askama_escape = "0.10.3"
|
||||
thiserror = "1.0.23"
|
||||
rustc-hash = "1.1.0"
|
||||
lru = "0.10.0"
|
||||
lru = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
http = "0.2.9"
|
||||
http = { workspace = true }
|
||||
tokio = { version = "1.28", features = ["fs", "io-util"], optional = true }
|
||||
async-trait = "0.1.58"
|
||||
serde_json = { version = "1.0" }
|
||||
async-trait = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true }
|
||||
dioxus-signals = { workspace = true }
|
||||
|
||||
tracing = { workspace = true }
|
||||
fern = { version = "0.6.0", features = ["colored"] }
|
||||
anyhow = "1.0"
|
||||
|
@ -31,7 +33,6 @@ argh = "0.1.4"
|
|||
serde = "1.0.120"
|
||||
serde_json = "1.0.61"
|
||||
fs_extra = "1.2.0"
|
||||
dioxus-signals = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
Loading…
Reference in a new issue