From 45a7a24442636a1028b5e16e04eb46ffddff1270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 10 May 2017 00:08:51 +0200 Subject: [PATCH 1/3] Use main Connector class where possible --- README.md | 31 +++++++++++++++++----------- examples/01-proxy-https.php | 14 +++++++------ examples/02-optional-proxy-https.php | 22 ++++++++------------ examples/11-proxy-smtp.php | 12 +++++++---- examples/12-proxy-smtps.php | 14 +++++++------ src/ProxyConnector.php | 4 ++-- 6 files changed, 54 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 7defa2f..18f2e39 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,15 @@ secure HTTPS request to google.com through a local HTTP proxy server: ```php $loop = React\EventLoop\Factory::create(); -$connector = new TcpConnector($loop); -$proxy = new ProxyConnector('127.0.0.1:8080', $connector); -$ssl = new SecureConnector($proxy, $loop); -$ssl->connect('google.com:443')->then(function (ConnectionInterface $stream) { +$proxy = new ProxyConnector('127.0.0.1:8080', new Connector($loop)); +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'timeout' => 3.0, + 'dns' => false +)); + +$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) { $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n"); $stream->on('data', function ($chunk) { echo $chunk; @@ -91,7 +95,7 @@ Its constructor simply accepts an HTTP proxy URL and a connector used to connect to the proxy server address: ```php -$connector = new TcpConnector($loop); +$connector = new Connector($loop); $proxy = new ProxyConnector('127.0.0.1:8080', $connector); ``` @@ -99,7 +103,7 @@ The proxy URL may or may not contain a scheme and port definition. The default port will be `80` for HTTP (or `443` for HTTPS), but many common HTTP proxy servers use custom ports. In its most simple form, the given connector will be a -[`TcpConnector`](https://github.com/reactphp/socket#tcpconnector) if you +[`\React\Socket\Connector`](https://github.com/reactphp/socket#connector) if you want to connect to a given IP address as above. This is the main class in this package. @@ -121,7 +125,7 @@ connector is actually inherently a general-purpose plain TCP/IP connector: ```php $proxy = new ProxyConnector('127.0.0.1:8080', $connector); -$proxy->connect('smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) { +$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) { $stream->write("EHLO local\r\n"); $stream->on('data', function ($chunk) use ($stream) { echo $chunk; @@ -133,15 +137,18 @@ Note that HTTP CONNECT proxies often restrict which ports one may connect to. Many (public) proxy servers do in fact limit this to HTTPS (443) only. If you want to establish a TLS connection (such as HTTPS) between you and -your destination, you may want to wrap this connector in a -[`SecureConnector`](https://github.com/reactphp/socket#secureconnector) -instance: +your destination, you may want to wrap this connector in React's +[`Connector`](https://github.com/reactphp/socket#connector) or the low-level +[`SecureConnector`](https://github.com/reactphp/socket#secureconnector): ```php $proxy = new ProxyConnector('127.0.0.1:8080', $connector); -$ssl = new SecureConnector($proxy, $loop); +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'dns' => false +)); -$ssl->connect('smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) { +$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) { $stream->write("EHLO local\r\n"); $stream->on('data', function ($chunk) use ($stream) { echo $chunk; diff --git a/examples/01-proxy-https.php b/examples/01-proxy-https.php index f5cc1ce..c07ea0d 100644 --- a/examples/01-proxy-https.php +++ b/examples/01-proxy-https.php @@ -4,8 +4,7 @@ // The proxy can be given as first argument and defaults to localhost:8080 otherwise. use Clue\React\HttpProxy\ProxyConnector; -use React\Socket\TcpConnector; -use React\Socket\SecureConnector; +use React\Socket\Connector; use React\Socket\ConnectionInterface; require __DIR__ . '/../vendor/autoload.php'; @@ -14,11 +13,14 @@ $loop = React\EventLoop\Factory::create(); -$connector = new TcpConnector($loop); -$proxy = new ProxyConnector($url, $connector); -$ssl = new SecureConnector($proxy, $loop); +$proxy = new ProxyConnector($url, new Connector($loop)); +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'timeout' => 3.0, + 'dns' => false +)); -$ssl->connect('google.com:443')->then(function (ConnectionInterface $stream) { +$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) { $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n"); $stream->on('data', function ($chunk) { echo $chunk; diff --git a/examples/02-optional-proxy-https.php b/examples/02-optional-proxy-https.php index 4e83d73..c65e69a 100644 --- a/examples/02-optional-proxy-https.php +++ b/examples/02-optional-proxy-https.php @@ -8,30 +8,26 @@ // network protocol otherwise. use Clue\React\HttpProxy\ProxyConnector; -use React\Socket\TcpConnector; -use React\Socket\SecureConnector; -use React\Socket\DnsConnector; -use React\Dns\Resolver\Factory; +use React\Socket\Connector; use React\Socket\ConnectionInterface; require __DIR__ . '/../vendor/autoload.php'; $loop = React\EventLoop\Factory::create(); -$tcp = new TcpConnector($loop); -$dnsFactory = new Factory(); -$resolver = $dnsFactory->create('8.8.8.8', $loop); -$dns = new DnsConnector($tcp, $resolver); +$connector = new Connector($loop); // first argument given? use this as the proxy URL if (isset($argv[1])) { - $proxy = new ProxyConnector($argv[1], $dns); - $connector = new SecureConnector($proxy, $loop); -} else { - $connector = new SecureConnector($dns, $loop); + $proxy = new ProxyConnector($argv[1], $connector); + $connector = new Connector($loop, array( + 'tcp' => $proxy, + 'timeout' => 3.0, + 'dns' => false + )); } -$connector->connect('google.com:443')->then(function (ConnectionInterface $stream) { +$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) { $stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n"); $stream->on('data', function ($chunk) { echo $chunk; diff --git a/examples/11-proxy-smtp.php b/examples/11-proxy-smtp.php index 703ca66..3225491 100644 --- a/examples/11-proxy-smtp.php +++ b/examples/11-proxy-smtp.php @@ -5,7 +5,7 @@ // Please note that MANY public proxies do not allow SMTP connections, YMMV. use Clue\React\HttpProxy\ProxyConnector; -use React\Socket\TcpConnector; +use React\Socket\Connector; use React\Socket\ConnectionInterface; require __DIR__ . '/../vendor/autoload.php'; @@ -14,10 +14,14 @@ $loop = React\EventLoop\Factory::create(); -$connector = new TcpConnector($loop); -$proxy = new ProxyConnector($url, $connector); +$proxy = new ProxyConnector($url, new Connector($loop)); +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'timeout' => 3.0, + 'dns' => false +)); -$proxy->connect('smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) { +$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) { $stream->write("EHLO local\r\n"); $stream->on('data', function ($chunk) use ($stream) { echo $chunk; diff --git a/examples/12-proxy-smtps.php b/examples/12-proxy-smtps.php index 6719f75..462dba3 100644 --- a/examples/12-proxy-smtps.php +++ b/examples/12-proxy-smtps.php @@ -8,8 +8,7 @@ // Please note that MANY public proxies do not allow SMTP connections, YMMV. use Clue\React\HttpProxy\ProxyConnector; -use React\Socket\TcpConnector; -use React\Socket\SecureConnector; +use React\Socket\Connector; use React\Socket\ConnectionInterface; require __DIR__ . '/../vendor/autoload.php'; @@ -18,11 +17,14 @@ $loop = React\EventLoop\Factory::create(); -$connector = new TcpConnector($loop); -$proxy = new ProxyConnector($url, $connector); -$ssl = new SecureConnector($proxy, $loop); +$proxy = new ProxyConnector($url, new Connector($loop)); +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'timeout' => 3.0, + 'dns' => false +)); -$ssl->connect('smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) { +$connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionInterface $stream) { $stream->write("EHLO local\r\n"); $stream->on('data', function ($chunk) use ($stream) { echo $chunk; diff --git a/src/ProxyConnector.php b/src/ProxyConnector.php index ff1f6af..f8e27be 100644 --- a/src/ProxyConnector.php +++ b/src/ProxyConnector.php @@ -49,8 +49,8 @@ class ProxyConnector implements ConnectorInterface * port definition. The default port will be `80` for HTTP (or `443` for * HTTPS), but many common HTTP proxy servers use custom ports. * @param ConnectorInterface $connector In its most simple form, the given - * connector will be a TcpConnector if you want to connect to a given IP - * address. + * connector will be a \React\Socket\Connector if you want to connect to + * a given IP address. * @throws InvalidArgumentException if the proxy URL is invalid */ public function __construct($proxyUrl, ConnectorInterface $connector) From 57d21379687efe9865cb0a2c8faba565608303e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 8 Jun 2017 16:18:03 +0200 Subject: [PATCH 2/3] Add chapters for plain TCP and secure TLS connections --- README.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18f2e39..0c8ed0b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ Async HTTP CONNECT proxy connector, use any TCP/IP protocol through an HTTP prox * [ConnectorInterface](#connectorinterface) * [connect()](#connect) * [ProxyConnector](#proxyconnector) + * [Plain TCP connections](#plain-tcp-connections) + * [Secure TLS connections](#secure-tls-connections) + * [Advanced secure proxy connections](#advanced-secure-proxy-connections) * [Install](#install) * [Tests](#tests) * [License](#license) @@ -118,9 +121,14 @@ higher-level component: + $client = new SomeClient($proxy); ``` +#### Plain TCP connections + This is most frequently used to issue HTTPS requests to your destination. However, this is actually performed on a higher protocol layer and this -connector is actually inherently a general-purpose plain TCP/IP connector: +connector is actually inherently a general-purpose plain TCP/IP connector. + +The `ProxyConnector` implements the [`ConnectorInterface`](#connectorinterface) and +hence provides a single public method, the [`connect()`](#connect) method. ```php $proxy = new ProxyConnector('127.0.0.1:8080', $connector); @@ -133,9 +141,28 @@ $proxy->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInter }); ``` +You can either use the `ProxyConnector` directly or you may want to wrap this connector +in React's [`Connector`](https://github.com/reactphp/socket#connector): + +```php +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'dns' => false +)); + +$connector->connect('tcp://smtp.googlemail.com:587')->then(function (ConnectionInterface $stream) { + $stream->write("EHLO local\r\n"); + $stream->on('data', function ($chunk) use ($stream) { + echo $chunk; + }); +}); +``` + Note that HTTP CONNECT proxies often restrict which ports one may connect to. Many (public) proxy servers do in fact limit this to HTTPS (443) only. +#### Secure TLS connections + If you want to establish a TLS connection (such as HTTPS) between you and your destination, you may want to wrap this connector in React's [`Connector`](https://github.com/reactphp/socket#connector) or the low-level @@ -156,6 +183,11 @@ $connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionI }); ``` +> Also note how secure TLS connections are in fact entirely handled outside of + this HTTP CONNECT client implementation. + +#### Advanced secure proxy connections + Note that communication between the client and the proxy is usually via an unencrypted, plain TCP/IP HTTP connection. Note that this is the most common setup, because you can still establish a TLS connection between you and the From 01013ffb0251de5da18c2cb9cd358ede2415887b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 8 Jun 2017 16:24:42 +0200 Subject: [PATCH 3/3] Documentation for connection timeouts and DNS resolution --- README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/README.md b/README.md index 0c8ed0b..75a0d7c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Async HTTP CONNECT proxy connector, use any TCP/IP protocol through an HTTP prox * [ProxyConnector](#proxyconnector) * [Plain TCP connections](#plain-tcp-connections) * [Secure TLS connections](#secure-tls-connections) + * [Connection timeout](#connection-timeout) + * [DNS resolution](#dns-resolution) * [Advanced secure proxy connections](#advanced-secure-proxy-connections) * [Install](#install) * [Tests](#tests) @@ -186,6 +188,85 @@ $connector->connect('tls://smtp.googlemail.com:465')->then(function (ConnectionI > Also note how secure TLS connections are in fact entirely handled outside of this HTTP CONNECT client implementation. +#### Connection timeout + +By default, the `ProxyConnector` does not implement any timeouts for establishing remote +connections. +Your underlying operating system may impose limits on pending and/or idle TCP/IP +connections, anywhere in a range of a few minutes to several hours. + +Many use cases require more control over the timeout and likely values much +smaller, usually in the range of a few seconds only. + +You can use React's [`Connector`](https://github.com/reactphp/socket#connector) +or the low-level +[`TimeoutConnector`](https://github.com/reactphp/socket#timeoutconnector) +to decorate any given `ConnectorInterface` instance. +It provides the same `connect()` method, but will automatically reject the +underlying connection attempt if it takes too long: + +```php +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'dns' => false, + 'timeout' => 3.0 +)); + +$connector->connect('tcp://google.com:80')->then(function ($stream) { + // connection succeeded within 3.0 seconds +}); +``` + +See also any of the [examples](examples). + +> Also note how connection timeout is in fact entirely handled outside of this + HTTP CONNECT client implementation. + +#### DNS resolution + +By default, the `ProxyConnector` does not perform any DNS resolution at all and simply +forwards any hostname you're trying to connect to the remote proxy server. +The remote proxy server is thus responsible for looking up any hostnames via DNS +(this default mode is thus called *remote DNS resolution*). + +As an alternative, you can also send the destination IP to the remote proxy +server. +In this mode you either have to stick to using IPs only (which is ofen unfeasable) +or perform any DNS lookups locally and only transmit the resolved destination IPs +(this mode is thus called *local DNS resolution*). + +The default *remote DNS resolution* is useful if your local `ProxyConnector` either can +not resolve target hostnames because it has no direct access to the internet or +if it should not resolve target hostnames because its outgoing DNS traffic might +be intercepted. + +As noted above, the `ProxyConnector` defaults to using remote DNS resolution. +However, wrapping the `ProxyConnector` in React's +[`Connector`](https://github.com/reactphp/socket#connector) actually +performs local DNS resolution unless explicitly defined otherwise. +Given that remote DNS resolution is assumed to be the preferred mode, all +other examples explicitly disable DNS resoltion like this: + +```php +$connector = new Connector($loop, array( + 'tcp' => $proxy, + 'dns' => false +)); +``` + +If you want to explicitly use *local DNS resolution*, you can use the following code: + +```php +// set up Connector which uses Google's public DNS (8.8.8.8) +$connector = Connector($loop, array( + 'tcp' => $proxy, + 'dns' => '8.8.8.8' +)); +``` + +> Also note how local DNS resolution is in fact entirely handled outside of this + HTTP CONNECT client implementation. + #### Advanced secure proxy connections Note that communication between the client and the proxy is usually via an