actix_flash/
lib.rs

1/*!
2Flash message middleware for `actix-web` 2.0 or 3.0.
3
4Supports `actix-web` 3.0 by default. For 2.0, use:
5
6```
7actix-flash = { version = "0.2", features = ["v2"], default-features = false }
8```
9
10For `actix-web` 1.0 support, check out [`actix-web-flash`](https://github.com/hatzel/actix-web-flash).
11
12# Usage
13
14```rust,no_run
15use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder};
16
17async fn show_flash(flash: actix_flash::Message<String>) -> impl Responder {
18    flash.into_inner()
19}
20
21async fn set_flash(_req: HttpRequest) -> actix_flash::Response<HttpResponse, String> {
22    actix_flash::Response::with_redirect("This is the message".to_owned(), "/show_flash")
23}
24
25#[actix_rt::main]
26async fn main() -> std::io::Result<()> {
27    HttpServer::new(move || {
28        App::new()
29            .wrap(actix_flash::Flash::default())
30            .route("/show_flash", web::get().to(show_flash))
31            .route("/set_flash", web::get().to(set_flash))
32    })
33    .bind("127.0.0.1:8080")?
34    .run()
35    .await
36}
37```
38*/
39
40use std::rc::Rc;
41use std::task::{Context, Poll};
42
43use futures::future::{err, ok, LocalBoxFuture, Ready, TryFutureExt};
44
45use serde::{de::DeserializeOwned, Serialize, Deserialize};
46
47use actix_service::{Service, Transform};
48
49#[cfg(feature = "v2")]
50pub(crate) use actix_web_v2 as actix_web;
51
52#[cfg(feature = "v3")]
53pub(crate) use actix_web_v3 as actix_web;
54
55use actix_web::cookie::{Cookie, CookieJar};
56use actix_web::dev::{MessageBody, ServiceRequest, ServiceResponse};
57use actix_web::error::{Error, ErrorBadRequest, Result};
58use actix_web::{FromRequest, HttpMessage, HttpRequest, HttpResponse, Responder};
59
60#[derive(Debug)]
61struct FlashCookie(Cookie<'static>);
62#[derive(Clone)]
63struct FlashCookieValue(String);
64
65/// The flash message wrapper
66#[derive(Debug)]
67pub struct Message<T>(T);
68
69#[derive(Deserialize)]
70struct ValuedMessage<T> {
71    #[serde(rename="_")]
72    value: T
73}
74
75#[derive(Serialize)]
76struct ValuedMessageRef<'a, T> {
77    #[serde(rename="_")]
78    value: &'a T
79}
80
81impl<T> FromRequest for Message<T>
82where
83    T: DeserializeOwned + Serialize,
84{
85    type Config = ();
86    type Future = Ready<Result<Self, Self::Error>>;
87    type Error = Error;
88
89    fn from_request(req: &HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
90        if let Some(cookie) = req.extensions().get::<FlashCookie>() {
91            match serde_json::from_str(cookie.0.value()) {
92                Ok(ValuedMessage { value }) => { return ok(Message(value)); },
93                _ => {}
94            }
95        }
96        err(ErrorBadRequest("Invalid/missing flash cookie"))
97    }
98}
99
100impl<T> Message<T> {
101    pub fn new(inner: T) -> Self {
102        Self(inner)
103    }
104
105    pub fn into_inner(self) -> T {
106        self.0
107    }
108}
109
110/// The "flashing" response
111pub struct Response<R, T>
112where
113    R: Responder,
114    T: Serialize + DeserializeOwned,
115{
116    responder: R,
117    message: Option<Message<T>>,
118}
119
120impl<R, T> Responder for Response<R, T>
121where
122    R: Responder + 'static,
123    T: Serialize + DeserializeOwned + 'static,
124{
125    type Error = Error;
126    type Future = LocalBoxFuture<'static, Result<HttpResponse, Self::Error>>;
127
128    fn respond_to(mut self, req: &HttpRequest) -> Self::Future {
129        let msg = self.message.take();
130
131        let out = self.responder.respond_to(req).err_into().and_then(|mut res| async {
132            if let Some(msg) = msg {
133                let json = serde_json::to_string(&ValuedMessageRef { value: &msg.0 })?;
134                res.extensions_mut().insert(FlashCookieValue(json));
135            }
136            Ok(res)
137        });
138
139        Box::pin(out)
140    }
141}
142
143impl<R, T> Response<R, T>
144where
145    R: Responder,
146    T: Serialize + DeserializeOwned,
147{
148    pub fn new(message: Option<T>, responder: R) -> Self {
149        Self { responder, message: message.map(Message) }
150    }
151}
152
153impl<T> Response<HttpResponse, T>
154where
155    T: Serialize + DeserializeOwned,
156{
157    /// Create a new flashing response with given message and redirect location.
158    pub fn with_redirect(message: T, location: &str) -> Self {
159        let response =
160            HttpResponse::SeeOther().header(actix_web::http::header::LOCATION, location).finish();
161        Self { message: Some(Message(message)), responder: response }
162    }
163}
164
165/// The flash middleware transformer
166pub struct Flash {
167    cookie_name: Rc<str>,
168}
169
170impl Flash {
171    /// Create a new flash middleware transformer, using the given string as the cookie name.
172    pub fn new<I: Into<Rc<str>>>(cookie_name: I) -> Self {
173        Self { cookie_name: cookie_name.into() }
174    }
175}
176
177impl Default for Flash {
178    fn default() -> Self {
179        Self::new("_flash")
180    }
181}
182
183/// The actual flash middleware
184pub struct FlashMiddleware<S> {
185    cookie_name: Rc<str>,
186    service: S,
187}
188
189impl<S, B> Transform<S> for Flash
190where
191    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
192    B: MessageBody + 'static,
193{
194    type Request = ServiceRequest;
195    type Response = ServiceResponse<B>;
196    type Error = Error;
197    type InitError = ();
198    type Transform = FlashMiddleware<S>;
199    type Future = Ready<Result<Self::Transform, Self::InitError>>;
200
201    fn new_transform(&self, service: S) -> Self::Future {
202        ok(FlashMiddleware { service, cookie_name: self.cookie_name.clone() })
203    }
204}
205
206impl<S, B> Service for FlashMiddleware<S>
207where
208    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
209    B: MessageBody + 'static,
210{
211    type Request = ServiceRequest;
212    type Response = ServiceResponse<B>;
213    type Error = Error;
214    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
215
216    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
217        self.service.poll_ready(cx)
218    }
219
220    fn call(&mut self, req: ServiceRequest) -> Self::Future {
221        let cookie_name = String::from(self.cookie_name.as_ref());
222
223        if let Some(cookie) = req.cookie(&cookie_name) {
224            req.extensions_mut().insert(FlashCookie(cookie));
225        }
226
227        Box::pin(self.service.call(req).and_then(|mut res| async move {
228            let maybe_set_cookie = res.response().extensions().get::<FlashCookieValue>().cloned();
229
230            if let Some(FlashCookieValue(json)) = maybe_set_cookie {
231                let mut cookie = Cookie::new(cookie_name.clone(), json);
232                cookie.set_path("/");
233                res.response_mut().add_cookie(&cookie)?;
234            }
235
236            let mut jar = CookieJar::new();
237            if let Some(cookie) = res.request().cookie(&cookie_name) {
238                jar.add_original(cookie);
239                jar.remove(Cookie::build(cookie_name, "").path("/").finish());
240            }
241
242            for cookie in jar.delta() {
243                res.response_mut().add_cookie(cookie)?;
244            }
245
246            Ok(res)
247        }))
248    }
249}