From fecbf59622ec541fdd883a4cda8acf60a2469311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 23 Nov 2015 02:37:58 +0100 Subject: [PATCH 1/3] Rename create() to connect() --- README.md | 30 +++++++++++++++--------------- src/Connector.php | 4 ++-- src/ConnectorInterface.php | 2 +- src/DnsConnector.php | 8 ++++---- src/SecureConnector.php | 8 ++++---- src/TcpConnector.php | 2 +- src/TimeoutConnector.php | 4 ++-- src/UnixConnector.php | 2 +- tests/DnsConnectorTest.php | 22 +++++++++++----------- tests/IntegrationTest.php | 10 +++++----- tests/SecureConnectorTest.php | 12 ++++++------ tests/SecureIntegrationTest.php | 16 ++++++++-------- tests/TcpConnectorTest.php | 14 +++++++------- tests/TimeoutConnectorTest.php | 24 ++++++++++++------------ tests/UnixConnectorTest.php | 4 ++-- 15 files changed, 81 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 204cac8..32da5b0 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ $loop = React\EventLoop\Factory::create(); ### Async TCP/IP connections The `React\SocketClient\TcpConnector` provides a single promise-based -`create($ip, $port)` method which resolves as soon as the connection +`connect($ip, $port)` method which resolves as soon as the connection succeeds or fails. ```php $tcpConnector = new React\SocketClient\TcpConnector($loop); -$tcpConnector->create('127.0.0.1', 80)->then(function (React\Stream\Stream $stream) { +$tcpConnector->connect('127.0.0.1', 80)->then(function (React\Stream\Stream $stream) { $stream->write('...'); $stream->end(); }); @@ -50,7 +50,7 @@ See also the [first example](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $tcpConnector->create($host, $port); +$promise = $tcpConnector->connect($host, $port); $promise->cancel(); ``` @@ -78,18 +78,18 @@ The `DnsConnector` class decorates a given `TcpConnector` instance by first looking up the given domain name and then establishing the underlying TCP/IP connection to the resolved IP address. -It provides the same promise-based `create($host, $port)` method which resolves with +It provides the same promise-based `connect($host, $port)` method which resolves with a `Stream` instance that can be used just like above. Make sure to set up your DNS resolver and underlying TCP connector like this: ```php $dnsResolverFactory = new React\Dns\Resolver\Factory(); -$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop); +$dns = $dnsResolverFactory->connectCached('8.8.8.8', $loop); $dnsConnector = new React\SocketClient\DnsConnector($tcpConnector, $dns); -$dnsConnector->create('www.google.com', 80)->then(function (React\Stream\Stream $stream) { +$dnsConnector->connect('www.google.com', 80)->then(function (React\Stream\Stream $stream) { $stream->write('...'); $stream->end(); }); @@ -102,7 +102,7 @@ See also the [first example](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $dnsConnector->create($host, $port); +$promise = $dnsConnector->connect($host, $port); $promise->cancel(); ``` @@ -117,7 +117,7 @@ set up like this: ```php $connector = new React\SocketClient\Connector($loop, $dns); -$connector->create('www.google.com', 80)->then($callback); +$connector->connect('www.google.com', 80)->then($callback); ``` ### Async SSL/TLS connections @@ -125,13 +125,13 @@ $connector->create('www.google.com', 80)->then($callback); The `SecureConnector` class decorates a given `Connector` instance by enabling SSL/TLS encryption as soon as the raw TCP/IP connection succeeds. -It provides the same promise- based `create($host, $port)` method which resolves with +It provides the same promise- based `connect($host, $port)` method which resolves with a `Stream` instance that can be used just like any non-encrypted stream: ```php $secureConnector = new React\SocketClient\SecureConnector($dnsConnector, $loop); -$secureConnector->create('www.google.com', 443)->then(function (React\Stream\Stream $stream) { +$secureConnector->connect('www.google.com', 443)->then(function (React\Stream\Stream $stream) { $stream->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); ... }); @@ -144,7 +144,7 @@ See also the [second example](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $secureConnector->create($host, $port); +$promise = $secureConnector->connect($host, $port); $promise->cancel(); ``` @@ -174,13 +174,13 @@ stream resources will use a single, shared *default context* resource otherwise. ### Connection timeouts The `TimeoutConnector` class decorates any given `Connector` instance. -It provides the same `create()` method, but will automatically reject the +It provides the same `connect()` method, but will automatically reject the underlying connection attempt if it takes too long. ```php $timeoutConnector = new React\SocketClient\TimeoutConnector($connector, 3.0, $loop); -$timeoutConnector->create('google.com', 80)->then(function (React\Stream\Stream $stream) { +$timeoutConnector->connect('google.com', 80)->then(function (React\Stream\Stream $stream) { // connection succeeded within 3.0 seconds }); ``` @@ -190,7 +190,7 @@ See also any of the [examples](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $timeoutConnector->create($host, $port); +$promise = $timeoutConnector->connect($host, $port); $promise->cancel(); ``` @@ -206,7 +206,7 @@ paths like this: ```php $connector = new React\SocketClient\UnixConnector($loop); -$connector->create('/tmp/demo.sock')->then(function (React\Stream\Stream $stream) { +$connector->connect('/tmp/demo.sock')->then(function (React\Stream\Stream $stream) { $stream->write("HELLO\n"); }); diff --git a/src/Connector.php b/src/Connector.php index 6cf991c..d50c222 100644 --- a/src/Connector.php +++ b/src/Connector.php @@ -17,8 +17,8 @@ public function __construct(LoopInterface $loop, Resolver $resolver) $this->connector = new DnsConnector(new TcpConnector($loop), $resolver); } - public function create($host, $port) + public function connect($host, $port) { - return $this->connector->create($host, $port); + return $this->connector->connect($host, $port); } } diff --git a/src/ConnectorInterface.php b/src/ConnectorInterface.php index b40b3a1..6c6d8ad 100644 --- a/src/ConnectorInterface.php +++ b/src/ConnectorInterface.php @@ -4,5 +4,5 @@ interface ConnectorInterface { - public function create($host, $port); + public function connect($host, $port); } diff --git a/src/DnsConnector.php b/src/DnsConnector.php index 44c2179..d6979ec 100644 --- a/src/DnsConnector.php +++ b/src/DnsConnector.php @@ -18,14 +18,14 @@ public function __construct(ConnectorInterface $connector, Resolver $resolver) $this->resolver = $resolver; } - public function create($host, $port) + public function connect($host, $port) { $that = $this; return $this ->resolveHostname($host) ->then(function ($ip) use ($that, $port) { - return $that->connect($ip, $port); + return $that->connectTcp($ip, $port); }); } @@ -55,9 +55,9 @@ function ($_, $reject) use ($promise) { } /** @internal */ - public function connect($ip, $port) + public function connectTcp($ip, $port) { - $promise = $this->connector->create($ip, $port); + $promise = $this->connector->connect($ip, $port); return new Promise\Promise( function ($resolve, $reject) use ($promise) { diff --git a/src/SecureConnector.php b/src/SecureConnector.php index b4ac22b..5644b4b 100644 --- a/src/SecureConnector.php +++ b/src/SecureConnector.php @@ -20,7 +20,7 @@ public function __construct(ConnectorInterface $connector, LoopInterface $loop, $this->context = $context; } - public function create($host, $port) + public function connect($host, $port) { if (!function_exists('stream_socket_enable_crypto')) { return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); @@ -40,7 +40,7 @@ public function create($host, $port) } $encryption = $this->streamEncryption; - return $this->connect($host, $port)->then(function (Stream $stream) use ($context, $encryption) { + return $this->connectTcp($host, $port)->then(function (Stream $stream) use ($context, $encryption) { // (unencrypted) TCP/IP connection succeeded // set required SSL/TLS context options @@ -57,9 +57,9 @@ public function create($host, $port) }); } - private function connect($host, $port) + private function connectTcp($host, $port) { - $promise = $this->connector->create($host, $port); + $promise = $this->connector->connect($host, $port); return new Promise\Promise( function ($resolve, $reject) use ($promise) { diff --git a/src/TcpConnector.php b/src/TcpConnector.php index fa64ed8..809f333 100644 --- a/src/TcpConnector.php +++ b/src/TcpConnector.php @@ -19,7 +19,7 @@ public function __construct(LoopInterface $loop, array $context = array()) $this->context = $context; } - public function create($ip, $port) + public function connect($ip, $port) { if (false === filter_var($ip, FILTER_VALIDATE_IP)) { return Promise\reject(new \InvalidArgumentException('Given parameter "' . $ip . '" is not a valid IP')); diff --git a/src/TimeoutConnector.php b/src/TimeoutConnector.php index c4cfd5e..c957655 100644 --- a/src/TimeoutConnector.php +++ b/src/TimeoutConnector.php @@ -22,9 +22,9 @@ public function __construct(ConnectorInterface $connector, $timeout, LoopInterfa $this->loop = $loop; } - public function create($host, $port) + public function connect($host, $port) { - $promise = $this->connector->create($host, $port); + $promise = $this->connector->connect($host, $port); return Timer\timeout(new Promise( function ($resolve, $reject) use ($promise) { diff --git a/src/UnixConnector.php b/src/UnixConnector.php index e12e7ef..b235198 100644 --- a/src/UnixConnector.php +++ b/src/UnixConnector.php @@ -23,7 +23,7 @@ public function __construct(LoopInterface $loop) $this->loop = $loop; } - public function create($path, $unusedPort = 0) + public function connect($path, $unusedPort = 0) { $resource = @stream_socket_client('unix://' . $path, $errno, $errstr, 1.0); diff --git a/tests/DnsConnectorTest.php b/tests/DnsConnectorTest.php index f588dd6..b7bf150 100644 --- a/tests/DnsConnectorTest.php +++ b/tests/DnsConnectorTest.php @@ -22,25 +22,25 @@ public function setUp() public function testPassByResolverIfGivenIp() { $this->resolver->expects($this->never())->method('resolve'); - $this->tcp->expects($this->once())->method('create')->with($this->equalTo('127.0.0.1'), $this->equalTo(80))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1'), $this->equalTo(80))->will($this->returnValue(Promise\reject())); - $this->connector->create('127.0.0.1', 80); + $this->connector->connect('127.0.0.1', 80); } public function testPassThroughResolverIfGivenHost() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('create')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue(Promise\reject())); - $this->connector->create('google.com', 80); + $this->connector->connect('google.com', 80); } public function testSkipConnectionIfDnsFails() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject())); - $this->tcp->expects($this->never())->method('create'); + $this->tcp->expects($this->never())->method('connect'); - $this->connector->create('example.invalid', 80); + $this->connector->connect('example.invalid', 80); } public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection() @@ -49,7 +49,7 @@ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection() $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue($pending)); $this->tcp->expects($this->never())->method('resolve'); - $promise = $this->connector->create('example.com', 80); + $promise = $this->connector->connect('example.com', 80); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -59,9 +59,9 @@ public function testCancelDuringTcpConnectionCancelsTcpConnection() { $pending = new Promise\Promise(function () { }, $this->expectCallableOnce()); $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('create')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue($pending)); - $promise = $this->connector->create('example.com', 80); + $promise = $this->connector->connect('example.com', 80); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -77,9 +77,9 @@ public function testCancelClosesStreamIfTcpResolvesDespiteCancellation() }); $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('create')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue($pending)); - $promise = $this->connector->create('example.com', 80); + $promise = $this->connector->connect('example.com', 80); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 0568dec..5795a47 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -24,7 +24,7 @@ public function gettingStuffFromGoogleShouldWork() $dns = $factory->create('8.8.8.8', $loop); $connector = new Connector($loop, $dns); - $conn = Block\await($connector->create('google.com', 80), $loop); + $conn = Block\await($connector->connect('google.com', 80), $loop); $conn->write("GET / HTTP/1.0\r\n\r\n"); @@ -50,7 +50,7 @@ public function gettingEncryptedStuffFromGoogleShouldWork() $loop ); - $conn = Block\await($secureConnector->create('google.com', 443), $loop); + $conn = Block\await($secureConnector->connect('google.com', 443), $loop); $conn->write("GET / HTTP/1.0\r\n\r\n"); @@ -80,7 +80,7 @@ public function testSelfSignedRejectsIfVerificationIsEnabled() ); $this->setExpectedException('RuntimeException'); - Block\await($secureConnector->create('self-signed.badssl.com', 443), $loop, self::TIMEOUT); + Block\await($secureConnector->connect('self-signed.badssl.com', 443), $loop, self::TIMEOUT); } /** @test */ @@ -103,7 +103,7 @@ public function testSelfSignedResolvesIfVerificationIsDisabled() ) ); - $conn = Block\await($secureConnector->create('self-signed.badssl.com', 443), $loop, self::TIMEOUT); + $conn = Block\await($secureConnector->connect('self-signed.badssl.com', 443), $loop, self::TIMEOUT); $conn->close(); } @@ -112,7 +112,7 @@ public function testCancelPendingConnection() $loop = new StreamSelectLoop(); $connector = new TcpConnector($loop); - $pending = $connector->create('8.8.8.8', 80); + $pending = $connector->connect('8.8.8.8', 80); $loop->addTimer(0.001, function () use ($pending) { $pending->cancel(); diff --git a/tests/SecureConnectorTest.php b/tests/SecureConnectorTest.php index 9b2a6c9..e81936f 100644 --- a/tests/SecureConnectorTest.php +++ b/tests/SecureConnectorTest.php @@ -25,9 +25,9 @@ public function setUp() public function testConnectionWillWaitForTcpConnection() { $pending = new Promise\Promise(function () { }); - $this->tcp->expects($this->once())->method('create')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); - $promise = $this->connector->create('example.com', 80); + $promise = $this->connector->connect('example.com', 80); $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); } @@ -35,9 +35,9 @@ public function testConnectionWillWaitForTcpConnection() public function testCancelDuringTcpConnectionCancelsTcpConnection() { $pending = new Promise\Promise(function () { }, $this->expectCallableOnce()); - $this->tcp->expects($this->once())->method('create')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); - $promise = $this->connector->create('example.com', 80); + $promise = $this->connector->connect('example.com', 80); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -52,9 +52,9 @@ public function testCancelClosesStreamIfTcpResolvesDespiteCancellation() $resolve($stream); }); - $this->tcp->expects($this->once())->method('create')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); - $promise = $this->connector->create('example.com', 80); + $promise = $this->connector->connect('example.com', 80); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); diff --git a/tests/SecureIntegrationTest.php b/tests/SecureIntegrationTest.php index ef85dad..87d2308 100644 --- a/tests/SecureIntegrationTest.php +++ b/tests/SecureIntegrationTest.php @@ -53,7 +53,7 @@ public function tearDown() public function testConnectToServer() { - $client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $client->close(); @@ -63,7 +63,7 @@ public function testConnectToServerEmitsConnection() { $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce()); - $promiseClient = $this->connector->create('127.0.0.1', $this->portSecure); + $promiseClient = $this->connector->connect('127.0.0.1', $this->portSecure); list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop, self::TIMEOUT); /* @var $client Stream */ @@ -81,7 +81,7 @@ public function testSendSmallDataToServerReceivesOneChunk() }); }); - $client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $client->write('hello'); @@ -107,7 +107,7 @@ public function testSendDataWithEndToServerReceivesAllData() }); }); - $client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $data = str_repeat('a', 200000); @@ -128,7 +128,7 @@ public function testSendDataWithoutEndingToServerReceivesAllData() }); }); - $client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $data = str_repeat('d', 200000); @@ -148,7 +148,7 @@ public function testConnectToServerWhichSendsSmallDataReceivesOneChunk() $peer->write('hello'); }); - $client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ // await client to report one "data" event @@ -165,7 +165,7 @@ public function testConnectToServerWhichSendsDataWithEndReceivesAllData() $peer->end($data); }); - $client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ // await data from client until it closes @@ -181,7 +181,7 @@ public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData() $peer->write($data); }); - $client = Block\await($this->connector->create('127.0.0.1', $this->portSecure), $this->loop); + $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop); /* @var $client Stream */ // buffer incoming data for 0.1s (should be plenty of time) diff --git a/tests/TcpConnectorTest.php b/tests/TcpConnectorTest.php index 964297d..72770a3 100644 --- a/tests/TcpConnectorTest.php +++ b/tests/TcpConnectorTest.php @@ -17,7 +17,7 @@ public function connectionToEmptyPortShouldFail() $loop = new StreamSelectLoop(); $connector = new TcpConnector($loop); - $connector->create('127.0.0.1', 9999) + $connector->connect('127.0.0.1', 9999) ->then($this->expectCallableNever(), $this->expectCallableOnce()); $loop->run(); @@ -37,7 +37,7 @@ public function connectionToTcpServerShouldSucceed() $connector = new TcpConnector($loop); - $stream = Block\await($connector->create('127.0.0.1', 9999), $loop, self::TIMEOUT); + $stream = Block\await($connector->connect('127.0.0.1', 9999), $loop, self::TIMEOUT); $this->assertInstanceOf('React\Stream\Stream', $stream); @@ -51,7 +51,7 @@ public function connectionToEmptyIp6PortShouldFail() $connector = new TcpConnector($loop); $connector - ->create('::1', 9999) + ->connect('::1', 9999) ->then($this->expectCallableNever(), $this->expectCallableOnce()); $loop->run(); @@ -69,7 +69,7 @@ public function connectionToIp6TcpServerShouldSucceed() $connector = new TcpConnector($loop); - $stream = Block\await($connector->create('::1', 9999), $loop, self::TIMEOUT); + $stream = Block\await($connector->connect('::1', 9999), $loop, self::TIMEOUT); $this->assertInstanceOf('React\Stream\Stream', $stream); @@ -82,7 +82,7 @@ public function connectionToHostnameShouldFailImmediately() $loop = $this->getMock('React\EventLoop\LoopInterface'); $connector = new TcpConnector($loop); - $connector->create('www.google.com', 80)->then( + $connector->connect('www.google.com', 80)->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -94,7 +94,7 @@ public function connectionToInvalidAddressShouldFailImmediately() $loop = $this->getMock('React\EventLoop\LoopInterface'); $connector = new TcpConnector($loop); - $connector->create('255.255.255.255', 12345678)->then( + $connector->connect('255.255.255.255', 12345678)->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -109,7 +109,7 @@ public function cancellingConnectionShouldRejectPromise() $server = new Server($loop); $server->listen(0); - $promise = $connector->create('127.0.0.1', $server->getPort()); + $promise = $connector->connect('127.0.0.1', $server->getPort()); $promise->cancel(); $this->setExpectedException('RuntimeException', 'Cancelled'); diff --git a/tests/TimeoutConnectorTest.php b/tests/TimeoutConnectorTest.php index 2cb0969..59486cd 100644 --- a/tests/TimeoutConnectorTest.php +++ b/tests/TimeoutConnectorTest.php @@ -13,13 +13,13 @@ public function testRejectsOnTimeout() $promise = new Promise\Promise(function () { }); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('create')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $timeout->create('google.com', 80)->then( + $timeout->connect('google.com', 80)->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -32,13 +32,13 @@ public function testRejectsWhenConnectorRejects() $promise = Promise\reject(new \RuntimeException()); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('create')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 5.0, $loop); - $timeout->create('google.com', 80)->then( + $timeout->connect('google.com', 80)->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -51,13 +51,13 @@ public function testResolvesWhenConnectorResolves() $promise = Promise\resolve(); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('create')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 5.0, $loop); - $timeout->create('google.com', 80)->then( + $timeout->connect('google.com', 80)->then( $this->expectCallableOnce(), $this->expectCallableNever() ); @@ -70,13 +70,13 @@ public function testRejectsAndCancelsPendingPromiseOnTimeout() $promise = new Promise\Promise(function () { }, $this->expectCallableOnce()); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('create')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $timeout->create('google.com', 80)->then( + $timeout->connect('google.com', 80)->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -89,13 +89,13 @@ public function testCancelsPendingPromiseOnCancel() $promise = new Promise\Promise(function () { }, $this->expectCallableOnce()); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('create')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $out = $timeout->create('google.com', 80); + $out = $timeout->connect('google.com', 80); $out->cancel(); $out->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -111,13 +111,13 @@ public function testCancelClosesStreamIfTcpResolvesDespiteCancellation() }); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('create')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $out = $timeout->create('google.com', 80); + $out = $timeout->connect('google.com', 80); $out->cancel(); $out->then($this->expectCallableNever(), $this->expectCallableOnce()); diff --git a/tests/UnixConnectorTest.php b/tests/UnixConnectorTest.php index 4070aed..574afa4 100644 --- a/tests/UnixConnectorTest.php +++ b/tests/UnixConnectorTest.php @@ -17,7 +17,7 @@ public function setUp() public function testInvalid() { - $promise = $this->connector->create('google.com', 80); + $promise = $this->connector->connect('google.com', 80); $promise->then(null, $this->expectCallableOnce()); } @@ -36,7 +36,7 @@ public function testValid() } // tests succeeds if we get notified of successful connection - $promise = $this->connector->create($path, 0); + $promise = $this->connector->connect($path, 0); $promise->then($this->expectCallableOnce()); // clean up server From 89ae4f6031964e5f797d5d1edbc8b23ad559eb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 25 Nov 2015 21:33:07 +0100 Subject: [PATCH 2/3] Use string URIs instead of host and port --- README.md | 26 +++++++++++++------------- src/Connector.php | 4 ++-- src/ConnectorInterface.php | 9 ++++++++- src/DnsConnector.php | 24 +++++++++++++++++++----- src/SecureConnector.php | 10 ++++++---- src/TcpConnector.php | 25 ++++++++++--------------- src/TimeoutConnector.php | 4 ++-- src/UnixConnector.php | 2 +- tests/DnsConnectorTest.php | 20 ++++++++++---------- tests/IntegrationTest.php | 10 +++++----- tests/SecureConnectorTest.php | 12 ++++++------ tests/SecureIntegrationTest.php | 16 ++++++++-------- tests/TcpConnectorTest.php | 14 +++++++------- tests/TimeoutConnectorTest.php | 24 ++++++++++++------------ tests/UnixConnectorTest.php | 4 ++-- 15 files changed, 111 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 32da5b0..7f4ceb2 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ $loop = React\EventLoop\Factory::create(); ### Async TCP/IP connections The `React\SocketClient\TcpConnector` provides a single promise-based -`connect($ip, $port)` method which resolves as soon as the connection +`connect($uri)` method which resolves as soon as the connection succeeds or fails. ```php $tcpConnector = new React\SocketClient\TcpConnector($loop); -$tcpConnector->connect('127.0.0.1', 80)->then(function (React\Stream\Stream $stream) { +$tcpConnector->connect('127.0.0.1:80')->then(function (React\Stream\Stream $stream) { $stream->write('...'); $stream->end(); }); @@ -50,7 +50,7 @@ See also the [first example](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $tcpConnector->connect($host, $port); +$promise = $tcpConnector->connect('127.0.0.1:80'); $promise->cancel(); ``` @@ -78,7 +78,7 @@ The `DnsConnector` class decorates a given `TcpConnector` instance by first looking up the given domain name and then establishing the underlying TCP/IP connection to the resolved IP address. -It provides the same promise-based `connect($host, $port)` method which resolves with +It provides the same promise-based `connect($uri)` method which resolves with a `Stream` instance that can be used just like above. Make sure to set up your DNS resolver and underlying TCP connector like this: @@ -89,7 +89,7 @@ $dns = $dnsResolverFactory->connectCached('8.8.8.8', $loop); $dnsConnector = new React\SocketClient\DnsConnector($tcpConnector, $dns); -$dnsConnector->connect('www.google.com', 80)->then(function (React\Stream\Stream $stream) { +$dnsConnector->connect('www.google.com:80')->then(function (React\Stream\Stream $stream) { $stream->write('...'); $stream->end(); }); @@ -102,7 +102,7 @@ See also the [first example](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $dnsConnector->connect($host, $port); +$promise = $dnsConnector->connect('www.google.com:80'); $promise->cancel(); ``` @@ -117,7 +117,7 @@ set up like this: ```php $connector = new React\SocketClient\Connector($loop, $dns); -$connector->connect('www.google.com', 80)->then($callback); +$connector->connect('www.google.com:80')->then($callback); ``` ### Async SSL/TLS connections @@ -125,13 +125,13 @@ $connector->connect('www.google.com', 80)->then($callback); The `SecureConnector` class decorates a given `Connector` instance by enabling SSL/TLS encryption as soon as the raw TCP/IP connection succeeds. -It provides the same promise- based `connect($host, $port)` method which resolves with +It provides the same promise- based `connect($uri)` method which resolves with a `Stream` instance that can be used just like any non-encrypted stream: ```php $secureConnector = new React\SocketClient\SecureConnector($dnsConnector, $loop); -$secureConnector->connect('www.google.com', 443)->then(function (React\Stream\Stream $stream) { +$secureConnector->connect('www.google.com:443')->then(function (React\Stream\Stream $stream) { $stream->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); ... }); @@ -144,7 +144,7 @@ See also the [second example](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $secureConnector->connect($host, $port); +$promise = $secureConnector->connect('www.google.com:443'); $promise->cancel(); ``` @@ -174,13 +174,13 @@ stream resources will use a single, shared *default context* resource otherwise. ### Connection timeouts The `TimeoutConnector` class decorates any given `Connector` instance. -It provides the same `connect()` method, but will automatically reject the +It provides the same `connect($uri)` method, but will automatically reject the underlying connection attempt if it takes too long. ```php $timeoutConnector = new React\SocketClient\TimeoutConnector($connector, 3.0, $loop); -$timeoutConnector->connect('google.com', 80)->then(function (React\Stream\Stream $stream) { +$timeoutConnector->connect('google.com:80')->then(function (React\Stream\Stream $stream) { // connection succeeded within 3.0 seconds }); ``` @@ -190,7 +190,7 @@ See also any of the [examples](examples). Pending connection attempts can be cancelled by cancelling its pending promise like so: ```php -$promise = $timeoutConnector->connect($host, $port); +$promise = $timeoutConnector->connect('google.com:80'); $promise->cancel(); ``` diff --git a/src/Connector.php b/src/Connector.php index d50c222..79d5f09 100644 --- a/src/Connector.php +++ b/src/Connector.php @@ -17,8 +17,8 @@ public function __construct(LoopInterface $loop, Resolver $resolver) $this->connector = new DnsConnector(new TcpConnector($loop), $resolver); } - public function connect($host, $port) + public function connect($uri) { - return $this->connector->connect($host, $port); + return $this->connector->connect($uri); } } diff --git a/src/ConnectorInterface.php b/src/ConnectorInterface.php index 6c6d8ad..0036086 100644 --- a/src/ConnectorInterface.php +++ b/src/ConnectorInterface.php @@ -4,5 +4,12 @@ interface ConnectorInterface { - public function connect($host, $port); + /** + * + * + * @param string $uri + * @return Promise Returns a Promise<\React\Stream\Stream, \Exception>, i.e. + * it either resolves with a Stream instance or rejects with an Exception. + */ + public function connect($uri); } diff --git a/src/DnsConnector.php b/src/DnsConnector.php index d6979ec..9680e50 100644 --- a/src/DnsConnector.php +++ b/src/DnsConnector.php @@ -18,14 +18,28 @@ public function __construct(ConnectorInterface $connector, Resolver $resolver) $this->resolver = $resolver; } - public function connect($host, $port) + public function connect($uri) { $that = $this; + $parts = parse_url('tcp://' . $uri); + if (!$parts || !isset($parts['host'], $parts['port'])) { + return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid')); + } + + $host = trim($parts['host'], '[]'); + return $this ->resolveHostname($host) - ->then(function ($ip) use ($that, $port) { - return $that->connectTcp($ip, $port); + ->then(function ($ip) use ($that, $parts) { + if (strpos($ip, ':') !== false) { + // enclose IPv6 addresses in square brackets before appending port + $ip = '[' . $ip . ']'; + } + + return $that->connectTcp( + $ip . ':' . $parts['port'] + ); }); } @@ -55,9 +69,9 @@ function ($_, $reject) use ($promise) { } /** @internal */ - public function connectTcp($ip, $port) + public function connectTcp($uri) { - $promise = $this->connector->connect($ip, $port); + $promise = $this->connector->connect($uri); return new Promise\Promise( function ($resolve, $reject) use ($promise) { diff --git a/src/SecureConnector.php b/src/SecureConnector.php index 5644b4b..c0cb351 100644 --- a/src/SecureConnector.php +++ b/src/SecureConnector.php @@ -20,12 +20,14 @@ public function __construct(ConnectorInterface $connector, LoopInterface $loop, $this->context = $context; } - public function connect($host, $port) + public function connect($uri) { if (!function_exists('stream_socket_enable_crypto')) { return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); } + $host = trim(parse_url('tcp://' . $uri, PHP_URL_HOST), '[]'); + $context = $this->context + array( 'SNI_enabled' => true, 'peer_name' => $host @@ -40,7 +42,7 @@ public function connect($host, $port) } $encryption = $this->streamEncryption; - return $this->connectTcp($host, $port)->then(function (Stream $stream) use ($context, $encryption) { + return $this->connectTcp($uri)->then(function (Stream $stream) use ($context, $encryption) { // (unencrypted) TCP/IP connection succeeded // set required SSL/TLS context options @@ -57,9 +59,9 @@ public function connect($host, $port) }); } - private function connectTcp($host, $port) + private function connectTcp($uri) { - $promise = $this->connector->connect($host, $port); + $promise = $this->connector->connect($uri); return new Promise\Promise( function ($resolve, $reject) use ($promise) { diff --git a/src/TcpConnector.php b/src/TcpConnector.php index 809f333..3bf5ca4 100644 --- a/src/TcpConnector.php +++ b/src/TcpConnector.php @@ -19,16 +19,20 @@ public function __construct(LoopInterface $loop, array $context = array()) $this->context = $context; } - public function connect($ip, $port) + public function connect($uri) { - if (false === filter_var($ip, FILTER_VALIDATE_IP)) { - return Promise\reject(new \InvalidArgumentException('Given parameter "' . $ip . '" is not a valid IP')); + $parts = parse_url('tcp://' . $uri); + if (!$parts || !isset($parts['host'], $parts['port'])) { + return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid')); } + $ip = trim($parts['host'], '[]'); - $url = $this->getSocketUrl($ip, $port); + if (false === filter_var($ip, FILTER_VALIDATE_IP)) { + return Promise\reject(new \InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP')); + } $socket = @stream_socket_client( - $url, + $uri, $errno, $errstr, 0, @@ -38,7 +42,7 @@ public function connect($ip, $port) if (false === $socket) { return Promise\reject(new \RuntimeException( - sprintf("Connection to %s:%d failed: %s", $ip, $port, $errstr), + sprintf("Connection to %s failed: %s", $uri, $errstr), $errno )); } @@ -90,13 +94,4 @@ public function handleConnectedSocket($socket) { return new Stream($socket, $this->loop); } - - private function getSocketUrl($ip, $port) - { - if (strpos($ip, ':') !== false) { - // enclose IPv6 addresses in square brackets before appending port - $ip = '[' . $ip . ']'; - } - return sprintf('tcp://%s:%s', $ip, $port); - } } diff --git a/src/TimeoutConnector.php b/src/TimeoutConnector.php index c957655..8088f83 100644 --- a/src/TimeoutConnector.php +++ b/src/TimeoutConnector.php @@ -22,9 +22,9 @@ public function __construct(ConnectorInterface $connector, $timeout, LoopInterfa $this->loop = $loop; } - public function connect($host, $port) + public function connect($uri) { - $promise = $this->connector->connect($host, $port); + $promise = $this->connector->connect($uri); return Timer\timeout(new Promise( function ($resolve, $reject) use ($promise) { diff --git a/src/UnixConnector.php b/src/UnixConnector.php index b235198..74ea3ae 100644 --- a/src/UnixConnector.php +++ b/src/UnixConnector.php @@ -23,7 +23,7 @@ public function __construct(LoopInterface $loop) $this->loop = $loop; } - public function connect($path, $unusedPort = 0) + public function connect($path) { $resource = @stream_socket_client('unix://' . $path, $errno, $errstr, 1.0); diff --git a/tests/DnsConnectorTest.php b/tests/DnsConnectorTest.php index b7bf150..0ac542c 100644 --- a/tests/DnsConnectorTest.php +++ b/tests/DnsConnectorTest.php @@ -22,17 +22,17 @@ public function setUp() public function testPassByResolverIfGivenIp() { $this->resolver->expects($this->never())->method('resolve'); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1'), $this->equalTo(80))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('127.0.0.1:80'))->will($this->returnValue(Promise\reject())); - $this->connector->connect('127.0.0.1', 80); + $this->connector->connect('127.0.0.1:80'); } public function testPassThroughResolverIfGivenHost() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue(Promise\reject())); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80'))->will($this->returnValue(Promise\reject())); - $this->connector->connect('google.com', 80); + $this->connector->connect('google.com:80'); } public function testSkipConnectionIfDnsFails() @@ -40,7 +40,7 @@ public function testSkipConnectionIfDnsFails() $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject())); $this->tcp->expects($this->never())->method('connect'); - $this->connector->connect('example.invalid', 80); + $this->connector->connect('example.invalid:80'); } public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection() @@ -49,7 +49,7 @@ public function testCancelDuringDnsCancelsDnsAndDoesNotStartTcpConnection() $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue($pending)); $this->tcp->expects($this->never())->method('resolve'); - $promise = $this->connector->connect('example.com', 80); + $promise = $this->connector->connect('example.com:80'); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -59,9 +59,9 @@ public function testCancelDuringTcpConnectionCancelsTcpConnection() { $pending = new Promise\Promise(function () { }, $this->expectCallableOnce()); $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80'))->will($this->returnValue($pending)); - $promise = $this->connector->connect('example.com', 80); + $promise = $this->connector->connect('example.com:80'); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -77,9 +77,9 @@ public function testCancelClosesStreamIfTcpResolvesDespiteCancellation() }); $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.com'))->will($this->returnValue(Promise\resolve('1.2.3.4'))); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('1.2.3.4:80'))->will($this->returnValue($pending)); - $promise = $this->connector->connect('example.com', 80); + $promise = $this->connector->connect('example.com:80'); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 5795a47..4c64973 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -24,7 +24,7 @@ public function gettingStuffFromGoogleShouldWork() $dns = $factory->create('8.8.8.8', $loop); $connector = new Connector($loop, $dns); - $conn = Block\await($connector->connect('google.com', 80), $loop); + $conn = Block\await($connector->connect('google.com:80'), $loop); $conn->write("GET / HTTP/1.0\r\n\r\n"); @@ -50,7 +50,7 @@ public function gettingEncryptedStuffFromGoogleShouldWork() $loop ); - $conn = Block\await($secureConnector->connect('google.com', 443), $loop); + $conn = Block\await($secureConnector->connect('google.com:443'), $loop); $conn->write("GET / HTTP/1.0\r\n\r\n"); @@ -80,7 +80,7 @@ public function testSelfSignedRejectsIfVerificationIsEnabled() ); $this->setExpectedException('RuntimeException'); - Block\await($secureConnector->connect('self-signed.badssl.com', 443), $loop, self::TIMEOUT); + Block\await($secureConnector->connect('self-signed.badssl.com:443'), $loop, self::TIMEOUT); } /** @test */ @@ -103,7 +103,7 @@ public function testSelfSignedResolvesIfVerificationIsDisabled() ) ); - $conn = Block\await($secureConnector->connect('self-signed.badssl.com', 443), $loop, self::TIMEOUT); + $conn = Block\await($secureConnector->connect('self-signed.badssl.com:443'), $loop, self::TIMEOUT); $conn->close(); } @@ -112,7 +112,7 @@ public function testCancelPendingConnection() $loop = new StreamSelectLoop(); $connector = new TcpConnector($loop); - $pending = $connector->connect('8.8.8.8', 80); + $pending = $connector->connect('8.8.8.8:80'); $loop->addTimer(0.001, function () use ($pending) { $pending->cancel(); diff --git a/tests/SecureConnectorTest.php b/tests/SecureConnectorTest.php index e81936f..0d3346e 100644 --- a/tests/SecureConnectorTest.php +++ b/tests/SecureConnectorTest.php @@ -25,9 +25,9 @@ public function setUp() public function testConnectionWillWaitForTcpConnection() { $pending = new Promise\Promise(function () { }); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending)); - $promise = $this->connector->connect('example.com', 80); + $promise = $this->connector->connect('example.com:80'); $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); } @@ -35,9 +35,9 @@ public function testConnectionWillWaitForTcpConnection() public function testCancelDuringTcpConnectionCancelsTcpConnection() { $pending = new Promise\Promise(function () { }, $this->expectCallableOnce()); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending)); - $promise = $this->connector->connect('example.com', 80); + $promise = $this->connector->connect('example.com:80'); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -52,9 +52,9 @@ public function testCancelClosesStreamIfTcpResolvesDespiteCancellation() $resolve($stream); }); - $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com'), $this->equalTo(80))->will($this->returnValue($pending)); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80'))->will($this->returnValue($pending)); - $promise = $this->connector->connect('example.com', 80); + $promise = $this->connector->connect('example.com:80'); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); diff --git a/tests/SecureIntegrationTest.php b/tests/SecureIntegrationTest.php index 87d2308..279083d 100644 --- a/tests/SecureIntegrationTest.php +++ b/tests/SecureIntegrationTest.php @@ -53,7 +53,7 @@ public function tearDown() public function testConnectToServer() { - $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1:' . $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $client->close(); @@ -63,7 +63,7 @@ public function testConnectToServerEmitsConnection() { $promiseServer = $this->createPromiseForEvent($this->server, 'connection', $this->expectCallableOnce()); - $promiseClient = $this->connector->connect('127.0.0.1', $this->portSecure); + $promiseClient = $this->connector->connect('127.0.0.1:' . $this->portSecure); list($_, $client) = Block\awaitAll(array($promiseServer, $promiseClient), $this->loop, self::TIMEOUT); /* @var $client Stream */ @@ -81,7 +81,7 @@ public function testSendSmallDataToServerReceivesOneChunk() }); }); - $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1:' . $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $client->write('hello'); @@ -107,7 +107,7 @@ public function testSendDataWithEndToServerReceivesAllData() }); }); - $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1:' . $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $data = str_repeat('a', 200000); @@ -128,7 +128,7 @@ public function testSendDataWithoutEndingToServerReceivesAllData() }); }); - $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1:' . $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ $data = str_repeat('d', 200000); @@ -148,7 +148,7 @@ public function testConnectToServerWhichSendsSmallDataReceivesOneChunk() $peer->write('hello'); }); - $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1:' . $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ // await client to report one "data" event @@ -165,7 +165,7 @@ public function testConnectToServerWhichSendsDataWithEndReceivesAllData() $peer->end($data); }); - $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop, self::TIMEOUT); + $client = Block\await($this->connector->connect('127.0.0.1:' . $this->portSecure), $this->loop, self::TIMEOUT); /* @var $client Stream */ // await data from client until it closes @@ -181,7 +181,7 @@ public function testConnectToServerWhichSendsDataWithoutEndingReceivesAllData() $peer->write($data); }); - $client = Block\await($this->connector->connect('127.0.0.1', $this->portSecure), $this->loop); + $client = Block\await($this->connector->connect('127.0.0.1:' . $this->portSecure), $this->loop); /* @var $client Stream */ // buffer incoming data for 0.1s (should be plenty of time) diff --git a/tests/TcpConnectorTest.php b/tests/TcpConnectorTest.php index 72770a3..5be7153 100644 --- a/tests/TcpConnectorTest.php +++ b/tests/TcpConnectorTest.php @@ -17,7 +17,7 @@ public function connectionToEmptyPortShouldFail() $loop = new StreamSelectLoop(); $connector = new TcpConnector($loop); - $connector->connect('127.0.0.1', 9999) + $connector->connect('127.0.0.1:9999') ->then($this->expectCallableNever(), $this->expectCallableOnce()); $loop->run(); @@ -37,7 +37,7 @@ public function connectionToTcpServerShouldSucceed() $connector = new TcpConnector($loop); - $stream = Block\await($connector->connect('127.0.0.1', 9999), $loop, self::TIMEOUT); + $stream = Block\await($connector->connect('127.0.0.1:9999'), $loop, self::TIMEOUT); $this->assertInstanceOf('React\Stream\Stream', $stream); @@ -51,7 +51,7 @@ public function connectionToEmptyIp6PortShouldFail() $connector = new TcpConnector($loop); $connector - ->connect('::1', 9999) + ->connect('[::1]:9999') ->then($this->expectCallableNever(), $this->expectCallableOnce()); $loop->run(); @@ -69,7 +69,7 @@ public function connectionToIp6TcpServerShouldSucceed() $connector = new TcpConnector($loop); - $stream = Block\await($connector->connect('::1', 9999), $loop, self::TIMEOUT); + $stream = Block\await($connector->connect('[::1]:9999'), $loop, self::TIMEOUT); $this->assertInstanceOf('React\Stream\Stream', $stream); @@ -82,7 +82,7 @@ public function connectionToHostnameShouldFailImmediately() $loop = $this->getMock('React\EventLoop\LoopInterface'); $connector = new TcpConnector($loop); - $connector->connect('www.google.com', 80)->then( + $connector->connect('www.google.com:80')->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -94,7 +94,7 @@ public function connectionToInvalidAddressShouldFailImmediately() $loop = $this->getMock('React\EventLoop\LoopInterface'); $connector = new TcpConnector($loop); - $connector->connect('255.255.255.255', 12345678)->then( + $connector->connect('255.255.255.255:12345678')->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -109,7 +109,7 @@ public function cancellingConnectionShouldRejectPromise() $server = new Server($loop); $server->listen(0); - $promise = $connector->connect('127.0.0.1', $server->getPort()); + $promise = $connector->connect('127.0.0.1:' . $server->getPort()); $promise->cancel(); $this->setExpectedException('RuntimeException', 'Cancelled'); diff --git a/tests/TimeoutConnectorTest.php b/tests/TimeoutConnectorTest.php index 59486cd..d00f501 100644 --- a/tests/TimeoutConnectorTest.php +++ b/tests/TimeoutConnectorTest.php @@ -13,13 +13,13 @@ public function testRejectsOnTimeout() $promise = new Promise\Promise(function () { }); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $timeout->connect('google.com', 80)->then( + $timeout->connect('google.com:80')->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -32,13 +32,13 @@ public function testRejectsWhenConnectorRejects() $promise = Promise\reject(new \RuntimeException()); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 5.0, $loop); - $timeout->connect('google.com', 80)->then( + $timeout->connect('google.com:80')->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -51,13 +51,13 @@ public function testResolvesWhenConnectorResolves() $promise = Promise\resolve(); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 5.0, $loop); - $timeout->connect('google.com', 80)->then( + $timeout->connect('google.com:80')->then( $this->expectCallableOnce(), $this->expectCallableNever() ); @@ -70,13 +70,13 @@ public function testRejectsAndCancelsPendingPromiseOnTimeout() $promise = new Promise\Promise(function () { }, $this->expectCallableOnce()); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $timeout->connect('google.com', 80)->then( + $timeout->connect('google.com:80')->then( $this->expectCallableNever(), $this->expectCallableOnce() ); @@ -89,13 +89,13 @@ public function testCancelsPendingPromiseOnCancel() $promise = new Promise\Promise(function () { }, $this->expectCallableOnce()); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $out = $timeout->connect('google.com', 80); + $out = $timeout->connect('google.com:80'); $out->cancel(); $out->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -111,13 +111,13 @@ public function testCancelClosesStreamIfTcpResolvesDespiteCancellation() }); $connector = $this->getMock('React\SocketClient\ConnectorInterface'); - $connector->expects($this->once())->method('connect')->with('google.com', 80)->will($this->returnValue($promise)); + $connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise)); $loop = Factory::create(); $timeout = new TimeoutConnector($connector, 0.01, $loop); - $out = $timeout->connect('google.com', 80); + $out = $timeout->connect('google.com:80'); $out->cancel(); $out->then($this->expectCallableNever(), $this->expectCallableOnce()); diff --git a/tests/UnixConnectorTest.php b/tests/UnixConnectorTest.php index 574afa4..af80c4a 100644 --- a/tests/UnixConnectorTest.php +++ b/tests/UnixConnectorTest.php @@ -17,7 +17,7 @@ public function setUp() public function testInvalid() { - $promise = $this->connector->connect('google.com', 80); + $promise = $this->connector->connect('google.com:80'); $promise->then(null, $this->expectCallableOnce()); } @@ -36,7 +36,7 @@ public function testValid() } // tests succeeds if we get notified of successful connection - $promise = $this->connector->connect($path, 0); + $promise = $this->connector->connect($path); $promise->then($this->expectCallableOnce()); // clean up server From 5414f9847a1aa1927fd454070111c357d38f8111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 29 Nov 2016 11:05:42 +0100 Subject: [PATCH 3/3] Preserve all components from URI when passing through --- src/DnsConnector.php | 46 +++++++++++++++++++++++++++++------ src/SecureConnector.php | 12 ++++++++- src/TcpConnector.php | 10 +++++--- src/UnixConnector.php | 8 +++++- tests/DnsConnectorTest.php | 26 ++++++++++++++++++++ tests/SecureConnectorTest.php | 17 +++++++++++++ tests/TcpConnectorTest.php | 28 ++++++++++++++++++++- tests/UnixConnectorTest.php | 6 +++++ 8 files changed, 140 insertions(+), 13 deletions(-) diff --git a/src/DnsConnector.php b/src/DnsConnector.php index 9680e50..21a4623 100644 --- a/src/DnsConnector.php +++ b/src/DnsConnector.php @@ -20,26 +20,58 @@ public function __construct(ConnectorInterface $connector, Resolver $resolver) public function connect($uri) { - $that = $this; + if (strpos($uri, '://') === false) { + $parts = parse_url('tcp://' . $uri); + unset($parts['scheme']); + } else { + $parts = parse_url($uri); + } - $parts = parse_url('tcp://' . $uri); - if (!$parts || !isset($parts['host'], $parts['port'])) { + if (!$parts || !isset($parts['host'])) { return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid')); } + $that = $this; $host = trim($parts['host'], '[]'); return $this ->resolveHostname($host) ->then(function ($ip) use ($that, $parts) { + $uri = ''; + + // prepend original scheme if known + if (isset($parts['scheme'])) { + $uri .= $parts['scheme'] . '://'; + } + if (strpos($ip, ':') !== false) { // enclose IPv6 addresses in square brackets before appending port - $ip = '[' . $ip . ']'; + $uri .= '[' . $ip . ']'; + } else { + $uri .= $ip; + } + + // append original port if known + if (isset($parts['port'])) { + $uri .= ':' . $parts['port']; + } + + // append orignal path if known + if (isset($parts['path'])) { + $uri .= $parts['path']; + } + + // append original query if known + if (isset($parts['query'])) { + $uri .= '?' . $parts['query']; + } + + // append original fragment if known + if (isset($parts['fragment'])) { + $uri .= '#' . $parts['fragment']; } - return $that->connectTcp( - $ip . ':' . $parts['port'] - ); + return $that->connectTcp($uri); }); } diff --git a/src/SecureConnector.php b/src/SecureConnector.php index c0cb351..bf06064 100644 --- a/src/SecureConnector.php +++ b/src/SecureConnector.php @@ -26,7 +26,17 @@ public function connect($uri) return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); } - $host = trim(parse_url('tcp://' . $uri, PHP_URL_HOST), '[]'); + if (strpos($uri, '://') === false) { + $uri = 'tls://' . $uri; + } + + $parts = parse_url($uri); + if (!$parts || !isset($parts['host']) || $parts['scheme'] !== 'tls') { + return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid')); + } + + $uri = str_replace('tls://', '', $uri); + $host = trim($parts['host'], '[]'); $context = $this->context + array( 'SNI_enabled' => true, diff --git a/src/TcpConnector.php b/src/TcpConnector.php index 3bf5ca4..d13f02e 100644 --- a/src/TcpConnector.php +++ b/src/TcpConnector.php @@ -21,12 +21,16 @@ public function __construct(LoopInterface $loop, array $context = array()) public function connect($uri) { - $parts = parse_url('tcp://' . $uri); - if (!$parts || !isset($parts['host'], $parts['port'])) { + if (strpos($uri, '://') === false) { + $uri = 'tcp://' . $uri; + } + + $parts = parse_url($uri); + if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') { return Promise\reject(new \InvalidArgumentException('Given URI "' . $uri . '" is invalid')); } - $ip = trim($parts['host'], '[]'); + $ip = trim($parts['host'], '[]'); if (false === filter_var($ip, FILTER_VALIDATE_IP)) { return Promise\reject(new \InvalidArgumentException('Given URI "' . $ip . '" does not contain a valid host IP')); } diff --git a/src/UnixConnector.php b/src/UnixConnector.php index 74ea3ae..44d225a 100644 --- a/src/UnixConnector.php +++ b/src/UnixConnector.php @@ -25,7 +25,13 @@ public function __construct(LoopInterface $loop) public function connect($path) { - $resource = @stream_socket_client('unix://' . $path, $errno, $errstr, 1.0); + if (strpos($path, '://') === false) { + $path = 'unix://' . $path; + } elseif (substr($path, 0, 7) !== 'unix://') { + return Promise\reject(new \InvalidArgumentException('Given URI "' . $path . '" is invalid')); + } + + $resource = @stream_socket_client($path, $errno, $errstr, 1.0); if (!$resource) { return Promise\reject(new RuntimeException('Unable to connect to unix domain socket "' . $path . '": ' . $errstr, $errno)); diff --git a/tests/DnsConnectorTest.php b/tests/DnsConnectorTest.php index 0ac542c..737ed9a 100644 --- a/tests/DnsConnectorTest.php +++ b/tests/DnsConnectorTest.php @@ -35,6 +35,32 @@ public function testPassThroughResolverIfGivenHost() $this->connector->connect('google.com:80'); } + public function testPassThroughResolverIfGivenHostWhichResolvesToIpv6() + { + $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('google.com'))->will($this->returnValue(Promise\resolve('::1'))); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('[::1]:80'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('google.com:80'); + } + + public function testPassByResolverIfGivenCompleteUri() + { + $this->resolver->expects($this->never())->method('resolve'); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('scheme://127.0.0.1:80/path?query#fragment'))->will($this->returnValue(Promise\reject())); + + $this->connector->connect('scheme://127.0.0.1:80/path?query#fragment'); + } + + public function testRejectsImmediatelyIfUriIsInvalid() + { + $this->resolver->expects($this->never())->method('resolve'); + $this->tcp->expects($this->never())->method('connect'); + + $promise = $this->connector->connect('////'); + + $promise->then($this->expectCallableNever(), $this->expectCallableOnce()); + } + public function testSkipConnectionIfDnsFails() { $this->resolver->expects($this->once())->method('resolve')->with($this->equalTo('example.invalid'))->will($this->returnValue(Promise\reject())); diff --git a/tests/SecureConnectorTest.php b/tests/SecureConnectorTest.php index 0d3346e..1756f43 100644 --- a/tests/SecureConnectorTest.php +++ b/tests/SecureConnectorTest.php @@ -32,6 +32,23 @@ public function testConnectionWillWaitForTcpConnection() $this->assertInstanceOf('React\Promise\PromiseInterface', $promise); } + public function testConnectionWithCompleteUriWillBePassedThroughExpectForScheme() + { + $pending = new Promise\Promise(function () { }); + $this->tcp->expects($this->once())->method('connect')->with($this->equalTo('example.com:80/path?query#fragment'))->will($this->returnValue($pending)); + + $this->connector->connect('tls://example.com:80/path?query#fragment'); + } + + public function testConnectionToInvalidSchemeWillReject() + { + $this->tcp->expects($this->never())->method('connect'); + + $promise = $this->connector->connect('tcp://example.com:80'); + + $promise->then(null, $this->expectCallableOnce()); + } + public function testCancelDuringTcpConnectionCancelsTcpConnection() { $pending = new Promise\Promise(function () { }, $this->expectCallableOnce()); diff --git a/tests/TcpConnectorTest.php b/tests/TcpConnectorTest.php index 5be7153..01d54a3 100644 --- a/tests/TcpConnectorTest.php +++ b/tests/TcpConnectorTest.php @@ -89,7 +89,7 @@ public function connectionToHostnameShouldFailImmediately() } /** @test */ - public function connectionToInvalidAddressShouldFailImmediately() + public function connectionToInvalidPortShouldFailImmediately() { $loop = $this->getMock('React\EventLoop\LoopInterface'); @@ -100,6 +100,32 @@ public function connectionToInvalidAddressShouldFailImmediately() ); } + /** @test */ + public function connectionToInvalidSchemeShouldFailImmediately() + { + $loop = $this->getMock('React\EventLoop\LoopInterface'); + + $connector = new TcpConnector($loop); + $connector->connect('tls://google.com:443')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } + + /** @test */ + public function connectionWithInvalidContextShouldFailImmediately() + { + $this->markTestIncomplete(); + + $loop = $this->getMock('React\EventLoop\LoopInterface'); + + $connector = new TcpConnector($loop, array('bindto' => 'invalid.invalid:123456')); + $connector->connect('127.0.0.1:80')->then( + $this->expectCallableNever(), + $this->expectCallableOnce() + ); + } + /** @test */ public function cancellingConnectionShouldRejectPromise() { diff --git a/tests/UnixConnectorTest.php b/tests/UnixConnectorTest.php index af80c4a..9ade720 100644 --- a/tests/UnixConnectorTest.php +++ b/tests/UnixConnectorTest.php @@ -21,6 +21,12 @@ public function testInvalid() $promise->then(null, $this->expectCallableOnce()); } + public function testInvalidScheme() + { + $promise = $this->connector->connect('tcp://google.com:80'); + $promise->then(null, $this->expectCallableOnce()); + } + public function testValid() { // random unix domain socket path