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