From 02ce42573ff5cf6f279c3932b68901bfd48922dc Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Thu, 19 Aug 2021 07:31:48 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20added=20access=20to=20reque?= =?UTF-8?q?st=20data=20in=20ssr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Haven't set up conversion for actix yet though. --- Cargo.toml | 1 + examples/showcase/app/src/pages/ip.rs | 14 +++++++++++--- examples/showcase/server/src/conv_req.rs | 11 +++++++++++ examples/showcase/server/src/main.rs | 17 +++++++++++------ src/lib.rs | 5 +++++ src/serve.rs | 7 +++++-- src/template.rs | 8 +++++--- 7 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 examples/showcase/server/src/conv_req.rs diff --git a/Cargo.toml b/Cargo.toml index b24b7b0a74..cdc59b5e44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = [ diff --git a/examples/showcase/app/src/pages/ip.rs b/examples/showcase/app/src/pages/ip.rs index b366aaa235..c6fe95c1c0 100644 --- a/examples/showcase/app/src/pages/ip.rs +++ b/examples/showcase/app/src/pages/ip.rs @@ -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}; @@ -27,10 +28,17 @@ pub fn get_page() -> Template { .template(template_fn()) } -pub async fn get_request_state(_path: String) -> Result { +pub async fn get_request_state(_path: String, req: Request) -> Result { // 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()) } diff --git a/examples/showcase/server/src/conv_req.rs b/examples/showcase/server/src/conv_req.rs new file mode 100644 index 0000000000..022fa1c5ee --- /dev/null +++ b/examples/showcase/server/src/conv_req.rs @@ -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 { + let req = HttpRequest::builder() + .body(()) + .map_err(|err| format!("converting actix web request to perseus-compliant request failed: '{}'", err))?; + + Ok(req) +} \ No newline at end of file diff --git a/examples/showcase/server/src/main.rs b/examples/showcase/server/src/main.rs index d0f63e01cd..251b128176 100644 --- a/examples/showcase/server/src/main.rs +++ b/examples/showcase/server/src/main.rs @@ -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(|| { @@ -51,12 +53,15 @@ async fn page_data( config_manager: web::Data, ) -> 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 + } } diff --git a/src/lib.rs b/src/lib.rs index 13b593bb7b..17d7f96978 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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<()>; \ No newline at end of file diff --git a/src/serve.rs b/src/serve.rs index 5eab1cfce8..1d6a8e7d63 100644 --- a/src/serve.rs +++ b/src/serve.rs @@ -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; @@ -47,9 +48,10 @@ fn render_build_state( async fn render_request_state( template: &Template, path: &str, + req: Request ) -> Result<(String, Option)> { // 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())); @@ -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, templates: &TemplateMap, config_manager: &impl ConfigManager, @@ -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; diff --git a/src/template.rs b/src/template.rs index e181a8900f..fb799572f5 100644 --- a/src/template.rs +++ b/src/template.rs @@ -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; @@ -90,7 +91,8 @@ make_async_trait!(GetBuildStateFnType, StringResult, path: String); make_async_trait!( GetRequestStateFnType, StringResultWithCause, - path: String + path: String, + req: Request ); make_async_trait!(ShouldRevalidateFnType, StringResultWithCause); @@ -207,9 +209,9 @@ impl Template { /// 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 { + pub async fn get_request_state(&self, path: String, req: Request) -> Result { 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(