+
This commit is contained in:
785
vendor/react/socket/CHANGELOG.md
vendored
Normal file
785
vendor/react/socket/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,785 @@
|
||||
# Changelog
|
||||
|
||||
## 1.16.0 (2024-07-26)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
||||
(#318 by @clue)
|
||||
|
||||
## 1.15.0 (2023-12-15)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#310 by @clue)
|
||||
|
||||
* Fix: Fix cancelling during the 50ms resolution delay when DNS is still pending.
|
||||
(#311 by @clue)
|
||||
|
||||
## 1.14.0 (2023-08-25)
|
||||
|
||||
* Feature: Improve Promise v3 support and use template types.
|
||||
(#307 and #309 by @clue)
|
||||
|
||||
* Improve test suite and update to collect all garbage cycles.
|
||||
(#308 by @clue)
|
||||
|
||||
## 1.13.0 (2023-06-07)
|
||||
|
||||
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
|
||||
(#305 by @clue)
|
||||
|
||||
* Feature: Improve errno detection for failed connections without `ext-sockets`.
|
||||
(#304 by @clue)
|
||||
|
||||
* Improve test suite, clean up leftover `.sock` files and report failed assertions.
|
||||
(#299, #300, #301 and #306 by @clue)
|
||||
|
||||
## 1.12.0 (2022-08-25)
|
||||
|
||||
* Feature: Forward compatibility with react/promise 3.
|
||||
(#214 by @WyriHaximus and @clue)
|
||||
|
||||
* Feature: Full support for PHP 8.2 release.
|
||||
(#298 by @WyriHaximus)
|
||||
|
||||
* Feature: Avoid unneeded syscall on socket close.
|
||||
(#292 by @clue)
|
||||
|
||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
||||
(#290 by @clue)
|
||||
|
||||
* Fix: Fix invalid references in exception stack trace.
|
||||
(#284 by @clue)
|
||||
|
||||
* Minor documentation improvements, update to use new reactphp/async package instead of clue/reactphp-block.
|
||||
(#296 by @clue, #285 by @SimonFrings and #295 by @nhedger)
|
||||
|
||||
* Improve test suite, update macOS and HHVM environment, fix optional tests for `ENETUNREACH`.
|
||||
(#288, #289 and #297 by @clue)
|
||||
|
||||
## 1.11.0 (2022-01-14)
|
||||
|
||||
* Feature: Full support for PHP 8.1 release.
|
||||
(#277 by @clue)
|
||||
|
||||
* Feature: Avoid dependency on `ext-filter`.
|
||||
(#279 by @clue)
|
||||
|
||||
* Improve test suite to skip FD test when hitting memory limit
|
||||
and skip legacy TLS 1.0 tests if disabled by system.
|
||||
(#278 and #281 by @clue and #283 by @SimonFrings)
|
||||
|
||||
## 1.10.0 (2021-11-29)
|
||||
|
||||
* Feature: Support listening on existing file descriptors (FDs) with `SocketServer`.
|
||||
(#269 by @clue)
|
||||
|
||||
```php
|
||||
$socket = new React\Socket\SocketSever('php://fd/3');
|
||||
```
|
||||
|
||||
This is particularly useful when using [systemd socket activation](https://www.freedesktop.org/software/systemd/man/systemd.socket.html) like this:
|
||||
|
||||
```bash
|
||||
$ systemd-socket-activate -l 8000 php examples/03-http-server.php php://fd/3
|
||||
```
|
||||
|
||||
* Feature: Improve error messages for failed connection attempts with `errno` and `errstr`.
|
||||
(#265, #266, #267, #270 and #271 by @clue and #268 by @SimonFrings)
|
||||
|
||||
All error messages now always include the appropriate `errno` and `errstr` to
|
||||
give more details about the error reason when available. Along with these
|
||||
error details exposed by the underlying system functions, it will also
|
||||
include the appropriate error constant name (such as `ECONNREFUSED`) when
|
||||
available. Accordingly, failed TCP/IP connections will now report the actual
|
||||
underlying error condition instead of a generic "Connection refused" error.
|
||||
Higher-level error messages will now consistently report the connection URI
|
||||
scheme and hostname used in all error messages.
|
||||
|
||||
For most common use cases this means that simply reporting the `Exception`
|
||||
message should give the most relevant details for any connection issues:
|
||||
|
||||
```php
|
||||
$connector = new React\Socket\Connector();
|
||||
$connector->connect($uri)->then(function (React\Socket\ConnectionInterface $conn) {
|
||||
// …
|
||||
}, function (Exception $e) {
|
||||
echo 'Error:' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite, test against PHP 8.1 release.
|
||||
(#274 by @SimonFrings)
|
||||
|
||||
## 1.9.0 (2021-08-03)
|
||||
|
||||
* Feature: Add new `SocketServer` and deprecate `Server` to avoid class name collisions.
|
||||
(#263 by @clue)
|
||||
|
||||
The new `SocketServer` class has been added with an improved constructor signature
|
||||
as a replacement for the previous `Server` class in order to avoid any ambiguities.
|
||||
The previous name has been deprecated and should not be used anymore.
|
||||
In its most basic form, the deprecated `Server` can now be considered an alias for new `SocketServer`.
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$socket = new React\Socket\Server(0);
|
||||
$socket = new React\Socket\Server('127.0.0.1:8000');
|
||||
$socket = new React\Socket\Server('127.0.0.1:8000', null, $context);
|
||||
$socket = new React\Socket\Server('127.0.0.1:8000', $loop, $context);
|
||||
|
||||
// new
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:0');
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:8000');
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:8000', $context, $loop);
|
||||
```
|
||||
|
||||
* Feature: Update `Connector` signature to take optional `$context` as first argument.
|
||||
(#264 by @clue)
|
||||
|
||||
The new signature has been added to match the new `SocketServer` and
|
||||
consistently move the now commonly unneeded loop argument to the last argument.
|
||||
The previous signature has been deprecated and should not be used anymore.
|
||||
In its most basic form, both signatures are compatible.
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$connector = new React\Socket\Connector(null, $context);
|
||||
$connector = new React\Socket\Connector($loop, $context);
|
||||
|
||||
// new
|
||||
$connector = new React\Socket\Connector($context);
|
||||
$connector = new React\Socket\Connector($context, $loop);
|
||||
```
|
||||
|
||||
## 1.8.0 (2021-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
|
||||
(#260 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
|
||||
$connector = new React\Socket\Connector($loop);
|
||||
|
||||
// new (using default loop)
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080');
|
||||
$connector = new React\Socket\Connector();
|
||||
```
|
||||
|
||||
## 1.7.0 (2021-06-25)
|
||||
|
||||
* Feature: Support falling back to multiple DNS servers from DNS config.
|
||||
(#257 by @clue)
|
||||
|
||||
If you're using the default `Connector`, it will now use all DNS servers
|
||||
configured on your system. If you have multiple DNS servers configured and
|
||||
connectivity to the primary DNS server is broken, it will now fall back to
|
||||
your other DNS servers, thus providing improved connectivity and redundancy
|
||||
for broken DNS configurations.
|
||||
|
||||
* Feature: Use round robin for happy eyeballs DNS responses (load balancing).
|
||||
(#247 by @clue)
|
||||
|
||||
If you're using the default `Connector`, it will now randomize the order of
|
||||
the IP addresses resolved via DNS when connecting. This allows the load to
|
||||
be distributed more evenly across all returned IP addresses. This can be
|
||||
used as a very basic DNS load balancing mechanism.
|
||||
|
||||
* Internal improvement to avoid unhandled rejection for future Promise API.
|
||||
(#258 by @clue)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
||||
(#254 by @SimonFrings)
|
||||
|
||||
## 1.6.0 (2020-08-28)
|
||||
|
||||
* Feature: Support upcoming PHP 8 release.
|
||||
(#246 by @clue)
|
||||
|
||||
* Feature: Change default socket backlog size to 511.
|
||||
(#242 by @clue)
|
||||
|
||||
* Fix: Fix closing connection when cancelling during TLS handshake.
|
||||
(#241 by @clue)
|
||||
|
||||
* Fix: Fix blocking during possible `accept()` race condition
|
||||
when multiple socket servers listen on same socket address.
|
||||
(#244 by @clue)
|
||||
|
||||
* Improve test suite, update PHPUnit config and add full core team to the license.
|
||||
(#243 by @SimonFrings and #245 by @WyriHaximus)
|
||||
|
||||
## 1.5.0 (2020-07-01)
|
||||
|
||||
* Feature / Fix: Improve error handling and reporting for happy eyeballs and
|
||||
immediately try next connection when one connection attempt fails.
|
||||
(#230, #231, #232 and #233 by @clue)
|
||||
|
||||
Error messages for failed connection attempts now include more details to
|
||||
ease debugging. Additionally, the happy eyeballs algorithm has been improved
|
||||
to avoid having to wait for some timers to expire which significantly
|
||||
improves connection setup times (in particular when IPv6 isn't available).
|
||||
|
||||
* Improve test suite, minor code cleanup and improve code coverage to 100%.
|
||||
Update to PHPUnit 9 and skip legacy TLS 1.0 / TLS 1.1 tests if disabled by
|
||||
system. Run tests on Windows and simplify Travis CI test matrix for Mac OS X
|
||||
setup and skip all TLS tests on legacy HHVM.
|
||||
(#229, #235, #236 and #238 by @clue and #239 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2020-03-12)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2020/introducing-ipv6-for-reactphp).
|
||||
|
||||
* Feature: Add IPv6 support to `Connector` (implement "Happy Eyeballs" algorithm to support IPv6 probing).
|
||||
IPv6 support is turned on by default, use new `happy_eyeballs` option in `Connector` to toggle behavior.
|
||||
(#196, #224 and #225 by @WyriHaximus and @clue)
|
||||
|
||||
* Feature: Default to using DNS cache (with max 256 entries) for `Connector`.
|
||||
(#226 by @clue)
|
||||
|
||||
* Add `.gitattributes` to exclude dev files from exports and some minor code style fixes.
|
||||
(#219 by @reedy and #218 by @mmoreram)
|
||||
|
||||
* Improve test suite to fix failing test cases when using new DNS component,
|
||||
significantly improve test performance by awaiting events instead of sleeping,
|
||||
exclude TLS 1.3 test on PHP 7.3, run tests on PHP 7.4 and simplify test matrix.
|
||||
(#208, #209, #210, #217 and #223 by @clue)
|
||||
|
||||
## 1.3.0 (2019-07-10)
|
||||
|
||||
* Feature: Forward compatibility with upcoming stable DNS component.
|
||||
(#206 by @clue)
|
||||
|
||||
## 1.2.1 (2019-06-03)
|
||||
|
||||
* Avoid uneeded fragmented TLS work around for PHP 7.3.3+ and
|
||||
work around failing test case detecting EOF on TLS 1.3 socket streams.
|
||||
(#201 and #202 by @clue)
|
||||
|
||||
* Improve TLS certificate/passphrase example.
|
||||
(#190 by @jsor)
|
||||
|
||||
## 1.2.0 (2019-01-07)
|
||||
|
||||
* Feature / Fix: Improve TLS 1.3 support.
|
||||
(#186 by @clue)
|
||||
|
||||
TLS 1.3 is now an official standard as of August 2018! :tada:
|
||||
The protocol has major improvements in the areas of security, performance, and privacy.
|
||||
TLS 1.3 is supported by default as of [OpenSSL 1.1.1](https://www.openssl.org/blog/blog/2018/09/11/release111/).
|
||||
For example, this version ships with Ubuntu 18.10 (and newer) by default, meaning that recent installations support TLS 1.3 out of the box :shipit:
|
||||
|
||||
* Fix: Avoid possibility of missing remote address when TLS handshake fails.
|
||||
(#188 by @clue)
|
||||
|
||||
* Improve performance by prefixing all global functions calls with `\` to skip the look up and resolve process and go straight to the global function.
|
||||
(#183 by @WyriHaximus)
|
||||
|
||||
* Update documentation to use full class names with namespaces.
|
||||
(#187 by @clue)
|
||||
|
||||
* Improve test suite to avoid some possible race conditions,
|
||||
test against PHP 7.3 on Travis and
|
||||
use dedicated `assertInstanceOf()` assertions.
|
||||
(#185 by @clue, #178 by @WyriHaximus and #181 by @carusogabriel)
|
||||
|
||||
## 1.1.0 (2018-10-01)
|
||||
|
||||
* Feature: Improve error reporting for failed connection attempts and improve
|
||||
cancellation forwarding during DNS lookup, TCP/IP connection or TLS handshake.
|
||||
(#168, #169, #170, #171, #176 and #177 by @clue)
|
||||
|
||||
All error messages now always contain a reference to the remote URI to give
|
||||
more details which connection actually failed and the reason for this error.
|
||||
Accordingly, failures during DNS lookup will now mention both the remote URI
|
||||
as well as the DNS error reason. TCP/IP connection issues and errors during
|
||||
a secure TLS handshake will both mention the remote URI as well as the
|
||||
underlying socket error. Similarly, lost/dropped connections during a TLS
|
||||
handshake will now report a lost connection instead of an empty error reason.
|
||||
|
||||
For most common use cases this means that simply reporting the `Exception`
|
||||
message should give the most relevant details for any connection issues:
|
||||
|
||||
```php
|
||||
$promise = $connector->connect('tls://example.com:443');
|
||||
$promise->then(function (ConnectionInterface $conn) use ($loop) {
|
||||
// …
|
||||
}, function (Exception $e) {
|
||||
echo $e->getMessage();
|
||||
});
|
||||
```
|
||||
|
||||
## 1.0.0 (2018-07-11)
|
||||
|
||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
||||
We'd like to emphasize that this component is production ready and battle-tested.
|
||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
||||
so you have a rock-solid foundation to build on top of.
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.8.12 release.
|
||||
|
||||
## 0.8.12 (2018-06-11)
|
||||
|
||||
* Feature: Improve memory consumption for failed and cancelled connection attempts.
|
||||
(#161 by @clue)
|
||||
|
||||
* Improve test suite to fix Travis config to test against legacy PHP 5.3 again.
|
||||
(#162 by @clue)
|
||||
|
||||
## 0.8.11 (2018-04-24)
|
||||
|
||||
* Feature: Improve memory consumption for cancelled connection attempts and
|
||||
simplify skipping DNS lookup when connecting to IP addresses.
|
||||
(#159 and #160 by @clue)
|
||||
|
||||
## 0.8.10 (2018-02-28)
|
||||
|
||||
* Feature: Update DNS dependency to support loading system default DNS
|
||||
nameserver config on all supported platforms
|
||||
(`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows)
|
||||
(#152 by @clue)
|
||||
|
||||
This means that connecting to hosts that are managed by a local DNS server,
|
||||
such as a corporate DNS server or when using Docker containers, will now
|
||||
work as expected across all platforms with no changes required:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$connector->connect('intranet.example:80')->then(function ($connection) {
|
||||
// …
|
||||
});
|
||||
```
|
||||
|
||||
## 0.8.9 (2018-01-18)
|
||||
|
||||
* Feature: Support explicitly choosing TLS version to negotiate with remote side
|
||||
by respecting `crypto_method` context parameter for all classes.
|
||||
(#149 by @clue)
|
||||
|
||||
By default, all connector and server classes support TLSv1.0+ and exclude
|
||||
support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly
|
||||
choose the TLS version you want to negotiate with the remote side:
|
||||
|
||||
```php
|
||||
// new: now supports 'crypto_method` context parameter for all classes
|
||||
$connector = new Connector($loop, array(
|
||||
'tls' => array(
|
||||
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
|
||||
)
|
||||
));
|
||||
```
|
||||
|
||||
* Minor internal clean up to unify class imports
|
||||
(#148 by @clue)
|
||||
|
||||
## 0.8.8 (2018-01-06)
|
||||
|
||||
* Improve test suite by adding test group to skip integration tests relying on
|
||||
internet connection and fix minor documentation typo.
|
||||
(#146 by @clue and #145 by @cn007b)
|
||||
|
||||
## 0.8.7 (2017-12-24)
|
||||
|
||||
* Fix: Fix closing socket resource before removing from loop
|
||||
(#141 by @clue)
|
||||
|
||||
This fixes the root cause of an uncaught `Exception` that only manifested
|
||||
itself after the recent Stream v0.7.4 component update and only if you're
|
||||
using `ext-event` (`ExtEventLoop`).
|
||||
|
||||
* Improve test suite by testing against PHP 7.2
|
||||
(#140 by @carusogabriel)
|
||||
|
||||
## 0.8.6 (2017-11-18)
|
||||
|
||||
* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme
|
||||
and add advanced `UnixServer` class.
|
||||
(#120 by @andig)
|
||||
|
||||
```php
|
||||
// new: Server now supports "unix://" scheme
|
||||
$server = new Server('unix:///tmp/server.sock', $loop);
|
||||
|
||||
// new: advanced usage
|
||||
$server = new UnixServer('/tmp/server.sock', $loop);
|
||||
```
|
||||
|
||||
* Restructure examples to ease getting started
|
||||
(#136 by @clue)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6 and
|
||||
ignore Mac OS X test failures for now until Travis tests work again
|
||||
(#133 by @gabriel-caruso and #134 by @clue)
|
||||
|
||||
## 0.8.5 (2017-10-23)
|
||||
|
||||
* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X
|
||||
(#123 by @andig)
|
||||
|
||||
* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed
|
||||
(#129 by @clue)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit v5 and
|
||||
forward compatibility with upcoming EventLoop releases in tests and
|
||||
test Mac OS X on Travis
|
||||
(#122 by @andig and #125, #127 and #130 by @clue)
|
||||
|
||||
* Readme improvements
|
||||
(#118 by @jsor)
|
||||
|
||||
## 0.8.4 (2017-09-16)
|
||||
|
||||
* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead
|
||||
(#117 by @clue)
|
||||
|
||||
This can be useful for consumers that do not support certain URIs, such as
|
||||
when you want to explicitly connect to a Unix domain socket (UDS) path
|
||||
instead of connecting to a default address assumed by an higher-level API:
|
||||
|
||||
```php
|
||||
$connector = new FixedUriConnector(
|
||||
'unix:///var/run/docker.sock',
|
||||
new UnixConnector($loop)
|
||||
);
|
||||
|
||||
// destination will be ignored, actually connects to Unix domain socket
|
||||
$promise = $connector->connect('localhost:80');
|
||||
```
|
||||
|
||||
## 0.8.3 (2017-09-08)
|
||||
|
||||
* Feature: Reduce memory consumption for failed connections
|
||||
(#113 by @valga)
|
||||
|
||||
* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14
|
||||
(#114 by @clue)
|
||||
|
||||
## 0.8.2 (2017-08-25)
|
||||
|
||||
* Feature: Update DNS dependency to support hosts file on all platforms
|
||||
(#112 by @clue)
|
||||
|
||||
This means that connecting to hosts such as `localhost` will now work as
|
||||
expected across all platforms with no changes required:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$connector->connect('localhost:8080')->then(function ($connection) {
|
||||
// …
|
||||
});
|
||||
```
|
||||
|
||||
## 0.8.1 (2017-08-15)
|
||||
|
||||
* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and
|
||||
target evenement 3.0 a long side 2.0 and 1.0
|
||||
(#104 by @clue and #111 by @WyriHaximus)
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build and
|
||||
fix HHVM build for now again and ignore future HHVM build errors
|
||||
(#109 and #110 by @clue)
|
||||
|
||||
* Minor documentation fixes
|
||||
(#103 by @christiaan and #108 by @hansott)
|
||||
|
||||
## 0.8.0 (2017-05-09)
|
||||
|
||||
* Feature: New `Server` class now acts as a facade for existing server classes
|
||||
and renamed old `Server` to `TcpServer` for advanced usage.
|
||||
(#96 and #97 by @clue)
|
||||
|
||||
The `Server` class is now the main class in this package that implements the
|
||||
`ServerInterface` and allows you to accept incoming streaming connections,
|
||||
such as plaintext TCP/IP or secure TLS connection streams.
|
||||
|
||||
> This is not a BC break and consumer code does not have to be updated.
|
||||
|
||||
* Feature / BC break: All addresses are now URIs that include the URI scheme
|
||||
(#98 by @clue)
|
||||
|
||||
```diff
|
||||
- $parts = parse_url('tcp://' . $conn->getRemoteAddress());
|
||||
+ $parts = parse_url($conn->getRemoteAddress());
|
||||
```
|
||||
|
||||
* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths
|
||||
(#100 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with Stream v1.0 and v0.7
|
||||
(#99 by @clue)
|
||||
|
||||
## 0.7.2 (2017-04-24)
|
||||
|
||||
* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs
|
||||
(#94 by @clue)
|
||||
|
||||
## 0.7.1 (2017-04-10)
|
||||
|
||||
* Fix: Ignore HHVM errors when closing connection that is already closing
|
||||
(#91 by @clue)
|
||||
|
||||
## 0.7.0 (2017-04-10)
|
||||
|
||||
* Feature: Merge SocketClient component into this component
|
||||
(#87 by @clue)
|
||||
|
||||
This means that this package now provides async, streaming plaintext TCP/IP
|
||||
and secure TLS socket server and client connections for ReactPHP.
|
||||
|
||||
```
|
||||
$connector = new React\Socket\Connector($loop);
|
||||
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$connection->write('…');
|
||||
});
|
||||
```
|
||||
|
||||
Accordingly, the `ConnectionInterface` is now used to represent both incoming
|
||||
server side connections as well as outgoing client side connections.
|
||||
|
||||
If you've previously used the SocketClient component to establish outgoing
|
||||
client connections, upgrading should take no longer than a few minutes.
|
||||
All classes have been merged as-is from the latest `v0.7.0` release with no
|
||||
other changes, so you can simply update your code to use the updated namespace
|
||||
like this:
|
||||
|
||||
```php
|
||||
// old from SocketClient component and namespace
|
||||
$connector = new React\SocketClient\Connector($loop);
|
||||
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$connection->write('…');
|
||||
});
|
||||
|
||||
// new
|
||||
$connector = new React\Socket\Connector($loop);
|
||||
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$connection->write('…');
|
||||
});
|
||||
```
|
||||
|
||||
## 0.6.0 (2017-04-04)
|
||||
|
||||
* Feature: Add `LimitingServer` to limit and keep track of open connections
|
||||
(#86 by @clue)
|
||||
|
||||
```php
|
||||
$server = new Server(0, $loop);
|
||||
$server = new LimitingServer($server, 100);
|
||||
|
||||
$server->on('connection', function (ConnectionInterface $connection) {
|
||||
$connection->write('hello there!' . PHP_EOL);
|
||||
…
|
||||
});
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `pause()` and `resume()` methods to limit active
|
||||
connections
|
||||
(#84 by @clue)
|
||||
|
||||
```php
|
||||
$server = new Server(0, $loop);
|
||||
$server->pause();
|
||||
|
||||
$loop->addTimer(1.0, function() use ($server) {
|
||||
$server->resume();
|
||||
});
|
||||
```
|
||||
|
||||
## 0.5.1 (2017-03-09)
|
||||
|
||||
* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6
|
||||
(#79 by @clue)
|
||||
|
||||
## 0.5.0 (2017-02-14)
|
||||
|
||||
* Feature / BC break: Replace `listen()` call with URIs passed to constructor
|
||||
and reject listening on hostnames with `InvalidArgumentException`
|
||||
and replace `ConnectionException` with `RuntimeException` for consistency
|
||||
(#61, #66 and #72 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($loop);
|
||||
$server->listen(8080);
|
||||
|
||||
// new
|
||||
$server = new Server(8080, $loop);
|
||||
```
|
||||
|
||||
Similarly, you can now pass a full listening URI to the constructor to change
|
||||
the listening host:
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($loop);
|
||||
$server->listen(8080, '127.0.0.1');
|
||||
|
||||
// new
|
||||
$server = new Server('127.0.0.1:8080', $loop);
|
||||
```
|
||||
|
||||
Trying to start listening on (DNS) host names will now throw an
|
||||
`InvalidArgumentException`, use IP addresses instead:
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($loop);
|
||||
$server->listen(8080, 'localhost');
|
||||
|
||||
// new
|
||||
$server = new Server('127.0.0.1:8080', $loop);
|
||||
```
|
||||
|
||||
If trying to listen fails (such as if port is already in use or port below
|
||||
1024 may require root access etc.), it will now throw a `RuntimeException`,
|
||||
the `ConnectionException` class has been removed:
|
||||
|
||||
```php
|
||||
// old: throws React\Socket\ConnectionException
|
||||
$server = new Server($loop);
|
||||
$server->listen(80);
|
||||
|
||||
// new: throws RuntimeException
|
||||
$server = new Server(80, $loop);
|
||||
```
|
||||
|
||||
* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React
|
||||
(#62 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server->shutdown();
|
||||
|
||||
// new
|
||||
$server->close();
|
||||
```
|
||||
|
||||
* Feature / BC break: Replace `getPort()` with `getAddress()`
|
||||
(#67 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
echo $server->getPort(); // 8080
|
||||
|
||||
// new
|
||||
echo $server->getAddress(); // 127.0.0.1:8080
|
||||
```
|
||||
|
||||
* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP
|
||||
(#65 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
echo $connection->getRemoteAddress(); // 192.168.0.1
|
||||
|
||||
// new
|
||||
echo $connection->getRemoteAddress(); // 192.168.0.1:51743
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `getLocalAddress()` method
|
||||
(#68 by @clue)
|
||||
|
||||
```php
|
||||
echo $connection->getLocalAddress(); // 127.0.0.1:8080
|
||||
```
|
||||
|
||||
* BC break: The `Server` and `SecureServer` class are now marked `final`
|
||||
and you can no longer `extend` them
|
||||
(which was never documented or recommended anyway).
|
||||
Public properties and event handlers are now internal only.
|
||||
Please use composition instead of extension.
|
||||
(#71, #70 and #69 by @clue)
|
||||
|
||||
## 0.4.6 (2017-01-26)
|
||||
|
||||
* Feature: Support socket context options passed to `Server`
|
||||
(#64 by @clue)
|
||||
|
||||
* Fix: Properly return `null` for unknown addresses
|
||||
(#63 by @clue)
|
||||
|
||||
* Improve documentation for `ServerInterface` and lock test suite requirements
|
||||
(#60 by @clue, #57 by @shaunbramley)
|
||||
|
||||
## 0.4.5 (2017-01-08)
|
||||
|
||||
* Feature: Add `SecureServer` for secure TLS connections
|
||||
(#55 by @clue)
|
||||
|
||||
* Add functional integration tests
|
||||
(#54 by @clue)
|
||||
|
||||
## 0.4.4 (2016-12-19)
|
||||
|
||||
* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation
|
||||
(#50 by @clue)
|
||||
|
||||
* Feature / Fix: Improve test suite and switch to normal stream handler
|
||||
(#51 by @clue)
|
||||
|
||||
* Feature: Add examples
|
||||
(#49 by @clue)
|
||||
|
||||
## 0.4.3 (2016-03-01)
|
||||
|
||||
* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing
|
||||
* Support for PHP7 and HHVM
|
||||
* Support PHP 5.3 again
|
||||
|
||||
## 0.4.2 (2014-05-25)
|
||||
|
||||
* Verify stream is a valid resource in Connection
|
||||
|
||||
## 0.4.1 (2014-04-13)
|
||||
|
||||
* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev)
|
||||
* Bug fix: v0.3.4 changes merged for v0.4.1
|
||||
|
||||
## 0.3.4 (2014-03-30)
|
||||
|
||||
* Bug fix: Reset socket to non-blocking after shutting down (PHP bug)
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: Update to React/Promise 2.0
|
||||
* BC break: Update to Evenement 2.0
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
* Bump React dependencies to v0.4
|
||||
|
||||
## 0.3.3 (2013-07-08)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.3.1 (2013-04-21)
|
||||
|
||||
* Feature: Support binding to IPv6 addresses (@clue)
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Bump React dependencies to v0.3
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Bump React dependencies to v0.2
|
||||
|
||||
## 0.1.1 (2012-07-12)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.1.0 (2012-07-11)
|
||||
|
||||
* First tagged release
|
||||
21
vendor/react/socket/LICENSE
vendored
Normal file
21
vendor/react/socket/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
1564
vendor/react/socket/README.md
vendored
Normal file
1564
vendor/react/socket/README.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
52
vendor/react/socket/composer.json
vendored
Normal file
52
vendor/react/socket/composer.json
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "react/socket",
|
||||
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
|
||||
"keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"react/dns": "^1.13",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
|
||||
"react/stream": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4.3 || ^3.3 || ^2",
|
||||
"react/promise-stream": "^1.4",
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Socket\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Socket\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
183
vendor/react/socket/src/Connection.php
vendored
Normal file
183
vendor/react/socket/src/Connection.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableResourceStream;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* The actual connection implementation for ConnectionInterface
|
||||
*
|
||||
* This class should only be used internally, see ConnectionInterface instead.
|
||||
*
|
||||
* @see ConnectionInterface
|
||||
* @internal
|
||||
*/
|
||||
class Connection extends EventEmitter implements ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Internal flag whether this is a Unix domain socket (UDS) connection
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public $unix = false;
|
||||
|
||||
/**
|
||||
* Internal flag whether encryption has been enabled on this connection
|
||||
*
|
||||
* Mostly used by internal StreamEncryption so that connection returns
|
||||
* `tls://` scheme for encrypted connections instead of `tcp://`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public $encryptionEnabled = false;
|
||||
|
||||
/** @internal */
|
||||
public $stream;
|
||||
|
||||
private $input;
|
||||
|
||||
public function __construct($resource, LoopInterface $loop)
|
||||
{
|
||||
// PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might
|
||||
// block with 100% CPU usage on fragmented TLS records.
|
||||
// We try to work around this by always consuming the complete receive
|
||||
// buffer at once to avoid stale data in TLS buffers. This is known to
|
||||
// work around high CPU usage for well-behaving peers, but this may
|
||||
// cause very large data chunks for high throughput scenarios. The buggy
|
||||
// behavior can still be triggered due to network I/O buffers or
|
||||
// malicious peers on affected versions, upgrading is highly recommended.
|
||||
// @link https://bugs.php.net/bug.php?id=77390
|
||||
$clearCompleteBuffer = \PHP_VERSION_ID < 70215 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70303);
|
||||
|
||||
// PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
|
||||
// chunks of data over TLS streams at once.
|
||||
// We try to work around this by limiting the write chunk size to 8192
|
||||
// bytes for older PHP versions only.
|
||||
// This is only a work-around and has a noticable performance penalty on
|
||||
// affected versions. Please update your PHP version.
|
||||
// This applies to all streams because TLS may be enabled later on.
|
||||
// See https://github.com/reactphp/socket/issues/105
|
||||
$limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104));
|
||||
|
||||
$this->input = new DuplexResourceStream(
|
||||
$resource,
|
||||
$loop,
|
||||
$clearCompleteBuffer ? -1 : null,
|
||||
new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
|
||||
);
|
||||
|
||||
$this->stream = $resource;
|
||||
|
||||
Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
|
||||
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->input->isWritable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return $this->input->pipe($dest, $options);
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
return $this->input->write($data);
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
$this->input->end($data);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->input->close();
|
||||
$this->handleClose();
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function handleClose()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to cleanly shut down socket and ignore any errors in case other
|
||||
// side already closed. Underlying Stream implementation will take care
|
||||
// of closing stream resource, so we otherwise keep this open here.
|
||||
@\stream_socket_shutdown($this->stream, \STREAM_SHUT_RDWR);
|
||||
}
|
||||
|
||||
public function getRemoteAddress()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->parseAddress(\stream_socket_get_name($this->stream, true));
|
||||
}
|
||||
|
||||
public function getLocalAddress()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->parseAddress(\stream_socket_get_name($this->stream, false));
|
||||
}
|
||||
|
||||
private function parseAddress($address)
|
||||
{
|
||||
if ($address === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->unix) {
|
||||
// remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
|
||||
// note that technically ":" is a valid address, so keep this in place otherwise
|
||||
if (\substr($address, -1) === ':' && \defined('HHVM_VERSION_ID') && \HHVM_VERSION_ID < 31900) {
|
||||
$address = (string)\substr($address, 0, -1); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
|
||||
// PHP uses "\0" string and HHVM uses empty string (colon removed above)
|
||||
if ($address === '' || $address[0] === "\x00" ) {
|
||||
return null; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return 'unix://' . $address;
|
||||
}
|
||||
|
||||
// check if this is an IPv6 address which includes multiple colons but no square brackets
|
||||
$pos = \strrpos($address, ':');
|
||||
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
|
||||
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
|
||||
}
|
||||
}
|
||||
119
vendor/react/socket/src/ConnectionInterface.php
vendored
Normal file
119
vendor/react/socket/src/ConnectionInterface.php
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
* Any incoming and outgoing connection is represented by this interface,
|
||||
* such as a normal TCP/IP connection.
|
||||
*
|
||||
* An incoming or 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 (client IP)
|
||||
* where this connection has been established to/from.
|
||||
*
|
||||
* Most commonly, instances implementing this `ConnectionInterface` are emitted
|
||||
* by all classes implementing the [`ServerInterface`](#serverinterface) and
|
||||
* used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
|
||||
*
|
||||
* 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 $chunk;
|
||||
* });
|
||||
*
|
||||
* $connection->on('end', function () {
|
||||
* echo 'ended';
|
||||
* });
|
||||
*
|
||||
* $connection->on('error', function (Exception $e) {
|
||||
* echo 'error: ' . $e->getMessage();
|
||||
* });
|
||||
*
|
||||
* $connection->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 ServerInterface
|
||||
* @see ConnectorInterface
|
||||
*/
|
||||
interface ConnectionInterface extends DuplexStreamInterface
|
||||
{
|
||||
/**
|
||||
* Returns the full remote address (URI) where this connection has been established with
|
||||
*
|
||||
* ```php
|
||||
* $address = $connection->getRemoteAddress();
|
||||
* echo 'Connection with ' . $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 address (URI) as a string value, such
|
||||
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
|
||||
* `unix://example.sock` or `unix:///path/to/example.sock`.
|
||||
* Note that individual URI components are application specific and depend
|
||||
* on the underlying transport protocol.
|
||||
*
|
||||
* 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($address, PHP_URL_HOST), '[]');
|
||||
* echo 'Connection with ' . $ip . PHP_EOL;
|
||||
* ```
|
||||
*
|
||||
* @return ?string remote address (URI) or null if unknown
|
||||
*/
|
||||
public function getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
|
||||
*
|
||||
* ```php
|
||||
* $address = $connection->getLocalAddress();
|
||||
* echo 'Connection with ' . $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 address (URI) as a string value, such
|
||||
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
|
||||
* `unix://example.sock` or `unix:///path/to/example.sock`.
|
||||
* Note that individual URI components are application specific and depend
|
||||
* on the underlying transport protocol.
|
||||
*
|
||||
* This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
|
||||
* so they should not be confused.
|
||||
*
|
||||
* If your `TcpServer` instance is listening on multiple interfaces (e.g. using
|
||||
* the address `0.0.0.0`), you can use this method to find out which interface
|
||||
* actually accepted this connection (such as a public or local interface).
|
||||
*
|
||||
* 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 (URI) or null if unknown
|
||||
* @see self::getRemoteAddress()
|
||||
*/
|
||||
public function getLocalAddress();
|
||||
}
|
||||
236
vendor/react/socket/src/Connector.php
vendored
Normal file
236
vendor/react/socket/src/Connector.php
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Config\Config as DnsConfig;
|
||||
use React\Dns\Resolver\Factory as DnsFactory;
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* The `Connector` class is the main class in this package that implements the
|
||||
* `ConnectorInterface` and allows you to create streaming connections.
|
||||
*
|
||||
* You can use this connector to create any kind of streaming connections, such
|
||||
* as plaintext TCP/IP, secure TLS or local Unix connection streams.
|
||||
*
|
||||
* Under the hood, the `Connector` is implemented as a *higher-level facade*
|
||||
* for the lower-level connectors implemented in this package. This means it
|
||||
* also shares all of their features and implementation details.
|
||||
* If you want to typehint in your higher-level protocol implementation, you SHOULD
|
||||
* use the generic [`ConnectorInterface`](#connectorinterface) instead.
|
||||
*
|
||||
* @see ConnectorInterface for the base interface
|
||||
*/
|
||||
final class Connector implements ConnectorInterface
|
||||
{
|
||||
private $connectors = array();
|
||||
|
||||
/**
|
||||
* Instantiate new `Connector`
|
||||
*
|
||||
* ```php
|
||||
* $connector = new React\Socket\Connector();
|
||||
* ```
|
||||
*
|
||||
* This class takes two optional arguments for more advanced usage:
|
||||
*
|
||||
* ```php
|
||||
* // constructor signature as of v1.9.0
|
||||
* $connector = new React\Socket\Connector(array $context = [], ?LoopInterface $loop = null);
|
||||
*
|
||||
* // legacy constructor signature before v1.9.0
|
||||
* $connector = new React\Socket\Connector(?LoopInterface $loop = null, array $context = []);
|
||||
* ```
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param array|LoopInterface|null $context
|
||||
* @param null|LoopInterface|array $loop
|
||||
* @throws \InvalidArgumentException for invalid arguments
|
||||
*/
|
||||
public function __construct($context = array(), $loop = null)
|
||||
{
|
||||
// swap arguments for legacy constructor signature
|
||||
if (($context instanceof LoopInterface || $context === null) && (\func_num_args() <= 1 || \is_array($loop))) {
|
||||
$swap = $loop === null ? array(): $loop;
|
||||
$loop = $context;
|
||||
$context = $swap;
|
||||
}
|
||||
|
||||
if (!\is_array($context) || ($loop !== null && !$loop instanceof LoopInterface)) {
|
||||
throw new \InvalidArgumentException('Expected "array $context" and "?LoopInterface $loop" arguments');
|
||||
}
|
||||
|
||||
// apply default options if not explicitly given
|
||||
$context += array(
|
||||
'tcp' => true,
|
||||
'tls' => true,
|
||||
'unix' => true,
|
||||
|
||||
'dns' => true,
|
||||
'timeout' => true,
|
||||
'happy_eyeballs' => true,
|
||||
);
|
||||
|
||||
if ($context['timeout'] === true) {
|
||||
$context['timeout'] = (float)\ini_get("default_socket_timeout");
|
||||
}
|
||||
|
||||
if ($context['tcp'] instanceof ConnectorInterface) {
|
||||
$tcp = $context['tcp'];
|
||||
} else {
|
||||
$tcp = new TcpConnector(
|
||||
$loop,
|
||||
\is_array($context['tcp']) ? $context['tcp'] : array()
|
||||
);
|
||||
}
|
||||
|
||||
if ($context['dns'] !== false) {
|
||||
if ($context['dns'] instanceof ResolverInterface) {
|
||||
$resolver = $context['dns'];
|
||||
} else {
|
||||
if ($context['dns'] !== true) {
|
||||
$config = $context['dns'];
|
||||
} else {
|
||||
// try to load nameservers from system config or default to Google's public DNS
|
||||
$config = DnsConfig::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8'; // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
||||
$factory = new DnsFactory();
|
||||
$resolver = $factory->createCached(
|
||||
$config,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
if ($context['happy_eyeballs'] === true) {
|
||||
$tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver);
|
||||
} else {
|
||||
$tcp = new DnsConnector($tcp, $resolver);
|
||||
}
|
||||
}
|
||||
|
||||
if ($context['tcp'] !== false) {
|
||||
$context['tcp'] = $tcp;
|
||||
|
||||
if ($context['timeout'] !== false) {
|
||||
$context['tcp'] = new TimeoutConnector(
|
||||
$context['tcp'],
|
||||
$context['timeout'],
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
$this->connectors['tcp'] = $context['tcp'];
|
||||
}
|
||||
|
||||
if ($context['tls'] !== false) {
|
||||
if (!$context['tls'] instanceof ConnectorInterface) {
|
||||
$context['tls'] = new SecureConnector(
|
||||
$tcp,
|
||||
$loop,
|
||||
\is_array($context['tls']) ? $context['tls'] : array()
|
||||
);
|
||||
}
|
||||
|
||||
if ($context['timeout'] !== false) {
|
||||
$context['tls'] = new TimeoutConnector(
|
||||
$context['tls'],
|
||||
$context['timeout'],
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
$this->connectors['tls'] = $context['tls'];
|
||||
}
|
||||
|
||||
if ($context['unix'] !== false) {
|
||||
if (!$context['unix'] instanceof ConnectorInterface) {
|
||||
$context['unix'] = new UnixConnector($loop);
|
||||
}
|
||||
$this->connectors['unix'] = $context['unix'];
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$scheme = 'tcp';
|
||||
if (\strpos($uri, '://') !== false) {
|
||||
$scheme = (string)\substr($uri, 0, \strpos($uri, '://'));
|
||||
}
|
||||
|
||||
if (!isset($this->connectors[$scheme])) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'No connector available for URI scheme "' . $scheme . '" (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
return $this->connectors[$scheme]->connect($uri);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [internal] Builds on URI from the given URI parts and ip address with original hostname as query
|
||||
*
|
||||
* @param array $parts
|
||||
* @param string $host
|
||||
* @param string $ip
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public static function uri(array $parts, $host, $ip)
|
||||
{
|
||||
$uri = '';
|
||||
|
||||
// prepend original scheme if known
|
||||
if (isset($parts['scheme'])) {
|
||||
$uri .= $parts['scheme'] . '://';
|
||||
}
|
||||
|
||||
if (\strpos($ip, ':') !== false) {
|
||||
// enclose IPv6 addresses in square brackets before appending port
|
||||
$uri .= '[' . $ip . ']';
|
||||
} else {
|
||||
$uri .= $ip;
|
||||
}
|
||||
|
||||
// append original port if known
|
||||
if (isset($parts['port'])) {
|
||||
$uri .= ':' . $parts['port'];
|
||||
}
|
||||
|
||||
// append orignal path if known
|
||||
if (isset($parts['path'])) {
|
||||
$uri .= $parts['path'];
|
||||
}
|
||||
|
||||
// append original query if known
|
||||
if (isset($parts['query'])) {
|
||||
$uri .= '?' . $parts['query'];
|
||||
}
|
||||
|
||||
// append original hostname as query if resolved via DNS and if
|
||||
// destination URI does not contain "hostname" query param already
|
||||
$args = array();
|
||||
\parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
|
||||
if ($host !== $ip && !isset($args['hostname'])) {
|
||||
$uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($host);
|
||||
}
|
||||
|
||||
// append original fragment if known
|
||||
if (isset($parts['fragment'])) {
|
||||
$uri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
59
vendor/react/socket/src/ConnectorInterface.php
vendored
Normal file
59
vendor/react/socket/src/ConnectorInterface.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
/**
|
||||
* The `ConnectorInterface` is responsible for providing an interface for
|
||||
* establishing streaming connections, such as a normal TCP/IP connection.
|
||||
*
|
||||
* This is the main interface defined in this package and it is used throughout
|
||||
* React's vast ecosystem.
|
||||
*
|
||||
* Most higher-level components (such as HTTP, database or other networking
|
||||
* service clients) accept an instance implementing this interface to create their
|
||||
* TCP/IP connection to the underlying networking service.
|
||||
* This is usually done via dependency injection, so it's fairly simple to actually
|
||||
* swap this implementation against any other implementation of this interface.
|
||||
*
|
||||
* The interface only offers a single `connect()` method.
|
||||
*
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
interface ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* Creates a streaming connection to the given remote address
|
||||
*
|
||||
* If returns a Promise which either fulfills with a stream implementing
|
||||
* `ConnectionInterface` on success or rejects with an `Exception` if the
|
||||
* connection is not successful.
|
||||
*
|
||||
* ```php
|
||||
* $connector->connect('google.com:443')->then(
|
||||
* function (React\Socket\ConnectionInterface $connection) {
|
||||
* // connection successfully established
|
||||
* },
|
||||
* function (Exception $error) {
|
||||
* // failed to connect due to $error
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* 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
|
||||
* resources and references as applicable.
|
||||
*
|
||||
* ```php
|
||||
* $promise = $connector->connect($uri);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $uri
|
||||
* @return \React\Promise\PromiseInterface<ConnectionInterface>
|
||||
* Resolves with a `ConnectionInterface` on success or rejects with an `Exception` on error.
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
public function connect($uri);
|
||||
}
|
||||
117
vendor/react/socket/src/DnsConnector.php
vendored
Normal file
117
vendor/react/socket/src/DnsConnector.php
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
final class DnsConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $resolver;
|
||||
|
||||
public function __construct(ConnectorInterface $connector, ResolverInterface $resolver)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$original = $uri;
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
$parts = \parse_url($uri);
|
||||
if (isset($parts['scheme'])) {
|
||||
unset($parts['scheme']);
|
||||
}
|
||||
} else {
|
||||
$parts = \parse_url($uri);
|
||||
}
|
||||
|
||||
if (!$parts || !isset($parts['host'])) {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $original . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$host = \trim($parts['host'], '[]');
|
||||
$connector = $this->connector;
|
||||
|
||||
// skip DNS lookup / URI manipulation if this URI already contains an IP
|
||||
if (@\inet_pton($host) !== false) {
|
||||
return $connector->connect($original);
|
||||
}
|
||||
|
||||
$promise = $this->resolver->resolve($host);
|
||||
$resolved = null;
|
||||
|
||||
return new Promise\Promise(
|
||||
function ($resolve, $reject) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
|
||||
// resolve/reject with result of DNS lookup
|
||||
$promise->then(function ($ip) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
|
||||
$resolved = $ip;
|
||||
|
||||
return $promise = $connector->connect(
|
||||
Connector::uri($parts, $host, $ip)
|
||||
)->then(null, function (\Exception $e) use ($uri) {
|
||||
if ($e instanceof \RuntimeException) {
|
||||
$message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
|
||||
$e = new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed: ' . $message,
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as $ti => $one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as $ai => $arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
});
|
||||
}, function ($e) use ($uri, $reject) {
|
||||
$reject(new \RuntimeException('Connection to ' . $uri .' failed during DNS lookup: ' . $e->getMessage(), 0, $e));
|
||||
})->then($resolve, $reject);
|
||||
},
|
||||
function ($_, $reject) use (&$promise, &$resolved, $uri) {
|
||||
// cancellation should reject connection attempt
|
||||
// reject DNS resolution with custom reason, otherwise rely on connection cancellation below
|
||||
if ($resolved === null) {
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled during DNS lookup (ECONNABORTED)',
|
||||
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
}
|
||||
|
||||
// (try to) cancel pending DNS lookup / connection attempt
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
// overwrite callback arguments for PHP7+ only, so they do not show
|
||||
// up in the Exception trace and do not cause a possible cyclic reference.
|
||||
$_ = $reject = null;
|
||||
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
222
vendor/react/socket/src/FdServer.php
vendored
Normal file
222
vendor/react/socket/src/FdServer.php
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* [Internal] The `FdServer` class implements the `ServerInterface` and
|
||||
* is responsible for accepting connections from an existing file descriptor.
|
||||
*
|
||||
* ```php
|
||||
* $socket = new React\Socket\FdServer(3);
|
||||
* ```
|
||||
*
|
||||
* Whenever a client connects, it will emit a `connection` event with a connection
|
||||
* instance implementing `ConnectionInterface`:
|
||||
*
|
||||
* ```php
|
||||
* $socket->on('connection', function (ConnectionInterface $connection) {
|
||||
* echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
* @internal
|
||||
*/
|
||||
final class FdServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $master;
|
||||
private $loop;
|
||||
private $unix = false;
|
||||
private $listening = false;
|
||||
|
||||
/**
|
||||
* Creates a socket server and starts listening on the given file descriptor
|
||||
*
|
||||
* This starts accepting new incoming connections on the given file descriptor.
|
||||
* See also the `connection event` documented in the `ServerInterface`
|
||||
* for more details.
|
||||
*
|
||||
* ```php
|
||||
* $socket = new React\Socket\FdServer(3);
|
||||
* ```
|
||||
*
|
||||
* If the given FD is invalid or out of range, it will throw an `InvalidArgumentException`:
|
||||
*
|
||||
* ```php
|
||||
* // throws InvalidArgumentException
|
||||
* $socket = new React\Socket\FdServer(-1);
|
||||
* ```
|
||||
*
|
||||
* If the given FD appears to be valid, but listening on it fails (such as
|
||||
* if the FD does not exist or does not refer to a socket server), it will
|
||||
* throw a `RuntimeException`:
|
||||
*
|
||||
* ```php
|
||||
* // throws RuntimeException because FD does not reference a socket server
|
||||
* $socket = new React\Socket\FdServer(0, $loop);
|
||||
* ```
|
||||
*
|
||||
* Note that these error conditions may vary depending on your system and/or
|
||||
* configuration.
|
||||
* See the exception message and code for more details about the actual error
|
||||
* condition.
|
||||
*
|
||||
* @param int|string $fd FD number such as `3` or as URL in the form of `php://fd/3`
|
||||
* @param ?LoopInterface $loop
|
||||
* @throws \InvalidArgumentException if the listening address is invalid
|
||||
* @throws \RuntimeException if listening on this address fails (already in use etc.)
|
||||
*/
|
||||
public function __construct($fd, $loop = null)
|
||||
{
|
||||
if (\preg_match('#^php://fd/(\d+)$#', $fd, $m)) {
|
||||
$fd = (int) $m[1];
|
||||
}
|
||||
if (!\is_int($fd) || $fd < 0 || $fd >= \PHP_INT_MAX) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid FD number given (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
);
|
||||
}
|
||||
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// fopen(php://fd/3): Failed to open stream: Error duping file descriptor 3; possibly it doesn't exist: [9]: Bad file descriptor
|
||||
\preg_match('/\[(\d+)\]: (.*)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
$this->master = \fopen('php://fd/' . $fd, 'r+');
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if (false === $this->master) {
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on FD ' . $fd . ': ' . $errstr . SocketServer::errconst($errno),
|
||||
$errno
|
||||
);
|
||||
}
|
||||
|
||||
$meta = \stream_get_meta_data($this->master);
|
||||
if (!isset($meta['stream_type']) || $meta['stream_type'] !== 'tcp_socket') {
|
||||
\fclose($this->master);
|
||||
|
||||
$errno = \defined('SOCKET_ENOTSOCK') ? \SOCKET_ENOTSOCK : 88;
|
||||
$errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Not a socket';
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (ENOTSOCK)',
|
||||
$errno
|
||||
);
|
||||
}
|
||||
|
||||
// Socket should not have a peer address if this is a listening socket.
|
||||
// Looks like this work-around is the closest we can get because PHP doesn't expose SO_ACCEPTCONN even with ext-sockets.
|
||||
if (\stream_socket_get_name($this->master, true) !== false) {
|
||||
\fclose($this->master);
|
||||
|
||||
$errno = \defined('SOCKET_EISCONN') ? \SOCKET_EISCONN : 106;
|
||||
$errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Socket is connected';
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (EISCONN)',
|
||||
$errno
|
||||
);
|
||||
}
|
||||
|
||||
// Assume this is a Unix domain socket (UDS) when its listening address doesn't parse as a valid URL with a port.
|
||||
// Looks like this work-around is the closest we can get because PHP doesn't expose SO_DOMAIN even with ext-sockets.
|
||||
$this->unix = \parse_url($this->getAddress(), \PHP_URL_PORT) === false;
|
||||
|
||||
\stream_set_blocking($this->master, false);
|
||||
|
||||
$this->resume();
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$address = \stream_socket_get_name($this->master, false);
|
||||
|
||||
if ($this->unix === true) {
|
||||
return 'unix://' . $address;
|
||||
}
|
||||
|
||||
// check if this is an IPv6 address which includes multiple colons but no square brackets
|
||||
$pos = \strrpos($address, ':');
|
||||
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
|
||||
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return 'tcp://' . $address;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if (!$this->listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loop->removeReadStream($this->master);
|
||||
$this->listening = false;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->listening || !\is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$this->loop->addReadStream($this->master, function ($master) use ($that) {
|
||||
try {
|
||||
$newSocket = SocketServer::accept($master);
|
||||
} catch (\RuntimeException $e) {
|
||||
$that->emit('error', array($e));
|
||||
return;
|
||||
}
|
||||
$that->handleConnection($newSocket);
|
||||
});
|
||||
$this->listening = true;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pause();
|
||||
\fclose($this->master);
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection($socket)
|
||||
{
|
||||
$connection = new Connection($socket, $this->loop);
|
||||
$connection->unix = $this->unix;
|
||||
|
||||
$this->emit('connection', array($connection));
|
||||
}
|
||||
}
|
||||
41
vendor/react/socket/src/FixedUriConnector.php
vendored
Normal file
41
vendor/react/socket/src/FixedUriConnector.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
/**
|
||||
* Decorates an existing Connector to always use a fixed, preconfigured URI
|
||||
*
|
||||
* This can be useful for consumers that do not support certain URIs, such as
|
||||
* when you want to explicitly connect to a Unix domain socket (UDS) path
|
||||
* instead of connecting to a default address assumed by an higher-level API:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new React\Socket\FixedUriConnector(
|
||||
* 'unix:///var/run/docker.sock',
|
||||
* new React\Socket\UnixConnector()
|
||||
* );
|
||||
*
|
||||
* // destination will be ignored, actually connects to Unix domain socket
|
||||
* $promise = $connector->connect('localhost:80');
|
||||
* ```
|
||||
*/
|
||||
class FixedUriConnector implements ConnectorInterface
|
||||
{
|
||||
private $uri;
|
||||
private $connector;
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param ConnectorInterface $connector
|
||||
*/
|
||||
public function __construct($uri, ConnectorInterface $connector)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
public function connect($_)
|
||||
{
|
||||
return $this->connector->connect($this->uri);
|
||||
}
|
||||
}
|
||||
334
vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php
vendored
Normal file
334
vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\TimerInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HappyEyeBallsConnectionBuilder
|
||||
{
|
||||
/**
|
||||
* As long as we haven't connected yet keep popping an IP address of the connect queue until one of them
|
||||
* succeeds or they all fail. We will wait 100ms between connection attempts as per RFC.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc8305#section-5
|
||||
*/
|
||||
const CONNECTION_ATTEMPT_DELAY = 0.1;
|
||||
|
||||
/**
|
||||
* Delay `A` lookup by 50ms sending out connection to IPv4 addresses when IPv6 records haven't
|
||||
* resolved yet as per RFC.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc8305#section-3
|
||||
*/
|
||||
const RESOLUTION_DELAY = 0.05;
|
||||
|
||||
public $loop;
|
||||
public $connector;
|
||||
public $resolver;
|
||||
public $uri;
|
||||
public $host;
|
||||
public $resolved = array(
|
||||
Message::TYPE_A => false,
|
||||
Message::TYPE_AAAA => false,
|
||||
);
|
||||
public $resolverPromises = array();
|
||||
public $connectionPromises = array();
|
||||
public $connectQueue = array();
|
||||
public $nextAttemptTimer;
|
||||
public $parts;
|
||||
public $ipsCount = 0;
|
||||
public $failureCount = 0;
|
||||
public $resolve;
|
||||
public $reject;
|
||||
|
||||
public $lastErrorFamily;
|
||||
public $lastError6;
|
||||
public $lastError4;
|
||||
|
||||
public function __construct(LoopInterface $loop, ConnectorInterface $connector, ResolverInterface $resolver, $uri, $host, $parts)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
$this->connector = $connector;
|
||||
$this->resolver = $resolver;
|
||||
$this->uri = $uri;
|
||||
$this->host = $host;
|
||||
$this->parts = $parts;
|
||||
}
|
||||
|
||||
public function connect()
|
||||
{
|
||||
$that = $this;
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($that) {
|
||||
$lookupResolve = function ($type) use ($that, $resolve, $reject) {
|
||||
return function (array $ips) use ($that, $type, $resolve, $reject) {
|
||||
unset($that->resolverPromises[$type]);
|
||||
$that->resolved[$type] = true;
|
||||
|
||||
$that->mixIpsIntoConnectQueue($ips);
|
||||
|
||||
// start next connection attempt if not already awaiting next
|
||||
if ($that->nextAttemptTimer === null && $that->connectQueue) {
|
||||
$that->check($resolve, $reject);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$that->resolverPromises[Message::TYPE_AAAA] = $that->resolve(Message::TYPE_AAAA, $reject)->then($lookupResolve(Message::TYPE_AAAA));
|
||||
$that->resolverPromises[Message::TYPE_A] = $that->resolve(Message::TYPE_A, $reject)->then(function (array $ips) use ($that) {
|
||||
// happy path: IPv6 has resolved already (or could not resolve), continue with IPv4 addresses
|
||||
if ($that->resolved[Message::TYPE_AAAA] === true || !$ips) {
|
||||
return $ips;
|
||||
}
|
||||
|
||||
// Otherwise delay processing IPv4 lookup until short timer passes or IPv6 resolves in the meantime
|
||||
$deferred = new Promise\Deferred(function () use (&$ips) {
|
||||
// discard all IPv4 addresses if cancelled
|
||||
$ips = array();
|
||||
});
|
||||
$timer = $that->loop->addTimer($that::RESOLUTION_DELAY, function () use ($deferred, $ips) {
|
||||
$deferred->resolve($ips);
|
||||
});
|
||||
|
||||
$that->resolverPromises[Message::TYPE_AAAA]->then(function () use ($that, $timer, $deferred, &$ips) {
|
||||
$that->loop->cancelTimer($timer);
|
||||
$deferred->resolve($ips);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
})->then($lookupResolve(Message::TYPE_A));
|
||||
}, function ($_, $reject) use ($that) {
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $that->uri . ' cancelled' . (!$that->connectionPromises ? ' during DNS lookup' : '') . ' (ECONNABORTED)',
|
||||
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
$_ = $reject = null;
|
||||
|
||||
$that->cleanUp();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param int $type DNS query type
|
||||
* @param callable $reject
|
||||
* @return \React\Promise\PromiseInterface<string[]> Returns a promise that
|
||||
* always resolves with a list of IP addresses on success or an empty
|
||||
* list on error.
|
||||
*/
|
||||
public function resolve($type, $reject)
|
||||
{
|
||||
$that = $this;
|
||||
return $that->resolver->resolveAll($that->host, $type)->then(null, function (\Exception $e) use ($type, $reject, $that) {
|
||||
unset($that->resolverPromises[$type]);
|
||||
$that->resolved[$type] = true;
|
||||
|
||||
if ($type === Message::TYPE_A) {
|
||||
$that->lastError4 = $e->getMessage();
|
||||
$that->lastErrorFamily = 4;
|
||||
} else {
|
||||
$that->lastError6 = $e->getMessage();
|
||||
$that->lastErrorFamily = 6;
|
||||
}
|
||||
|
||||
// cancel next attempt timer when there are no more IPs to connect to anymore
|
||||
if ($that->nextAttemptTimer !== null && !$that->connectQueue) {
|
||||
$that->loop->cancelTimer($that->nextAttemptTimer);
|
||||
$that->nextAttemptTimer = null;
|
||||
}
|
||||
|
||||
if ($that->hasBeenResolved() && $that->ipsCount === 0) {
|
||||
$reject(new \RuntimeException(
|
||||
$that->error(),
|
||||
0,
|
||||
$e
|
||||
));
|
||||
}
|
||||
|
||||
// Exception already handled above, so don't throw an unhandled rejection here
|
||||
return array();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function check($resolve, $reject)
|
||||
{
|
||||
$ip = \array_shift($this->connectQueue);
|
||||
|
||||
// start connection attempt and remember array position to later unset again
|
||||
$this->connectionPromises[] = $this->attemptConnection($ip);
|
||||
\end($this->connectionPromises);
|
||||
$index = \key($this->connectionPromises);
|
||||
|
||||
$that = $this;
|
||||
$that->connectionPromises[$index]->then(function ($connection) use ($that, $index, $resolve) {
|
||||
unset($that->connectionPromises[$index]);
|
||||
|
||||
$that->cleanUp();
|
||||
|
||||
$resolve($connection);
|
||||
}, function (\Exception $e) use ($that, $index, $ip, $resolve, $reject) {
|
||||
unset($that->connectionPromises[$index]);
|
||||
|
||||
$that->failureCount++;
|
||||
|
||||
$message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
|
||||
if (\strpos($ip, ':') === false) {
|
||||
$that->lastError4 = $message;
|
||||
$that->lastErrorFamily = 4;
|
||||
} else {
|
||||
$that->lastError6 = $message;
|
||||
$that->lastErrorFamily = 6;
|
||||
}
|
||||
|
||||
// start next connection attempt immediately on error
|
||||
if ($that->connectQueue) {
|
||||
if ($that->nextAttemptTimer !== null) {
|
||||
$that->loop->cancelTimer($that->nextAttemptTimer);
|
||||
$that->nextAttemptTimer = null;
|
||||
}
|
||||
|
||||
$that->check($resolve, $reject);
|
||||
}
|
||||
|
||||
if ($that->hasBeenResolved() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($that->ipsCount === $that->failureCount) {
|
||||
$that->cleanUp();
|
||||
|
||||
$reject(new \RuntimeException(
|
||||
$that->error(),
|
||||
$e->getCode(),
|
||||
$e
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
// Allow next connection attempt in 100ms: https://tools.ietf.org/html/rfc8305#section-5
|
||||
// Only start timer when more IPs are queued or when DNS query is still pending (might add more IPs)
|
||||
if ($this->nextAttemptTimer === null && (\count($this->connectQueue) > 0 || $this->resolved[Message::TYPE_A] === false || $this->resolved[Message::TYPE_AAAA] === false)) {
|
||||
$this->nextAttemptTimer = $this->loop->addTimer(self::CONNECTION_ATTEMPT_DELAY, function () use ($that, $resolve, $reject) {
|
||||
$that->nextAttemptTimer = null;
|
||||
|
||||
if ($that->connectQueue) {
|
||||
$that->check($resolve, $reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function attemptConnection($ip)
|
||||
{
|
||||
$uri = Connector::uri($this->parts, $this->host, $ip);
|
||||
|
||||
return $this->connector->connect($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function cleanUp()
|
||||
{
|
||||
// clear list of outstanding IPs to avoid creating new connections
|
||||
$this->connectQueue = array();
|
||||
|
||||
// cancel pending connection attempts
|
||||
foreach ($this->connectionPromises as $connectionPromise) {
|
||||
if ($connectionPromise instanceof PromiseInterface && \method_exists($connectionPromise, 'cancel')) {
|
||||
$connectionPromise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// cancel pending DNS resolution (cancel IPv4 first in case it is awaiting IPv6 resolution delay)
|
||||
foreach (\array_reverse($this->resolverPromises) as $resolverPromise) {
|
||||
if ($resolverPromise instanceof PromiseInterface && \method_exists($resolverPromise, 'cancel')) {
|
||||
$resolverPromise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->nextAttemptTimer instanceof TimerInterface) {
|
||||
$this->loop->cancelTimer($this->nextAttemptTimer);
|
||||
$this->nextAttemptTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function hasBeenResolved()
|
||||
{
|
||||
foreach ($this->resolved as $typeHasBeenResolved) {
|
||||
if ($typeHasBeenResolved === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes an array of IP addresses into the connect queue in such a way they alternate when attempting to connect.
|
||||
* The goal behind it is first attempt to connect to IPv6, then to IPv4, then to IPv6 again until one of those
|
||||
* attempts succeeds.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc8305#section-4
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function mixIpsIntoConnectQueue(array $ips)
|
||||
{
|
||||
\shuffle($ips);
|
||||
$this->ipsCount += \count($ips);
|
||||
$connectQueueStash = $this->connectQueue;
|
||||
$this->connectQueue = array();
|
||||
while (\count($connectQueueStash) > 0 || \count($ips) > 0) {
|
||||
if (\count($ips) > 0) {
|
||||
$this->connectQueue[] = \array_shift($ips);
|
||||
}
|
||||
if (\count($connectQueueStash) > 0) {
|
||||
$this->connectQueue[] = \array_shift($connectQueueStash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
if ($this->lastError4 === $this->lastError6) {
|
||||
$message = $this->lastError6;
|
||||
} elseif ($this->lastErrorFamily === 6) {
|
||||
$message = 'Last error for IPv6: ' . $this->lastError6 . '. Previous error for IPv4: ' . $this->lastError4;
|
||||
} else {
|
||||
$message = 'Last error for IPv4: ' . $this->lastError4 . '. Previous error for IPv6: ' . $this->lastError6;
|
||||
}
|
||||
|
||||
if ($this->hasBeenResolved() && $this->ipsCount === 0) {
|
||||
if ($this->lastError6 === $this->lastError4) {
|
||||
$message = ' during DNS lookup: ' . $this->lastError6;
|
||||
} else {
|
||||
$message = ' during DNS lookup. ' . $message;
|
||||
}
|
||||
} else {
|
||||
$message = ': ' . $message;
|
||||
}
|
||||
|
||||
return 'Connection to ' . $this->uri . ' failed' . $message;
|
||||
}
|
||||
}
|
||||
80
vendor/react/socket/src/HappyEyeBallsConnector.php
vendored
Normal file
80
vendor/react/socket/src/HappyEyeBallsConnector.php
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
|
||||
final class HappyEyeBallsConnector implements ConnectorInterface
|
||||
{
|
||||
private $loop;
|
||||
private $connector;
|
||||
private $resolver;
|
||||
|
||||
/**
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ConnectorInterface $connector
|
||||
* @param ResolverInterface $resolver
|
||||
*/
|
||||
public function __construct($loop = null, $connector = null, $resolver = null)
|
||||
{
|
||||
// $connector and $resolver arguments are actually required, marked
|
||||
// optional for technical reasons only. Nullable $loop without default
|
||||
// requires PHP 7.1, null default is also supported in legacy PHP
|
||||
// versions, but required parameters are not allowed after arguments
|
||||
// with null default. Mark all parameters optional and check accordingly.
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
if (!$connector instanceof ConnectorInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($connector) expected React\Socket\ConnectorInterface');
|
||||
}
|
||||
if (!$resolver instanceof ResolverInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($resolver) expected React\Dns\Resolver\ResolverInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->connector = $connector;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$original = $uri;
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
$parts = \parse_url($uri);
|
||||
if (isset($parts['scheme'])) {
|
||||
unset($parts['scheme']);
|
||||
}
|
||||
} else {
|
||||
$parts = \parse_url($uri);
|
||||
}
|
||||
|
||||
if (!$parts || !isset($parts['host'])) {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $original . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$host = \trim($parts['host'], '[]');
|
||||
|
||||
// skip DNS lookup / URI manipulation if this URI already contains an IP
|
||||
if (@\inet_pton($host) !== false) {
|
||||
return $this->connector->connect($original);
|
||||
}
|
||||
|
||||
$builder = new HappyEyeBallsConnectionBuilder(
|
||||
$this->loop,
|
||||
$this->connector,
|
||||
$this->resolver,
|
||||
$uri,
|
||||
$host,
|
||||
$parts
|
||||
);
|
||||
return $builder->connect();
|
||||
}
|
||||
}
|
||||
203
vendor/react/socket/src/LimitingServer.php
vendored
Normal file
203
vendor/react/socket/src/LimitingServer.php
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Exception;
|
||||
use OverflowException;
|
||||
|
||||
/**
|
||||
* The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
|
||||
* for limiting and keeping track of open connections to this server instance.
|
||||
*
|
||||
* Whenever the underlying server emits a `connection` event, it will check its
|
||||
* limits and then either
|
||||
* - keep track of this connection by adding it to the list of
|
||||
* open connections and then forward the `connection` event
|
||||
* - or reject (close) the connection when its limits are exceeded and will
|
||||
* forward an `error` event instead.
|
||||
*
|
||||
* Whenever a connection closes, it will remove this connection from the list of
|
||||
* open connections.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\LimitingServer($server, 100);
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
class LimitingServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $connections = array();
|
||||
private $server;
|
||||
private $limit;
|
||||
|
||||
private $pauseOnLimit = false;
|
||||
private $autoPaused = false;
|
||||
private $manuPaused = false;
|
||||
|
||||
/**
|
||||
* Instantiates a new LimitingServer.
|
||||
*
|
||||
* You have to pass a maximum number of open connections to ensure
|
||||
* the server will automatically reject (close) connections once this limit
|
||||
* is exceeded. In this case, it will emit an `error` event to inform about
|
||||
* this and no `connection` event will be emitted.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\LimitingServer($server, 100);
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You MAY pass a `null` limit in order to put no limit on the number of
|
||||
* open connections and keep accepting new connection until you run out of
|
||||
* operating system resources (such as open file handles). This may be
|
||||
* useful if you do not want to take care of applying a limit but still want
|
||||
* to use the `getConnections()` method.
|
||||
*
|
||||
* You can optionally configure the server to pause accepting new
|
||||
* connections once the connection limit is reached. In this case, it will
|
||||
* pause the underlying server and no longer process any new connections at
|
||||
* all, thus also no longer closing any excessive connections.
|
||||
* The underlying operating system is responsible for keeping a backlog of
|
||||
* pending connections until its limit is reached, at which point it will
|
||||
* start rejecting further connections.
|
||||
* Once the server is below the connection limit, it will continue consuming
|
||||
* connections from the backlog and will process any outstanding data on
|
||||
* each connection.
|
||||
* This mode may be useful for some protocols that are designed to wait for
|
||||
* a response message (such as HTTP), but may be less useful for other
|
||||
* protocols that demand immediate responses (such as a "welcome" message in
|
||||
* an interactive chat).
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\LimitingServer($server, 100, true);
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param ServerInterface $server
|
||||
* @param int|null $connectionLimit
|
||||
* @param bool $pauseOnLimit
|
||||
*/
|
||||
public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
|
||||
{
|
||||
$this->server = $server;
|
||||
$this->limit = $connectionLimit;
|
||||
if ($connectionLimit !== null) {
|
||||
$this->pauseOnLimit = $pauseOnLimit;
|
||||
}
|
||||
|
||||
$this->server->on('connection', array($this, 'handleConnection'));
|
||||
$this->server->on('error', array($this, 'handleError'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all currently active connections
|
||||
*
|
||||
* ```php
|
||||
* foreach ($server->getConnection() as $connection) {
|
||||
* $connection->write('Hi!');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @return ConnectionInterface[]
|
||||
*/
|
||||
public function getConnections()
|
||||
{
|
||||
return $this->connections;
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->server->getAddress();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if (!$this->manuPaused) {
|
||||
$this->manuPaused = true;
|
||||
|
||||
if (!$this->autoPaused) {
|
||||
$this->server->pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->manuPaused) {
|
||||
$this->manuPaused = false;
|
||||
|
||||
if (!$this->autoPaused) {
|
||||
$this->server->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->server->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection(ConnectionInterface $connection)
|
||||
{
|
||||
// close connection if limit exceeded
|
||||
if ($this->limit !== null && \count($this->connections) >= $this->limit) {
|
||||
$this->handleError(new \OverflowException('Connection closed because server reached connection limit'));
|
||||
$connection->close();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->connections[] = $connection;
|
||||
$that = $this;
|
||||
$connection->on('close', function () use ($that, $connection) {
|
||||
$that->handleDisconnection($connection);
|
||||
});
|
||||
|
||||
// pause accepting new connections if limit exceeded
|
||||
if ($this->pauseOnLimit && !$this->autoPaused && \count($this->connections) >= $this->limit) {
|
||||
$this->autoPaused = true;
|
||||
|
||||
if (!$this->manuPaused) {
|
||||
$this->server->pause();
|
||||
}
|
||||
}
|
||||
|
||||
$this->emit('connection', array($connection));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleDisconnection(ConnectionInterface $connection)
|
||||
{
|
||||
unset($this->connections[\array_search($connection, $this->connections)]);
|
||||
|
||||
// continue accepting new connection if below limit
|
||||
if ($this->autoPaused && \count($this->connections) < $this->limit) {
|
||||
$this->autoPaused = false;
|
||||
|
||||
if (!$this->manuPaused) {
|
||||
$this->server->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $error)
|
||||
{
|
||||
$this->emit('error', array($error));
|
||||
}
|
||||
}
|
||||
132
vendor/react/socket/src/SecureConnector.php
vendored
Normal file
132
vendor/react/socket/src/SecureConnector.php
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use BadMethodCallException;
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
final class SecureConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $streamEncryption;
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @param ConnectorInterface $connector
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
*/
|
||||
public function __construct(ConnectorInterface $connector, $loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->connector = $connector;
|
||||
$this->streamEncryption = new StreamEncryption($loop ?: Loop::get(), false);
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
if (!\function_exists('stream_socket_enable_crypto')) {
|
||||
return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tls://' . $uri;
|
||||
}
|
||||
|
||||
$parts = \parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $uri . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$context = $this->context;
|
||||
$encryption = $this->streamEncryption;
|
||||
$connected = false;
|
||||
/** @var \React\Promise\PromiseInterface<ConnectionInterface> $promise */
|
||||
$promise = $this->connector->connect(
|
||||
\str_replace('tls://', '', $uri)
|
||||
)->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri, &$promise, &$connected) {
|
||||
// (unencrypted) TCP/IP connection succeeded
|
||||
$connected = true;
|
||||
|
||||
if (!$connection instanceof Connection) {
|
||||
$connection->close();
|
||||
throw new \UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
|
||||
}
|
||||
|
||||
// set required SSL/TLS context options
|
||||
foreach ($context as $name => $value) {
|
||||
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
|
||||
}
|
||||
|
||||
// try to enable encryption
|
||||
return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) {
|
||||
// establishing encryption failed => close invalid connection and return error
|
||||
$connection->close();
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(),
|
||||
$error->getCode()
|
||||
);
|
||||
});
|
||||
}, function (\Exception $e) use ($uri) {
|
||||
if ($e instanceof \RuntimeException) {
|
||||
$message = \preg_replace('/^Connection to [^ ]+/', '', $e->getMessage());
|
||||
$e = new \RuntimeException(
|
||||
'Connection to ' . $uri . $message,
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as $ti => $one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as $ai => $arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
});
|
||||
|
||||
return new \React\Promise\Promise(
|
||||
function ($resolve, $reject) use ($promise) {
|
||||
$promise->then($resolve, $reject);
|
||||
},
|
||||
function ($_, $reject) use (&$promise, $uri, &$connected) {
|
||||
if ($connected) {
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled during TLS handshake (ECONNABORTED)',
|
||||
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
}
|
||||
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
210
vendor/react/socket/src/SecureServer.php
vendored
Normal file
210
vendor/react/socket/src/SecureServer.php
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use BadMethodCallException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* The `SecureServer` class implements the `ServerInterface` and is responsible
|
||||
* for providing a secure TLS (formerly known as SSL) server.
|
||||
*
|
||||
* It does so by wrapping a `TcpServer` instance which waits for plaintext
|
||||
* TCP/IP connections and then performs a TLS handshake for each connection.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8000);
|
||||
* $server = new React\Socket\SecureServer($server, null, array(
|
||||
* // tls context options here…
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Whenever a client completes the TLS handshake, it will emit a `connection` event
|
||||
* with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
|
||||
*
|
||||
* ```php
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
|
||||
*
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Whenever a client fails to perform a successful TLS handshake, it will emit an
|
||||
* `error` event and then close the underlying TCP/IP connection:
|
||||
*
|
||||
* ```php
|
||||
* $server->on('error', function (Exception $e) {
|
||||
* echo 'Error' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* Note that the `SecureServer` class is a concrete implementation for TLS sockets.
|
||||
* If you want to typehint in your higher-level protocol implementation, you SHOULD
|
||||
* use the generic `ServerInterface` instead.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
final class SecureServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $tcp;
|
||||
private $encryption;
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Creates a secure TLS server and starts waiting for incoming connections
|
||||
*
|
||||
* It does so by wrapping a `TcpServer` instance which waits for plaintext
|
||||
* TCP/IP connections and then performs a TLS handshake for each connection.
|
||||
* It thus requires valid [TLS context options],
|
||||
* which in its most basic form may look something like this if you're using a
|
||||
* PEM encoded certificate file:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8000);
|
||||
* $server = new React\Socket\SecureServer($server, null, array(
|
||||
* 'local_cert' => 'server.pem'
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Note that the certificate file will not be loaded on instantiation but when an
|
||||
* incoming connection initializes its TLS context.
|
||||
* This implies that any invalid certificate file paths or contents will only cause
|
||||
* an `error` event at a later time.
|
||||
*
|
||||
* If your private key is encrypted with a passphrase, you have to specify it
|
||||
* like this:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8000);
|
||||
* $server = new React\Socket\SecureServer($server, null, array(
|
||||
* 'local_cert' => 'server.pem',
|
||||
* 'passphrase' => 'secret'
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Note that available [TLS context options],
|
||||
* their defaults and effects of changing these may vary depending on your system
|
||||
* and/or PHP version.
|
||||
* Passing unknown context options has no effect.
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* Advanced usage: Despite allowing any `ServerInterface` as first parameter,
|
||||
* you SHOULD pass a `TcpServer` instance as first parameter, unless you
|
||||
* know what you're doing.
|
||||
* Internally, the `SecureServer` has to set the required TLS context options on
|
||||
* the underlying stream resources.
|
||||
* These resources are not exposed through any of the interfaces defined in this
|
||||
* package, but only through the internal `Connection` class.
|
||||
* The `TcpServer` class is guaranteed to emit connections that implement
|
||||
* the `ConnectionInterface` and uses the internal `Connection` class in order to
|
||||
* expose these underlying resources.
|
||||
* If you use a custom `ServerInterface` and its `connection` event does not
|
||||
* meet this requirement, the `SecureServer` will emit an `error` event and
|
||||
* then close the underlying connection.
|
||||
*
|
||||
* @param ServerInterface|TcpServer $tcp
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
* @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
|
||||
* @see TcpServer
|
||||
* @link https://www.php.net/manual/en/context.ssl.php for TLS context options
|
||||
*/
|
||||
public function __construct(ServerInterface $tcp, $loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
if (!\function_exists('stream_socket_enable_crypto')) {
|
||||
throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// default to empty passphrase to suppress blocking passphrase prompt
|
||||
$context += array(
|
||||
'passphrase' => ''
|
||||
);
|
||||
|
||||
$this->tcp = $tcp;
|
||||
$this->encryption = new StreamEncryption($loop ?: Loop::get());
|
||||
$this->context = $context;
|
||||
|
||||
$that = $this;
|
||||
$this->tcp->on('connection', function ($connection) use ($that) {
|
||||
$that->handleConnection($connection);
|
||||
});
|
||||
$this->tcp->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
});
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
$address = $this->tcp->getAddress();
|
||||
if ($address === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \str_replace('tcp://' , 'tls://', $address);
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->tcp->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->tcp->resume();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
return $this->tcp->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection(ConnectionInterface $connection)
|
||||
{
|
||||
if (!$connection instanceof Connection) {
|
||||
$this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
|
||||
$connection->close();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->context as $name => $value) {
|
||||
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
|
||||
}
|
||||
|
||||
// get remote address before starting TLS handshake in case connection closes during handshake
|
||||
$remote = $connection->getRemoteAddress();
|
||||
$that = $this;
|
||||
|
||||
$this->encryption->enable($connection)->then(
|
||||
function ($conn) use ($that) {
|
||||
$that->emit('connection', array($conn));
|
||||
},
|
||||
function ($error) use ($that, $connection, $remote) {
|
||||
$error = new \RuntimeException(
|
||||
'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(),
|
||||
$error->getCode()
|
||||
);
|
||||
|
||||
$that->emit('error', array($error));
|
||||
$connection->close();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
118
vendor/react/socket/src/Server.php
vendored
Normal file
118
vendor/react/socket/src/Server.php
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @deprecated 1.9.0 See `SocketServer` instead
|
||||
* @see SocketServer
|
||||
*/
|
||||
final class Server extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* [Deprecated] `Server`
|
||||
*
|
||||
* This class exists for BC reasons only and should not be used anymore.
|
||||
*
|
||||
* ```php
|
||||
* // deprecated
|
||||
* $socket = new React\Socket\Server(0);
|
||||
* $socket = new React\Socket\Server('127.0.0.1:8000');
|
||||
* $socket = new React\Socket\Server('127.0.0.1:8000', null, $context);
|
||||
* $socket = new React\Socket\Server('127.0.0.1:8000', $loop, $context);
|
||||
*
|
||||
* // new
|
||||
* $socket = new React\Socket\SocketServer('127.0.0.1:0');
|
||||
* $socket = new React\Socket\SocketServer('127.0.0.1:8000');
|
||||
* $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
|
||||
* $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context, $loop);
|
||||
* ```
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* For BC reasons, you can also pass the TCP socket context options as a simple
|
||||
* array without wrapping this in another array under the `tcp` key.
|
||||
*
|
||||
* @param string|int $uri
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
* @deprecated 1.9.0 See `SocketServer` instead
|
||||
* @see SocketServer
|
||||
*/
|
||||
public function __construct($uri, $loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$loop = $loop ?: Loop::get();
|
||||
|
||||
// sanitize TCP context options if not properly wrapped
|
||||
if ($context && (!isset($context['tcp']) && !isset($context['tls']) && !isset($context['unix']))) {
|
||||
$context = array('tcp' => $context);
|
||||
}
|
||||
|
||||
// apply default options if not explicitly given
|
||||
$context += array(
|
||||
'tcp' => array(),
|
||||
'tls' => array(),
|
||||
'unix' => array()
|
||||
);
|
||||
|
||||
$scheme = 'tcp';
|
||||
$pos = \strpos($uri, '://');
|
||||
if ($pos !== false) {
|
||||
$scheme = \substr($uri, 0, $pos);
|
||||
}
|
||||
|
||||
if ($scheme === 'unix') {
|
||||
$server = new UnixServer($uri, $loop, $context['unix']);
|
||||
} else {
|
||||
$server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
|
||||
|
||||
if ($scheme === 'tls') {
|
||||
$server = new SecureServer($server, $loop, $context['tls']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->server = $server;
|
||||
|
||||
$that = $this;
|
||||
$server->on('connection', function (ConnectionInterface $conn) use ($that) {
|
||||
$that->emit('connection', array($conn));
|
||||
});
|
||||
$server->on('error', function (Exception $error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
});
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->server->getAddress();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->server->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->server->resume();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->server->close();
|
||||
}
|
||||
}
|
||||
151
vendor/react/socket/src/ServerInterface.php
vendored
Normal file
151
vendor/react/socket/src/ServerInterface.php
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitterInterface;
|
||||
|
||||
/**
|
||||
* The `ServerInterface` is responsible for providing an interface for accepting
|
||||
* incoming streaming connections, such as a normal TCP/IP connection.
|
||||
*
|
||||
* Most higher-level components (such as a HTTP server) accept an instance
|
||||
* implementing this interface to accept incoming streaming connections.
|
||||
* This is usually done via dependency injection, so it's fairly simple to actually
|
||||
* swap this implementation against any other implementation of this interface.
|
||||
* This means that you SHOULD typehint against this interface instead of a concrete
|
||||
* implementation of this interface.
|
||||
*
|
||||
* Besides defining a few methods, this interface also implements the
|
||||
* `EventEmitterInterface` which allows you to react to certain events:
|
||||
*
|
||||
* connection event:
|
||||
* The `connection` event will be emitted whenever a new connection has been
|
||||
* established, i.e. a new client connects to this server socket:
|
||||
*
|
||||
* ```php
|
||||
* $socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* echo 'new connection' . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ConnectionInterface` for more details about handling the
|
||||
* incoming connection.
|
||||
*
|
||||
* error event:
|
||||
* The `error` event will be emitted whenever there's an error accepting a new
|
||||
* connection from a client.
|
||||
*
|
||||
* ```php
|
||||
* $socket->on('error', function (Exception $e) {
|
||||
* echo 'error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that this is not a fatal error event, i.e. the server keeps listening for
|
||||
* new connections even after this event.
|
||||
*
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
interface ServerInterface extends EventEmitterInterface
|
||||
{
|
||||
/**
|
||||
* Returns the full address (URI) this server is currently listening on
|
||||
*
|
||||
* ```php
|
||||
* $address = $socket->getAddress();
|
||||
* echo 'Server listening on ' . $address . PHP_EOL;
|
||||
* ```
|
||||
*
|
||||
* If the address can not be determined or is unknown at this time (such as
|
||||
* after the socket has been closed), it MAY return a `NULL` value instead.
|
||||
*
|
||||
* Otherwise, it will return the full address (URI) as a string value, such
|
||||
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
|
||||
* Note that individual URI components are application specific and depend
|
||||
* on the underlying transport protocol.
|
||||
*
|
||||
* If this is a TCP/IP based server and you only want the local port, you may
|
||||
* use something like this:
|
||||
*
|
||||
* ```php
|
||||
* $address = $socket->getAddress();
|
||||
* $port = parse_url($address, PHP_URL_PORT);
|
||||
* echo 'Server listening on port ' . $port . PHP_EOL;
|
||||
* ```
|
||||
*
|
||||
* @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed)
|
||||
*/
|
||||
public function getAddress();
|
||||
|
||||
/**
|
||||
* Pauses accepting new incoming connections.
|
||||
*
|
||||
* Removes the socket resource from the EventLoop and thus stop accepting
|
||||
* new connections. Note that the listening socket stays active and is not
|
||||
* closed.
|
||||
*
|
||||
* This means that new incoming connections will stay pending in the
|
||||
* operating system backlog until its configurable backlog is filled.
|
||||
* Once the backlog is filled, the operating system may reject further
|
||||
* incoming connections until the backlog is drained again by resuming
|
||||
* to accept new connections.
|
||||
*
|
||||
* Once the server is paused, no futher `connection` events SHOULD
|
||||
* be emitted.
|
||||
*
|
||||
* ```php
|
||||
* $socket->pause();
|
||||
*
|
||||
* $socket->on('connection', assertShouldNeverCalled());
|
||||
* ```
|
||||
*
|
||||
* This method is advisory-only, though generally not recommended, the
|
||||
* server MAY continue emitting `connection` events.
|
||||
*
|
||||
* Unless otherwise noted, a successfully opened server SHOULD NOT start
|
||||
* in paused state.
|
||||
*
|
||||
* You can continue processing events by calling `resume()` again.
|
||||
*
|
||||
* Note that both methods can be called any number of times, in particular
|
||||
* calling `pause()` more than once SHOULD NOT have any effect.
|
||||
* Similarly, calling this after `close()` is a NO-OP.
|
||||
*
|
||||
* @see self::resume()
|
||||
* @return void
|
||||
*/
|
||||
public function pause();
|
||||
|
||||
/**
|
||||
* Resumes accepting new incoming connections.
|
||||
*
|
||||
* Re-attach the socket resource to the EventLoop after a previous `pause()`.
|
||||
*
|
||||
* ```php
|
||||
* $socket->pause();
|
||||
*
|
||||
* Loop::addTimer(1.0, function () use ($socket) {
|
||||
* $socket->resume();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that both methods can be called any number of times, in particular
|
||||
* calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
|
||||
* Similarly, calling this after `close()` is a NO-OP.
|
||||
*
|
||||
* @see self::pause()
|
||||
* @return void
|
||||
*/
|
||||
public function resume();
|
||||
|
||||
/**
|
||||
* Shuts down this listening socket
|
||||
*
|
||||
* This will stop listening for new incoming connections on this socket.
|
||||
*
|
||||
* Calling this method more than once on the same instance is a NO-OP.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close();
|
||||
}
|
||||
215
vendor/react/socket/src/SocketServer.php
vendored
Normal file
215
vendor/react/socket/src/SocketServer.php
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
final class SocketServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* The `SocketServer` class is the main class in this package that implements the `ServerInterface` and
|
||||
* allows you to accept incoming streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
|
||||
*
|
||||
* ```php
|
||||
* $socket = new React\Socket\SocketServer('127.0.0.1:0');
|
||||
* $socket = new React\Socket\SocketServer('127.0.0.1:8000');
|
||||
* $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
|
||||
* ```
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param string $uri
|
||||
* @param array $context
|
||||
* @param ?LoopInterface $loop
|
||||
* @throws \InvalidArgumentException if the listening address is invalid
|
||||
* @throws \RuntimeException if listening on this address fails (already in use etc.)
|
||||
*/
|
||||
public function __construct($uri, array $context = array(), $loop = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
// apply default options if not explicitly given
|
||||
$context += array(
|
||||
'tcp' => array(),
|
||||
'tls' => array(),
|
||||
'unix' => array()
|
||||
);
|
||||
|
||||
$scheme = 'tcp';
|
||||
$pos = \strpos($uri, '://');
|
||||
if ($pos !== false) {
|
||||
$scheme = \substr($uri, 0, $pos);
|
||||
}
|
||||
|
||||
if ($scheme === 'unix') {
|
||||
$server = new UnixServer($uri, $loop, $context['unix']);
|
||||
} elseif ($scheme === 'php') {
|
||||
$server = new FdServer($uri, $loop);
|
||||
} else {
|
||||
if (preg_match('#^(?:\w+://)?\d+$#', $uri)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid URI given (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
);
|
||||
}
|
||||
|
||||
$server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
|
||||
|
||||
if ($scheme === 'tls') {
|
||||
$server = new SecureServer($server, $loop, $context['tls']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->server = $server;
|
||||
|
||||
$that = $this;
|
||||
$server->on('connection', function (ConnectionInterface $conn) use ($that) {
|
||||
$that->emit('connection', array($conn));
|
||||
});
|
||||
$server->on('error', function (\Exception $error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
});
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->server->getAddress();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->server->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->server->resume();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->server->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* [internal] Internal helper method to accept new connection from given server socket
|
||||
*
|
||||
* @param resource $socket server socket to accept connection from
|
||||
* @return resource new client socket if any
|
||||
* @throws \RuntimeException if accepting fails
|
||||
* @internal
|
||||
*/
|
||||
public static function accept($socket)
|
||||
{
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// stream_socket_accept(): accept failed: Connection timed out
|
||||
$errstr = \preg_replace('#.*: #', '', $error);
|
||||
$errno = SocketServer::errno($errstr);
|
||||
});
|
||||
|
||||
$newSocket = \stream_socket_accept($socket, 0);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if (false === $newSocket) {
|
||||
throw new \RuntimeException(
|
||||
'Unable to accept new connection: ' . $errstr . self::errconst($errno),
|
||||
$errno
|
||||
);
|
||||
}
|
||||
|
||||
return $newSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] Returns errno value for given errstr
|
||||
*
|
||||
* The errno and errstr values describes the type of error that has been
|
||||
* encountered. This method tries to look up the given errstr and find a
|
||||
* matching errno value which can be useful to provide more context to error
|
||||
* messages. It goes through the list of known errno constants when either
|
||||
* `ext-sockets`, `ext-posix` or `ext-pcntl` is available to find an errno
|
||||
* matching the given errstr.
|
||||
*
|
||||
* @param string $errstr
|
||||
* @return int errno value (e.g. value of `SOCKET_ECONNREFUSED`) or 0 if not found
|
||||
* @internal
|
||||
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function errno($errstr)
|
||||
{
|
||||
// PHP defines the required `strerror()` function through either `ext-sockets`, `ext-posix` or `ext-pcntl`
|
||||
$strerror = \function_exists('socket_strerror') ? 'socket_strerror' : (\function_exists('posix_strerror') ? 'posix_strerror' : (\function_exists('pcntl_strerror') ? 'pcntl_strerror' : null));
|
||||
if ($strerror !== null) {
|
||||
assert(\is_string($strerror) && \is_callable($strerror));
|
||||
|
||||
// PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
|
||||
// PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
|
||||
// go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errstr`
|
||||
foreach (\get_defined_constants(false) as $name => $value) {
|
||||
if (\is_int($value) && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0) && $strerror($value) === $errstr) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
|
||||
// go through list of all possible errno values from 1 to `MAX_ERRNO` and see if they match the given `$errstr`
|
||||
for ($errno = 1, $max = \defined('MAX_ERRNO') ? \MAX_ERRNO : 4095; $errno <= $max; ++$errno) {
|
||||
if ($strerror($errno) === $errstr) {
|
||||
return $errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach this, no matching errno value could be found (unlikely when either `ext-sockets`, `ext-posix` or `ext-pcntl` is available)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] Returns errno constant name for given errno value
|
||||
*
|
||||
* The errno value describes the type of error that has been encountered.
|
||||
* This method tries to look up the given errno value and find a matching
|
||||
* errno constant name which can be useful to provide more context and more
|
||||
* descriptive error messages. It goes through the list of known errno
|
||||
* constants when either `ext-sockets` or `ext-pcntl` is available to find
|
||||
* the matching errno constant name.
|
||||
*
|
||||
* Because this method is used to append more context to error messages, the
|
||||
* constant name will be prefixed with a space and put between parenthesis
|
||||
* when found.
|
||||
*
|
||||
* @param int $errno
|
||||
* @return string e.g. ` (ECONNREFUSED)` or empty string if no matching const for the given errno could be found
|
||||
* @internal
|
||||
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function errconst($errno)
|
||||
{
|
||||
// PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
|
||||
// PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
|
||||
// go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errno`
|
||||
foreach (\get_defined_constants(false) as $name => $value) {
|
||||
if ($value === $errno && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0)) {
|
||||
return ' (' . \substr($name, \strpos($name, '_') + 1) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
|
||||
return '';
|
||||
}
|
||||
}
|
||||
158
vendor/react/socket/src/StreamEncryption.php
vendored
Normal file
158
vendor/react/socket/src/StreamEncryption.php
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* This class is considered internal and its API should not be relied upon
|
||||
* outside of Socket.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StreamEncryption
|
||||
{
|
||||
private $loop;
|
||||
private $method;
|
||||
private $server;
|
||||
|
||||
public function __construct(LoopInterface $loop, $server = true)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
$this->server = $server;
|
||||
|
||||
// support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
|
||||
// As of PHP 7.2+ the main crypto method constant includes all TLS versions.
|
||||
// As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions.
|
||||
// For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions.
|
||||
// @link https://3v4l.org/9PSST
|
||||
if ($server) {
|
||||
$this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER;
|
||||
|
||||
if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
|
||||
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; // @codeCoverageIgnore
|
||||
}
|
||||
} else {
|
||||
$this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT;
|
||||
|
||||
if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
|
||||
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Connection $stream
|
||||
* @return \React\Promise\PromiseInterface<Connection>
|
||||
*/
|
||||
public function enable(Connection $stream)
|
||||
{
|
||||
return $this->toggle($stream, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Connection $stream
|
||||
* @param bool $toggle
|
||||
* @return \React\Promise\PromiseInterface<Connection>
|
||||
*/
|
||||
public function toggle(Connection $stream, $toggle)
|
||||
{
|
||||
// pause actual stream instance to continue operation on raw stream socket
|
||||
$stream->pause();
|
||||
|
||||
// TODO: add write() event to make sure we're not sending any excessive data
|
||||
|
||||
// cancelling this leaves this stream in an inconsistent state…
|
||||
$deferred = new Deferred(function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
// get actual stream socket from stream instance
|
||||
$socket = $stream->stream;
|
||||
|
||||
// get crypto method from context options or use global setting from constructor
|
||||
$method = $this->method;
|
||||
$context = \stream_context_get_options($socket);
|
||||
if (isset($context['ssl']['crypto_method'])) {
|
||||
$method = $context['ssl']['crypto_method'];
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
|
||||
$that->toggleCrypto($socket, $deferred, $toggle, $method);
|
||||
};
|
||||
|
||||
$this->loop->addReadStream($socket, $toggleCrypto);
|
||||
|
||||
if (!$this->server) {
|
||||
$toggleCrypto();
|
||||
}
|
||||
|
||||
$loop = $this->loop;
|
||||
|
||||
return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
|
||||
$loop->removeReadStream($socket);
|
||||
|
||||
$stream->encryptionEnabled = $toggle;
|
||||
$stream->resume();
|
||||
|
||||
return $stream;
|
||||
}, function($error) use ($stream, $socket, $loop) {
|
||||
$loop->removeReadStream($socket);
|
||||
$stream->resume();
|
||||
throw $error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param resource $socket
|
||||
* @param Deferred<null> $deferred
|
||||
* @param bool $toggle
|
||||
* @param int $method
|
||||
* @return void
|
||||
*/
|
||||
public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
|
||||
{
|
||||
$error = null;
|
||||
\set_error_handler(function ($_, $errstr) use (&$error) {
|
||||
$error = \str_replace(array("\r", "\n"), ' ', $errstr);
|
||||
|
||||
// remove useless function name from error message
|
||||
if (($pos = \strpos($error, "): ")) !== false) {
|
||||
$error = \substr($error, $pos + 3);
|
||||
}
|
||||
});
|
||||
|
||||
$result = \stream_socket_enable_crypto($socket, $toggle, $method);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if (true === $result) {
|
||||
$deferred->resolve(null);
|
||||
} else if (false === $result) {
|
||||
// overwrite callback arguments for PHP7+ only, so they do not show
|
||||
// up in the Exception trace and do not cause a possible cyclic reference.
|
||||
$d = $deferred;
|
||||
$deferred = null;
|
||||
|
||||
if (\feof($socket) || $error === null) {
|
||||
// EOF or failed without error => connection closed during handshake
|
||||
$d->reject(new \UnexpectedValueException(
|
||||
'Connection lost during TLS handshake (ECONNRESET)',
|
||||
\defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 104
|
||||
));
|
||||
} else {
|
||||
// handshake failed with error message
|
||||
$d->reject(new \UnexpectedValueException(
|
||||
$error
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// need more data, will retry
|
||||
}
|
||||
}
|
||||
}
|
||||
173
vendor/react/socket/src/TcpConnector.php
vendored
Normal file
173
vendor/react/socket/src/TcpConnector.php
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class TcpConnector implements ConnectorInterface
|
||||
{
|
||||
private $loop;
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
*/
|
||||
public function __construct($loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = \parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $uri . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$ip = \trim($parts['host'], '[]');
|
||||
if (@\inet_pton($ip) === false) {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
// use context given in constructor
|
||||
$context = array(
|
||||
'socket' => $this->context
|
||||
);
|
||||
|
||||
// parse arguments from query component of URI
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
\parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// If an original hostname has been given, use this for TLS setup.
|
||||
// This can happen due to layers of nested connectors, such as a
|
||||
// DnsConnector reporting its original hostname.
|
||||
// These context options are here in case TLS is enabled later on this stream.
|
||||
// If TLS is not enabled later, this doesn't hurt either.
|
||||
if (isset($args['hostname'])) {
|
||||
$context['ssl'] = array(
|
||||
'SNI_enabled' => true,
|
||||
'peer_name' => $args['hostname']
|
||||
);
|
||||
|
||||
// Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
|
||||
// The SNI_server_name context option has to be set here during construction,
|
||||
// as legacy PHP ignores any values set later.
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\PHP_VERSION_ID < 50600) {
|
||||
$context['ssl'] += array(
|
||||
'SNI_server_name' => $args['hostname'],
|
||||
'CN_match' => $args['hostname']
|
||||
);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// latest versions of PHP no longer accept any other URI components and
|
||||
// HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
|
||||
$remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
$stream = @\stream_socket_client(
|
||||
$remote,
|
||||
$errno,
|
||||
$errstr,
|
||||
0,
|
||||
\STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT,
|
||||
\stream_context_create($context)
|
||||
);
|
||||
|
||||
if (false === $stream) {
|
||||
return Promise\reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// wait for connection
|
||||
$loop = $this->loop;
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream, $uri) {
|
||||
$loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject, $uri) {
|
||||
$loop->removeWriteStream($stream);
|
||||
|
||||
// The following hack looks like the only way to
|
||||
// detect connection refused errors with PHP's stream sockets.
|
||||
if (false === \stream_socket_get_name($stream, true)) {
|
||||
// If we reach this point, we know the connection is dead, but we don't know the underlying error condition.
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\function_exists('socket_import_stream')) {
|
||||
// actual socket errno and errstr can be retrieved with ext-sockets on PHP 5.4+
|
||||
$socket = \socket_import_stream($stream);
|
||||
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
|
||||
$errstr = \socket_strerror($errno);
|
||||
} elseif (\PHP_OS === 'Linux') {
|
||||
// Linux reports socket errno and errstr again when trying to write to the dead socket.
|
||||
// Suppress error reporting to get error message below and close dead socket before rejecting.
|
||||
// This is only known to work on Linux, Mac and Windows are known to not support this.
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// fwrite(): send of 1 bytes failed with errno=111 Connection refused
|
||||
\preg_match('/errno=(\d+) (.+)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
\fwrite($stream, \PHP_EOL);
|
||||
|
||||
\restore_error_handler();
|
||||
} else {
|
||||
// Not on Linux and ext-sockets not available? Too bad.
|
||||
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
|
||||
$errstr = 'Connection refused?';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
\fclose($stream);
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
|
||||
$errno
|
||||
));
|
||||
} else {
|
||||
$resolve(new Connection($stream, $loop));
|
||||
}
|
||||
});
|
||||
}, function () use ($loop, $stream, $uri) {
|
||||
$loop->removeWriteStream($stream);
|
||||
\fclose($stream);
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
// legacy PHP 5.3 sometimes requires a second close call (see tests)
|
||||
if (\PHP_VERSION_ID < 50400 && \is_resource($stream)) {
|
||||
\fclose($stream);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled during TCP/IP handshake (ECONNABORTED)',
|
||||
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
262
vendor/react/socket/src/TcpServer.php
vendored
Normal file
262
vendor/react/socket/src/TcpServer.php
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* The `TcpServer` class implements the `ServerInterface` and
|
||||
* is responsible for accepting plaintext TCP/IP connections.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8080);
|
||||
* ```
|
||||
*
|
||||
* Whenever a client connects, it will emit a `connection` event with a connection
|
||||
* instance implementing `ConnectionInterface`:
|
||||
*
|
||||
* ```php
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
final class TcpServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $master;
|
||||
private $loop;
|
||||
private $listening = false;
|
||||
|
||||
/**
|
||||
* Creates a plaintext TCP/IP socket server and starts listening on the given address
|
||||
*
|
||||
* This starts accepting new incoming connections on the given address.
|
||||
* See also the `connection event` documented in the `ServerInterface`
|
||||
* for more details.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8080);
|
||||
* ```
|
||||
*
|
||||
* As above, the `$uri` parameter can consist of only a port, in which case the
|
||||
* server will default to listening on the localhost address `127.0.0.1`,
|
||||
* which means it will not be reachable from outside of this system.
|
||||
*
|
||||
* In order to use a random port assignment, you can use the port `0`:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(0);
|
||||
* $address = $server->getAddress();
|
||||
* ```
|
||||
*
|
||||
* In order to change the host the socket is listening on, you can provide an IP
|
||||
* address through the first parameter provided to the constructor, optionally
|
||||
* preceded by the `tcp://` scheme:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer('192.168.0.1:8080');
|
||||
* ```
|
||||
*
|
||||
* If you want to listen on an IPv6 address, you MUST enclose the host in square
|
||||
* brackets:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer('[::1]:8080');
|
||||
* ```
|
||||
*
|
||||
* If the given URI is invalid, does not contain a port, any other scheme or if it
|
||||
* contains a hostname, it will throw an `InvalidArgumentException`:
|
||||
*
|
||||
* ```php
|
||||
* // throws InvalidArgumentException due to missing port
|
||||
* $server = new React\Socket\TcpServer('127.0.0.1');
|
||||
* ```
|
||||
*
|
||||
* If the given URI appears to be valid, but listening on it fails (such as if port
|
||||
* is already in use or port below 1024 may require root access etc.), it will
|
||||
* throw a `RuntimeException`:
|
||||
*
|
||||
* ```php
|
||||
* $first = new React\Socket\TcpServer(8080);
|
||||
*
|
||||
* // throws RuntimeException because port is already in use
|
||||
* $second = new React\Socket\TcpServer(8080);
|
||||
* ```
|
||||
*
|
||||
* Note that these error conditions may vary depending on your system and/or
|
||||
* configuration.
|
||||
* See the exception message and code for more details about the actual error
|
||||
* condition.
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php)
|
||||
* for the underlying stream socket resource like this:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer('[::1]:8080', null, array(
|
||||
* 'backlog' => 200,
|
||||
* 'so_reuseport' => true,
|
||||
* 'ipv6_v6only' => true
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Note that available [socket context options](https://www.php.net/manual/en/context.socket.php),
|
||||
* their defaults and effects of changing these may vary depending on your system
|
||||
* and/or PHP version.
|
||||
* Passing unknown context options has no effect.
|
||||
* The `backlog` context option defaults to `511` unless given explicitly.
|
||||
*
|
||||
* @param string|int $uri
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
* @throws InvalidArgumentException if the listening address is invalid
|
||||
* @throws RuntimeException if listening on this address fails (already in use etc.)
|
||||
*/
|
||||
public function __construct($uri, $loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
|
||||
// a single port has been given => assume localhost
|
||||
if ((string)(int)$uri === (string)$uri) {
|
||||
$uri = '127.0.0.1:' . $uri;
|
||||
}
|
||||
|
||||
// assume default scheme if none has been given
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
// parse_url() does not accept null ports (random port assignment) => manually remove
|
||||
if (\substr($uri, -2) === ':0') {
|
||||
$parts = \parse_url(\substr($uri, 0, -2));
|
||||
if ($parts) {
|
||||
$parts['port'] = 0;
|
||||
}
|
||||
} else {
|
||||
$parts = \parse_url($uri);
|
||||
}
|
||||
|
||||
// ensure URI contains TCP scheme, host and port
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid URI "' . $uri . '" given (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
);
|
||||
}
|
||||
|
||||
if (@\inet_pton(\trim($parts['host'], '[]')) === false) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
);
|
||||
}
|
||||
|
||||
$this->master = @\stream_socket_server(
|
||||
$uri,
|
||||
$errno,
|
||||
$errstr,
|
||||
\STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
|
||||
\stream_context_create(array('socket' => $context + array('backlog' => 511)))
|
||||
);
|
||||
if (false === $this->master) {
|
||||
if ($errno === 0) {
|
||||
// PHP does not seem to report errno, so match errno from errstr
|
||||
// @link https://3v4l.org/3qOBl
|
||||
$errno = SocketServer::errno($errstr);
|
||||
}
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on "' . $uri . '": ' . $errstr . SocketServer::errconst($errno),
|
||||
$errno
|
||||
);
|
||||
}
|
||||
\stream_set_blocking($this->master, false);
|
||||
|
||||
$this->resume();
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$address = \stream_socket_get_name($this->master, false);
|
||||
|
||||
// check if this is an IPv6 address which includes multiple colons but no square brackets
|
||||
$pos = \strrpos($address, ':');
|
||||
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
|
||||
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return 'tcp://' . $address;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if (!$this->listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loop->removeReadStream($this->master);
|
||||
$this->listening = false;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->listening || !\is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$this->loop->addReadStream($this->master, function ($master) use ($that) {
|
||||
try {
|
||||
$newSocket = SocketServer::accept($master);
|
||||
} catch (\RuntimeException $e) {
|
||||
$that->emit('error', array($e));
|
||||
return;
|
||||
}
|
||||
$that->handleConnection($newSocket);
|
||||
});
|
||||
$this->listening = true;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pause();
|
||||
\fclose($this->master);
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection($socket)
|
||||
{
|
||||
$this->emit('connection', array(
|
||||
new Connection($socket, $this->loop)
|
||||
));
|
||||
}
|
||||
}
|
||||
79
vendor/react/socket/src/TimeoutConnector.php
vendored
Normal file
79
vendor/react/socket/src/TimeoutConnector.php
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class TimeoutConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $timeout;
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* @param ConnectorInterface $connector
|
||||
* @param float $timeout
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct(ConnectorInterface $connector, $timeout, $loop = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->connector = $connector;
|
||||
$this->timeout = $timeout;
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$promise = $this->connector->connect($uri);
|
||||
|
||||
$loop = $this->loop;
|
||||
$time = $this->timeout;
|
||||
return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $uri) {
|
||||
$timer = null;
|
||||
$promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
|
||||
if ($timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
$timer = false;
|
||||
$resolve($v);
|
||||
}, function ($v) use (&$timer, $loop, $reject) {
|
||||
if ($timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
$timer = false;
|
||||
$reject($v);
|
||||
});
|
||||
|
||||
// promise already resolved => no need to start timer
|
||||
if ($timer === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start timeout timer which will cancel the pending promise
|
||||
$timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $uri) {
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' timed out after ' . $time . ' seconds (ETIMEDOUT)',
|
||||
\defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110
|
||||
));
|
||||
|
||||
// Cancel pending connection to clean up any underlying resources and references.
|
||||
// Avoid garbage references in call stack by passing pending promise by reference.
|
||||
assert(\method_exists($promise, 'cancel'));
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
});
|
||||
}, function () use (&$promise) {
|
||||
// Cancelling this promise will cancel the pending connection, thus triggering the rejection logic above.
|
||||
// Avoid garbage references in call stack by passing pending promise by reference.
|
||||
assert(\method_exists($promise, 'cancel'));
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
58
vendor/react/socket/src/UnixConnector.php
vendored
Normal file
58
vendor/react/socket/src/UnixConnector.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Unix domain socket connector
|
||||
*
|
||||
* Unix domain sockets use atomic operations, so we can as well emulate
|
||||
* async behavior.
|
||||
*/
|
||||
final class UnixConnector implements ConnectorInterface
|
||||
{
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct($loop = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
}
|
||||
|
||||
public function connect($path)
|
||||
{
|
||||
if (\strpos($path, '://') === false) {
|
||||
$path = 'unix://' . $path;
|
||||
} elseif (\substr($path, 0, 7) !== 'unix://') {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $path . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$resource = @\stream_socket_client($path, $errno, $errstr, 1.0);
|
||||
|
||||
if (!$resource) {
|
||||
return Promise\reject(new \RuntimeException(
|
||||
'Unable to connect to unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
$connection = new Connection($resource, $this->loop);
|
||||
$connection->unix = true;
|
||||
|
||||
return Promise\resolve($connection);
|
||||
}
|
||||
}
|
||||
162
vendor/react/socket/src/UnixServer.php
vendored
Normal file
162
vendor/react/socket/src/UnixServer.php
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* The `UnixServer` class implements the `ServerInterface` and
|
||||
* is responsible for accepting plaintext connections on unix domain sockets.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
final class UnixServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $master;
|
||||
private $loop;
|
||||
private $listening = false;
|
||||
|
||||
/**
|
||||
* Creates a plaintext socket server and starts listening on the given unix socket
|
||||
*
|
||||
* This starts accepting new incoming connections on the given address.
|
||||
* See also the `connection event` documented in the `ServerInterface`
|
||||
* for more details.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
|
||||
* ```
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param string $path
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
* @throws InvalidArgumentException if the listening address is invalid
|
||||
* @throws RuntimeException if listening on this address fails (already in use etc.)
|
||||
*/
|
||||
public function __construct($path, $loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
|
||||
if (\strpos($path, '://') === false) {
|
||||
$path = 'unix://' . $path;
|
||||
} elseif (\substr($path, 0, 7) !== 'unix://') {
|
||||
throw new \InvalidArgumentException(
|
||||
'Given URI "' . $path . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
);
|
||||
}
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now.
|
||||
// This only applies to UDS server sockets, see also https://3v4l.org/NAhpr.
|
||||
// Parse PHP warning message containing unknown error, HHVM reports proper info at least.
|
||||
if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error, $match)) {
|
||||
$errstr = isset($match[3]) ? $match['3'] : $match[1];
|
||||
$errno = isset($match[2]) ? (int)$match[2] : 0;
|
||||
}
|
||||
});
|
||||
|
||||
$this->master = \stream_socket_server(
|
||||
$path,
|
||||
$errno,
|
||||
$errstr,
|
||||
\STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
|
||||
\stream_context_create(array('socket' => $context))
|
||||
);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if (false === $this->master) {
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on Unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
|
||||
$errno
|
||||
);
|
||||
}
|
||||
\stream_set_blocking($this->master, 0);
|
||||
|
||||
$this->resume();
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'unix://' . \stream_socket_get_name($this->master, false);
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if (!$this->listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loop->removeReadStream($this->master);
|
||||
$this->listening = false;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->listening || !is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$this->loop->addReadStream($this->master, function ($master) use ($that) {
|
||||
try {
|
||||
$newSocket = SocketServer::accept($master);
|
||||
} catch (\RuntimeException $e) {
|
||||
$that->emit('error', array($e));
|
||||
return;
|
||||
}
|
||||
$that->handleConnection($newSocket);
|
||||
});
|
||||
$this->listening = true;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pause();
|
||||
\fclose($this->master);
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection($socket)
|
||||
{
|
||||
$connection = new Connection($socket, $this->loop);
|
||||
$connection->unix = true;
|
||||
|
||||
$this->emit('connection', array(
|
||||
$connection
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user