From fab3c35ba9aeb1d87f5e0faa12d4768095cc96db Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 28 Dec 2017 21:14:04 -0800 Subject: [PATCH 01/84] initial import --- .gitignore | 14 ++++ Cargo.toml | 20 +++++ LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 25 ++++++ example/basic.rs | 56 +++++++++++++ src/lib.rs | 17 ++++ src/redis.rs | 118 ++++++++++++++++++++++++++++ src/session.rs | 156 ++++++++++++++++++++++++++++++++++++ 8 files changed, 607 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 example/basic.rs create mode 100644 src/lib.rs create mode 100644 src/redis.rs create mode 100644 src/session.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..42d0755dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +Cargo.lock +target/ +guide/build/ +/gh-pages + +*.so +*.out +*.pyc +*.pid +*.sock +*~ + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..e88f30859 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "actix-redis" +version = "0.1.0" +authors = ["Nikolay Kim "] + +[lib] +name = "actix_redis" +path = "src/lib.rs" + +[dependencies] +bytes = "0.4" +failure = "^0.1.1" +futures = "0.1" +serde = "1.0" +serde_json = "1.0" +tokio-io = "0.1" +tokio-core = "0.1" +actix = "^0.3.5" +actix-web = { path = "../actix-web/" } +redis-async = { git = "https://github.com/benashford/redis-async-rs.git" } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..6cdf2d16c --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017-NOW Nikolay Kim + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..410ce45a4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017 Nikilay Kim + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/example/basic.rs b/example/basic.rs new file mode 100644 index 000000000..c6dbd93e4 --- /dev/null +++ b/example/basic.rs @@ -0,0 +1,56 @@ +#![allow(unused_variables)] +#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))] + +extern crate actix; +extern crate actix_web; +extern crate actix_redis; +extern crate env_logger; +extern crate futures; + +use actix_web::*; +use actix_web::middleware::RequestSession; +use actix_redis::RedisSessionBackend; + + +/// simple handler +fn index(mut req: HttpRequest) -> Result { + println!("{:?}", req); + if let Ok(ch) = req.payload_mut().readany().poll() { + if let futures::Async::Ready(Some(d)) = ch { + println!("{}", String::from_utf8_lossy(d.as_ref())); + } + } + + // session + if let Some(count) = req.session().get::("counter")? { + println!("SESSION value: {}", count); + req.session().set("counter", count+1)?; + } else { + req.session().set("counter", 1)?; + } + + Ok("Welcome!".into()) +} + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("basic-example"); + + HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + // cookie session middleware + .middleware(middleware::SessionStorage::new( + RedisSessionBackend::new("127.0.0.1:6379", Duration::from_secs(7200)) + .expect("Can not connect to redis server") + )) + // register simple route, handle all methods + .resource("/", |r| r.f(index))) + .bind("0.0.0.0:8080").unwrap() + .start(); + + println!("Starting http server: 127.0.0.1:8080"); + let _ = sys.run(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..3cf9edbb8 --- /dev/null +++ b/src/lib.rs @@ -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; diff --git a/src/redis.rs b/src/redis.rs new file mode 100644 index 000000000..ba7420e92 --- /dev/null +++ b/src/redis.rs @@ -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 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, 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>>, +} + +impl RedisActor { + pub fn start(io: TcpStream) -> Address { + RedisActor{queue: VecDeque::new()}.framed(io, RedisCodec) + } +} + +impl Actor for RedisActor { + type Context = FramedContext; +} + +impl FramedActor for RedisActor { + type Io = TcpStream; + type Codec = RedisCodec; +} + +impl StreamHandler for RedisActor {} + +impl Handler 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 { + if let Some(tx) = self.queue.pop_front() { + let _ = tx.send(Ok(msg.0)); + } + Self::empty() + } +} + +impl Handler for RedisActor { + fn handle(&mut self, msg: Command, ctx: &mut Self::Context) -> Response { + 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()) + } +} diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 000000000..49a4b0c48 --- /dev/null +++ b/src/session.rs @@ -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, + state: HashMap, +} + +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); + +impl RedisSessionBackend { + /// Create new redis session backend + pub fn new(addr: S, ttl: Duration) -> io::Result { + 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 SessionBackend for RedisSessionBackend { + + type Session = RedisSession; + type ReadFuture = Box>; + + fn from_request(&self, req: &mut HttpRequest) -> 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, +} + +impl Inner { + fn load(&self, req: &mut HttpRequest) + -> Box>, 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) -> Box> { + 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()) + } + })) + } + }) + } +} From 8924335338ead36d874a9f951ac906c757fb6cc3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 01:10:27 -0800 Subject: [PATCH 02/84] complete redis session backend --- Cargo.toml | 20 ++++- README.md | 43 ++++++++++ {example => examples}/basic.rs | 9 +- src/lib.rs | 4 + src/redis.rs | 3 +- src/session.rs | 149 ++++++++++++++++++++++++++------- 6 files changed, 185 insertions(+), 43 deletions(-) create mode 100644 README.md rename {example => examples}/basic.rs (80%) diff --git a/Cargo.toml b/Cargo.toml index e88f30859..f47f47606 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,23 @@ name = "actix-redis" version = "0.1.0" authors = ["Nikolay Kim "] +description = "Redis integration for actix web framework" +license = "MIT/Apache-2.0" +readme = "README.md" +keywords = ["http", "web", "framework", "async", "actix"] +homepage = "https://github.com/actix/actix-redis" +repository = "https://github.com/actix/actix-redis.git" +documentation = "https://docs.rs/actix-redis/" +categories = ["network-programming", "asynchronous"] +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] [lib] name = "actix_redis" path = "src/lib.rs" [dependencies] +rand = "0.3" +http = "0.1" bytes = "0.4" failure = "^0.1.1" futures = "0.1" @@ -16,5 +27,10 @@ serde_json = "1.0" tokio-io = "0.1" tokio-core = "0.1" actix = "^0.3.5" -actix-web = { path = "../actix-web/" } -redis-async = { git = "https://github.com/benashford/redis-async-rs.git" } +redis-async = "0.0" +cookie = { version="0.10", features=["percent-encode", "secure"] } + +actix-web = { git = "https://github.com/actix/actix-web.git" } + +[dev-dependencies] +env_logger = "0.4" diff --git a/README.md b/README.md new file mode 100644 index 000000000..53cbcc354 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Actix redis + +## Redis session backend + +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. + +```rust,ignore +# extern crate actix; +# extern crate actix_web; +# use actix_web::*; +use actix_web::middleware::SessionStorage; +use actix_redis::RedisSessionBackend; + +fn main() { + ::std::env::set_var("RUST_LOG", "actix_web=info"); + let _ = env_logger::init(); + let sys = actix::System::new("basic-example"); + + HttpServer::new( + || Application::new() + // enable logger + .middleware(middleware::Logger::default()) + // cookie session middleware + .middleware(SessionStorage::new( + RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) + .expect("Can not connect to redis server") + )) + // register simple route, handle all methods + .resource("/", |r| r.f(index))) + .bind("0.0.0.0:8080").unwrap() + .start(); + + let _ = sys.run(); +} +``` diff --git a/example/basic.rs b/examples/basic.rs similarity index 80% rename from example/basic.rs rename to examples/basic.rs index c6dbd93e4..53e6bd3b3 100644 --- a/example/basic.rs +++ b/examples/basic.rs @@ -15,11 +15,6 @@ use actix_redis::RedisSessionBackend; /// simple handler fn index(mut req: HttpRequest) -> Result { println!("{:?}", req); - if let Ok(ch) = req.payload_mut().readany().poll() { - if let futures::Async::Ready(Some(d)) = ch { - println!("{}", String::from_utf8_lossy(d.as_ref())); - } - } // session if let Some(count) = req.session().get::("counter")? { @@ -43,14 +38,14 @@ fn main() { .middleware(middleware::Logger::default()) // cookie session middleware .middleware(middleware::SessionStorage::new( - RedisSessionBackend::new("127.0.0.1:6379", Duration::from_secs(7200)) + RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) .expect("Can not connect to redis server") )) // register simple route, handle all methods .resource("/", |r| r.f(index))) .bind("0.0.0.0:8080").unwrap() + .threads(1) .start(); - println!("Starting http server: 127.0.0.1:8080"); let _ = sys.run(); } diff --git a/src/lib.rs b/src/lib.rs index 3cf9edbb8..38665b500 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,12 @@ extern crate actix; extern crate actix_web; extern crate bytes; +extern crate cookie; extern crate futures; extern crate serde; extern crate serde_json; +extern crate rand; +extern crate http; extern crate tokio_io; extern crate tokio_core; #[macro_use] @@ -14,4 +17,5 @@ extern crate failure; mod redis; mod session; +pub use redis::RedisActor; pub use session::RedisSessionBackend; diff --git a/src/redis.rs b/src/redis.rs index ba7420e92..6ecbec22a 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -3,7 +3,6 @@ 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}; @@ -108,7 +107,7 @@ impl Handler for RedisActor { fn handle(&mut self, msg: Command, ctx: &mut Self::Context) -> Response { let (tx, rx) = oneshot::channel(); self.queue.push_back(tx); - ctx.send(Value(msg.0)); + let _ = ctx.send(Value(msg.0)); Self::async_reply( rx.map_err(|_| io::Error::new(io::ErrorKind::Other, "").into()) diff --git a/src/session.rs b/src/session.rs index 49a4b0c48..9db88f092 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,12 +1,16 @@ use std::{io, net}; use std::rc::Rc; -use std::time::Duration; +use std::iter::FromIterator; use std::collections::HashMap; use serde_json; +use rand::{self, Rng}; use futures::Future; use futures::future::{Either, ok as FutOk, err as FutErr}; use tokio_core::net::TcpStream; +use redis_async::resp::RespValue; +use cookie::{CookieJar, Cookie, Key}; +use http::header::{self, HeaderValue}; use actix::prelude::*; use actix_web::{error, Error, HttpRequest, HttpResponse}; use actix_web::middleware::{SessionImpl, SessionBackend, Response as MiddlewareResponse}; @@ -19,6 +23,7 @@ pub struct RedisSession { changed: bool, inner: Rc, state: HashMap, + value: Option, } impl SessionImpl for RedisSession { @@ -46,27 +51,44 @@ impl SessionImpl for RedisSession { self.state.clear() } - fn write(&self, mut resp: HttpResponse) -> MiddlewareResponse { + fn write(&self, resp: HttpResponse) -> MiddlewareResponse { if self.changed { - let _ = self.inner.update(&self.state); + MiddlewareResponse::Future(self.inner.update(&self.state, resp, self.value.as_ref())) + } else { + MiddlewareResponse::Done(resp) } - MiddlewareResponse::Done(resp) } } +/// 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. pub struct RedisSessionBackend(Rc); impl RedisSessionBackend { /// Create new redis session backend - pub fn new(addr: S, ttl: Duration) -> io::Result { + /// + /// * `addr` - address of the redis server + pub fn new(addr: S, key: &[u8]) -> io::Result { let h = Arbiter::handle(); let mut err = None; for addr in addr.to_socket_addrs()? { - match TcpStream::connect(&addr, &h).wait() { + match net::TcpStream::connect(&addr) { Err(e) => err = Some(e), Ok(conn) => { - let addr = RedisActor::start(conn); - return Ok(RedisSessionBackend(Rc::new(Inner{ttl: ttl, addr: addr}))); + 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()}))); }, } } @@ -76,6 +98,17 @@ impl RedisSessionBackend { Err(io::Error::new(io::ErrorKind::Other, "Can not connect to redis server.")) } } + + /// 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 + } } impl SessionBackend for RedisSessionBackend { @@ -87,17 +120,19 @@ impl SessionBackend for RedisSessionBackend { let inner = Rc::clone(&self.0); Box::new(self.0.load(req).map(move |state| { - if let Some(state) = state { + if let Some((state, value)) = state { RedisSession { changed: false, inner: inner, state: state, + value: Some(value), } } else { RedisSession { changed: false, inner: inner, state: HashMap::new(), + value: None, } } })) @@ -105,49 +140,99 @@ impl SessionBackend for RedisSessionBackend { } struct Inner { - ttl: Duration, + key: Key, + ttl: String, + name: String, addr: Address, } impl Inner { fn load(&self, req: &mut HttpRequest) - -> Box>, Error=Error>> - { + -> Box, 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()) - } - })) + 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)) + } } } } Box::new(FutOk(None)) } - fn update(&self, state: &HashMap) -> Box> { + fn update(&self, state: &HashMap, + mut resp: HttpResponse, + value: Option<&String>) -> Box> + { + 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)) + }; + 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"])) + self.addr.call_fut( + Command(resp_array!["SET", value, body,"EX", &self.ttl])) .map_err(Error::from) - .and_then(|res| { + .and_then(move |res| { match res { - Ok(val) => Ok(()), - Err(err) => Err( - error::ErrorInternalServerError(err).into()) + 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()) } })) } From 43407261980e987fd08a91c93aa38567b0f47a32 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 01:12:24 -0800 Subject: [PATCH 03/84] update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 53cbcc354..7fdc1297d 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,10 @@ Note that whatever you write into your session is visible by the user (but not m Constructor panics if key length is less than 32 bytes. ```rust,ignore -# extern crate actix; -# extern crate actix_web; -# use actix_web::*; +extern crate actix_web; +extern crate actix_redis; + +use actix_web::*; use actix_web::middleware::SessionStorage; use actix_redis::RedisSessionBackend; From d7242659fe3f31480f501e605f4c804b3515ebc8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Dec 2017 01:20:29 -0800 Subject: [PATCH 04/84] add web feature --- Cargo.toml | 8 +++++++- src/lib.rs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f47f47606..a42a93dae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,12 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] name = "actix_redis" path = "src/lib.rs" +[features] +default = ["web"] + +# actix-web integration +web = ["actix-web"] + [dependencies] rand = "0.3" http = "0.1" @@ -30,7 +36,7 @@ actix = "^0.3.5" redis-async = "0.0" cookie = { version="0.10", features=["percent-encode", "secure"] } -actix-web = { git = "https://github.com/actix/actix-web.git" } +actix-web = { git="https://github.com/actix/actix-web.git", optional=true } [dev-dependencies] env_logger = "0.4" diff --git a/src/lib.rs b/src/lib.rs index 38665b500..763936b53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ extern crate actix; -extern crate actix_web; extern crate bytes; extern crate cookie; extern crate futures; @@ -14,8 +13,15 @@ extern crate redis_async; #[macro_use] extern crate failure; +#[cfg(feature="web")] +extern crate actix_web; + mod redis; + +#[cfg(feature="web")] mod session; pub use redis::RedisActor; + +#[cfg(feature="web")] pub use session::RedisSessionBackend; From cad71401e8b9c7454d8085022fb262ea6b9e2401 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Jan 2018 14:52:07 -0800 Subject: [PATCH 05/84] update actix to 0.4 --- Cargo.toml | 5 +++-- src/lib.rs | 6 ++---- src/redis.rs | 61 +++++++++++----------------------------------------- 3 files changed, 17 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a42a93dae..e8d15987e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,10 +32,11 @@ serde = "1.0" serde_json = "1.0" tokio-io = "0.1" tokio-core = "0.1" -actix = "^0.3.5" redis-async = "0.0" -cookie = { version="0.10", features=["percent-encode", "secure"] } +# cookie = { version="0.10", features=["percent-encode", "secure"] } +cookie = { git="https://github.com/alexcrichton/cookie-rs.git", features=["percent-encode", "secure"] } +actix = "0.4" actix-web = { git="https://github.com/actix/actix-web.git", optional=true } [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 763936b53..73f28f561 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,9 @@ extern crate failure; extern crate actix_web; mod redis; - -#[cfg(feature="web")] -mod session; - pub use redis::RedisActor; +#[cfg(feature="web")] +mod session; #[cfg(feature="web")] pub use session::RedisSessionBackend; diff --git a/src/redis.rs b/src/redis.rs index 6ecbec22a..173c01bef 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -1,11 +1,9 @@ use std::io; use std::collections::VecDeque; -use bytes::BytesMut; use futures::Future; use futures::unsync::oneshot; use tokio_core::net::TcpStream; -use tokio_io::codec::{Decoder, Encoder}; use redis_async::{resp, error}; use actix::prelude::*; @@ -27,34 +25,9 @@ impl From for Error { } } -#[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, Self::Error> { - match resp::RespCodec.decode(buf) { - Ok(Some(item)) => Ok(Some(Value(item))), - Ok(None) => Ok(None), - Err(err) => Err(Error::Redis(err)), - } +impl From for Error { + fn from(err: error::Error) -> Error { + Error::Redis(err) } } @@ -72,7 +45,7 @@ pub struct RedisActor { impl RedisActor { pub fn start(io: TcpStream) -> Address { - RedisActor{queue: VecDeque::new()}.framed(io, RedisCodec) + RedisActor{queue: VecDeque::new()}.framed(io, resp::RespCodec) } } @@ -82,34 +55,24 @@ impl Actor for RedisActor { impl FramedActor for RedisActor { type Io = TcpStream; - type Codec = RedisCodec; -} + type Codec = resp::RespCodec; -impl StreamHandler for RedisActor {} - -impl Handler for RedisActor { - - fn error(&mut self, err: Error, _: &mut Self::Context) { + fn handle(&mut self, msg: Result, _ctx: &mut Self::Context) { if let Some(tx) = self.queue.pop_front() { - let _ = tx.send(Err(err)); + let _ = tx.send(msg.map_err(|e| e.into())); } } - - fn handle(&mut self, msg: Value, _ctx: &mut Self::Context) -> Response { - if let Some(tx) = self.queue.pop_front() { - let _ = tx.send(Ok(msg.0)); - } - Self::empty() - } } impl Handler for RedisActor { - fn handle(&mut self, msg: Command, ctx: &mut Self::Context) -> Response { + type Result = ResponseFuture; + + fn handle(&mut self, msg: Command, ctx: &mut Self::Context) -> Self::Result { let (tx, rx) = oneshot::channel(); self.queue.push_back(tx); - let _ = ctx.send(Value(msg.0)); + let _ = ctx.send(msg.0); - Self::async_reply( + Box::new( rx.map_err(|_| io::Error::new(io::ErrorKind::Other, "").into()) .and_then(|res| res) .actfuture()) From ae28b1ad810e7b65408424b00848fb14072906ba Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 7 Jan 2018 19:26:51 -0800 Subject: [PATCH 06/84] use actix 0.4.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e8d15987e..48b764bdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ redis-async = "0.0" # cookie = { version="0.10", features=["percent-encode", "secure"] } cookie = { git="https://github.com/alexcrichton/cookie-rs.git", features=["percent-encode", "secure"] } -actix = "0.4" +actix = "^0.4.2" actix-web = { git="https://github.com/actix/actix-web.git", optional=true } [dev-dependencies] From abb6c6ba0ac487d9b5bbd328f1f0b359ad90ef2f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 10 Jan 2018 16:51:09 -0800 Subject: [PATCH 07/84] update session impl --- src/session.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/session.rs b/src/session.rs index 9db88f092..c34318e31 100644 --- a/src/session.rs +++ b/src/session.rs @@ -12,7 +12,7 @@ use redis_async::resp::RespValue; use cookie::{CookieJar, Cookie, Key}; use http::header::{self, HeaderValue}; use actix::prelude::*; -use actix_web::{error, Error, HttpRequest, HttpResponse}; +use actix_web::{error, Error, Result, HttpRequest, HttpResponse}; use actix_web::middleware::{SessionImpl, SessionBackend, Response as MiddlewareResponse}; use redis::{Command, RedisActor}; @@ -51,11 +51,12 @@ impl SessionImpl for RedisSession { self.state.clear() } - fn write(&self, resp: HttpResponse) -> MiddlewareResponse { + fn write(&self, resp: HttpResponse) -> Result { if self.changed { - MiddlewareResponse::Future(self.inner.update(&self.state, resp, self.value.as_ref())) + Ok(MiddlewareResponse::Future( + self.inner.update(&self.state, resp, self.value.as_ref()))) } else { - MiddlewareResponse::Done(resp) + Ok(MiddlewareResponse::Done(resp)) } } } From 69c3ab2f920f187b8f4f8ea5079269e04f6a528e Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Jan 2018 10:38:36 -0800 Subject: [PATCH 08/84] use actix-web release --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48b764bdf..281a1c56c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,10 @@ serde_json = "1.0" tokio-io = "0.1" tokio-core = "0.1" redis-async = "0.0" -# cookie = { version="0.10", features=["percent-encode", "secure"] } -cookie = { git="https://github.com/alexcrichton/cookie-rs.git", features=["percent-encode", "secure"] } +cookie = { version="0.10", features=["percent-encode", "secure"] } actix = "^0.4.2" -actix-web = { git="https://github.com/actix/actix-web.git", optional=true } +actix-web = { version="0.3", optional=true } [dev-dependencies] env_logger = "0.4" From 237030dbfc27b5bce64f452054ba5dbec53e3c73 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 00:40:50 -0800 Subject: [PATCH 09/84] better connection handling --- Cargo.toml | 25 ++++--- README.md | 1 - examples/basic.rs | 5 +- src/connect.rs | 174 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 29 +++++--- src/redis.rs | 90 +++++++++++++++++++----- src/session.rs | 30 ++------ 7 files changed, 291 insertions(+), 63 deletions(-) create mode 100644 src/connect.rs diff --git a/Cargo.toml b/Cargo.toml index 281a1c56c..3fe330e44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,23 +20,30 @@ path = "src/lib.rs" default = ["web"] # actix-web integration -web = ["actix-web"] +web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -rand = "0.3" -http = "0.1" -bytes = "0.4" +actix = "^0.4.3" + +log = "0.4" +backoff = "0.1" failure = "^0.1.1" futures = "0.1" -serde = "1.0" -serde_json = "1.0" tokio-io = "0.1" tokio-core = "0.1" redis-async = "0.0" -cookie = { version="0.10", features=["percent-encode", "secure"] } +trust-dns-resolver = "0.7" -actix = "^0.4.2" +# actix web session actix-web = { version="0.3", optional=true } +cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } +http = { version="0.1", optional=true } +rand = { version="0.3", optional=true } +serde = { version="1.0", optional=true } +serde_json = { version="1.0", optional=true } [dev-dependencies] -env_logger = "0.4" +env_logger = "0.5" + +[patch.crates-io] +"actix" = { git = 'https://github.com/actix/actix.git' } diff --git a/README.md b/README.md index 7fdc1297d..c2d500259 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ fn main() { // cookie session middleware .middleware(SessionStorage::new( RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) - .expect("Can not connect to redis server") )) // register simple route, handle all methods .resource("/", |r| r.f(index))) diff --git a/examples/basic.rs b/examples/basic.rs index 53e6bd3b3..e7e5874bd 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -28,8 +28,8 @@ fn index(mut req: HttpRequest) -> Result { } fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); + ::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); + env_logger::init(); let sys = actix::System::new("basic-example"); HttpServer::new( @@ -39,7 +39,6 @@ fn main() { // cookie session middleware .middleware(middleware::SessionStorage::new( RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) - .expect("Can not connect to redis server") )) // register simple route, handle all methods .resource("/", |r| r.f(index))) diff --git a/src/connect.rs b/src/connect.rs new file mode 100644 index 000000000..f2c1d052b --- /dev/null +++ b/src/connect.rs @@ -0,0 +1,174 @@ +use std::io; +use std::net::SocketAddr; +use std::collections::VecDeque; +use std::time::Duration; + +use actix::Arbiter; +use trust_dns_resolver::ResolverFuture; +use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; +use trust_dns_resolver::lookup_ip::LookupIpFuture; +use futures::{Async, Future, Poll}; +use tokio_core::reactor::Timeout; +use tokio_core::net::{TcpStream, TcpStreamNew}; + + +#[derive(Fail, Debug)] +pub enum TcpConnectorError { + /// Failed to resolve the hostname + #[fail(display = "Failed resolving hostname: {}", _0)] + Dns(String), + + /// Address is invalid + #[fail(display = "Invalid input: {}", _0)] + InvalidInput(&'static str), + + /// Connecting took too long + #[fail(display = "Timeout out while establishing connection")] + Timeout, + + /// Connection io error + #[fail(display = "{}", _0)] + IoError(io::Error), +} + +pub struct TcpConnector { + lookup: Option, + port: u16, + ips: VecDeque, + error: Option, + timeout: Timeout, + stream: Option, +} + +impl TcpConnector { + + pub fn new>(addr: S) -> TcpConnector { + TcpConnector::with_timeout(addr, Duration::from_secs(1)) + } + + pub fn with_timeout>(addr: S, timeout: Duration) -> TcpConnector { + // try to parse as a regular SocketAddr first + if let Ok(addr) = addr.as_ref().parse() { + let mut ips = VecDeque::new(); + ips.push_back(addr); + + TcpConnector { + lookup: None, + port: 0, + ips: ips, + error: None, + stream: None, + timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() } + } else { + match TcpConnector::parse(addr.as_ref()) { + Ok((host, port)) => { + // we need to do dns resolution + let resolve = match ResolverFuture::from_system_conf(Arbiter::handle()) { + Ok(resolve) => resolve, + Err(err) => { + warn!("Can not create system dns resolver: {}", err); + ResolverFuture::new( + ResolverConfig::default(), + ResolverOpts::default(), + Arbiter::handle()) + } + }; + + TcpConnector { + lookup: Some(resolve.lookup_ip(host)), + port: port, + ips: VecDeque::new(), + error: None, + stream: None, + timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() } + }, + Err(err) => + TcpConnector { + lookup: None, + port: 0, + ips: VecDeque::new(), + error: Some(err), + stream: None, + timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() }, + } + } + } + + fn parse(addr: &str) -> Result<(&str, u16), TcpConnectorError> { + macro_rules! try_opt { + ($e:expr, $msg:expr) => ( + match $e { + Some(r) => r, + None => return Err(TcpConnectorError::InvalidInput($msg)), + } + ) + } + + // split the string by ':' and convert the second part to u16 + let mut parts_iter = addr.rsplitn(2, ':'); + let port_str = try_opt!(parts_iter.next(), "invalid socket address"); + let host = try_opt!(parts_iter.next(), "invalid socket address"); + let port: u16 = try_opt!(port_str.parse().ok(), "invalid port value"); + + Ok((host, port)) + } +} + +impl Future for TcpConnector { + type Item = TcpStream; + type Error = TcpConnectorError; + + fn poll(&mut self) -> Poll { + if let Some(err) = self.error.take() { + Err(err) + } else { + // timeout + if let Ok(Async::Ready(_)) = self.timeout.poll() { + return Err(TcpConnectorError::Timeout) + } + + // lookip ips + if let Some(mut lookup) = self.lookup.take() { + match lookup.poll() { + Ok(Async::NotReady) => { + self.lookup = Some(lookup); + return Ok(Async::NotReady) + }, + Ok(Async::Ready(ips)) => { + let port = self.port; + let ips = ips.iter().map(|ip| SocketAddr::new(ip, port)); + self.ips.extend(ips); + if self.ips.is_empty() { + return Err(TcpConnectorError::Dns( + "Expect at least one A dns record".to_owned())) + } + }, + Err(err) => return Err(TcpConnectorError::Dns(format!("{}", err))), + } + } + + // connect + loop { + if let Some(mut new) = self.stream.take() { + match new.poll() { + Ok(Async::Ready(sock)) => + return Ok(Async::Ready(sock)), + Ok(Async::NotReady) => { + self.stream = Some(new); + return Ok(Async::NotReady) + }, + Err(err) => { + if self.ips.is_empty() { + return Err(TcpConnectorError::IoError(err)) + } + } + } + } + + // try to connect + let addr = self.ips.pop_front().unwrap(); + self.stream = Some(TcpStream::connect(&addr, Arbiter::handle())); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 73f28f561..f25bf0337 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,23 +1,34 @@ extern crate actix; -extern crate bytes; -extern crate cookie; +extern crate backoff; extern crate futures; -extern crate serde; -extern crate serde_json; -extern crate rand; -extern crate http; extern crate tokio_io; extern crate tokio_core; #[macro_use] +extern crate log; +#[macro_use] extern crate redis_async; #[macro_use] extern crate failure; +extern crate trust_dns_resolver; + +mod redis; +mod connect; + +pub use redis::RedisActor; +pub use connect::TcpConnector; #[cfg(feature="web")] extern crate actix_web; - -mod redis; -pub use redis::RedisActor; +#[cfg(feature="web")] +extern crate cookie; +#[cfg(feature="web")] +extern crate rand; +#[cfg(feature="web")] +extern crate http; +#[cfg(feature="web")] +extern crate serde; +#[cfg(feature="web")] +extern crate serde_json; #[cfg(feature="web")] mod session; diff --git a/src/redis.rs b/src/redis.rs index 173c01bef..149c35778 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -1,30 +1,32 @@ use std::io; use std::collections::VecDeque; +use actix::prelude::*; +use backoff::ExponentialBackoff; +use backoff::backoff::Backoff; use futures::Future; use futures::unsync::oneshot; +use tokio_io::AsyncRead; use tokio_core::net::TcpStream; use redis_async::{resp, error}; -use actix::prelude::*; +use connect::TcpConnector; #[derive(Fail, Debug)] pub enum Error { - #[fail(display="Io error: {}", _0)] - Io(io::Error), - #[fail(display="Redis error")] + #[fail(display="Redis error {}", _0)] Redis(error::Error), + /// Receiving message during reconnecting + #[fail(display="Redis: Not connected")] + NotConnected, + /// Cancel all waters when connection get dropped + #[fail(display="Redis: Disconnected")] + Disconnected, } unsafe impl Send for Error {} unsafe impl Sync for Error {} -impl From for Error { - fn from(err: io::Error) -> Error { - Error::Io(err) - } -} - impl From for Error { fn from(err: error::Error) -> Error { Error::Redis(err) @@ -40,23 +42,73 @@ impl ResponseType for Command { /// Redis comminucation actor pub struct RedisActor { + addr: String, + backoff: ExponentialBackoff, + cell: Option>, queue: VecDeque>>, } impl RedisActor { - pub fn start(io: TcpStream) -> Address { - RedisActor{queue: VecDeque::new()}.framed(io, resp::RespCodec) + pub fn start>(addr: S) -> Address { + let addr = addr.into(); + + Supervisor::start(|_| { + RedisActor { addr: addr, + cell: None, + backoff: ExponentialBackoff::default(), + queue: VecDeque::new() } + }).0 } } impl Actor for RedisActor { - type Context = FramedContext; + type Context = Context; + + fn started(&mut self, ctx: &mut Context) { + TcpConnector::new(self.addr.as_str()) + .into_actor(self) + .map(|stream, act, ctx| { + info!("Connected to redis server: {}", act.addr); + act.backoff.reset(); + act.cell = Some(act.add_framed(ctx, stream.framed(resp::RespCodec))); + }) + .map_err(|err, act, ctx| { + error!("Can not connect to redis server: {}", err); + debug!("{:?}", err); + if let Some(timeout) = act.backoff.next_backoff() { + // delay re-connect, drop all messages during this period + ctx.run_later(timeout, |_, ctx| { + ctx.stop() + }); + } else { + ctx.stop(); + } + }) + .wait(ctx); + } +} + +impl Supervised for RedisActor { + fn restarting(&mut self, _: &mut Self::Context) { + self.cell.take(); + for tx in self.queue.drain(..) { + let _ = tx.send(Err(Error::Disconnected)); + } + } } impl FramedActor for RedisActor { type Io = TcpStream; type Codec = resp::RespCodec; + fn closed(&mut self, error: Option, _: &mut Self::Context) { + if let Some(err) = error { + warn!("Redis connection dropped: {} error: {}", self.addr, err); + } else { + warn!("Redis connection dropped: {}", self.addr); + } + } + fn handle(&mut self, msg: Result, _ctx: &mut Self::Context) { if let Some(tx) = self.queue.pop_front() { let _ = tx.send(msg.map_err(|e| e.into())); @@ -67,13 +119,17 @@ impl FramedActor for RedisActor { impl Handler for RedisActor { type Result = ResponseFuture; - fn handle(&mut self, msg: Command, ctx: &mut Self::Context) -> Self::Result { + fn handle(&mut self, msg: Command, _: &mut Self::Context) -> Self::Result { let (tx, rx) = oneshot::channel(); - self.queue.push_back(tx); - let _ = ctx.send(msg.0); + if let Some(ref mut cell) = self.cell { + self.queue.push_back(tx); + cell.send(msg.0); + } else { + let _ = tx.send(Err(Error::NotConnected)); + } Box::new( - rx.map_err(|_| io::Error::new(io::ErrorKind::Other, "").into()) + rx.map_err(|_| Error::Disconnected) .and_then(|res| res) .actfuture()) } diff --git a/src/session.rs b/src/session.rs index c34318e31..bc7a5684d 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,4 +1,3 @@ -use std::{io, net}; use std::rc::Rc; use std::iter::FromIterator; use std::collections::HashMap; @@ -7,7 +6,6 @@ use serde_json; use rand::{self, Rng}; use futures::Future; use futures::future::{Either, ok as FutOk, err as FutErr}; -use tokio_core::net::TcpStream; use redis_async::resp::RespValue; use cookie::{CookieJar, Cookie, Key}; use http::header::{self, HeaderValue}; @@ -76,28 +74,12 @@ impl RedisSessionBackend { /// Create new redis session backend /// /// * `addr` - address of the redis server - pub fn new(addr: S, key: &[u8]) -> io::Result { - let h = Arbiter::handle(); - let mut err = None; - for addr in addr.to_socket_addrs()? { - match net::TcpStream::connect(&addr) { - Err(e) => err = Some(e), - Ok(conn) => { - 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()}))); - }, - } - } - if let Some(e) = err.take() { - Err(e) - } else { - Err(io::Error::new(io::ErrorKind::Other, "Can not connect to redis server.")) - } + pub fn new>(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()})) } /// Set time to live in seconds for session value From cb4a6036f825abac7cb8b907ffc0f4ed700ab924 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 01:05:50 -0800 Subject: [PATCH 10/84] update actix --- src/redis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redis.rs b/src/redis.rs index 149c35778..1c1d5e5e3 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -70,7 +70,7 @@ impl Actor for RedisActor { .map(|stream, act, ctx| { info!("Connected to redis server: {}", act.addr); act.backoff.reset(); - act.cell = Some(act.add_framed(ctx, stream.framed(resp::RespCodec))); + act.cell = Some(act.add_framed(stream.framed(resp::RespCodec), ctx)); }) .map_err(|err, act, ctx| { error!("Can not connect to redis server: {}", err); From d665d4d76118e87ed4d6a0eceed369db057d9829 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 10:27:59 -0800 Subject: [PATCH 11/84] update actix api --- src/redis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redis.rs b/src/redis.rs index 1c1d5e5e3..6d8665759 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -57,7 +57,7 @@ impl RedisActor { cell: None, backoff: ExponentialBackoff::default(), queue: VecDeque::new() } - }).0 + }) } } From 91709bc17fc75cf3752e8caf9be4cac67ad88283 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 10:42:13 -0800 Subject: [PATCH 12/84] cleanup impl --- src/lib.rs | 22 ++++++++++++++++++++++ src/redis.rs | 48 ++++++++++++------------------------------------ 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f25bf0337..29146b9d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,3 +34,25 @@ extern crate serde_json; mod session; #[cfg(feature="web")] pub use session::RedisSessionBackend; + + +#[derive(Fail, Debug)] +pub enum Error { + #[fail(display="Redis error {}", _0)] + Redis(redis_async::error::Error), + /// Receiving message during reconnecting + #[fail(display="Redis: Not connected")] + NotConnected, + /// Cancel all waters when connection get dropped + #[fail(display="Redis: Disconnected")] + Disconnected, +} + +unsafe impl Send for Error {} +unsafe impl Sync for Error {} + +impl From for Error { + fn from(err: redis_async::error::Error) -> Error { + Error::Redis(err) + } +} diff --git a/src/redis.rs b/src/redis.rs index 6d8665759..184eb8621 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -8,44 +8,22 @@ use futures::Future; use futures::unsync::oneshot; use tokio_io::AsyncRead; use tokio_core::net::TcpStream; -use redis_async::{resp, error}; +use redis_async::error::Error as RespError; +use redis_async::resp::{RespCodec, RespValue}; +use Error; use connect::TcpConnector; -#[derive(Fail, Debug)] -pub enum Error { - #[fail(display="Redis error {}", _0)] - Redis(error::Error), - /// Receiving message during reconnecting - #[fail(display="Redis: Not connected")] - NotConnected, - /// Cancel all waters when connection get dropped - #[fail(display="Redis: Disconnected")] - Disconnected, -} - -unsafe impl Send for Error {} -unsafe impl Sync for Error {} - -impl From for Error { - fn from(err: error::Error) -> Error { - Error::Redis(err) - } -} - -pub struct Command(pub resp::RespValue); - -impl ResponseType for Command { - type Item = resp::RespValue; - type Error = Error; -} +#[derive(Message)] +#[rtype(RespValue, Error)] +pub struct Command(pub RespValue); /// Redis comminucation actor pub struct RedisActor { addr: String, backoff: ExponentialBackoff, cell: Option>, - queue: VecDeque>>, + queue: VecDeque>>, } impl RedisActor { @@ -70,16 +48,14 @@ impl Actor for RedisActor { .map(|stream, act, ctx| { info!("Connected to redis server: {}", act.addr); act.backoff.reset(); - act.cell = Some(act.add_framed(stream.framed(resp::RespCodec), ctx)); + act.cell = Some(act.add_framed(stream.framed(RespCodec), ctx)); }) .map_err(|err, act, ctx| { error!("Can not connect to redis server: {}", err); debug!("{:?}", err); + // re-connect with backoff time if let Some(timeout) = act.backoff.next_backoff() { - // delay re-connect, drop all messages during this period - ctx.run_later(timeout, |_, ctx| { - ctx.stop() - }); + ctx.run_later(timeout, |_, ctx| ctx.stop()); } else { ctx.stop(); } @@ -99,7 +75,7 @@ impl Supervised for RedisActor { impl FramedActor for RedisActor { type Io = TcpStream; - type Codec = resp::RespCodec; + type Codec = RespCodec; fn closed(&mut self, error: Option, _: &mut Self::Context) { if let Some(err) = error { @@ -109,7 +85,7 @@ impl FramedActor for RedisActor { } } - fn handle(&mut self, msg: Result, _ctx: &mut Self::Context) { + fn handle(&mut self, msg: Result, _ctx: &mut Self::Context) { if let Some(tx) = self.queue.pop_front() { let _ = tx.send(msg.map_err(|e| e.into())); } From 7910c7b5112065a9179d1127931fd26436f211ae Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 10:43:36 -0800 Subject: [PATCH 13/84] comment --- src/redis.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/redis.rs b/src/redis.rs index 184eb8621..a369dfacd 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -53,7 +53,8 @@ impl Actor for RedisActor { .map_err(|err, act, ctx| { error!("Can not connect to redis server: {}", err); debug!("{:?}", err); - // re-connect with backoff time + // re-connect with backoff time. + // we stop currect context, supervisor will restart it. if let Some(timeout) = act.backoff.next_backoff() { ctx.run_later(timeout, |_, ctx| ctx.stop()); } else { From 5b9b8c6dd92f92cc806d15049a848a1f56ec5216 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 10:50:24 -0800 Subject: [PATCH 14/84] session code cleanup --- src/session.rs | 93 +++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/src/session.rs b/src/session.rs index bc7a5684d..38466c169 100644 --- a/src/session.rs +++ b/src/session.rs @@ -27,11 +27,7 @@ pub struct RedisSession { impl SessionImpl for RedisSession { fn get(&self, key: &str) -> Option<&str> { - if let Some(s) = self.state.get(key) { - Some(s) - } else { - None - } + self.state.get(key).map(|s| s.as_str()) } fn set(&mut self, key: &str, value: String) { @@ -88,6 +84,7 @@ impl RedisSessionBackend { self } + /// Set custom cookie name for session id pub fn cookie_name(mut self, name: &str) -> Self { Rc::get_mut(&mut self.0).unwrap().name = name.to_owned(); self @@ -108,15 +105,13 @@ impl SessionBackend for RedisSessionBackend { changed: false, inner: inner, state: state, - value: Some(value), - } + value: Some(value) } } else { RedisSession { changed: false, inner: inner, state: HashMap::new(), - value: None, - } + value: None } } })) } @@ -130,8 +125,10 @@ struct Inner { } impl Inner { + #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] fn load(&self, req: &mut HttpRequest) - -> Box, String)>, Error=Error>> { + -> Box, String)>, Error=Error>> + { if let Ok(cookies) = req.cookies() { for cookie in cookies { if cookie.name() == self.name { @@ -142,28 +139,26 @@ impl Inner { 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))) - } + .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))) }, - _ => (), - } - Ok(None) - }, - Err(err) => Err(error::ErrorInternalServerError(err).into()) - } + 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)) @@ -198,27 +193,23 @@ impl Inner { 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!["SET", value, body,"EX", &self.ttl])) - .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); - } - } - Ok(resp) - }, - Err(err) => Err(error::ErrorInternalServerError(err).into()) + Ok(body) => Either::B( + self.addr.call_fut( + Command(resp_array!["SET", value, body,"EX", &self.ttl])) + .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); + } } - })) - } + Ok(resp) + }, + Err(err) => Err(error::ErrorInternalServerError(err).into()) + })) }) } } From 118d0de8e67e543120fcca8e91b375270d1b8ede Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 22:28:29 -0800 Subject: [PATCH 15/84] add tests --- .travis.yml | 67 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/lib.rs | 6 +++- src/redis.rs | 2 +- tests/test_redis.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 .travis.yml create mode 100644 tests/test_redis.rs diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..82d8b9f17 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,67 @@ +language: rust +rust: + - 1.20.0 + - stable + - beta + - nightly + +sudo: required +dist: trusty + +services: + - redis-server + +env: + global: + - RUSTFLAGS="-C link-dead-code" + +addons: + apt: + packages: + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev + - cmake + - gcc + - binutils-dev + - libiberty-dev + +# Add clippy +before_script: + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + ( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false ); + fi + - export PATH=$PATH:~/.cargo/bin + +script: + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + USE_SKEPTIC=1 cargo test + else + cargo test + cd examples/chat && cargo check && cd ../.. + fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then + cargo clippy + fi + +# Upload docs +after_success: + - | + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then + cargo doc --no-deps && + echo "" > target/doc/index.html && + git clone https://github.com/davisp/ghp-import.git && + ./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc && + echo "Uploaded documentation" + fi + + - | + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) + USE_SKEPTIC=1 cargo tarpaulin --out Xml + bash <(curl -s https://codecov.io/bash) + echo "Uploaded code coverage" + fi diff --git a/Cargo.toml b/Cargo.toml index 3fe330e44..ddef1c8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ default = ["web"] web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -actix = "^0.4.3" +actix = "^0.4.5" log = "0.4" backoff = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 29146b9d9..a5b40f4fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ extern crate trust_dns_resolver; mod redis; mod connect; -pub use redis::RedisActor; +pub use redis::{Command, RedisActor}; pub use connect::TcpConnector; #[cfg(feature="web")] @@ -56,3 +56,7 @@ impl From for Error { Error::Redis(err) } } + +// re-export +pub use redis_async::resp::RespValue; +pub use redis_async::error::Error as RespError; diff --git a/src/redis.rs b/src/redis.rs index a369dfacd..ddaebdd95 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -14,7 +14,7 @@ use redis_async::resp::{RespCodec, RespValue}; use Error; use connect::TcpConnector; -#[derive(Message)] +#[derive(Message, Debug)] #[rtype(RespValue, Error)] pub struct Command(pub RespValue); diff --git a/tests/test_redis.rs b/tests/test_redis.rs new file mode 100644 index 000000000..2123c7e1b --- /dev/null +++ b/tests/test_redis.rs @@ -0,0 +1,68 @@ +extern crate actix; +extern crate actix_redis; +#[macro_use] +extern crate redis_async; +extern crate futures; +extern crate env_logger; + +use actix::prelude::*; +use actix_redis::{RedisActor, Command, Error, RespValue}; +use futures::Future; + +#[test] +fn test_error_connect() { + let sys = System::new("test"); + + let addr = RedisActor::start("localhost:54000"); + let _addr2 = addr.clone(); + + Arbiter::handle().spawn_fn(move || { + addr.call_fut(Command(resp_array!["GET", "test"])) + .then(|res| { + match res { + Ok(Err(Error::NotConnected)) => (), + _ => panic!("Should not happen {:?}", res), + } + Arbiter::system().send(actix::msgs::SystemExit(0)); + Ok(()) + }) + }); + + sys.run(); +} + + +#[test] +fn test_redis() { + env_logger::init(); + let sys = System::new("test"); + + let addr = RedisActor::start("127.0.0.1:6379"); + let _addr2 = addr.clone(); + + Arbiter::handle().spawn_fn(move || { + let addr2 = addr.clone(); + addr.call_fut(Command(resp_array!["SET", "test", "value"])) + .then(move |res| match res { + Ok(Ok(resp)) => { + assert_eq!(resp, RespValue::SimpleString("OK".to_owned())); + addr2.call_fut(Command(resp_array!["GET", "test"])) + .then(|res| { + match res { + Ok(Ok(resp)) => { + println!("RESP: {:?}", resp); + assert_eq!( + resp, RespValue::BulkString((&b"value"[..]).into())); + }, + _ => panic!("Should not happen {:?}", res), + } + Arbiter::system().send(actix::msgs::SystemExit(0)); + Ok(()) + }) + }, + _ => panic!("Should not happen {:?}", res), + }) + }); + + sys.run(); +} From ae292eae6d6b19da8c92652d4aa102c4cbac9d7c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 22:37:38 -0800 Subject: [PATCH 16/84] actix ver --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ddef1c8c6..8b64f7b24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ default = ["web"] web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -actix = "^0.4.5" +actix = "^0.4.4" log = "0.4" backoff = "0.1" From 0b9821b36c874596cf2bb213b765053be7774a05 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 22:46:39 -0800 Subject: [PATCH 17/84] remove chat check --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 82d8b9f17..92a03fefa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,6 @@ script: USE_SKEPTIC=1 cargo test else cargo test - cd examples/chat && cargo check && cd ../.. fi - | if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then From 1d23c14676e4a7d18c5d857d8bb26c3e147e2c41 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 22:50:04 -0800 Subject: [PATCH 18/84] update readme --- Cargo.toml | 2 +- README.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8b64f7b24..93d6231da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "actix-redis" version = "0.1.0" authors = ["Nikolay Kim "] -description = "Redis integration for actix web framework" +description = "Redis integration for actix framework" license = "MIT/Apache-2.0" readme = "README.md" keywords = ["http", "web", "framework", "async", "actix"] diff --git a/README.md b/README.md index c2d500259..9e0a7b7fe 100644 --- a/README.md +++ b/README.md @@ -41,3 +41,18 @@ fn main() { let _ = sys.run(); } ``` + +## License + +This project is licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) + +at your option. + +## Code of Conduct + +Contribution to the actix-web crate is organized under the terms of the +Contributor Covenant, the maintainer of actix-redis, @fafhrd91, promises to +intervene to uphold that code of conduct. From 4c603179263e5a49f0614fbe390d85fedcdc09ad Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 23:13:18 -0800 Subject: [PATCH 19/84] add doc links --- .travis.yml | 2 +- Cargo.toml | 3 ++- README.md | 11 ++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 92a03fefa..ec1451fdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.20.0 + - 1.21.0 - stable - beta - nightly diff --git a/Cargo.toml b/Cargo.toml index 93d6231da..59a0268f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ default = ["web"] web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -actix = "^0.4.4" +#actix = "^0.4.4" +actix = { git = 'https://github.com/actix/actix.git' } log = "0.4" backoff = "0.1" diff --git a/README.md b/README.md index 9e0a7b7fe..a73e3c24e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# Actix redis +# Redis integration for actix framework + +## Documentation + +* [API Documentation (Development)](http://actix.github.io/actix-redis/actix_redis/) +* [API Documentation (Releases)](https://docs.rs/actix-redis/) +* [Chat on gitter](https://gitter.im/actix/actix) +* Cargo package: [actix-redis](https://crates.io/crates/actix-redis) +* Minimum supported Rust version: 1.21 or later + ## Redis session backend From 06e813cf03cba485317d8db234d2dedc62b9fbf7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 23:14:04 -0800 Subject: [PATCH 20/84] fix crate name in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a73e3c24e..426fa36e2 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,6 @@ at your option. ## Code of Conduct -Contribution to the actix-web crate is organized under the terms of the +Contribution to the actix-redis crate is organized under the terms of the Contributor Covenant, the maintainer of actix-redis, @fafhrd91, promises to intervene to uphold that code of conduct. From cc314cee7667ef07fba2c3ffe976a51235df7918 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 23:16:24 -0800 Subject: [PATCH 21/84] add badges --- Cargo.toml | 4 ++++ README.md | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 59a0268f1..d7dce37c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,10 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] name = "actix_redis" path = "src/lib.rs" +[badges] +travis-ci = { repository = "actix/actix-redis", branch = "master" } +codecov = { repository = "actix/actix-redis", branch = "master", service = "github" } + [features] default = ["web"] diff --git a/README.md b/README.md index 426fa36e2..3ee05dcb4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# Redis integration for actix framework +# Actix redis [![Build Status](https://travis-ci.org/actix/actix-redis.svg?branch=master)](https://travis-ci.org/actix/actix-redis) [![codecov](https://codecov.io/gh/actix/actix-redis/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-redis) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-redis) + +Redis integration for actix framework. ## Documentation From 2e481e5b7d9cbab55e1a7f11d79e6f46e7509713 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 22 Jan 2018 23:17:37 -0800 Subject: [PATCH 22/84] fix crate link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ee05dcb4..d7e972459 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Actix redis [![Build Status](https://travis-ci.org/actix/actix-redis.svg?branch=master)](https://travis-ci.org/actix/actix-redis) [![codecov](https://codecov.io/gh/actix/actix-redis/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-redis) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-redis) +# Actix redis [![Build Status](https://travis-ci.org/actix/actix-redis.svg?branch=master)](https://travis-ci.org/actix/actix-redis) [![codecov](https://codecov.io/gh/actix/actix-redis/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-redis) [![crates.io](http://meritbadge.herokuapp.com/actix-redis)](https://crates.io/crates/actix-redis) Redis integration for actix framework. From 43032995689f5d8c38c62920f05d9d96464aa968 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Jan 2018 09:26:52 -0800 Subject: [PATCH 23/84] use actix release --- Cargo.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d7dce37c4..99a27ac30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,7 @@ default = ["web"] web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -#actix = "^0.4.4" -actix = { git = 'https://github.com/actix/actix.git' } +actix = "^0.4.5" log = "0.4" backoff = "0.1" @@ -49,6 +48,3 @@ serde_json = { version="1.0", optional=true } [dev-dependencies] env_logger = "0.5" - -[patch.crates-io] -"actix" = { git = 'https://github.com/actix/actix.git' } From 088132fc969fd8720b2b86f26874ddaac37a7ce9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 23 Jan 2018 10:06:43 -0800 Subject: [PATCH 24/84] prepare release --- CHANGES.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 000000000..8a6842cda --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## 0.1.0 (2018-01-23) + +* First release diff --git a/Cargo.toml b/Cargo.toml index 99a27ac30..3527e61a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" readme = "README.md" -keywords = ["http", "web", "framework", "async", "actix"] +keywords = ["web", "redis", "async", "actix", "tokio"] homepage = "https://github.com/actix/actix-redis" repository = "https://github.com/actix/actix-redis.git" documentation = "https://docs.rs/actix-redis/" From f11752437c5b57c3b6d68504b49913bc068a4bd3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 15 Feb 2018 16:53:05 -0800 Subject: [PATCH 25/84] use latest actix api --- Cargo.toml | 7 +- src/connect.rs | 174 -------------------------------------------- src/lib.rs | 4 - src/redis.rs | 82 ++++++++++++++------- src/session.rs | 7 +- tests/test_redis.rs | 10 +-- 6 files changed, 67 insertions(+), 217 deletions(-) delete mode 100644 src/connect.rs diff --git a/Cargo.toml b/Cargo.toml index 3527e61a9..c92c93d27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,8 @@ default = ["web"] web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -actix = "^0.4.5" +#actix = "^0.4.5" +actix = { git = "https://github.com/actix/actix.git" } log = "0.4" backoff = "0.1" @@ -36,10 +37,10 @@ futures = "0.1" tokio-io = "0.1" tokio-core = "0.1" redis-async = "0.0" -trust-dns-resolver = "0.7" # actix web session -actix-web = { version="0.3", optional=true } +# actix-web = { version="0.3", optional=true } +actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.3", optional=true } diff --git a/src/connect.rs b/src/connect.rs deleted file mode 100644 index f2c1d052b..000000000 --- a/src/connect.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::io; -use std::net::SocketAddr; -use std::collections::VecDeque; -use std::time::Duration; - -use actix::Arbiter; -use trust_dns_resolver::ResolverFuture; -use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; -use trust_dns_resolver::lookup_ip::LookupIpFuture; -use futures::{Async, Future, Poll}; -use tokio_core::reactor::Timeout; -use tokio_core::net::{TcpStream, TcpStreamNew}; - - -#[derive(Fail, Debug)] -pub enum TcpConnectorError { - /// Failed to resolve the hostname - #[fail(display = "Failed resolving hostname: {}", _0)] - Dns(String), - - /// Address is invalid - #[fail(display = "Invalid input: {}", _0)] - InvalidInput(&'static str), - - /// Connecting took too long - #[fail(display = "Timeout out while establishing connection")] - Timeout, - - /// Connection io error - #[fail(display = "{}", _0)] - IoError(io::Error), -} - -pub struct TcpConnector { - lookup: Option, - port: u16, - ips: VecDeque, - error: Option, - timeout: Timeout, - stream: Option, -} - -impl TcpConnector { - - pub fn new>(addr: S) -> TcpConnector { - TcpConnector::with_timeout(addr, Duration::from_secs(1)) - } - - pub fn with_timeout>(addr: S, timeout: Duration) -> TcpConnector { - // try to parse as a regular SocketAddr first - if let Ok(addr) = addr.as_ref().parse() { - let mut ips = VecDeque::new(); - ips.push_back(addr); - - TcpConnector { - lookup: None, - port: 0, - ips: ips, - error: None, - stream: None, - timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() } - } else { - match TcpConnector::parse(addr.as_ref()) { - Ok((host, port)) => { - // we need to do dns resolution - let resolve = match ResolverFuture::from_system_conf(Arbiter::handle()) { - Ok(resolve) => resolve, - Err(err) => { - warn!("Can not create system dns resolver: {}", err); - ResolverFuture::new( - ResolverConfig::default(), - ResolverOpts::default(), - Arbiter::handle()) - } - }; - - TcpConnector { - lookup: Some(resolve.lookup_ip(host)), - port: port, - ips: VecDeque::new(), - error: None, - stream: None, - timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() } - }, - Err(err) => - TcpConnector { - lookup: None, - port: 0, - ips: VecDeque::new(), - error: Some(err), - stream: None, - timeout: Timeout::new(timeout, Arbiter::handle()).unwrap() }, - } - } - } - - fn parse(addr: &str) -> Result<(&str, u16), TcpConnectorError> { - macro_rules! try_opt { - ($e:expr, $msg:expr) => ( - match $e { - Some(r) => r, - None => return Err(TcpConnectorError::InvalidInput($msg)), - } - ) - } - - // split the string by ':' and convert the second part to u16 - let mut parts_iter = addr.rsplitn(2, ':'); - let port_str = try_opt!(parts_iter.next(), "invalid socket address"); - let host = try_opt!(parts_iter.next(), "invalid socket address"); - let port: u16 = try_opt!(port_str.parse().ok(), "invalid port value"); - - Ok((host, port)) - } -} - -impl Future for TcpConnector { - type Item = TcpStream; - type Error = TcpConnectorError; - - fn poll(&mut self) -> Poll { - if let Some(err) = self.error.take() { - Err(err) - } else { - // timeout - if let Ok(Async::Ready(_)) = self.timeout.poll() { - return Err(TcpConnectorError::Timeout) - } - - // lookip ips - if let Some(mut lookup) = self.lookup.take() { - match lookup.poll() { - Ok(Async::NotReady) => { - self.lookup = Some(lookup); - return Ok(Async::NotReady) - }, - Ok(Async::Ready(ips)) => { - let port = self.port; - let ips = ips.iter().map(|ip| SocketAddr::new(ip, port)); - self.ips.extend(ips); - if self.ips.is_empty() { - return Err(TcpConnectorError::Dns( - "Expect at least one A dns record".to_owned())) - } - }, - Err(err) => return Err(TcpConnectorError::Dns(format!("{}", err))), - } - } - - // connect - loop { - if let Some(mut new) = self.stream.take() { - match new.poll() { - Ok(Async::Ready(sock)) => - return Ok(Async::Ready(sock)), - Ok(Async::NotReady) => { - self.stream = Some(new); - return Ok(Async::NotReady) - }, - Err(err) => { - if self.ips.is_empty() { - return Err(TcpConnectorError::IoError(err)) - } - } - } - } - - // try to connect - let addr = self.ips.pop_front().unwrap(); - self.stream = Some(TcpStream::connect(&addr, Arbiter::handle())); - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs index a5b40f4fa..3df10fbda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,9 @@ extern crate log; extern crate redis_async; #[macro_use] extern crate failure; -extern crate trust_dns_resolver; mod redis; -mod connect; - pub use redis::{Command, RedisActor}; -pub use connect::TcpConnector; #[cfg(feature="web")] extern crate actix_web; diff --git a/src/redis.rs b/src/redis.rs index ddaebdd95..ac6efab81 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -2,32 +2,38 @@ use std::io; use std::collections::VecDeque; use actix::prelude::*; +use actix::actors::{Connect, Connector}; use backoff::ExponentialBackoff; use backoff::backoff::Backoff; use futures::Future; use futures::unsync::oneshot; use tokio_io::AsyncRead; +use tokio_io::io::WriteHalf; +use tokio_io::codec::FramedRead; use tokio_core::net::TcpStream; use redis_async::error::Error as RespError; use redis_async::resp::{RespCodec, RespValue}; use Error; -use connect::TcpConnector; -#[derive(Message, Debug)] -#[rtype(RespValue, Error)] + +#[derive(Debug)] pub struct Command(pub RespValue); +impl Message for Command { + type Result = Result; +} + /// Redis comminucation actor pub struct RedisActor { addr: String, backoff: ExponentialBackoff, - cell: Option>, + cell: Option, RespCodec>>, queue: VecDeque>>, } impl RedisActor { - pub fn start>(addr: S) -> Address { + pub fn start>(addr: S) -> Addr { let addr = addr.into(); Supervisor::start(|_| { @@ -43,16 +49,36 @@ impl Actor for RedisActor { type Context = Context; fn started(&mut self, ctx: &mut Context) { - TcpConnector::new(self.addr.as_str()) + Connector::from_registry().send(Connect::host(self.addr.as_str())) .into_actor(self) - .map(|stream, act, ctx| { - info!("Connected to redis server: {}", act.addr); - act.backoff.reset(); - act.cell = Some(act.add_framed(stream.framed(RespCodec), ctx)); + .map(|res, act, ctx| match res { + Ok(stream) => { + info!("Connected to redis server: {}", act.addr); + + let (r, w) = stream.split(); + + // configure write side of the connection + let mut framed = actix::io::FramedWrite::new(w, RespCodec, ctx); + act.cell = Some(framed); + + // read side of the connection + ctx.add_stream(FramedRead::new(r, RespCodec)); + + act.backoff.reset(); + }, + Err(err) => { + error!("Can not connect to redis server: {}", err); + // re-connect with backoff time. + // we stop currect context, supervisor will restart it. + if let Some(timeout) = act.backoff.next_backoff() { + ctx.run_later(timeout, |_, ctx| ctx.stop()); + } else { + ctx.stop(); + } + } }) .map_err(|err, act, ctx| { error!("Can not connect to redis server: {}", err); - debug!("{:?}", err); // re-connect with backoff time. // we stop currect context, supervisor will restart it. if let Some(timeout) = act.backoff.next_backoff() { @@ -74,40 +100,42 @@ impl Supervised for RedisActor { } } -impl FramedActor for RedisActor { - type Io = TcpStream; - type Codec = RespCodec; +impl actix::io::WriteHandler for RedisActor { - fn closed(&mut self, error: Option, _: &mut Self::Context) { - if let Some(err) = error { - warn!("Redis connection dropped: {} error: {}", self.addr, err); - } else { - warn!("Redis connection dropped: {}", self.addr); + fn error(&mut self, err: io::Error, _: &mut Self::Context) -> Running { + warn!("Redis connection dropped: {} error: {}", self.addr, err); + Running::Stop + } +} + +impl StreamHandler for RedisActor { + + fn error(&mut self, err: RespError, _: &mut Self::Context) -> Running { + if let Some(tx) = self.queue.pop_front() { + let _ = tx.send(Err(err.into())); } + Running::Stop } - fn handle(&mut self, msg: Result, _ctx: &mut Self::Context) { + fn handle(&mut self, msg: RespValue, _: &mut Self::Context) { if let Some(tx) = self.queue.pop_front() { - let _ = tx.send(msg.map_err(|e| e.into())); + let _ = tx.send(Ok(msg)); } } } impl Handler for RedisActor { - type Result = ResponseFuture; + type Result = ResponseFuture; fn handle(&mut self, msg: Command, _: &mut Self::Context) -> Self::Result { let (tx, rx) = oneshot::channel(); if let Some(ref mut cell) = self.cell { self.queue.push_back(tx); - cell.send(msg.0); + cell.write(msg.0); } else { let _ = tx.send(Err(Error::NotConnected)); } - Box::new( - rx.map_err(|_| Error::Disconnected) - .and_then(|res| res) - .actfuture()) + Box::new(rx.map_err(|_| Error::Disconnected).and_then(|res| res)) } } diff --git a/src/session.rs b/src/session.rs index 38466c169..efc1733c9 100644 --- a/src/session.rs +++ b/src/session.rs @@ -121,7 +121,7 @@ struct Inner { key: Key, ttl: String, name: String, - addr: Address, + addr: Addr, } impl Inner { @@ -137,7 +137,7 @@ impl Inner { 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()])) + self.addr.send(Command(resp_array!["GET", cookie.value()])) .map_err(Error::from) .and_then(move |res| match res { Ok(val) => { @@ -194,8 +194,7 @@ impl Inner { match serde_json::to_string(state) { Err(e) => Either::A(FutErr(e.into())), Ok(body) => Either::B( - self.addr.call_fut( - Command(resp_array!["SET", value, body,"EX", &self.ttl])) + self.addr.send(Command(resp_array!["SET", value, body,"EX", &self.ttl])) .map_err(Error::from) .and_then(move |res| match res { Ok(_) => { diff --git a/tests/test_redis.rs b/tests/test_redis.rs index 2123c7e1b..f19cfcec5 100644 --- a/tests/test_redis.rs +++ b/tests/test_redis.rs @@ -17,13 +17,13 @@ fn test_error_connect() { let _addr2 = addr.clone(); Arbiter::handle().spawn_fn(move || { - addr.call_fut(Command(resp_array!["GET", "test"])) + addr.send(Command(resp_array!["GET", "test"])) .then(|res| { match res { Ok(Err(Error::NotConnected)) => (), _ => panic!("Should not happen {:?}", res), } - Arbiter::system().send(actix::msgs::SystemExit(0)); + Arbiter::system().do_send(actix::msgs::SystemExit(0)); Ok(()) }) }); @@ -42,11 +42,11 @@ fn test_redis() { Arbiter::handle().spawn_fn(move || { let addr2 = addr.clone(); - addr.call_fut(Command(resp_array!["SET", "test", "value"])) + addr.send(Command(resp_array!["SET", "test", "value"])) .then(move |res| match res { Ok(Ok(resp)) => { assert_eq!(resp, RespValue::SimpleString("OK".to_owned())); - addr2.call_fut(Command(resp_array!["GET", "test"])) + addr2.send(Command(resp_array!["GET", "test"])) .then(|res| { match res { Ok(Ok(resp)) => { @@ -56,7 +56,7 @@ fn test_redis() { }, _ => panic!("Should not happen {:?}", res), } - Arbiter::system().send(actix::msgs::SystemExit(0)); + Arbiter::system().do_send(actix::msgs::SystemExit(0)); Ok(()) }) }, From 2555c3442754d648c36f0eb32b45f7b87d43529e Mon Sep 17 00:00:00 2001 From: Alexander Andreev Date: Sat, 17 Feb 2018 10:59:21 +0300 Subject: [PATCH 26/84] spelling check --- src/redis.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/redis.rs b/src/redis.rs index ac6efab81..8667cb816 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -69,7 +69,7 @@ impl Actor for RedisActor { Err(err) => { error!("Can not connect to redis server: {}", err); // re-connect with backoff time. - // we stop currect context, supervisor will restart it. + // we stop current context, supervisor will restart it. if let Some(timeout) = act.backoff.next_backoff() { ctx.run_later(timeout, |_, ctx| ctx.stop()); } else { @@ -80,7 +80,7 @@ impl Actor for RedisActor { .map_err(|err, act, ctx| { error!("Can not connect to redis server: {}", err); // re-connect with backoff time. - // we stop currect context, supervisor will restart it. + // we stop current context, supervisor will restart it. if let Some(timeout) = act.backoff.next_backoff() { ctx.run_later(timeout, |_, ctx| ctx.stop()); } else { From 14cfcb7e8771689ed14495b8c1160efac807d701 Mon Sep 17 00:00:00 2001 From: Alexander Andreev Date: Sat, 17 Feb 2018 21:37:30 +0300 Subject: [PATCH 27/84] added some documentation --- src/lib.rs | 11 ++++++++++- src/redis.rs | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3df10fbda..cbcd9f502 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,12 @@ +//! Redis integration for Actix framework. +//! +//! ## Documentation +//! * [API Documentation (Development)](http://actix.github.io/actix-redis/actix_redis/) +//! * [API Documentation (Releases)](https://docs.rs/actix-redis/) +//! * [Chat on gitter](https://gitter.im/actix/actix) +//! * Cargo package: [actix-redis](https://crates.io/crates/actix-redis) +//! * Minimum supported Rust version: 1.21 or later +//! extern crate actix; extern crate backoff; extern crate futures; @@ -31,7 +40,7 @@ mod session; #[cfg(feature="web")] pub use session::RedisSessionBackend; - +/// General purpose actix redis error #[derive(Fail, Debug)] pub enum Error { #[fail(display="Redis error {}", _0)] diff --git a/src/redis.rs b/src/redis.rs index 8667cb816..0289e84ce 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -17,6 +17,7 @@ use redis_async::resp::{RespCodec, RespValue}; use Error; +/// Command for send data to Redis #[derive(Debug)] pub struct Command(pub RespValue); @@ -33,6 +34,7 @@ pub struct RedisActor { } impl RedisActor { + /// Start new `Supervisor` with `RedisActor`. pub fn start>(addr: S) -> Addr { let addr = addr.into(); From 2b3b5c2f2b2ceacfa2d24052344e5e5fa6f4e973 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 17 Feb 2018 14:01:06 -0800 Subject: [PATCH 28/84] actix 0.5 --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c92c93d27..c2ea592b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,7 @@ default = ["web"] web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -#actix = "^0.4.5" -actix = { git = "https://github.com/actix/actix.git" } +actix = "0.5" log = "0.4" backoff = "0.1" @@ -39,7 +38,7 @@ tokio-core = "0.1" redis-async = "0.0" # actix web session -# actix-web = { version="0.3", optional=true } +# actix-web = { version="0.4", optional=true } actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } From 08f80a5bff37e23623ed0eb6e8fdfef31d0c49bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 28 Feb 2018 08:05:44 -0800 Subject: [PATCH 29/84] prepare release --- CHANGES.md | 6 ++++++ Cargo.toml | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8a6842cda..2d1f35eea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## 0.2.0 (2018-02-28) + +* Use resolver actor from actix + +* Use actix web 0.5 + ## 0.1.0 (2018-01-23) * First release diff --git a/Cargo.toml b/Cargo.toml index c2ea592b7..10a045739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.1.0" +version = "0.2.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -38,8 +38,8 @@ tokio-core = "0.1" redis-async = "0.0" # actix web session -# actix-web = { version="0.4", optional=true } -actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } +actix-web = { version="0.4", optional=true } +# actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.3", optional=true } From f1e5126a1d24d64980e7fade1f208104b6eb78e8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 9 Apr 2018 19:35:30 -0700 Subject: [PATCH 30/84] use actix-web master --- Cargo.toml | 4 ++-- examples/basic.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 10a045739..baef790dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,8 @@ tokio-core = "0.1" redis-async = "0.0" # actix web session -actix-web = { version="0.4", optional=true } -# actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } +# actix-web = { version="0.4", optional=true } +actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.3", optional=true } diff --git a/examples/basic.rs b/examples/basic.rs index e7e5874bd..87e0049c8 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -7,7 +7,7 @@ extern crate actix_redis; extern crate env_logger; extern crate futures; -use actix_web::*; +use actix_web::{server, middleware, App, HttpRequest, HttpResponse, Result}; use actix_web::middleware::RequestSession; use actix_redis::RedisSessionBackend; @@ -32,8 +32,8 @@ fn main() { env_logger::init(); let sys = actix::System::new("basic-example"); - HttpServer::new( - || Application::new() + server::new( + || App::new() // enable logger .middleware(middleware::Logger::default()) // cookie session middleware From f77a4f8865910a9a2a91cfccbcbfe025bd0b6b45 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 12:19:41 -0700 Subject: [PATCH 31/84] prepare release --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index baef790dc..3c37af72e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.2.0" +version = "0.3.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -38,8 +38,7 @@ tokio-core = "0.1" redis-async = "0.0" # actix web session -# actix-web = { version="0.4", optional=true } -actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } +actix-web = { version="0.5", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.3", optional=true } From af3a8d6775f65743ecbeb3519bbadd14fd31ac46 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 12:20:24 -0700 Subject: [PATCH 32/84] changes --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2d1f35eea..907703d35 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.3.0 (2018-04-10) + +* Actix web 0.5 compatibility + ## 0.2.0 (2018-02-28) * Use resolver actor from actix From 2a7ffe6e0a88d50fb2c9ed3b39c92a1609100d14 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 10 Apr 2018 12:22:19 -0700 Subject: [PATCH 33/84] update readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d7e972459..5ed02fbad 100644 --- a/README.md +++ b/README.md @@ -27,19 +27,19 @@ Constructor panics if key length is less than 32 bytes. extern crate actix_web; extern crate actix_redis; -use actix_web::*; -use actix_web::middleware::SessionStorage; +use actix_web::{App, server}; +use actix_web::middleware::{Logger, SessionStorage}; use actix_redis::RedisSessionBackend; fn main() { ::std::env::set_var("RUST_LOG", "actix_web=info"); - let _ = env_logger::init(); + env_logger::init(); let sys = actix::System::new("basic-example"); - HttpServer::new( - || Application::new() + server::new( + || App::new() // enable logger - .middleware(middleware::Logger::default()) + .middleware(Logger::default()) // cookie session middleware .middleware(SessionStorage::new( RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) From 7af09390ee429d3fb766d6154075e8225fa8df08 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 10:12:57 -0700 Subject: [PATCH 34/84] actix-web 0.6 compatibility --- CHANGES.md | 4 ++ Cargo.toml | 4 +- README.md | 10 +-- examples/basic.rs | 20 +++--- rustfmt.toml | 5 ++ src/lib.rs | 30 ++++----- src/redis.rs | 44 +++++++------ src/session.rs | 154 +++++++++++++++++++++++++------------------- tests/test_redis.rs | 16 +++-- 9 files changed, 161 insertions(+), 126 deletions(-) create mode 100644 rustfmt.toml diff --git a/CHANGES.md b/CHANGES.md index 907703d35..153f19be1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.4.0 (2018-05-08) + +* Actix web 0.6 compatibility + ## 0.3.0 (2018-04-10) * Actix web 0.5 compatibility diff --git a/Cargo.toml b/Cargo.toml index 3c37af72e..c12075685 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.3.0" +version = "0.4.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -38,7 +38,7 @@ tokio-core = "0.1" redis-async = "0.0" # actix web session -actix-web = { version="0.5", optional=true } +actix-web = { version="0.6", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.3", optional=true } diff --git a/README.md b/README.md index 5ed02fbad..4fe12b66c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Redis integration for actix framework. * [API Documentation (Releases)](https://docs.rs/actix-redis/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-redis](https://crates.io/crates/actix-redis) -* Minimum supported Rust version: 1.21 or later +* Minimum supported Rust version: 1.24 or later ## Redis session backend @@ -23,12 +23,12 @@ Note that whatever you write into your session is visible by the user (but not m Constructor panics if key length is less than 32 bytes. -```rust,ignore +```rust extern crate actix_web; extern crate actix_redis; -use actix_web::{App, server}; -use actix_web::middleware::{Logger, SessionStorage}; +use actix_web::{App, server, middleware}; +use actix_web::middleware::session::SessionStorage; use actix_redis::RedisSessionBackend; fn main() { @@ -39,7 +39,7 @@ fn main() { server::new( || App::new() // enable logger - .middleware(Logger::default()) + .middleware(middleware::Logger::default()) // cookie session middleware .middleware(SessionStorage::new( RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) diff --git a/examples/basic.rs b/examples/basic.rs index 87e0049c8..ef31724a4 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,16 +1,15 @@ #![allow(unused_variables)] -#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))] +#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] extern crate actix; -extern crate actix_web; extern crate actix_redis; +extern crate actix_web; extern crate env_logger; extern crate futures; -use actix_web::{server, middleware, App, HttpRequest, HttpResponse, Result}; -use actix_web::middleware::RequestSession; use actix_redis::RedisSessionBackend; - +use actix_web::middleware::RequestSession; +use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Result}; /// simple handler fn index(mut req: HttpRequest) -> Result { @@ -19,7 +18,7 @@ fn index(mut req: HttpRequest) -> Result { // session if let Some(count) = req.session().get::("counter")? { println!("SESSION value: {}", count); - req.session().set("counter", count+1)?; + req.session().set("counter", count + 1)?; } else { req.session().set("counter", 1)?; } @@ -32,8 +31,8 @@ fn main() { env_logger::init(); let sys = actix::System::new("basic-example"); - server::new( - || App::new() + server::new(|| { + App::new() // enable logger .middleware(middleware::Logger::default()) // cookie session middleware @@ -41,8 +40,9 @@ fn main() { RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) )) // register simple route, handle all methods - .resource("/", |r| r.f(index))) - .bind("0.0.0.0:8080").unwrap() + .resource("/", |r| r.f(index)) + }).bind("0.0.0.0:8080") + .unwrap() .threads(1) .start(); diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..6db67ed28 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,5 @@ +max_width = 89 +reorder_imports = true +wrap_comments = true +fn_args_density = "Compressed" +#use_small_heuristics = false diff --git a/src/lib.rs b/src/lib.rs index cbcd9f502..71a2c86a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,12 +6,12 @@ //! * [Chat on gitter](https://gitter.im/actix/actix) //! * Cargo package: [actix-redis](https://crates.io/crates/actix-redis) //! * Minimum supported Rust version: 1.21 or later -//! +//! extern crate actix; extern crate backoff; extern crate futures; -extern crate tokio_io; extern crate tokio_core; +extern crate tokio_io; #[macro_use] extern crate log; #[macro_use] @@ -22,34 +22,34 @@ extern crate failure; mod redis; pub use redis::{Command, RedisActor}; -#[cfg(feature="web")] +#[cfg(feature = "web")] extern crate actix_web; -#[cfg(feature="web")] +#[cfg(feature = "web")] extern crate cookie; -#[cfg(feature="web")] -extern crate rand; -#[cfg(feature="web")] +#[cfg(feature = "web")] extern crate http; -#[cfg(feature="web")] +#[cfg(feature = "web")] +extern crate rand; +#[cfg(feature = "web")] extern crate serde; -#[cfg(feature="web")] +#[cfg(feature = "web")] extern crate serde_json; -#[cfg(feature="web")] +#[cfg(feature = "web")] mod session; -#[cfg(feature="web")] +#[cfg(feature = "web")] pub use session::RedisSessionBackend; /// General purpose actix redis error #[derive(Fail, Debug)] pub enum Error { - #[fail(display="Redis error {}", _0)] + #[fail(display = "Redis error {}", _0)] Redis(redis_async::error::Error), /// Receiving message during reconnecting - #[fail(display="Redis: Not connected")] + #[fail(display = "Redis: Not connected")] NotConnected, /// Cancel all waters when connection get dropped - #[fail(display="Redis: Disconnected")] + #[fail(display = "Redis: Disconnected")] Disconnected, } @@ -63,5 +63,5 @@ impl From for Error { } // re-export -pub use redis_async::resp::RespValue; pub use redis_async::error::Error as RespError; +pub use redis_async::resp::RespValue; diff --git a/src/redis.rs b/src/redis.rs index 0289e84ce..2fd6ab8c4 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -1,22 +1,21 @@ -use std::io; use std::collections::VecDeque; +use std::io; -use actix::prelude::*; use actix::actors::{Connect, Connector}; -use backoff::ExponentialBackoff; +use actix::prelude::*; use backoff::backoff::Backoff; -use futures::Future; +use backoff::ExponentialBackoff; use futures::unsync::oneshot; -use tokio_io::AsyncRead; -use tokio_io::io::WriteHalf; -use tokio_io::codec::FramedRead; -use tokio_core::net::TcpStream; +use futures::Future; use redis_async::error::Error as RespError; use redis_async::resp::{RespCodec, RespValue}; +use tokio_core::net::TcpStream; +use tokio_io::codec::FramedRead; +use tokio_io::io::WriteHalf; +use tokio_io::AsyncRead; use Error; - /// Command for send data to Redis #[derive(Debug)] pub struct Command(pub RespValue); @@ -38,11 +37,11 @@ impl RedisActor { pub fn start>(addr: S) -> Addr { let addr = addr.into(); - Supervisor::start(|_| { - RedisActor { addr: addr, - cell: None, - backoff: ExponentialBackoff::default(), - queue: VecDeque::new() } + Supervisor::start(|_| RedisActor { + addr: addr, + cell: None, + backoff: ExponentialBackoff::default(), + queue: VecDeque::new(), }) } } @@ -51,7 +50,8 @@ impl Actor for RedisActor { type Context = Context; fn started(&mut self, ctx: &mut Context) { - Connector::from_registry().send(Connect::host(self.addr.as_str())) + Connector::from_registry() + .send(Connect::host(self.addr.as_str())) .into_actor(self) .map(|res, act, ctx| match res { Ok(stream) => { @@ -67,7 +67,7 @@ impl Actor for RedisActor { ctx.add_stream(FramedRead::new(r, RespCodec)); act.backoff.reset(); - }, + } Err(err) => { error!("Can not connect to redis server: {}", err); // re-connect with backoff time. @@ -103,15 +103,16 @@ impl Supervised for RedisActor { } impl actix::io::WriteHandler for RedisActor { - fn error(&mut self, err: io::Error, _: &mut Self::Context) -> Running { - warn!("Redis connection dropped: {} error: {}", self.addr, err); + warn!( + "Redis connection dropped: {} error: {}", + self.addr, err + ); Running::Stop } } impl StreamHandler for RedisActor { - fn error(&mut self, err: RespError, _: &mut Self::Context) -> Running { if let Some(tx) = self.queue.pop_front() { let _ = tx.send(Err(err.into())); @@ -138,6 +139,9 @@ impl Handler for RedisActor { let _ = tx.send(Err(Error::NotConnected)); } - Box::new(rx.map_err(|_| Error::Disconnected).and_then(|res| res)) + Box::new( + rx.map_err(|_| Error::Disconnected) + .and_then(|res| res), + ) } } diff --git a/src/session.rs b/src/session.rs index efc1733c9..011f1ad58 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,21 +1,21 @@ -use std::rc::Rc; -use std::iter::FromIterator; use std::collections::HashMap; +use std::iter::FromIterator; +use std::rc::Rc; -use serde_json; -use rand::{self, Rng}; -use futures::Future; -use futures::future::{Either, ok as FutOk, err as FutErr}; -use redis_async::resp::RespValue; -use cookie::{CookieJar, Cookie, Key}; -use http::header::{self, HeaderValue}; use actix::prelude::*; -use actix_web::{error, Error, Result, HttpRequest, HttpResponse}; -use actix_web::middleware::{SessionImpl, SessionBackend, Response as MiddlewareResponse}; +use actix_web::middleware::session::{SessionBackend, SessionImpl}; +use actix_web::middleware::Response as MiddlewareResponse; +use actix_web::{error, Error, HttpRequest, HttpResponse, Result}; +use cookie::{Cookie, CookieJar, Key}; +use futures::future::{err as FutErr, ok as FutOk, Either}; +use futures::Future; +use http::header::{self, HeaderValue}; +use rand::{self, Rng}; +use redis_async::resp::RespValue; +use serde_json; use redis::{Command, RedisActor}; - /// Session that stores data in redis pub struct RedisSession { changed: bool, @@ -25,7 +25,6 @@ pub struct RedisSession { } impl SessionImpl for RedisSession { - fn get(&self, key: &str) -> Option<&str> { self.state.get(key).map(|s| s.as_str()) } @@ -47,8 +46,11 @@ impl SessionImpl for RedisSession { fn write(&self, resp: HttpResponse) -> Result { if self.changed { - Ok(MiddlewareResponse::Future( - self.inner.update(&self.state, resp, self.value.as_ref()))) + Ok(MiddlewareResponse::Future(self.inner.update( + &self.state, + resp, + self.value.as_ref(), + ))) } else { Ok(MiddlewareResponse::Done(resp)) } @@ -58,10 +60,11 @@ impl SessionImpl for RedisSession { /// 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. +/// 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). +/// 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. pub struct RedisSessionBackend(Rc); @@ -71,11 +74,12 @@ impl RedisSessionBackend { /// /// * `addr` - address of the redis server pub fn new>(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()})) + RedisSessionBackend(Rc::new(Inner { + key: Key::from_master(key), + ttl: "7200".to_owned(), + addr: RedisActor::start(addr), + name: "actix-session".to_owned(), + })) } /// Set time to live in seconds for session value @@ -92,9 +96,8 @@ impl RedisSessionBackend { } impl SessionBackend for RedisSessionBackend { - type Session = RedisSession; - type ReadFuture = Box>; + type ReadFuture = Box>; fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { let inner = Rc::clone(&self.0); @@ -105,13 +108,15 @@ impl SessionBackend for RedisSessionBackend { changed: false, inner: inner, state: state, - value: Some(value) } + value: Some(value), + } } else { RedisSession { changed: false, inner: inner, state: HashMap::new(), - value: None } + value: None, + } } })) } @@ -126,8 +131,9 @@ struct Inner { impl Inner { #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] - fn load(&self, req: &mut HttpRequest) - -> Box, String)>, Error=Error>> + fn load( + &self, req: &mut HttpRequest, + ) -> Box, String)>, Error = Error>> { if let Ok(cookies) = req.cookies() { for cookie in cookies { @@ -137,31 +143,42 @@ impl Inner { if let Some(cookie) = jar.signed(&self.key).get(&self.name) { let value = cookie.value().to_owned(); return Box::new( - self.addr.send(Command(resp_array!["GET", cookie.value()])) + self.addr + .send(Command(resp_array!["GET", cookie.value()])) .map_err(Error::from) .and_then(move |res| match res { Ok(val) => { match val { - RespValue::Error(err) => + 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))) + 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()) - })) + } + Err(err) => { + Err(error::ErrorInternalServerError(err).into()) + } + }), + ); } else { - return Box::new(FutOk(None)) + return Box::new(FutOk(None)); } } } @@ -169,10 +186,10 @@ impl Inner { Box::new(FutOk(None)) } - fn update(&self, state: &HashMap, - mut resp: HttpResponse, - value: Option<&String>) -> Box> - { + fn update( + &self, state: &HashMap, mut resp: HttpResponse, + value: Option<&String>, + ) -> Box> { let (value, jar) = if let Some(value) = value { (value.clone(), None) } else { @@ -190,25 +207,28 @@ impl Inner { (value, Some(jar)) }; - Box::new( - match serde_json::to_string(state) { - Err(e) => Either::A(FutErr(e.into())), - Ok(body) => Either::B( - self.addr.send(Command(resp_array!["SET", value, body,"EX", &self.ttl])) - .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); - } + Box::new(match serde_json::to_string(state) { + Err(e) => Either::A(FutErr(e.into())), + Ok(body) => Either::B( + self.addr + .send(Command(resp_array![ + "SET", value, body, "EX", &self.ttl + ])) + .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); } - Ok(resp) - }, - Err(err) => Err(error::ErrorInternalServerError(err).into()) - })) - }) + } + Ok(resp) + } + Err(err) => Err(error::ErrorInternalServerError(err).into()), + }), + ), + }) } } diff --git a/tests/test_redis.rs b/tests/test_redis.rs index f19cfcec5..a3d650e45 100644 --- a/tests/test_redis.rs +++ b/tests/test_redis.rs @@ -2,11 +2,11 @@ extern crate actix; extern crate actix_redis; #[macro_use] extern crate redis_async; -extern crate futures; extern crate env_logger; +extern crate futures; use actix::prelude::*; -use actix_redis::{RedisActor, Command, Error, RespValue}; +use actix_redis::{Command, Error, RedisActor, RespValue}; use futures::Future; #[test] @@ -31,7 +31,6 @@ fn test_error_connect() { sys.run(); } - #[test] fn test_redis() { env_logger::init(); @@ -46,20 +45,23 @@ fn test_redis() { .then(move |res| match res { Ok(Ok(resp)) => { assert_eq!(resp, RespValue::SimpleString("OK".to_owned())); - addr2.send(Command(resp_array!["GET", "test"])) + addr2 + .send(Command(resp_array!["GET", "test"])) .then(|res| { match res { Ok(Ok(resp)) => { println!("RESP: {:?}", resp); assert_eq!( - resp, RespValue::BulkString((&b"value"[..]).into())); - }, + resp, + RespValue::BulkString((&b"value"[..]).into()) + ); + } _ => panic!("Should not happen {:?}", res), } Arbiter::system().do_send(actix::msgs::SystemExit(0)); Ok(()) }) - }, + } _ => panic!("Should not happen {:?}", res), }) }); From 9e610a813423130cca207890a927a81da70e036f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 10:15:14 -0700 Subject: [PATCH 35/84] bump rustc min version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec1451fdb..428cd97ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.21.0 + - 1.24.0 - stable - beta - nightly From 93707884d25d73f31811a7a3a3cdfe0df51afe35 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 8 May 2018 10:48:23 -0700 Subject: [PATCH 36/84] fix example --- examples/basic.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index ef31724a4..cb76627ec 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,11 +8,11 @@ extern crate env_logger; extern crate futures; use actix_redis::RedisSessionBackend; -use actix_web::middleware::RequestSession; +use actix_web::middleware::session::{self, RequestSession}; use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Result}; /// simple handler -fn index(mut req: HttpRequest) -> Result { +fn index(req: HttpRequest) -> Result { println!("{:?}", req); // session @@ -36,14 +36,14 @@ fn main() { // enable logger .middleware(middleware::Logger::default()) // cookie session middleware - .middleware(middleware::SessionStorage::new( + .middleware(session::SessionStorage::new( RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) )) // register simple route, handle all methods .resource("/", |r| r.f(index)) }).bind("0.0.0.0:8080") .unwrap() - .threads(1) + .workers(1) .start(); let _ = sys.run(); From 3bbda68adf34fe82385a96370040b6db6f6b8e51 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sun, 27 May 2018 09:54:52 +0200 Subject: [PATCH 37/84] Adjust `ExponentialBackoff` to never stop connecting --- src/redis.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/redis.rs b/src/redis.rs index 2fd6ab8c4..c9705458a 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -37,10 +37,13 @@ impl RedisActor { pub fn start>(addr: S) -> Addr { let addr = addr.into(); + let mut backoff = ExponentialBackoff::default(); + backoff.max_elapsed_time = None; + Supervisor::start(|_| RedisActor { addr: addr, cell: None, - backoff: ExponentialBackoff::default(), + backoff: backoff, queue: VecDeque::new(), }) } From 6624af63e39c55000d4493d05eecb0f8848ab633 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sun, 27 May 2018 09:55:41 +0200 Subject: [PATCH 38/84] Keep running unconnected actor forever if `backoff` says so --- src/redis.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/redis.rs b/src/redis.rs index c9705458a..52853a433 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -77,8 +77,6 @@ impl Actor for RedisActor { // we stop current context, supervisor will restart it. if let Some(timeout) = act.backoff.next_backoff() { ctx.run_later(timeout, |_, ctx| ctx.stop()); - } else { - ctx.stop(); } } }) @@ -88,8 +86,6 @@ impl Actor for RedisActor { // we stop current context, supervisor will restart it. if let Some(timeout) = act.backoff.next_backoff() { ctx.run_later(timeout, |_, ctx| ctx.stop()); - } else { - ctx.stop(); } }) .wait(ctx); From ac93ee7cdb9a31cbd4448c3146fcff1ce40ce216 Mon Sep 17 00:00:00 2001 From: bugong <1761869346@qq.com> Date: Sat, 23 Jun 2018 12:17:37 +0800 Subject: [PATCH 39/84] custom cookie --- Cargo.toml | 1 + src/lib.rs | 1 + src/session.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c12075685..a25f2a96e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ futures = "0.1" tokio-io = "0.1" tokio-core = "0.1" redis-async = "0.0" +time = "0.1" # actix web session actix-web = { version="0.6", optional=true } diff --git a/src/lib.rs b/src/lib.rs index 71a2c86a9..52cb761f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ extern crate log; extern crate redis_async; #[macro_use] extern crate failure; +extern crate time; mod redis; pub use redis::{Command, RedisActor}; diff --git a/src/session.rs b/src/session.rs index 011f1ad58..285a7280a 100644 --- a/src/session.rs +++ b/src/session.rs @@ -6,13 +6,14 @@ use actix::prelude::*; use actix_web::middleware::session::{SessionBackend, SessionImpl}; use actix_web::middleware::Response as MiddlewareResponse; use actix_web::{error, Error, HttpRequest, HttpResponse, Result}; -use cookie::{Cookie, CookieJar, Key}; +use cookie::{Cookie, CookieJar, Key, SameSite}; use futures::future::{err as FutErr, ok as FutOk, Either}; use futures::Future; use http::header::{self, HeaderValue}; use rand::{self, Rng}; use redis_async::resp::RespValue; use serde_json; +use time::Duration; use redis::{Command, RedisActor}; @@ -79,6 +80,11 @@ impl RedisSessionBackend { ttl: "7200".to_owned(), addr: RedisActor::start(addr), name: "actix-session".to_owned(), + path: "/".to_owned(), + domain: None, + secure: false, + max_age: None, + same_site: None, })) } @@ -93,6 +99,38 @@ impl RedisSessionBackend { Rc::get_mut(&mut self.0).unwrap().name = name.to_owned(); self } + + /// Set custom cookie path + pub fn cookie_path(mut self, path: &str) -> Self { + Rc::get_mut(&mut self.0).unwrap().path = path.to_owned(); + self + } + + /// Set custom cookie domain + pub fn cookie_domain(mut self, domain: &str) -> Self { + Rc::get_mut(&mut self.0).unwrap().domain = Some(domain.to_owned()); + self + } + + /// Set custom cookie secure + /// If the `secure` field is set, a cookie will only be transmitted when the + /// connection is secure - i.e. `https` + pub fn cookie_secure(mut self, secure: bool) -> Self { + Rc::get_mut(&mut self.0).unwrap().secure = secure; + self + } + + /// Set custom cookie max-age + pub fn cookie_max_age(mut self, max_age: Duration) -> Self { + Rc::get_mut(&mut self.0).unwrap().max_age = Some(max_age); + self + } + + /// Set custom cookie SameSite + pub fn cookie_same_site(mut self, same_site: SameSite) -> Self { + Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); + self + } } impl SessionBackend for RedisSessionBackend { @@ -125,8 +163,13 @@ impl SessionBackend for RedisSessionBackend { struct Inner { key: Key, ttl: String, - name: String, addr: Addr, + name: String, + path: String, + domain: Option, + secure: bool, + max_age: Option, + same_site: Option, } impl Inner { @@ -197,9 +240,22 @@ impl Inner { 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_path(self.path.clone()); + cookie.set_secure(self.secure); cookie.set_http_only(true); + if let Some(ref domain) = self.domain { + cookie.set_domain(domain.clone()); + } + + if let Some(max_age) = self.max_age { + cookie.set_max_age(max_age); + } + + if let Some(same_site) = self.same_site { + cookie.set_same_site(same_site); + } + // set cookie let mut jar = CookieJar::new(); jar.signed(&self.key).add(cookie); From 0e2a4a97fdf8678d267b5bd38cb175ba882bcdd9 Mon Sep 17 00:00:00 2001 From: bugong <1761869346@qq.com> Date: Sat, 23 Jun 2018 16:31:00 +0800 Subject: [PATCH 40/84] set max-age default value --- src/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/session.rs b/src/session.rs index 285a7280a..77e41d69f 100644 --- a/src/session.rs +++ b/src/session.rs @@ -83,7 +83,7 @@ impl RedisSessionBackend { path: "/".to_owned(), domain: None, secure: false, - max_age: None, + max_age: Some(Duration::days(7)), same_site: None, })) } From 6c8801dc8caebe901257e8ea41e465e62db94c3f Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 19 Jul 2018 11:23:11 -0700 Subject: [PATCH 41/84] Re-export SameSite so it's immediately usable from this package... --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 52cb761f5..d062bfa89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ extern crate serde_json; mod session; #[cfg(feature = "web")] pub use session::RedisSessionBackend; +#[cfg(feature = "web")] +pub use cookie::{SameSite}; /// General purpose actix redis error #[derive(Fail, Debug)] From 62c382d28049b84071bbd27e5c58298966a493d3 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 20 Jul 2018 14:53:56 -0700 Subject: [PATCH 42/84] upgrade to actix/actix-web 0.7 --- Cargo.toml | 13 ++++++------ examples/basic.rs | 2 +- src/lib.rs | 8 +++----- src/redis.rs | 22 ++++++++------------ src/session.rs | 29 ++++++++++++++------------- tests/test_redis.rs | 49 +++++++++++++++++++++------------------------ 6 files changed, 57 insertions(+), 66 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c12075685..5e3d3afd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.4.0" +version = "0.5.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -27,21 +27,22 @@ default = ["web"] web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] [dependencies] -actix = "0.5" +actix = "0.7" log = "0.4" backoff = "0.1" failure = "^0.1.1" futures = "0.1" tokio-io = "0.1" -tokio-core = "0.1" -redis-async = "0.0" +tokio-codec = "0.1" +tokio-tcp = "0.1" +redis-async = "0.3.2" # actix web session -actix-web = { version="0.6", optional=true } +actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } -rand = { version="0.3", optional=true } +rand = { version="0.5", optional=true } serde = { version="1.0", optional=true } serde_json = { version="1.0", optional=true } diff --git a/examples/basic.rs b/examples/basic.rs index cb76627ec..4dbed88a7 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -12,7 +12,7 @@ use actix_web::middleware::session::{self, RequestSession}; use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Result}; /// simple handler -fn index(req: HttpRequest) -> Result { +fn index(req: &HttpRequest) -> Result { println!("{:?}", req); // session diff --git a/src/lib.rs b/src/lib.rs index 71a2c86a9..5a0a96547 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,13 +5,14 @@ //! * [API Documentation (Releases)](https://docs.rs/actix-redis/) //! * [Chat on gitter](https://gitter.im/actix/actix) //! * Cargo package: [actix-redis](https://crates.io/crates/actix-redis) -//! * Minimum supported Rust version: 1.21 or later +//! * Minimum supported Rust version: 1.26 or later //! extern crate actix; extern crate backoff; extern crate futures; -extern crate tokio_core; +extern crate tokio_codec; extern crate tokio_io; +extern crate tokio_tcp; #[macro_use] extern crate log; #[macro_use] @@ -53,9 +54,6 @@ pub enum Error { Disconnected, } -unsafe impl Send for Error {} -unsafe impl Sync for Error {} - impl From for Error { fn from(err: redis_async::error::Error) -> Error { Error::Redis(err) diff --git a/src/redis.rs b/src/redis.rs index 2fd6ab8c4..91fd4f5dc 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; use std::io; -use actix::actors::{Connect, Connector}; +use actix::actors::resolver::{Connect, Resolver}; use actix::prelude::*; use backoff::backoff::Backoff; use backoff::ExponentialBackoff; @@ -9,10 +9,10 @@ use futures::unsync::oneshot; use futures::Future; use redis_async::error::Error as RespError; use redis_async::resp::{RespCodec, RespValue}; -use tokio_core::net::TcpStream; -use tokio_io::codec::FramedRead; +use tokio_codec::FramedRead; use tokio_io::io::WriteHalf; use tokio_io::AsyncRead; +use tokio_tcp::TcpStream; use Error; @@ -34,11 +34,11 @@ pub struct RedisActor { impl RedisActor { /// Start new `Supervisor` with `RedisActor`. - pub fn start>(addr: S) -> Addr { + pub fn start>(addr: S) -> Addr { let addr = addr.into(); Supervisor::start(|_| RedisActor { - addr: addr, + addr, cell: None, backoff: ExponentialBackoff::default(), queue: VecDeque::new(), @@ -50,7 +50,7 @@ impl Actor for RedisActor { type Context = Context; fn started(&mut self, ctx: &mut Context) { - Connector::from_registry() + Resolver::from_registry() .send(Connect::host(self.addr.as_str())) .into_actor(self) .map(|res, act, ctx| match res { @@ -104,10 +104,7 @@ impl Supervised for RedisActor { impl actix::io::WriteHandler for RedisActor { fn error(&mut self, err: io::Error, _: &mut Self::Context) -> Running { - warn!( - "Redis connection dropped: {} error: {}", - self.addr, err - ); + warn!("Redis connection dropped: {} error: {}", self.addr, err); Running::Stop } } @@ -139,9 +136,6 @@ impl Handler for RedisActor { let _ = tx.send(Err(Error::NotConnected)); } - Box::new( - rx.map_err(|_| Error::Disconnected) - .and_then(|res| res), - ) + Box::new(rx.map_err(|_| Error::Disconnected).and_then(|res| res)) } } diff --git a/src/session.rs b/src/session.rs index 011f1ad58..91d9936c5 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::iter::FromIterator; +use std::iter; use std::rc::Rc; use actix::prelude::*; @@ -10,6 +10,7 @@ use cookie::{Cookie, CookieJar, Key}; use futures::future::{err as FutErr, ok as FutOk, Either}; use futures::Future; use http::header::{self, HeaderValue}; +use rand::distributions::Alphanumeric; use rand::{self, Rng}; use redis_async::resp::RespValue; use serde_json; @@ -105,15 +106,15 @@ impl SessionBackend for RedisSessionBackend { Box::new(self.0.load(req).map(move |state| { if let Some((state, value)) = state { RedisSession { + inner, + state, changed: false, - inner: inner, - state: state, value: Some(value), } } else { RedisSession { + inner, changed: false, - inner: inner, state: HashMap::new(), value: None, } @@ -126,7 +127,7 @@ struct Inner { key: Key, ttl: String, name: String, - addr: Addr, + addr: Addr, } impl Inner { @@ -136,7 +137,7 @@ impl Inner { ) -> Box, String)>, Error = Error>> { if let Ok(cookies) = req.cookies() { - for cookie in cookies { + for cookie in cookies.iter() { if cookie.name() == self.name { let mut jar = CookieJar::new(); jar.add_original(cookie.clone()); @@ -151,8 +152,7 @@ impl Inner { match val { RespValue::Error(err) => { return Err( - error::ErrorInternalServerError(err) - .into(), + error::ErrorInternalServerError(err), ) } RespValue::SimpleString(s) => { @@ -173,7 +173,7 @@ impl Inner { Ok(None) } Err(err) => { - Err(error::ErrorInternalServerError(err).into()) + Err(error::ErrorInternalServerError(err)) } }), ); @@ -194,7 +194,10 @@ impl Inner { (value.clone(), None) } else { let mut rng = rand::OsRng::new().unwrap(); - let value = String::from_iter(rng.gen_ascii_chars().take(32)); + let value: String = iter::repeat(()) + .map(|()| rng.sample(Alphanumeric)) + .take(32) + .collect(); let mut cookie = Cookie::new(self.name.clone(), value.clone()); cookie.set_path("/"); @@ -211,9 +214,7 @@ impl Inner { Err(e) => Either::A(FutErr(e.into())), Ok(body) => Either::B( self.addr - .send(Command(resp_array![ - "SET", value, body, "EX", &self.ttl - ])) + .send(Command(resp_array!["SET", value, body, "EX", &self.ttl])) .map_err(Error::from) .and_then(move |res| match res { Ok(_) => { @@ -226,7 +227,7 @@ impl Inner { } Ok(resp) } - Err(err) => Err(error::ErrorInternalServerError(err).into()), + Err(err) => Err(error::ErrorInternalServerError(err)), }), ), }) diff --git a/tests/test_redis.rs b/tests/test_redis.rs index a3d650e45..a9fef4a74 100644 --- a/tests/test_redis.rs +++ b/tests/test_redis.rs @@ -16,16 +16,15 @@ fn test_error_connect() { let addr = RedisActor::start("localhost:54000"); let _addr2 = addr.clone(); - Arbiter::handle().spawn_fn(move || { - addr.send(Command(resp_array!["GET", "test"])) - .then(|res| { - match res { - Ok(Err(Error::NotConnected)) => (), - _ => panic!("Should not happen {:?}", res), - } - Arbiter::system().do_send(actix::msgs::SystemExit(0)); - Ok(()) - }) + Arbiter::spawn_fn(move || { + addr.send(Command(resp_array!["GET", "test"])).then(|res| { + match res { + Ok(Err(Error::NotConnected)) => (), + _ => panic!("Should not happen {:?}", res), + } + System::current().stop(); + Ok(()) + }) }); sys.run(); @@ -39,28 +38,26 @@ fn test_redis() { let addr = RedisActor::start("127.0.0.1:6379"); let _addr2 = addr.clone(); - Arbiter::handle().spawn_fn(move || { + Arbiter::spawn_fn(move || { let addr2 = addr.clone(); addr.send(Command(resp_array!["SET", "test", "value"])) .then(move |res| match res { Ok(Ok(resp)) => { assert_eq!(resp, RespValue::SimpleString("OK".to_owned())); - addr2 - .send(Command(resp_array!["GET", "test"])) - .then(|res| { - match res { - Ok(Ok(resp)) => { - println!("RESP: {:?}", resp); - assert_eq!( - resp, - RespValue::BulkString((&b"value"[..]).into()) - ); - } - _ => panic!("Should not happen {:?}", res), + addr2.send(Command(resp_array!["GET", "test"])).then(|res| { + match res { + Ok(Ok(resp)) => { + println!("RESP: {:?}", resp); + assert_eq!( + resp, + RespValue::BulkString((&b"value"[..]).into()) + ); } - Arbiter::system().do_send(actix::msgs::SystemExit(0)); - Ok(()) - }) + _ => panic!("Should not happen {:?}", res), + } + System::current().stop(); + Ok(()) + }) } _ => panic!("Should not happen {:?}", res), }) From ac5a83134dec6f2644eca3b34ae3075055a4fb66 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 20 Jul 2018 14:54:50 -0700 Subject: [PATCH 43/84] update changes --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 153f19be1..a2b880e23 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.5.0 (2018-07-21) + +* Actix/Actix-web 0.7 compatibility + + ## 0.4.0 (2018-05-08) * Actix web 0.6 compatibility From b65158b71021e6f7597e5f4235c60dd2438ae6a8 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 20 Jul 2018 15:09:08 -0700 Subject: [PATCH 44/84] update travis stable rustc ver --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 428cd97ee..ec5926e1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.24.0 + - 1.26.0 - stable - beta - nightly From 35ab32c06373bf063596081f3254636de9cd5b02 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 21 Jul 2018 06:55:11 -0700 Subject: [PATCH 45/84] use actix-web 0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 77f139b63..8eda70a5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ redis-async = "0.3.2" time = "0.1" # actix web session -actix-web = { git = "https://github.com/actix/actix-web.git", optional=true } +actix-web = { version = "0.7", optional=true } cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.5", optional=true } From 8f6d800202ddaad4eaeee02ee9716e60e17a66ae Mon Sep 17 00:00:00 2001 From: Darin Date: Mon, 30 Jul 2018 09:57:53 -0400 Subject: [PATCH 46/84] updated version of cookie crate, to eliminate version conflicts --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8eda70a5f..94b81cf39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ time = "0.1" # actix web session actix-web = { version = "0.7", optional=true } -cookie = { version="0.10", features=["percent-encode", "secure"], optional=true } +cookie = { version="0.11", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.5", optional=true } serde = { version="1.0", optional=true } From 33b09d78e99bc3f4e5a5f252e160fb28cc163d4a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 2 Aug 2018 11:35:34 -0700 Subject: [PATCH 47/84] prepare release --- CHANGES.md | 5 +++++ Cargo.toml | 6 +++--- src/session.rs | 3 --- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a0702c2ef..b38b1ba20 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.5.1 (2018-08-02) + +* USe cookie 0.11 + + ## 0.5.0 (2018-07-21) * Session cookie configuration diff --git a/Cargo.toml b/Cargo.toml index 94b81cf39..66ef46a31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.5.0" +version = "0.5.1" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -36,11 +36,11 @@ futures = "0.1" tokio-io = "0.1" tokio-codec = "0.1" tokio-tcp = "0.1" -redis-async = "0.3.2" +redis-async = "0.4" time = "0.1" # actix web session -actix-web = { version = "0.7", optional=true } +actix-web = { version = "^0.7.3", optional=true } cookie = { version="0.11", features=["percent-encode", "secure"], optional=true } http = { version="0.1", optional=true } rand = { version="0.5", optional=true } diff --git a/src/session.rs b/src/session.rs index 22a97feb6..8f067a276 100644 --- a/src/session.rs +++ b/src/session.rs @@ -65,9 +65,6 @@ impl SessionImpl for RedisSession { /// 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. pub struct RedisSessionBackend(Rc); From 8bf43b49151cf79c35d7f79da7176844873b830b Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 2 Aug 2018 11:39:41 -0700 Subject: [PATCH 48/84] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4fe12b66c..3d207298a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Redis integration for actix framework. * [API Documentation (Releases)](https://docs.rs/actix-redis/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-redis](https://crates.io/crates/actix-redis) -* Minimum supported Rust version: 1.24 or later +* Minimum supported Rust version: 1.26 or later ## Redis session backend From 55fbdf1d00cbf25bca558d3317e4f9f4a15718a2 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 2 Aug 2018 11:42:41 -0700 Subject: [PATCH 49/84] fix typo --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b38b1ba20..a576f6e1b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## 0.5.1 (2018-08-02) -* USe cookie 0.11 +* Use cookie 0.11 ## 0.5.0 (2018-07-21) From 6d90615e487746a8574949f217951177bb9fe24f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 29 Mar 2019 11:31:48 -0700 Subject: [PATCH 50/84] update actix and actix-web --- Cargo.toml | 19 +++-- examples/basic.rs | 49 ++++------- rustfmt.toml | 3 - src/lib.rs | 40 ++------- src/redis.rs | 4 +- src/session.rs | 198 +++++++++++++++++++++++--------------------- tests/test_redis.rs | 12 +-- 7 files changed, 146 insertions(+), 179 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 66ef46a31..9ee01175d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.5.1" +version = "0.6.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -11,6 +11,7 @@ repository = "https://github.com/actix/actix-redis.git" documentation = "https://docs.rs/actix-redis/" categories = ["network-programming", "asynchronous"] exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +edition = "2018" [lib] name = "actix_redis" @@ -24,14 +25,14 @@ codecov = { repository = "actix/actix-redis", branch = "master", service = "gith default = ["web"] # actix-web integration -web = ["actix-web", "cookie", "http", "rand", "serde", "serde_json"] +web = ["actix/http", "actix-service", "actix-utils", "actix-web/cookies", "actix-session", "cookie", "rand", "serde", "serde_json"] [dependencies] -actix = "0.7" +actix = "0.8.0-alpha.2" log = "0.4" backoff = "0.1" -failure = "^0.1.1" +derive_more = "0.14" futures = "0.1" tokio-io = "0.1" tokio-codec = "0.1" @@ -40,12 +41,14 @@ redis-async = "0.4" time = "0.1" # actix web session -actix-web = { version = "^0.7.3", optional=true } +actix-web = { version = "1.0.0-alpha.1", optional=true } +actix-utils = { version = "0.3.3", optional=true } +actix-service = { version = "0.3.4", optional=true } +actix-session = { version = "0.1.0-alpha.1", optional=true } cookie = { version="0.11", features=["percent-encode", "secure"], optional=true } -http = { version="0.1", optional=true } -rand = { version="0.5", optional=true } +rand = { version="0.6.5", optional=true } serde = { version="1.0", optional=true } serde_json = { version="1.0", optional=true } [dev-dependencies] -env_logger = "0.5" +env_logger = "0.6" diff --git a/examples/basic.rs b/examples/basic.rs index 4dbed88a7..14e1ce9ba 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,50 +1,35 @@ -#![allow(unused_variables)] -#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] - -extern crate actix; -extern crate actix_redis; -extern crate actix_web; -extern crate env_logger; -extern crate futures; - -use actix_redis::RedisSessionBackend; -use actix_web::middleware::session::{self, RequestSession}; -use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Result}; +use actix_redis::RedisSession; +use actix_session::Session; +use actix_web::{middleware, web, App, Error, HttpRequest, HttpServer, Responder}; /// simple handler -fn index(req: &HttpRequest) -> Result { +fn index(req: HttpRequest, session: Session) -> Result { println!("{:?}", req); // session - if let Some(count) = req.session().get::("counter")? { + if let Some(count) = session.get::("counter")? { println!("SESSION value: {}", count); - req.session().set("counter", count + 1)?; + session.set("counter", count + 1)?; } else { - req.session().set("counter", 1)?; + session.set("counter", 1)?; } - Ok("Welcome!".into()) + Ok("Welcome!") } -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); env_logger::init(); - let sys = actix::System::new("basic-example"); - server::new(|| { + HttpServer::new(|| { App::new() // enable logger - .middleware(middleware::Logger::default()) + .wrap(middleware::Logger::default()) // cookie session middleware - .middleware(session::SessionStorage::new( - RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) - )) + .wrap(RedisSession::new("127.0.0.1:6379", &[0; 32])) // register simple route, handle all methods - .resource("/", |r| r.f(index)) - }).bind("0.0.0.0:8080") - .unwrap() - .workers(1) - .start(); - - let _ = sys.run(); + .service(web::resource("/").to(index)) + }) + .bind("0.0.0.0:8080")? + .run() } diff --git a/rustfmt.toml b/rustfmt.toml index 6db67ed28..94bd11d51 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,2 @@ max_width = 89 reorder_imports = true -wrap_comments = true -fn_args_density = "Compressed" -#use_small_heuristics = false diff --git a/src/lib.rs b/src/lib.rs index 62deca66e..baa446c3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,62 +7,36 @@ //! * Cargo package: [actix-redis](https://crates.io/crates/actix-redis) //! * Minimum supported Rust version: 1.26 or later //! -extern crate actix; -extern crate backoff; -extern crate futures; -extern crate tokio_codec; -extern crate tokio_io; -extern crate tokio_tcp; #[macro_use] extern crate log; #[macro_use] extern crate redis_async; #[macro_use] -extern crate failure; -extern crate time; +extern crate derive_more; mod redis; pub use redis::{Command, RedisActor}; -#[cfg(feature = "web")] -extern crate actix_web; -#[cfg(feature = "web")] -extern crate cookie; -#[cfg(feature = "web")] -extern crate http; -#[cfg(feature = "web")] -extern crate rand; -#[cfg(feature = "web")] -extern crate serde; -#[cfg(feature = "web")] -extern crate serde_json; - #[cfg(feature = "web")] mod session; #[cfg(feature = "web")] -pub use session::RedisSessionBackend; +pub use cookie::SameSite; #[cfg(feature = "web")] -pub use cookie::{SameSite}; +pub use session::RedisSession; /// General purpose actix redis error -#[derive(Fail, Debug)] +#[derive(Debug, Display, From)] pub enum Error { - #[fail(display = "Redis error {}", _0)] + #[display(fmt = "Redis error {}", _0)] Redis(redis_async::error::Error), /// Receiving message during reconnecting - #[fail(display = "Redis: Not connected")] + #[display(fmt = "Redis: Not connected")] NotConnected, /// Cancel all waters when connection get dropped - #[fail(display = "Redis: Disconnected")] + #[display(fmt = "Redis: Disconnected")] Disconnected, } -impl From for Error { - fn from(err: redis_async::error::Error) -> Error { - Error::Redis(err) - } -} - // re-export pub use redis_async::error::Error as RespError; pub use redis_async::resp::RespValue; diff --git a/src/redis.rs b/src/redis.rs index f2f0aa6da..7d4debadb 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -14,7 +14,7 @@ use tokio_io::io::WriteHalf; use tokio_io::AsyncRead; use tokio_tcp::TcpStream; -use Error; +use crate::Error; /// Command for send data to Redis #[derive(Debug)] @@ -63,7 +63,7 @@ impl Actor for RedisActor { let (r, w) = stream.split(); // configure write side of the connection - let mut framed = actix::io::FramedWrite::new(w, RespCodec, ctx); + let framed = actix::io::FramedWrite::new(w, RespCodec, ctx); act.cell = Some(framed); // read side of the connection diff --git a/src/session.rs b/src/session.rs index 8f067a276..2b1ebbbcf 100644 --- a/src/session.rs +++ b/src/session.rs @@ -3,61 +3,20 @@ use std::iter; use std::rc::Rc; use actix::prelude::*; -use actix_web::middleware::session::{SessionBackend, SessionImpl}; -use actix_web::middleware::Response as MiddlewareResponse; -use actix_web::{error, Error, HttpRequest, HttpResponse, Result}; +use actix_service::{Service, Transform}; +use actix_session::Session; +use actix_utils::cloneable::CloneableService; +use actix_web::dev::{ServiceRequest, ServiceResponse}; +use actix_web::http::header::{self, HeaderValue}; +use actix_web::{error, Error, HttpMessage}; use cookie::{Cookie, CookieJar, Key, SameSite}; -use futures::future::{err as FutErr, ok as FutOk, Either}; -use futures::Future; -use http::header::{self, HeaderValue}; -use rand::distributions::Alphanumeric; -use rand::{self, Rng}; +use futures::future::{err, ok, Either, Future, FutureResult}; +use futures::Poll; +use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use redis_async::resp::RespValue; -use serde_json; use time::Duration; -use redis::{Command, RedisActor}; - -/// Session that stores data in redis -pub struct RedisSession { - changed: bool, - inner: Rc, - state: HashMap, - value: Option, -} - -impl SessionImpl for RedisSession { - fn get(&self, key: &str) -> Option<&str> { - self.state.get(key).map(|s| s.as_str()) - } - - 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, resp: HttpResponse) -> Result { - if self.changed { - Ok(MiddlewareResponse::Future(self.inner.update( - &self.state, - resp, - self.value.as_ref(), - ))) - } else { - Ok(MiddlewareResponse::Done(resp)) - } - } -} +use crate::redis::{Command, RedisActor}; /// Use redis as session storage. /// @@ -66,14 +25,14 @@ impl SessionImpl for RedisSession { /// session, When this value is changed, all session data is lost. /// /// Constructor panics if key length is less than 32 bytes. -pub struct RedisSessionBackend(Rc); +pub struct RedisSession(Rc); -impl RedisSessionBackend { +impl RedisSession { /// Create new redis session backend /// /// * `addr` - address of the redis server - pub fn new>(addr: S, key: &[u8]) -> RedisSessionBackend { - RedisSessionBackend(Rc::new(Inner { + pub fn new>(addr: S, key: &[u8]) -> RedisSession { + RedisSession(Rc::new(Inner { key: Key::from_master(key), ttl: "7200".to_owned(), addr: RedisActor::start(addr), @@ -131,29 +90,77 @@ impl RedisSessionBackend { } } -impl SessionBackend for RedisSessionBackend { - type Session = RedisSession; - type ReadFuture = Box>; +impl Transform for RedisSession +where + S: Service< + Request = ServiceRequest

, + Response = ServiceResponse, + Error = Error, + > + 'static, + S::Future: 'static, + P: 'static, + B: 'static, +{ + type Request = ServiceRequest

; + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = RedisSessionMiddleware; + type Future = FutureResult; - fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { - let inner = Rc::clone(&self.0); + fn new_transform(&self, service: S) -> Self::Future { + ok(RedisSessionMiddleware { + service: CloneableService::new(service), + inner: self.0.clone(), + }) + } +} - Box::new(self.0.load(req).map(move |state| { - if let Some((state, value)) = state { - RedisSession { - inner, - state, - changed: false, - value: Some(value), - } +/// Cookie session middleware +pub struct RedisSessionMiddleware { + service: CloneableService, + inner: Rc, +} + +impl Service for RedisSessionMiddleware +where + S: Service< + Request = ServiceRequest

, + Response = ServiceResponse, + Error = Error, + > + 'static, + S::Future: 'static, + P: 'static, + B: 'static, +{ + type Request = ServiceRequest

; + type Response = ServiceResponse; + type Error = Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { + let mut srv = self.service.clone(); + let inner = self.inner.clone(); + + Box::new(self.inner.load(&req).and_then(move |state| { + let value = if let Some((state, value)) = state { + Session::set_session(state.into_iter(), &mut req); + Some(value) } else { - RedisSession { - inner, - changed: false, - state: HashMap::new(), - value: None, + None + }; + + srv.call(req).and_then(move |mut res| { + if let Some(state) = Session::get_changes(&mut res) { + Either::A(inner.update(res, state, value)) + } else { + Either::B(ok(res)) } - } + }) })) } } @@ -171,10 +178,10 @@ struct Inner { } impl Inner { - #[cfg_attr(feature = "cargo-clippy", allow(type_complexity))] - fn load( - &self, req: &mut HttpRequest, - ) -> Box, String)>, Error = Error>> + fn load

( + &self, + req: &ServiceRequest

, + ) -> impl Future, String)>, Error = Error> { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { @@ -183,7 +190,7 @@ impl Inner { 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( + return Either::A( self.addr .send(Command(resp_array!["GET", cookie.value()])) .map_err(Error::from) @@ -193,7 +200,7 @@ impl Inner { RespValue::Error(err) => { return Err( error::ErrorInternalServerError(err), - ) + ); } RespValue::SimpleString(s) => { if let Ok(val) = serde_json::from_str(&s) @@ -218,27 +225,30 @@ impl Inner { }), ); } else { - return Box::new(FutOk(None)); + return Either::B(ok(None)); } } } } - Box::new(FutOk(None)) + Either::B(ok(None)) } - fn update( - &self, state: &HashMap, mut resp: HttpResponse, - value: Option<&String>, - ) -> Box> { + fn update( + &self, + mut res: ServiceResponse, + state: impl Iterator, + value: Option, + ) -> impl Future, Error = Error> { let (value, jar) = if let Some(value) = value { (value.clone(), None) } else { - let mut rng = rand::OsRng::new().unwrap(); + let mut rng = OsRng::new().unwrap(); let value: String = iter::repeat(()) .map(|()| rng.sample(Alphanumeric)) .take(32) .collect(); + // prepare session id cookie let mut cookie = Cookie::new(self.name.clone(), value.clone()); cookie.set_path(self.path.clone()); cookie.set_secure(self.secure); @@ -263,26 +273,28 @@ impl Inner { (value, Some(jar)) }; - Box::new(match serde_json::to_string(state) { - Err(e) => Either::A(FutErr(e.into())), + let state: HashMap<_, _> = state.collect(); + + match serde_json::to_string(&state) { + Err(e) => Either::A(err(e.into())), Ok(body) => Either::B( self.addr .send(Command(resp_array!["SET", value, body, "EX", &self.ttl])) .map_err(Error::from) - .and_then(move |res| match res { + .and_then(move |redis_result| match redis_result { 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); + res.headers_mut().append(header::SET_COOKIE, val); } } - Ok(resp) + Ok(res) } Err(err) => Err(error::ErrorInternalServerError(err)), }), ), - }) + } } } diff --git a/tests/test_redis.rs b/tests/test_redis.rs index a9fef4a74..979c0230a 100644 --- a/tests/test_redis.rs +++ b/tests/test_redis.rs @@ -1,16 +1,12 @@ -extern crate actix; -extern crate actix_redis; #[macro_use] extern crate redis_async; -extern crate env_logger; -extern crate futures; use actix::prelude::*; use actix_redis::{Command, Error, RedisActor, RespValue}; use futures::Future; #[test] -fn test_error_connect() { +fn test_error_connect() -> std::io::Result<()> { let sys = System::new("test"); let addr = RedisActor::start("localhost:54000"); @@ -27,11 +23,11 @@ fn test_error_connect() { }) }); - sys.run(); + sys.run() } #[test] -fn test_redis() { +fn test_redis() -> std::io::Result<()> { env_logger::init(); let sys = System::new("test"); @@ -63,5 +59,5 @@ fn test_redis() { }) }); - sys.run(); + sys.run() } From b95dd9f19358afb9e8b3ca896825765a6fba1460 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 08:21:47 -0700 Subject: [PATCH 51/84] update actix-web --- Cargo.toml | 7 +++---- src/lib.rs | 2 +- src/session.rs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ee01175d..df9dc456c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ codecov = { repository = "actix/actix-redis", branch = "master", service = "gith default = ["web"] # actix-web integration -web = ["actix/http", "actix-service", "actix-utils", "actix-web/cookies", "actix-session", "cookie", "rand", "serde", "serde_json"] +web = ["actix/http", "actix-service", "actix-utils", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] [dependencies] actix = "0.8.0-alpha.2" @@ -41,11 +41,10 @@ redis-async = "0.4" time = "0.1" # actix web session -actix-web = { version = "1.0.0-alpha.1", optional=true } +actix-web = { version = "1.0.0-alpha.2", optional=true } actix-utils = { version = "0.3.3", optional=true } actix-service = { version = "0.3.4", optional=true } -actix-session = { version = "0.1.0-alpha.1", optional=true } -cookie = { version="0.11", features=["percent-encode", "secure"], optional=true } +actix-session = { version = "0.1.0-alpha.2", optional=true } rand = { version="0.6.5", optional=true } serde = { version="1.0", optional=true } serde_json = { version="1.0", optional=true } diff --git a/src/lib.rs b/src/lib.rs index baa446c3b..11a03dd26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ pub use redis::{Command, RedisActor}; #[cfg(feature = "web")] mod session; #[cfg(feature = "web")] -pub use cookie::SameSite; +pub use actix_web::cookie::SameSite; #[cfg(feature = "web")] pub use session::RedisSession; diff --git a/src/session.rs b/src/session.rs index 2b1ebbbcf..b36ea0ff0 100644 --- a/src/session.rs +++ b/src/session.rs @@ -6,10 +6,10 @@ use actix::prelude::*; use actix_service::{Service, Transform}; use actix_session::Session; use actix_utils::cloneable::CloneableService; +use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::header::{self, HeaderValue}; use actix_web::{error, Error, HttpMessage}; -use cookie::{Cookie, CookieJar, Key, SameSite}; use futures::future::{err, ok, Either, Future, FutureResult}; use futures::Poll; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; From 345e67cd13255631c3726b5626654b8738602a7c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 30 Mar 2019 09:44:18 -0700 Subject: [PATCH 52/84] remove rust 1.26 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec5926e1f..cb3e21855 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: rust rust: - - 1.26.0 - stable - beta - nightly From cb682b08e47b1caab06c7ca45dccef6505d45a9d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 14 Apr 2019 10:30:37 -0700 Subject: [PATCH 53/84] update to alpha6 --- Cargo.toml | 6 +++--- src/session.rs | 30 +++++++++++------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index df9dc456c..36df610b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ default = ["web"] web = ["actix/http", "actix-service", "actix-utils", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] [dependencies] -actix = "0.8.0-alpha.2" +actix = "0.8.0-alpha.3" log = "0.4" backoff = "0.1" @@ -41,10 +41,10 @@ redis-async = "0.4" time = "0.1" # actix web session -actix-web = { version = "1.0.0-alpha.2", optional=true } +actix-web = { version = "1.0.0-alpha.6", optional=true } actix-utils = { version = "0.3.3", optional=true } actix-service = { version = "0.3.4", optional=true } -actix-session = { version = "0.1.0-alpha.2", optional=true } +actix-session = { version = "0.1.0-alpha.6", optional=true } rand = { version="0.6.5", optional=true } serde = { version="1.0", optional=true } serde_json = { version="1.0", optional=true } diff --git a/src/session.rs b/src/session.rs index b36ea0ff0..935a7ce22 100644 --- a/src/session.rs +++ b/src/session.rs @@ -90,18 +90,14 @@ impl RedisSession { } } -impl Transform for RedisSession +impl Transform for RedisSession where - S: Service< - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - > + 'static, + S: Service, Error = Error> + + 'static, S::Future: 'static, - P: 'static, B: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; type InitError = (); @@ -122,18 +118,14 @@ pub struct RedisSessionMiddleware { inner: Rc, } -impl Service for RedisSessionMiddleware +impl Service for RedisSessionMiddleware where - S: Service< - Request = ServiceRequest

, - Response = ServiceResponse, - Error = Error, - > + 'static, + S: Service, Error = Error> + + 'static, S::Future: 'static, - P: 'static, B: 'static, { - type Request = ServiceRequest

; + type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = Box>; @@ -142,7 +134,7 @@ where self.service.poll_ready() } - fn call(&mut self, mut req: ServiceRequest

) -> Self::Future { + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let mut srv = self.service.clone(); let inner = self.inner.clone(); @@ -178,9 +170,9 @@ struct Inner { } impl Inner { - fn load

( + fn load( &self, - req: &ServiceRequest

, + req: &ServiceRequest, ) -> impl Future, String)>, Error = Error> { if let Ok(cookies) = req.cookies() { From 38d4bdb8aa5012fba604a74aa89e0193b38808c0 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 12 May 2019 21:36:05 -0700 Subject: [PATCH 54/84] update dependencies --- Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 36df610b9..b076f312e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.6.0" +version = "0.6.0-beta.4" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -28,7 +28,7 @@ default = ["web"] web = ["actix/http", "actix-service", "actix-utils", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] [dependencies] -actix = "0.8.0-alpha.3" +actix = "0.8.2" log = "0.4" backoff = "0.1" @@ -41,10 +41,10 @@ redis-async = "0.4" time = "0.1" # actix web session -actix-web = { version = "1.0.0-alpha.6", optional=true } -actix-utils = { version = "0.3.3", optional=true } -actix-service = { version = "0.3.4", optional=true } -actix-session = { version = "0.1.0-alpha.6", optional=true } +actix-web = { version = "1.0.0-beta.5", optional=true } +actix-utils = { version = "0.4.0", optional=true } +actix-service = { version = "0.4.0", optional=true } +actix-session = { version = "0.1.0-beta.4", optional=true } rand = { version="0.6.5", optional=true } serde = { version="1.0", optional=true } serde_json = { version="1.0", optional=true } From c7266e3ddd21f26ff71642c647f42203b76bdd69 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 18 May 2019 11:24:26 -0700 Subject: [PATCH 55/84] prepare release --- CHANGES.md | 5 +++++ Cargo.toml | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a576f6e1b..d8a1632f0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## 0.6.0 (2019-05-18) + +* actix-web 1.0.0 compatibility + + ## 0.5.1 (2018-08-02) * Use cookie 0.11 diff --git a/Cargo.toml b/Cargo.toml index b076f312e..b1c82a7d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.6.0-beta.4" +version = "0.6.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -33,7 +33,7 @@ actix = "0.8.2" log = "0.4" backoff = "0.1" derive_more = "0.14" -futures = "0.1" +futures = "0.1.25" tokio-io = "0.1" tokio-codec = "0.1" tokio-tcp = "0.1" @@ -41,10 +41,10 @@ redis-async = "0.4" time = "0.1" # actix web session -actix-web = { version = "1.0.0-beta.5", optional=true } -actix-utils = { version = "0.4.0", optional=true } +actix-web = { version = "1.0.0-rc", optional=true } +actix-utils = { version = "0.4.1", optional=true } actix-service = { version = "0.4.0", optional=true } -actix-session = { version = "0.1.0-beta.4", optional=true } +actix-session = { version = "0.1.0", optional=true } rand = { version="0.6.5", optional=true } serde = { version="1.0", optional=true } serde_json = { version="1.0", optional=true } From fa531fb03d82ecb2d76d83963bfabcca734ba0d2 Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 12 Jun 2019 08:14:18 -0400 Subject: [PATCH 56/84] updated session logic to support login and logout functionality --- .gitignore | 1 + src/session.rs | 77 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 42d0755dd..c786504ea 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ Cargo.lock target/ guide/build/ /gh-pages +/.history *.so *.out diff --git a/src/session.rs b/src/session.rs index 935a7ce22..bb8e890d1 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,10 +1,7 @@ -use std::collections::HashMap; -use std::iter; -use std::rc::Rc; - +use std::{collections::HashMap, iter, rc::Rc}; use actix::prelude::*; use actix_service::{Service, Transform}; -use actix_session::Session; +use actix_session::{Session, SessionStatus}; use actix_utils::cloneable::CloneableService; use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; @@ -14,7 +11,7 @@ use futures::future::{err, ok, Either, Future, FutureResult}; use futures::Poll; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use redis_async::resp::RespValue; -use time::Duration; +use time::{self, Duration}; use crate::redis::{Command, RedisActor}; @@ -147,10 +144,32 @@ where }; srv.call(req).and_then(move |mut res| { - if let Some(state) = Session::get_changes(&mut res) { - Either::A(inner.update(res, state, value)) - } else { - Either::B(ok(res)) + match Session::get_changes(&mut res) { + (SessionStatus::Unchanged, _) => + Either::A(Either::A(ok(res))), + (SessionStatus::Changed, Some(state)) => + Either::B(Either::A(inner.update(res, state, value))), + (SessionStatus::Purged, Some(_)) => { + Either::B(Either::B( + inner.clear_cache(value) + .and_then(move |_| + match inner.remove_cookie(&mut res){ + Ok(_) => Either::A(ok(res)), + Err(_err) => Either::B(err( + error::ErrorInternalServerError(_err))) + }) + )) + }, + (SessionStatus::Renewed, Some(state)) => { + Either::A( + Either::B( + inner.clear_cache(value) + .and_then(move |_| + inner.update(res, state, None)) + ) + ) + }, + (_, None) => unreachable!() } }) })) @@ -289,4 +308,42 @@ impl Inner { ), } } + + /// removes cache entry + fn clear_cache(&self, value: Option) + -> impl Future { + if let Some(key) = value { + Either::A( + self.addr + .send(Command(resp_array!["DEL", key])) + .map_err(Error::from) + .and_then(|res| + match res { + // redis responds with number of deleted records + Ok(RespValue::Integer(x)) if x > 0 => Ok(()), + _ => Err(error::ErrorInternalServerError( + "failed to remove session from cache")) + } + ) + ) + } else { + Either::B(err(error::ErrorInternalServerError( + "missing session key - unexpected"))) + } + } + + /// invalidates session cookie + fn remove_cookie(&self, res: &mut ServiceResponse) + -> Result<(), Error> { + let mut cookie = Cookie::named(self.name.clone()); + cookie.set_value(""); + cookie.set_max_age(Duration::seconds(0)); + cookie.set_expires(time::now() - Duration::days(365)); + + let val = HeaderValue::from_str(&cookie.to_string()) + .map_err(|err| error::ErrorInternalServerError(err))?; + res.headers_mut().append(header::SET_COOKIE, val); + + Ok(()) + } } From 1086180267f989ae8bda2137e99aec2e5541716e Mon Sep 17 00:00:00 2001 From: dowwie Date: Tue, 18 Jun 2019 13:47:14 -0400 Subject: [PATCH 57/84] resolved bug related to updating session cookie --- src/session.rs | 58 ++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/session.rs b/src/session.rs index bb8e890d1..275bd0f2a 100644 --- a/src/session.rs +++ b/src/session.rs @@ -151,23 +151,33 @@ where Either::B(Either::A(inner.update(res, state, value))), (SessionStatus::Purged, Some(_)) => { Either::B(Either::B( - inner.clear_cache(value) - .and_then(move |_| - match inner.remove_cookie(&mut res){ - Ok(_) => Either::A(ok(res)), - Err(_err) => Either::B(err( - error::ErrorInternalServerError(_err))) - }) + if let Some(val) = value { + Either::A( + inner.clear_cache(val) + .and_then(move |_| + match inner.remove_cookie(&mut res){ + Ok(_) => Either::A(ok(res)), + Err(_err) => Either::B(err( + error::ErrorInternalServerError(_err))) + }) + ) + } else { + Either::B(err(error::ErrorInternalServerError("unexpected"))) + } )) }, (SessionStatus::Renewed, Some(state)) => { + if let Some(val) = value { Either::A( Either::B( - inner.clear_cache(value) + inner.clear_cache(val) .and_then(move |_| inner.update(res, state, None)) ) ) + } else { + Either::B(Either::A(inner.update(res, state, None))) + } }, (_, None) => unreachable!() } @@ -285,7 +295,6 @@ impl Inner { }; let state: HashMap<_, _> = state.collect(); - match serde_json::to_string(&state) { Err(e) => Either::A(err(e.into())), Ok(body) => Either::B( @@ -310,26 +319,19 @@ impl Inner { } /// removes cache entry - fn clear_cache(&self, value: Option) + fn clear_cache(&self, key: String) -> impl Future { - if let Some(key) = value { - Either::A( - self.addr - .send(Command(resp_array!["DEL", key])) - .map_err(Error::from) - .and_then(|res| - match res { - // redis responds with number of deleted records - Ok(RespValue::Integer(x)) if x > 0 => Ok(()), - _ => Err(error::ErrorInternalServerError( - "failed to remove session from cache")) - } - ) - ) - } else { - Either::B(err(error::ErrorInternalServerError( - "missing session key - unexpected"))) - } + self.addr + .send(Command(resp_array!["DEL", key])) + .map_err(Error::from) + .and_then(|res| { + match res { + // redis responds with number of deleted records + Ok(RespValue::Integer(x)) if x > 0 => Ok(()), + _ => Err(error::ErrorInternalServerError( + "failed to remove session from cache")) + } + }) } /// invalidates session cookie From 48ddb769152634f9d75c75f0d51fb807a4a32988 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 1 Jul 2019 14:20:18 -0400 Subject: [PATCH 58/84] updated logic to create session on new request wo cookie --- src/session.rs | 54 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/session.rs b/src/session.rs index 275bd0f2a..315dc189e 100644 --- a/src/session.rs +++ b/src/session.rs @@ -145,38 +145,50 @@ where srv.call(req).and_then(move |mut res| { match Session::get_changes(&mut res) { - (SessionStatus::Unchanged, _) => - Either::A(Either::A(ok(res))), - (SessionStatus::Changed, Some(state)) => - Either::B(Either::A(inner.update(res, state, value))), - (SessionStatus::Purged, Some(_)) => { - Either::B(Either::B( - if let Some(val) = value { + (SessionStatus::Unchanged, None) => + Either::A(Either::A(Either::A(ok(res)))), + (SessionStatus::Unchanged, Some(state)) => + Either::A(Either::A(Either::B( + if value.is_none(){ // implies the session is new Either::A( - inner.clear_cache(val) - .and_then(move |_| - match inner.remove_cookie(&mut res){ - Ok(_) => Either::A(ok(res)), - Err(_err) => Either::B(err( - error::ErrorInternalServerError(_err))) - }) + inner.update(res, state, value) ) } else { - Either::B(err(error::ErrorInternalServerError("unexpected"))) + Either::B( + ok(res) + ) + } + ))), + (SessionStatus::Changed, Some(state)) => + Either::A(Either::B(Either::A(inner.update(res, state, value)))), + (SessionStatus::Purged, Some(_)) => { + if let Some(val) = value { + Either::A(Either::B(Either::B(Either::A( + inner.clear_cache(val) + .and_then(move |_| + match inner.remove_cookie(&mut res){ + Ok(_) => Either::A(ok(res)), + Err(_err) => Either::B(err( + error::ErrorInternalServerError(_err))) + }) + )))) + } else { + Either::A(Either::B(Either::B(Either::B( + err(error::ErrorInternalServerError("unexpected")) + )))) } - )) }, (SessionStatus::Renewed, Some(state)) => { if let Some(val) = value { - Either::A( - Either::B( + Either::B(Either::A( inner.clear_cache(val) .and_then(move |_| inner.update(res, state, None)) - ) - ) + )) } else { - Either::B(Either::A(inner.update(res, state, None))) + Either::B(Either::B( + inner.update(res, state, None) + )) } }, (_, None) => unreachable!() From 1d1ff6606336edbd84af6e657e92e26a8427b001 Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 4 Jul 2019 06:10:03 -0400 Subject: [PATCH 59/84] updated version to 0.7 and added comments to CHANGES.md --- CHANGES.md | 11 +++++++++++ Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d8a1632f0..55ffc6b6b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # Changes + +## 0.7.0 (2019-xx-xx) + +* Upgraded logic that evaluates session state, including new SessionStatus field, + and introduced ``session.renew()`` and ``session.purge()`` functionality. + Use ``renew()`` to cycle the session key at successful login. ``renew()`` keeps a + session's state while replacing the old cookie and session key with new ones. + Use ``purge()`` at logout to invalidate the session cookie and remove the + session's redis cache entry. + + ## 0.6.0 (2019-05-18) * actix-web 1.0.0 compatibility diff --git a/Cargo.toml b/Cargo.toml index b1c82a7d4..64b83b133 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.6.0" +version = "0.7.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" From 3244a359df842396162d8878ba82ec6df6547a9c Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 8 Jul 2019 07:15:20 -0400 Subject: [PATCH 60/84] reverted to 0.6 because it wasn't ever released --- CHANGES.md | 8 +++----- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 55ffc6b6b..3e22d0019 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,9 @@ # Changes -## 0.7.0 (2019-xx-xx) +## 0.6.0 (2019-07-08) + +* actix-web 1.0.0 compatibility * Upgraded logic that evaluates session state, including new SessionStatus field, and introduced ``session.renew()`` and ``session.purge()`` functionality. @@ -11,10 +13,6 @@ session's redis cache entry. -## 0.6.0 (2019-05-18) - -* actix-web 1.0.0 compatibility - ## 0.5.1 (2018-08-02) diff --git a/Cargo.toml b/Cargo.toml index 64b83b133..b1c82a7d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.7.0" +version = "0.6.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" From 06ce468ee0c01ceeccdaa947142436299510b2af Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 8 Jul 2019 14:49:34 -0400 Subject: [PATCH 61/84] updated deps --- Cargo.toml | 33 +++++++++++++++++---------------- src/session.rs | 3 +-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1c82a7d4..c9bde7a72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,26 +28,27 @@ default = ["web"] web = ["actix/http", "actix-service", "actix-utils", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] [dependencies] -actix = "0.8.2" +actix = "0.8.3" log = "0.4" -backoff = "0.1" -derive_more = "0.14" -futures = "0.1.25" -tokio-io = "0.1" -tokio-codec = "0.1" -tokio-tcp = "0.1" -redis-async = "0.4" -time = "0.1" +backoff = "0.1.5" +derive_more = "0.15.0" +futures = "0.1.28" +tokio-io = "0.1.12" +tokio-codec = "0.1.1" +tokio-tcp = "0.1.3" +redis-async = "0.4.5" +time = "0.1.42" # actix web session -actix-web = { version = "1.0.0-rc", optional=true } -actix-utils = { version = "0.4.1", optional=true } -actix-service = { version = "0.4.0", optional=true } -actix-session = { version = "0.1.0", optional=true } -rand = { version="0.6.5", optional=true } -serde = { version="1.0", optional=true } -serde_json = { version="1.0", optional=true } +actix-web = { version = "1.0.3", optional = true } +actix-utils = { version = "0.4.2", optional = true } +actix-service = { version = "0.4.1", optional = true } +actix-session = { version = "0.2.0", optional = true } +rand = { version = "0.7.0", optional = true } +serde = { version = "1.0.94", optional = true } +serde_json = { version = "1.0.40", optional = true } +env_logger = "0.6.2" [dev-dependencies] env_logger = "0.6" diff --git a/src/session.rs b/src/session.rs index 315dc189e..9623c6c16 100644 --- a/src/session.rs +++ b/src/session.rs @@ -275,9 +275,8 @@ impl Inner { let (value, jar) = if let Some(value) = value { (value.clone(), None) } else { - let mut rng = OsRng::new().unwrap(); let value: String = iter::repeat(()) - .map(|()| rng.sample(Alphanumeric)) + .map(|()| OsRng.sample(Alphanumeric)) .take(32) .collect(); From b29f8cc0f08db700f86fc847485718c4a29409fb Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 8 Jul 2019 14:51:54 -0400 Subject: [PATCH 62/84] updated log dep --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c9bde7a72..08501b837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ web = ["actix/http", "actix-service", "actix-utils", "actix-web", "actix-session [dependencies] actix = "0.8.3" -log = "0.4" +log = "0.4.6" backoff = "0.1.5" derive_more = "0.15.0" futures = "0.1.28" From 24b78b8e175abe2a8e4acc292975bb8071982c4b Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 11 Jul 2019 15:17:57 -0400 Subject: [PATCH 63/84] added comprehensive tests for session workflow --- Cargo.toml | 2 + src/session.rs | 233 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 08501b837..b2c61e0cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,8 @@ rand = { version = "0.7.0", optional = true } serde = { version = "1.0.94", optional = true } serde_json = { version = "1.0.40", optional = true } env_logger = "0.6.2" +actix-http-test = "0.2.2" +actix-http = "0.2.5" [dev-dependencies] env_logger = "0.6" diff --git a/src/session.rs b/src/session.rs index 9623c6c16..2de0b2d87 100644 --- a/src/session.rs +++ b/src/session.rs @@ -360,3 +360,236 @@ impl Inner { Ok(()) } } + + +#[cfg(test)] +mod test { + use super::*; + use actix_http::{HttpService, httpmessage::HttpMessage}; + use actix_http_test::{TestServer, block_on}; + use actix_session::Session; + use actix_web::{middleware, web, App, HttpResponse, HttpServer, Result, + web::{resource, get, post}}; + use serde::{Deserialize, Serialize}; + use serde_json::json; + use time; + + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + pub struct IndexResponse { + user_id: Option, + counter: i32 + } + + fn index(session: Session) -> Result { + let user_id: Option = session.get::("user_id").unwrap(); + let counter: i32 = session.get::("counter") + .unwrap_or(Some(0)) + .unwrap_or(0); + + Ok(HttpResponse::Ok().json(IndexResponse{user_id, counter})) + } + + + fn do_something(session: Session) -> Result { + let user_id: Option = session.get::("user_id").unwrap(); + let counter: i32 = session.get::("counter") + .unwrap_or(Some(0)) + .map_or(1, |inner| inner + 1); + session.set("counter", counter)?; + + Ok(HttpResponse::Ok().json(IndexResponse{user_id, counter})) + } + + #[derive(Deserialize)] + struct Identity { + user_id: String + } + fn login(user_id: web::Json, session: Session) -> Result { + let id = user_id.into_inner().user_id; + session.set("user_id", &id)?; + session.renew(); + + let counter: i32 = session.get::("counter") + .unwrap_or(Some(0)) + .unwrap_or(0); + + Ok(HttpResponse::Ok().json(IndexResponse{user_id: Some(id), counter})) + } + + fn logout(session: Session) -> Result { + let id: Option = session.get("user_id")?; + if let Some(x) = id{ + session.purge(); + Ok(format!("Logged out: {}", x).into()) + } else { + Ok("Could not log out anonymous user".into()) + } + } + + #[test] + fn test_workflow() { + // Step 1: GET index + // - set-cookie actix-session will be in response (session cookie #1) + // - response should be: {"counter": 0, "user_id": None} + // Step 2: GET index, including session cookie #1 in request + // - set-cookie will *not* be in response + // - response should be: {"counter": 0, "user_id": None} + // Step 3: POST to do_something, including session cookie #1 in request + // - adds new session state in redis: {"counter": 1} + // - response should be: {"counter": 1, "user_id": None} + // Step 4: POST again to do_something, including session cookie #1 in request + // - updates session state in redis: {"counter": 2} + // - response should be: {"counter": 2, "user_id": None} + // Step 5: POST to login, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #2) + // - updates session state in redis: {"counter": 2, "user_id": "ferris"} + // Step 6: GET index, including session cookie #2 in request + // - response should be: {"counter": 2, "user_id": "ferris"} + // Step 7: POST again to do_something, including session cookie #2 in request + // - updates session state in redis: {"counter": 3, "user_id": "ferris"} + // - response should be: {"counter": 2, "user_id": None} + // Step 8: GET index, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + // Step 9: POST to logout, including session cookie #2 + // - set-cookie actix-session will be in response with session cookie #2 + // invalidation logic + // Step 10: GET index, including session cookie #2 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + + let mut srv = + TestServer::new(|| { + HttpService::new( + App::new() + .wrap(RedisSession::new("127.0.0.1:6379", &[0; 32]) + .cookie_name("test-session")) + .wrap(middleware::Logger::default()) + .service(resource("/").route(get().to(index))) + .service(resource("/do_something").route(post().to(do_something))) + .service(resource("/login").route(post().to(login))) + .service(resource("/logout").route(post().to(logout))) + ) + }); + + + // Step 1: GET index + // - set-cookie actix-session will be in response (session cookie #1) + // - response should be: {"counter": 0, "user_id": None} + let req_1a = srv.get("/").send(); + let mut resp_1 = srv.block_on(req_1a).unwrap(); + let cookie_1 = resp_1.cookies().unwrap().clone() + .into_iter().find(|c| c.name() == "test-session") + .unwrap(); + let result_1 = block_on(resp_1.json::()).unwrap(); + assert_eq!(result_1, IndexResponse{user_id: None, counter: 0}); + + + // Step 2: GET index, including session cookie #1 in request + // - set-cookie will *not* be in response + // - response should be: {"counter": 0, "user_id": None} + let req_2 = srv.get("/").cookie(cookie_1.clone()).send(); + let resp_2 = srv.block_on(req_2).unwrap(); + let cookie_2 = resp_2.cookies().unwrap().clone() + .into_iter().find(|c| c.name() == "test-session"); + assert_eq!(cookie_2, None); + + + // Step 3: POST to do_something, including session cookie #1 in request + // - adds new session state in redis: {"counter": 1} + // - response should be: {"counter": 1, "user_id": None} + let req_3 = srv.post("/do_something").cookie(cookie_1.clone()).send(); + let mut resp_3 = srv.block_on(req_3).unwrap(); + let result_3 = block_on(resp_3.json::()).unwrap(); + assert_eq!(result_3, IndexResponse{user_id: None, counter: 1}); + + + // Step 4: POST again to do_something, including session cookie #1 in request + // - updates session state in redis: {"counter": 2} + // - response should be: {"counter": 2, "user_id": None} + let req_4 = srv.post("/do_something").cookie(cookie_1.clone()).send(); + let mut resp_4 = srv.block_on(req_4).unwrap(); + let result_4 = block_on(resp_4.json::()).unwrap(); + assert_eq!(result_4, IndexResponse{user_id: None, counter: 2}); + + + // Step 5: POST to login, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #2) + // - updates session state in redis: {"counter": 2, "user_id": "ferris"} + let req_5 = srv.post("/login") + .cookie(cookie_1.clone()) + .send_json(&json!({"user_id": "ferris"})); + let mut resp_5 = srv.block_on(req_5).unwrap(); + let cookie_2 = resp_5.cookies().unwrap().clone() + .into_iter().find(|c| c.name() == "test-session") + .unwrap(); + assert_eq!(true, cookie_1.value().to_string() != cookie_2.value().to_string()); + + let result_5 = block_on(resp_5.json::()).unwrap(); + assert_eq!(result_5, IndexResponse{user_id: Some("ferris".into()), counter: 2}); + + + // Step 6: GET index, including session cookie #2 in request + // - response should be: {"counter": 2, "user_id": "ferris"} + let req_6 = srv.get("/") + .cookie(cookie_2.clone()) + .send(); + let mut resp_6 = srv.block_on(req_6).unwrap(); + let result_6 = block_on(resp_6.json::()).unwrap(); + assert_eq!(result_6, IndexResponse{user_id: Some("ferris".into()), counter: 2}); + + + // Step 7: POST again to do_something, including session cookie #2 in request + // - updates session state in redis: {"counter": 3, "user_id": "ferris"} + // - response should be: {"counter": 2, "user_id": None} + let req_7 = srv.post("/do_something").cookie(cookie_2.clone()).send(); + let mut resp_7 = srv.block_on(req_7).unwrap(); + let result_7 = block_on(resp_7.json::()).unwrap(); + assert_eq!(result_7, IndexResponse{user_id: Some("ferris".into()), counter: 3}); + + + // Step 8: GET index, including session cookie #1 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + let req_8 = srv.get("/") + .cookie(cookie_1.clone()) + .send(); + let mut resp_8 = srv.block_on(req_8).unwrap(); + let cookie_3 = resp_8.cookies().unwrap().clone() + .into_iter().find(|c| c.name() == "test-session") + .unwrap(); + let result_8 = block_on(resp_8.json::()).unwrap(); + assert_eq!(result_8, IndexResponse{user_id: None, counter: 0}); + assert!(cookie_3.value().to_string() != cookie_2.value().to_string()); + + + // Step 9: POST to logout, including session cookie #2 + // - set-cookie actix-session will be in response with session cookie #2 + // invalidation logic + let req_9 = srv.post("/logout") + .cookie(cookie_2.clone()) + .send(); + let resp_9 = srv.block_on(req_9).unwrap(); + let cookie_4 = resp_9.cookies().unwrap().clone() + .into_iter().find(|c| c.name() == "test-session") + .unwrap(); + assert!(&time::now().tm_year != &cookie_4.expires().map(|t| t.tm_year).unwrap()); + + + // Step 10: GET index, including session cookie #2 in request + // - set-cookie actix-session will be in response (session cookie #3) + // - response should be: {"counter": 0, "user_id": None} + let req_10 = srv.get("/") + .cookie(cookie_2.clone()) + .send(); + let mut resp_10 = srv.block_on(req_10).unwrap(); + let result_10 = block_on(resp_10.json::()).unwrap(); + assert_eq!(result_10, IndexResponse{user_id: None, counter: 0}); + + let cookie_5 = resp_10.cookies().unwrap().clone() + .into_iter().find(|c| c.name() == "test-session") + .unwrap(); + assert!(cookie_5.value().to_string() != cookie_2.value().to_string()); + } +} From 3d9c1c55d6a25793ab3c71b197865347e452ccc2 Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 11 Jul 2019 15:19:22 -0400 Subject: [PATCH 64/84] updated CHANGES.md --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3e22d0019..7dc8a36a9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.6.1 (2019-07-11) + +* added comprehensive tests for session workflow + ## 0.6.0 (2019-07-08) From 7df26cb26979feec6b47486a5b3d0e3a9f11a420 Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 11 Jul 2019 15:19:37 -0400 Subject: [PATCH 65/84] updated CHANGES.md --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b2c61e0cb..a3ad2c8f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.6.0" +version = "0.6.1" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" From b874e0311a519d5568b3c8803c5503a5f204be4b Mon Sep 17 00:00:00 2001 From: dowwie Date: Thu, 11 Jul 2019 15:35:43 -0400 Subject: [PATCH 66/84] added derive feature for serde --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a3ad2c8f3..434f6ea26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ actix-utils = { version = "0.4.2", optional = true } actix-service = { version = "0.4.1", optional = true } actix-session = { version = "0.2.0", optional = true } rand = { version = "0.7.0", optional = true } -serde = { version = "1.0.94", optional = true } +serde = { version = "1.0.94", optional = true , features = ["derive"]} serde_json = { version = "1.0.40", optional = true } env_logger = "0.6.2" actix-http-test = "0.2.2" From 085ee4c394fd0a39adb2dbcd0f029bee0b14aadf Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 Jul 2019 14:10:23 +0600 Subject: [PATCH 67/84] remove ClonableService usage --- CHANGES.md | 4 +- Cargo.toml | 4 +- src/session.rs | 372 ++++++++++++++++++++++++++++--------------------- 3 files changed, 222 insertions(+), 158 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7dc8a36a9..ee7e0799a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes -## 0.6.1 (2019-07-11) +## 0.6.1 (2019-07-19) + +* remove ClonableService usage * added comprehensive tests for session workflow diff --git a/Cargo.toml b/Cargo.toml index 434f6ea26..e87513d2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ time = "0.1.42" # actix web session actix-web = { version = "1.0.3", optional = true } -actix-utils = { version = "0.4.2", optional = true } +actix-utils = { version = "0.4.5", optional = true } actix-service = { version = "0.4.1", optional = true } actix-session = { version = "0.2.0", optional = true } rand = { version = "0.7.0", optional = true } @@ -50,7 +50,7 @@ serde = { version = "1.0.94", optional = true , features = ["derive"]} serde_json = { version = "1.0.40", optional = true } env_logger = "0.6.2" actix-http-test = "0.2.2" -actix-http = "0.2.5" +actix-http = "0.2.6" [dev-dependencies] env_logger = "0.6" diff --git a/src/session.rs b/src/session.rs index 2de0b2d87..a395f0fec 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,8 +1,9 @@ +use std::cell::RefCell; use std::{collections::HashMap, iter, rc::Rc}; + use actix::prelude::*; use actix_service::{Service, Transform}; use actix_session::{Session, SessionStatus}; -use actix_utils::cloneable::CloneableService; use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::header::{self, HeaderValue}; @@ -103,7 +104,7 @@ where fn new_transform(&self, service: S) -> Self::Future { ok(RedisSessionMiddleware { - service: CloneableService::new(service), + service: Rc::new(RefCell::new(service)), inner: self.0.clone(), }) } @@ -111,7 +112,7 @@ where /// Cookie session middleware pub struct RedisSessionMiddleware { - service: CloneableService, + service: Rc>, inner: Rc, } @@ -128,7 +129,7 @@ where type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.poll_ready() + self.service.borrow_mut().poll_ready() } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { @@ -145,53 +146,50 @@ where srv.call(req).and_then(move |mut res| { match Session::get_changes(&mut res) { - (SessionStatus::Unchanged, None) => - Either::A(Either::A(Either::A(ok(res)))), - (SessionStatus::Unchanged, Some(state)) => - Either::A(Either::A(Either::B( - if value.is_none(){ // implies the session is new - Either::A( - inner.update(res, state, value) - ) - } else { - Either::B( - ok(res) - ) - } - ))), - (SessionStatus::Changed, Some(state)) => - Either::A(Either::B(Either::A(inner.update(res, state, value)))), + (SessionStatus::Unchanged, None) => { + Either::A(Either::A(Either::A(ok(res)))) + } + (SessionStatus::Unchanged, Some(state)) => { + Either::A(Either::A(Either::B(if value.is_none() { + // implies the session is new + Either::A(inner.update(res, state, value)) + } else { + Either::B(ok(res)) + }))) + } + (SessionStatus::Changed, Some(state)) => { + Either::A(Either::B(Either::A(inner.update(res, state, value)))) + } (SessionStatus::Purged, Some(_)) => { - if let Some(val) = value { - Either::A(Either::B(Either::B(Either::A( - inner.clear_cache(val) - .and_then(move |_| - match inner.remove_cookie(&mut res){ - Ok(_) => Either::A(ok(res)), - Err(_err) => Either::B(err( - error::ErrorInternalServerError(_err))) - }) - )))) - } else { - Either::A(Either::B(Either::B(Either::B( - err(error::ErrorInternalServerError("unexpected")) - )))) - } - }, - (SessionStatus::Renewed, Some(state)) => { + if let Some(val) = value { + Either::A(Either::B(Either::B(Either::A( + inner.clear_cache(val).and_then(move |_| { + match inner.remove_cookie(&mut res) { + Ok(_) => Either::A(ok(res)), + Err(_err) => Either::B(err( + error::ErrorInternalServerError(_err), + )), + } + }), + )))) + } else { + Either::A(Either::B(Either::B(Either::B(err( + error::ErrorInternalServerError("unexpected"), + ))))) + } + } + (SessionStatus::Renewed, Some(state)) => { if let Some(val) = value { Either::B(Either::A( - inner.clear_cache(val) - .and_then(move |_| - inner.update(res, state, None)) + inner + .clear_cache(val) + .and_then(move |_| inner.update(res, state, None)), )) } else { - Either::B(Either::B( - inner.update(res, state, None) - )) + Either::B(Either::B(inner.update(res, state, None))) } - }, - (_, None) => unreachable!() + } + (_, None) => unreachable!(), } }) })) @@ -329,97 +327,102 @@ impl Inner { } } - /// removes cache entry - fn clear_cache(&self, key: String) - -> impl Future { + /// removes cache entry + fn clear_cache(&self, key: String) -> impl Future { self.addr - .send(Command(resp_array!["DEL", key])) - .map_err(Error::from) - .and_then(|res| { - match res { - // redis responds with number of deleted records - Ok(RespValue::Integer(x)) if x > 0 => Ok(()), - _ => Err(error::ErrorInternalServerError( - "failed to remove session from cache")) - } - }) - } + .send(Command(resp_array!["DEL", key])) + .map_err(Error::from) + .and_then(|res| { + match res { + // redis responds with number of deleted records + Ok(RespValue::Integer(x)) if x > 0 => Ok(()), + _ => Err(error::ErrorInternalServerError( + "failed to remove session from cache", + )), + } + }) + } /// invalidates session cookie - fn remove_cookie(&self, res: &mut ServiceResponse) - -> Result<(), Error> { + fn remove_cookie(&self, res: &mut ServiceResponse) -> Result<(), Error> { let mut cookie = Cookie::named(self.name.clone()); cookie.set_value(""); cookie.set_max_age(Duration::seconds(0)); cookie.set_expires(time::now() - Duration::days(365)); let val = HeaderValue::from_str(&cookie.to_string()) - .map_err(|err| error::ErrorInternalServerError(err))?; + .map_err(|err| error::ErrorInternalServerError(err))?; res.headers_mut().append(header::SET_COOKIE, val); Ok(()) } } - #[cfg(test)] mod test { use super::*; - use actix_http::{HttpService, httpmessage::HttpMessage}; - use actix_http_test::{TestServer, block_on}; + use actix_http::{httpmessage::HttpMessage, HttpService}; + use actix_http_test::{block_on, TestServer}; use actix_session::Session; - use actix_web::{middleware, web, App, HttpResponse, HttpServer, Result, - web::{resource, get, post}}; + use actix_web::{ + middleware, web, + web::{get, post, resource}, + App, HttpResponse, HttpServer, Result, + }; use serde::{Deserialize, Serialize}; use serde_json::json; use time; - #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct IndexResponse { user_id: Option, - counter: i32 + counter: i32, } fn index(session: Session) -> Result { let user_id: Option = session.get::("user_id").unwrap(); - let counter: i32 = session.get::("counter") - .unwrap_or(Some(0)) - .unwrap_or(0); + let counter: i32 = session + .get::("counter") + .unwrap_or(Some(0)) + .unwrap_or(0); - Ok(HttpResponse::Ok().json(IndexResponse{user_id, counter})) + Ok(HttpResponse::Ok().json(IndexResponse { user_id, counter })) } - fn do_something(session: Session) -> Result { let user_id: Option = session.get::("user_id").unwrap(); - let counter: i32 = session.get::("counter") - .unwrap_or(Some(0)) - .map_or(1, |inner| inner + 1); + let counter: i32 = session + .get::("counter") + .unwrap_or(Some(0)) + .map_or(1, |inner| inner + 1); session.set("counter", counter)?; - Ok(HttpResponse::Ok().json(IndexResponse{user_id, counter})) + Ok(HttpResponse::Ok().json(IndexResponse { user_id, counter })) } #[derive(Deserialize)] struct Identity { - user_id: String + user_id: String, } fn login(user_id: web::Json, session: Session) -> Result { let id = user_id.into_inner().user_id; session.set("user_id", &id)?; session.renew(); - let counter: i32 = session.get::("counter") - .unwrap_or(Some(0)) - .unwrap_or(0); + let counter: i32 = session + .get::("counter") + .unwrap_or(Some(0)) + .unwrap_or(0); - Ok(HttpResponse::Ok().json(IndexResponse{user_id: Some(id), counter})) + Ok(HttpResponse::Ok().json(IndexResponse { + user_id: Some(id), + counter, + })) } fn logout(session: Session) -> Result { let id: Option = session.get("user_id")?; - if let Some(x) = id{ + if let Some(x) = id { session.purge(); Ok(format!("Logged out: {}", x).into()) } else { @@ -429,17 +432,17 @@ mod test { #[test] fn test_workflow() { - // Step 1: GET index + // Step 1: GET index // - set-cookie actix-session will be in response (session cookie #1) // - response should be: {"counter": 0, "user_id": None} // Step 2: GET index, including session cookie #1 in request // - set-cookie will *not* be in response // - response should be: {"counter": 0, "user_id": None} // Step 3: POST to do_something, including session cookie #1 in request - // - adds new session state in redis: {"counter": 1} + // - adds new session state in redis: {"counter": 1} // - response should be: {"counter": 1, "user_id": None} // Step 4: POST again to do_something, including session cookie #1 in request - // - updates session state in redis: {"counter": 2} + // - updates session state in redis: {"counter": 2} // - response should be: {"counter": 2, "user_id": None} // Step 5: POST to login, including session cookie #1 in request // - set-cookie actix-session will be in response (session cookie #2) @@ -459,86 +462,124 @@ mod test { // - set-cookie actix-session will be in response (session cookie #3) // - response should be: {"counter": 0, "user_id": None} - let mut srv = - TestServer::new(|| { - HttpService::new( - App::new() - .wrap(RedisSession::new("127.0.0.1:6379", &[0; 32]) - .cookie_name("test-session")) - .wrap(middleware::Logger::default()) - .service(resource("/").route(get().to(index))) - .service(resource("/do_something").route(post().to(do_something))) - .service(resource("/login").route(post().to(login))) - .service(resource("/logout").route(post().to(logout))) - ) - }); + let mut srv = TestServer::new(|| { + HttpService::new( + App::new() + .wrap( + RedisSession::new("127.0.0.1:6379", &[0; 32]) + .cookie_name("test-session"), + ) + .wrap(middleware::Logger::default()) + .service(resource("/").route(get().to(index))) + .service(resource("/do_something").route(post().to(do_something))) + .service(resource("/login").route(post().to(login))) + .service(resource("/logout").route(post().to(logout))), + ) + }); - - // Step 1: GET index + // Step 1: GET index // - set-cookie actix-session will be in response (session cookie #1) // - response should be: {"counter": 0, "user_id": None} let req_1a = srv.get("/").send(); let mut resp_1 = srv.block_on(req_1a).unwrap(); - let cookie_1 = resp_1.cookies().unwrap().clone() - .into_iter().find(|c| c.name() == "test-session") - .unwrap(); + let cookie_1 = resp_1 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); let result_1 = block_on(resp_1.json::()).unwrap(); - assert_eq!(result_1, IndexResponse{user_id: None, counter: 0}); - + assert_eq!( + result_1, + IndexResponse { + user_id: None, + counter: 0 + } + ); // Step 2: GET index, including session cookie #1 in request // - set-cookie will *not* be in response // - response should be: {"counter": 0, "user_id": None} let req_2 = srv.get("/").cookie(cookie_1.clone()).send(); let resp_2 = srv.block_on(req_2).unwrap(); - let cookie_2 = resp_2.cookies().unwrap().clone() - .into_iter().find(|c| c.name() == "test-session"); + let cookie_2 = resp_2 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session"); assert_eq!(cookie_2, None); - // Step 3: POST to do_something, including session cookie #1 in request - // - adds new session state in redis: {"counter": 1} + // - adds new session state in redis: {"counter": 1} // - response should be: {"counter": 1, "user_id": None} let req_3 = srv.post("/do_something").cookie(cookie_1.clone()).send(); let mut resp_3 = srv.block_on(req_3).unwrap(); let result_3 = block_on(resp_3.json::()).unwrap(); - assert_eq!(result_3, IndexResponse{user_id: None, counter: 1}); - + assert_eq!( + result_3, + IndexResponse { + user_id: None, + counter: 1 + } + ); // Step 4: POST again to do_something, including session cookie #1 in request - // - updates session state in redis: {"counter": 2} + // - updates session state in redis: {"counter": 2} // - response should be: {"counter": 2, "user_id": None} let req_4 = srv.post("/do_something").cookie(cookie_1.clone()).send(); let mut resp_4 = srv.block_on(req_4).unwrap(); let result_4 = block_on(resp_4.json::()).unwrap(); - assert_eq!(result_4, IndexResponse{user_id: None, counter: 2}); - + assert_eq!( + result_4, + IndexResponse { + user_id: None, + counter: 2 + } + ); // Step 5: POST to login, including session cookie #1 in request // - set-cookie actix-session will be in response (session cookie #2) // - updates session state in redis: {"counter": 2, "user_id": "ferris"} - let req_5 = srv.post("/login") - .cookie(cookie_1.clone()) - .send_json(&json!({"user_id": "ferris"})); + let req_5 = srv + .post("/login") + .cookie(cookie_1.clone()) + .send_json(&json!({"user_id": "ferris"})); let mut resp_5 = srv.block_on(req_5).unwrap(); - let cookie_2 = resp_5.cookies().unwrap().clone() - .into_iter().find(|c| c.name() == "test-session") - .unwrap(); - assert_eq!(true, cookie_1.value().to_string() != cookie_2.value().to_string()); + let cookie_2 = resp_5 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); + assert_eq!( + true, + cookie_1.value().to_string() != cookie_2.value().to_string() + ); let result_5 = block_on(resp_5.json::()).unwrap(); - assert_eq!(result_5, IndexResponse{user_id: Some("ferris".into()), counter: 2}); - + assert_eq!( + result_5, + IndexResponse { + user_id: Some("ferris".into()), + counter: 2 + } + ); // Step 6: GET index, including session cookie #2 in request // - response should be: {"counter": 2, "user_id": "ferris"} - let req_6 = srv.get("/") - .cookie(cookie_2.clone()) - .send(); + let req_6 = srv.get("/").cookie(cookie_2.clone()).send(); let mut resp_6 = srv.block_on(req_6).unwrap(); let result_6 = block_on(resp_6.json::()).unwrap(); - assert_eq!(result_6, IndexResponse{user_id: Some("ferris".into()), counter: 2}); - + assert_eq!( + result_6, + IndexResponse { + user_id: Some("ferris".into()), + counter: 2 + } + ); // Step 7: POST again to do_something, including session cookie #2 in request // - updates session state in redis: {"counter": 3, "user_id": "ferris"} @@ -546,50 +587,71 @@ mod test { let req_7 = srv.post("/do_something").cookie(cookie_2.clone()).send(); let mut resp_7 = srv.block_on(req_7).unwrap(); let result_7 = block_on(resp_7.json::()).unwrap(); - assert_eq!(result_7, IndexResponse{user_id: Some("ferris".into()), counter: 3}); - + assert_eq!( + result_7, + IndexResponse { + user_id: Some("ferris".into()), + counter: 3 + } + ); // Step 8: GET index, including session cookie #1 in request // - set-cookie actix-session will be in response (session cookie #3) // - response should be: {"counter": 0, "user_id": None} - let req_8 = srv.get("/") - .cookie(cookie_1.clone()) - .send(); + let req_8 = srv.get("/").cookie(cookie_1.clone()).send(); let mut resp_8 = srv.block_on(req_8).unwrap(); - let cookie_3 = resp_8.cookies().unwrap().clone() - .into_iter().find(|c| c.name() == "test-session") - .unwrap(); + let cookie_3 = resp_8 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); let result_8 = block_on(resp_8.json::()).unwrap(); - assert_eq!(result_8, IndexResponse{user_id: None, counter: 0}); + assert_eq!( + result_8, + IndexResponse { + user_id: None, + counter: 0 + } + ); assert!(cookie_3.value().to_string() != cookie_2.value().to_string()); - // Step 9: POST to logout, including session cookie #2 // - set-cookie actix-session will be in response with session cookie #2 // invalidation logic - let req_9 = srv.post("/logout") - .cookie(cookie_2.clone()) - .send(); + let req_9 = srv.post("/logout").cookie(cookie_2.clone()).send(); let resp_9 = srv.block_on(req_9).unwrap(); - let cookie_4 = resp_9.cookies().unwrap().clone() - .into_iter().find(|c| c.name() == "test-session") - .unwrap(); + let cookie_4 = resp_9 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); assert!(&time::now().tm_year != &cookie_4.expires().map(|t| t.tm_year).unwrap()); - // Step 10: GET index, including session cookie #2 in request // - set-cookie actix-session will be in response (session cookie #3) // - response should be: {"counter": 0, "user_id": None} - let req_10 = srv.get("/") - .cookie(cookie_2.clone()) - .send(); + let req_10 = srv.get("/").cookie(cookie_2.clone()).send(); let mut resp_10 = srv.block_on(req_10).unwrap(); let result_10 = block_on(resp_10.json::()).unwrap(); - assert_eq!(result_10, IndexResponse{user_id: None, counter: 0}); - - let cookie_5 = resp_10.cookies().unwrap().clone() - .into_iter().find(|c| c.name() == "test-session") - .unwrap(); + assert_eq!( + result_10, + IndexResponse { + user_id: None, + counter: 0 + } + ); + + let cookie_5 = resp_10 + .cookies() + .unwrap() + .clone() + .into_iter() + .find(|c| c.name() == "test-session") + .unwrap(); assert!(cookie_5.value().to_string() != cookie_2.value().to_string()); } } From 6498ef1af188de6959956642d575739727e8214b Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 23 Sep 2019 09:38:11 -0400 Subject: [PATCH 68/84] introduced configurable cache_key strategy. updated some deps --- Cargo.toml | 10 +++++----- src/session.rs | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e87513d2a..6cf5d1613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,16 +41,16 @@ redis-async = "0.4.5" time = "0.1.42" # actix web session -actix-web = { version = "1.0.3", optional = true } +actix-web = { version = "1.0.7", optional = true } actix-utils = { version = "0.4.5", optional = true } -actix-service = { version = "0.4.1", optional = true } +actix-service = { version = "0.4.2", optional = true } actix-session = { version = "0.2.0", optional = true } rand = { version = "0.7.0", optional = true } -serde = { version = "1.0.94", optional = true , features = ["derive"]} +serde = { version = "1.0.101", optional = true, features = ["derive"] } serde_json = { version = "1.0.40", optional = true } env_logger = "0.6.2" -actix-http-test = "0.2.2" -actix-http = "0.2.6" +actix-http-test = "0.2.5" +actix-http = "0.2.10" [dev-dependencies] env_logger = "0.6" diff --git a/src/session.rs b/src/session.rs index a395f0fec..fbacfba06 100644 --- a/src/session.rs +++ b/src/session.rs @@ -32,6 +32,7 @@ impl RedisSession { pub fn new>(addr: S, key: &[u8]) -> RedisSession { RedisSession(Rc::new(Inner { key: Key::from_master(key), + cache_keygen: Box::new(|key: &str| format!("session:{}", &key)), ttl: "7200".to_owned(), addr: RedisActor::start(addr), name: "actix-session".to_owned(), @@ -86,6 +87,11 @@ impl RedisSession { Rc::get_mut(&mut self.0).unwrap().same_site = Some(same_site); self } + + pub fn cache_keygen(mut self, keygen: Box String>) -> Self { + Rc::get_mut(&mut self.0).unwrap().cache_keygen = keygen; + self + } } impl Transform for RedisSession @@ -126,7 +132,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.borrow_mut().poll_ready() @@ -198,6 +204,7 @@ where struct Inner { key: Key, + cache_keygen: Box String>, ttl: String, addr: Addr, name: String, @@ -221,9 +228,10 @@ impl Inner { jar.add_original(cookie.clone()); if let Some(cookie) = jar.signed(&self.key).get(&self.name) { let value = cookie.value().to_owned(); + let cachekey = (self.cache_keygen)(&cookie.value()); return Either::A( self.addr - .send(Command(resp_array!["GET", cookie.value()])) + .send(Command(resp_array!["GET", cachekey])) .map_err(Error::from) .and_then(move |res| match res { Ok(val) => { @@ -303,12 +311,14 @@ impl Inner { (value, Some(jar)) }; + let cachekey = (self.cache_keygen)(&value); + let state: HashMap<_, _> = state.collect(); match serde_json::to_string(&state) { Err(e) => Either::A(err(e.into())), Ok(body) => Either::B( self.addr - .send(Command(resp_array!["SET", value, body, "EX", &self.ttl])) + .send(Command(resp_array!["SET", cachekey, body, "EX", &self.ttl])) .map_err(Error::from) .and_then(move |redis_result| match redis_result { Ok(_) => { @@ -329,8 +339,10 @@ impl Inner { /// removes cache entry fn clear_cache(&self, key: String) -> impl Future { + let cachekey = (self.cache_keygen)(&key); + self.addr - .send(Command(resp_array!["DEL", key])) + .send(Command(resp_array!["DEL", cachekey])) .map_err(Error::from) .and_then(|res| { match res { From 468118a8ee14b4f9d78dddaa7867b9637ca3e67c Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 25 Sep 2019 08:42:49 -0400 Subject: [PATCH 69/84] updated version of crate, CHANGES.md, documented cache_keygen method, ready for release --- CHANGES.md | 6 ++++++ Cargo.toml | 2 +- src/session.rs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ee7e0799a..59898cbab 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changes +## 0.7 (2019-09-25) + +* added cache_keygen functionality to RedisSession builder, enabling support for + customizable cache key creation + + ## 0.6.1 (2019-07-19) * remove ClonableService usage diff --git a/Cargo.toml b/Cargo.toml index 6cf5d1613..28b0e688c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.6.1" +version = "0.7.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" diff --git a/src/session.rs b/src/session.rs index fbacfba06..9cd903a59 100644 --- a/src/session.rs +++ b/src/session.rs @@ -88,6 +88,7 @@ impl RedisSession { self } + /// Set a custom cache key generation strategy, expecting session key as input pub fn cache_keygen(mut self, keygen: Box String>) -> Self { Rc::get_mut(&mut self.0).unwrap().cache_keygen = keygen; self From 9f64a38e172a65ae378f8b526abdacaec7c79faf Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 30 Sep 2019 09:19:24 +0900 Subject: [PATCH 70/84] Fix clippy warnings (#31) --- src/redis.rs | 2 +- src/session.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/redis.rs b/src/redis.rs index 7d4debadb..296273f4c 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -43,7 +43,7 @@ impl RedisActor { Supervisor::start(|_| RedisActor { addr, cell: None, - backoff: backoff, + backoff, queue: VecDeque::new(), }) } diff --git a/src/session.rs b/src/session.rs index 9cd903a59..a8e0da7ff 100644 --- a/src/session.rs +++ b/src/session.rs @@ -364,7 +364,7 @@ impl Inner { cookie.set_expires(time::now() - Duration::days(365)); let val = HeaderValue::from_str(&cookie.to_string()) - .map_err(|err| error::ErrorInternalServerError(err))?; + .map_err(error::ErrorInternalServerError)?; res.headers_mut().append(header::SET_COOKIE, val); Ok(()) From 25a254693394483575307a008d20f4efb75de1ce Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Dec 2019 23:46:03 +0600 Subject: [PATCH 71/84] migrate to actix 0.9 --- CHANGES.md | 4 + Cargo.toml | 29 +++-- src/redis.rs | 81 ++++++------ src/session.rs | 301 ++++++++++++++++++++++---------------------- tests/test_redis.rs | 69 ++++------ 5 files changed, 237 insertions(+), 247 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 59898cbab..2ab9fe9af 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.8.0-alpha.1 (2019-12-xx) + +* Migrate to actix 0.9 + ## 0.7 (2019-09-25) * added cache_keygen functionality to RedisSession builder, enabling support for diff --git a/Cargo.toml b/Cargo.toml index 28b0e688c..9bb5dce3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.7.0" +version = "0.8.0-alpha.1" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -25,32 +25,31 @@ codecov = { repository = "actix/actix-redis", branch = "master", service = "gith default = ["web"] # actix-web integration -web = ["actix/http", "actix-service", "actix-utils", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] +web = ["actix/http", "actix-service", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] [dependencies] -actix = "0.8.3" +actix = "0.9.0-alpha.1" +actix-utils = "1.0.3" log = "0.4.6" backoff = "0.1.5" -derive_more = "0.15.0" -futures = "0.1.28" -tokio-io = "0.1.12" -tokio-codec = "0.1.1" -tokio-tcp = "0.1.3" -redis-async = "0.4.5" +derive_more = "0.99.2" +futures = "0.3.1" +redis-async = "0.6.1" +actix-rt = "1.0.0" time = "0.1.42" +tokio = "0.2.4" +tokio-util = "0.2.0" # actix web session -actix-web = { version = "1.0.7", optional = true } -actix-utils = { version = "0.4.5", optional = true } -actix-service = { version = "0.4.2", optional = true } -actix-session = { version = "0.2.0", optional = true } +actix-web = { version = "2.0.0-alpha.6", optional = true } +actix-service = { version = "1.0.0", optional = true } +actix-session = { version = "0.3.0-alpha", optional = true } rand = { version = "0.7.0", optional = true } serde = { version = "1.0.101", optional = true, features = ["derive"] } serde_json = { version = "1.0.40", optional = true } env_logger = "0.6.2" -actix-http-test = "0.2.5" -actix-http = "0.2.10" [dev-dependencies] env_logger = "0.6" +actix-http = "1.0.0" diff --git a/src/redis.rs b/src/redis.rs index 296273f4c..c9aa643e7 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -3,16 +3,15 @@ use std::io; use actix::actors::resolver::{Connect, Resolver}; use actix::prelude::*; +use actix_utils::oneshot; use backoff::backoff::Backoff; use backoff::ExponentialBackoff; -use futures::unsync::oneshot; -use futures::Future; +use futures::FutureExt; use redis_async::error::Error as RespError; use redis_async::resp::{RespCodec, RespValue}; -use tokio_codec::FramedRead; -use tokio_io::io::WriteHalf; -use tokio_io::AsyncRead; -use tokio_tcp::TcpStream; +use tokio::io::{split, WriteHalf}; +use tokio::net::TcpStream; +use tokio_util::codec::FramedRead; use crate::Error; @@ -57,20 +56,30 @@ impl Actor for RedisActor { .send(Connect::host(self.addr.as_str())) .into_actor(self) .map(|res, act, ctx| match res { - Ok(stream) => { - info!("Connected to redis server: {}", act.addr); + Ok(res) => match res { + Ok(stream) => { + info!("Connected to redis server: {}", act.addr); - let (r, w) = stream.split(); + let (r, w) = split(stream); - // configure write side of the connection - let framed = actix::io::FramedWrite::new(w, RespCodec, ctx); - act.cell = Some(framed); + // configure write side of the connection + let framed = actix::io::FramedWrite::new(w, RespCodec, ctx); + act.cell = Some(framed); - // read side of the connection - ctx.add_stream(FramedRead::new(r, RespCodec)); + // read side of the connection + ctx.add_stream(FramedRead::new(r, RespCodec)); - act.backoff.reset(); - } + act.backoff.reset(); + } + Err(err) => { + error!("Can not connect to redis server: {}", err); + // re-connect with backoff time. + // we stop current context, supervisor will restart it. + if let Some(timeout) = act.backoff.next_backoff() { + ctx.run_later(timeout, |_, ctx| ctx.stop()); + } + } + }, Err(err) => { error!("Can not connect to redis server: {}", err); // re-connect with backoff time. @@ -80,14 +89,6 @@ impl Actor for RedisActor { } } }) - .map_err(|err, act, ctx| { - error!("Can not connect to redis server: {}", err); - // re-connect with backoff time. - // we stop current context, supervisor will restart it. - if let Some(timeout) = act.backoff.next_backoff() { - ctx.run_later(timeout, |_, ctx| ctx.stop()); - } - }) .wait(ctx); } } @@ -108,23 +109,26 @@ impl actix::io::WriteHandler for RedisActor { } } -impl StreamHandler for RedisActor { - fn error(&mut self, err: RespError, _: &mut Self::Context) -> Running { - if let Some(tx) = self.queue.pop_front() { - let _ = tx.send(Err(err.into())); - } - Running::Stop - } - - fn handle(&mut self, msg: RespValue, _: &mut Self::Context) { - if let Some(tx) = self.queue.pop_front() { - let _ = tx.send(Ok(msg)); +impl StreamHandler> for RedisActor { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + match msg { + Err(e) => { + if let Some(tx) = self.queue.pop_front() { + let _ = tx.send(Err(e.into())); + } + ctx.stop(); + } + Ok(val) => { + if let Some(tx) = self.queue.pop_front() { + let _ = tx.send(Ok(val)); + } + } } } } impl Handler for RedisActor { - type Result = ResponseFuture; + type Result = ResponseFuture>; fn handle(&mut self, msg: Command, _: &mut Self::Context) -> Self::Result { let (tx, rx) = oneshot::channel(); @@ -135,6 +139,9 @@ impl Handler for RedisActor { let _ = tx.send(Err(Error::NotConnected)); } - Box::new(rx.map_err(|_| Error::Disconnected).and_then(|res| res)) + Box::new(rx.map(|res| match res { + Ok(res) => res, + Err(_) => Err(Error::Disconnected), + })) } } diff --git a/src/session.rs b/src/session.rs index a8e0da7ff..5728809bf 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,4 +1,6 @@ use std::cell::RefCell; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{collections::HashMap, iter, rc::Rc}; use actix::prelude::*; @@ -8,8 +10,7 @@ use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::header::{self, HeaderValue}; use actix_web::{error, Error, HttpMessage}; -use futures::future::{err, ok, Either, Future, FutureResult}; -use futures::Poll; +use futures::future::{err, ok, Either, Future, FutureExt, Ready}; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use redis_async::resp::RespValue; use time::{self, Duration}; @@ -107,7 +108,7 @@ where type Error = S::Error; type InitError = (); type Transform = RedisSessionMiddleware; - type Future = FutureResult; + type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { ok(RedisSessionMiddleware { @@ -133,17 +134,18 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Pin>>>; - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - self.service.borrow_mut().poll_ready() + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.borrow_mut().poll_ready(cx) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let mut srv = self.service.clone(); let inner = self.inner.clone(); - Box::new(self.inner.load(&req).and_then(move |state| { + Box::pin(async move { + let state = inner.load(&req).await?; let value = if let Some((state, value)) = state { Session::set_session(state.into_iter(), &mut req); Some(value) @@ -151,55 +153,43 @@ where None }; - srv.call(req).and_then(move |mut res| { - match Session::get_changes(&mut res) { - (SessionStatus::Unchanged, None) => { - Either::A(Either::A(Either::A(ok(res)))) + let mut res = srv.call(req).await?; + + match Session::get_changes(&mut res) { + (SessionStatus::Unchanged, None) => Ok(res), + (SessionStatus::Unchanged, Some(state)) => { + if value.is_none() { + // implies the session is new + inner.update(res, state, value).await + } else { + Ok(res) } - (SessionStatus::Unchanged, Some(state)) => { - Either::A(Either::A(Either::B(if value.is_none() { - // implies the session is new - Either::A(inner.update(res, state, value)) - } else { - Either::B(ok(res)) - }))) - } - (SessionStatus::Changed, Some(state)) => { - Either::A(Either::B(Either::A(inner.update(res, state, value)))) - } - (SessionStatus::Purged, Some(_)) => { - if let Some(val) = value { - Either::A(Either::B(Either::B(Either::A( - inner.clear_cache(val).and_then(move |_| { - match inner.remove_cookie(&mut res) { - Ok(_) => Either::A(ok(res)), - Err(_err) => Either::B(err( - error::ErrorInternalServerError(_err), - )), - } - }), - )))) - } else { - Either::A(Either::B(Either::B(Either::B(err( - error::ErrorInternalServerError("unexpected"), - ))))) - } - } - (SessionStatus::Renewed, Some(state)) => { - if let Some(val) = value { - Either::B(Either::A( - inner - .clear_cache(val) - .and_then(move |_| inner.update(res, state, None)), - )) - } else { - Either::B(Either::B(inner.update(res, state, None))) - } - } - (_, None) => unreachable!(), } - }) - })) + (SessionStatus::Changed, Some(state)) => { + inner.update(res, state, value).await + } + (SessionStatus::Purged, Some(_)) => { + if let Some(val) = value { + inner.clear_cache(val).await?; + match inner.remove_cookie(&mut res) { + Ok(_) => Ok(res), + Err(_err) => Err(error::ErrorInternalServerError(_err)), + } + } else { + Err(error::ErrorInternalServerError("unexpected")) + } + } + (SessionStatus::Renewed, Some(state)) => { + if let Some(val) = value { + inner.clear_cache(val).await?; + inner.update(res, state, None).await + } else { + inner.update(res, state, None).await + } + } + (_, None) => unreachable!(), + } + }) } } @@ -220,7 +210,7 @@ impl Inner { fn load( &self, req: &ServiceRequest, - ) -> impl Future, String)>, Error = Error> + ) -> impl Future, String)>, Error>> { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { @@ -230,47 +220,52 @@ impl Inner { if let Some(cookie) = jar.signed(&self.key).get(&self.name) { let value = cookie.value().to_owned(); let cachekey = (self.cache_keygen)(&cookie.value()); - return Either::A( - self.addr - .send(Command(resp_array!["GET", cachekey])) - .map_err(Error::from) - .and_then(move |res| match res { - Ok(val) => { - match val { - RespValue::Error(err) => { - return Err( - error::ErrorInternalServerError(err), - ); - } - RespValue::SimpleString(s) => { - if let Ok(val) = serde_json::from_str(&s) - { - return Ok(Some((val, value))); + return Either::Left( + self.addr.send(Command(resp_array!["GET", cachekey])).map( + |result| match result { + Err(e) => Err(Error::from(e)), + Ok(res) => match res { + Ok(val) => { + match val { + RespValue::Error(err) => { + return Err( + error::ErrorInternalServerError( + err, + ), + ); } - } - RespValue::BulkString(s) => { - if let Ok(val) = - serde_json::from_slice(&s) - { - return Ok(Some((val, value))); + 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) } - Ok(None) - } - Err(err) => { - Err(error::ErrorInternalServerError(err)) - } - }), + Err(err) => { + Err(error::ErrorInternalServerError(err)) + } + }, + }, + ), ); } else { - return Either::B(ok(None)); + return Either::Right(ok(None)); } } } } - Either::B(ok(None)) + Either::Right(ok(None)) } fn update( @@ -278,7 +273,7 @@ impl Inner { mut res: ServiceResponse, state: impl Iterator, value: Option, - ) -> impl Future, Error = Error> { + ) -> impl Future, Error>> { let (value, jar) = if let Some(value) = value { (value.clone(), None) } else { @@ -316,42 +311,47 @@ impl Inner { let state: HashMap<_, _> = state.collect(); match serde_json::to_string(&state) { - Err(e) => Either::A(err(e.into())), - Ok(body) => Either::B( + Err(e) => Either::Left(err(e.into())), + Ok(body) => Either::Right( self.addr .send(Command(resp_array!["SET", cachekey, body, "EX", &self.ttl])) - .map_err(Error::from) - .and_then(move |redis_result| match redis_result { - Ok(_) => { - if let Some(jar) = jar { - for cookie in jar.delta() { - let val = - HeaderValue::from_str(&cookie.to_string())?; - res.headers_mut().append(header::SET_COOKIE, val); + .map(|result| match result { + Err(e) => Err(Error::from(e)), + Ok(redis_result) => match redis_result { + Ok(_) => { + if let Some(jar) = jar { + for cookie in jar.delta() { + let val = + HeaderValue::from_str(&cookie.to_string())?; + res.headers_mut() + .append(header::SET_COOKIE, val); + } } + Ok(res) } - Ok(res) - } - Err(err) => Err(error::ErrorInternalServerError(err)), + Err(err) => Err(error::ErrorInternalServerError(err)), + }, }), ), } } /// removes cache entry - fn clear_cache(&self, key: String) -> impl Future { + fn clear_cache(&self, key: String) -> impl Future> { let cachekey = (self.cache_keygen)(&key); self.addr .send(Command(resp_array!["DEL", cachekey])) - .map_err(Error::from) - .and_then(|res| { - match res { - // redis responds with number of deleted records - Ok(RespValue::Integer(x)) if x > 0 => Ok(()), - _ => Err(error::ErrorInternalServerError( - "failed to remove session from cache", - )), + .map(|res| match res { + Err(e) => Err(Error::from(e)), + Ok(res) => { + match res { + // redis responds with number of deleted records + Ok(RespValue::Integer(x)) if x > 0 => Ok(()), + _ => Err(error::ErrorInternalServerError( + "failed to remove session from cache", + )), + } } }) } @@ -374,13 +374,12 @@ impl Inner { #[cfg(test)] mod test { use super::*; - use actix_http::{httpmessage::HttpMessage, HttpService}; - use actix_http_test::{block_on, TestServer}; + use actix_http::httpmessage::HttpMessage; use actix_session::Session; use actix_web::{ - middleware, web, + middleware, test, web, web::{get, post, resource}, - App, HttpResponse, HttpServer, Result, + App, HttpResponse, Result, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -392,7 +391,7 @@ mod test { counter: i32, } - fn index(session: Session) -> Result { + async fn index(session: Session) -> Result { let user_id: Option = session.get::("user_id").unwrap(); let counter: i32 = session .get::("counter") @@ -402,7 +401,7 @@ mod test { Ok(HttpResponse::Ok().json(IndexResponse { user_id, counter })) } - fn do_something(session: Session) -> Result { + async fn do_something(session: Session) -> Result { let user_id: Option = session.get::("user_id").unwrap(); let counter: i32 = session .get::("counter") @@ -417,7 +416,11 @@ mod test { struct Identity { user_id: String, } - fn login(user_id: web::Json, session: Session) -> Result { + + async fn login( + user_id: web::Json, + session: Session, + ) -> Result { let id = user_id.into_inner().user_id; session.set("user_id", &id)?; session.renew(); @@ -433,7 +436,7 @@ mod test { })) } - fn logout(session: Session) -> Result { + async fn logout(session: Session) -> Result { let id: Option = session.get("user_id")?; if let Some(x) = id { session.purge(); @@ -443,8 +446,8 @@ mod test { } } - #[test] - fn test_workflow() { + #[actix_rt::test] + async fn test_workflow() { // Step 1: GET index // - set-cookie actix-session will be in response (session cookie #1) // - response should be: {"counter": 0, "user_id": None} @@ -475,26 +478,24 @@ mod test { // - set-cookie actix-session will be in response (session cookie #3) // - response should be: {"counter": 0, "user_id": None} - let mut srv = TestServer::new(|| { - HttpService::new( - App::new() - .wrap( - RedisSession::new("127.0.0.1:6379", &[0; 32]) - .cookie_name("test-session"), - ) - .wrap(middleware::Logger::default()) - .service(resource("/").route(get().to(index))) - .service(resource("/do_something").route(post().to(do_something))) - .service(resource("/login").route(post().to(login))) - .service(resource("/logout").route(post().to(logout))), - ) + let srv = test::start(|| { + App::new() + .wrap( + RedisSession::new("127.0.0.1:6379", &[0; 32]) + .cookie_name("test-session"), + ) + .wrap(middleware::Logger::default()) + .service(resource("/").route(get().to(index))) + .service(resource("/do_something").route(post().to(do_something))) + .service(resource("/login").route(post().to(login))) + .service(resource("/logout").route(post().to(logout))) }); // Step 1: GET index // - set-cookie actix-session will be in response (session cookie #1) // - response should be: {"counter": 0, "user_id": None} let req_1a = srv.get("/").send(); - let mut resp_1 = srv.block_on(req_1a).unwrap(); + let mut resp_1 = req_1a.await.unwrap(); let cookie_1 = resp_1 .cookies() .unwrap() @@ -502,7 +503,7 @@ mod test { .into_iter() .find(|c| c.name() == "test-session") .unwrap(); - let result_1 = block_on(resp_1.json::()).unwrap(); + let result_1 = resp_1.json::().await.unwrap(); assert_eq!( result_1, IndexResponse { @@ -515,7 +516,7 @@ mod test { // - set-cookie will *not* be in response // - response should be: {"counter": 0, "user_id": None} let req_2 = srv.get("/").cookie(cookie_1.clone()).send(); - let resp_2 = srv.block_on(req_2).unwrap(); + let resp_2 = req_2.await.unwrap(); let cookie_2 = resp_2 .cookies() .unwrap() @@ -528,8 +529,8 @@ mod test { // - adds new session state in redis: {"counter": 1} // - response should be: {"counter": 1, "user_id": None} let req_3 = srv.post("/do_something").cookie(cookie_1.clone()).send(); - let mut resp_3 = srv.block_on(req_3).unwrap(); - let result_3 = block_on(resp_3.json::()).unwrap(); + let mut resp_3 = req_3.await.unwrap(); + let result_3 = resp_3.json::().await.unwrap(); assert_eq!( result_3, IndexResponse { @@ -542,8 +543,8 @@ mod test { // - updates session state in redis: {"counter": 2} // - response should be: {"counter": 2, "user_id": None} let req_4 = srv.post("/do_something").cookie(cookie_1.clone()).send(); - let mut resp_4 = srv.block_on(req_4).unwrap(); - let result_4 = block_on(resp_4.json::()).unwrap(); + let mut resp_4 = req_4.await.unwrap(); + let result_4 = resp_4.json::().await.unwrap(); assert_eq!( result_4, IndexResponse { @@ -559,7 +560,7 @@ mod test { .post("/login") .cookie(cookie_1.clone()) .send_json(&json!({"user_id": "ferris"})); - let mut resp_5 = srv.block_on(req_5).unwrap(); + let mut resp_5 = req_5.await.unwrap(); let cookie_2 = resp_5 .cookies() .unwrap() @@ -572,7 +573,7 @@ mod test { cookie_1.value().to_string() != cookie_2.value().to_string() ); - let result_5 = block_on(resp_5.json::()).unwrap(); + let result_5 = resp_5.json::().await.unwrap(); assert_eq!( result_5, IndexResponse { @@ -584,8 +585,8 @@ mod test { // Step 6: GET index, including session cookie #2 in request // - response should be: {"counter": 2, "user_id": "ferris"} let req_6 = srv.get("/").cookie(cookie_2.clone()).send(); - let mut resp_6 = srv.block_on(req_6).unwrap(); - let result_6 = block_on(resp_6.json::()).unwrap(); + let mut resp_6 = req_6.await.unwrap(); + let result_6 = resp_6.json::().await.unwrap(); assert_eq!( result_6, IndexResponse { @@ -598,8 +599,8 @@ mod test { // - updates session state in redis: {"counter": 3, "user_id": "ferris"} // - response should be: {"counter": 2, "user_id": None} let req_7 = srv.post("/do_something").cookie(cookie_2.clone()).send(); - let mut resp_7 = srv.block_on(req_7).unwrap(); - let result_7 = block_on(resp_7.json::()).unwrap(); + let mut resp_7 = req_7.await.unwrap(); + let result_7 = resp_7.json::().await.unwrap(); assert_eq!( result_7, IndexResponse { @@ -612,7 +613,7 @@ mod test { // - set-cookie actix-session will be in response (session cookie #3) // - response should be: {"counter": 0, "user_id": None} let req_8 = srv.get("/").cookie(cookie_1.clone()).send(); - let mut resp_8 = srv.block_on(req_8).unwrap(); + let mut resp_8 = req_8.await.unwrap(); let cookie_3 = resp_8 .cookies() .unwrap() @@ -620,7 +621,7 @@ mod test { .into_iter() .find(|c| c.name() == "test-session") .unwrap(); - let result_8 = block_on(resp_8.json::()).unwrap(); + let result_8 = resp_8.json::().await.unwrap(); assert_eq!( result_8, IndexResponse { @@ -634,7 +635,7 @@ mod test { // - set-cookie actix-session will be in response with session cookie #2 // invalidation logic let req_9 = srv.post("/logout").cookie(cookie_2.clone()).send(); - let resp_9 = srv.block_on(req_9).unwrap(); + let resp_9 = req_9.await.unwrap(); let cookie_4 = resp_9 .cookies() .unwrap() @@ -648,8 +649,8 @@ mod test { // - set-cookie actix-session will be in response (session cookie #3) // - response should be: {"counter": 0, "user_id": None} let req_10 = srv.get("/").cookie(cookie_2.clone()).send(); - let mut resp_10 = srv.block_on(req_10).unwrap(); - let result_10 = block_on(resp_10.json::()).unwrap(); + let mut resp_10 = req_10.await.unwrap(); + let result_10 = resp_10.json::().await.unwrap(); assert_eq!( result_10, IndexResponse { diff --git a/tests/test_redis.rs b/tests/test_redis.rs index 979c0230a..b9bb9c390 100644 --- a/tests/test_redis.rs +++ b/tests/test_redis.rs @@ -1,63 +1,42 @@ #[macro_use] extern crate redis_async; -use actix::prelude::*; use actix_redis::{Command, Error, RedisActor, RespValue}; -use futures::Future; - -#[test] -fn test_error_connect() -> std::io::Result<()> { - let sys = System::new("test"); +#[actix_rt::test] +async fn test_error_connect() { let addr = RedisActor::start("localhost:54000"); let _addr2 = addr.clone(); - Arbiter::spawn_fn(move || { - addr.send(Command(resp_array!["GET", "test"])).then(|res| { - match res { - Ok(Err(Error::NotConnected)) => (), - _ => panic!("Should not happen {:?}", res), - } - System::current().stop(); - Ok(()) - }) - }); - - sys.run() + let res = addr.send(Command(resp_array!["GET", "test"])).await; + match res { + Ok(Err(Error::NotConnected)) => (), + _ => panic!("Should not happen {:?}", res), + } } -#[test] -fn test_redis() -> std::io::Result<()> { +#[actix_rt::test] +async fn test_redis() { env_logger::init(); - let sys = System::new("test"); let addr = RedisActor::start("127.0.0.1:6379"); - let _addr2 = addr.clone(); + let res = addr + .send(Command(resp_array!["SET", "test", "value"])) + .await; - Arbiter::spawn_fn(move || { - let addr2 = addr.clone(); - addr.send(Command(resp_array!["SET", "test", "value"])) - .then(move |res| match res { + match res { + Ok(Ok(resp)) => { + assert_eq!(resp, RespValue::SimpleString("OK".to_owned())); + + let res = addr.send(Command(resp_array!["GET", "test"])).await; + match res { Ok(Ok(resp)) => { - assert_eq!(resp, RespValue::SimpleString("OK".to_owned())); - addr2.send(Command(resp_array!["GET", "test"])).then(|res| { - match res { - Ok(Ok(resp)) => { - println!("RESP: {:?}", resp); - assert_eq!( - resp, - RespValue::BulkString((&b"value"[..]).into()) - ); - } - _ => panic!("Should not happen {:?}", res), - } - System::current().stop(); - Ok(()) - }) + println!("RESP: {:?}", resp); + assert_eq!(resp, RespValue::BulkString((&b"value"[..]).into())); } _ => panic!("Should not happen {:?}", res), - }) - }); - - sys.run() + } + } + _ => panic!("Should not happen {:?}", res), + } } From 616e42dedc67bb60e53e1b9c7ffd41d91e40f548 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Dec 2019 00:04:47 +0600 Subject: [PATCH 72/84] fix example --- examples/basic.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 14e1ce9ba..6f023fea2 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -3,7 +3,7 @@ use actix_session::Session; use actix_web::{middleware, web, App, Error, HttpRequest, HttpServer, Responder}; /// simple handler -fn index(req: HttpRequest, session: Session) -> Result { +async fn index(req: HttpRequest, session: Session) -> Result { println!("{:?}", req); // session @@ -17,7 +17,8 @@ fn index(req: HttpRequest, session: Session) -> Result { Ok("Welcome!") } -fn main() -> std::io::Result<()> { +#[actix_rt::main] +async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); env_logger::init(); @@ -31,5 +32,6 @@ fn main() -> std::io::Result<()> { .service(web::resource("/").to(index)) }) .bind("0.0.0.0:8080")? - .run() + .start() + .await } From 45956b15108cdb9b1770eb2a091b929061352993 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Dec 2019 07:59:09 +0600 Subject: [PATCH 73/84] update actix ver --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9bb5dce3e..dd2d163b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ default = ["web"] web = ["actix/http", "actix-service", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] [dependencies] -actix = "0.9.0-alpha.1" +actix = "0.9.0-alpha.2" actix-utils = "1.0.3" log = "0.4.6" From 4d50ce9d2ecf8412fe6069df870e95d4659e4073 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Dec 2019 10:26:35 +0600 Subject: [PATCH 74/84] remove unused deps --- CHANGES.md | 2 +- Cargo.toml | 1 - src/session.rs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2ab9fe9af..d2e7fa6ea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## 0.8.0-alpha.1 (2019-12-xx) +## 0.8.0-alpha.1 (2019-12-16) * Migrate to actix 0.9 diff --git a/Cargo.toml b/Cargo.toml index dd2d163b8..a10e9aa8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,4 +52,3 @@ env_logger = "0.6.2" [dev-dependencies] env_logger = "0.6" -actix-http = "1.0.0" diff --git a/src/session.rs b/src/session.rs index 5728809bf..7d4fffd83 100644 --- a/src/session.rs +++ b/src/session.rs @@ -374,7 +374,6 @@ impl Inner { #[cfg(test)] mod test { use super::*; - use actix_http::httpmessage::HttpMessage; use actix_session::Session; use actix_web::{ middleware, test, web, From d3819b758870ad042545f17a54636b9ff37185db Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 16 Dec 2019 10:57:34 +0600 Subject: [PATCH 75/84] add ResponseError for redis Error --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 11a03dd26..5ee762010 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,9 @@ pub enum Error { Disconnected, } +#[cfg(feature = "web")] +impl actix_web::ResponseError for Error {} + // re-export pub use redis_async::error::Error as RespError; pub use redis_async::resp::RespValue; From 2c5679f498c21e1c5a9f1d5e6e1cf7d7386c7506 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 16 Dec 2019 11:43:35 -0500 Subject: [PATCH 76/84] turned inner methods into async ones --- src/session.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/session.rs b/src/session.rs index 7d4fffd83..8bf0254ee 100644 --- a/src/session.rs +++ b/src/session.rs @@ -207,10 +207,10 @@ struct Inner { } impl Inner { - fn load( + async fn load( &self, req: &ServiceRequest, - ) -> impl Future, String)>, Error>> + ) -> Result, String)>, Error> { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { @@ -220,7 +220,7 @@ impl Inner { if let Some(cookie) = jar.signed(&self.key).get(&self.name) { let value = cookie.value().to_owned(); let cachekey = (self.cache_keygen)(&cookie.value()); - return Either::Left( + return self.addr.send(Command(resp_array!["GET", cachekey])).map( |result| match result { Err(e) => Err(Error::from(e)), @@ -257,23 +257,23 @@ impl Inner { } }, }, - ), - ); + ) + .await; } else { - return Either::Right(ok(None)); + return ok(None).await } } } } - Either::Right(ok(None)) + ok(None).await } - fn update( + async fn update( &self, mut res: ServiceResponse, state: impl Iterator, value: Option, - ) -> impl Future, Error>> { + ) -> Result, Error> { let (value, jar) = if let Some(value) = value { (value.clone(), None) } else { @@ -311,8 +311,8 @@ impl Inner { let state: HashMap<_, _> = state.collect(); match serde_json::to_string(&state) { - Err(e) => Either::Left(err(e.into())), - Ok(body) => Either::Right( + Err(e) => err(e.into()).await, + Ok(body) => self.addr .send(Command(resp_array!["SET", cachekey, body, "EX", &self.ttl])) .map(|result| match result { @@ -331,13 +331,13 @@ impl Inner { } Err(err) => Err(error::ErrorInternalServerError(err)), }, - }), - ), + }) + .await, } } /// removes cache entry - fn clear_cache(&self, key: String) -> impl Future> { + async fn clear_cache(&self, key: String) -> Result<(), Error> { let cachekey = (self.cache_keygen)(&key); self.addr @@ -354,6 +354,7 @@ impl Inner { } } }) + .await } /// invalidates session cookie From bef5dfd90d8936ef3c7b1f7d00aec00e6d2d2ce6 Mon Sep 17 00:00:00 2001 From: dowwie Date: Mon, 16 Dec 2019 11:51:59 -0500 Subject: [PATCH 77/84] removed the unused import of Either --- src/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/session.rs b/src/session.rs index 8bf0254ee..00730de58 100644 --- a/src/session.rs +++ b/src/session.rs @@ -10,7 +10,7 @@ use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::header::{self, HeaderValue}; use actix_web::{error, Error, HttpMessage}; -use futures::future::{err, ok, Either, Future, FutureExt, Ready}; +use futures::future::{err, ok, Future, FutureExt, Ready}; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use redis_async::resp::RespValue; use time::{self, Duration}; From 512fe9c0e0c3e340379ccb00f5be8e670413ad8d Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 18 Dec 2019 13:56:12 -0500 Subject: [PATCH 78/84] cleaned up obvious syntax issue --- src/session.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/session.rs b/src/session.rs index 00730de58..c4e77a0e7 100644 --- a/src/session.rs +++ b/src/session.rs @@ -260,12 +260,12 @@ impl Inner { ) .await; } else { - return ok(None).await + return Ok(None) } } } } - ok(None).await + Ok(None) } async fn update( @@ -311,7 +311,7 @@ impl Inner { let state: HashMap<_, _> = state.collect(); match serde_json::to_string(&state) { - Err(e) => err(e.into()).await, + Err(e) => Err(e.into()), Ok(body) => self.addr .send(Command(resp_array!["SET", cachekey, body, "EX", &self.ttl])) From 2d6ff41563339c1de57f15177e7cf45f03b56233 Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 18 Dec 2019 15:07:47 -0500 Subject: [PATCH 79/84] removed map combinators --- src/session.rs | 85 ++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/src/session.rs b/src/session.rs index c4e77a0e7..240a1139b 100644 --- a/src/session.rs +++ b/src/session.rs @@ -221,44 +221,42 @@ impl Inner { let value = cookie.value().to_owned(); let cachekey = (self.cache_keygen)(&cookie.value()); return - self.addr.send(Command(resp_array!["GET", cachekey])).map( - |result| match result { - Err(e) => Err(Error::from(e)), - Ok(res) => match res { - Ok(val) => { - match val { - RespValue::Error(err) => { - return Err( - error::ErrorInternalServerError( - err, - ), - ); - } - 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))); - } - } - _ => (), + match self.addr.send(Command(resp_array!["GET", cachekey])) + .await { + Err(e) => Err(Error::from(e)), + Ok(res) => match res { + Ok(val) => { + match val { + RespValue::Error(err) => { + return Err( + error::ErrorInternalServerError( + err, + ), + ); } - Ok(None) + 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))); + } + } + _ => (), } - Err(err) => { - Err(error::ErrorInternalServerError(err)) - } - }, + Ok(None) + } + Err(err) => { + Err(error::ErrorInternalServerError(err)) + } }, - ) - .await; + } } else { return Ok(None) } @@ -312,10 +310,10 @@ impl Inner { let state: HashMap<_, _> = state.collect(); match serde_json::to_string(&state) { Err(e) => Err(e.into()), - Ok(body) => - self.addr + Ok(body) => { + match self.addr .send(Command(resp_array!["SET", cachekey, body, "EX", &self.ttl])) - .map(|result| match result { + .await { Err(e) => Err(Error::from(e)), Ok(redis_result) => match redis_result { Ok(_) => { @@ -331,8 +329,8 @@ impl Inner { } Err(err) => Err(error::ErrorInternalServerError(err)), }, - }) - .await, + } + } } } @@ -340,9 +338,9 @@ impl Inner { async fn clear_cache(&self, key: String) -> Result<(), Error> { let cachekey = (self.cache_keygen)(&key); - self.addr + match self.addr .send(Command(resp_array!["DEL", cachekey])) - .map(|res| match res { + .await { Err(e) => Err(Error::from(e)), Ok(res) => { match res { @@ -353,8 +351,7 @@ impl Inner { )), } } - }) - .await + } } /// invalidates session cookie From 26aa3d447a5366bfcd6face92891c22bf4ef9fe2 Mon Sep 17 00:00:00 2001 From: dowwie Date: Wed, 18 Dec 2019 15:08:08 -0500 Subject: [PATCH 80/84] removed FutureExt import --- src/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/session.rs b/src/session.rs index 240a1139b..5b67f2af5 100644 --- a/src/session.rs +++ b/src/session.rs @@ -10,7 +10,7 @@ use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::header::{self, HeaderValue}; use actix_web::{error, Error, HttpMessage}; -use futures::future::{err, ok, Future, FutureExt, Ready}; +use futures::future::{err, ok, Future, Ready}; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use redis_async::resp::RespValue; use time::{self, Duration}; From 9b0a0f451b64cd1a673529392fcf89b5cd8260b1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 20 Dec 2019 22:09:08 +0600 Subject: [PATCH 81/84] update actix; prep release --- CHANGES.md | 6 ++- Cargo.toml | 10 ++-- src/redis.rs | 2 +- src/session.rs | 124 +++++++++++++++++++++++-------------------------- 4 files changed, 69 insertions(+), 73 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d2e7fa6ea..8e0f972bc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes -## 0.8.0-alpha.1 (2019-12-16) +## [0.8.0] 2019-12-20 + +* Release + +## [0.8.0-alpha.1] 2019-12-16 * Migrate to actix 0.9 diff --git a/Cargo.toml b/Cargo.toml index a10e9aa8e..1de850069 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-redis" -version = "0.8.0-alpha.1" +version = "0.8.0" authors = ["Nikolay Kim "] description = "Redis integration for actix framework" license = "MIT/Apache-2.0" @@ -28,7 +28,7 @@ default = ["web"] web = ["actix/http", "actix-service", "actix-web", "actix-session/cookie-session", "rand", "serde", "serde_json"] [dependencies] -actix = "0.9.0-alpha.2" +actix = "0.9.0" actix-utils = "1.0.3" log = "0.4.6" @@ -38,13 +38,13 @@ futures = "0.3.1" redis-async = "0.6.1" actix-rt = "1.0.0" time = "0.1.42" -tokio = "0.2.4" +tokio = "0.2.6" tokio-util = "0.2.0" # actix web session -actix-web = { version = "2.0.0-alpha.6", optional = true } +actix-web = { version = "2.0.0-rc", optional = true } actix-service = { version = "1.0.0", optional = true } -actix-session = { version = "0.3.0-alpha", optional = true } +actix-session = { version = "0.3.0", optional = true } rand = { version = "0.7.0", optional = true } serde = { version = "1.0.101", optional = true, features = ["derive"] } serde_json = { version = "1.0.40", optional = true } diff --git a/src/redis.rs b/src/redis.rs index c9aa643e7..6a4c81a3f 100644 --- a/src/redis.rs +++ b/src/redis.rs @@ -139,7 +139,7 @@ impl Handler for RedisActor { let _ = tx.send(Err(Error::NotConnected)); } - Box::new(rx.map(|res| match res { + Box::pin(rx.map(|res| match res { Ok(res) => res, Err(_) => Err(Error::Disconnected), })) diff --git a/src/session.rs b/src/session.rs index 5b67f2af5..b7d281dcf 100644 --- a/src/session.rs +++ b/src/session.rs @@ -210,8 +210,7 @@ impl Inner { async fn load( &self, req: &ServiceRequest, - ) -> Result, String)>, Error> - { + ) -> Result, String)>, Error> { if let Ok(cookies) = req.cookies() { for cookie in cookies.iter() { if cookie.name() == self.name { @@ -220,45 +219,39 @@ impl Inner { if let Some(cookie) = jar.signed(&self.key).get(&self.name) { let value = cookie.value().to_owned(); let cachekey = (self.cache_keygen)(&cookie.value()); - return - match self.addr.send(Command(resp_array!["GET", cachekey])) - .await { - Err(e) => Err(Error::from(e)), - Ok(res) => match res { - Ok(val) => { - match val { - RespValue::Error(err) => { - return Err( - error::ErrorInternalServerError( - err, - ), - ); - } - 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))); - } - } - _ => (), + return match self + .addr + .send(Command(resp_array!["GET", cachekey])) + .await + { + Err(e) => Err(Error::from(e)), + Ok(res) => match res { + Ok(val) => { + match val { + RespValue::Error(err) => { + return Err( + error::ErrorInternalServerError(err), + ); } - Ok(None) + 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))); + } + } + _ => (), } - Err(err) => { - Err(error::ErrorInternalServerError(err)) - } - }, - } + Ok(None) + } + Err(err) => Err(error::ErrorInternalServerError(err)), + }, + }; } else { - return Ok(None) + return Ok(None); } } } @@ -311,25 +304,26 @@ impl Inner { match serde_json::to_string(&state) { Err(e) => Err(e.into()), Ok(body) => { - match self.addr + match self + .addr .send(Command(resp_array!["SET", cachekey, body, "EX", &self.ttl])) - .await { - Err(e) => Err(Error::from(e)), - Ok(redis_result) => match redis_result { - Ok(_) => { - if let Some(jar) = jar { - for cookie in jar.delta() { - let val = - HeaderValue::from_str(&cookie.to_string())?; - res.headers_mut() - .append(header::SET_COOKIE, val); - } + .await + { + Err(e) => Err(Error::from(e)), + Ok(redis_result) => match redis_result { + Ok(_) => { + if let Some(jar) = jar { + for cookie in jar.delta() { + let val = + HeaderValue::from_str(&cookie.to_string())?; + res.headers_mut().append(header::SET_COOKIE, val); } - Ok(res) } - Err(err) => Err(error::ErrorInternalServerError(err)), - }, - } + Ok(res) + } + Err(err) => Err(error::ErrorInternalServerError(err)), + }, + } } } } @@ -338,20 +332,18 @@ impl Inner { async fn clear_cache(&self, key: String) -> Result<(), Error> { let cachekey = (self.cache_keygen)(&key); - match self.addr - .send(Command(resp_array!["DEL", cachekey])) - .await { - Err(e) => Err(Error::from(e)), - Ok(res) => { - match res { - // redis responds with number of deleted records - Ok(RespValue::Integer(x)) if x > 0 => Ok(()), - _ => Err(error::ErrorInternalServerError( - "failed to remove session from cache", - )), - } + match self.addr.send(Command(resp_array!["DEL", cachekey])).await { + Err(e) => Err(Error::from(e)), + Ok(res) => { + match res { + // redis responds with number of deleted records + Ok(RespValue::Integer(x)) if x > 0 => Ok(()), + _ => Err(error::ErrorInternalServerError( + "failed to remove session from cache", + )), } } + } } /// invalidates session cookie From 2e1d98a4be2b263ac827a2f54f7beec0ca5a2eb7 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 20 Dec 2019 22:11:38 +0600 Subject: [PATCH 82/84] update readme --- README.md | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 3d207298a..5b973fb83 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,10 @@ Redis integration for actix framework. ## Documentation -* [API Documentation (Development)](http://actix.github.io/actix-redis/actix_redis/) -* [API Documentation (Releases)](https://docs.rs/actix-redis/) +* [API Documentation](http://actix.github.io/actix-redis/actix_redis/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-redis](https://crates.io/crates/actix-redis) -* Minimum supported Rust version: 1.26 or later +* Minimum supported Rust version: 1.39 or later ## Redis session backend @@ -24,32 +23,25 @@ Note that whatever you write into your session is visible by the user (but not m Constructor panics if key length is less than 32 bytes. ```rust -extern crate actix_web; -extern crate actix_redis; - -use actix_web::{App, server, middleware}; +use actix_web::{App, HttpServer, web, middleware}; use actix_web::middleware::session::SessionStorage; use actix_redis::RedisSessionBackend; -fn main() { - ::std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - let sys = actix::System::new("basic-example"); - - server::new( - || App::new() - // enable logger - .middleware(middleware::Logger::default()) - // cookie session middleware - .middleware(SessionStorage::new( - RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) - )) - // register simple route, handle all methods - .resource("/", |r| r.f(index))) - .bind("0.0.0.0:8080").unwrap() - .start(); - - let _ = sys.run(); +#[actix_rt::main] +async fn main() -> std::io::Result { + HttpServer::new(|| App::new() + // enable logger + .middleware(middleware::Logger::default()) + // cookie session middleware + .middleware(SessionStorage::new( + RedisSessionBackend::new("127.0.0.1:6379", &[0; 32]) + )) + // register simple route, handle all methods + .service(web::resource("/").to(index)) + ) + .bind("0.0.0.0:8080")? + .start() + .await } ``` From 73e70bd2497e7ad748c63b95071c65ce558aae4b Mon Sep 17 00:00:00 2001 From: pandaman64 Date: Thu, 26 Dec 2019 15:47:31 +0900 Subject: [PATCH 83/84] bump actix-web version --- Cargo.toml | 2 +- examples/basic.rs | 2 +- src/session.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1de850069..add06caa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ tokio = "0.2.6" tokio-util = "0.2.0" # actix web session -actix-web = { version = "2.0.0-rc", optional = true } +actix-web = { version = "2.0.0", optional = true } actix-service = { version = "1.0.0", optional = true } actix-session = { version = "0.3.0", optional = true } rand = { version = "0.7.0", optional = true } diff --git a/examples/basic.rs b/examples/basic.rs index 6f023fea2..0d95cad80 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -32,6 +32,6 @@ async fn main() -> std::io::Result<()> { .service(web::resource("/").to(index)) }) .bind("0.0.0.0:8080")? - .start() + .run() .await } diff --git a/src/session.rs b/src/session.rs index b7d281dcf..ced1822ad 100644 --- a/src/session.rs +++ b/src/session.rs @@ -10,7 +10,7 @@ use actix_web::cookie::{Cookie, CookieJar, Key, SameSite}; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::http::header::{self, HeaderValue}; use actix_web::{error, Error, HttpMessage}; -use futures::future::{err, ok, Future, Ready}; +use futures::future::{ok, Future, Ready}; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use redis_async::resp::RespValue; use time::{self, Duration}; From 2c216e471e754a83b14cc67be1f8c775e032a99f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 29 Jan 2020 11:37:27 +0000 Subject: [PATCH 84/84] move redis to own module --- .gitignore => actix-redis/.gitignore | 0 .travis.yml => actix-redis/.travis.yml | 0 CHANGES.md => actix-redis/CHANGES.md | 0 Cargo.toml => actix-redis/Cargo.toml | 0 LICENSE-APACHE => actix-redis/LICENSE-APACHE | 0 LICENSE-MIT => actix-redis/LICENSE-MIT | 0 README.md => actix-redis/README.md | 0 {examples => actix-redis/examples}/basic.rs | 0 rustfmt.toml => actix-redis/rustfmt.toml | 0 {src => actix-redis/src}/lib.rs | 0 {src => actix-redis/src}/redis.rs | 0 {src => actix-redis/src}/session.rs | 0 {tests => actix-redis/tests}/test_redis.rs | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename .gitignore => actix-redis/.gitignore (100%) rename .travis.yml => actix-redis/.travis.yml (100%) rename CHANGES.md => actix-redis/CHANGES.md (100%) rename Cargo.toml => actix-redis/Cargo.toml (100%) rename LICENSE-APACHE => actix-redis/LICENSE-APACHE (100%) rename LICENSE-MIT => actix-redis/LICENSE-MIT (100%) rename README.md => actix-redis/README.md (100%) rename {examples => actix-redis/examples}/basic.rs (100%) rename rustfmt.toml => actix-redis/rustfmt.toml (100%) rename {src => actix-redis/src}/lib.rs (100%) rename {src => actix-redis/src}/redis.rs (100%) rename {src => actix-redis/src}/session.rs (100%) rename {tests => actix-redis/tests}/test_redis.rs (100%) diff --git a/.gitignore b/actix-redis/.gitignore similarity index 100% rename from .gitignore rename to actix-redis/.gitignore diff --git a/.travis.yml b/actix-redis/.travis.yml similarity index 100% rename from .travis.yml rename to actix-redis/.travis.yml diff --git a/CHANGES.md b/actix-redis/CHANGES.md similarity index 100% rename from CHANGES.md rename to actix-redis/CHANGES.md diff --git a/Cargo.toml b/actix-redis/Cargo.toml similarity index 100% rename from Cargo.toml rename to actix-redis/Cargo.toml diff --git a/LICENSE-APACHE b/actix-redis/LICENSE-APACHE similarity index 100% rename from LICENSE-APACHE rename to actix-redis/LICENSE-APACHE diff --git a/LICENSE-MIT b/actix-redis/LICENSE-MIT similarity index 100% rename from LICENSE-MIT rename to actix-redis/LICENSE-MIT diff --git a/README.md b/actix-redis/README.md similarity index 100% rename from README.md rename to actix-redis/README.md diff --git a/examples/basic.rs b/actix-redis/examples/basic.rs similarity index 100% rename from examples/basic.rs rename to actix-redis/examples/basic.rs diff --git a/rustfmt.toml b/actix-redis/rustfmt.toml similarity index 100% rename from rustfmt.toml rename to actix-redis/rustfmt.toml diff --git a/src/lib.rs b/actix-redis/src/lib.rs similarity index 100% rename from src/lib.rs rename to actix-redis/src/lib.rs diff --git a/src/redis.rs b/actix-redis/src/redis.rs similarity index 100% rename from src/redis.rs rename to actix-redis/src/redis.rs diff --git a/src/session.rs b/actix-redis/src/session.rs similarity index 100% rename from src/session.rs rename to actix-redis/src/session.rs diff --git a/tests/test_redis.rs b/actix-redis/tests/test_redis.rs similarity index 100% rename from tests/test_redis.rs rename to actix-redis/tests/test_redis.rs