actix_middleware_macro/lib.rs
1//! # actix-middleware-macro
2//!
3//! For usage see the doc for the [`create_middleware`] macro.
4
5/// Macro to to reduce boilerplate codes for simple actix_web middleware.
6///
7/// # Example
8/// ```rust
9/// use actix_middleware_macro::create_middleware;
10///
11/// create_middleware!(
12/// TimingHeaders,
13/// |ctx: &MiddlewareTransform<S>, req: ServiceRequest| {
14/// use actix_web::http::header::{HeaderName, HeaderValue};
15/// use chrono::Utc;
16///
17/// let start = Utc::now();
18///
19/// let fut = ctx.service.call(req);
20/// Box::pin(async move {
21/// let mut res = fut.await?;
22/// let duration = Utc::now() - start;
23/// res.headers_mut().insert(
24/// HeaderName::from_static("x-app-time-ms"),
25/// HeaderValue::from_str(&format!("{}", duration.num_milliseconds()))?,
26/// );
27/// Ok(res)
28/// })
29/// }
30/// );
31///
32/// // Usage
33/// #[actix_web::test]
34/// async fn works() {
35/// let _server = tokio::spawn(async {
36/// actix_web::HttpServer::new(|| {
37/// actix_web::App::new()
38/// .default_service(web::to(|| async { actix_web::HttpResponse::Ok() }))
39/// .wrap(timing_cors_headers_middleware::Middleware)
40/// })
41/// .bind("127.1:8080")
42/// .unwrap()
43/// .run()
44/// .await
45/// .unwrap();
46/// });
47/// tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
48/// let response = ureq::get("http://127.1:8080").call().unwrap();
49/// assert!(response.header("x-app-time-ms").is_some());
50/// }
51/// ```
52#[macro_export]
53macro_rules! create_middleware {
54 ($name: ident, $code: expr) => {
55 paste::paste! {
56 mod [<$name:snake _middleware>] {
57 use futures_util::future::LocalBoxFuture;
58 use std::future::{ready, Ready};
59
60 use actix_web::{
61 dev::{Service, ServiceRequest, ServiceResponse, Transform},
62 Error
63 };
64
65 pub struct Middleware;
66 pub struct MiddlewareTransform<S> {
67 service: S,
68 }
69
70 impl<S, B> Transform<S, ServiceRequest> for Middleware
71 where
72 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
73 S::Future: 'static,
74 B: 'static,
75 {
76 type Response = ServiceResponse<B>;
77 type Error = Error;
78 type InitError = ();
79 type Transform = MiddlewareTransform<S>;
80 type Future = Ready<Result<Self::Transform, Self::InitError>>;
81
82 fn new_transform(&self, service: S) -> Self::Future {
83 ready(Ok(MiddlewareTransform { service }))
84 }
85 }
86
87 impl<S, B> Service<ServiceRequest> for MiddlewareTransform<S>
88 where
89 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
90 S::Future: 'static,
91 B: 'static,
92 {
93 type Response = ServiceResponse<B>;
94
95 type Error = Error;
96
97 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
98
99 fn poll_ready(
100 &self,
101 ctx: &mut core::task::Context<'_>,
102 ) -> std::task::Poll<Result<(), Self::Error>> {
103 self.service.poll_ready(ctx)
104 }
105
106 fn call(&self, req: ServiceRequest) -> Self::Future {
107 $code(self, req)
108 }
109 }
110 }
111 }
112 };
113}
114
115#[cfg(test)]
116mod test {
117 use actix_web::web;
118
119 use super::*;
120
121 create_middleware!(
122 TimingCorsHeaders,
123 |ctx: &MiddlewareTransform<S>, req: ServiceRequest| {
124 use actix_web::http::header::{HeaderName, HeaderValue};
125 use chrono::Utc;
126
127 let start = Utc::now();
128
129 let fut = ctx.service.call(req);
130 Box::pin(async move {
131 let mut res = fut.await?;
132 let duration = Utc::now() - start;
133 res.headers_mut().insert(
134 HeaderName::from_static("x-app-time-ms"),
135 HeaderValue::from_str(&format!("{}", duration.num_milliseconds()))?,
136 );
137 res.headers_mut().insert(
138 HeaderName::from_static("x-app-time-micros"),
139 HeaderValue::from_str(&format!(
140 "{}",
141 duration.num_microseconds().unwrap_or_default()
142 ))?,
143 );
144 // CORS header
145 res.headers_mut().insert(
146 HeaderName::from_static("access-control-allow-origin"),
147 HeaderValue::from_str("*")?,
148 );
149 res.headers_mut().insert(
150 HeaderName::from_static("access-control-allow-methods"),
151 HeaderValue::from_str("GET, POST, OPTIONS")?,
152 );
153 Ok(res)
154 })
155 }
156 );
157
158 #[actix_web::test]
159 async fn works() {
160 let _server = tokio::spawn(async {
161 actix_web::HttpServer::new(|| {
162 actix_web::App::new()
163 .default_service(web::to(|| async { actix_web::HttpResponse::Ok() }))
164 .wrap(timing_cors_headers_middleware::Middleware)
165 })
166 .bind("127.1:8080")
167 .unwrap()
168 .run()
169 .await
170 .unwrap();
171 });
172
173 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
174
175 let response = ureq::get("http://127.1:8080").call().unwrap();
176 assert!(response.header("x-app-time-ms").is_some());
177 }
178}