Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down
5 changes: 5 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
33 changes: 29 additions & 4 deletions src/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
}
70 changes: 70 additions & 0 deletions tests/FunctionalServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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', '<')) {
Expand Down