1
0
mirror of https://github.com/actix/actix-extras.git synced 2025-06-27 10:39:03 +02:00

initial import

This commit is contained in:
Nikolay Kim
2017-12-28 21:14:04 -08:00
commit fab3c35ba9
8 changed files with 607 additions and 0 deletions

17
src/lib.rs Normal file
View File

@ -0,0 +1,17 @@
extern crate actix;
extern crate actix_web;
extern crate bytes;
extern crate futures;
extern crate serde;
extern crate serde_json;
extern crate tokio_io;
extern crate tokio_core;
#[macro_use]
extern crate redis_async;
#[macro_use]
extern crate failure;
mod redis;
mod session;
pub use session::RedisSessionBackend;

118
src/redis.rs Normal file
View File

@ -0,0 +1,118 @@
use std::io;
use std::collections::VecDeque;
use bytes::BytesMut;
use futures::Future;
use futures::future::Either;
use futures::unsync::oneshot;
use tokio_core::net::TcpStream;
use tokio_io::codec::{Decoder, Encoder};
use redis_async::{resp, error};
use actix::prelude::*;
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display="Io error: {}", _0)]
Io(io::Error),
#[fail(display="Redis error")]
Redis(error::Error),
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io(err)
}
}
#[derive(Message)]
pub struct Value(resp::RespValue);
/// Redis codec wrapper
pub struct RedisCodec;
impl Encoder for RedisCodec {
type Item = Value;
type Error = Error;
fn encode(&mut self, msg: Value, buf: &mut BytesMut) -> Result<(), Self::Error> {
match resp::RespCodec.encode(msg.0, buf) {
Ok(()) => Ok(()),
Err(err) => Err(Error::Io(err))
}
}
}
impl Decoder for RedisCodec {
type Item = Value;
type Error = Error;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match resp::RespCodec.decode(buf) {
Ok(Some(item)) => Ok(Some(Value(item))),
Ok(None) => Ok(None),
Err(err) => Err(Error::Redis(err)),
}
}
}
pub struct Command(pub resp::RespValue);
impl ResponseType for Command {
type Item = resp::RespValue;
type Error = Error;
}
/// Redis comminucation actor
pub struct RedisActor {
queue: VecDeque<oneshot::Sender<Result<resp::RespValue, Error>>>,
}
impl RedisActor {
pub fn start(io: TcpStream) -> Address<RedisActor> {
RedisActor{queue: VecDeque::new()}.framed(io, RedisCodec)
}
}
impl Actor for RedisActor {
type Context = FramedContext<Self>;
}
impl FramedActor for RedisActor {
type Io = TcpStream;
type Codec = RedisCodec;
}
impl StreamHandler<Value, Error> for RedisActor {}
impl Handler<Value, Error> for RedisActor {
fn error(&mut self, err: Error, _: &mut Self::Context) {
if let Some(tx) = self.queue.pop_front() {
let _ = tx.send(Err(err));
}
}
fn handle(&mut self, msg: Value, _ctx: &mut Self::Context) -> Response<Self, Value> {
if let Some(tx) = self.queue.pop_front() {
let _ = tx.send(Ok(msg.0));
}
Self::empty()
}
}
impl Handler<Command> for RedisActor {
fn handle(&mut self, msg: Command, ctx: &mut Self::Context) -> Response<Self, Command> {
let (tx, rx) = oneshot::channel();
self.queue.push_back(tx);
ctx.send(Value(msg.0));
Self::async_reply(
rx.map_err(|_| io::Error::new(io::ErrorKind::Other, "").into())
.and_then(|res| res)
.actfuture())
}
}

156
src/session.rs Normal file
View File

@ -0,0 +1,156 @@
use std::{io, net};
use std::rc::Rc;
use std::time::Duration;
use std::collections::HashMap;
use serde_json;
use futures::Future;
use futures::future::{Either, ok as FutOk, err as FutErr};
use tokio_core::net::TcpStream;
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>,
}
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()
}
fn write(&self, mut resp: HttpResponse) -> MiddlewareResponse {
if self.changed {
let _ = self.inner.update(&self.state);
}
MiddlewareResponse::Done(resp)
}
}
pub struct RedisSessionBackend(Rc<Inner>);
impl RedisSessionBackend {
/// Create new redis session backend
pub fn new<S: net::ToSocketAddrs>(addr: S, ttl: Duration) -> io::Result<RedisSessionBackend> {
let h = Arbiter::handle();
let mut err = None;
for addr in addr.to_socket_addrs()? {
match TcpStream::connect(&addr, &h).wait() {
Err(e) => err = Some(e),
Ok(conn) => {
let addr = RedisActor::start(conn);
return Ok(RedisSessionBackend(Rc::new(Inner{ttl: ttl, addr: addr})));
},
}
}
if let Some(e) = err.take() {
Err(e)
} else {
Err(io::Error::new(io::ErrorKind::Other, "Can not connect to redis server."))
}
}
}
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| {
if let Some(state) = state {
RedisSession {
changed: false,
inner: inner,
state: state,
}
} else {
RedisSession {
changed: false,
inner: inner,
state: HashMap::new(),
}
}
}))
}
}
struct Inner {
ttl: Duration,
addr: Address<RedisActor>,
}
impl Inner {
fn load<S>(&self, req: &mut HttpRequest<S>)
-> Box<Future<Item=Option<HashMap<String, String>>, Error=Error>>
{
if let Ok(cookies) = req.cookies() {
for cookie in cookies {
if cookie.name() == "actix-session" {
return Box::new(
self.addr.call_fut(Command(resp_array!["GET", cookie.value()]))
.map_err(Error::from)
.and_then(|res| {
match res {
Ok(val) => {
println!("VAL {:?}", val);
Ok(Some(HashMap::new()))
},
Err(err) => Err(
io::Error::new(io::ErrorKind::Other, "Error").into())
}
}))
}
}
}
Box::new(FutOk(None))
}
fn update(&self, state: &HashMap<String, String>) -> Box<Future<Item=(), Error=Error>> {
Box::new(
match serde_json::to_string(state) {
Err(e) => Either::A(FutErr(e.into())),
Ok(body) => {
Either::B(
self.addr.call_fut(Command(resp_array!["GET", "test"]))
.map_err(Error::from)
.and_then(|res| {
match res {
Ok(val) => Ok(()),
Err(err) => Err(
error::ErrorInternalServerError(err).into())
}
}))
}
})
}
}