Skip to content

Commit 02ce425

Browse files
committed
feat: ✨ added access to request data in ssr
Haven't set up conversion for actix yet though.
1 parent 0e509a0 commit 02ce425

File tree

7 files changed

+49
-14
lines changed

7 files changed

+49
-14
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ futures = "0.3"
1919
console_error_panic_hook = "0.1.6"
2020
urlencoding = "2.1"
2121
chrono = "0.4"
22+
http = "0.2"
2223

2324
[workspace]
2425
members = [

examples/showcase/app/src/pages/ip.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// This page illustrates SSR
22

33
use perseus::errors::ErrorCause;
4-
use perseus::template::Template;
4+
use perseus::template::{Template};
5+
use perseus::Request;
56
use serde::{Deserialize, Serialize};
67
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};
78

@@ -27,10 +28,17 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
2728
.template(template_fn())
2829
}
2930

30-
pub async fn get_request_state(_path: String) -> Result<String, (String, ErrorCause)> {
31+
pub async fn get_request_state(_path: String, req: Request) -> Result<String, (String, ErrorCause)> {
3132
// Err(("this is a test error!".to_string(), ErrorCause::Client(None)))
3233
Ok(serde_json::to_string(&IpPageProps {
33-
ip: "x.x.x.x".to_string(),
34+
// Gets the client's IP address
35+
ip: format!(
36+
"{:?}",
37+
req
38+
.headers()
39+
.get("X-Forwarded-For")
40+
.unwrap_or(&perseus::http::HeaderValue::from_str("hidden from view!").unwrap())
41+
),
3442
})
3543
.unwrap())
3644
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use perseus::{HttpRequest, Request};
2+
3+
// TODO set up proper error handling in an integration crate
4+
/// Converts an Actix Web request into an `http::request`.
5+
pub fn convert_req(raw: &actix_web::HttpRequest) -> Result<Request, String> {
6+
let req = HttpRequest::builder()
7+
.body(())
8+
.map_err(|err| format!("converting actix web request to perseus-compliant request failed: '{}'", err))?;
9+
10+
Ok(req)
11+
}

examples/showcase/server/src/main.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ use sycamore::prelude::SsrNode;
66
use perseus::{
77
config_manager::FsConfigManager,
88
errors::err_to_status_code,
9-
errors::ErrorKind as PerseusErr,
109
serve::{get_page, get_render_cfg},
1110
template::TemplateMap,
1211
};
1312
use perseus_showcase_app::pages;
1413

14+
mod conv_req;
15+
use conv_req::convert_req;
16+
1517
#[actix_web::main]
1618
async fn main() -> std::io::Result<()> {
1719
HttpServer::new(|| {
@@ -51,12 +53,15 @@ async fn page_data(
5153
config_manager: web::Data<FsConfigManager>,
5254
) -> HttpResponse {
5355
let path = req.match_info().query("filename");
54-
let page_data = get_page(path, &render_cfg, &templates, config_manager.get_ref()).await;
55-
let http_res = match page_data {
56+
// We need to turn the Actix Web request into one acceptable for Perseus (uses `http` internally)
57+
// TODO proper error handling here
58+
let http_req = convert_req(&req).unwrap();
59+
let page_data = get_page(path, http_req, &render_cfg, &templates, config_manager.get_ref()).await;
60+
61+
match page_data {
5662
Ok(page_data) => HttpResponse::Ok().body(serde_json::to_string(&page_data).unwrap()),
63+
// We parse the error to return an appropriate status code
5764
Err(err) => HttpResponse::build(StatusCode::from_u16(err_to_status_code(&err)).unwrap())
5865
.body(err.to_string()),
59-
};
60-
61-
http_res
66+
}
6267
}

src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ pub mod errors;
55
pub mod serve;
66
pub mod shell;
77
pub mod template;
8+
9+
pub use http;
10+
pub use http::Request as HttpRequest;
11+
/// All HTTP requests use empty bodies for simplicity of passing them around. They'll never need payloads (value in path requested).
12+
pub type Request = HttpRequest<()>;

src/serve.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::config_manager::ConfigManager;
44
use crate::decode_time_str::decode_time_str;
55
use crate::errors::*;
66
use crate::template::{States, Template, TemplateMap};
7+
use crate::Request;
78
use chrono::{DateTime, Utc};
89
use serde::{Deserialize, Serialize};
910
use std::collections::HashMap;
@@ -47,9 +48,10 @@ fn render_build_state(
4748
async fn render_request_state(
4849
template: &Template<SsrNode>,
4950
path: &str,
51+
req: Request
5052
) -> Result<(String, Option<String>)> {
5153
// Generate the initial state (this may generate an error, but there's no file that can't exist)
52-
let state = Some(template.get_request_state(path.to_string()).await?);
54+
let state = Some(template.get_request_state(path.to_string(), req).await?);
5355
// Use that to render the static HTML
5456
let html = sycamore::render_to_string(|| template.render_for_template(state.clone()));
5557

@@ -137,6 +139,7 @@ async fn revalidate(
137139
// TODO possible further optimizations on this for futures?
138140
pub async fn get_page(
139141
path: &str,
142+
req: Request,
140143
render_cfg: &HashMap<String, String>,
141144
templates: &TemplateMap<SsrNode>,
142145
config_manager: &impl ConfigManager,
@@ -280,7 +283,7 @@ pub async fn get_page(
280283
}
281284
// Handle request state
282285
if template.uses_request_state() {
283-
let (html_val, state) = render_request_state(template, path).await?;
286+
let (html_val, state) = render_request_state(template, path, req).await?;
284287
// Request-time HTML always overrides anything generated at build-time or incrementally (this has more information)
285288
html = html_val;
286289
states.request_state = state;

src/template.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This file contains logic to define how templates are rendered
22

33
use crate::errors::*;
4+
use crate::Request;
45
use futures::Future;
56
use std::collections::HashMap;
67
use std::pin::Pin;
@@ -90,7 +91,8 @@ make_async_trait!(GetBuildStateFnType, StringResult<String>, path: String);
9091
make_async_trait!(
9192
GetRequestStateFnType,
9293
StringResultWithCause<String>,
93-
path: String
94+
path: String,
95+
req: Request
9496
);
9597
make_async_trait!(ShouldRevalidateFnType, StringResultWithCause<bool>);
9698

@@ -207,9 +209,9 @@ impl<G: GenericNode> Template<G> {
207209
/// Gets the request-time state for a template. This is equivalent to SSR, and will not be performed at build-time. Unlike
208210
/// `.get_build_paths()` though, this will be passed information about the request that triggered the render. Errors here can be caused
209211
/// by either the server or the client, so the user must specify an [`ErrorCause`].
210-
pub async fn get_request_state(&self, path: String) -> Result<String> {
212+
pub async fn get_request_state(&self, path: String, req: Request) -> Result<String> {
211213
if let Some(get_request_state) = &self.get_request_state {
212-
let res = get_request_state.call(path).await;
214+
let res = get_request_state.call(path, req).await;
213215
match res {
214216
Ok(res) => Ok(res),
215217
Err((err, cause)) => bail!(ErrorKind::RenderFnFailed(

0 commit comments

Comments
 (0)