more Actix work
This commit is contained in:
parent
60efaefff4
commit
e1a9856ca9
|
@ -17,8 +17,6 @@ use actix_web::{
|
|||
use futures::{Stream, StreamExt};
|
||||
use http::StatusCode;
|
||||
use leptos::{
|
||||
leptos_server::{server_fn_by_path, Payload},
|
||||
server_fn::Encoding,
|
||||
ssr::render_to_stream_with_prefix_undisposed_with_context_and_block_replacement,
|
||||
*,
|
||||
};
|
||||
|
@ -27,6 +25,7 @@ use leptos_meta::*;
|
|||
use leptos_router::*;
|
||||
use parking_lot::RwLock;
|
||||
use regex::Regex;
|
||||
use server_fn::request::actix::ActixRequest;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
|
@ -185,113 +184,34 @@ pub fn handle_server_fns_with_context(
|
|||
async move {
|
||||
let additional_context = additional_context.clone();
|
||||
|
||||
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_ref: &[u8] = &body;
|
||||
|
||||
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();
|
||||
let res_options = ResponseOptions::default();
|
||||
|
||||
// provide HttpRequest as context in server scope
|
||||
provide_context(req.clone());
|
||||
provide_context(res_options.clone());
|
||||
let res_parts = ResponseOptions::default();
|
||||
provide_context(res_parts.clone());
|
||||
|
||||
// we consume the body here (using the web::Bytes extractor), but it is required for things
|
||||
// like MultipartForm
|
||||
if req
|
||||
.headers()
|
||||
.get("Content-Type")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(|value| {
|
||||
value.starts_with("multipart/form-data; boundary=")
|
||||
})
|
||||
== Some(true)
|
||||
{
|
||||
provide_context(body.clone());
|
||||
let mut res =
|
||||
service.0.run(ActixRequest::from(req)).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;
|
||||
}
|
||||
|
||||
let query = req.query_string().as_bytes();
|
||||
// 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());
|
||||
}
|
||||
|
||||
let data = match &server_fn.encoding() {
|
||||
Encoding::Url | Encoding::Cbor => body_ref,
|
||||
Encoding::GetJSON | Encoding::GetCBOR => query,
|
||||
};
|
||||
|
||||
let res = match server_fn.call((), data).await {
|
||||
Ok(serialized) => {
|
||||
let res_options =
|
||||
use_context::<ResponseOptions>().unwrap();
|
||||
|
||||
let mut res: HttpResponseBuilder =
|
||||
HttpResponse::Ok();
|
||||
let res_parts = res_options.0.write();
|
||||
|
||||
// if accept_header isn't set to one of these, it's a form submit
|
||||
// redirect back to the referrer if not redirect has been set
|
||||
if accept_header != Some("application/json")
|
||||
&& accept_header
|
||||
!= Some("application/x-www-form-urlencoded")
|
||||
&& accept_header != Some("application/cbor")
|
||||
{
|
||||
// Location will already be set if redirect() has been used
|
||||
let has_location_set =
|
||||
res_parts.headers.get("Location").is_some();
|
||||
if !has_location_set {
|
||||
let referer = req
|
||||
.headers()
|
||||
.get("Referer")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.unwrap_or("/");
|
||||
res = HttpResponse::SeeOther();
|
||||
res.insert_header(("Location", referer))
|
||||
.content_type("application/json");
|
||||
}
|
||||
};
|
||||
// Override StatusCode if it was set in a Resource or Element
|
||||
if let Some(status) = res_parts.status {
|
||||
res.status(status);
|
||||
}
|
||||
|
||||
// Use provided ResponseParts headers if they exist
|
||||
let _count = res_parts
|
||||
.headers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
res.append_header((k, v));
|
||||
})
|
||||
.count();
|
||||
|
||||
match serialized {
|
||||
Payload::Binary(data) => {
|
||||
res.content_type("application/cbor");
|
||||
res.body(Bytes::from(data))
|
||||
}
|
||||
Payload::Url(data) => {
|
||||
res.content_type(
|
||||
"application/x-www-form-urlencoded",
|
||||
);
|
||||
res.body(data)
|
||||
}
|
||||
Payload::Json(data) => {
|
||||
res.content_type("application/json");
|
||||
res.body(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => HttpResponse::InternalServerError().body(
|
||||
serde_json::to_string(&e)
|
||||
.unwrap_or_else(|_| e.to_string()),
|
||||
),
|
||||
};
|
||||
// clean up the scope
|
||||
runtime.dispose();
|
||||
res
|
||||
|
@ -1388,62 +1308,6 @@ impl LeptosRoutes for &mut ServiceConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// A helper to make it easier to use Actix extractors in server functions. This takes
|
||||
/// a handler function as its argument. The handler follows similar rules to an Actix
|
||||
/// [Handler]: it is an async function that receives arguments that
|
||||
/// will be extracted from the request and returns some value.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use leptos::*;
|
||||
/// use serde::Deserialize;
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Search {
|
||||
/// q: String,
|
||||
/// }
|
||||
///
|
||||
/// #[server(ExtractoServerFn, "/api")]
|
||||
/// pub async fn extractor_server_fn() -> Result<String, ServerFnError> {
|
||||
/// use actix_web::dev::ConnectionInfo;
|
||||
/// use actix_web::web::{Data, Query};
|
||||
///
|
||||
/// extract(
|
||||
/// |data: Data<String>, search: Query<Search>, connection: ConnectionInfo| async move {
|
||||
/// format!(
|
||||
/// "data = {}\nsearch = {}\nconnection = {:?}",
|
||||
/// data.into_inner(),
|
||||
/// search.q,
|
||||
/// connection
|
||||
/// )
|
||||
/// },
|
||||
/// )
|
||||
/// .await
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract<F, E>(
|
||||
f: F,
|
||||
) -> Result<<<F as Extractor<E>>::Future as Future>::Output, ServerFnError>
|
||||
where
|
||||
F: Extractor<E>,
|
||||
E: actix_web::FromRequest,
|
||||
<E as actix_web::FromRequest>::Error: Display,
|
||||
<F as Extractor<E>>::Future: Future,
|
||||
{
|
||||
let req = use_context::<actix_web::HttpRequest>()
|
||||
.expect("HttpRequest should have been provided via context");
|
||||
|
||||
let input = if let Some(body) = use_context::<Bytes>() {
|
||||
let (_, mut payload) = actix_http::h1::Payload::create(false);
|
||||
payload.unread_data(body);
|
||||
E::from_request(&req, &mut dev::Payload::from(payload))
|
||||
} else {
|
||||
E::extract(&req)
|
||||
}
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
|
||||
|
||||
Ok(f.call(input).await)
|
||||
}
|
||||
|
||||
/// A helper to make it easier to use Axum extractors in server functions, with a
|
||||
/// simpler API than [`extract()`].
|
||||
///
|
||||
|
|
|
@ -242,7 +242,6 @@ async fn handle_server_fns_inner(
|
|||
|
||||
additional_context();
|
||||
provide_context(parts);
|
||||
// Add this so that we can set headers and status of the response
|
||||
provide_context(ResponseOptions::default());
|
||||
|
||||
let mut res = service.run(req).await;
|
||||
|
|
|
@ -307,6 +307,9 @@ pub mod actix {
|
|||
ServerFnTraitObj,
|
||||
};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use http::Method;
|
||||
#[doc(hidden)]
|
||||
pub use send_wrapper::SendWrapper;
|
||||
|
||||
inventory::collect!(ServerFnTraitObj<ActixRequest, ActixResponse>);
|
||||
|
||||
|
@ -333,6 +336,12 @@ pub mod actix {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn server_fn_paths() -> impl Iterator<Item = (&'static str, Method)> {
|
||||
REGISTERED_SERVER_FUNCTIONS
|
||||
.iter()
|
||||
.map(|item| (item.path(), item.method()))
|
||||
}
|
||||
|
||||
pub async fn handle_server_fn(req: HttpRequest) -> HttpResponse {
|
||||
let path = req.uri().path();
|
||||
if let Some(server_fn) = REGISTERED_SERVER_FUNCTIONS.get(path) {
|
||||
|
@ -343,11 +352,6 @@ pub mod actix {
|
|||
service = middleware.layer(service);
|
||||
}
|
||||
service.0.run(ActixRequest::from(req)).await.0.take()
|
||||
/*server_fn
|
||||
.run(ActixRequest(SendWrapper::new(req)))
|
||||
.await
|
||||
.0
|
||||
.take()*/
|
||||
} else {
|
||||
HttpResponse::BadRequest().body(format!(
|
||||
"Could not find a server function at the route {path}. \
|
||||
|
@ -361,4 +365,17 @@ pub mod actix {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_server_fn_service(
|
||||
path: &str,
|
||||
) -> Option<BoxedService<ActixRequest, ActixResponse>> {
|
||||
REGISTERED_SERVER_FUNCTIONS.get(path).map(|server_fn| {
|
||||
let middleware = (server_fn.middleware)();
|
||||
let mut service = BoxedService::new(server_fn.clone());
|
||||
for middleware in middleware {
|
||||
service = middleware.layer(service);
|
||||
}
|
||||
service
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ mod axum {
|
|||
#[cfg(feature = "actix")]
|
||||
mod actix {
|
||||
use crate::{
|
||||
request::actix::ActixRequest,
|
||||
response::{actix::ActixResponse, Res},
|
||||
ServerFnError,
|
||||
};
|
||||
|
@ -121,9 +122,29 @@ mod actix {
|
|||
Box::pin(async move {
|
||||
inner.await.unwrap_or_else(|e| {
|
||||
let err = ServerFnError::from(e);
|
||||
ActixResponse::error_response(err).into_inner()
|
||||
ActixResponse::error_response(err).take()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> super::Service<ActixRequest, ActixResponse> for S
|
||||
where
|
||||
S: actix_web::dev::Service<HttpRequest, Response = HttpResponse>,
|
||||
S::Future: Send + 'static,
|
||||
S::Error: Into<ServerFnError> + Debug + Display + 'static,
|
||||
{
|
||||
fn run(
|
||||
&mut self,
|
||||
req: ActixRequest,
|
||||
) -> Pin<Box<dyn Future<Output = ActixResponse> + Send>> {
|
||||
let inner = self.call(req.0.take());
|
||||
Box::pin(async move {
|
||||
ActixResponse::from(inner.await.unwrap_or_else(|e| {
|
||||
let err = ServerFnError::from(e);
|
||||
ActixResponse::error_response(err).take()
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,12 @@ use std::future::Future;
|
|||
|
||||
pub struct ActixRequest(pub(crate) SendWrapper<HttpRequest>);
|
||||
|
||||
impl ActixRequest {
|
||||
pub fn take(self) -> HttpRequest {
|
||||
self.0.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpRequest> for ActixRequest {
|
||||
fn from(value: HttpRequest) -> Self {
|
||||
ActixRequest(SendWrapper::new(value))
|
||||
|
|
|
@ -12,11 +12,17 @@ use std::fmt::{Debug, Display};
|
|||
pub struct ActixResponse(pub(crate) SendWrapper<HttpResponse>);
|
||||
|
||||
impl ActixResponse {
|
||||
pub fn into_inner(self) -> HttpResponse {
|
||||
pub fn take(self) -> HttpResponse {
|
||||
self.0.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpResponse> for ActixResponse {
|
||||
fn from(value: HttpResponse) -> Self {
|
||||
Self(SendWrapper::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<CustErr> Res<CustErr> for ActixResponse
|
||||
where
|
||||
CustErr: Display + Debug + 'static,
|
||||
|
|
|
@ -308,7 +308,6 @@ pub fn server_macro_impl(
|
|||
#server_fn_path::client::browser::BrowserClient
|
||||
};
|
||||
|
||||
// TODO Actix etc
|
||||
let req = if !cfg!(feature = "ssr") {
|
||||
quote! {
|
||||
#server_fn_path::request::BrowserMockReq
|
||||
|
|
Loading…
Reference in New Issue