Skip to content

Commit ad33efb

Browse files
author
Iskandar Najmuddin
committed
Improve Resque_Redis DSN parsing.
- Allow for DSN URIs to work as expected. - Backward-compatible with simple 'host:port' format. - Does not parse DSNs provided in array format for Credis_Cluster.
1 parent 610c4dc commit ad33efb

File tree

2 files changed

+247
-33
lines changed

2 files changed

+247
-33
lines changed

lib/Resque/Redis.php

100644100755
Lines changed: 90 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,29 @@
88
*/
99
class Resque_Redis
1010
{
11-
/**
12-
* Redis namespace
13-
* @var string
14-
*/
15-
private static $defaultNamespace = 'resque:';
11+
/**
12+
* Redis namespace
13+
* @var string
14+
*/
15+
private static $defaultNamespace = 'resque:';
16+
17+
/**
18+
* A default host to connect to
19+
*/
20+
const DEFAULT_HOST = 'localhost';
21+
22+
/**
23+
* The default Redis port
24+
*/
25+
const DEFAULT_PORT = 6379;
26+
27+
/**
28+
* The default Redis Database number
29+
*/
30+
const DEFAULT_DATABASE = 0;
1631

17-
private $server;
18-
private $database;
32+
private $server;
33+
private $database;
1934

2035
/**
2136
* @var array List of all commands in Redis that supply a key as their
@@ -92,47 +107,89 @@ public static function prefix($namespace)
92107
self::$defaultNamespace = $namespace;
93108
}
94109

95-
public function __construct($server, $database = null)
110+
/**
111+
* @param string|array $server A DSN or array
112+
* @param int $database A database number to select
113+
*/
114+
public function __construct($server, $database = null)
96115
{
97116
$this->server = $server;
98117
$this->database = $database;
99118

100119
if (is_array($this->server)) {
101120
$this->driver = new Credis_Cluster($server);
102-
}
103-
else {
104-
$port = null;
105-
$password = null;
106-
$host = $server;
107-
108-
// If not a UNIX socket path or tcp:// formatted connections string
109-
// assume host:port combination.
110-
if (strpos($server, '/') === false) {
111-
$parts = explode(':', $server);
112-
if (isset($parts[1])) {
113-
$port = $parts[1];
114-
}
115-
$host = $parts[0];
116-
}else if (strpos($server, 'redis://') !== false){
117-
// Redis format is:
118-
// redis://[user]:[password]@[host]:[port]
119-
list($userpwd,$hostport) = explode('@', $server);
120-
$userpwd = substr($userpwd, strpos($userpwd, 'redis://')+8);
121-
list($host, $port) = explode(':', $hostport);
122-
list($user, $password) = explode(':', $userpwd);
123-
}
124-
125-
$this->driver = new Credis_Client($host, $port);
126-
if (isset($password)){
121+
122+
} else {
123+
124+
list($host, $port, $dsnDatabase, $user, $password, $options) = $this->parseDsn($server);
125+
// $user is are unused here
126+
127+
// Look for known Credis_Client options
128+
$timeout = isset($options['timeout']) ? intval($options['timeout']) : null;
129+
$persistent = isset($options['persistent']) ? $options['persistent'] : '';
130+
131+
$this->driver = new Credis_Client($host, $port, $timeout, $persistent);
132+
if ($password){
127133
$this->driver->auth($password);
128134
}
135+
136+
// If the `$database` constructor argument is not set, use the value from the DSN.
137+
if (is_null($database)) {
138+
$database = $dsnDatabase;
139+
}
129140
}
130141

131142
if ($this->database !== null) {
132143
$this->driver->select($database);
133144
}
134145
}
135146

147+
/**
148+
* Parse a DSN string
149+
* @param string $dsn
150+
* @return array [host, port, db, user, pass, options]
151+
*/
152+
public function parseDsn($dsn)
153+
{
154+
$validSchemes = array('redis', 'tcp');
155+
if ($dsn == '') {
156+
// Use a sensible default for an empty DNS string
157+
$dsn = 'redis://' . self::DEFAULT_HOST;
158+
}
159+
$parts = parse_url($dsn);
160+
if (isset($parts['scheme']) && ! in_array($parts['scheme'], $validSchemes)) {
161+
throw new \InvalidArgumentException("Invalid DSN. Supported schemes are " . implode(', ', $validSchemes));
162+
}
163+
164+
// Allow simple 'hostname' format, which parse_url treats as a path, not host.
165+
if ( ! isset($parts['host'])) {
166+
$parts = array('host' => $parts['path']);
167+
}
168+
169+
$port = isset($parts['port']) ? intval($parts['port']) : self::DEFAULT_PORT;
170+
171+
$database = self::DEFAULT_DATABASE;
172+
if (isset($parts['path'])) {
173+
// Strip non-digit chars from path
174+
$database = intval(preg_replace('/[^0-9]/', '', $parts['path']));
175+
}
176+
177+
$options = array();
178+
if (isset($parts['query'])) {
179+
// Parse the query string into an array
180+
parse_str($parts['query'], $options);
181+
}
182+
183+
return array(
184+
$parts['host'],
185+
$port,
186+
$database,
187+
isset($parts['user']) ? $parts['user'] : false,
188+
isset($parts['pass']) ? $parts['pass'] : false,
189+
$options,
190+
);
191+
}
192+
136193
/**
137194
* Magic method to handle all function requests and prefix key based
138195
* operations with the {self::$defaultNamespace} key prefix.

test/Resque/Tests/DsnTest.php

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
<?php
2+
/**
3+
* Resque_Redis DSN tests.
4+
*
5+
* @package Resque/Tests
6+
* @author Iskandar Najmuddin <[email protected]>
7+
* @license http://www.opensource.org/licenses/mit-license.php
8+
*/
9+
class Resque_Tests_DsnTest extends Resque_Tests_TestCase
10+
{
11+
12+
/**
13+
* These DNS strings are considered valid.
14+
*
15+
* @return array
16+
*/
17+
public function validDsnStringProvider()
18+
{
19+
return array(
20+
// Input , Expected output
21+
array('', array(
22+
'localhost',
23+
Resque_Redis::DEFAULT_PORT,
24+
Resque_Redis::DEFAULT_DATABASE,
25+
false, false,
26+
array(),
27+
)),
28+
array('localhost', array(
29+
'localhost',
30+
Resque_Redis::DEFAULT_PORT,
31+
Resque_Redis::DEFAULT_DATABASE,
32+
false, false,
33+
array(),
34+
)),
35+
array('localhost:1234', array(
36+
'localhost',
37+
1234,
38+
Resque_Redis::DEFAULT_DATABASE,
39+
false, false,
40+
array(),
41+
)),
42+
array('localhost:1234/2', array(
43+
'localhost',
44+
1234,
45+
2,
46+
false, false,
47+
array(),
48+
)),
49+
array('redis://foobar', array(
50+
'foobar',
51+
Resque_Redis::DEFAULT_PORT,
52+
Resque_Redis::DEFAULT_DATABASE,
53+
false, false,
54+
array(),
55+
)),
56+
array('redis://foobar:1234', array(
57+
'foobar',
58+
1234,
59+
Resque_Redis::DEFAULT_DATABASE,
60+
false, false,
61+
array(),
62+
)),
63+
array('redis://user@foobar:1234', array(
64+
'foobar',
65+
1234,
66+
Resque_Redis::DEFAULT_DATABASE,
67+
'user', false,
68+
array(),
69+
)),
70+
array('redis://user:pass@foobar:1234', array(
71+
'foobar',
72+
1234,
73+
Resque_Redis::DEFAULT_DATABASE,
74+
'user', 'pass',
75+
array(),
76+
)),
77+
array('redis://user:pass@foobar:1234?x=y&a=b', array(
78+
'foobar',
79+
1234,
80+
Resque_Redis::DEFAULT_DATABASE,
81+
'user', 'pass',
82+
array('x' => 'y', 'a' => 'b'),
83+
)),
84+
array('redis://:pass@foobar:1234?x=y&a=b', array(
85+
'foobar',
86+
1234,
87+
Resque_Redis::DEFAULT_DATABASE,
88+
false, 'pass',
89+
array('x' => 'y', 'a' => 'b'),
90+
)),
91+
array('redis://user@foobar:1234?x=y&a=b', array(
92+
'foobar',
93+
1234,
94+
Resque_Redis::DEFAULT_DATABASE,
95+
'user', false,
96+
array('x' => 'y', 'a' => 'b'),
97+
)),
98+
array('redis://foobar:1234?x=y&a=b', array(
99+
'foobar',
100+
1234,
101+
Resque_Redis::DEFAULT_DATABASE,
102+
false, false,
103+
array('x' => 'y', 'a' => 'b'),
104+
)),
105+
array('redis://user@foobar:1234/12?x=y&a=b', array(
106+
'foobar',
107+
1234,
108+
12,
109+
'user', false,
110+
array('x' => 'y', 'a' => 'b'),
111+
)),
112+
array('tcp://user@foobar:1234/12?x=y&a=b', array(
113+
'foobar',
114+
1234,
115+
12,
116+
'user', false,
117+
array('x' => 'y', 'a' => 'b'),
118+
)),
119+
);
120+
}
121+
122+
/**
123+
* These DSN values should throw exceptions
124+
* @return array
125+
*/
126+
public function bogusDsnStringProvider()
127+
{
128+
return array(
129+
'http://foo.bar/',
130+
'://foo.bar/',
131+
'user:@foobar:1234?x=y&a=b',
132+
'foobar:1234?x=y&a=b',
133+
);
134+
}
135+
136+
/**
137+
* @dataProvider validDsnStringProvider
138+
*/
139+
public function testParsingValidDsnString($dsn, $expected)
140+
{
141+
$resqueRedis = new Resque_Redis('localhost');
142+
$result = $resqueRedis->parseDsn($dsn);
143+
$this->assertEquals($expected, $result);
144+
}
145+
146+
/**
147+
* @dataProvider bogusDsnStringProvider
148+
* @expectedException InvalidArgumentException
149+
*/
150+
public function testParsingBogusDsnStringThrowsException($dsn)
151+
{
152+
$resqueRedis = new Resque_Redis('localhost');
153+
// The next line should throw an InvalidArgumentException
154+
$result = $resqueRedis->parseDsn($dsn);
155+
}
156+
157+
}

0 commit comments

Comments
 (0)