Skip to content

Commit 535f323

Browse files
authored
Merge pull request #68 from clue-labs/getlocaladdress
Add getLocalAddress() method
2 parents 2d5a2e5 + 48a42c1 commit 535f323

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ For the code of the current stable 0.4.x release, checkout the
2525
* [SecureServer](#secureserver)
2626
* [ConnectionInterface](#connectioninterface)
2727
* [getRemoteAddress()](#getremoteaddress)
28+
* [getLocalAddress()](#getlocaladdress)
2829
* [Install](#install)
2930
* [Tests](#tests)
3031
* [License](#license)
@@ -291,8 +292,8 @@ The `ConnectionInterface` is used to represent any incoming connection.
291292

292293
An incoming connection is a duplex stream (both readable and writable) that
293294
implements React's
294-
[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
295-
and contains only a single additional property, the remote address (client IP)
295+
[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
296+
It contains additional properties for the local and remote address (client IP)
296297
where this connection has been established from.
297298

298299
> 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), '[]');
346347
echo 'Connection from ' . $ip . PHP_EOL;
347348
```
348349

350+
#### getLocalAddress()
351+
352+
The `getLocalAddress(): ?string` method returns the full local address
353+
(client IP and port) where this connection has been established to.
354+
355+
```php
356+
$address = $connection->getLocalAddress();
357+
echo 'Connection to ' . $address . PHP_EOL;
358+
```
359+
360+
If the local address can not be determined or is unknown at this time (such as
361+
after the connection has been closed), it MAY return a `NULL` value instead.
362+
363+
Otherwise, it will return the full local address as a string value.
364+
365+
This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
366+
so they should not be confused.
367+
If your `Server` instance is listening on multiple interfaces (e.g. using
368+
the address `0.0.0.0`), you can use this method to find out which interface
369+
actually accepted this connection (such as a public or local interface).
370+
349371
## Install
350372

351373
The recommended way to install this library is [through Composer](http://getcomposer.org).

src/Connection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public function getRemoteAddress()
3131
return $this->parseAddress(@stream_socket_get_name($this->stream, true));
3232
}
3333

34+
public function getLocalAddress()
35+
{
36+
return $this->parseAddress(@stream_socket_get_name($this->stream, false));
37+
}
38+
3439
private function parseAddress($address)
3540
{
3641
if ($address === false) {

src/ConnectionInterface.php

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
* Any incoming connection is represented by this interface.
99
*
1010
* An incoming connection is a duplex stream (both readable and writable) that
11-
* implements React's DuplexStreamInterface and contains only a single
12-
* additional property, the remote address (client IP) where this connection has
13-
* been established from.
11+
* implements React's DuplexStreamInterface.
12+
* It contains additional properties for the local and remote address (client IP)
13+
* where this connection has been established from.
1414
*
1515
* Note that this interface is only to be used to represent the server-side end
1616
* of an incoming connection.
@@ -47,7 +47,32 @@ interface ConnectionInterface extends DuplexStreamInterface
4747
* echo 'Connection from ' . $ip . PHP_EOL;
4848
* ```
4949
*
50-
* @return string|null remote address (client IP and port) or null if unknown
50+
* @return ?string remote address (client IP and port) or null if unknown
5151
*/
5252
public function getRemoteAddress();
53+
54+
/**
55+
* Returns the full local address (client IP and port) where this connection has been established to
56+
*
57+
* ```php
58+
* $address = $connection->getLocalAddress();
59+
* echo 'Connection to ' . $address . PHP_EOL;
60+
* ```
61+
*
62+
* If the local address can not be determined or is unknown at this time (such as
63+
* after the connection has been closed), it MAY return a `NULL` value instead.
64+
*
65+
* Otherwise, it will return the full local address as a string value.
66+
*
67+
* This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
68+
* so they should not be confused.
69+
*
70+
* If your `Server` instance is listening on multiple interfaces (e.g. using
71+
* the address `0.0.0.0`), you can use this method to find out which interface
72+
* actually accepted this connection (such as a public or local interface).
73+
*
74+
* @return ?string local address (client IP and port) or null if unknown
75+
* @see self::getRemoteAddress()
76+
*/
77+
public function getLocalAddress();
5378
}

tests/FunctionalServerTest.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,49 @@ public function testEmitsConnectionWithRemoteIp()
4949
$this->assertContains('127.0.0.1:', $peer);
5050
}
5151

52+
public function testEmitsConnectionWithLocalIp()
53+
{
54+
$loop = Factory::create();
55+
56+
$server = new Server(0, $loop);
57+
$local = null;
58+
$server->on('connection', function (ConnectionInterface $conn) use (&$local) {
59+
$local = $conn->getLocalAddress();
60+
});
61+
$port = $this->getPort($server);
62+
63+
$connector = new TcpConnector($loop);
64+
$promise = $connector->create('127.0.0.1', $port);
65+
66+
$promise->then($this->expectCallableOnce());
67+
68+
Block\sleep(0.1, $loop);
69+
70+
$this->assertEquals('127.0.0.1:' . $port, $local);
71+
$this->assertEquals($server->getAddress(), $local);
72+
}
73+
74+
public function testEmitsConnectionWithLocalIpDespiteListeningOnAll()
75+
{
76+
$loop = Factory::create();
77+
78+
$server = new Server('0.0.0.0:0', $loop);
79+
$local = null;
80+
$server->on('connection', function (ConnectionInterface $conn) use (&$local) {
81+
$local = $conn->getLocalAddress();
82+
});
83+
$port = $this->getPort($server);
84+
85+
$connector = new TcpConnector($loop);
86+
$promise = $connector->create('127.0.0.1', $port);
87+
88+
$promise->then($this->expectCallableOnce());
89+
90+
Block\sleep(0.1, $loop);
91+
92+
$this->assertEquals('127.0.0.1:' . $port, $local);
93+
}
94+
5295
public function testEmitsConnectionWithRemoteIpAfterConnectionIsClosedByPeer()
5396
{
5497
$loop = Factory::create();
@@ -159,6 +202,33 @@ public function testEmitsConnectionWithRemoteIpv6()
159202
$this->assertContains('[::1]:', $peer);
160203
}
161204

205+
public function testEmitsConnectionWithLocalIpv6()
206+
{
207+
$loop = Factory::create();
208+
209+
try {
210+
$server = new Server('[::1]:0', $loop);
211+
} catch (ConnectionException $e) {
212+
$this->markTestSkipped('Unable to start IPv6 server socket (not available on your platform?)');
213+
}
214+
215+
$local = null;
216+
$server->on('connection', function (ConnectionInterface $conn) use (&$local) {
217+
$local = $conn->getLocalAddress();
218+
});
219+
$port = $this->getPort($server);
220+
221+
$connector = new TcpConnector($loop);
222+
$promise = $connector->create('::1', $port);
223+
224+
$promise->then($this->expectCallableOnce());
225+
226+
Block\sleep(0.1, $loop);
227+
228+
$this->assertEquals('[::1]:' . $port, $local);
229+
$this->assertEquals($server->getAddress(), $local);
230+
}
231+
162232
public function testAppliesContextOptionsToSocketStreamResource()
163233
{
164234
if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.13', '<')) {

0 commit comments

Comments
 (0)