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:
17
src/lib.rs
Normal file
17
src/lib.rs
Normal 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
118
src/redis.rs
Normal 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
156
src/session.rs
Normal 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())
|
||||
}
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user