From 48a42c1a2338ad3be83363045b3fe108041a90a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 4 Feb 2017 18:21:50 +0100 Subject: [PATCH] Add getLocalAddress() method --- README.md | 26 ++++++++++++- src/Connection.php | 5 +++ src/ConnectionInterface.php | 33 ++++++++++++++-- tests/FunctionalServerTest.php | 70 ++++++++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2e1126b1..28ba5ca4 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ For the code of the current stable 0.4.x release, checkout the * [SecureServer](#secureserver) * [ConnectionInterface](#connectioninterface) * [getRemoteAddress()](#getremoteaddress) + * [getLocalAddress()](#getlocaladdress) * [Install](#install) * [Tests](#tests) * [License](#license) @@ -291,8 +292,8 @@ The `ConnectionInterface` is used to represent any incoming connection. An incoming connection is a duplex stream (both readable and writable) that implements React's -[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface) -and contains only a single additional property, the remote address (client IP) +[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface). +It contains additional properties for the local and remote address (client IP) where this connection has been established from. > Note that this interface is only to be used to represent the server-side end @@ -346,6 +347,27 @@ $ip = trim(parse_url('tcp://' . $address, PHP_URL_HOST), '[]'); echo 'Connection from ' . $ip . PHP_EOL; ``` +#### getLocalAddress() + +The `getLocalAddress(): ?string` method returns the full local address +(client IP and port) where this connection has been established to. + +```php +$address = $connection->getLocalAddress(); +echo 'Connection to ' . $address . PHP_EOL; +``` + +If the local address can not be determined or is unknown at this time (such as +after the connection has been closed), it MAY return a `NULL` value instead. + +Otherwise, it will return the full local address as a string value. + +This method complements the [`getRemoteAddress()`](#getremoteaddress) method, +so they should not be confused. +If your `Server` instance is listening on multiple interfaces (e.g. using +the address `0.0.0.0`), you can use this method to find out which interface +actually accepted this connection (such as a public or local interface). + ## Install The recommended way to install this library is [through Composer](http://getcomposer.org). diff --git a/src/Connection.php b/src/Connection.php index a1215c32..75228b67 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -31,6 +31,11 @@ public function getRemoteAddress() return $this->parseAddress(@stream_socket_get_name($this->stream, true)); } + public function getLocalAddress() + { + return $this->parseAddress(@stream_socket_get_name($this->stream, false)); + } + private function parseAddress($address) { if ($address === false) { diff --git a/src/ConnectionInterface.php b/src/ConnectionInterface.php index 3687f9dd..d13177c5 100644 --- a/src/ConnectionInterface.php +++ b/src/ConnectionInterface.php @@ -8,9 +8,9 @@ * Any incoming connection is represented by this interface. * * An incoming connection is a duplex stream (both readable and writable) that - * implements React's DuplexStreamInterface and contains only a single - * additional property, the remote address (client IP) where this connection has - * been established from. + * implements React's DuplexStreamInterface. + * It contains additional properties for the local and remote address (client IP) + * where this connection has been established from. * * Note that this interface is only to be used to represent the server-side end * of an incoming connection. @@ -47,7 +47,32 @@ interface ConnectionInterface extends DuplexStreamInterface * echo 'Connection from ' . $ip . PHP_EOL; * ``` * - * @return string|null remote address (client IP and port) or null if unknown + * @return ?string remote address (client IP and port) or null if unknown */ public function getRemoteAddress(); + + /** + * Returns the full local address (client IP and port) where this connection has been established to + * + * ```php + * $address = $connection->getLocalAddress(); + * echo 'Connection to ' . $address . PHP_EOL; + * ``` + * + * If the local address can not be determined or is unknown at this time (such as + * after the connection has been closed), it MAY return a `NULL` value instead. + * + * Otherwise, it will return the full local address as a string value. + * + * This method complements the [`getRemoteAddress()`](#getremoteaddress) method, + * so they should not be confused. + * + * If your `Server` instance is listening on multiple interfaces (e.g. using + * the address `0.0.0.0`), you can use this method to find out which interface + * actually accepted this connection (such as a public or local interface). + * + * @return ?string local address (client IP and port) or null if unknown + * @see self::getRemoteAddress() + */ + public function getLocalAddress(); } diff --git a/tests/FunctionalServerTest.php b/tests/FunctionalServerTest.php index 9e0678cb..ee450500 100644 --- a/tests/FunctionalServerTest.php +++ b/tests/FunctionalServerTest.php @@ -49,6 +49,49 @@ public function testEmitsConnectionWithRemoteIp() $this->assertContains('127.0.0.1:', $peer); } + public function testEmitsConnectionWithLocalIp() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + $local = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$local) { + $local = $conn->getLocalAddress(); + }); + $port = $this->getPort($server); + + $connector = new TcpConnector($loop); + $promise = $connector->create('127.0.0.1', $port); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertEquals('127.0.0.1:' . $port, $local); + $this->assertEquals($server->getAddress(), $local); + } + + public function testEmitsConnectionWithLocalIpDespiteListeningOnAll() + { + $loop = Factory::create(); + + $server = new Server('0.0.0.0:0', $loop); + $local = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$local) { + $local = $conn->getLocalAddress(); + }); + $port = $this->getPort($server); + + $connector = new TcpConnector($loop); + $promise = $connector->create('127.0.0.1', $port); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertEquals('127.0.0.1:' . $port, $local); + } + public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer() { $loop = Factory::create(); @@ -159,6 +202,33 @@ public function testEmitsConnectionWithRemoteIpv6() $this->assertContains('[::1]:', $peer); } + public function testEmitsConnectionWithLocalIpv6() + { + $loop = Factory::create(); + + try { + $server = new Server('[::1]:0', $loop); + } catch (ConnectionException $e) { + $this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)'); + } + + $local = null; + $server->on('connection', function (ConnectionInterface $conn) use (&$local) { + $local = $conn->getLocalAddress(); + }); + $port = $this->getPort($server); + + $connector = new TcpConnector($loop); + $promise = $connector->create('::1', $port); + + $promise->then($this->expectCallableOnce()); + + Block\sleep(0.1, $loop); + + $this->assertEquals('[::1]:' . $port, $local); + $this->assertEquals($server->getAddress(), $local); + } + public function testAppliesContextOptionsToSocketStreamResource() { if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {