From d802b750845d3f48c49ca436d603edd6094855e3 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 16 Sep 2025 09:35:53 +0200 Subject: [PATCH] WIP: access Arc in render context - Pass `RequestInfo` as an `Arc` to avoid cloning the struct in many places. - The `source_path` in `HeaderContext` and `HtmlRenderContext` are now available through `RequestInfo`. - The logging component now receives the `RequestInfo` object. - Added `is_embedded()` method to `RequestInfo`. --- src/render.rs | 26 ++++++++++++-------------- src/webserver/http.rs | 18 +++++++----------- src/webserver/http_request_info.rs | 8 ++++++++ 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/render.rs b/src/render.rs index 09e9f051..f0fca7fd 100644 --- a/src/render.rs +++ b/src/render.rs @@ -43,6 +43,7 @@ use crate::templates::SplitTemplate; use crate::webserver::http::RequestContext; +use crate::webserver::http_request_info::RequestInfo; use crate::webserver::response_writer::{AsyncResponseWriter, ResponseWriter}; use crate::webserver::ErrorWithStatus; use crate::AppState; @@ -80,7 +81,7 @@ pub enum PageContext { /// Handles the first SQL statements, before the headers have been sent to pub struct HeaderContext { app_state: Arc, - request_context: RequestContext, + request_context: Arc, pub writer: ResponseWriter, response: HttpResponseBuilder, has_status: bool, @@ -90,7 +91,7 @@ impl HeaderContext { #[must_use] pub fn new( app_state: Arc, - request_context: RequestContext, + request_context: Arc, writer: ResponseWriter, ) -> Self { let mut response = HttpResponseBuilder::new(StatusCode::OK); @@ -364,7 +365,7 @@ impl HeaderContext { } fn log(self, data: &JsonValue) -> anyhow::Result { - handle_log_component(&self.request_context.source_path, Option::None, data)?; + handle_log_component(&self.request_context, Option::None, data)?; Ok(PageContext::Header(self)) } @@ -651,7 +652,7 @@ pub struct HtmlRenderContext { current_component: Option, shell_renderer: SplitTemplateRenderer, current_statement: usize, - request_context: RequestContext, + request_context: Arc, } const DEFAULT_COMPONENT: &str = "table"; @@ -661,7 +662,7 @@ const FRAGMENT_SHELL_COMPONENT: &str = "shell-empty"; impl HtmlRenderContext { pub async fn new( app_state: Arc, - request_context: RequestContext, + request_context: Arc, mut writer: W, initial_row: JsonValue, ) -> anyhow::Result> { @@ -669,12 +670,13 @@ impl HtmlRenderContext { let mut initial_rows = vec![Cow::Borrowed(&initial_row)]; + let is_embedded = request_context.is_embedded(); if !initial_rows .first() .and_then(|c| get_object_str(c, "component")) .is_some_and(Self::is_shell_component) { - let default_shell = if request_context.is_embedded { + let default_shell = if is_embedded { FRAGMENT_SHELL_COMPONENT } else { PAGE_SHELL_COMPONENT @@ -692,7 +694,7 @@ impl HtmlRenderContext { .expect("shell row should exist at this point"); let mut shell_component = get_object_str(&shell_row, "component").expect("shell should exist"); - if request_context.is_embedded && shell_component != FRAGMENT_SHELL_COMPONENT { + if is_embedded && shell_component != FRAGMENT_SHELL_COMPONENT { log::warn!( "Embedded pages cannot use a shell component! Ignoring the '{shell_component}' component and its properties: {shell_row}" ); @@ -739,11 +741,7 @@ impl HtmlRenderContext { } if component_name == "log" { - return handle_log_component( - &self.request_context.source_path, - Some(self.current_statement), - data, - ); + return handle_log_component(&self.request_context, Some(self.current_statement), data); } match self.open_component_with_data(component_name, &data).await { @@ -910,14 +908,14 @@ impl HtmlRenderContext { } fn handle_log_component( - source_path: &Path, + request_context: &RequestInfo, current_statement: Option, data: &JsonValue, ) -> anyhow::Result<()> { let level_name = get_object_str(data, "level").unwrap_or("info"); let log_level = log::Level::from_str(level_name).with_context(|| "Invalid log level value")?; - let mut target = format!("sqlpage::log from \"{}\"", source_path.display()); + let mut target = format!("sqlpage::log from \"{}\"", request_context.path); if let Some(current_statement) = current_statement { write!(&mut target, " statement {current_statement}")?; } diff --git a/src/webserver/http.rs b/src/webserver/http.rs index 88c9966e..38dd4d77 100644 --- a/src/webserver/http.rs +++ b/src/webserver/http.rs @@ -6,7 +6,7 @@ use crate::render::{AnyRenderBodyContext, HeaderContext, PageContext}; use crate::webserver::content_security_policy::ContentSecurityPolicy; use crate::webserver::database::execute_queries::stop_at_first_error; use crate::webserver::database::{execute_queries::stream_query_results_with_conn, DbItem}; -use crate::webserver::http_request_info::extract_request_info; +use crate::webserver::http_request_info::{extract_request_info, RequestInfo}; use crate::webserver::ErrorWithStatus; use crate::{AppConfig, AppState, ParsedSqlFile, DEFAULT_404_FILE}; use actix_web::dev::{fn_service, ServiceFactory, ServiceRequest}; @@ -97,12 +97,12 @@ async fn stream_response(stream: impl Stream, mut renderer: AnyRe async fn build_response_header_and_stream>( app_state: Arc, database_entries: S, - request_context: RequestContext, + request_params: Arc, ) -> anyhow::Result> { let chan_size = app_state.config.max_pending_rows; let (sender, receiver) = mpsc::channel(chan_size); let writer = ResponseWriter::new(sender); - let mut head_context = HeaderContext::new(app_state, request_context, writer); + let mut head_context = HeaderContext::new(app_state, request_params, writer); let mut stream = Box::pin(database_entries); while let Some(item) = stream.next().await { let page_context = match item { @@ -173,24 +173,20 @@ async fn render_sql( let mut req_param = extract_request_info(srv_req, Arc::clone(&app_state)) .await .map_err(|e| anyhow_err_to_actix(e, &app_state))?; - log::debug!("Received a request with the following parameters: {req_param:?}"); + let sql_path = &sql_file.source_path; + log::debug!("Received a request to {sql_path:?} with the following parameters: {req_param:?}"); let (resp_send, resp_recv) = tokio::sync::oneshot::channel::(); - let source_path: PathBuf = sql_file.source_path.clone(); actix_web::rt::spawn(async move { - let request_context = RequestContext { - is_embedded: req_param.get_variables.contains_key("_sqlpage_embed"), - source_path, - content_security_policy: ContentSecurityPolicy::with_random_nonce(), - }; let mut conn = None; let database_entries_stream = stream_query_results_with_conn(&sql_file, &mut req_param, &mut conn); let database_entries_stream = stop_at_first_error(database_entries_stream); + let req_param = Arc::new(req_param); let response_with_writer = build_response_header_and_stream( Arc::clone(&app_state), database_entries_stream, - request_context, + Arc::clone(&req_param), ) .await; match response_with_writer { diff --git a/src/webserver/http_request_info.rs b/src/webserver/http_request_info.rs index 23675a51..179ae181 100644 --- a/src/webserver/http_request_info.rs +++ b/src/webserver/http_request_info.rs @@ -1,3 +1,4 @@ +use crate::webserver::content_security_policy::ContentSecurityPolicy; use crate::AppState; use actix_multipart::form::bytes::Bytes; use actix_multipart::form::tempfile::TempFile; @@ -42,6 +43,7 @@ pub struct RequestInfo { pub clone_depth: u8, pub raw_body: Option>, pub oidc_claims: Option, + pub content_security_policy: ContentSecurityPolicy, } impl RequestInfo { @@ -62,8 +64,13 @@ impl RequestInfo { clone_depth: self.clone_depth + 1, raw_body: self.raw_body.clone(), oidc_claims: self.oidc_claims.clone(), + content_security_policy: self.content_security_policy.clone(), } } + + pub fn is_embedded(&self) -> bool { + self.get_variables.contains_key("_sqlpage_embed") + } } impl Clone for RequestInfo { @@ -123,6 +130,7 @@ pub(crate) async fn extract_request_info( clone_depth: 0, raw_body, oidc_claims, + content_security_policy: ContentSecurityPolicy::with_random_nonce() }) }