actix-web integration with builtin server function handler route

This commit is contained in:
Greg Johnston 2022-11-20 15:25:45 -05:00
parent 20634e38a1
commit eff42a196f
7 changed files with 107 additions and 84 deletions

View file

@ -8,6 +8,9 @@ members = [
"leptos_reactive",
"leptos_server",
# integrations
"integrations/actix",
# libraries
"meta",
"router",

View file

@ -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"]]

View file

@ -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())
})

View file

@ -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"]]

View file

@ -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())
})

View 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",
] }

View 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."))
}
}