2017-12-28 21:14:04 -08:00
|
|
|
use std::rc::Rc;
|
2017-12-29 01:10:27 -08:00
|
|
|
use std::iter::FromIterator;
|
2017-12-28 21:14:04 -08:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
use serde_json;
|
2017-12-29 01:10:27 -08:00
|
|
|
use rand::{self, Rng};
|
2017-12-28 21:14:04 -08:00
|
|
|
use futures::Future;
|
|
|
|
use futures::future::{Either, ok as FutOk, err as FutErr};
|
2017-12-29 01:10:27 -08:00
|
|
|
use redis_async::resp::RespValue;
|
|
|
|
use cookie::{CookieJar, Cookie, Key};
|
|
|
|
use http::header::{self, HeaderValue};
|
2017-12-28 21:14:04 -08:00
|
|
|
use actix::prelude::*;
|
2018-01-10 16:51:09 -08:00
|
|
|
use actix_web::{error, Error, Result, HttpRequest, HttpResponse};
|
2017-12-28 21:14:04 -08:00
|
|
|
use actix_web::middleware::{SessionImpl, SessionBackend, Response as MiddlewareResponse};
|
|
|
|
|
|
|
|
use redis::{Command, RedisActor};
|
|
|
|
|
|
|
|
|
|
|
|
/// Session that stores data in redis
|
|
|
|
pub struct RedisSession {
|
|
|
|
changed: bool,
|
|
|
|
inner: Rc<Inner>,
|
|
|
|
state: HashMap<String, String>,
|
2017-12-29 01:10:27 -08:00
|
|
|
value: Option<String>,
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SessionImpl for RedisSession {
|
|
|
|
|
|
|
|
fn get(&self, key: &str) -> Option<&str> {
|
2018-01-22 10:50:24 -08:00
|
|
|
self.state.get(key).map(|s| s.as_str())
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set(&mut self, key: &str, value: String) {
|
|
|
|
self.changed = true;
|
|
|
|
self.state.insert(key.to_owned(), value);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn remove(&mut self, key: &str) {
|
|
|
|
self.changed = true;
|
|
|
|
self.state.remove(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear(&mut self) {
|
|
|
|
self.changed = true;
|
|
|
|
self.state.clear()
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:51:09 -08:00
|
|
|
fn write(&self, resp: HttpResponse) -> Result<MiddlewareResponse> {
|
2017-12-28 21:14:04 -08:00
|
|
|
if self.changed {
|
2018-01-10 16:51:09 -08:00
|
|
|
Ok(MiddlewareResponse::Future(
|
|
|
|
self.inner.update(&self.state, resp, self.value.as_ref())))
|
2017-12-29 01:10:27 -08:00
|
|
|
} else {
|
2018-01-10 16:51:09 -08:00
|
|
|
Ok(MiddlewareResponse::Done(resp))
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-29 01:10:27 -08:00
|
|
|
/// Use redis as session storage.
|
|
|
|
///
|
|
|
|
/// You need to pass an address of the redis server and random value to the
|
|
|
|
/// constructor of `RedisSessionBackend`. This is private key for cookie session,
|
|
|
|
/// When this value is changed, all session data is lost.
|
|
|
|
///
|
|
|
|
/// Note that whatever you write into your session is visible by the user (but not modifiable).
|
|
|
|
///
|
|
|
|
/// Constructor panics if key length is less than 32 bytes.
|
2017-12-28 21:14:04 -08:00
|
|
|
pub struct RedisSessionBackend(Rc<Inner>);
|
|
|
|
|
|
|
|
impl RedisSessionBackend {
|
|
|
|
/// Create new redis session backend
|
2017-12-29 01:10:27 -08:00
|
|
|
///
|
|
|
|
/// * `addr` - address of the redis server
|
2018-01-22 00:40:50 -08:00
|
|
|
pub fn new<S: Into<String>>(addr: S, key: &[u8]) -> RedisSessionBackend {
|
|
|
|
RedisSessionBackend(
|
|
|
|
Rc::new(Inner{key: Key::from_master(key),
|
|
|
|
ttl: "7200".to_owned(),
|
|
|
|
addr: RedisActor::start(addr),
|
|
|
|
name: "actix-session".to_owned()}))
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
2017-12-29 01:10:27 -08:00
|
|
|
|
|
|
|
/// Set time to live in seconds for session value
|
|
|
|
pub fn ttl(mut self, ttl: u16) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().ttl = format!("{}", ttl);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-01-22 10:50:24 -08:00
|
|
|
/// Set custom cookie name for session id
|
2017-12-29 01:10:27 -08:00
|
|
|
pub fn cookie_name(mut self, name: &str) -> Self {
|
|
|
|
Rc::get_mut(&mut self.0).unwrap().name = name.to_owned();
|
|
|
|
self
|
|
|
|
}
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<S> SessionBackend<S> for RedisSessionBackend {
|
|
|
|
|
|
|
|
type Session = RedisSession;
|
|
|
|
type ReadFuture = Box<Future<Item=RedisSession, Error=Error>>;
|
|
|
|
|
|
|
|
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::ReadFuture {
|
|
|
|
let inner = Rc::clone(&self.0);
|
|
|
|
|
|
|
|
Box::new(self.0.load(req).map(move |state| {
|
2017-12-29 01:10:27 -08:00
|
|
|
if let Some((state, value)) = state {
|
2017-12-28 21:14:04 -08:00
|
|
|
RedisSession {
|
|
|
|
changed: false,
|
|
|
|
inner: inner,
|
|
|
|
state: state,
|
2018-01-22 10:50:24 -08:00
|
|
|
value: Some(value) }
|
2017-12-28 21:14:04 -08:00
|
|
|
} else {
|
|
|
|
RedisSession {
|
|
|
|
changed: false,
|
|
|
|
inner: inner,
|
|
|
|
state: HashMap::new(),
|
2018-01-22 10:50:24 -08:00
|
|
|
value: None }
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Inner {
|
2017-12-29 01:10:27 -08:00
|
|
|
key: Key,
|
|
|
|
ttl: String,
|
|
|
|
name: String,
|
2018-02-15 16:53:05 -08:00
|
|
|
addr: Addr<Unsync, RedisActor>,
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Inner {
|
2018-01-22 10:50:24 -08:00
|
|
|
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
|
2017-12-28 21:14:04 -08:00
|
|
|
fn load<S>(&self, req: &mut HttpRequest<S>)
|
2018-01-22 10:50:24 -08:00
|
|
|
-> Box<Future<Item=Option<(HashMap<String, String>, String)>, Error=Error>>
|
|
|
|
{
|
2017-12-28 21:14:04 -08:00
|
|
|
if let Ok(cookies) = req.cookies() {
|
|
|
|
for cookie in cookies {
|
2017-12-29 01:10:27 -08:00
|
|
|
if cookie.name() == self.name {
|
|
|
|
let mut jar = CookieJar::new();
|
|
|
|
jar.add_original(cookie.clone());
|
|
|
|
if let Some(cookie) = jar.signed(&self.key).get(&self.name) {
|
|
|
|
let value = cookie.value().to_owned();
|
|
|
|
return Box::new(
|
2018-02-15 16:53:05 -08:00
|
|
|
self.addr.send(Command(resp_array!["GET", cookie.value()]))
|
2017-12-29 01:10:27 -08:00
|
|
|
.map_err(Error::from)
|
2018-01-22 10:50:24 -08:00
|
|
|
.and_then(move |res| match res {
|
|
|
|
Ok(val) => {
|
|
|
|
match val {
|
|
|
|
RespValue::Error(err) =>
|
|
|
|
return Err(
|
|
|
|
error::ErrorInternalServerError(err).into()),
|
|
|
|
RespValue::SimpleString(s) =>
|
|
|
|
if let Ok(val) = serde_json::from_str(&s) {
|
|
|
|
return Ok(Some((val, value)))
|
2017-12-29 01:10:27 -08:00
|
|
|
},
|
2018-01-22 10:50:24 -08:00
|
|
|
RespValue::BulkString(s) => {
|
|
|
|
if let Ok(val) = serde_json::from_slice(&s) {
|
|
|
|
return Ok(Some((val, value)))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
Ok(None)
|
|
|
|
},
|
|
|
|
Err(err) => Err(error::ErrorInternalServerError(err).into())
|
2017-12-29 01:10:27 -08:00
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
return Box::new(FutOk(None))
|
|
|
|
}
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Box::new(FutOk(None))
|
|
|
|
}
|
|
|
|
|
2017-12-29 01:10:27 -08:00
|
|
|
fn update(&self, state: &HashMap<String, String>,
|
|
|
|
mut resp: HttpResponse,
|
|
|
|
value: Option<&String>) -> Box<Future<Item=HttpResponse, Error=Error>>
|
|
|
|
{
|
|
|
|
let (value, jar) = if let Some(value) = value {
|
|
|
|
(value.clone(), None)
|
|
|
|
} else {
|
|
|
|
let mut rng = rand::OsRng::new().unwrap();
|
|
|
|
let value = String::from_iter(rng.gen_ascii_chars().take(32));
|
|
|
|
|
|
|
|
let mut cookie = Cookie::new(self.name.clone(), value.clone());
|
|
|
|
cookie.set_path("/");
|
|
|
|
cookie.set_http_only(true);
|
|
|
|
|
|
|
|
// set cookie
|
|
|
|
let mut jar = CookieJar::new();
|
|
|
|
jar.signed(&self.key).add(cookie);
|
|
|
|
|
|
|
|
(value, Some(jar))
|
|
|
|
};
|
|
|
|
|
2017-12-28 21:14:04 -08:00
|
|
|
Box::new(
|
|
|
|
match serde_json::to_string(state) {
|
|
|
|
Err(e) => Either::A(FutErr(e.into())),
|
2018-01-22 10:50:24 -08:00
|
|
|
Ok(body) => Either::B(
|
2018-02-15 16:53:05 -08:00
|
|
|
self.addr.send(Command(resp_array!["SET", value, body,"EX", &self.ttl]))
|
2018-01-22 10:50:24 -08:00
|
|
|
.map_err(Error::from)
|
|
|
|
.and_then(move |res| match res {
|
|
|
|
Ok(_) => {
|
|
|
|
if let Some(jar) = jar {
|
|
|
|
for cookie in jar.delta() {
|
|
|
|
let val = HeaderValue::from_str(
|
|
|
|
&cookie.to_string())?;
|
|
|
|
resp.headers_mut().append(header::SET_COOKIE, val);
|
|
|
|
}
|
2017-12-28 21:14:04 -08:00
|
|
|
}
|
2018-01-22 10:50:24 -08:00
|
|
|
Ok(resp)
|
|
|
|
},
|
|
|
|
Err(err) => Err(error::ErrorInternalServerError(err).into())
|
|
|
|
}))
|
2017-12-28 21:14:04 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|