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        if let Some(rendered_page_type) = state_self.configuration.render.is_rendered_page(&uri_path) {
61            // Negotiate
62            let mut last_modified = None;
63            if let Some(if_modified_since) = request.headers().if_modified_since() {
64                let path = state_self.configuration.files.asset(&uri_path);
65                let mut modified = file_modified(&path).map_err_internal_server("file modified")?;
66
67                if let Some(coordinator_modified) = state_self
68                    .configuration
69                    .files
70                    .coordinate
71                    .coordinator_modified()
72                    .map_err_internal_server("coordinator modified")?
73                {
74                    modified = max(modified, coordinator_modified);
75                }
76
77                if modified_since(Some(modified), Some(if_modified_since)) {
78                    last_modified = Some(modified);
79
80                    // Don't let next service do conditional HTTP
81                    let headers = request.headers_mut();
82                    headers.remove(IF_MODIFIED_SINCE);
83                    headers.remove(IF_NONE_MATCH);
84                }
85            }
86
87            let original_uri_path = DeferredResponse::get(&request).and_then(|deferred_response| {
88                if let DeferredResponse::RewriteFrom(original_uri_path) = deferred_response {
89                    Some(original_uri_path.clone())
90                } else {
91                    None
92                }
93            });
94
95            let is_json = is_json(&request);
96
97            let query = request.uri().decoded_query_map();
98
99            let socket = request.extensions().get::<Socket>().cloned();
100
101            let response = next.run(request).await;
102
103            if response.status() == StatusCode::OK {
104                tracing::debug!("rendered page: {}", uri_path);
105
106                let rendered_page = RenderedPage::new_from_response(
107                    &uri_path,
108                    rendered_page_type,
109                    response,
110                    &state_self.configuration.render,
111                )
112                .await?;
113
114                let mut context = rendered_page.context(
115                    socket,
116                    uri_path,
117                    original_uri_path,
118                    query,
119                    last_modified,
120                    is_json,
121                    &state_self.templates,
122                    &state_self.configuration,
123                );
124
125                context.prepare_default().await?;
126                context.prepare(CatalogPreparer).await?;
127
128                context.into_response().await
129            } else {
130                Ok(response)
131            }
132        } else {
133            Ok(next.run(request).await)
134        }
135    }
136}
137
138fn is_json(request: &Request) -> (bool, bool) {
139    if request
140        .headers()
141        .accept()
142        .best(RENDERED_PAGE_MEDIA_TYPES)
143        .map(|media_type_selector| media_type_selector == &JSON_MEDIA_TYPE)
144        .unwrap_or_default()
145    {
146        if let Some(query) = request.uri().decoded_query_map() {
147            return (true, query.get_single_as_ref("pretty") == Some("true"));
148        }
149
150        (true, false)
151    } else {
152        (false, false)
153    }
154}