Skip to content

Commit 21e63fa

Browse files
committedOct 18, 2014
Switch to rust-url and added support for password based authentication.
This fixes redis-rs#11
1 parent c8ad896 commit 21e63fa

File tree

4 files changed

+126
-32
lines changed

4 files changed

+126
-32
lines changed
 

‎Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ authors = ["Armin Ronacher <armin.ronacher@active-4.com>"]
66
[dependencies.rust-crypto]
77
git = "https://github.com/DaGenix/rust-crypto.git"
88

9+
[dependencies.url]
10+
git = "https://github.com/servo/rust-url"
11+
912
[lib]
1013
name = "redis"
1114
path = "src/redis/lib.rs"

‎src/redis/client.rs

+9-23
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
use url::Url;
2-
3-
use connection::{Connection, connect, PubSub, connect_pubsub, ConnectionLike};
4-
use types::{RedisResult, Value, Error, InvalidClientConfig};
1+
use connection::{ConnectionInfo, IntoConnectionInfo, Connection, connect,
2+
PubSub, connect_pubsub, ConnectionLike};
3+
use types::{RedisResult, Value};
54

65

76
/// The client type.
87
pub struct Client {
9-
host: String,
10-
port: u16,
11-
db: i64,
8+
connection_info: ConnectionInfo,
129
}
1310

1411
/// The client acts as connector to the redis server. By itself it does not
@@ -32,20 +29,9 @@ impl Client {
3229
/// Connects to a redis server and returns a client. This does not
3330
/// actually open a connection yet but it does perform some basic
3431
/// checks on the URL that might make the operation fail.
35-
pub fn open(uri: &str) -> RedisResult<Client> {
36-
let u = unwrap_or!(from_str::<Url>(uri), return Err(Error::simple(
37-
InvalidClientConfig, "Redis URL did not parse")));
38-
ensure!(u.scheme.as_slice() == "redis", Err(Error::simple(
39-
InvalidClientConfig, "URL provided is not a redis URL")));
40-
32+
pub fn open<T: IntoConnectionInfo>(params: T) -> RedisResult<Client> {
4133
Ok(Client {
42-
host: u.host,
43-
port: u.port.unwrap_or(6379),
44-
db: match u.path.to_string().as_slice().trim_chars('/') {
45-
"" => 0,
46-
path => unwrap_or!(from_str::<i64>(path), return Err(Error::simple(
47-
InvalidClientConfig, "Path is not a valid redis database number")))
48-
},
34+
connection_info: try!(params.into_connection_info())
4935
})
5036
}
5137

@@ -55,7 +41,7 @@ impl Client {
5541
/// (like unreachable host) so it's important that you handle those
5642
/// errors.
5743
pub fn get_connection(&self) -> RedisResult<Connection> {
58-
Ok(try_io!(connect(self.host.as_slice(), self.port, self.db)))
44+
Ok(try_io!(connect(&self.connection_info)))
5945
}
6046

6147
/// Returns a PubSub connection. A pubsub connection can be used to
@@ -64,7 +50,7 @@ impl Client {
6450
///
6551
/// Note that redis' pubsub operates across all databases.
6652
pub fn get_pubsub(&self) -> RedisResult<PubSub> {
67-
Ok(try_io!(connect_pubsub(self.host.as_slice(), self.port)))
53+
Ok(try_io!(connect_pubsub(&self.connection_info)))
6854
}
6955
}
7056

@@ -80,6 +66,6 @@ impl ConnectionLike for Client {
8066
}
8167

8268
fn get_db(&self) -> i64 {
83-
self.db
69+
self.connection_info.db
8470
}
8571
}

‎src/redis/connection.rs

+112-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,99 @@ use std::cell::RefCell;
44
use std::collections::HashSet;
55
use std::str;
66

7+
use url;
8+
79
use cmd::{cmd, pipe, Pipeline};
810
use types::{RedisResult, Okay, Error, Value, Data, Nil, InternalIoError,
9-
ToRedisArgs, FromRedisValue, from_redis_value};
11+
ToRedisArgs, FromRedisValue, from_redis_value,
12+
InvalidClientConfig};
1013
use parser::Parser;
1114

1215

16+
static DEFAULT_PORT: u16 = 6370;
17+
18+
fn redis_scheme_type_mapper(scheme: &str) -> url::SchemeType {
19+
match scheme {
20+
"redis" => url::RelativeScheme(DEFAULT_PORT),
21+
_ => url::NonRelativeScheme,
22+
}
23+
}
24+
25+
/// This function takes a redis URL string and parses it into a URL
26+
/// as used by rust-url. This is necessary as the default parser does
27+
/// not understand how redis URLs function.
28+
pub fn parse_redis_url(input: &str) -> url::ParseResult<url::Url> {
29+
let mut parser = url::UrlParser::new();
30+
parser.scheme_type_mapper(redis_scheme_type_mapper);
31+
match parser.parse(input) {
32+
Ok(result) => {
33+
if result.scheme.as_slice() != "redis" {
34+
Err(url::InvalidScheme)
35+
} else {
36+
Ok(result)
37+
}
38+
},
39+
Err(err) => Err(err),
40+
}
41+
}
42+
43+
44+
/// Holds the connection information that redis should use for connecting.
45+
pub struct ConnectionInfo {
46+
pub host: String,
47+
pub port: u16,
48+
pub db: i64,
49+
pub passwd: Option<String>,
50+
}
51+
52+
/// Converts an object into a connection info struct. This allows the
53+
/// constructor of the client to accept connection information in a
54+
/// range of different formats.
55+
pub trait IntoConnectionInfo {
56+
fn into_connection_info(self) -> RedisResult<ConnectionInfo>;
57+
}
58+
59+
impl IntoConnectionInfo for ConnectionInfo {
60+
fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
61+
Ok(self)
62+
}
63+
}
64+
65+
impl<'a> IntoConnectionInfo for &'a str {
66+
fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
67+
match parse_redis_url(self) {
68+
Ok(u) => u.into_connection_info(),
69+
Err(_) => Err(Error::simple(
70+
InvalidClientConfig, "Redis URL did not parse")),
71+
}
72+
}
73+
}
74+
75+
impl IntoConnectionInfo for url::Url {
76+
fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
77+
ensure!(self.scheme.as_slice() == "redis", Err(Error::simple(
78+
InvalidClientConfig, "URL provided is not a redis URL")));
79+
80+
println!("{}", self);
81+
82+
Ok(ConnectionInfo {
83+
host: unwrap_or!(self.serialize_host(),
84+
return Err(Error::simple(InvalidClientConfig,
85+
"Missing hostname"))),
86+
port: self.port().unwrap_or(DEFAULT_PORT),
87+
db: match self.serialize_path().unwrap_or("".to_string())
88+
.as_slice().trim_chars('/') {
89+
"" => 0,
90+
path => unwrap_or!(from_str::<i64>(path),
91+
return Err(Error::simple(InvalidClientConfig,
92+
"Path is not a valid redis database number")))
93+
},
94+
passwd: self.password().and_then(|pw| Some(pw.to_string())),
95+
})
96+
}
97+
}
98+
99+
13100
struct ActualConnection {
14101
sock: TcpStream,
15102
}
@@ -60,11 +147,13 @@ impl ActualConnection {
60147
}
61148

62149

63-
pub fn connect(host: &str, port: u16, db: i64) -> IoResult<Connection> {
64-
let con = try!(ActualConnection::new(host, port));
65-
let rv = Connection { con: RefCell::new(con), db: db };
66-
if db != 0 {
67-
match cmd("SELECT").arg(db).query::<Value>(&rv) {
150+
pub fn connect(connection_info: &ConnectionInfo) -> IoResult<Connection> {
151+
let con = try!(ActualConnection::new(
152+
connection_info.host[], connection_info.port));
153+
let rv = Connection { con: RefCell::new(con), db: connection_info.db };
154+
155+
if connection_info.db != 0 {
156+
match cmd("SELECT").arg(connection_info.db).query::<Value>(&rv) {
68157
Ok(Okay) => {}
69158
_ => { return Err(IoError {
70159
kind: ConnectionFailed,
@@ -73,12 +162,27 @@ pub fn connect(host: &str, port: u16, db: i64) -> IoResult<Connection> {
73162
}); }
74163
}
75164
}
165+
166+
match connection_info.passwd {
167+
Some(ref passwd) => {
168+
match cmd("AUTH").arg(passwd[]).query::<Value>(&rv) {
169+
Ok(Okay) => {}
170+
_ => { return Err(IoError {
171+
kind: ConnectionFailed,
172+
desc: "Password authentication failed",
173+
detail: None,
174+
}); }
175+
}
176+
},
177+
None => {},
178+
}
179+
76180
Ok(rv)
77181
}
78182

79-
pub fn connect_pubsub(host: &str, port: u16) -> IoResult<PubSub> {
183+
pub fn connect_pubsub(connection_info: &ConnectionInfo) -> IoResult<PubSub> {
80184
Ok(PubSub {
81-
con: try!(connect(host, port, 0)),
185+
con: try!(connect(connection_info)),
82186
channels: HashSet::new(),
83187
pchannels: HashSet::new(),
84188
})

‎src/redis/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ extern crate "rust-crypto" as crypto;
256256
pub use parser::{parse_redis_value, Parser};
257257
pub use client::Client;
258258
pub use script::{Script, ScriptInvocation};
259-
pub use connection::{Connection, ConnectionLike, PubSub, Msg, transaction};
259+
pub use connection::{Connection, ConnectionLike, ConnectionInfo,
260+
IntoConnectionInfo, PubSub, Msg, transaction};
260261
pub use cmd::{cmd, Cmd, pipe, Pipeline, Iter, pack_command};
261262
pub use commands::{
262263
Commands,

0 commit comments

Comments
 (0)