1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-02-02 18:59:04 +01:00
actix-extras/src/session.rs

242 lines
8.6 KiB
Rust
Raw Normal View History

2017-12-28 21:14:04 -08:00
use std::{io, net};
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};
use tokio_core::net::TcpStream;
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::*;
use actix_web::{error, Error, HttpRequest, HttpResponse};
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> {
if let Some(s) = self.state.get(key) {
Some(s)
} else {
None
}
}
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()
}
2017-12-29 01:10:27 -08:00
fn write(&self, resp: HttpResponse) -> MiddlewareResponse {
2017-12-28 21:14:04 -08:00
if self.changed {
2017-12-29 01:10:27 -08:00
MiddlewareResponse::Future(self.inner.update(&self.state, resp, self.value.as_ref()))
} else {
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
pub fn new<S: net::ToSocketAddrs>(addr: S, key: &[u8]) -> io::Result<RedisSessionBackend> {
2017-12-28 21:14:04 -08:00
let h = Arbiter::handle();
let mut err = None;
for addr in addr.to_socket_addrs()? {
2017-12-29 01:10:27 -08:00
match net::TcpStream::connect(&addr) {
2017-12-28 21:14:04 -08:00
Err(e) => err = Some(e),
Ok(conn) => {
2017-12-29 01:10:27 -08:00
let addr = RedisActor::start(
TcpStream::from_stream(conn, h).expect("Can not create tcp stream"));
return Ok(RedisSessionBackend(
Rc::new(Inner{key: Key::from_master(key),
ttl: "7200".to_owned(),
addr: addr,
name: "actix-session".to_owned()})));
2017-12-28 21:14:04 -08:00
},
}
}
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(io::ErrorKind::Other, "Can not connect to redis server."))
}
}
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
}
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,
2017-12-29 01:10:27 -08:00
value: Some(value),
2017-12-28 21:14:04 -08:00
}
} else {
RedisSession {
changed: false,
inner: inner,
state: HashMap::new(),
2017-12-29 01:10:27 -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,
2017-12-28 21:14:04 -08:00
addr: Address<RedisActor>,
}
impl Inner {
fn load<S>(&self, req: &mut HttpRequest<S>)
2017-12-29 01:10:27 -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(
self.addr.call_fut(Command(resp_array!["GET", cookie.value()]))
.map_err(Error::from)
.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)))
},
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())
}
}))
} 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())),
Ok(body) => {
Either::B(
2017-12-29 01:10:27 -08:00
self.addr.call_fut(
Command(resp_array!["SET", value, body,"EX", &self.ttl]))
2017-12-28 21:14:04 -08:00
.map_err(Error::from)
2017-12-29 01:10:27 -08:00
.and_then(move |res| {
2017-12-28 21:14:04 -08:00
match res {
2017-12-29 01:10:27 -08:00
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);
}
}
Ok(resp)
},
Err(err) => Err(error::ErrorInternalServerError(err).into())
2017-12-28 21:14:04 -08:00
}
}))
}
})
}
}