Skip to content

Commit 9e07eec

Browse files
committed
Merge pull request #16 from clue-labs/legacy
Explicitly pass "legacy" protocol scheme to avoid probing protocol
2 parents dc85ec6 + 00c14b9 commit 9e07eec

File tree

3 files changed

+74
-11
lines changed

3 files changed

+74
-11
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ $factory->createClient('localhost')->then(
5050

5151
The `$uri` parameter must be a valid URI which must contain a host part and can optionally contain a port.
5252

53+
This method defauls to probing the Quassel IRC core for the correct protocol to
54+
use (newer "datastream" protocol or original "legacy" protocol).
55+
If you have trouble with the newer "datastream" protocol, you can force using
56+
the old "legacy" protocol by prefixing the `legacy` scheme identifier like this:
57+
58+
```php
59+
$factory->createClient('legacy://quassel.example.com:1234');
60+
```
61+
5362
### Client
5463

5564
The `Client` is responsible for exchanging messages with your Quassel IRC core

src/Factory.php

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use React\Dns\Resolver\Factory as ResolverFactory;
1111
use React\SocketClient\Connector;
1212
use Clue\React\Quassel\Io\Protocol;
13+
use React\Promise;
14+
use InvalidArgumentException;
1315

1416
class Factory
1517
{
@@ -31,23 +33,34 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector =
3133
public function createClient($address)
3234
{
3335
if (strpos($address, '://') === false) {
34-
$address = 'dummy://' . $address;
36+
$address = 'tcp://' . $address;
3537
}
3638
$parts = parse_url($address);
3739
if (!$parts || !isset($parts['host'])) {
38-
return;
40+
return Promise\reject(new InvalidArgumentException('Given argument "' . $address . '" is not a valid URI'));
3941
}
4042
if (!isset($parts['port'])) {
4143
$parts['port'] = 4242;
4244
}
4345

44-
$connector = $this->connector;
45-
$prober = $this->prober;
46+
// default to automatic probing protocol unless scheme is explicitly given
47+
$probe = 0;
48+
if (isset($parts['scheme'])) {
49+
if ($parts['scheme'] === 'legacy') {
50+
$probe = Protocol::TYPE_LEGACY;
51+
} elseif ($parts['scheme'] !== 'tcp') {
52+
return Promise\reject(new InvalidArgumentException('Given URI scheme "' . $parts['scheme'] . '" is invalid'));
53+
}
54+
}
55+
56+
$promise = $this->connector->create($parts['host'], $parts['port']);
4657

47-
return $connector->create($parts['host'], $parts['port'])->then(
48-
function (Stream $stream) use ($prober, $connector, $parts) {
49-
$probe = 0;
58+
// protocol probe not already set
59+
if ($probe === 0) {
60+
$connector = $this->connector;
61+
$prober = $this->prober;
5062

63+
$promise = $promise->then(function (Stream $stream) use ($prober, &$probe, $connector, $parts) {
5164
return $prober->probe($stream)->then(
5265
function ($ret) use (&$probe, $stream) {
5366
// probe returned successfully, create new client for this stream
@@ -56,17 +69,21 @@ function ($ret) use (&$probe, $stream) {
5669
return $stream;
5770
},
5871
function ($e) use ($connector, $parts) {
72+
// probing failed
5973
if ($e->getCode() === Prober::ERROR_CLOSED) {
6074
// legacy servers will terminate connection while probing
75+
// let's just open a new connection and assume default probe
6176
return $connector->create($parts['host'], $parts['port']);
6277
}
6378
throw $e;
6479
}
65-
)->then(
66-
function (Stream $stream) use (&$probe) {
67-
return new Client($stream, Protocol::createFromProbe($probe));
68-
}
6980
);
81+
});
82+
}
83+
84+
return $promise->then(
85+
function (Stream $stream) use (&$probe) {
86+
return new Client($stream, Protocol::createFromProbe($probe));
7087
}
7188
);
7289
}

tests/FactoryTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
use Clue\React\Quassel\Factory;
44
use React\Promise\Deferred;
5+
use Clue\React\Quassel\Io\Protocol;
6+
use React\Promise;
7+
58
class FactoryTest extends TestCase
69
{
710
public function setUp()
@@ -31,4 +34,38 @@ public function testPassHostnameAndPortToConnector()
3134
$this->connector->expects($this->once())->method('create')->with($this->equalTo('example.com', 1234))->will($this->returnValue($deferred->promise()));
3235
$this->factory->createClient('example.com:1234');
3336
}
37+
38+
public function testInvalidUriWillRejectWithoutConnecting()
39+
{
40+
$this->connector->expects($this->never())->method('create');
41+
42+
$this->expectPromiseReject($this->factory->createClient('///'));
43+
}
44+
45+
public function testInvalidSchemeWillRejectWithoutConnecting()
46+
{
47+
$this->connector->expects($this->never())->method('create');
48+
49+
$this->expectPromiseReject($this->factory->createClient('https://example.com:1234/'));
50+
}
51+
52+
public function testWillInvokeProberAfterConnecting()
53+
{
54+
$stream = $this->getMockBuilder('React\Stream\Stream')->disableOriginalConstructor()->getMock();
55+
56+
$this->connector->expects($this->once())->method('create')->will($this->returnValue(Promise\resolve($stream)));
57+
$this->prober->expects($this->once())->method('probe')->with($this->equalTo($stream))->will($this->returnValue(Promise\resolve(Protocol::TYPE_DATASTREAM)));
58+
59+
$this->expectPromiseResolve($this->factory->createClient('localhost'));
60+
}
61+
62+
public function testWillNotInvokeProberIfSchemeIsProtocol()
63+
{
64+
$stream = $this->getMockBuilder('React\Stream\Stream')->disableOriginalConstructor()->getMock();
65+
66+
$this->connector->expects($this->once())->method('create')->will($this->returnValue(Promise\resolve($stream)));
67+
$this->prober->expects($this->never())->method('probe');
68+
69+
$this->expectPromiseResolve($this->factory->createClient('legacy://localhost'));
70+
}
3471
}

0 commit comments

Comments
 (0)