diff --git a/README.md b/README.md index 8e7b7ea..ac9eb75 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,9 @@ The following example code demonstrates how this library can be used to send a secure HTTPS request to google.com through a local HTTP proxy server: ```php -$loop = React\EventLoop\Factory::create(); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false @@ -89,8 +85,6 @@ $connector->connect('tls://google.com:443')->then(function (React\Socket\Connect echo $chunk; }); }, 'printf'); - -$loop->run(); ``` See also the [examples](examples). @@ -106,20 +100,34 @@ any destination by using an intermediary HTTP CONNECT proxy. [you] -> [proxy] -> [destination] ``` -Its constructor simply accepts an HTTP proxy URL and a connector used to connect -to the proxy server address: +Its constructor simply accepts an HTTP proxy URL with the proxy server address: ```php -$connector = new React\Socket\Connector($loop); -$proxy = new Clue\React\HttpProxy\ProxyConnector('http://127.0.0.1:8080', $connector); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); ``` 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 (often the alternative HTTP port `8080`). -In its most simple form, the given connector will be a -[`\React\Socket\Connector`](https://github.com/reactphp/socket#connector) if you -want to connect to a given IP address as above. + +If you need custom connector settings (DNS resolution, TLS parameters, timeouts, +proxy servers etc.), you can explicitly pass a custom instance of the +[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface): + +```php +$connector = new React\Socket\Connector(null, array( + 'dns' => '127.0.0.1', + 'tcp' => array( + 'bindto' => '192.168.10.1:0' + ), + 'tls' => array( + 'verify_peer' => false, + 'verify_peer_name' => false + ) +)); + +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector); +``` This is the main class in this package. Because it implements ReactPHP's standard @@ -138,7 +146,7 @@ higher-level component: ```diff - $acme = new AcmeApi($connector); -+ $proxy = new Clue\React\HttpProxy\ProxyConnector('http://127.0.0.1:8080', $connector); ++ $proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector); + $acme = new AcmeApi($proxy); ``` @@ -151,10 +159,7 @@ As documented above, you can simply invoke its `connect()` method to establish a streaming plain TCP/IP connection and use any higher level protocol like so: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); $proxy->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write("EHLO local\r\n"); @@ -168,12 +173,9 @@ You can either use the `ProxyConnector` directly or you may want to wrap this co in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector): ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); @@ -194,16 +196,12 @@ Many (public) proxy servers do in fact limit this to HTTPS (443) only. This class can also be used if you want to establish a secure TLS connection (formerly known as SSL) between you and your destination, such as when using secure HTTPS to your destination site. You can simply wrap this connector in -ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector) or the -low-level [`SecureConnector`](https://github.com/reactphp/socket#secureconnector): +ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector): ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); @@ -228,17 +226,14 @@ In order to send HTTP requests, you first have to add a dependency for This allows you to send both plain HTTP and TLS-encrypted HTTPS requests like this: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); -$browser = new React\Http\Browser($loop, $connector); +$browser = new React\Http\Browser(null, $connector); $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string) $response->getBody()); @@ -261,19 +256,14 @@ 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 ReactPHP'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 -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false, 'timeout' => 3.0 @@ -315,12 +305,9 @@ Given that remote DNS resolution is assumed to be the preferred mode, all other examples explicitly disable DNS resolution like this: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); @@ -329,13 +316,10 @@ $connector = new React\Socket\Connector($loop, array( If you want to explicitly use *local DNS resolution*, you can use the following code: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - '127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); // set up Connector which uses Google's public DNS (8.8.8.8) -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => '8.8.8.8' )); @@ -350,10 +334,7 @@ If your HTTP proxy server requires authentication, you may pass the username and password as part of the HTTP proxy URL like this: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - 'http://user:pass@127.0.0.1:8080', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('user:pass@127.0.0.1:8080'); ``` Note that both the username and password must be percent-encoded if they contain @@ -362,11 +343,9 @@ special characters: ```php $user = 'he:llo'; $pass = 'p@ss'; +$url = rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080'; -$proxy = new Clue\React\HttpProxy\ProxyConnector( - rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080', - $connector -); +$proxy = new Clue\React\HttpProxy\ProxyConnector($url); ``` > The authentication details will be used for basic authentication and will be @@ -389,7 +368,7 @@ you may simply pass an assoc array of additional request headers like this: ```php $proxy = new Clue\React\HttpProxy\ProxyConnector( '127.0.0.1:8080', - $connector, + null, array( 'Proxy-Authorization' => 'Bearer abc123', 'User-Agent' => 'ReactPHP' @@ -405,16 +384,10 @@ setup, because you can still establish a TLS connection between you and the destination host as above. If you want to connect to a (rather rare) HTTPS proxy, you may want use the -`https://` scheme (HTTPS default port 443) and use ReactPHP's -[`Connector`](https://github.com/reactphp/socket#connector) or the low-level -[`SecureConnector`](https://github.com/reactphp/socket#secureconnector) -instance to create a secure connection to the proxy: +`https://` scheme (HTTPS default port 443) to create a secure connection to the proxy: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - 'https://127.0.0.1:443', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('https://127.0.0.1:443'); $proxy->connect('tcp://smtp.googlemail.com:587'); ``` @@ -431,10 +404,7 @@ having to rely on explicit [authentication](#authentication). You can simply use the `http+unix://` URI scheme like this: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - 'http+unix:///tmp/proxy.sock', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('http+unix:///tmp/proxy.sock'); $proxy->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { // connected… @@ -445,10 +415,7 @@ Similarly, you can also combine this with [authentication](#authentication) like this: ```php -$proxy = new Clue\React\HttpProxy\ProxyConnector( - 'http+unix://user:pass@/tmp/proxy.sock', - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector('http+unix://user:pass@/tmp/proxy.sock'); ``` > Note that Unix domain sockets (UDS) are considered advanced usage and PHP only diff --git a/composer.json b/composer.json index e34a14c..6e427a9 100644 --- a/composer.json +++ b/composer.json @@ -19,13 +19,13 @@ "require": { "php": ">=5.3", "react/promise": " ^2.1 || ^1.2.1", - "react/socket": "^1.1", + "react/socket": "^1.8", "ringcentral/psr7": "^1.2" }, "require-dev": { "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8", - "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3", - "react/http": "^1.0", + "react/event-loop": "^1.2", + "react/http": "^1.4", "clue/block-react": "^1.1" } } diff --git a/examples/01-http-request.php b/examples/01-http-request.php index 4b192ea..a6bc6c0 100644 --- a/examples/01-http-request.php +++ b/examples/01-http-request.php @@ -21,23 +21,17 @@ $url = 'localhost:8080'; } -$loop = React\EventLoop\Factory::create(); -$proxy = new Clue\React\HttpProxy\ProxyConnector( - $url, - new React\Socket\Connector($loop) -); +$proxy = new Clue\React\HttpProxy\ProxyConnector($url); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'dns' => false )); -$browser = new React\Http\Browser($loop, $connector); +$browser = new React\Http\Browser(null, $connector); $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string) $response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/02-optional-proxy-http-request.php b/examples/02-optional-proxy-http-request.php index 1c8005d..d018cea 100644 --- a/examples/02-optional-proxy-http-request.php +++ b/examples/02-optional-proxy-http-request.php @@ -15,28 +15,22 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); - $connector = null; $url = getenv('http_proxy'); if ($url !== false) { - $proxy = new Clue\React\HttpProxy\ProxyConnector( - $url, - new React\Socket\Connector($loop) - ); - $connector = new React\Socket\Connector($loop, array( + $proxy = new Clue\React\HttpProxy\ProxyConnector($url); + + $connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false )); } -$browser = new React\Http\Browser($loop, $connector); +$browser = new React\Http\Browser(null, $connector); $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string) $response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/11-proxy-raw-https-protocol.php b/examples/11-proxy-raw-https-protocol.php index d3c1f2b..1c98cc2 100644 --- a/examples/11-proxy-raw-https-protocol.php +++ b/examples/11-proxy-raw-https-protocol.php @@ -24,14 +24,9 @@ $url = 'localhost:8080'; } -$loop = React\EventLoop\Factory::create(); +$proxy = new Clue\React\HttpProxy\ProxyConnector($url); -$proxy = new Clue\React\HttpProxy\ProxyConnector( - $url, - new React\Socket\Connector($loop) -); - -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false @@ -45,5 +40,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/12-optional-proxy-raw-https-protocol.php b/examples/12-optional-proxy-raw-https-protocol.php index 9d3e919..873f963 100644 --- a/examples/12-optional-proxy-raw-https-protocol.php +++ b/examples/12-optional-proxy-raw-https-protocol.php @@ -22,21 +22,17 @@ require __DIR__ . '/../vendor/autoload.php'; -$loop = React\EventLoop\Factory::create(); - -$connector = new React\Socket\Connector($loop); - $url = getenv('http_proxy'); if ($url !== false) { - $proxy = new Clue\React\HttpProxy\ProxyConnector( - $url, - $connector - ); - $connector = new React\Socket\Connector($loop, array( + $proxy = new Clue\React\HttpProxy\ProxyConnector($url); + + $connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false )); +} else { + $connector = new React\Socket\Connector(); } $connector->connect('tls://google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { @@ -47,5 +43,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/13-custom-proxy-headers.php b/examples/13-custom-proxy-headers.php index 2612ee1..5314765 100644 --- a/examples/13-custom-proxy-headers.php +++ b/examples/13-custom-proxy-headers.php @@ -24,18 +24,16 @@ $url = 'localhost:8080'; } -$loop = React\EventLoop\Factory::create(); - $proxy = new Clue\React\HttpProxy\ProxyConnector( $url, - new React\Socket\Connector($loop), + null, array( 'X-Custom-Header-1' => 'Value-1', 'X-Custom-Header-2' => 'Value-2', ) ); -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false, @@ -49,5 +47,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/21-proxy-raw-smtp-protocol.php b/examples/21-proxy-raw-smtp-protocol.php index f66d253..4feeea9 100644 --- a/examples/21-proxy-raw-smtp-protocol.php +++ b/examples/21-proxy-raw-smtp-protocol.php @@ -23,14 +23,9 @@ $url = 'localhost:8080'; } -$loop = React\EventLoop\Factory::create(); +$proxy = new Clue\React\HttpProxy\ProxyConnector($url); -$proxy = new Clue\React\HttpProxy\ProxyConnector( - $url, - new React\Socket\Connector($loop) -); - -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false @@ -45,5 +40,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/examples/22-proxy-raw-smtps-protocol.php b/examples/22-proxy-raw-smtps-protocol.php index 796f59b..296390a 100644 --- a/examples/22-proxy-raw-smtps-protocol.php +++ b/examples/22-proxy-raw-smtps-protocol.php @@ -26,14 +26,9 @@ $url = 'localhost:8080'; } -$loop = React\EventLoop\Factory::create(); +$proxy = new Clue\React\HttpProxy\ProxyConnector($url); -$proxy = new Clue\React\HttpProxy\ProxyConnector( - $url, - new React\Socket\Connector($loop) -); - -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, array( 'tcp' => $proxy, 'timeout' => 3.0, 'dns' => false @@ -48,5 +43,3 @@ }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); - -$loop->run(); diff --git a/src/ProxyConnector.php b/src/ProxyConnector.php index 020e12a..0bca17e 100644 --- a/src/ProxyConnector.php +++ b/src/ProxyConnector.php @@ -9,8 +9,10 @@ use React\Promise; use React\Promise\Deferred; use React\Socket\ConnectionInterface; +use React\Socket\Connector; use React\Socket\ConnectorInterface; use React\Socket\FixedUriConnector; +use React\Socket\UnixConnector; /** * A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination @@ -51,13 +53,11 @@ class ProxyConnector implements ConnectorInterface * @param string $proxyUrl 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. - * @param ConnectorInterface $connector In its most simple form, the given - * connector will be a \React\Socket\Connector if you want to connect to - * a given IP address. + * @param ?ConnectorInterface $connector (Optional) Connector to use. * @param array $httpHeaders Custom HTTP headers to be sent to the proxy. * @throws InvalidArgumentException if the proxy URL is invalid */ - public function __construct($proxyUrl, ConnectorInterface $connector, array $httpHeaders = array()) + public function __construct($proxyUrl, ConnectorInterface $connector = null, array $httpHeaders = array()) { // support `http+unix://` scheme for Unix domain socket (UDS) paths if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) { @@ -67,7 +67,7 @@ public function __construct($proxyUrl, ConnectorInterface $connector, array $htt // connector uses Unix transport scheme and explicit path given $connector = new FixedUriConnector( 'unix://' . $match[2], - $connector + $connector ?: new UnixConnector() ); } @@ -86,7 +86,7 @@ public function __construct($proxyUrl, ConnectorInterface $connector, array $htt } $parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp'; - $this->connector = $connector; + $this->connector = $connector ?: new Connector(); $this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port']; // prepare Proxy-Authorization header if URI contains username/password diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index 321ace8..bbf60ba 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -2,19 +2,16 @@ namespace Clue\Tests\React\HttpProxy; -use React\EventLoop\Factory; -use Clue\React\HttpProxy\ProxyConnector; -use React\Socket\TcpConnector; -use React\Socket\DnsConnector; use Clue\React\Block; -use React\Socket\SecureConnector; +use Clue\React\HttpProxy\ProxyConnector; +use React\EventLoop\Factory; +use React\Socket\Connector; /** @group internet */ class FunctionalTest extends AbstractTestCase { private $loop; - private $tcpConnector; - private $dnsConnector; + private $connector; /** * @before @@ -22,18 +19,12 @@ class FunctionalTest extends AbstractTestCase public function setUpConnector() { $this->loop = Factory::create(); - - $this->tcpConnector = new TcpConnector($this->loop); - - $f = new \React\Dns\Resolver\Factory(); - $resolver = $f->create('8.8.8.8', $this->loop); - - $this->dnsConnector = new DnsConnector($this->tcpConnector, $resolver); + $this->connector = new Connector($this->loop); } public function testNonListeningSocketRejectsConnection() { - $proxy = new ProxyConnector('127.0.0.1:9999', $this->dnsConnector); + $proxy = new ProxyConnector('127.0.0.1:9999', $this->connector); $promise = $proxy->connect('google.com:80'); @@ -47,7 +38,7 @@ public function testNonListeningSocketRejectsConnection() public function testPlainGoogleDoesNotAcceptConnectMethod() { - $proxy = new ProxyConnector('google.com', $this->dnsConnector); + $proxy = new ProxyConnector('google.com', $this->connector); $promise = $proxy->connect('google.com:80'); @@ -65,8 +56,7 @@ public function testSecureGoogleDoesNotAcceptConnectMethod() $this->markTestSkipped('TLS not supported on legacy HHVM'); } - $secure = new SecureConnector($this->dnsConnector, $this->loop); - $proxy = new ProxyConnector('https://google.com:443', $secure); + $proxy = new ProxyConnector('https://google.com:443', $this->connector); $promise = $proxy->connect('google.com:80'); @@ -80,7 +70,7 @@ public function testSecureGoogleDoesNotAcceptConnectMethod() public function testSecureGoogleDoesNotAcceptPlainStream() { - $proxy = new ProxyConnector('google.com:443', $this->dnsConnector); + $proxy = new ProxyConnector('google.com:443'); $promise = $proxy->connect('google.com:80'); @@ -97,7 +87,7 @@ public function testSecureGoogleDoesNotAcceptPlainStream() */ public function testCancelWhileConnectingShouldNotCreateGarbageCycles() { - $proxy = new ProxyConnector('google.com', $this->dnsConnector); + $proxy = new ProxyConnector('google.com', $this->connector); gc_collect_cycles(); gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on diff --git a/tests/ProxyConnectorTest.php b/tests/ProxyConnectorTest.php index 654699a..a92a5c2 100644 --- a/tests/ProxyConnectorTest.php +++ b/tests/ProxyConnectorTest.php @@ -19,6 +19,17 @@ public function setUpMock() $this->connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); } + public function testConstructWithoutConnectorAssignsConnectorAutomatically() + { + $proxy = new ProxyConnector('proxy.example.com'); + + $ref = new \ReflectionProperty($proxy, 'connector'); + $ref->setAccessible(true); + $connector = $ref->getValue($proxy); + + $this->assertInstanceOf('React\Socket\ConnectorInterface', $connector); + } + public function testInvalidProxy() { $this->setExpectedException('InvalidArgumentException');