Skip to content

Commit

Permalink
Merge pull request #82 from clue-labs/connection
Browse files Browse the repository at this point in the history
Connections now resolve with a ConnectionInterface
  • Loading branch information
clue authored Feb 16, 2017
2 parents ea36cab + 87c77d0 commit 3473047
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 47 deletions.
124 changes: 108 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,16 @@ The interface only offers a single method:

#### connect()

The `connect(string $uri): PromiseInterface<Stream, Exception>` method
can be used to establish a streaming connection.
The `connect(string $uri): PromiseInterface<ConnectionInterface, Exception>` method
can be used to create a streaming connection to the given remote address.

It returns a [Promise](https://github.com/reactphp/promise) which either
fulfills with a [Stream](https://github.com/reactphp/stream) or
rejects with an `Exception`:
fulfills with a stream implementing [`ConnectionInterface`](#connectioninterface)
on success or rejects with an `Exception` if the connection is not successful:

```php
$connector->connect('google.com:443')->then(
function (Stream $stream) {
function (ConnectionInterface $connection) {
// connection successfully established
},
function (Exception $error) {
Expand All @@ -67,6 +68,8 @@ $connector->connect('google.com:443')->then(
);
```

See also [`ConnectionInterface`](#connectioninterface) for more details.

The returned Promise MUST be implemented in such a way that it can be
cancelled when it is still pending. Cancelling a pending promise MUST
reject its value with an `Exception`. It SHOULD clean up any underlying
Expand All @@ -78,6 +81,95 @@ $promise = $connector->connect($uri);
$promise->cancel();
```

### ConnectionInterface

The `ConnectionInterface` is used to represent any outgoing connection,
such as a normal TCP/IP connection.

An outgoing connection is a duplex stream (both readable and writable) that
implements React's
[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
It contains additional properties for the local and remote address
where this connection has been established to.

Most commonly, instances implementing this `ConnectionInterface` are returned
by all classes implementing the [`ConnectorInterface`](#connectorinterface).

> Note that this interface is only to be used to represent the client-side end
of an outgoing connection.
It MUST NOT be used to represent an incoming connection in a server-side context.
If you want to accept incoming connections,
use the [`Socket`](https://github.com/reactphp/socket) component instead.

Because the `ConnectionInterface` implements the underlying
[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
you can use any of its events and methods as usual:

```php
$connection->on('data', function ($chunk) {
echo $data;
});

$conenction->on('close', function () {
echo 'closed';
});

$connection->write($data);
$connection->end($data = null);
$connection->close();
// …
```

For more details, see the
[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).

#### getRemoteAddress()

The `getRemoteAddress(): ?string` method can be used to
return the remote address (IP and port) where this connection has been
established to.

```php
$address = $connection->getRemoteAddress();
echo 'Connected to ' . $address . PHP_EOL;
```

If the remote 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 remote address as a string value.
If this is a TCP/IP based connection and you only want the remote IP, you may
use something like this:

```php
$address = $connection->getRemoteAddress();
$ip = trim(parse_url('tcp://' . $address, PHP_URL_HOST), '[]');
echo 'Connected to ' . $ip . PHP_EOL;
```

#### getLocalAddress()

The `getLocalAddress(): ?string` method can be used to
return the full local address (IP and port) where this connection has been
established from.

```php
$address = $connection->getLocalAddress();
echo 'Connected via ' . $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 system has multiple interfaces (e.g. a WAN and a LAN interface),
you can use this method to find out which interface was actually
used for this connection.

### Async TCP/IP connections

The `React\SocketClient\TcpConnector` class implements the
Expand All @@ -87,9 +179,9 @@ TCP/IP connections to any IP-port-combination:
```php
$tcpConnector = new React\SocketClient\TcpConnector($loop);

$tcpConnector->connect('127.0.0.1:80')->then(function (React\Stream\Stream $stream) {
$stream->write('...');
$stream->end();
$tcpConnector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
$connection->write('...');
$connection->end();
});

$loop->run();
Expand Down Expand Up @@ -140,9 +232,9 @@ $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) {
$stream->write('...');
$stream->end();
$dnsConnector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
$connection->write('...');
$connection->end();
});

$loop->run();
Expand Down Expand Up @@ -184,8 +276,8 @@ stream.
```php
$secureConnector = new React\SocketClient\SecureConnector($dnsConnector, $loop);

$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");
$secureConnector->connect('www.google.com:443')->then(function (ConnectionInterface $connection) {
$connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
...
});

Expand Down Expand Up @@ -237,7 +329,7 @@ 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 (ConnectionInterface $connection) {
// connection succeeded within 3.0 seconds
});
```
Expand All @@ -264,8 +356,8 @@ Unix domain socket (UDS) paths like this:
```php
$connector = new React\SocketClient\UnixConnector($loop);

$connector->connect('/tmp/demo.sock')->then(function (React\Stream\Stream $stream) {
$stream->write("HELLO\n");
$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) {
$connection->write("HELLO\n");
});

$loop->run();
Expand Down
10 changes: 5 additions & 5 deletions examples/01-http.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use React\EventLoop\Factory;
use React\SocketClient\TcpConnector;
use React\SocketClient\DnsConnector;
use React\Stream\Stream;
use React\SocketClient\TimeoutConnector;
use React\SocketClient\ConnectionInterface;

require __DIR__ . '/../vendor/autoload.php';

Expand All @@ -19,15 +19,15 @@
// time out connection attempt in 3.0s
$dns = new TimeoutConnector($dns, 3.0, $loop);

$dns->create('www.google.com', 80)->then(function (Stream $stream) {
$stream->on('data', function ($data) {
$dns->create('www.google.com', 80)->then(function (ConnectionInterface $connection) {
$connection->on('data', function ($data) {
echo $data;
});
$stream->on('close', function () {
$connection->on('close', function () {
echo '[CLOSED]' . PHP_EOL;
});

$stream->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
$connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
}, 'printf');

$loop->run();
10 changes: 5 additions & 5 deletions examples/02-https.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use React\SocketClient\TcpConnector;
use React\SocketClient\DnsConnector;
use React\SocketClient\SecureConnector;
use React\Stream\Stream;
use React\SocketClient\TimeoutConnector;
use React\SocketClient\ConnectionInterface;

require __DIR__ . '/../vendor/autoload.php';

Expand All @@ -21,15 +21,15 @@
// time out connection attempt in 3.0s
$tls = new TimeoutConnector($tls, 3.0, $loop);

$tls->create('www.google.com', 443)->then(function (Stream $stream) {
$stream->on('data', function ($data) {
$tls->create('www.google.com', 443)->then(function (ConnectionInterface $connection) {
$connection->on('data', function ($data) {
echo $data;
});
$stream->on('close', function () {
$connection->on('close', function () {
echo '[CLOSED]' . PHP_EOL;
});

$stream->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
$connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
}, 'printf');

$loop->run();
12 changes: 6 additions & 6 deletions examples/03-netcat.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use React\EventLoop\Factory;
use React\SocketClient\TcpConnector;
use React\SocketClient\DnsConnector;
use React\Stream\Stream;
use React\SocketClient\TimeoutConnector;
use React\SocketClient\ConnectionInterface;

require __DIR__ . '/../vendor/autoload.php';

Expand Down Expand Up @@ -33,21 +33,21 @@

$stderr->write('Connecting' . PHP_EOL);

$dns->create($argv[1], $argv[2])->then(function (Stream $stream) use ($stdin, $stdout, $stderr) {
$dns->create($argv[1], $argv[2])->then(function (ConnectionInterface $connection) use ($stdin, $stdout, $stderr) {
// pipe everything from STDIN into connection
$stdin->resume();
$stdin->pipe($stream);
$stdin->pipe($connection);

// pipe everything from connection to STDOUT
$stream->pipe($stdout);
$connection->pipe($stdout);

// report errors to STDERR
$stream->on('error', function ($error) use ($stderr) {
$connection->on('error', function ($error) use ($stderr) {
$stderr->write('Stream ERROR: ' . $error . PHP_EOL);
});

// report closing and stop reading from input
$stream->on('close', function () use ($stderr, $stdin) {
$connection->on('close', function () use ($stderr, $stdin) {
$stderr->write('[CLOSED]' . PHP_EOL);
$stdin->close();
});
Expand Down
102 changes: 102 additions & 0 deletions src/ConnectionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace React\SocketClient;

use React\Stream\DuplexStreamInterface;

/**
* Any outgoing connection is represented by this interface,
* such as a normal TCP/IP connection.
*
* An outgoing connection is a duplex stream (both readable and writable) that
* implements React's
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
* It contains additional properties for the local and remote address
* where this connection has been established to.
*
* Most commonly, instances implementing this `ConnectionInterface` are returned
* by all classes implementing the [`ConnectorInterface`](#connectorinterface).
*
* > Note that this interface is only to be used to represent the client-side end
* of an outgoing connection.
* It MUST NOT be used to represent an incoming connection in a server-side context.
* If you want to accept incoming connections,
* use the [`Socket`](https://github.com/reactphp/socket) component instead.
*
* Because the `ConnectionInterface` implements the underlying
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
* you can use any of its events and methods as usual:
*
* ```php
* $connection->on('data', function ($chunk) {
* echo $data;
* });
*
* $conenction->on('close', function () {
* echo 'closed';
* });
*
* $connection->write($data);
* $connection->end($data = null);
* $connection->close();
* // …
* ```
*
* For more details, see the
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
*
* @see DuplexStreamInterface
* @see ConnectorInterface
*/
interface ConnectionInterface extends DuplexStreamInterface
{
/**
* Returns the remote address (IP and port) where this connection has been established to
*
* ```php
* $address = $connection->getRemoteAddress();
* echo 'Connected to ' . $address . PHP_EOL;
* ```
*
* If the remote 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 remote address as a string value.
* If this is a TCP/IP based connection and you only want the remote IP, you may
* use something like this:
*
* ```php
* $address = $connection->getRemoteAddress();
* $ip = trim(parse_url('tcp://' . $address, PHP_URL_HOST), '[]');
* echo 'Connected to ' . $ip . PHP_EOL;
* ```
*
* @return ?string remote address (IP and port) or null if unknown
*/
public function getRemoteAddress();

/**
* Returns the full local address (IP and port) where this connection has been established from
*
* ```php
* $address = $connection->getLocalAddress();
* echo 'Connected via ' . $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 system has multiple interfaces (e.g. a WAN and a LAN interface),
* you can use this method to find out which interface was actually
* used for this connection.
*
* @return ?string local address (IP and port) or null if unknown
* @see self::getRemoteAddress()
*/
public function getLocalAddress();
}
Loading

0 comments on commit 3473047

Please sign in to comment.