This version of the documentation is for a version that has not yet been released, and features documented here may not be present in the latest release. You can see the latest stable version of the docs here.
Request-time state
Request-time state can be thought of as the next level up from build-time state, because it provides access to everything build state does, plus the actual HTTP request of the user who made the query. This allows you to generate user-specific state: for instance a custom dashboard could be prerendered for them based on their settings.
Here's an example of request state being used to show a user their own IP address (as their browser reports it, which can be spoofed):
use perseus::prelude::*;
use serde::{Deserialize, Serialize};
use sycamore::prelude::*;
#[derive(Serialize, Deserialize, Clone, ReactiveState)]
#[rx(alias = "PageStateRx")]
struct PageState {
ip: String,
}
fn request_state_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View<G> {
view! { cx,
p {
(
format!("Your IP address is {}.", state.ip.get())
)
}
}
}
pub fn get_template<G: Html>() -> Template<G> {
Template::build("request_state")
.request_state_fn(get_request_state)
.view_with_state(request_state_page)
.build()
}
// This returns a `Result<T, BlamedError<E>>` (or just `T`) because, obviously,
// it will be run at request-time: any errors could be a mising file (our
// fault), or a malformed cookie (the client's fault), etc., so we have to note
// the blame to get an accurate HTTP status code. This example is really
// infallible, but we've spelled it all out rather than using `T` so you can see
// how it works.
#[engine_only_fn]
async fn get_request_state(
// We get all the same info as build state in here
_info: StateGeneratorInfo<()>,
// Unlike in build state, in request state we *also* get access to the information that the
// user sent with their HTTP request IN this example, we extract the browser's reporting of
// their IP address and display it to them
req: Request,
) -> Result<PageState, BlamedError<std::convert::Infallible>> {
Ok(PageState {
ip: format!(
"{:?}",
req.headers()
// NOTE: This header can be trivially spoofed, and may well not be the user's actual
// IP address
.get("X-Forwarded-For")
.unwrap_or(&perseus::http::HeaderValue::from_str("hidden from view!").unwrap())
),
})
}
Just as with a get_build_state
function, this get_request_state
function is async
and returns a BlamedError<E>
, where E
is any compliant error type, but it could return the PageState
directly if it wanted to. If you need a refresher on these properties, especially on error handling in these kinds of functions, see this page.
Importantly, request-time state gives us access to the usual StateGeneratorInfo
, which contains the path, locale, and helper state, plus the Request
, which is an HTTP request with the body stripped out. This is done because the body is meaningless in Perseus requests, and anything that needs it should use custom API endpoints.
In this example, we're using req
to access the headers of the request, which are the main things you'll use (since these contain cookies, etc.). Here, we're just accessing the non-standard X-Forwarded-For
header, which represents the IP address of the client in many cases.
Critically, the request provided to this function is not the 'real' request, meaning altering parts of it will have absolutely no effect whatsoever --- it's just a representation of it provided to your functions so they can access user details. For example, if you wanted to set headers, you should not add them here, but do this instead.
A request-time state generating function can be specified using .request_state_fn()
on Template
.