by_loco/controller/middleware/
mod.rs

1//! Base Middleware for Loco Application
2//!
3//! This module defines the various middleware components that Loco provides.
4//! Each middleware is responsible for handling different aspects of request
5//! processing, such as authentication, logging, CORS, compression, and error
6//! handling. The middleware can be easily configured and applied to the
7//! application's router.
8
9pub mod catch_panic;
10pub mod compression;
11pub mod cors;
12pub mod etag;
13pub mod fallback;
14pub mod format;
15pub mod limit_payload;
16pub mod logger;
17pub mod powered_by;
18pub mod remote_ip;
19pub mod request_id;
20pub mod secure_headers;
21#[cfg(feature = "embedded_assets")]
22pub mod static_assets_embedded;
23#[cfg(feature = "embedded_assets")]
24pub use static_assets_embedded as static_assets;
25
26#[cfg(not(feature = "embedded_assets"))]
27pub mod static_assets;
28pub mod timeout;
29
30use axum::Router as AXRouter;
31use serde::{Deserialize, Serialize};
32
33use crate::{app::AppContext, environment::Environment, Result};
34
35/// Trait representing the behavior of middleware components in the application.
36/// When implementing a new middleware, make sure to go over this checklist:
37/// * The name of the middleware should be an ID that is similar to the field
38///   name in configuration (look at how `serde` calls it)
39/// * Default value implementation should be paired with `serde` default
40///   handlers and default serialization implementation. Which means deriving
41///   `Default` will _not_ work. You can use `serde_json` and serialize a new
42///   config from an empty value, which will cause `serde` default value
43///   handlers to kick in.
44/// * If you need completely blank values for configuration (for example for
45///   testing), implement an `::empty() -> Self` call ad-hoc.
46pub trait MiddlewareLayer {
47    /// Returns the name of the middleware.
48    /// This should match the name of the property in the containing
49    /// `middleware` section in configuration (as named by `serde`)
50    fn name(&self) -> &'static str;
51
52    /// Returns whether the middleware is enabled or not.
53    /// If the middleware is switchable, take this value from a configuration
54    /// value
55    fn is_enabled(&self) -> bool {
56        true
57    }
58
59    /// Returns middleware config.
60    ///
61    /// # Errors
62    /// when could not convert middleware to [`serde_json::Value`]
63    fn config(&self) -> serde_json::Result<serde_json::Value>;
64
65    /// Applies the middleware to the given Axum router and returns the modified
66    /// router.
67    ///
68    /// # Errors
69    ///
70    /// If there is an issue when adding the middleware to the router.
71    fn apply(&self, app: AXRouter<AppContext>) -> Result<AXRouter<AppContext>>;
72}
73
74#[allow(clippy::unnecessary_lazy_evaluations)]
75#[must_use]
76pub fn default_middleware_stack(ctx: &AppContext) -> Vec<Box<dyn MiddlewareLayer>> {
77    // Shortened reference to middlewares
78    let middlewares = &ctx.config.server.middlewares;
79
80    vec![
81        // Limit Payload middleware with a default if none
82        Box::new(middlewares.limit_payload.clone().unwrap_or_default()),
83        // CORS middleware with a default if none
84        Box::new(middlewares.cors.clone().unwrap_or_else(|| cors::Cors {
85            enable: false,
86            ..Default::default()
87        })),
88        // Catch Panic middleware with a default if none
89        Box::new(
90            middlewares
91                .catch_panic
92                .clone()
93                .unwrap_or_else(|| catch_panic::CatchPanic { enable: true }),
94        ),
95        // Etag middleware with a default if none
96        Box::new(
97            middlewares
98                .etag
99                .clone()
100                .unwrap_or_else(|| etag::Etag { enable: true }),
101        ),
102        // Remote IP middleware with a default if none
103        Box::new(
104            middlewares
105                .remote_ip
106                .clone()
107                .unwrap_or_else(|| remote_ip::RemoteIpMiddleware {
108                    enable: false,
109                    ..Default::default()
110                }),
111        ),
112        // Compression middleware with a default if none
113        Box::new(
114            middlewares
115                .compression
116                .clone()
117                .unwrap_or_else(|| compression::Compression { enable: false }),
118        ),
119        // Timeout Request middleware with a default if none
120        Box::new(
121            middlewares
122                .timeout_request
123                .clone()
124                .unwrap_or_else(|| timeout::TimeOut {
125                    enable: false,
126                    ..Default::default()
127                }),
128        ),
129        // Static Assets middleware with a default if none
130        Box::new(middlewares.static_assets.clone().unwrap_or_else(|| {
131            static_assets::StaticAssets {
132                enable: false,
133                ..Default::default()
134            }
135        })),
136        // Secure Headers middleware with a default if none
137        Box::new(middlewares.secure_headers.clone().unwrap_or_else(|| {
138            secure_headers::SecureHeader {
139                enable: false,
140                ..Default::default()
141            }
142        })),
143        // Logger middleware with default logger configuration
144        Box::new(logger::new(
145            &middlewares
146                .logger
147                .clone()
148                .unwrap_or_else(|| logger::Config { enable: true }),
149            &ctx.environment,
150        )),
151        // Request ID middleware with a default if none
152        Box::new(
153            middlewares
154                .request_id
155                .clone()
156                .unwrap_or_else(|| request_id::RequestId { enable: true }),
157        ),
158        // Fallback middleware with a default if none
159        Box::new(
160            middlewares
161                .fallback
162                .clone()
163                .unwrap_or_else(|| fallback::Fallback {
164                    enable: ctx.environment != Environment::Production,
165                    ..Default::default()
166                }),
167        ),
168        // Powered by middleware with a default identifier
169        Box::new(powered_by::new(ctx.config.server.ident.as_deref())),
170    ]
171}
172
173/// Server middleware configuration structure.
174#[derive(Default, Debug, Clone, Deserialize, Serialize)]
175pub struct Config {
176    /// Compression for the response.
177    pub compression: Option<compression::Compression>,
178
179    /// Etag cache headers.
180    pub etag: Option<etag::Etag>,
181
182    /// Limit the payload request.
183    pub limit_payload: Option<limit_payload::LimitPayload>,
184
185    /// Logger and augmenting trace id with request data
186    pub logger: Option<logger::Config>,
187
188    /// Catch any code panic and log the error.
189    pub catch_panic: Option<catch_panic::CatchPanic>,
190
191    /// Setting a global timeout for requests
192    pub timeout_request: Option<timeout::TimeOut>,
193
194    /// CORS configuration
195    pub cors: Option<cors::Cors>,
196
197    /// Serving static assets
198    #[serde(rename = "static")]
199    pub static_assets: Option<static_assets::StaticAssets>,
200
201    /// Sets a set of secure headers
202    pub secure_headers: Option<secure_headers::SecureHeader>,
203
204    /// Calculates a remote IP based on `X-Forwarded-For` when behind a proxy
205    pub remote_ip: Option<remote_ip::RemoteIpMiddleware>,
206
207    /// Configure fallback behavior when hitting a missing URL
208    pub fallback: Option<fallback::Fallback>,
209
210    /// Request ID
211    pub request_id: Option<request_id::RequestId>,
212}