Skip to content

Commit

Permalink
feat: ✨ added access to request data in ssr
Browse files Browse the repository at this point in the history
Haven't set up conversion for actix yet though.
  • Loading branch information
arctic-hen7 committed Aug 18, 2021
1 parent 0e509a0 commit 02ce425
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ futures = "0.3"
console_error_panic_hook = "0.1.6"
urlencoding = "2.1"
chrono = "0.4"
http = "0.2"

[workspace]
members = [
Expand Down
14 changes: 11 additions & 3 deletions examples/showcase/app/src/pages/ip.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// This page illustrates SSR

use perseus::errors::ErrorCause;
use perseus::template::Template;
use perseus::template::{Template};
use perseus::Request;
use serde::{Deserialize, Serialize};
use sycamore::prelude::{component, template, GenericNode, Template as SycamoreTemplate};

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

pub async fn get_request_state(_path: String) -> Result<String, (String, ErrorCause)> {
pub async fn get_request_state(_path: String, req: Request) -> Result<String, (String, ErrorCause)> {
// Err(("this is a test error!".to_string(), ErrorCause::Client(None)))
Ok(serde_json::to_string(&IpPageProps {
ip: "x.x.x.x".to_string(),
// Gets the client's IP address
ip: format!(
"{:?}",
req
.headers()
.get("X-Forwarded-For")
.unwrap_or(&perseus::http::HeaderValue::from_str("hidden from view!").unwrap())
),
})
.unwrap())
}
Expand Down
11 changes: 11 additions & 0 deletions examples/showcase/server/src/conv_req.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use perseus::{HttpRequest, Request};

// TODO set up proper error handling in an integration crate
/// Converts an Actix Web request into an `http::request`.
pub fn convert_req(raw: &actix_web::HttpRequest) -> Result<Request, String> {
let req = HttpRequest::builder()
.body(())
.map_err(|err| format!("converting actix web request to perseus-compliant request failed: '{}'", err))?;

Ok(req)
}
17 changes: 11 additions & 6 deletions examples/showcase/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ use sycamore::prelude::SsrNode;
use perseus::{
config_manager::FsConfigManager,
errors::err_to_status_code,
errors::ErrorKind as PerseusErr,
serve::{get_page, get_render_cfg},
template::TemplateMap,
};
use perseus_showcase_app::pages;

mod conv_req;
use conv_req::convert_req;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
Expand Down Expand Up @@ -51,12 +53,15 @@ async fn page_data(
config_manager: web::Data<FsConfigManager>,
) -> HttpResponse {
let path = req.match_info().query("filename");
let page_data = get_page(path, &render_cfg, &templates, config_manager.get_ref()).await;
let http_res = match page_data {
// We need to turn the Actix Web request into one acceptable for Perseus (uses `http` internally)
// TODO proper error handling here
let http_req = convert_req(&req).unwrap();
let page_data = get_page(path, http_req, &render_cfg, &templates, config_manager.get_ref()).await;

match page_data {
Ok(page_data) => HttpResponse::Ok().body(serde_json::to_string(&page_data).unwrap()),
// We parse the error to return an appropriate status code
Err(err) => HttpResponse::build(StatusCode::from_u16(err_to_status_code(&err)).unwrap())
.body(err.to_string()),
};

http_res
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ pub mod errors;
pub mod serve;
pub mod shell;
pub mod template;

pub use http;
pub use http::Request as HttpRequest;
/// All HTTP requests use empty bodies for simplicity of passing them around. They'll never need payloads (value in path requested).
pub type Request = HttpRequest<()>;
7 changes: 5 additions & 2 deletions src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::config_manager::ConfigManager;
use crate::decode_time_str::decode_time_str;
use crate::errors::*;
use crate::template::{States, Template, TemplateMap};
use crate::Request;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -47,9 +48,10 @@ fn render_build_state(
async fn render_request_state(
template: &Template<SsrNode>,
path: &str,
req: Request
) -> Result<(String, Option<String>)> {
// Generate the initial state (this may generate an error, but there's no file that can't exist)
let state = Some(template.get_request_state(path.to_string()).await?);
let state = Some(template.get_request_state(path.to_string(), req).await?);
// Use that to render the static HTML
let html = sycamore::render_to_string(|| template.render_for_template(state.clone()));

Expand Down Expand Up @@ -137,6 +139,7 @@ async fn revalidate(
// TODO possible further optimizations on this for futures?
pub async fn get_page(
path: &str,
req: Request,
render_cfg: &HashMap<String, String>,
templates: &TemplateMap<SsrNode>,
config_manager: &impl ConfigManager,
Expand Down Expand Up @@ -280,7 +283,7 @@ pub async fn get_page(
}
// Handle request state
if template.uses_request_state() {
let (html_val, state) = render_request_state(template, path).await?;
let (html_val, state) = render_request_state(template, path, req).await?;
// Request-time HTML always overrides anything generated at build-time or incrementally (this has more information)
html = html_val;
states.request_state = state;
Expand Down
8 changes: 5 additions & 3 deletions src/template.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// This file contains logic to define how templates are rendered

use crate::errors::*;
use crate::Request;
use futures::Future;
use std::collections::HashMap;
use std::pin::Pin;
Expand Down Expand Up @@ -90,7 +91,8 @@ make_async_trait!(GetBuildStateFnType, StringResult<String>, path: String);
make_async_trait!(
GetRequestStateFnType,
StringResultWithCause<String>,
path: String
path: String,
req: Request
);
make_async_trait!(ShouldRevalidateFnType, StringResultWithCause<bool>);

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

0 comments on commit 02ce425

Please sign in to comment.