finished Actix support?

This commit is contained in:
Greg Johnston 2024-01-05 15:34:39 -05:00
parent e1a9856ca9
commit c8fbee18c8
7 changed files with 125 additions and 142 deletions

Binary file not shown.

View file

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

View file

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

View file

@ -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}. \

View file

@ -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);

View file

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

View file

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