set up redirects in Actix

This commit is contained in:
Greg Johnston 2024-01-06 18:11:11 -05:00
parent 4e3f1c834c
commit 5e6f4403ca
1 changed files with 51 additions and 5 deletions

View File

@ -6,7 +6,7 @@
//! [`examples`](https://github.com/leptos-rs/leptos/tree/main/examples)
//! directory in the Leptos repository.
use actix_http::header::{HeaderName, HeaderValue};
use actix_http::header::{HeaderName, HeaderValue, ACCEPT};
use actix_web::{
body::BoxBody,
dev::{ServiceFactory, ServiceRequest},
@ -25,7 +25,7 @@ use leptos_meta::*;
use leptos_router::*;
use parking_lot::RwLock;
use regex::Regex;
use server_fn::request::actix::ActixRequest;
use server_fn::{redirect::REDIRECT_HEADER, request::actix::ActixRequest};
use std::{
fmt::{Debug, Display},
future::Future,
@ -99,11 +99,57 @@ impl ResponseOptions {
}
}
/// Provides an easy way to redirect the user from within a server function. Mimicking the Remix `redirect()`,
/// it sets a [StatusCode] of 302 and a [LOCATION](header::LOCATION) header with the provided value.
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead.
/// Provides an easy way to redirect the user from within a server function.
///
/// This sets the `Location` header to the URL given.
///
/// If the route or server function in which this is called is being accessed
/// by an ordinary `GET` request or an HTML `<form>` without any enhancement, it also sets a
/// status code of `302` for a temporary redirect. (This is determined by whether the `Accept`
/// header contains `text/html` as it does for an ordinary navigation.)
///
/// Otherwise, it sets a custom header that indicates to the client that it should redirect,
/// without actually setting the status code. This means that the client will not follow the
/// redirect, and can therefore return the value of the server function and then handle
/// the redirect with client-side routing.
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn redirect(path: &str) {
if let (Some(req), Some(res)) = (
use_context::<HttpRequest>(),
use_context::<ResponseOptions>(),
) {
// insert the Location header in any case
res.insert_header(
header::LOCATION,
header::HeaderValue::from_str(path)
.expect("Failed to create HeaderValue"),
);
let accepts_html = req
.headers()
.get(ACCEPT)
.and_then(|v| v.to_str().ok())
.map(|v| v.contains("text/html"))
.unwrap_or(false);
if accepts_html {
// if the request accepts text/html, it's a plain form request and needs
// to have the 302 code set
res.set_status(StatusCode::FOUND);
} else {
// otherwise, we sent it from the server fn client and actually don't want
// to set a real redirect, as this will break the ability to return data
// instead, set the REDIRECT_HEADER to indicate that the client should redirect
res.insert_header(
HeaderName::from_static(REDIRECT_HEADER),
HeaderValue::from_str("").unwrap(),
);
}
} else {
tracing::warn!(
"Couldn't retrieve either Parts or ResponseOptions while trying \
to redirect()."
);
}
if let Some(response_options) = use_context::<ResponseOptions>() {
response_options.set_status(StatusCode::FOUND);
response_options.insert_header(