mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
actix-web
integration with builtin server function handler route
This commit is contained in:
parent
20634e38a1
commit
eff42a196f
7 changed files with 107 additions and 84 deletions
|
@ -8,6 +8,9 @@ members = [
|
|||
"leptos_reactive",
|
||||
"leptos_server",
|
||||
|
||||
# integrations
|
||||
"integrations/actix",
|
||||
|
||||
# libraries
|
||||
"meta",
|
||||
"router",
|
||||
|
|
|
@ -19,12 +19,12 @@ lazy_static = "1"
|
|||
leptos = { path = "../../../leptos/leptos", default-features = false, features = [
|
||||
"serde",
|
||||
] }
|
||||
leptos_actix = { path = "../../../leptos/integrations/actix", optional = true }
|
||||
leptos_meta = { path = "../../../leptos/meta", default-features = false }
|
||||
leptos_router = { path = "../../../leptos/router", default-features = false }
|
||||
log = "0.4"
|
||||
simple_logger = "2"
|
||||
gloo = { git = "https://github.com/rustwasm/gloo" }
|
||||
#counter = { path = "../counter", default-features = false}
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
|
@ -34,10 +34,11 @@ ssr = [
|
|||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web"]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
|
|
@ -41,45 +41,6 @@ cfg_if! {
|
|||
))
|
||||
}
|
||||
|
||||
#[post("/api/{tail:.*}")]
|
||||
async fn handle_server_fns(
|
||||
req: HttpRequest,
|
||||
params: web::Path<String>,
|
||||
body: web::Bytes,
|
||||
) -> impl Responder {
|
||||
let path = params.into_inner();
|
||||
let accept_header = req
|
||||
.headers()
|
||||
.get("Accept")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
|
||||
if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
let body: &[u8] = &body;
|
||||
let (cx, disposer) = raw_scope_and_disposer();
|
||||
match server_fn(cx, &body).await {
|
||||
Ok(serialized) => {
|
||||
// if this is Accept: application/json then send a serialized JSON response
|
||||
if let Some("application/json") = accept_header {
|
||||
HttpResponse::Ok().body(serialized)
|
||||
}
|
||||
// otherwise, it's probably a <form> submit or something: redirect back to the referrer
|
||||
else {
|
||||
HttpResponse::SeeOther()
|
||||
.insert_header(("Location", "/"))
|
||||
.content_type("application/json")
|
||||
.body(serialized)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("server function error: {e:#?}");
|
||||
HttpResponse::InternalServerError().body(e.to_string())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HttpResponse::BadRequest().body(format!("Could not find a server function at that route."))
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/events")]
|
||||
async fn counter_events() -> impl Responder {
|
||||
use futures::StreamExt;
|
||||
|
@ -105,7 +66,7 @@ cfg_if! {
|
|||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg"))
|
||||
.service(counter_events)
|
||||
.service(handle_server_fns)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.service(render)
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
|
|
|
@ -19,6 +19,7 @@ cfg-if = "1"
|
|||
leptos = { path = "../../../leptos/leptos", default-features = false, features = [
|
||||
"serde",
|
||||
] }
|
||||
leptos_actix = { path = "../../../leptos/integrations/actix", optional = true }
|
||||
leptos_meta = { path = "../../../leptos/meta", default-features = false }
|
||||
leptos_router = { path = "../../../leptos/router", default-features = false }
|
||||
log = "0.4"
|
||||
|
@ -37,10 +38,11 @@ ssr = [
|
|||
"dep:actix-web",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "sqlx"]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
|
|
@ -54,46 +54,6 @@ cfg_if! {
|
|||
)
|
||||
}
|
||||
|
||||
#[post("/api/{tail:.*}")]
|
||||
async fn handle_server_fns(
|
||||
req: HttpRequest,
|
||||
params: web::Path<String>,
|
||||
body: web::Bytes,
|
||||
) -> impl Responder {
|
||||
let path = params.into_inner();
|
||||
let accept_header = req
|
||||
.headers()
|
||||
.get("Accept")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
|
||||
if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
let body: &[u8] = &body;
|
||||
let (cx, disposer) = raw_scope_and_disposer();
|
||||
provide_context(cx, req.clone());
|
||||
match server_fn(cx, &body).await {
|
||||
Ok(serialized) => {
|
||||
// if this is Accept: application/json then send a serialized JSON response
|
||||
if let Some("application/json") = accept_header {
|
||||
HttpResponse::Ok().body(serialized)
|
||||
}
|
||||
// otherwise, it's probably a <form> submit or something: redirect back to the referrer
|
||||
else {
|
||||
HttpResponse::SeeOther()
|
||||
.insert_header(("Location", "/"))
|
||||
.content_type("application/json")
|
||||
.body(serialized)
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("server function error: {e:#?}");
|
||||
HttpResponse::InternalServerError().body(e.to_string())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HttpResponse::BadRequest().body(format!("Could not find a server function at that route."))
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let mut conn = db().await.expect("couldn't connect to DB");
|
||||
|
@ -107,7 +67,7 @@ cfg_if! {
|
|||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg"))
|
||||
.service(handle_server_fns)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.service(render)
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
|
|
10
integrations/actix/Cargo.toml
Normal file
10
integrations/actix/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "leptos_actix"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [
|
||||
"ssr",
|
||||
] }
|
86
integrations/actix/src/lib.rs
Normal file
86
integrations/actix/src/lib.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use actix_web::*;
|
||||
use leptos::*;
|
||||
|
||||
/// An Actix [Route](actix_web::Route) that listens for a `POST` request with
|
||||
/// Leptos server function arguments in the body, runs the server function if found,
|
||||
/// and returns the resulting [HttpResponse].
|
||||
///
|
||||
/// The provides the [HttpRequest] to the server [Scope](leptos_reactive::Scope).
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::*;
|
||||
///
|
||||
/// fn register_server_functions() {
|
||||
/// // call ServerFn::register() for each of the server functions you've defined
|
||||
/// }
|
||||
///
|
||||
/// # if false { // don't actually try to run a server in a doctest...
|
||||
/// #[actix_web::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
/// // make sure you actually register your server functions
|
||||
/// register_server_functions();
|
||||
///
|
||||
/// HttpServer::new(|| {
|
||||
/// App::new()
|
||||
/// // "/api" should match the prefix, if any, declared when defining server functions
|
||||
/// // {tail:.*} passes the remainder of the URL as the server function name
|
||||
/// .route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
/// })
|
||||
/// .bind(("127.0.0.1", 8080))?
|
||||
/// .run()
|
||||
/// .await
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn handle_server_fns() -> Route {
|
||||
web::post().to(handle_server_fn)
|
||||
}
|
||||
|
||||
async fn handle_server_fn(
|
||||
req: HttpRequest,
|
||||
params: web::Path<String>,
|
||||
body: web::Bytes,
|
||||
) -> impl Responder {
|
||||
let path = params.into_inner();
|
||||
let accept_header = req
|
||||
.headers()
|
||||
.get("Accept")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
|
||||
if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
let body: &[u8] = &body;
|
||||
let (cx, disposer) = raw_scope_and_disposer();
|
||||
|
||||
// provide HttpRequest as context in server scope
|
||||
provide_context(cx, req.clone());
|
||||
|
||||
match server_fn(cx, body).await {
|
||||
Ok(serialized) => {
|
||||
// clean up the scope, which we only needed to run the server fn
|
||||
disposer.dispose();
|
||||
|
||||
// if this is Accept: application/json then send a serialized JSON response
|
||||
if let Some("application/json") = accept_header {
|
||||
HttpResponse::Ok().body(serialized)
|
||||
}
|
||||
// otherwise, it's probably a <form> submit or something: redirect back to the referrer
|
||||
else {
|
||||
let referer = req
|
||||
.headers()
|
||||
.get("Referer")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.unwrap_or("/");
|
||||
HttpResponse::SeeOther()
|
||||
.insert_header(("Location", referer))
|
||||
.content_type("application/json")
|
||||
.body(serialized)
|
||||
}
|
||||
}
|
||||
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
|
||||
}
|
||||
} else {
|
||||
HttpResponse::BadRequest().body(format!("Could not find a server function at that route."))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue