credence_lib/middleware/
render.rs

1use super::{
2    super::{configuration::*, render::*},
3    defer::*,
4    socket::*,
5};
6
7use {
8    ::axum::{
9        extract::{Request, *},
10        http::{header::*, *},
11        middleware::*,
12        response::Response,
13    },
14    bytestring::*,
15    kutil_http::*,
16    std::{cmp::*, result::Result},
17};
18
19//
20// RenderMiddleware
21//
22
23/// Axum middleware that renders pages.
24#[derive(Clone, Debug)]
25pub struct RenderMiddleware {
26    /// Configuration.
27    pub configuration: CredenceConfiguration,
28
29    /// Templates.
30    pub templates: Templates,
31}
32
33impl RenderMiddleware {
34    /// Constrctor.
35    pub fn new(configuration: CredenceConfiguration) -> Self {
36        let templates = configuration.files.templates();
37        Self::new_with(configuration, templates)
38    }
39
40    /// Constructor.
41    pub fn new_with(configuration: CredenceConfiguration, templates: Templates) -> Self {
42        Self { configuration, templates }
43    }
44
45    /// To be used with [from_fn_with_state].
46    pub async fn function(
47        State(state_self): State<Self>,
48        mut request: Request,
49        next: Next,
50    ) -> Result<Response, StatusCode> {
51        let uri_path: ByteString = match request.uri().decoded_path() {
52            Some(uri_path) => uri_path.as_ref().into(),
53            None => {
54                // Cannot decode path
55                tracing::error!("cannot decode path: {}", request.uri());
56                return Err(StatusCode::INTERNAL_SERVER_ERROR);
57            }
58        };
59
60        let original_uri_path = DeferredResponse::get(&request).and_then(|deferred_response| {
61            if let DeferredResponse::RewriteFrom(original_uri_path) = deferred_response {
62                Some(original_uri_path.clone())
63            } else {
64                None
65            }
66        });
67
68        if let Some(rendered_page_type) = state_self.configuration.render.is_rendered_page(&uri_path) {
69            // Negotiate
70            let mut last_modified = None;
71            if let Some(if_modified_since) = request.headers().if_modified_since() {
72                let path = state_self.configuration.files.asset(&uri_path);
73                let mut modified = file_modified(&path).map_err_internal_server("file modified")?;
74
75                if let Some(coordinator_modified) = state_self
76                    .configuration
77                    .files
78                    .coordinate
79                    .coordinator_modified()
80                    .map_err_internal_server("coordinator modified")?
81                {
82                    modified = max(modified, coordinator_modified);
83                }
84
85                if modified_since(Some(modified), Some(if_modified_since)) {
86                    last_modified = Some(modified);
87
88                    // Don't let next service do conditional HTTP
89                    let headers = request.headers_mut();
90                    headers.remove(IF_MODIFIED_SINCE);
91                    headers.remove(IF_NONE_MATCH);
92                }
93            }
94
95            let is_json = is_json(&request);
96            let socket = request.extensions().get::<Socket>().cloned();
97
98            let response = next.run(request).await;
99
100            if response.status() == StatusCode::OK {
101                tracing::debug!("rendered page: {}", uri_path);
102
103                let rendered_page = RenderedPage::new_from_response(
104                    &uri_path,
105                    rendered_page_type,
106                    response,
107                    &state_self.configuration.render,
108                )
109                .await?;
110
111                let mut context = rendered_page.context(
112                    socket,
113                    uri_path,
114                    original_uri_path,
115                    last_modified,
116                    is_json,
117                    &state_self.templates,
118                    &state_self.configuration,
119                );
120
121                context.prepare_default().await?;
122                context.prepare(CatalogPreparer).await?;
123
124                context.into_response().await
125            } else {
126                Ok(response)
127            }
128        } else {
129            Ok(next.run(request).await)
130        }
131    }
132}
133
134fn is_json(request: &Request) -> (bool, bool) {
135    if request
136        .headers()
137        .accept()
138        .best(RENDERED_PAGE_MEDIA_TYPES)
139        .map(|media_type_selector| media_type_selector == &JSON_MEDIA_TYPE)
140        .unwrap_or_default()
141    {
142        if let Some(query) = request.uri().decoded_query_map() {
143            if let Some(pretty) = query.get("pretty") {
144                return (true, pretty == "true");
145            }
146        }
147
148        (true, false)
149    } else {
150        (false, false)
151    }
152}