mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
finished Actix support?
This commit is contained in:
parent
e1a9856ca9
commit
c8fbee18c8
7 changed files with 125 additions and 142 deletions
Binary file not shown.
|
@ -46,7 +46,7 @@ cfg_if! {
|
|||
|
||||
App::new()
|
||||
.service(css)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
// .route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), TodoApp)
|
||||
.service(Files::new("/", site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
|
|
|
@ -11,7 +11,7 @@ use actix_web::{
|
|||
body::BoxBody,
|
||||
dev::{ServiceFactory, ServiceRequest},
|
||||
http::header,
|
||||
web::{Bytes, ServiceConfig},
|
||||
web::{Payload, ServiceConfig},
|
||||
*,
|
||||
};
|
||||
use futures::{Stream, StreamExt};
|
||||
|
@ -178,60 +178,59 @@ pub fn handle_server_fns() -> Route {
|
|||
pub fn handle_server_fns_with_context(
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
) -> Route {
|
||||
web::to(
|
||||
move |req: HttpRequest, params: web::Path<String>, body: web::Bytes| {
|
||||
web::to(move |req: HttpRequest, payload: Payload| {
|
||||
let additional_context = additional_context.clone();
|
||||
async move {
|
||||
let additional_context = additional_context.clone();
|
||||
async move {
|
||||
let additional_context = additional_context.clone();
|
||||
|
||||
let path = req.path();
|
||||
if let Some(mut service) =
|
||||
server_fn::actix::get_server_fn_service(path)
|
||||
{
|
||||
let runtime = create_runtime();
|
||||
let path = req.path();
|
||||
if let Some(mut service) =
|
||||
server_fn::actix::get_server_fn_service(path)
|
||||
{
|
||||
let runtime = create_runtime();
|
||||
|
||||
// Add additional info to the context of the server function
|
||||
additional_context();
|
||||
provide_context(req.clone());
|
||||
let res_parts = ResponseOptions::default();
|
||||
provide_context(res_parts.clone());
|
||||
// Add additional info to the context of the server function
|
||||
additional_context();
|
||||
provide_context(req.clone());
|
||||
let res_parts = ResponseOptions::default();
|
||||
provide_context(res_parts.clone());
|
||||
|
||||
let mut res =
|
||||
service.0.run(ActixRequest::from(req)).await.take();
|
||||
let mut res = service
|
||||
.0
|
||||
.run(ActixRequest::from((req, payload)))
|
||||
.await
|
||||
.take();
|
||||
|
||||
// Override StatusCode if it was set in a Resource or Element
|
||||
if let Some(status) = res_parts.0.read().status {
|
||||
*res.status_mut() = status;
|
||||
}
|
||||
|
||||
// Use provided ResponseParts headers if they exist
|
||||
let headers = res.headers_mut();
|
||||
for (k, v) in
|
||||
std::mem::take(&mut res_parts.0.write().headers)
|
||||
{
|
||||
headers.append(k.clone(), v.clone());
|
||||
}
|
||||
|
||||
// clean up the scope
|
||||
runtime.dispose();
|
||||
res
|
||||
} else {
|
||||
HttpResponse::BadRequest().body(format!(
|
||||
"Could not find a server function at the route {:?}. \
|
||||
\n\nIt's likely that either
|
||||
1. The API prefix you specify in the `#[server]` \
|
||||
macro doesn't match the prefix at which your server \
|
||||
function handler is mounted, or \n2. You are on a \
|
||||
platform that doesn't support automatic server \
|
||||
function registration and you need to call \
|
||||
ServerFn::register_explicit() on the server function \
|
||||
type, somewhere in your `main` function.",
|
||||
req.path()
|
||||
))
|
||||
// Override StatusCode if it was set in a Resource or Element
|
||||
if let Some(status) = res_parts.0.read().status {
|
||||
*res.status_mut() = status;
|
||||
}
|
||||
|
||||
// Use provided ResponseParts headers if they exist
|
||||
let headers = res.headers_mut();
|
||||
for (k, v) in std::mem::take(&mut res_parts.0.write().headers) {
|
||||
headers.append(k.clone(), v.clone());
|
||||
}
|
||||
|
||||
// clean up the scope
|
||||
runtime.dispose();
|
||||
res
|
||||
} else {
|
||||
HttpResponse::BadRequest().body(format!(
|
||||
"Could not find a server function at the route {:?}. \
|
||||
\n\nIt's likely that either
|
||||
1. The API prefix you specify in the `#[server]` \
|
||||
macro doesn't match the prefix at which your server \
|
||||
function handler is mounted, or \n2. You are on a \
|
||||
platform that doesn't support automatic server function \
|
||||
registration and you need to call \
|
||||
ServerFn::register_explicit() on the server function \
|
||||
type, somewhere in your `main` function.",
|
||||
req.path()
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
|
@ -1229,6 +1228,14 @@ where
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// register server functions
|
||||
for (path, _) in server_fn::actix::server_fn_paths() {
|
||||
let additional_context = additional_context.clone();
|
||||
let handler = handle_server_fns_with_context(additional_context);
|
||||
router = router.route(path, handler);
|
||||
}
|
||||
|
||||
router
|
||||
}
|
||||
}
|
||||
|
@ -1322,82 +1329,22 @@ impl LeptosRoutes for &mut ServiceConfig {
|
|||
/// pub async fn query_extract() -> Result<MyQuery, ServerFnError> {
|
||||
/// use actix_web::web::Query;
|
||||
/// use leptos_actix::*;
|
||||
/// let Query(data) = extractor().await?;
|
||||
/// let Query(data) = extract().await?;
|
||||
/// Ok(data)
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extractor<T>() -> Result<T, ServerFnError>
|
||||
pub async fn extract<T, CustErr>() -> Result<T, ServerFnError<CustErr>>
|
||||
where
|
||||
T: actix_web::FromRequest,
|
||||
<T as FromRequest>::Error: Debug,
|
||||
<T as FromRequest>::Error: Display,
|
||||
{
|
||||
let req = use_context::<actix_web::HttpRequest>()
|
||||
.expect("HttpRequest should have been provided via context");
|
||||
let req = use_context::<HttpRequest>().ok_or_else(|| {
|
||||
ServerFnError::ServerError(
|
||||
"HttpRequest should have been provided via context".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Some(body) = use_context::<Bytes>() {
|
||||
let (_, mut payload) = actix_http::h1::Payload::create(false);
|
||||
payload.unread_data(body);
|
||||
T::from_request(&req, &mut dev::Payload::from(payload))
|
||||
} else {
|
||||
T::extract(&req)
|
||||
}
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(format!("{e:?}")))
|
||||
T::extract(&req)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(e.to_string()))
|
||||
}
|
||||
|
||||
/// A macro that makes it easier to use extractors in server functions. The macro
|
||||
/// takes a type or types, and extracts them from the request, returning from the
|
||||
/// server function with an `Err(_)` if there is an error during extraction.
|
||||
/// ```rust,ignore
|
||||
/// let info = extract!(ConnectionInfo);
|
||||
/// let Query(data) = extract!(Query<Search>);
|
||||
/// let (info, Query(data)) = extract!(ConnectionInfo, Query<Search>);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! extract {
|
||||
($($x:ty),+) => {
|
||||
$crate::extract(|fields: ($($x),+)| async move { fields }).await?
|
||||
};
|
||||
}
|
||||
|
||||
// Drawn from the Actix Handler implementation
|
||||
// https://github.com/actix/actix-web/blob/19c9d858f25e8262e14546f430d713addb397e96/actix-web/src/handler.rs#L124
|
||||
pub trait Extractor<T> {
|
||||
type Future;
|
||||
|
||||
fn call(self, args: T) -> Self::Future;
|
||||
}
|
||||
|
||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||
impl<Func, Fut, $($param,)*> Extractor<($($param,)*)> for Func
|
||||
where
|
||||
Func: FnOnce($($param),*) -> Fut + Clone + 'static,
|
||||
Fut: Future,
|
||||
{
|
||||
type Future = Fut;
|
||||
|
||||
#[inline]
|
||||
#[allow(non_snake_case)]
|
||||
fn call(self, ($($param,)*): ($($param,)*)) -> Self::Future {
|
||||
(self)($($param,)*)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
factory_tuple! {}
|
||||
factory_tuple! { A }
|
||||
factory_tuple! { A B }
|
||||
factory_tuple! { A B C }
|
||||
factory_tuple! { A B C D }
|
||||
factory_tuple! { A B C D E }
|
||||
factory_tuple! { A B C D E F }
|
||||
factory_tuple! { A B C D E F G }
|
||||
factory_tuple! { A B C D E F G H }
|
||||
factory_tuple! { A B C D E F G H I }
|
||||
factory_tuple! { A B C D E F G H I J }
|
||||
factory_tuple! { A B C D E F G H I J K }
|
||||
factory_tuple! { A B C D E F G H I J K L }
|
||||
factory_tuple! { A B C D E F G H I J K L M }
|
||||
factory_tuple! { A B C D E F G H I J K L M N }
|
||||
factory_tuple! { A B C D E F G H I J K L M N O }
|
||||
factory_tuple! { A B C D E F G H I J K L M N O P }
|
||||
|
|
|
@ -288,11 +288,11 @@ pub mod axum {
|
|||
path: &str,
|
||||
) -> Option<BoxedService<Request<Body>, Response<Body>>> {
|
||||
REGISTERED_SERVER_FUNCTIONS.get(path).map(|server_fn| {
|
||||
let middleware = (server_fn.middleware)();
|
||||
//let middleware = (server_fn.middleware)();
|
||||
let mut service = BoxedService::new(server_fn.clone());
|
||||
for middleware in middleware {
|
||||
service = middleware.layer(service);
|
||||
}
|
||||
//for middleware in middleware {
|
||||
//service = middleware.layer(service);
|
||||
//}
|
||||
service
|
||||
})
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ pub mod actix {
|
|||
response::actix::ActixResponse, Encoding, LazyServerFnMap, ServerFn,
|
||||
ServerFnTraitObj,
|
||||
};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use actix_web::{web::Payload, HttpRequest, HttpResponse};
|
||||
use http::Method;
|
||||
#[doc(hidden)]
|
||||
pub use send_wrapper::SendWrapper;
|
||||
|
@ -342,7 +342,10 @@ pub mod actix {
|
|||
.map(|item| (item.path(), item.method()))
|
||||
}
|
||||
|
||||
pub async fn handle_server_fn(req: HttpRequest) -> HttpResponse {
|
||||
pub async fn handle_server_fn(
|
||||
req: HttpRequest,
|
||||
payload: Payload,
|
||||
) -> HttpResponse {
|
||||
let path = req.uri().path();
|
||||
if let Some(server_fn) = REGISTERED_SERVER_FUNCTIONS.get(path) {
|
||||
let middleware = (server_fn.middleware)();
|
||||
|
@ -351,7 +354,12 @@ pub mod actix {
|
|||
for middleware in middleware {
|
||||
service = middleware.layer(service);
|
||||
}
|
||||
service.0.run(ActixRequest::from(req)).await.0.take()
|
||||
service
|
||||
.0
|
||||
.run(ActixRequest::from((req, payload)))
|
||||
.await
|
||||
.0
|
||||
.take()
|
||||
} else {
|
||||
HttpResponse::BadRequest().body(format!(
|
||||
"Could not find a server function at the route {path}. \
|
||||
|
|
|
@ -138,7 +138,7 @@ mod actix {
|
|||
&mut self,
|
||||
req: ActixRequest,
|
||||
) -> Pin<Box<dyn Future<Output = ActixResponse> + Send>> {
|
||||
let inner = self.call(req.0.take());
|
||||
let inner = self.call(req.0.take().0);
|
||||
Box::pin(async move {
|
||||
ActixResponse::from(inner.await.unwrap_or_else(|e| {
|
||||
let err = ServerFnError::from(e);
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
use crate::{error::ServerFnError, request::Req};
|
||||
use actix_web::{FromRequest, HttpRequest};
|
||||
use actix_web::{web::Payload, HttpRequest};
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::future::Future;
|
||||
|
||||
pub struct ActixRequest(pub(crate) SendWrapper<HttpRequest>);
|
||||
pub struct ActixRequest(pub(crate) SendWrapper<(HttpRequest, Payload)>);
|
||||
|
||||
impl ActixRequest {
|
||||
pub fn take(self) -> HttpRequest {
|
||||
pub fn take(self) -> (HttpRequest, Payload) {
|
||||
self.0.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpRequest> for ActixRequest {
|
||||
fn from(value: HttpRequest) -> Self {
|
||||
impl From<(HttpRequest, Payload)> for ActixRequest {
|
||||
fn from(value: (HttpRequest, Payload)) -> Self {
|
||||
ActixRequest(SendWrapper::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> Req<CustErr> for ActixRequest {
|
||||
fn as_query(&self) -> Option<&str> {
|
||||
self.0.uri().query()
|
||||
self.0 .0.uri().query()
|
||||
}
|
||||
|
||||
fn to_content_type(&self) -> Option<String> {
|
||||
self.0
|
||||
.0
|
||||
.headers()
|
||||
.get("Content-Type")
|
||||
.map(|h| String::from_utf8_lossy(h.as_bytes()).to_string())
|
||||
|
@ -38,7 +39,9 @@ impl<CustErr> Req<CustErr> for ActixRequest {
|
|||
// Actix is going to keep this on a single thread anyway so it's fine to wrap it
|
||||
// with SendWrapper, which makes it `Send` but will panic if it moves to another thread
|
||||
SendWrapper::new(async move {
|
||||
Bytes::extract(&self.0)
|
||||
let payload = self.0.take().1;
|
||||
payload
|
||||
.to_bytes()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
})
|
||||
|
@ -51,8 +54,12 @@ impl<CustErr> Req<CustErr> for ActixRequest {
|
|||
// Actix is going to keep this on a single thread anyway so it's fine to wrap it
|
||||
// with SendWrapper, which makes it `Send` but will panic if it moves to another thread
|
||||
SendWrapper::new(async move {
|
||||
String::extract(&self.0)
|
||||
let payload = self.0.take().1;
|
||||
let bytes = payload
|
||||
.to_bytes()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
|
||||
String::from_utf8(bytes.into())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -238,6 +238,7 @@ pub fn server_macro_impl(
|
|||
#struct_name::PATH,
|
||||
<#struct_name as ServerFn>::InputEncoding::METHOD,
|
||||
|req| {
|
||||
println!("running {:?}", stringify!(#struct_name));
|
||||
Box::pin(#struct_name::run_on_server(req))
|
||||
},
|
||||
#struct_name::middlewares
|
||||
|
@ -250,10 +251,30 @@ pub fn server_macro_impl(
|
|||
|
||||
// run_body in the trait implementation
|
||||
let run_body = if cfg!(feature = "ssr") {
|
||||
// using the impl Future syntax here is thanks to Actix
|
||||
//
|
||||
// if we use Actix types inside the function, here, it becomes !Send
|
||||
// so we need to add SendWrapper, because Actix won't actually send it anywhere
|
||||
// but if we used SendWrapper in an async fn, the types don't work out because it
|
||||
// becomes impl Future<Output = SendWrapper<_>>
|
||||
//
|
||||
// however, SendWrapper<Future<Output = T>> impls Future<Output = T>
|
||||
let body = quote! {
|
||||
let #struct_name { #(#field_names),* } = self;
|
||||
#dummy_name(#(#field_names),*).await
|
||||
};
|
||||
let body = if cfg!(feature = "actix") {
|
||||
quote! {
|
||||
#server_fn_path::actix::SendWrapper::new(async move {
|
||||
#body
|
||||
})
|
||||
}
|
||||
} else {
|
||||
body
|
||||
};
|
||||
quote! {
|
||||
async fn run_body(self) -> #return_ty {
|
||||
let #struct_name { #(#field_names),* } = self;
|
||||
#dummy_name(#(#field_names),*).await
|
||||
fn run_body(self) -> impl std::future::Future<Output = #return_ty> + Send {
|
||||
#body
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -271,7 +292,7 @@ pub fn server_macro_impl(
|
|||
#docs
|
||||
#(#attrs)*
|
||||
#vis async fn #fn_name(#(#fn_args),*) #output_arrow #return_ty {
|
||||
#block
|
||||
#dummy_name(#(#field_names),*).await
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -318,7 +339,7 @@ pub fn server_macro_impl(
|
|||
}
|
||||
} else if cfg!(feature = "actix") {
|
||||
quote! {
|
||||
#server_fn_path::actix_export::HttpRequest
|
||||
#server_fn_path::request::actix::ActixRequest
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
|
@ -337,7 +358,7 @@ pub fn server_macro_impl(
|
|||
}
|
||||
} else if cfg!(feature = "actix") {
|
||||
quote! {
|
||||
#server_fn_path::actix_export::HttpResponse
|
||||
#server_fn_path::response::actix::ActixResponse
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
|
|
Loading…
Reference in a new issue