8000 Avoid unneeded promise wrapping & avoid garbage references on legacy PHP · reactphp/socket@5b51fb9 · GitHub
[go: up one dir, main page]

Skip to content {"props":{"docsUrl":"https://docs.github.com/get-started/accessibility/keyboard-shortcuts"}}

Commit 5b51fb9

Browse files
committed
Avoid unneeded promise wrapping & avoid garbage references on legacy PHP
1 parent cc4737d commit 5b51fb9

File tree

4 files changed

+100
-70
lines changed

4 files changed

+100
-70
lines changed

README.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,22 @@ also shares all of their features and implementation details.
927927
If you want to typehint in your higher-level protocol implementation, you SHOULD
928928
use the generic [`ConnectorInterface`](#connectorinterface) instead.
929929

930+
As of `v1.4.0`, the `Connector` class defaults to using the
931+
[happy eyeballs algorithm](https://en.wikipedia.org/wiki/Happy_Eyeballs) to
932+
automatically connect over IPv4 or IPv6 when a hostname is given.
933+
This automatically attempts to connect using both IPv4 and IPv6 at the same time
934+
(preferring IPv6), thus avoiding the usual problems faced by users with imperfect
935+
IPv6 connections or setups.
936+
If you want to revert to the old behavior of only doing an IPv4 lookup and
937+
only attempt a single IPv4 connection, you can set up the `Connector` like this:
938+
939+
```php
940+
$connector = new React\Socket\Connector($loop, array(
941+
'happy_eyeballs' => false
942+
));
943+
```
944+
945+
Similarly, you can also affect the default DNS behavior as follows.
930946
The `Connector` class will try to detect your system DNS settings (and uses
931947
Google's public DNS server `8.8.8.8` as a fallback if unable to determine your
932948
system settings) to resolve all public hostnames into underlying IP addresses by
@@ -977,7 +993,7 @@ $connector->connect('localhost:80')->then(function (React\Socket\ConnectionInter
977993
```
978994

979995
By default, the `tcp://` and `tls://` URI schemes will use timeout value that
980-
repects your `default_socket_timeout` ini setting (which defaults to 60s).
996+
respects your `default_socket_timeout` ini setting (which defaults to 60s).
981997
If you want a custom timeout value, you can simply pass this like this:
982998

983999
```php
@@ -1094,17 +1110,6 @@ $connector->connect('google.com:80')->then(function (React\Socket\ConnectionInte
10941110
Internally, the `tcp://` and `tls://` connectors will always be wrapped by
10951111
`TimeoutConnector`, unless you disable timeouts like in the above example.
10961112

1097-
> Internally the `HappyEyeBallsConnector` has replaced the `DnsConnector` as default
1098-
resolving connector. It is still available as `Connector` has a new option, namely
1099-
`happy_eyeballs`, to control which of the two will be used. By default it's `true`
1100-
and will use `HappyEyeBallsConnector`, when set to `false` `DnsConnector` is used.
1101-
We only recommend doing so when there are any backwards compatible issues on older
1102-
systems only supporting IPv4. The `HappyEyeBallsConnector` implements most of
1103-
RFC6555 and RFC8305 and will use concurrency to connect to the remote host by
1104-
attempting to connect over both IPv4 and IPv6 with a priority for IPv6 when
1105-
available. Which ever connection attempt succeeds first will be used, the rest
1106-
connection attempts will be canceled.
1107-
11081113
### Advanced client usage
11091114

11101115
#### TcpConnector

src/Connector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function __construct(LoopInterface $loop, array $options = array())
3636

3737
'dns' => true,
3838
'timeout' => true,
39-
'happy_eyeballs' => \PHP_VERSION_ID < 70000 ? false : true,
39+
'happy_eyeballs' => true,
4040
);
4141

4242
if ($options['timeout'] === true) {

src/HappyEyeBallsConnectionBuilder.php

Lines changed: 35 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -190,71 +190,49 @@ public function check($resolve, $reject)
190190
*/
191191
public function attemptConnection($ip)
192192
{
193-
$promise = null;
194-
$that = $this;
195-
196-
return new Promise\Promise(
197-
function ($resolve, $reject) use (&$promise, $that, $ip) {
198-
$uri = '';
193+
$uri = '';
199194

200-
// prepend original scheme if known
201-
if (isset($that->parts['scheme'])) {
202-
$uri .= $that->parts['scheme'] . '://';
203-
}
195+
// prepend original scheme if known
196+
if (isset($this->parts['scheme'])) {
197+
$uri .= $this->parts['scheme'] . '://';
198+
}
204199

205-
if (\strpos($ip, ':') !== false) {
206-
// enclose IPv6 addresses in square brackets before appending port
207-
$uri .= '[' . $ip . ']';
208-
} else {
209-
$uri .= $ip;
210-
}
200+
if (\strpos($ip, ':') !== false) {
201+
// enclose IPv6 addresses in square brackets before appending port
202+
$uri .= '[' . $ip . ']';
203+
} else {
204+
$uri .= $ip;
205+
}
211206

212-
// append original port if known
213-
if (isset($that->parts['port'])) {
214-
$uri .= ':' . $that->parts['port'];
215-
}
207+
// append original port if known
208+
if (isset($this->parts['port'])) {
209+
$uri .= ':' . $this->parts['port'];
210+
}
216211

217-
// append orignal path if known
218-
if (isset($that->parts['path'])) {
219-
$uri .= $that->parts['path'];
220-
}
212+
// append orignal path if known
213+
if (isset($this->parts['path'])) {
214+
$uri .= $this->parts['path'];
215+
}
221216

222-
// append original query if known
223-
if (isset($that->parts['query'])) {
224-
$uri .= '?' . $that->parts['query'];
225-
}
217+
// append original query if known
218+
if (isset($this->parts['query'])) {
219+
$uri .= '?' . $this->parts['query'];
220+
}
226221

227-
// append original hostname as query if resolved via DNS and if
228-
// destination URI does not contain "hostname" query param already
229-
$args = array();
230-
\parse_str(isset($that->parts['query']) ? $that->parts['query'] : '', $args);
231-
if ($that->host !== $ip && !isset($args['hostname'])) {
232-
$uri .= (isset($that->parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($that->host);
233-
}
222+
// append original hostname as query if resolved via DNS and if
223+
// destination URI does not contain "hostname" query param already
224+
$args = array();
225+
\parse_str(isset($this->parts['query']) ? $this->parts['query'] : '', $args);
226+
if ($this->host !== $ip && !isset($args['hostname'])) {
227+
$uri .= (isset($this->parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($this->host);
228+
}
234229

235-
// append original fragment if known
236-
if (isset($that->parts['fragment'])) {
237-
$uri .= '#' . $that->parts['fragment'];
238-
}
230+
// append original fragment if known
231+
if (isset($this->parts['fragment'])) {
232+
$uri .= '#' . $this->parts['fragment'];
233+
}
239234

240-
$promise = $that->connector->connect($uri);
241-
$promise->then($resolve, $reject);
242-
},
243-
function ($_, $reject) use (&$promise, $that) {
244-
// cancellation should reject connection attempt
245-
// (try to) cancel pending connection attempt
246-
$reject(new \RuntimeException('Connection to ' . $that->uri . ' cancelled during connection attempt'));
247-
248-
if ($promise instanceof CancellablePromiseInterface) {
249-
// overwrite callback arguments for PHP7+ only, so they do not show
250-
// up in the Exception trace and do not cause a possible cyclic reference.
251-
$_ = $reject = null;
252-
253-
$promise->cancel();
254-
$promise = null;
255-
}
256-
}
257-
);
235+
return $this->connector->connect($uri);
258236
}
259237

260238
/**
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace React\Tests\Socket;
4+
5+
use React\Promise\Promise;
6+
use React\Socket\HappyEyeBallsConnectionBuilder;
7+
8+
class HappyEyeBallsConnectionBuilderTest extends TestCase
9+
{
10+
public function testAttemptConnectionWillConnectViaConnectorToGivenIpWithPortAndHostnameFromUriParts()
11+
{
12+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
13+
14+
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
15+
$connector->expects($this->once())->method('connect')->with('tcp://10.1.1.1:80?hostname=reactphp.org')->willReturn(new Promise(function () { }));
16+
17+
$resolver = $this->getMockBuilder('React\Dns\Resolver\ResolverInterface')->getMock();
18+
$resolver->expects($this->never())->method('resolveAll');
19+
20+
$uri = 'tcp://reactphp.org:80';
21+
$host = 'reactphp.org';
22+
$parts = parse_url($uri);
23+
24+
$builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts);
25+
26+
$builder->attemptConnection('10.1.1.1');
27+
}
28+
29+
public function testAttemptConnectionWillConnectViaConnectorToGivenIpv6WithAllUriParts()
30+
{
31+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
32+
33+
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
34+
$connector->expects($this->once())->method('connect')->with('tcp://[::1]:80/path?test=yes&hostname=reactphp.org#start')->willReturn(new Promise(function () { }));
35+
36+
$resolver = $this->getMockBuilder('React\Dns\Resolver\ResolverInterface')->getMock();
37+
$resolver->expects($this->never())->method('resolveAll');
38+
39+
$uri = 'tcp://reactphp.org:80/path?test=yes#start';
40+
$host = 'reactphp.org';
41+
$parts = parse_url($uri);
42+
43+
$builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts);
44+
45+
$builder->attemptConnection('::1');
46+
}
47+
}

0 commit comments

Comments
 (0)
0