mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
handle_server_fns
for Axum
This commit is contained in:
parent
525a31bf3d
commit
8b3a8489b6
4 changed files with 102 additions and 5 deletions
|
@ -8,7 +8,7 @@ use leptos_router::*;
|
|||
/// 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 provides the [HttpRequest] to the server [Scope](leptos_reactive::Scope).
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
///
|
||||
|
|
|
@ -4,6 +4,7 @@ version = "0.0.1"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum-macros = "0.2"
|
||||
axum = "0.5"
|
||||
futures = "0.3"
|
||||
leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use axum::{
|
||||
body::{Body, Bytes, StreamBody},
|
||||
http::Request,
|
||||
extract::Path,
|
||||
http::{HeaderMap, HeaderValue, Request, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use axum_macros::debug_handler;
|
||||
use futures::{Future, SinkExt, Stream, StreamExt};
|
||||
use leptos::*;
|
||||
use leptos_meta::MetaContext;
|
||||
|
@ -9,6 +12,101 @@ use leptos_router::*;
|
|||
use std::io;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// An Axum handlers to listens for a request with Leptos server function arguments in the body,
|
||||
/// run the server function if found, and return the resulting [Response].
|
||||
///
|
||||
/// This provides the [HeaderMap] to the server [Scope](leptos_reactive::Scope).
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{handler::Handler, routing::post, Router};
|
||||
/// use std::net::SocketAddr;
|
||||
/// use leptos::*;
|
||||
///
|
||||
/// # if false { // don't actually try to run a server in a doctest...
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let addr = SocketAddr::from(([127, 0, 0, 1], 8082));
|
||||
///
|
||||
/// // build our application with a route
|
||||
/// let app = Router::new()
|
||||
/// .route("/api/tail*", post(leptos_axum::handle_server_fns));
|
||||
///
|
||||
/// // run our app with hyper
|
||||
/// // `axum::Server` is a re-export of `hyper::Server`
|
||||
/// axum::Server::bind(&addr)
|
||||
/// .serve(app.into_make_service())
|
||||
/// .await
|
||||
/// .unwrap();
|
||||
/// }
|
||||
/// # }
|
||||
#[debug_handler]
|
||||
pub async fn handle_server_fns(
|
||||
Path(path): Path<String>,
|
||||
headers: HeaderMap<HeaderValue>,
|
||||
body: Bytes,
|
||||
) -> impl IntoResponse {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
std::thread::spawn({
|
||||
move || {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("couldn't spawn runtime")
|
||||
.block_on({
|
||||
async move {
|
||||
let body: &[u8] = &body;
|
||||
|
||||
let res = if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
// TODO this leaks a runtime once per invocation
|
||||
let (cx, disposer) = raw_scope_and_disposer();
|
||||
|
||||
// provide headers as context in server scope
|
||||
provide_context(cx, headers.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
|
||||
let accept_header =
|
||||
headers.get("Accept").and_then(|value| value.to_str().ok());
|
||||
if let Some("application/json") = accept_header {
|
||||
Response::builder().status(StatusCode::OK).body(serialized)
|
||||
}
|
||||
// otherwise, it's probably a <form> submit or something: redirect back to the referrer
|
||||
else {
|
||||
let referer = headers
|
||||
.get("Referer")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.unwrap_or("/");
|
||||
Response::builder()
|
||||
.status(StatusCode::SEE_OTHER)
|
||||
.header("Location", referer)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serialized)
|
||||
}
|
||||
}
|
||||
Err(e) => Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(e.to_string()),
|
||||
}
|
||||
} else {
|
||||
Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body("Could not find a server function at that route.".to_string())
|
||||
}
|
||||
.expect("could not build Response");
|
||||
|
||||
_ = tx.send(res);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
rx.await.unwrap()
|
||||
}
|
||||
|
||||
pub type PinnedHtmlStream = Pin<Box<dyn Stream<Item = io::Result<Bytes>> + Send>>;
|
||||
|
||||
/// Returns an Axum [Handler](axum::Handler) that listens for a `GET` request and tries
|
||||
|
@ -125,7 +223,7 @@ pub fn render_app_to_stream(
|
|||
}
|
||||
}));
|
||||
while let Some(fragment) = shell.next().await {
|
||||
tx.send(fragment).await;
|
||||
_ = tx.send(fragment).await;
|
||||
}
|
||||
tx.close_channel();
|
||||
})
|
||||
|
|
|
@ -140,13 +140,11 @@
|
|||
#![feature(type_name_of_val)]
|
||||
|
||||
mod components;
|
||||
mod fetch;
|
||||
mod history;
|
||||
mod hooks;
|
||||
mod matching;
|
||||
|
||||
pub use components::*;
|
||||
pub use fetch::*;
|
||||
pub use history::*;
|
||||
pub use hooks::*;
|
||||
pub use matching::Branch;
|
||||
|
|
Loading…
Reference in a new issue