Subir todo el proyecto incluyendo vendor y dependencias
This commit is contained in:
96
vendor/react/cache/CHANGELOG.md
vendored
Executable file
96
vendor/react/cache/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,96 @@
|
||||
# Changelog
|
||||
|
||||
## 1.2.0 (2022-11-30)
|
||||
|
||||
* Feature: Support PHP 8.1 and PHP 8.2.
|
||||
(#47 by @SimonFrings and #52 by @WyriHaximus)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#48 by @SimonFrings and #51 by @nhedger)
|
||||
|
||||
* Update test suite and use GitHub actions for continuous integration (CI).
|
||||
(#45 and #49 by @SimonFrings and #54 by @clue)
|
||||
|
||||
## 1.1.0 (2020-09-18)
|
||||
|
||||
* Feature: Forward compatibility with react/promise 3.
|
||||
(#39 by @WyriHaximus)
|
||||
|
||||
* Add `.gitattributes` to exclude dev files from exports.
|
||||
(#40 by @reedy)
|
||||
|
||||
* Improve test suite, update to support PHP 8 and PHPUnit 9.3.
|
||||
(#41 and #43 by @SimonFrings and #42 by @WyriHaximus)
|
||||
|
||||
## 1.0.0 (2019-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.6.0 release.
|
||||
|
||||
## 0.6.0 (2019-07-04)
|
||||
|
||||
* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()`
|
||||
supporting multiple cache items (inspired by PSR-16).
|
||||
(#32 by @krlv and #37 by @clue)
|
||||
|
||||
* Documentation for TTL precision with millisecond accuracy or below and
|
||||
use high-resolution timer for cache TTL on PHP 7.3+.
|
||||
(#35 and #38 by @clue)
|
||||
|
||||
* Improve API documentation and allow legacy HHVM to fail in Travis CI config.
|
||||
(#34 and #36 by @clue)
|
||||
|
||||
* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
|
||||
(#31 by @WyriHaximus)
|
||||
|
||||
## 0.5.0 (2018-06-25)
|
||||
|
||||
* Improve documentation by describing what is expected of a class implementing `CacheInterface`.
|
||||
(#21, #22, #23, #27 by @WyriHaximus)
|
||||
|
||||
* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`.
|
||||
(#26 by @clue)
|
||||
|
||||
* Added support for cache expiration (TTL).
|
||||
(#29 by @clue and @WyriHaximus)
|
||||
|
||||
* Renamed `remove` to `delete` making it more in line with `PSR-16`.
|
||||
(#30 by @clue)
|
||||
|
||||
## 0.4.2 (2017-12-20)
|
||||
|
||||
* Improve documentation with usage and installation instructions
|
||||
(#10 by @clue)
|
||||
|
||||
* Improve test suite by adding PHPUnit to `require-dev` and
|
||||
add forward compatibility with PHPUnit 5 and PHPUnit 6 and
|
||||
sanitize Composer autoload paths
|
||||
(#14 by @shaunbramley and #12 and #18 by @clue)
|
||||
|
||||
## 0.4.1 (2016-02-25)
|
||||
|
||||
* Repository maintenance, split off from main repo, improve test suite and documentation
|
||||
* First class support for PHP7 and HHVM (#9 by @clue)
|
||||
* Adjust compatibility to 5.3 (#7 by @clue)
|
||||
|
||||
## 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
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Feature: New cache component, used by DNS
|
||||
21
vendor/react/cache/LICENSE
vendored
Executable file
21
vendor/react/cache/LICENSE
vendored
Executable 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.
|
||||
367
vendor/react/cache/README.md
vendored
Executable file
367
vendor/react/cache/README.md
vendored
Executable file
@@ -0,0 +1,367 @@
|
||||
# Cache
|
||||
|
||||
[](https://github.com/reactphp/cache/actions)
|
||||
[](https://packagist.org/packages/react/cache)
|
||||
|
||||
Async, [Promise](https://github.com/reactphp/promise)-based cache interface
|
||||
for [ReactPHP](https://reactphp.org/).
|
||||
|
||||
The cache component provides a
|
||||
[Promise](https://github.com/reactphp/promise)-based
|
||||
[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
|
||||
implementation of that.
|
||||
This allows consumers to type hint against the interface and third parties to
|
||||
provide alternate implementations.
|
||||
This project is heavily inspired by
|
||||
[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/),
|
||||
but uses an interface more suited for async, non-blocking applications.
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
* [Usage](#usage)
|
||||
* [CacheInterface](#cacheinterface)
|
||||
* [get()](#get)
|
||||
* [set()](#set)
|
||||
* [delete()](#delete)
|
||||
* [getMultiple()](#getmultiple)
|
||||
* [setMultiple()](#setmultiple)
|
||||
* [deleteMultiple()](#deletemultiple)
|
||||
* [clear()](#clear)
|
||||
* [has()](#has)
|
||||
* [ArrayCache](#arraycache)
|
||||
* [Common usage](#common-usage)
|
||||
* [Fallback get](#fallback-get)
|
||||
* [Fallback-get-and-set](#fallback-get-and-set)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Usage
|
||||
|
||||
### CacheInterface
|
||||
|
||||
The `CacheInterface` describes the main interface of this component.
|
||||
This allows consumers to type hint against the interface and third parties to
|
||||
provide alternate implementations.
|
||||
|
||||
#### get()
|
||||
|
||||
The `get(string $key, mixed $default = null): PromiseInterface<mixed>` method can be used to
|
||||
retrieve an item from the cache.
|
||||
|
||||
This method will resolve with the cached value on success or with the
|
||||
given `$default` value when no item can be found or when an error occurs.
|
||||
Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
considered a cache miss.
|
||||
|
||||
```php
|
||||
$cache
|
||||
->get('foo')
|
||||
->then('var_dump');
|
||||
```
|
||||
|
||||
This example fetches the value of the key `foo` and passes it to the
|
||||
`var_dump` function. You can use any of the composition provided by
|
||||
[promises](https://github.com/reactphp/promise).
|
||||
|
||||
#### set()
|
||||
|
||||
The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface<bool>` method can be used to
|
||||
store an item in the cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. If the cache implementation has to go over the network to store
|
||||
it, it may take a while.
|
||||
|
||||
The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
for this cache item. If this parameter is omitted (or `null`), the item
|
||||
will stay in the cache for as long as the underlying implementation
|
||||
supports. Trying to access an expired cache item results in a cache miss,
|
||||
see also [`get()`](#get).
|
||||
|
||||
```php
|
||||
$cache->set('foo', 'bar', 60);
|
||||
```
|
||||
|
||||
This example eventually sets the value of the key `foo` to `bar`. If it
|
||||
already exists, it is overridden.
|
||||
|
||||
This interface does not enforce any particular TTL resolution, so special
|
||||
care may have to be taken if you rely on very high precision with
|
||||
millisecond accuracy or below. Cache implementations SHOULD work on a
|
||||
best effort basis and SHOULD provide at least second accuracy unless
|
||||
otherwise noted. Many existing cache implementations are known to provide
|
||||
microsecond or millisecond accuracy, but it's generally not recommended
|
||||
to rely on this high precision.
|
||||
|
||||
This interface suggests that cache implementations SHOULD use a monotonic
|
||||
time source if available. Given that a monotonic time source is only
|
||||
available as of PHP 7.3 by default, cache implementations MAY fall back
|
||||
to using wall-clock time.
|
||||
While this does not affect many common use cases, this is an important
|
||||
distinction for programs that rely on a high time precision or on systems
|
||||
that are subject to discontinuous time adjustments (time jumps).
|
||||
This means that if you store a cache item with a TTL of 30s and then
|
||||
adjust your system time forward by 20s, the cache item SHOULD still
|
||||
expire in 30s.
|
||||
|
||||
#### delete()
|
||||
|
||||
The `delete(string $key): PromiseInterface<bool>` method can be used to
|
||||
delete an item from the cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. When no item for `$key` is found in the cache, it also resolves
|
||||
to `true`. If the cache implementation has to go over the network to
|
||||
delete it, it may take a while.
|
||||
|
||||
```php
|
||||
$cache->delete('foo');
|
||||
```
|
||||
|
||||
This example eventually deletes the key `foo` from the cache. As with
|
||||
`set()`, this may not happen instantly and a promise is returned to
|
||||
provide guarantees whether or not the item has been removed from cache.
|
||||
|
||||
#### getMultiple()
|
||||
|
||||
The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface<array>` method can be used to
|
||||
retrieve multiple cache items by their unique keys.
|
||||
|
||||
This method will resolve with an array of cached values on success or with the
|
||||
given `$default` value when an item can not be found or when an error occurs.
|
||||
Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
considered a cache miss.
|
||||
|
||||
```php
|
||||
$cache->getMultiple(array('name', 'age'))->then(function (array $values) {
|
||||
$name = $values['name'] ?? 'User';
|
||||
$age = $values['age'] ?? 'n/a';
|
||||
|
||||
echo $name . ' is ' . $age . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This example fetches the cache items for the `name` and `age` keys and
|
||||
prints some example output. You can use any of the composition provided
|
||||
by [promises](https://github.com/reactphp/promise).
|
||||
|
||||
#### setMultiple()
|
||||
|
||||
The `setMultiple(array $values, ?float $ttl = null): PromiseInterface<bool>` method can be used to
|
||||
persist a set of key => value pairs in the cache, with an optional TTL.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. If the cache implementation has to go over the network to store
|
||||
it, it may take a while.
|
||||
|
||||
The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
for these cache items. If this parameter is omitted (or `null`), these items
|
||||
will stay in the cache for as long as the underlying implementation
|
||||
supports. Trying to access an expired cache items results in a cache miss,
|
||||
see also [`getMultiple()`](#getmultiple).
|
||||
|
||||
```php
|
||||
$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
|
||||
```
|
||||
|
||||
This example eventually sets the list of values - the key `foo` to `1` value
|
||||
and the key `bar` to `2`. If some of the keys already exist, they are overridden.
|
||||
|
||||
#### deleteMultiple()
|
||||
|
||||
The `setMultiple(string[] $keys): PromiseInterface<bool>` method can be used to
|
||||
delete multiple cache items in a single operation.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. When no items for `$keys` are found in the cache, it also resolves
|
||||
to `true`. If the cache implementation has to go over the network to
|
||||
delete it, it may take a while.
|
||||
|
||||
```php
|
||||
$cache->deleteMultiple(array('foo', 'bar, 'baz'));
|
||||
```
|
||||
|
||||
This example eventually deletes keys `foo`, `bar` and `baz` from the cache.
|
||||
As with `setMultiple()`, this may not happen instantly and a promise is returned to
|
||||
provide guarantees whether or not the item has been removed from cache.
|
||||
|
||||
#### clear()
|
||||
|
||||
The `clear(): PromiseInterface<bool>` method can be used to
|
||||
wipe clean the entire cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. If the cache implementation has to go over the network to
|
||||
delete it, it may take a while.
|
||||
|
||||
```php
|
||||
$cache->clear();
|
||||
```
|
||||
|
||||
This example eventually deletes all keys from the cache. As with `deleteMultiple()`,
|
||||
this may not happen instantly and a promise is returned to provide guarantees
|
||||
whether or not all the items have been removed from cache.
|
||||
|
||||
#### has()
|
||||
|
||||
The `has(string $key): PromiseInterface<bool>` method can be used to
|
||||
determine whether an item is present in the cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when no item can be found
|
||||
or when an error occurs. Similarly, an expired cache item (once the time-to-live
|
||||
is expired) is considered a cache miss.
|
||||
|
||||
```php
|
||||
$cache
|
||||
->has('foo')
|
||||
->then('var_dump');
|
||||
```
|
||||
|
||||
This example checks if the value of the key `foo` is set in the cache and passes
|
||||
the result to the `var_dump` function. You can use any of the composition provided by
|
||||
[promises](https://github.com/reactphp/promise).
|
||||
|
||||
NOTE: It is recommended that has() is only to be used for cache warming type purposes
|
||||
and not to be used within your live applications operations for get/set, as this method
|
||||
is subject to a race condition where your has() will return true and immediately after,
|
||||
another script can remove it making the state of your app out of date.
|
||||
|
||||
### ArrayCache
|
||||
|
||||
The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
|
||||
|
||||
```php
|
||||
$cache = new ArrayCache();
|
||||
|
||||
$cache->set('foo', 'bar');
|
||||
```
|
||||
|
||||
Its constructor accepts an optional `?int $limit` parameter to limit the
|
||||
maximum number of entries to store in the LRU cache. If you add more
|
||||
entries to this instance, it will automatically take care of removing
|
||||
the one that was least recently used (LRU).
|
||||
|
||||
For example, this snippet will overwrite the first value and only store
|
||||
the last two entries:
|
||||
|
||||
```php
|
||||
$cache = new ArrayCache(2);
|
||||
|
||||
$cache->set('foo', '1');
|
||||
$cache->set('bar', '2');
|
||||
$cache->set('baz', '3');
|
||||
```
|
||||
|
||||
This cache implementation is known to rely on wall-clock time to schedule
|
||||
future cache expiration times when using any version before PHP 7.3,
|
||||
because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
|
||||
While this does not affect many common use cases, this is an important
|
||||
distinction for programs that rely on a high time precision or on systems
|
||||
that are subject to discontinuous time adjustments (time jumps).
|
||||
This means that if you store a cache item with a TTL of 30s on PHP < 7.3
|
||||
and then adjust your system time forward by 20s, the cache item may
|
||||
expire in 10s. See also [`set()`](#set) for more details.
|
||||
|
||||
## Common usage
|
||||
|
||||
### Fallback get
|
||||
|
||||
A common use case of caches is to attempt fetching a cached value and as a
|
||||
fallback retrieve it from the original data source if not found. Here is an
|
||||
example of that:
|
||||
|
||||
```php
|
||||
$cache
|
||||
->get('foo')
|
||||
->then(function ($result) {
|
||||
if ($result === null) {
|
||||
return getFooFromDb();
|
||||
}
|
||||
|
||||
return $result;
|
||||
})
|
||||
->then('var_dump');
|
||||
```
|
||||
|
||||
First an attempt is made to retrieve the value of `foo`. A callback function is
|
||||
registered that will call `getFooFromDb` when the resulting value is null.
|
||||
`getFooFromDb` is a function (can be any PHP callable) that will be called if the
|
||||
key does not exist in the cache.
|
||||
|
||||
`getFooFromDb` can handle the missing key by returning a promise for the
|
||||
actual value from the database (or any other data source). As a result, this
|
||||
chain will correctly fall back, and provide the value in both cases.
|
||||
|
||||
### Fallback get and set
|
||||
|
||||
To expand on the fallback get example, often you want to set the value on the
|
||||
cache after fetching it from the data source.
|
||||
|
||||
```php
|
||||
$cache
|
||||
->get('foo')
|
||||
->then(function ($result) {
|
||||
if ($result === null) {
|
||||
return $this->getAndCacheFooFromDb();
|
||||
}
|
||||
|
||||
return $result;
|
||||
})
|
||||
->then('var_dump');
|
||||
|
||||
public function getAndCacheFooFromDb()
|
||||
{
|
||||
return $this->db
|
||||
->get('foo')
|
||||
->then(array($this, 'cacheFooFromDb'));
|
||||
}
|
||||
|
||||
public function cacheFooFromDb($foo)
|
||||
{
|
||||
$this->cache->set('foo', $foo);
|
||||
|
||||
return $foo;
|
||||
}
|
||||
```
|
||||
|
||||
By using chaining you can easily conditionally cache the value if it is
|
||||
fetched from the database.
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](https://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/cache:^1.2
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use PHP 7+* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
45
vendor/react/cache/composer.json
vendored
Executable file
45
vendor/react/cache/composer.json
vendored
Executable file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "react/cache",
|
||||
"description": "Async, Promise-based cache interface for ReactPHP",
|
||||
"keywords": ["cache", "caching", "promise", "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",
|
||||
"react/promise": "^3.0 || ^2.0 || ^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Cache\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Cache\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
181
vendor/react/cache/src/ArrayCache.php
vendored
Executable file
181
vendor/react/cache/src/ArrayCache.php
vendored
Executable file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace React\Cache;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class ArrayCache implements CacheInterface
|
||||
{
|
||||
private $limit;
|
||||
private $data = array();
|
||||
private $expires = array();
|
||||
private $supportsHighResolution;
|
||||
|
||||
/**
|
||||
* The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
|
||||
*
|
||||
* ```php
|
||||
* $cache = new ArrayCache();
|
||||
*
|
||||
* $cache->set('foo', 'bar');
|
||||
* ```
|
||||
*
|
||||
* Its constructor accepts an optional `?int $limit` parameter to limit the
|
||||
* maximum number of entries to store in the LRU cache. If you add more
|
||||
* entries to this instance, it will automatically take care of removing
|
||||
* the one that was least recently used (LRU).
|
||||
*
|
||||
* For example, this snippet will overwrite the first value and only store
|
||||
* the last two entries:
|
||||
*
|
||||
* ```php
|
||||
* $cache = new ArrayCache(2);
|
||||
*
|
||||
* $cache->set('foo', '1');
|
||||
* $cache->set('bar', '2');
|
||||
* $cache->set('baz', '3');
|
||||
* ```
|
||||
*
|
||||
* This cache implementation is known to rely on wall-clock time to schedule
|
||||
* future cache expiration times when using any version before PHP 7.3,
|
||||
* because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
|
||||
* While this does not affect many common use cases, this is an important
|
||||
* distinction for programs that rely on a high time precision or on systems
|
||||
* that are subject to discontinuous time adjustments (time jumps).
|
||||
* This means that if you store a cache item with a TTL of 30s on PHP < 7.3
|
||||
* and then adjust your system time forward by 20s, the cache item may
|
||||
* expire in 10s. See also [`set()`](#set) for more details.
|
||||
*
|
||||
* @param int|null $limit maximum number of entries to store in the LRU cache
|
||||
*/
|
||||
public function __construct($limit = null)
|
||||
{
|
||||
$this->limit = $limit;
|
||||
|
||||
// prefer high-resolution timer, available as of PHP 7.3+
|
||||
$this->supportsHighResolution = \function_exists('hrtime');
|
||||
}
|
||||
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
// delete key if it is already expired => below will detect this as a cache miss
|
||||
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($key, $this->data)) {
|
||||
return Promise\resolve($default);
|
||||
}
|
||||
|
||||
// remove and append to end of array to keep track of LRU info
|
||||
$value = $this->data[$key];
|
||||
unset($this->data[$key]);
|
||||
$this->data[$key] = $value;
|
||||
|
||||
return Promise\resolve($value);
|
||||
}
|
||||
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
// unset before setting to ensure this entry will be added to end of array (LRU info)
|
||||
unset($this->data[$key]);
|
||||
$this->data[$key] = $value;
|
||||
|
||||
// sort expiration times if TTL is given (first will expire first)
|
||||
unset($this->expires[$key]);
|
||||
if ($ttl !== null) {
|
||||
$this->expires[$key] = $this->now() + $ttl;
|
||||
\asort($this->expires);
|
||||
}
|
||||
|
||||
// ensure size limit is not exceeded or remove first entry from array
|
||||
if ($this->limit !== null && \count($this->data) > $this->limit) {
|
||||
// first try to check if there's any expired entry
|
||||
// expiration times are sorted, so we can simply look at the first one
|
||||
\reset($this->expires);
|
||||
$key = \key($this->expires);
|
||||
|
||||
// check to see if the first in the list of expiring keys is already expired
|
||||
// if the first key is not expired, we have to overwrite by using LRU info
|
||||
if ($key === null || $this->now() - $this->expires[$key] < 0) {
|
||||
\reset($this->data);
|
||||
$key = \key($this->data);
|
||||
}
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function delete($key)
|
||||
{
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function getMultiple(array $keys, $default = null)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$values[$key] = $this->get($key, $default);
|
||||
}
|
||||
|
||||
return Promise\all($values);
|
||||
}
|
||||
|
||||
public function setMultiple(array $values, $ttl = null)
|
||||
{
|
||||
foreach ($values as $key => $value) {
|
||||
$this->set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function deleteMultiple(array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->data = array();
|
||||
$this->expires = array();
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
// delete key if it is already expired
|
||||
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($key, $this->data)) {
|
||||
return Promise\resolve(false);
|
||||
}
|
||||
|
||||
// remove and append to end of array to keep track of LRU info
|
||||
$value = $this->data[$key];
|
||||
unset($this->data[$key]);
|
||||
$this->data[$key] = $value;
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
private function now()
|
||||
{
|
||||
return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
|
||||
}
|
||||
}
|
||||
194
vendor/react/cache/src/CacheInterface.php
vendored
Executable file
194
vendor/react/cache/src/CacheInterface.php
vendored
Executable file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace React\Cache;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
interface CacheInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves an item from the cache.
|
||||
*
|
||||
* This method will resolve with the cached value on success or with the
|
||||
* given `$default` value when no item can be found or when an error occurs.
|
||||
* Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
* considered a cache miss.
|
||||
*
|
||||
* ```php
|
||||
* $cache
|
||||
* ->get('foo')
|
||||
* ->then('var_dump');
|
||||
* ```
|
||||
*
|
||||
* This example fetches the value of the key `foo` and passes it to the
|
||||
* `var_dump` function. You can use any of the composition provided by
|
||||
* [promises](https://github.com/reactphp/promise).
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default Default value to return for cache miss or null if not given.
|
||||
* @return PromiseInterface<mixed>
|
||||
*/
|
||||
public function get($key, $default = null);
|
||||
|
||||
/**
|
||||
* Stores an item in the cache.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when an error
|
||||
* occurs. If the cache implementation has to go over the network to store
|
||||
* it, it may take a while.
|
||||
*
|
||||
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
* for this cache item. If this parameter is omitted (or `null`), the item
|
||||
* will stay in the cache for as long as the underlying implementation
|
||||
* supports. Trying to access an expired cache item results in a cache miss,
|
||||
* see also [`get()`](#get).
|
||||
*
|
||||
* ```php
|
||||
* $cache->set('foo', 'bar', 60);
|
||||
* ```
|
||||
*
|
||||
* This example eventually sets the value of the key `foo` to `bar`. If it
|
||||
* already exists, it is overridden.
|
||||
*
|
||||
* This interface does not enforce any particular TTL resolution, so special
|
||||
* care may have to be taken if you rely on very high precision with
|
||||
* millisecond accuracy or below. Cache implementations SHOULD work on a
|
||||
* best effort basis and SHOULD provide at least second accuracy unless
|
||||
* otherwise noted. Many existing cache implementations are known to provide
|
||||
* microsecond or millisecond accuracy, but it's generally not recommended
|
||||
* to rely on this high precision.
|
||||
*
|
||||
* This interface suggests that cache implementations SHOULD use a monotonic
|
||||
* time source if available. Given that a monotonic time source is only
|
||||
* available as of PHP 7.3 by default, cache implementations MAY fall back
|
||||
* to using wall-clock time.
|
||||
* While this does not affect many common use cases, this is an important
|
||||
* distinction for programs that rely on a high time precision or on systems
|
||||
* that are subject to discontinuous time adjustments (time jumps).
|
||||
* This means that if you store a cache item with a TTL of 30s and then
|
||||
* adjust your system time forward by 20s, the cache item SHOULD still
|
||||
* expire in 30s.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param ?float $ttl
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function set($key, $value, $ttl = null);
|
||||
|
||||
/**
|
||||
* Deletes an item from the cache.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when an error
|
||||
* occurs. When no item for `$key` is found in the cache, it also resolves
|
||||
* to `true`. If the cache implementation has to go over the network to
|
||||
* delete it, it may take a while.
|
||||
*
|
||||
* ```php
|
||||
* $cache->delete('foo');
|
||||
* ```
|
||||
*
|
||||
* This example eventually deletes the key `foo` from the cache. As with
|
||||
* `set()`, this may not happen instantly and a promise is returned to
|
||||
* provide guarantees whether or not the item has been removed from cache.
|
||||
*
|
||||
* @param string $key
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function delete($key);
|
||||
|
||||
/**
|
||||
* Retrieves multiple cache items by their unique keys.
|
||||
*
|
||||
* This method will resolve with an array of cached values on success or with the
|
||||
* given `$default` value when an item can not be found or when an error occurs.
|
||||
* Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
* considered a cache miss.
|
||||
*
|
||||
* ```php
|
||||
* $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
|
||||
* $name = $values['name'] ?? 'User';
|
||||
* $age = $values['age'] ?? 'n/a';
|
||||
*
|
||||
* echo $name . ' is ' . $age . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This example fetches the cache items for the `name` and `age` keys and
|
||||
* prints some example output. You can use any of the composition provided
|
||||
* by [promises](https://github.com/reactphp/promise).
|
||||
*
|
||||
* @param string[] $keys A list of keys that can obtained in a single operation.
|
||||
* @param mixed $default Default value to return for keys that do not exist.
|
||||
* @return PromiseInterface<array> Returns a promise which resolves to an `array` of cached values
|
||||
*/
|
||||
public function getMultiple(array $keys, $default = null);
|
||||
|
||||
/**
|
||||
* Persists a set of key => value pairs in the cache, with an optional TTL.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when an error
|
||||
* occurs. If the cache implementation has to go over the network to store
|
||||
* it, it may take a while.
|
||||
*
|
||||
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
* for these cache items. If this parameter is omitted (or `null`), these items
|
||||
* will stay in the cache for as long as the underlying implementation
|
||||
* supports. Trying to access an expired cache items results in a cache miss,
|
||||
* see also [`get()`](#get).
|
||||
*
|
||||
* ```php
|
||||
* $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
|
||||
* ```
|
||||
*
|
||||
* This example eventually sets the list of values - the key `foo` to 1 value
|
||||
* and the key `bar` to 2. If some of the keys already exist, they are overridden.
|
||||
*
|
||||
* @param array $values A list of key => value pairs for a multiple-set operation.
|
||||
* @param ?float $ttl Optional. The TTL value of this item.
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function setMultiple(array $values, $ttl = null);
|
||||
|
||||
/**
|
||||
* Deletes multiple cache items in a single operation.
|
||||
*
|
||||
* @param string[] $keys A list of string-based keys to be deleted.
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function deleteMultiple(array $keys);
|
||||
|
||||
/**
|
||||
* Wipes clean the entire cache.
|
||||
*
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function clear();
|
||||
|
||||
/**
|
||||
* Determines whether an item is present in the cache.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when no item can be found
|
||||
* or when an error occurs. Similarly, an expired cache item (once the time-to-live
|
||||
* is expired) is considered a cache miss.
|
||||
*
|
||||
* ```php
|
||||
* $cache
|
||||
* ->has('foo')
|
||||
* ->then('var_dump');
|
||||
* ```
|
||||
*
|
||||
* This example checks if the value of the key `foo` is set in the cache and passes
|
||||
* the result to the `var_dump` function. You can use any of the composition provided by
|
||||
* [promises](https://github.com/reactphp/promise).
|
||||
*
|
||||
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
|
||||
* and not to be used within your live applications operations for get/set, as this method
|
||||
* is subject to a race condition where your has() will return true and immediately after,
|
||||
* another script can remove it making the state of your app out of date.
|
||||
*
|
||||
* @param string $key The cache item key.
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function has($key);
|
||||
}
|
||||
176
vendor/react/child-process/CHANGELOG.md
vendored
Executable file
176
vendor/react/child-process/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,176 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.6 (2025-01-01)
|
||||
|
||||
This is a compatibility release that contains backported features from the `0.7.x` branch.
|
||||
Once v0.7 is released, it will be the way forward for this project.
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable types.
|
||||
(#114 by @clue)
|
||||
|
||||
* Improve test suite to run tests on latest PHP versions and report failed assertions.
|
||||
(#113 by @clue)
|
||||
|
||||
## 0.6.5 (2022-09-16)
|
||||
|
||||
* Feature: Full support for PHP 8.1 and PHP 8.2 release.
|
||||
(#91 by @SimonFrings and #99 by @WyriHaximus)
|
||||
|
||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
||||
(#94 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#92 by @SimonFrings and #95 by @nhedger)
|
||||
|
||||
* Improve test suite, skip failing tests on bugged versions and fix legacy HHVM build.
|
||||
(#96 and #98 by @clue and #93 by @SimonFrings)
|
||||
|
||||
## 0.6.4 (2021-10-12)
|
||||
|
||||
* Feature / Fix: Skip sigchild check if `phpinfo()` has been disabled.
|
||||
(#89 by @clue)
|
||||
|
||||
* Fix: Fix detecting closed socket pipes on PHP 8.
|
||||
(#90 by @clue)
|
||||
|
||||
## 0.6.3 (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).
|
||||
(#87 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$process = new React\ChildProcess\Process($command);
|
||||
$process->start($loop);
|
||||
|
||||
// new (using default loop)
|
||||
$process = new React\ChildProcess\Process($command);
|
||||
$process->start();
|
||||
```
|
||||
|
||||
## 0.6.2 (2021-02-05)
|
||||
|
||||
* Feature: Support PHP 8 and add non-blocking I/O support on Windows with PHP 8.
|
||||
(#85 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#78 by @WyriHaximus and #80 by @gdejong)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Run tests on PHPUnit 9, switch to GitHub actions and clean up test suite.
|
||||
(#75 by @reedy, #81 by @gdejong, #82 by @SimonFrings and #84 by @clue)
|
||||
|
||||
## 0.6.1 (2019-02-15)
|
||||
|
||||
* Feature / Fix: Improve error reporting when spawning child process fails.
|
||||
(#73 by @clue)
|
||||
|
||||
## 0.6.0 (2019-01-14)
|
||||
|
||||
A major feature release with some minor API improvements!
|
||||
This project now has limited Windows support and supports passing custom pipes
|
||||
and file descriptors to the child process.
|
||||
|
||||
This update involves a few minor BC breaks. We've tried hard to avoid BC breaks
|
||||
where possible and minimize impact otherwise. We expect that most consumers of
|
||||
this package will actually not be affected by any BC breaks, see below for more
|
||||
details.
|
||||
|
||||
* Feature / BC break: Support passing custom pipes and file descriptors to child process,
|
||||
expose all standard I/O pipes in an array and remove unused Windows-only options.
|
||||
(#62, #64 and #65 by @clue)
|
||||
|
||||
> BC note: The optional `$options` parameter in the `Process` constructor
|
||||
has been removed and a new `$fds` parameter has been added instead. The
|
||||
previous `$options` parameter was Windows-only, available options were not
|
||||
documented or referenced anywhere else in this library, so its actual
|
||||
impact is expected to be relatively small. See the documentation and the
|
||||
following changelog entry if you're looking for Windows support.
|
||||
|
||||
* Feature: Support spawning child process on Windows without process I/O pipes.
|
||||
(#67 by @clue)
|
||||
|
||||
* Feature / BC break: Improve sigchild compatibility and support explicit configuration.
|
||||
(#63 by @clue)
|
||||
|
||||
```php
|
||||
// advanced: not recommended by default
|
||||
Process::setSigchildEnabled(true);
|
||||
```
|
||||
|
||||
> BC note: The old public sigchild methods have been removed, but its
|
||||
practical impact is believed to be relatively small due to the automatic detection.
|
||||
|
||||
* Improve performance by prefixing all global functions calls with \ to skip
|
||||
the look up and resolve process and go straight to the global function.
|
||||
(#68 by @WyriHaximus)
|
||||
|
||||
* Minor documentation improvements and docblock updates.
|
||||
(#59 by @iamluc and #69 by @CharlotteDunois)
|
||||
|
||||
* Improve test suite to test against PHP7.2 and PHP 7.3, improve HHVM compatibility,
|
||||
add forward compatibility with PHPUnit 7 and run tests on Windows via Travis CI.
|
||||
(#66 and #71 by @clue)
|
||||
|
||||
## 0.5.2 (2018-01-18)
|
||||
|
||||
* Feature: Detect "exit" immediately if last process pipe is closed
|
||||
(#58 by @clue)
|
||||
|
||||
This introduces a simple check to see if the program is already known to be
|
||||
closed when the last process pipe is closed instead of relying on a periodic
|
||||
timer. This simple change improves "exit" detection significantly for most
|
||||
programs and does not cause a noticeable penalty for more advanced use cases.
|
||||
|
||||
* Fix forward compatibility with upcoming EventLoop releases
|
||||
(#56 by @clue)
|
||||
|
||||
## 0.5.1 (2017-12-22)
|
||||
|
||||
* Fix: Update Stream dependency to work around SEGFAULT in legacy PHP < 5.4.28
|
||||
and PHP < 5.5.12
|
||||
(#50 and #52 by @clue)
|
||||
|
||||
* Improve test suite by simplifying test bootstrapping logic via Composer and
|
||||
adding forward compatibility with PHPUnit 6
|
||||
(#53, #54 and #55 by @clue)
|
||||
|
||||
## 0.5.0 (2017-08-15)
|
||||
|
||||
* Forward compatibility: react/event-loop 1.0 and 0.5, react/stream 0.7.2 and 1.0, and Événement 3.0
|
||||
(#38 and #44 by @WyriHaximus, and #46 by @clue)
|
||||
* Windows compatibility: Documentate that windows isn't supported in 0.5 unless used from within WSL
|
||||
(#41 and #47 by @WyriHaximus)
|
||||
* Documentation: Termination examples
|
||||
(#42 by @clue)
|
||||
* BC: Throw LogicException in Process instanciating when on Windows or when proc_open is missing (was `RuntimeException`)
|
||||
(#49 by @mdrost)
|
||||
|
||||
## 0.4.3 (2017-03-14)
|
||||
|
||||
* Ease getting started by improving documentation and adding examples
|
||||
(#33 and #34 by @clue)
|
||||
|
||||
* First class support for PHP 5.3 through PHP 7.1 and HHVM
|
||||
(#29 by @clue and #32 by @WyriHaximus)
|
||||
|
||||
## 0.4.2 (2017-03-10)
|
||||
|
||||
* Feature: Forward compatibility with Stream v0.5
|
||||
(#26 by @clue)
|
||||
|
||||
* Improve test suite by removing AppVeyor and adding PHPUnit to `require-dev`
|
||||
(#27 and #28 by @clue)
|
||||
|
||||
## 0.4.1 (2016-08-01)
|
||||
|
||||
* Standalone component
|
||||
* Test against PHP 7 and HHVM, report test coverage, AppVeyor tests
|
||||
* Wait for stdout and stderr to close before watching for process exit
|
||||
(#18 by @mbonneau)
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* Feature: Added ChildProcess to run async child processes within the event loop (@jmikola)
|
||||
21
vendor/react/child-process/LICENSE
vendored
Executable file
21
vendor/react/child-process/LICENSE
vendored
Executable 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.
|
||||
619
vendor/react/child-process/README.md
vendored
Executable file
619
vendor/react/child-process/README.md
vendored
Executable file
@@ -0,0 +1,619 @@
|
||||
# ChildProcess
|
||||
|
||||
[](https://github.com/reactphp/child-process/actions)
|
||||
[](https://packagist.org/packages/react/child-process)
|
||||
|
||||
Event-driven library for executing child processes with
|
||||
[ReactPHP](https://reactphp.org/).
|
||||
|
||||
This library integrates [Program Execution](http://php.net/manual/en/book.exec.php)
|
||||
with the [EventLoop](https://github.com/reactphp/event-loop).
|
||||
Child processes launched may be signaled and will emit an
|
||||
`exit` event upon termination.
|
||||
Additionally, process I/O streams (i.e. STDIN, STDOUT, STDERR) are exposed
|
||||
as [Streams](https://github.com/reactphp/stream).
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Process](#process)
|
||||
* [Stream Properties](#stream-properties)
|
||||
* [Command](#command)
|
||||
* [Termination](#termination)
|
||||
* [Custom pipes](#custom-pipes)
|
||||
* [Sigchild Compatibility](#sigchild-compatibility)
|
||||
* [Windows Compatibility](#windows-compatibility)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Quickstart example
|
||||
|
||||
```php
|
||||
$process = new React\ChildProcess\Process('echo foo');
|
||||
$process->start();
|
||||
|
||||
$process->stdout->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
|
||||
$process->on('exit', function($exitCode, $termSignal) {
|
||||
echo 'Process exited with code ' . $exitCode . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Process
|
||||
|
||||
### Stream Properties
|
||||
|
||||
Once a process is started, its I/O streams will be constructed as instances of
|
||||
`React\Stream\ReadableStreamInterface` and `React\Stream\WritableStreamInterface`.
|
||||
Before `start()` is called, these properties are not set. Once a process terminates,
|
||||
the streams will become closed but not unset.
|
||||
|
||||
Following common Unix conventions, this library will start each child process
|
||||
with the three pipes matching the standard I/O streams as given below by default.
|
||||
You can use the named references for common use cases or access these as an
|
||||
array with all three pipes.
|
||||
|
||||
* `$stdin` or `$pipes[0]` is a `WritableStreamInterface`
|
||||
* `$stdout` or `$pipes[1]` is a `ReadableStreamInterface`
|
||||
* `$stderr` or `$pipes[2]` is a `ReadableStreamInterface`
|
||||
|
||||
Note that this default configuration may be overridden by explicitly passing
|
||||
[custom pipes](#custom-pipes), in which case they may not be set or be assigned
|
||||
different values. In particular, note that [Windows support](#windows-compatibility)
|
||||
is limited in that it doesn't support non-blocking STDIO pipes. The `$pipes`
|
||||
array will always contain references to all pipes as configured and the standard
|
||||
I/O references will always be set to reference the pipes matching the above
|
||||
conventions. See [custom pipes](#custom-pipes) for more details.
|
||||
|
||||
Because each of these implement the underlying
|
||||
[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) or
|
||||
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface),
|
||||
you can use any of their events and methods as usual:
|
||||
|
||||
```php
|
||||
$process = new Process($command);
|
||||
$process->start();
|
||||
|
||||
$process->stdout->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
|
||||
$process->stdout->on('end', function () {
|
||||
echo 'ended';
|
||||
});
|
||||
|
||||
$process->stdout->on('error', function (Exception $e) {
|
||||
echo 'error: ' . $e->getMessage();
|
||||
});
|
||||
|
||||
$process->stdout->on('close', function () {
|
||||
echo 'closed';
|
||||
});
|
||||
|
||||
$process->stdin->write($data);
|
||||
$process->stdin->end($data = null);
|
||||
// …
|
||||
```
|
||||
|
||||
For more details, see the
|
||||
[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) and
|
||||
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface).
|
||||
|
||||
### Command
|
||||
|
||||
The `Process` class allows you to pass any kind of command line string:
|
||||
|
||||
```php
|
||||
$process = new Process('echo test');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
The command line string usually consists of a whitespace-separated list with
|
||||
your main executable bin and any number of arguments. Special care should be
|
||||
taken to escape or quote any arguments, escpecially if you pass any user input
|
||||
along. Likewise, keep in mind that especially on Windows, it is rather common to
|
||||
have path names containing spaces and other special characters. If you want to
|
||||
run a binary like this, you will have to ensure this is quoted as a single
|
||||
argument using `escapeshellarg()` like this:
|
||||
|
||||
```php
|
||||
$bin = 'C:\\Program files (x86)\\PHP\\php.exe';
|
||||
$file = 'C:\\Users\\me\\Desktop\\Application\\main.php';
|
||||
|
||||
$process = new Process(escapeshellarg($bin) . ' ' . escapeshellarg($file));
|
||||
$process->start();
|
||||
```
|
||||
|
||||
By default, PHP will launch processes by wrapping the given command line string
|
||||
in a `sh` command on Unix, so that the first example will actually execute
|
||||
`sh -c echo test` under the hood on Unix. On Windows, it will not launch
|
||||
processes by wrapping them in a shell.
|
||||
|
||||
This is a very useful feature because it does not only allow you to pass single
|
||||
commands, but actually allows you to pass any kind of shell command line and
|
||||
launch multiple sub-commands using command chains (with `&&`, `||`, `;` and
|
||||
others) and allows you to redirect STDIO streams (with `2>&1` and family).
|
||||
This can be used to pass complete command lines and receive the resulting STDIO
|
||||
streams from the wrapping shell command like this:
|
||||
|
||||
```php
|
||||
$process = new Process('echo run && demo || echo failed');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
> Note that [Windows support](#windows-compatibility) is limited in that it
|
||||
doesn't support STDIO streams at all and also that processes will not be run
|
||||
in a wrapping shell by default. If you want to run a shell built-in function
|
||||
such as `echo hello` or `sleep 10`, you may have to prefix your command line
|
||||
with an explicit shell like `cmd /c echo hello`.
|
||||
|
||||
In other words, the underlying shell is responsible for managing this command
|
||||
line and launching the individual sub-commands and connecting their STDIO
|
||||
streams as appropriate.
|
||||
This implies that the `Process` class will only receive the resulting STDIO
|
||||
streams from the wrapping shell, which will thus contain the complete
|
||||
input/output with no way to discern the input/output of single sub-commands.
|
||||
|
||||
If you want to discern the output of single sub-commands, you may want to
|
||||
implement some higher-level protocol logic, such as printing an explicit
|
||||
boundary between each sub-command like this:
|
||||
|
||||
```php
|
||||
$process = new Process('cat first && echo --- && cat second');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
As an alternative, considering launching one process at a time and listening on
|
||||
its `exit` event to conditionally start the next process in the chain.
|
||||
This will give you an opportunity to configure the subsequent process I/O streams:
|
||||
|
||||
```php
|
||||
$first = new Process('cat first');
|
||||
$first->start();
|
||||
|
||||
$first->on('exit', function () {
|
||||
$second = new Process('cat second');
|
||||
$second->start();
|
||||
});
|
||||
```
|
||||
|
||||
Keep in mind that PHP uses the shell wrapper for ALL command lines on Unix.
|
||||
While this may seem reasonable for more complex command lines, this actually
|
||||
also applies to running the most simple single command:
|
||||
|
||||
```php
|
||||
$process = new Process('yes');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
This will actually spawn a command hierarchy similar to this on Unix:
|
||||
|
||||
```
|
||||
5480 … \_ php example.php
|
||||
5481 … \_ sh -c yes
|
||||
5482 … \_ yes
|
||||
```
|
||||
|
||||
This means that trying to get the underlying process PID or sending signals
|
||||
will actually target the wrapping shell, which may not be the desired result
|
||||
in many cases.
|
||||
|
||||
If you do not want this wrapping shell process to show up, you can simply
|
||||
prepend the command string with `exec` on Unix platforms, which will cause the
|
||||
wrapping shell process to be replaced by our process:
|
||||
|
||||
```php
|
||||
$process = new Process('exec yes');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
This will show a resulting command hierarchy similar to this:
|
||||
|
||||
```
|
||||
5480 … \_ php example.php
|
||||
5481 … \_ yes
|
||||
```
|
||||
|
||||
This means that trying to get the underlying process PID and sending signals
|
||||
will now target the actual command as expected.
|
||||
|
||||
Note that in this case, the command line will not be run in a wrapping shell.
|
||||
This implies that when using `exec`, there's no way to pass command lines such
|
||||
as those containing command chains or redirected STDIO streams.
|
||||
|
||||
As a rule of thumb, most commands will likely run just fine with the wrapping
|
||||
shell.
|
||||
If you pass a complete command line (or are unsure), you SHOULD most likely keep
|
||||
the wrapping shell.
|
||||
If you're running on Unix and you want to pass an invidual command only, you MAY
|
||||
want to consider prepending the command string with `exec` to avoid the wrapping shell.
|
||||
|
||||
### Termination
|
||||
|
||||
The `exit` event will be emitted whenever the process is no longer running.
|
||||
Event listeners will receive the exit code and termination signal as two
|
||||
arguments:
|
||||
|
||||
```php
|
||||
$process = new Process('sleep 10');
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($code, $term) {
|
||||
if ($term === null) {
|
||||
echo 'exit with code ' . $code . PHP_EOL;
|
||||
} else {
|
||||
echo 'terminated with signal ' . $term . PHP_EOL;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that `$code` is `null` if the process has terminated, but the exit
|
||||
code could not be determined (for example
|
||||
[sigchild compatibility](#sigchild-compatibility) was disabled).
|
||||
Similarly, `$term` is `null` unless the process has terminated in response to
|
||||
an uncaught signal sent to it.
|
||||
This is not a limitation of this project, but actual how exit codes and signals
|
||||
are exposed on POSIX systems, for more details see also
|
||||
[here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
|
||||
|
||||
It's also worth noting that process termination depends on all file descriptors
|
||||
being closed beforehand.
|
||||
This means that all [process pipes](#stream-properties) will emit a `close`
|
||||
event before the `exit` event and that no more `data` events will arrive after
|
||||
the `exit` event.
|
||||
Accordingly, if either of these pipes is in a paused state (`pause()` method
|
||||
or internally due to a `pipe()` call), this detection may not trigger.
|
||||
|
||||
The `terminate(?int $signal = null): bool` method can be used to send the
|
||||
process a signal (SIGTERM by default).
|
||||
Depending on which signal you send to the process and whether it has a signal
|
||||
handler registered, this can be used to either merely signal a process or even
|
||||
forcefully terminate it.
|
||||
|
||||
```php
|
||||
$process->terminate(SIGUSR1);
|
||||
```
|
||||
|
||||
Keep the above section in mind if you want to forcefully terminate a process.
|
||||
If your process spawn sub-processes or implicitly uses the
|
||||
[wrapping shell mentioned above](#command), its file descriptors may be
|
||||
inherited to child processes and terminating the main process may not
|
||||
necessarily terminate the whole process tree.
|
||||
It is highly suggested that you explicitly `close()` all process pipes
|
||||
accordingly when terminating a process:
|
||||
|
||||
```php
|
||||
$process = new Process('sleep 10');
|
||||
$process->start();
|
||||
|
||||
Loop::addTimer(2.0, function () use ($process) {
|
||||
foreach ($process->pipes as $pipe) {
|
||||
$pipe->close();
|
||||
}
|
||||
$process->terminate();
|
||||
});
|
||||
```
|
||||
|
||||
For many simple programs these seamingly complicated steps can also be avoided
|
||||
by prefixing the command line with `exec` to avoid the wrapping shell and its
|
||||
inherited process pipes as [mentioned above](#command).
|
||||
|
||||
```php
|
||||
$process = new Process('exec sleep 10');
|
||||
$process->start();
|
||||
|
||||
Loop::addTimer(2.0, function () use ($process) {
|
||||
$process->terminate();
|
||||
});
|
||||
```
|
||||
|
||||
Many command line programs also wait for data on `STDIN` and terminate cleanly
|
||||
when this pipe is closed.
|
||||
For example, the following can be used to "soft-close" a `cat` process:
|
||||
|
||||
```php
|
||||
$process = new Process('cat');
|
||||
$process->start();
|
||||
|
||||
Loop::addTimer(2.0, function () use ($process) {
|
||||
$process->stdin->end();
|
||||
});
|
||||
```
|
||||
|
||||
While process pipes and termination may seem confusing to newcomers, the above
|
||||
properties actually allow some fine grained control over process termination,
|
||||
such as first trying a soft-close and then applying a force-close after a
|
||||
timeout.
|
||||
|
||||
### Custom pipes
|
||||
|
||||
Following common Unix conventions, this library will start each child process
|
||||
with the three pipes matching the standard I/O streams by default. For more
|
||||
advanced use cases it may be useful to pass in custom pipes, such as explicitly
|
||||
passing additional file descriptors (FDs) or overriding default process pipes.
|
||||
|
||||
Note that passing custom pipes is considered advanced usage and requires a
|
||||
more in-depth understanding of Unix file descriptors and how they are inherited
|
||||
to child processes and shared in multi-processing applications.
|
||||
|
||||
If you do not want to use the default standard I/O pipes, you can explicitly
|
||||
pass an array containing the file descriptor specification to the constructor
|
||||
like this:
|
||||
|
||||
```php
|
||||
$fds = array(
|
||||
// standard I/O pipes for stdin/stdout/stderr
|
||||
0 => array('pipe', 'r'),
|
||||
1 => array('pipe', 'w'),
|
||||
2 => array('pipe', 'w'),
|
||||
|
||||
// example FDs for files or open resources
|
||||
4 => array('file', '/dev/null', 'r'),
|
||||
6 => fopen('log.txt','a'),
|
||||
8 => STDERR,
|
||||
|
||||
// example FDs for sockets
|
||||
10 => fsockopen('localhost', 8080),
|
||||
12 => stream_socket_server('tcp://0.0.0.0:4711')
|
||||
);
|
||||
|
||||
$process = new Process($cmd, null, null, $fds);
|
||||
$process->start();
|
||||
```
|
||||
|
||||
Unless your use case has special requirements that demand otherwise, you're
|
||||
highly recommended to (at least) pass in the standard I/O pipes as given above.
|
||||
The file descriptor specification accepts arguments in the exact same format
|
||||
as the underlying [`proc_open()`](http://php.net/proc_open) function.
|
||||
|
||||
Once the process is started, the `$pipes` array will always contain references to
|
||||
all pipes as configured and the standard I/O references will always be set to
|
||||
reference the pipes matching common Unix conventions. This library supports any
|
||||
number of pipes and additional file descriptors, but many common applications
|
||||
being run as a child process will expect that the parent process properly
|
||||
assigns these file descriptors.
|
||||
|
||||
### Sigchild Compatibility
|
||||
|
||||
Internally, this project uses a work-around to improve compatibility when PHP
|
||||
has been compiled with the `--enable-sigchild` option. This should not affect most
|
||||
installations as this configure option is not used by default and many
|
||||
distributions (such as Debian and Ubuntu) are known to not use this by default.
|
||||
Some installations that use [Oracle OCI8](http://php.net/manual/en/book.oci8.php)
|
||||
may use this configure option to circumvent `defunct` processes.
|
||||
|
||||
When PHP has been compiled with the `--enable-sigchild` option, a child process'
|
||||
exit code cannot be reliably determined via `proc_close()` or `proc_get_status()`.
|
||||
To work around this, we execute the child process with an additional pipe and
|
||||
use that to retrieve its exit code.
|
||||
|
||||
This work-around incurs some overhead, so we only trigger this when necessary
|
||||
and when we detect that PHP has been compiled with the `--enable-sigchild` option.
|
||||
Because PHP does not provide a way to reliably detect this option, we try to
|
||||
inspect output of PHP's configure options from the `phpinfo()` function.
|
||||
|
||||
The static `setSigchildEnabled(bool $sigchild): void` method can be used to
|
||||
explicitly enable or disable this behavior like this:
|
||||
|
||||
```php
|
||||
// advanced: not recommended by default
|
||||
Process::setSigchildEnabled(true);
|
||||
```
|
||||
|
||||
Note that all processes instantiated after this method call will be affected.
|
||||
If this work-around is disabled on an affected PHP installation, the `exit`
|
||||
event may receive `null` instead of the actual exit code as described above.
|
||||
Similarly, some distributions are known to omit the configure options from
|
||||
`phpinfo()`, so automatic detection may fail to enable this work-around in some
|
||||
cases. You may then enable this explicitly as given above.
|
||||
|
||||
**Note:** The original functionality was taken from Symfony's
|
||||
[Process](https://github.com/symfony/process) compoment.
|
||||
|
||||
### Windows Compatibility
|
||||
|
||||
Due to platform constraints, this library provides only limited support for
|
||||
spawning child processes on Windows. In particular, PHP does not allow accessing
|
||||
standard I/O pipes on Windows without blocking. As such, this project will not
|
||||
allow constructing a child process with the default process pipes and will
|
||||
instead throw a `LogicException` on Windows by default:
|
||||
|
||||
```php
|
||||
// throws LogicException on Windows
|
||||
$process = new Process('ping example.com');
|
||||
$process->start();
|
||||
```
|
||||
|
||||
There are a number of alternatives and workarounds as detailed below if you want
|
||||
to run a child process on Windows, each with its own set of pros and cons:
|
||||
|
||||
* As of PHP 8, you can start the child process with `socket` pair descriptors
|
||||
in place of normal standard I/O pipes like this:
|
||||
|
||||
```php
|
||||
$process = new Process(
|
||||
'ping example.com',
|
||||
null,
|
||||
null,
|
||||
[
|
||||
['socket'],
|
||||
['socket'],
|
||||
['socket']
|
||||
]
|
||||
);
|
||||
$process->start();
|
||||
|
||||
$process->stdout->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
```
|
||||
|
||||
These `socket` pairs support non-blocking process I/O on any platform,
|
||||
including Windows. However, not all programs accept stdio sockets.
|
||||
|
||||
* This package does work on
|
||||
[`Windows Subsystem for Linux`](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)
|
||||
(or WSL) without issues. When you are in control over how your application is
|
||||
deployed, we recommend [installing WSL](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide)
|
||||
when you want to run this package on Windows.
|
||||
|
||||
* If you only care about the exit code of a child process to check if its
|
||||
execution was successful, you can use [custom pipes](#custom-pipes) to omit
|
||||
any standard I/O pipes like this:
|
||||
|
||||
```php
|
||||
$process = new Process('ping example.com', null, null, array());
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($exitcode) {
|
||||
echo 'exit with ' . $exitcode . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, this is also useful if your child process communicates over
|
||||
sockets with remote servers or even your parent process using the
|
||||
[Socket component](https://github.com/reactphp/socket). This is usually
|
||||
considered the best alternative if you have control over how your child
|
||||
process communicates with the parent process.
|
||||
|
||||
* If you only care about command output after the child process has been
|
||||
executed, you can use [custom pipes](#custom-pipes) to configure file
|
||||
handles to be passed to the child process instead of pipes like this:
|
||||
|
||||
```php
|
||||
$process = new Process('ping example.com', null, null, array(
|
||||
array('file', 'nul', 'r'),
|
||||
$stdout = tmpfile(),
|
||||
array('file', 'nul', 'w')
|
||||
));
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($exitcode) use ($stdout) {
|
||||
echo 'exit with ' . $exitcode . PHP_EOL;
|
||||
|
||||
// rewind to start and then read full file (demo only, this is blocking).
|
||||
// reading from shared file is only safe if you have some synchronization in place
|
||||
// or after the child process has terminated.
|
||||
rewind($stdout);
|
||||
echo stream_get_contents($stdout);
|
||||
fclose($stdout);
|
||||
});
|
||||
```
|
||||
|
||||
Note that this example uses `tmpfile()`/`fopen()` for illustration purposes only.
|
||||
This should not be used in a truly async program because the filesystem is
|
||||
inherently blocking and each call could potentially take several seconds.
|
||||
See also the [Filesystem component](https://github.com/reactphp/filesystem) as an
|
||||
alternative.
|
||||
|
||||
* If you want to access command output as it happens in a streaming fashion,
|
||||
you can use redirection to spawn an additional process to forward your
|
||||
standard I/O streams to a socket and use [custom pipes](#custom-pipes) to
|
||||
omit any actual standard I/O pipes like this:
|
||||
|
||||
```php
|
||||
$server = new React\Socket\Server('127.0.0.1:0');
|
||||
$server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
$connection->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
|
||||
$command = 'ping example.com | foobar ' . escapeshellarg($server->getAddress());
|
||||
$process = new Process($command, null, null, array());
|
||||
$process->start();
|
||||
|
||||
$process->on('exit', function ($exitcode) use ($server) {
|
||||
$server->close();
|
||||
echo 'exit with ' . $exitcode . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
Note how this will spawn another fictional `foobar` helper program to consume
|
||||
the standard output from the actual child process. This is in fact similar
|
||||
to the above recommendation of using socket connections in the child process,
|
||||
but in this case does not require modification of the actual child process.
|
||||
|
||||
In this example, the fictional `foobar` helper program can be implemented by
|
||||
simply consuming all data from standard input and forwarding it to a socket
|
||||
connection like this:
|
||||
|
||||
```php
|
||||
$socket = stream_socket_client($argv[1]);
|
||||
do {
|
||||
fwrite($socket, $data = fread(STDIN, 8192));
|
||||
} while (isset($data[0]));
|
||||
```
|
||||
|
||||
Accordingly, this example can also be run with plain PHP without having to
|
||||
rely on any external helper program like this:
|
||||
|
||||
```php
|
||||
$code = '$s=stream_socket_client($argv[1]);do{fwrite($s,$d=fread(STDIN, 8192));}while(isset($d[0]));';
|
||||
$command = 'ping example.com | php -r ' . escapeshellarg($code) . ' ' . escapeshellarg($server->getAddress());
|
||||
$process = new Process($command, null, null, array());
|
||||
$process->start();
|
||||
```
|
||||
|
||||
See also [example #23](examples/23-forward-socket.php).
|
||||
|
||||
Note that this is for illustration purposes only and you may want to implement
|
||||
some proper error checks and/or socket verification in actual production use
|
||||
if you do not want to risk other processes connecting to the server socket.
|
||||
In this case, we suggest looking at the excellent
|
||||
[createprocess-windows](https://github.com/cubiclesoft/createprocess-windows).
|
||||
|
||||
Additionally, note that the [command](#command) given to the `Process` will be
|
||||
passed to the underlying Windows-API
|
||||
([`CreateProcess`](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa))
|
||||
as-is and the process will not be launched in a wrapping shell by default. In
|
||||
particular, this means that shell built-in functions such as `echo hello` or
|
||||
`sleep 10` may have to be prefixed with an explicit shell command like this:
|
||||
|
||||
```php
|
||||
$process = new Process('cmd /c echo hello', null, null, $pipes);
|
||||
$process->start();
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/child-process:^0.6.6
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
See above note for limited [Windows Compatibility](#windows-compatibility).
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
49
vendor/react/child-process/composer.json
vendored
Executable file
49
vendor/react/child-process/composer.json
vendored
Executable file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "react/child-process",
|
||||
"description": "Event-driven library for executing child processes with ReactPHP.",
|
||||
"keywords": ["process", "event-driven", "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/event-loop": "^1.2",
|
||||
"react/stream": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/socket": "^1.16",
|
||||
"sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\ChildProcess\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\ChildProcess\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
585
vendor/react/child-process/src/Process.php
vendored
Executable file
585
vendor/react/child-process/src/Process.php
vendored
Executable file
@@ -0,0 +1,585 @@
|
||||
<?php
|
||||
|
||||
namespace React\ChildProcess;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableResourceStream;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
* Process component.
|
||||
*
|
||||
* This class borrows logic from Symfony's Process component for ensuring
|
||||
* compatibility when PHP is compiled with the --enable-sigchild option.
|
||||
*
|
||||
* This class also implements the `EventEmitterInterface`
|
||||
* which allows you to react to certain events:
|
||||
*
|
||||
* exit event:
|
||||
* The `exit` event will be emitted whenever the process is no longer running.
|
||||
* Event listeners will receive the exit code and termination signal as two
|
||||
* arguments:
|
||||
*
|
||||
* ```php
|
||||
* $process = new Process('sleep 10');
|
||||
* $process->start();
|
||||
*
|
||||
* $process->on('exit', function ($code, $term) {
|
||||
* if ($term === null) {
|
||||
* echo 'exit with code ' . $code . PHP_EOL;
|
||||
* } else {
|
||||
* echo 'terminated with signal ' . $term . PHP_EOL;
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that `$code` is `null` if the process has terminated, but the exit
|
||||
* code could not be determined (for example
|
||||
* [sigchild compatibility](#sigchild-compatibility) was disabled).
|
||||
* Similarly, `$term` is `null` unless the process has terminated in response to
|
||||
* an uncaught signal sent to it.
|
||||
* This is not a limitation of this project, but actual how exit codes and signals
|
||||
* are exposed on POSIX systems, for more details see also
|
||||
* [here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
|
||||
*
|
||||
* It's also worth noting that process termination depends on all file descriptors
|
||||
* being closed beforehand.
|
||||
* This means that all [process pipes](#stream-properties) will emit a `close`
|
||||
* event before the `exit` event and that no more `data` events will arrive after
|
||||
* the `exit` event.
|
||||
* Accordingly, if either of these pipes is in a paused state (`pause()` method
|
||||
* or internally due to a `pipe()` call), this detection may not trigger.
|
||||
*/
|
||||
class Process extends EventEmitter
|
||||
{
|
||||
/**
|
||||
* @var WritableStreamInterface|null|DuplexStreamInterface|ReadableStreamInterface
|
||||
*/
|
||||
public $stdin;
|
||||
|
||||
/**
|
||||
* @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
|
||||
*/
|
||||
public $stdout;
|
||||
|
||||
/**
|
||||
* @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
|
||||
*/
|
||||
public $stderr;
|
||||
|
||||
/**
|
||||
* Array with all process pipes (once started)
|
||||
*
|
||||
* Unless explicitly configured otherwise during construction, the following
|
||||
* standard I/O pipes will be assigned by default:
|
||||
* - 0: STDIN (`WritableStreamInterface`)
|
||||
* - 1: STDOUT (`ReadableStreamInterface`)
|
||||
* - 2: STDERR (`ReadableStreamInterface`)
|
||||
*
|
||||
* @var array<ReadableStreamInterface|WritableStreamInterface|DuplexStreamInterface>
|
||||
*/
|
||||
public $pipes = array();
|
||||
|
||||
private $cmd;
|
||||
private $cwd;
|
||||
private $env;
|
||||
private $fds;
|
||||
|
||||
private $enhanceSigchildCompatibility;
|
||||
private $sigchildPipe;
|
||||
|
||||
private $process;
|
||||
private $status;
|
||||
private $exitCode;
|
||||
private $fallbackExitCode;
|
||||
private $stopSignal;
|
||||
private $termSignal;
|
||||
|
||||
private static $sigchild;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $cmd Command line to run
|
||||
* @param null|string $cwd Current working directory or null to inherit
|
||||
* @param null|array $env Environment variables or null to inherit
|
||||
* @param null|array $fds File descriptors to allocate for this process (or null = default STDIO streams)
|
||||
* @throws \LogicException On windows or when proc_open() is not installed
|
||||
*/
|
||||
public function __construct($cmd, $cwd = null, $env = null, $fds = null)
|
||||
{
|
||||
if ($env !== null && !\is_array($env)) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($env) expected null|array');
|
||||
}
|
||||
if ($fds !== null && !\is_array($fds)) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #4 ($fds) expected null|array');
|
||||
}
|
||||
if (!\function_exists('proc_open')) {
|
||||
throw new \LogicException('The Process class relies on proc_open(), which is not available on your PHP installation.');
|
||||
}
|
||||
|
||||
$this->cmd = $cmd;
|
||||
$this->cwd = $cwd;
|
||||
|
||||
if (null !== $env) {
|
||||
$this->env = array();
|
||||
foreach ($env as $key => $value) {
|
||||
$this->env[(binary) $key] = (binary) $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fds === null) {
|
||||
$fds = array(
|
||||
array('pipe', 'r'), // stdin
|
||||
array('pipe', 'w'), // stdout
|
||||
array('pipe', 'w'), // stderr
|
||||
);
|
||||
}
|
||||
|
||||
if (\DIRECTORY_SEPARATOR === '\\') {
|
||||
foreach ($fds as $fd) {
|
||||
if (isset($fd[0]) && $fd[0] === 'pipe') {
|
||||
throw new \LogicException('Process pipes are not supported on Windows due to their blocking nature on Windows');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->fds = $fds;
|
||||
$this->enhanceSigchildCompatibility = self::isSigchildEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the process.
|
||||
*
|
||||
* After the process is started, the standard I/O streams will be constructed
|
||||
* and available via public properties.
|
||||
*
|
||||
* This method takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this process. 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 ?LoopInterface $loop Loop interface for stream construction
|
||||
* @param float $interval Interval to periodically monitor process state (seconds)
|
||||
* @throws \RuntimeException If the process is already running or fails to start
|
||||
*/
|
||||
public function start($loop = null, $interval = 0.1)
|
||||
{
|
||||
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 ($this->isRunning()) {
|
||||
throw new \RuntimeException('Process is already running');
|
||||
}
|
||||
|
||||
$loop = $loop ?: Loop::get();
|
||||
$cmd = $this->cmd;
|
||||
$fdSpec = $this->fds;
|
||||
$sigchild = null;
|
||||
|
||||
// Read exit code through fourth pipe to work around --enable-sigchild
|
||||
if ($this->enhanceSigchildCompatibility) {
|
||||
$fdSpec[] = array('pipe', 'w');
|
||||
\end($fdSpec);
|
||||
$sigchild = \key($fdSpec);
|
||||
|
||||
// make sure this is fourth or higher (do not mess with STDIO)
|
||||
if ($sigchild < 3) {
|
||||
$fdSpec[3] = $fdSpec[$sigchild];
|
||||
unset($fdSpec[$sigchild]);
|
||||
$sigchild = 3;
|
||||
}
|
||||
|
||||
$cmd = \sprintf('(%s) ' . $sigchild . '>/dev/null; code=$?; echo $code >&' . $sigchild . '; exit $code', $cmd);
|
||||
}
|
||||
|
||||
// on Windows, we do not launch the given command line in a shell (cmd.exe) by default and omit any error dialogs
|
||||
// the cmd.exe shell can explicitly be given as part of the command as detailed in both documentation and tests
|
||||
$options = array();
|
||||
if (\DIRECTORY_SEPARATOR === '\\') {
|
||||
$options['bypass_shell'] = true;
|
||||
$options['suppress_errors'] = true;
|
||||
}
|
||||
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// proc_open(/dev/does-not-exist): Failed to open stream: No such file or directory
|
||||
$errstr = $error;
|
||||
});
|
||||
|
||||
$pipes = array();
|
||||
$this->process = @\proc_open($cmd, $fdSpec, $pipes, $this->cwd, $this->env, $options);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if (!\is_resource($this->process)) {
|
||||
throw new \RuntimeException('Unable to launch a new process: ' . $errstr);
|
||||
}
|
||||
|
||||
// count open process pipes and await close event for each to drain buffers before detecting exit
|
||||
$that = $this;
|
||||
$closeCount = 0;
|
||||
$streamCloseHandler = function () use (&$closeCount, $loop, $interval, $that) {
|
||||
$closeCount--;
|
||||
|
||||
if ($closeCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// process already closed => report immediately
|
||||
if (!$that->isRunning()) {
|
||||
$that->close();
|
||||
$that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
|
||||
return;
|
||||
}
|
||||
|
||||
// close not detected immediately => check regularly
|
||||
$loop->addPeriodicTimer($interval, function ($timer) use ($that, $loop) {
|
||||
if (!$that->isRunning()) {
|
||||
$loop->cancelTimer($timer);
|
||||
$that->close();
|
||||
$that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if ($sigchild !== null) {
|
||||
$this->sigchildPipe = $pipes[$sigchild];
|
||||
unset($pipes[$sigchild]);
|
||||
}
|
||||
|
||||
foreach ($pipes as $n => $fd) {
|
||||
// use open mode from stream meta data or fall back to pipe open mode for legacy HHVM
|
||||
$meta = \stream_get_meta_data($fd);
|
||||
$mode = $meta['mode'] === '' ? ($this->fds[$n][1] === 'r' ? 'w' : 'r') : $meta['mode'];
|
||||
|
||||
if ($mode === 'r+') {
|
||||
$stream = new DuplexResourceStream($fd, $loop);
|
||||
$stream->on('close', $streamCloseHandler);
|
||||
$closeCount++;
|
||||
} elseif ($mode === 'w') {
|
||||
$stream = new WritableResourceStream($fd, $loop);
|
||||
} else {
|
||||
$stream = new ReadableResourceStream($fd, $loop);
|
||||
$stream->on('close', $streamCloseHandler);
|
||||
$closeCount++;
|
||||
}
|
||||
$this->pipes[$n] = $stream;
|
||||
}
|
||||
|
||||
$this->stdin = isset($this->pipes[0]) ? $this->pipes[0] : null;
|
||||
$this->stdout = isset($this->pipes[1]) ? $this->pipes[1] : null;
|
||||
$this->stderr = isset($this->pipes[2]) ? $this->pipes[2] : null;
|
||||
|
||||
// immediately start checking for process exit when started without any I/O pipes
|
||||
if (!$closeCount) {
|
||||
$streamCloseHandler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the process.
|
||||
*
|
||||
* This method should only be invoked via the periodic timer that monitors
|
||||
* the process state.
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->pipes as $pipe) {
|
||||
$pipe->close();
|
||||
}
|
||||
|
||||
if ($this->enhanceSigchildCompatibility) {
|
||||
$this->pollExitCodePipe();
|
||||
$this->closeExitCodePipe();
|
||||
}
|
||||
|
||||
$exitCode = \proc_close($this->process);
|
||||
$this->process = null;
|
||||
|
||||
if ($this->exitCode === null && $exitCode !== -1) {
|
||||
$this->exitCode = $exitCode;
|
||||
}
|
||||
|
||||
if ($this->exitCode === null && $this->status['exitcode'] !== -1) {
|
||||
$this->exitCode = $this->status['exitcode'];
|
||||
}
|
||||
|
||||
if ($this->exitCode === null && $this->fallbackExitCode !== null) {
|
||||
$this->exitCode = $this->fallbackExitCode;
|
||||
$this->fallbackExitCode = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate the process with an optional signal.
|
||||
*
|
||||
* @param int $signal Optional signal (default: SIGTERM)
|
||||
* @return bool Whether the signal was sent successfully
|
||||
*/
|
||||
public function terminate($signal = null)
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($signal !== null) {
|
||||
return \proc_terminate($this->process, $signal);
|
||||
}
|
||||
|
||||
return \proc_terminate($this->process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command string used to launch the process.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exit code returned by the process.
|
||||
*
|
||||
* This value is only meaningful if isRunning() has returned false. Null
|
||||
* will be returned if the process is still running.
|
||||
*
|
||||
* Null may also be returned if the process has terminated, but the exit
|
||||
* code could not be determined (e.g. sigchild compatibility was disabled).
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getExitCode()
|
||||
{
|
||||
return $this->exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the process ID.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPid()
|
||||
{
|
||||
$status = $this->getCachedStatus();
|
||||
|
||||
return $status !== null ? $status['pid'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signal that caused the process to stop its execution.
|
||||
*
|
||||
* This value is only meaningful if isStopped() has returned true. Null will
|
||||
* be returned if the process was never stopped.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getStopSignal()
|
||||
{
|
||||
return $this->stopSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signal that caused the process to terminate its execution.
|
||||
*
|
||||
* This value is only meaningful if isTerminated() has returned true. Null
|
||||
* will be returned if the process was never terminated.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getTermSignal()
|
||||
{
|
||||
return $this->termSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the process is still running.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRunning()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = $this->getFreshStatus();
|
||||
|
||||
return $status !== null ? $status['running'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the process has been stopped by a signal.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isStopped()
|
||||
{
|
||||
$status = $this->getFreshStatus();
|
||||
|
||||
return $status !== null ? $status['stopped'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the process has been terminated by an uncaught signal.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isTerminated()
|
||||
{
|
||||
$status = $this->getFreshStatus();
|
||||
|
||||
return $status !== null ? $status['signaled'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether PHP has been compiled with the '--enable-sigchild' option.
|
||||
*
|
||||
* @see \Symfony\Component\Process\Process::isSigchildEnabled()
|
||||
* @return bool
|
||||
*/
|
||||
public final static function isSigchildEnabled()
|
||||
{
|
||||
if (null !== self::$sigchild) {
|
||||
return self::$sigchild;
|
||||
}
|
||||
|
||||
if (!\function_exists('phpinfo')) {
|
||||
return self::$sigchild = false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
\ob_start();
|
||||
\phpinfo(INFO_GENERAL);
|
||||
|
||||
return self::$sigchild = false !== \strpos(\ob_get_clean(), '--enable-sigchild');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable sigchild compatibility mode.
|
||||
*
|
||||
* Sigchild compatibility mode is required to get the exit code and
|
||||
* determine the success of a process when PHP has been compiled with
|
||||
* the --enable-sigchild option.
|
||||
*
|
||||
* @param bool $sigchild
|
||||
* @return void
|
||||
*/
|
||||
public final static function setSigchildEnabled($sigchild)
|
||||
{
|
||||
self::$sigchild = (bool) $sigchild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the fourth pipe for an exit code.
|
||||
*
|
||||
* This should only be used if --enable-sigchild compatibility was enabled.
|
||||
*/
|
||||
private function pollExitCodePipe()
|
||||
{
|
||||
if ($this->sigchildPipe === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$r = array($this->sigchildPipe);
|
||||
$w = $e = null;
|
||||
|
||||
$n = @\stream_select($r, $w, $e, 0);
|
||||
|
||||
if (1 !== $n) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = \fread($r[0], 8192);
|
||||
|
||||
if (\strlen($data) > 0) {
|
||||
$this->fallbackExitCode = (int) $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the fourth pipe used to relay an exit code.
|
||||
*
|
||||
* This should only be used if --enable-sigchild compatibility was enabled.
|
||||
*/
|
||||
private function closeExitCodePipe()
|
||||
{
|
||||
if ($this->sigchildPipe === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
\fclose($this->sigchildPipe);
|
||||
$this->sigchildPipe = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cached process status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getCachedStatus()
|
||||
{
|
||||
if ($this->status === null) {
|
||||
$this->updateStatus();
|
||||
}
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the updated process status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getFreshStatus()
|
||||
{
|
||||
$this->updateStatus();
|
||||
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the process status, stop/term signals, and exit code.
|
||||
*
|
||||
* Stop/term signals are only updated if the process is currently stopped or
|
||||
* signaled, respectively. Otherwise, signal values will remain as-is so the
|
||||
* corresponding getter methods may be used at a later point in time.
|
||||
*/
|
||||
private function updateStatus()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->status = \proc_get_status($this->process);
|
||||
|
||||
if ($this->status === false) {
|
||||
throw new \UnexpectedValueException('proc_get_status() failed');
|
||||
}
|
||||
|
||||
if ($this->status['stopped']) {
|
||||
$this->stopSignal = $this->status['stopsig'];
|
||||
}
|
||||
|
||||
if ($this->status['signaled']) {
|
||||
$this->termSignal = $this->status['termsig'];
|
||||
}
|
||||
|
||||
if (!$this->status['running'] && -1 !== $this->status['exitcode']) {
|
||||
$this->exitCode = $this->status['exitcode'];
|
||||
}
|
||||
}
|
||||
}
|
||||
2
vendor/react/datagram/.gitignore
vendored
Executable file
2
vendor/react/datagram/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
/vendor
|
||||
/composer.lock
|
||||
29
vendor/react/datagram/.travis.yml
vendored
Executable file
29
vendor/react/datagram/.travis.yml
vendored
Executable file
@@ -0,0 +1,29 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- hhvm # ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text
|
||||
91
vendor/react/datagram/CHANGELOG.md
vendored
Executable file
91
vendor/react/datagram/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,91 @@
|
||||
# Changelog
|
||||
|
||||
## 1.5.0 (2019-07-10)
|
||||
|
||||
* Feature: Forward compatibility with upcoming stable DNS component.
|
||||
(#29 by @clue)
|
||||
|
||||
* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
|
||||
(#28 by @WyriHaximus)
|
||||
|
||||
* Improve test suite to also test against PHP 7.1 and 7.2.
|
||||
(#25 by @andreybolonin)
|
||||
|
||||
## 1.4.0 (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)
|
||||
(#23 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
|
||||
$factory = new Factory($loop);
|
||||
$factory->createClient('intranet.example:5353');
|
||||
```
|
||||
|
||||
* Improve README
|
||||
(#22 by @jsor)
|
||||
|
||||
## 1.3.0 (2017-09-25)
|
||||
|
||||
* Feature: Always use `Resolver` with default DNS to match Socket component
|
||||
and update DNS dependency to support hosts file on all platforms
|
||||
(#19 and #20 by @clue)
|
||||
|
||||
This means that connecting to hosts such as `localhost` (and for example
|
||||
those used for Docker containers) will now work as expected across all
|
||||
platforms with no changes required:
|
||||
|
||||
```php
|
||||
$factory = new Factory($loop);
|
||||
$factory->createClient('localhost:5353');
|
||||
```
|
||||
|
||||
## 1.2.0 (2017-08-09)
|
||||
|
||||
* Feature: Target evenement 3.0 a long side 2.0 and 1.0
|
||||
(#16 by @WyriHaximus)
|
||||
|
||||
* Feature: Forward compatibility with EventLoop v1.0 and v0.5
|
||||
(#18 by @clue)
|
||||
|
||||
* Improve test suite by updating Travis build config so new defaults do not break the build
|
||||
(#17 by @clue)
|
||||
|
||||
## 1.1.1 (2017-01-23)
|
||||
|
||||
* Fix: Properly format IPv6 addresses and return `null` for unknown addresses
|
||||
(#14 by @clue)
|
||||
|
||||
* Fix: Skip IPv6 tests if not supported by the system
|
||||
(#15 by @clue)
|
||||
|
||||
## 1.1.0 (2016-03-19)
|
||||
|
||||
* Feature: Support promise cancellation (cancellation of underlying DNS lookup)
|
||||
(#12 by @clue)
|
||||
|
||||
* Fix: Fix error reporting when trying to create invalid sockets
|
||||
(#11 by @clue)
|
||||
|
||||
* Improve test suite and update dependencies
|
||||
(#7, #8 by @clue)
|
||||
|
||||
## 1.0.1 (2015-11-13)
|
||||
|
||||
* Fix: Correct formatting for remote peer address of incoming datagrams when using IPv6
|
||||
(#6 by @WyriHaximus)
|
||||
|
||||
* Improve test suite for different PHP versions
|
||||
|
||||
## 1.0.0 (2014-10-23)
|
||||
|
||||
* Initial tagged release
|
||||
|
||||
> This project has been migrated over from [clue/datagram](https://github.com/clue/php-datagram)
|
||||
> which has originally been released in January 2013.
|
||||
> Upgrading from clue/datagram v0.5.0? Use namespace `React\Datagram` instead of `Datagram` and you're ready to go!
|
||||
21
vendor/react/datagram/LICENSE
vendored
Executable file
21
vendor/react/datagram/LICENSE
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
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.
|
||||
70
vendor/react/datagram/README.md
vendored
Executable file
70
vendor/react/datagram/README.md
vendored
Executable file
@@ -0,0 +1,70 @@
|
||||
# Datagram
|
||||
|
||||
[](https://travis-ci.org/reactphp/datagram)
|
||||
|
||||
Event-driven UDP datagram socket client and server for [ReactPHP](https://reactphp.org).
|
||||
|
||||
## Quickstart example
|
||||
|
||||
Once [installed](#install), you can use the following code to connect to an UDP server listening on
|
||||
`localhost:1234` and send and receive UDP datagrams:
|
||||
|
||||
```php
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
$factory = new React\Datagram\Factory($loop);
|
||||
|
||||
$factory->createClient('localhost:1234')->then(function (React\Datagram\Socket $client) {
|
||||
$client->send('first');
|
||||
|
||||
$client->on('message', function($message, $serverAddress, $client) {
|
||||
echo 'received "' . $message . '" from ' . $serverAddress. PHP_EOL;
|
||||
});
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Usage
|
||||
|
||||
This library's API is modelled after node.js's API for
|
||||
[UDP / Datagram Sockets (dgram.Socket)](https://nodejs.org/api/dgram.html).
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](https://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
$ composer require react/datagram:^1.5
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use PHP 7+* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org):
|
||||
|
||||
```bash
|
||||
$ composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
$ php vendor/bin/phpunit
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
27
vendor/react/datagram/composer.json
vendored
Executable file
27
vendor/react/datagram/composer.json
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "react/datagram",
|
||||
"description": "Event-driven UDP datagram socket client and server for ReactPHP",
|
||||
"keywords": ["udp", "datagram", "dgram", "socket", "client", "server", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/reactphp/datagram",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {"React\\Datagram\\": "src"}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"react/dns": "^1.0 || ^0.4.13",
|
||||
"react/promise": "~2.1|~1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "~1.0",
|
||||
"phpunit/phpunit": "^5.0 || ^4.8"
|
||||
}
|
||||
}
|
||||
40
vendor/react/datagram/examples/client.php
vendored
Executable file
40
vendor/react/datagram/examples/client.php
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
$factory = new React\Datagram\Factory($loop);
|
||||
|
||||
$factory->createClient('localhost:1234')->then(function (React\Datagram\Socket $client) use ($loop) {
|
||||
$client->send('first');
|
||||
|
||||
$client->on('message', function($message, $serverAddress, $client) {
|
||||
echo 'received "' . $message . '" from ' . $serverAddress. PHP_EOL;
|
||||
});
|
||||
|
||||
$client->on('error', function($error, $client) {
|
||||
echo 'error: ' . $error->getMessage() . PHP_EOL;
|
||||
});
|
||||
|
||||
$n = 0;
|
||||
$tid = $loop->addPeriodicTimer(2.0, function() use ($client, &$n) {
|
||||
$client->send('tick' . ++$n);
|
||||
});
|
||||
|
||||
// read input from STDIN and forward everything to server
|
||||
$loop->addReadStream(STDIN, function () use ($client, $loop, $tid) {
|
||||
$msg = fgets(STDIN, 2000);
|
||||
if ($msg === false) {
|
||||
// EOF => flush client and stop perodic sending and waiting for input
|
||||
$client->end();
|
||||
$loop->cancelTimer($tid);
|
||||
$loop->removeReadStream(STDIN);
|
||||
} else {
|
||||
$client->send(trim($msg));
|
||||
}
|
||||
});
|
||||
}, function($error) {
|
||||
echo 'ERROR: ' . $error->getMessage() . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
16
vendor/react/datagram/examples/server.php
vendored
Executable file
16
vendor/react/datagram/examples/server.php
vendored
Executable file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
$factory = new React\Datagram\Factory($loop);
|
||||
|
||||
$factory->createServer('localhost:1234')->then(function (React\Datagram\Socket $server) {
|
||||
$server->on('message', function($message, $address, $server) {
|
||||
$server->send('hello ' . $address . '! echo: ' . $message, $address);
|
||||
|
||||
echo 'client ' . $address . ': ' . $message . PHP_EOL;
|
||||
});
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
14
vendor/react/datagram/phpunit.xml.dist
vendored
Executable file
14
vendor/react/datagram/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit colors="true" bootstrap="./tests/bootstrap.php">
|
||||
<testsuites>
|
||||
<testsuite name="Datagram Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
118
vendor/react/datagram/src/Buffer.php
vendored
Executable file
118
vendor/react/datagram/src/Buffer.php
vendored
Executable file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace React\Datagram;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use \Exception;
|
||||
|
||||
class Buffer extends EventEmitter
|
||||
{
|
||||
protected $loop;
|
||||
protected $socket;
|
||||
|
||||
private $listening = false;
|
||||
private $outgoing = array();
|
||||
private $writable = true;
|
||||
|
||||
public function __construct(LoopInterface $loop, $socket)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
$this->socket = $socket;
|
||||
}
|
||||
|
||||
public function send($data, $remoteAddress = null)
|
||||
{
|
||||
if ($this->writable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->outgoing []= array($data, $remoteAddress);
|
||||
|
||||
if (!$this->listening) {
|
||||
$this->handleResume();
|
||||
$this->listening = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function onWritable()
|
||||
{
|
||||
list($data, $remoteAddress) = \array_shift($this->outgoing);
|
||||
|
||||
try {
|
||||
$this->handleWrite($data, $remoteAddress);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->emit('error', array($e, $this));
|
||||
}
|
||||
|
||||
if (!$this->outgoing) {
|
||||
if ($this->listening) {
|
||||
$this->handlePause();
|
||||
$this->listening = false;
|
||||
}
|
||||
|
||||
if (!$this->writable) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->socket === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('close', array($this));
|
||||
|
||||
if ($this->listening) {
|
||||
$this->handlePause();
|
||||
$this->listening = false;
|
||||
}
|
||||
|
||||
$this->writable = false;
|
||||
$this->socket = false;
|
||||
$this->outgoing = array();
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
if ($this->writable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->writable = false;
|
||||
|
||||
if (!$this->outgoing) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
protected function handlePause()
|
||||
{
|
||||
$this->loop->removeWriteStream($this->socket);
|
||||
}
|
||||
|
||||
protected function handleResume()
|
||||
{
|
||||
$this->loop->addWriteStream($this->socket, array($this, 'onWritable'));
|
||||
}
|
||||
|
||||
protected function handleWrite($data, $remoteAddress)
|
||||
{
|
||||
if ($remoteAddress === null) {
|
||||
// do not use fwrite() as it obeys the stream buffer size and
|
||||
// packets are not to be split at 8kb
|
||||
$ret = @\stream_socket_sendto($this->socket, $data);
|
||||
} else {
|
||||
$ret = @\stream_socket_sendto($this->socket, $data, 0, $remoteAddress);
|
||||
}
|
||||
|
||||
if ($ret < 0 || $ret === false) {
|
||||
$error = \error_get_last();
|
||||
throw new Exception('Unable to send packet: ' . \trim($error['message']));
|
||||
}
|
||||
}
|
||||
}
|
||||
139
vendor/react/datagram/src/Factory.php
vendored
Executable file
139
vendor/react/datagram/src/Factory.php
vendored
Executable file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace React\Datagram;
|
||||
|
||||
use React\Datagram\Socket;
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Resolver\Factory as DnsFactory;
|
||||
use React\Dns\Resolver\Resolver;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use \Exception;
|
||||
|
||||
class Factory
|
||||
{
|
||||
protected $loop;
|
||||
protected $resolver;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param Resolver|null $resolver Resolver instance to use. Will otherwise
|
||||
* try to load the system default DNS config or fall back to using
|
||||
* Google's public DNS 8.8.8.8
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, Resolver $resolver = null)
|
||||
{
|
||||
if ($resolver === null) {
|
||||
// try to load nameservers from system config or default to Google's public DNS
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? \reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new DnsFactory();
|
||||
$resolver = $factory->create($server, $loop);
|
||||
}
|
||||
|
||||
$this->loop = $loop;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function createClient($address)
|
||||
{
|
||||
$loop = $this->loop;
|
||||
|
||||
return $this->resolveAddress($address)->then(function ($address) use ($loop) {
|
||||
$socket = @\stream_socket_client($address, $errno, $errstr);
|
||||
if (!$socket) {
|
||||
throw new Exception('Unable to create client socket: ' . $errstr, $errno);
|
||||
}
|
||||
|
||||
return new Socket($loop, $socket);
|
||||
});
|
||||
}
|
||||
|
||||
public function createServer($address)
|
||||
{
|
||||
$loop = $this->loop;
|
||||
|
||||
return $this->resolveAddress($address)->then(function ($address) use ($loop) {
|
||||
$socket = @\stream_socket_server($address, $errno, $errstr, \STREAM_SERVER_BIND);
|
||||
if (!$socket) {
|
||||
throw new Exception('Unable to create server socket: ' . $errstr, $errno);
|
||||
}
|
||||
|
||||
return new Socket($loop, $socket);
|
||||
});
|
||||
}
|
||||
|
||||
protected function resolveAddress($address)
|
||||
{
|
||||
if (\strpos($address, '://') === false) {
|
||||
$address = 'udp://' . $address;
|
||||
}
|
||||
|
||||
// parse_url() does not accept null ports (random port assignment) => manually remove
|
||||
$nullport = false;
|
||||
if (\substr($address, -2) === ':0') {
|
||||
$address = \substr($address, 0, -2);
|
||||
$nullport = true;
|
||||
}
|
||||
|
||||
$parts = \parse_url($address);
|
||||
|
||||
if (!$parts || !isset($parts['host'])) {
|
||||
return Promise\resolve($address);
|
||||
}
|
||||
|
||||
if ($nullport) {
|
||||
$parts['port'] = 0;
|
||||
}
|
||||
|
||||
// remove square brackets for IPv6 addresses
|
||||
$host = \trim($parts['host'], '[]');
|
||||
|
||||
return $this->resolveHost($host)->then(function ($host) use ($parts) {
|
||||
$address = $parts['scheme'] . '://';
|
||||
|
||||
if (isset($parts['port']) && \strpos($host, ':') !== false) {
|
||||
// enclose IPv6 address in square brackets if a port will be appended
|
||||
$host = '[' . $host . ']';
|
||||
}
|
||||
|
||||
$address .= $host;
|
||||
|
||||
if (isset($parts['port'])) {
|
||||
$address .= ':' . $parts['port'];
|
||||
}
|
||||
|
||||
return $address;
|
||||
});
|
||||
}
|
||||
|
||||
protected function resolveHost($host)
|
||||
{
|
||||
// there's no need to resolve if the host is already given as an IP address
|
||||
if (false !== \filter_var($host, \FILTER_VALIDATE_IP)) {
|
||||
return Promise\resolve($host);
|
||||
}
|
||||
|
||||
$promise = $this->resolver->resolve($host);
|
||||
|
||||
// wrap DNS lookup in order to control cancellation behavior
|
||||
return new Promise\Promise(
|
||||
function ($resolve, $reject) use ($promise) {
|
||||
// forward promise resolution
|
||||
$promise->then($resolve, $reject);
|
||||
},
|
||||
function ($_, $reject) use ($promise) {
|
||||
// reject with custom message once cancelled
|
||||
$reject(new \RuntimeException('Cancelled creating socket during DNS lookup'));
|
||||
|
||||
// (try to) cancel pending DNS lookup, otherwise ignoring its results
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
135
vendor/react/datagram/src/Socket.php
vendored
Executable file
135
vendor/react/datagram/src/Socket.php
vendored
Executable file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace React\Datagram;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use Evenement\EventEmitter;
|
||||
use Exception;
|
||||
|
||||
class Socket extends EventEmitter implements SocketInterface
|
||||
{
|
||||
protected $loop;
|
||||
protected $socket;
|
||||
|
||||
protected $buffer;
|
||||
|
||||
public $bufferSize = 65536;
|
||||
|
||||
public function __construct(LoopInterface $loop, $socket, Buffer $buffer = null)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
$this->socket = $socket;
|
||||
|
||||
if ($buffer === null) {
|
||||
$buffer = new Buffer($loop, $socket);
|
||||
}
|
||||
$this->buffer = $buffer;
|
||||
|
||||
$that = $this;
|
||||
$this->buffer->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error, $that));
|
||||
});
|
||||
$this->buffer->on('close', array($this, 'close'));
|
||||
|
||||
$this->resume();
|
||||
}
|
||||
|
||||
public function getLocalAddress()
|
||||
{
|
||||
return $this->sanitizeAddress(@\stream_socket_get_name($this->socket, false));
|
||||
}
|
||||
|
||||
public function getRemoteAddress()
|
||||
{
|
||||
return $this->sanitizeAddress(@\stream_socket_get_name($this->socket, true));
|
||||
}
|
||||
|
||||
public function send($data, $remoteAddress = null)
|
||||
{
|
||||
$this->buffer->send($data, $remoteAddress);
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->loop->removeReadStream($this->socket);
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->socket !== false) {
|
||||
$this->loop->addReadStream($this->socket, array($this, 'onReceive'));
|
||||
}
|
||||
}
|
||||
|
||||
public function onReceive()
|
||||
{
|
||||
try {
|
||||
$data = $this->handleReceive($peer);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// emit error message and local socket
|
||||
$this->emit('error', array($e, $this));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('message', array($data, $peer, $this));
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->socket === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('close', array($this));
|
||||
$this->pause();
|
||||
|
||||
$this->handleClose();
|
||||
$this->socket = false;
|
||||
$this->buffer->close();
|
||||
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
$this->buffer->end();
|
||||
}
|
||||
|
||||
private function sanitizeAddress($address)
|
||||
{
|
||||
if ($address === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// this is an IPv6 address which includes colons but no square brackets
|
||||
$pos = \strrpos($address, ':');
|
||||
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
|
||||
$port = \substr($address, $pos + 1);
|
||||
$address = '[' . \substr($address, 0, $pos) . ']:' . $port;
|
||||
}
|
||||
return $address;
|
||||
}
|
||||
|
||||
protected function handleReceive(&$peerAddress)
|
||||
{
|
||||
$data = \stream_socket_recvfrom($this->socket, $this->bufferSize, 0, $peerAddress);
|
||||
|
||||
if ($data === false) {
|
||||
// receiving data failed => remote side rejected one of our packets
|
||||
// due to the nature of UDP, there's no way to tell which one exactly
|
||||
// $peer is not filled either
|
||||
|
||||
throw new Exception('Invalid message');
|
||||
}
|
||||
|
||||
$peerAddress = $this->sanitizeAddress($peerAddress);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function handleClose()
|
||||
{
|
||||
\fclose($this->socket);
|
||||
}
|
||||
}
|
||||
29
vendor/react/datagram/src/SocketInterface.php
vendored
Executable file
29
vendor/react/datagram/src/SocketInterface.php
vendored
Executable file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace React\Datagram;
|
||||
|
||||
use Evenement\EventEmitterInterface;
|
||||
|
||||
/**
|
||||
* interface very similar to React\Stream\Stream
|
||||
*
|
||||
* @event message($data, $remoteAddress, $thisSocket)
|
||||
* @event error($exception, $thisSocket)
|
||||
* @event close($thisSocket)
|
||||
*/
|
||||
interface SocketInterface extends EventEmitterInterface
|
||||
{
|
||||
public function send($data, $remoteAddress = null);
|
||||
|
||||
public function close();
|
||||
|
||||
public function end();
|
||||
|
||||
public function resume();
|
||||
|
||||
public function pause();
|
||||
|
||||
public function getLocalAddress();
|
||||
|
||||
public function getRemoteAddress();
|
||||
}
|
||||
181
vendor/react/datagram/tests/FactoryTest.php
vendored
Executable file
181
vendor/react/datagram/tests/FactoryTest.php
vendored
Executable file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
use React\Datagram\Socket;
|
||||
use React\Datagram\Factory;
|
||||
use Clue\React\Block;
|
||||
use React\Promise;
|
||||
|
||||
class FactoryTest extends TestCase
|
||||
{
|
||||
private $loop;
|
||||
private $resolver;
|
||||
private $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = React\EventLoop\Factory::create();
|
||||
$this->resolver = $this->createResolverMock();
|
||||
$this->factory = new Factory($this->loop, $this->resolver);
|
||||
}
|
||||
|
||||
public function testCreateClient()
|
||||
{
|
||||
$this->resolver->expects($this->never())->method('resolve');
|
||||
|
||||
$promise = $this->factory->createClient('127.0.0.1:12345');
|
||||
|
||||
$capturedClient = Block\await($promise, $this->loop);
|
||||
$this->assertInstanceOf('React\Datagram\Socket', $capturedClient);
|
||||
|
||||
$this->assertEquals('127.0.0.1:12345', $capturedClient->getRemoteAddress());
|
||||
|
||||
$this->assertContains('127.0.0.1:', $capturedClient->getLocalAddress());
|
||||
$this->assertNotEquals('127.0.0.1:12345', $capturedClient->getLocalAddress());
|
||||
|
||||
$capturedClient->close();
|
||||
|
||||
$this->assertNull($capturedClient->getRemoteAddress());
|
||||
}
|
||||
|
||||
public function testCreateClientLocalhost()
|
||||
{
|
||||
$this->resolver->expects($this->once())->method('resolve')->with('localhost')->willReturn(Promise\resolve('127.0.0.1'));
|
||||
|
||||
$promise = $this->factory->createClient('localhost:12345');
|
||||
|
||||
$capturedClient = Block\await($promise, $this->loop);
|
||||
$this->assertInstanceOf('React\Datagram\Socket', $capturedClient);
|
||||
|
||||
$this->assertEquals('127.0.0.1:12345', $capturedClient->getRemoteAddress());
|
||||
|
||||
$this->assertContains('127.0.0.1:', $capturedClient->getLocalAddress());
|
||||
$this->assertNotEquals('127.0.0.1:12345', $capturedClient->getLocalAddress());
|
||||
|
||||
$capturedClient->close();
|
||||
}
|
||||
|
||||
public function testCreateClientLocalhostWithDefaultResolver()
|
||||
{
|
||||
$this->resolver = null;
|
||||
$this->factory = new Factory($this->loop);
|
||||
|
||||
$promise = $this->factory->createClient('localhost:12345');
|
||||
|
||||
$capturedClient = Block\await($promise, $this->loop);
|
||||
$capturedClient->close();
|
||||
}
|
||||
|
||||
public function testCreateClientIpv6()
|
||||
{
|
||||
$promise = $this->factory->createClient('[::1]:12345');
|
||||
|
||||
try {
|
||||
$capturedClient = Block\await($promise, $this->loop);
|
||||
} catch (\Exception $e) {
|
||||
$this->markTestSkipped('Unable to start IPv6 client socket (IPv6 not supported on this system?)');
|
||||
}
|
||||
|
||||
$this->assertInstanceOf('React\Datagram\Socket', $capturedClient);
|
||||
|
||||
$this->assertEquals('[::1]:12345', $capturedClient->getRemoteAddress());
|
||||
|
||||
$this->assertContains('[::1]:', $capturedClient->getLocalAddress());
|
||||
$this->assertNotEquals('[::1]:12345', $capturedClient->getLocalAddress());
|
||||
|
||||
$capturedClient->close();
|
||||
}
|
||||
|
||||
public function testCreateServer()
|
||||
{
|
||||
$promise = $this->factory->createServer('127.0.0.1:12345');
|
||||
|
||||
$capturedServer = Block\await($promise, $this->loop);
|
||||
$this->assertInstanceOf('React\Datagram\Socket', $capturedServer);
|
||||
|
||||
$this->assertEquals('127.0.0.1:12345', $capturedServer->getLocalAddress());
|
||||
$this->assertNull($capturedServer->getRemoteAddress());
|
||||
|
||||
$capturedServer->close();
|
||||
|
||||
$this->assertNull($capturedServer->getLocalAddress());
|
||||
}
|
||||
|
||||
public function testCreateServerRandomPort()
|
||||
{
|
||||
$promise = $this->factory->createServer('127.0.0.1:0');
|
||||
|
||||
$capturedServer = Block\await($promise, $this->loop);
|
||||
$this->assertInstanceOf('React\Datagram\Socket', $capturedServer);
|
||||
|
||||
$this->assertNotEquals('127.0.0.1:0', $capturedServer->getLocalAddress());
|
||||
$this->assertNull($capturedServer->getRemoteAddress());
|
||||
|
||||
$capturedServer->close();
|
||||
}
|
||||
|
||||
public function testCreateClientWithIpWillNotUseResolver()
|
||||
{
|
||||
$this->resolver->expects($this->never())->method('resolve');
|
||||
|
||||
$client = Block\await($this->factory->createClient('127.0.0.1:0'), $this->loop);
|
||||
$client->close();
|
||||
}
|
||||
|
||||
public function testCreateClientWithHostnameWillUseResolver()
|
||||
{
|
||||
$this->resolver->expects($this->once())->method('resolve')->with('example.com')->willReturn(Promise\resolve('127.0.0.1'));
|
||||
|
||||
$client = Block\await($this->factory->createClient('example.com:0'), $this->loop);
|
||||
$client->close();
|
||||
}
|
||||
|
||||
public function testCreateClientWithHostnameWillRejectIfResolverRejects()
|
||||
{
|
||||
$this->resolver->expects($this->once())->method('resolve')->with('example.com')->willReturn(Promise\reject(new \RuntimeException('test')));
|
||||
|
||||
$this->setExpectedException('RuntimeException');
|
||||
Block\await($this->factory->createClient('example.com:0'), $this->loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionMessage Unable to create client socket
|
||||
*/
|
||||
public function testCreateClientWithInvalidHostnameWillReject()
|
||||
{
|
||||
Block\await($this->factory->createClient('/////'), $this->loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Exception
|
||||
* @expectedExceptionMessage Unable to create server socket
|
||||
*/
|
||||
public function testCreateServerWithInvalidHostnameWillReject()
|
||||
{
|
||||
Block\await($this->factory->createServer('/////'), $this->loop);
|
||||
}
|
||||
|
||||
public function testCancelCreateClientWithCancellableHostnameResolver()
|
||||
{
|
||||
$promise = new Promise\Promise(function () { }, $this->expectCallableOnce());
|
||||
$this->resolver->expects($this->once())->method('resolve')->with('example.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->factory->createClient('example.com:0');
|
||||
$promise->cancel();
|
||||
|
||||
$this->setExpectedException('RuntimeException');
|
||||
Block\await($promise, $this->loop);
|
||||
}
|
||||
|
||||
public function testCancelCreateClientWithUncancellableHostnameResolver()
|
||||
{
|
||||
$promise = $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
|
||||
$this->resolver->expects($this->once())->method('resolve')->with('example.com')->willReturn($promise);
|
||||
|
||||
$promise = $this->factory->createClient('example.com:0');
|
||||
$promise->cancel();
|
||||
|
||||
$this->setExpectedException('RuntimeException');
|
||||
Block\await($promise, $this->loop);
|
||||
}
|
||||
}
|
||||
194
vendor/react/datagram/tests/SocketTest.php
vendored
Executable file
194
vendor/react/datagram/tests/SocketTest.php
vendored
Executable file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
use React\Datagram\Socket;
|
||||
use Clue\React\Block;
|
||||
|
||||
class SocketTest extends TestCase
|
||||
{
|
||||
private $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = React\EventLoop\Factory::create();
|
||||
$this->factory = new React\Datagram\Factory($this->loop, $this->createResolverMock());
|
||||
}
|
||||
|
||||
public function testCreateClientCloseWillNotBlock()
|
||||
{
|
||||
$promise = $this->factory->createClient('127.0.0.1:12345');
|
||||
$client = Block\await($promise, $this->loop);
|
||||
|
||||
$client->send('test');
|
||||
$client->close();
|
||||
|
||||
$this->loop->run();
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Socket $client
|
||||
* @depends testCreateClientCloseWillNotBlock
|
||||
*/
|
||||
public function testClientCloseAgainWillNotBlock(Socket $client)
|
||||
{
|
||||
$client->close();
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testCreateClientEndWillNotBlock()
|
||||
{
|
||||
$promise = $this->factory->createClient('127.0.0.1:12345');
|
||||
$client = Block\await($promise, $this->loop);
|
||||
|
||||
$client->send('test');
|
||||
$client->end();
|
||||
|
||||
$this->loop->run();
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Socket $client
|
||||
* @depends testCreateClientEndWillNotBlock
|
||||
*/
|
||||
public function testClientEndAgainWillNotBlock(Socket $client)
|
||||
{
|
||||
$client->end();
|
||||
$this->loop->run();
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Socket $client
|
||||
* @depends testClientEndAgainWillNotBlock
|
||||
*/
|
||||
public function testClientSendAfterEndIsNoop(Socket $client)
|
||||
{
|
||||
$client->send('does not matter');
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testClientSendHugeWillFail()
|
||||
{
|
||||
$promise = $this->factory->createClient('127.0.0.1:12345');
|
||||
$client = Block\await($promise, $this->loop);
|
||||
|
||||
$client->send(str_repeat(1, 1024 * 1024));
|
||||
$client->on('error', $this->expectCallableOnce());
|
||||
$client->end();
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testClientSendNoServerWillFail()
|
||||
{
|
||||
$promise = $this->factory->createClient('127.0.0.1:1234');
|
||||
$client = Block\await($promise, $this->loop);
|
||||
|
||||
// send a message to a socket that is not actually listening
|
||||
// expect the remote end to reject this by sending an ICMP message
|
||||
// which we will receive as an error message. This depends on the
|
||||
// host to actually reject UDP datagrams, which not all systems do.
|
||||
$client->send('hello');
|
||||
$client->on('error', $this->expectCallableOnce());
|
||||
|
||||
$loop = $this->loop;
|
||||
$client->on('error', function () use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
|
||||
$that = $this;
|
||||
$this->loop->addTimer(1.0, function () use ($that, $loop) {
|
||||
$loop->stop();
|
||||
$that->markTestSkipped('UDP packet was not rejected after 0.5s, ignoring test');
|
||||
});
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testCreatePair()
|
||||
{
|
||||
$promise = $this->factory->createServer('127.0.0.1:0');
|
||||
$server = Block\await($promise, $this->loop);
|
||||
|
||||
$promise = $this->factory->createClient($server->getLocalAddress());
|
||||
$client = Block\await($promise, $this->loop);
|
||||
|
||||
$that = $this;
|
||||
$server->on('message', function ($message, $remote, $server) use ($that) {
|
||||
$that->assertEquals('test', $message);
|
||||
|
||||
// once the server receives a message, send it pack to client and stop server
|
||||
$server->send('response:' . $message, $remote);
|
||||
$server->end();
|
||||
});
|
||||
|
||||
$client->on('message', function ($message, $remote, $client) use ($that) {
|
||||
$that->assertEquals('response:test', $message);
|
||||
|
||||
// once the client receives a message, stop client
|
||||
$client->end();
|
||||
});
|
||||
|
||||
$client->send('test');
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function provideSanitizeAddress()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'127.0.0.1:1337',
|
||||
),
|
||||
array(
|
||||
'[::1]:1337',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideSanitizeAddress
|
||||
*/
|
||||
public function testSanitizeAddress($address)
|
||||
{
|
||||
$promise = $this->factory->createServer($address);
|
||||
|
||||
try {
|
||||
$server = Block\await($promise, $this->loop);
|
||||
} catch (\Exception $e) {
|
||||
if (strpos($address, '[') === false) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->markTestSkipped('Unable to start IPv6 server socket (IPv6 not supported on this system?)');
|
||||
}
|
||||
|
||||
$promise = $this->factory->createClient($server->getLocalAddress());
|
||||
$client = Block\await($promise, $this->loop);
|
||||
|
||||
$that = $this;
|
||||
$server->on('message', function ($message, $remote, $server) use ($that) {
|
||||
// once the server receives a message, send it pack to client and stop server
|
||||
$server->send('response:' . $message, $remote);
|
||||
$server->end();
|
||||
});
|
||||
|
||||
$client->on('message', function ($message, $remote, $client) use ($that, $address) {
|
||||
$that->assertEquals($address, $remote);
|
||||
|
||||
// once the client receives a message, stop client
|
||||
$client->end();
|
||||
});
|
||||
|
||||
$client->send('test');
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
}
|
||||
45
vendor/react/datagram/tests/bootstrap.php
vendored
Executable file
45
vendor/react/datagram/tests/bootstrap.php
vendored
Executable file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
abstract class TestCase extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function createCallableMock()
|
||||
{
|
||||
return $this->getMockBuilder('CallableStub')->getMock();
|
||||
}
|
||||
|
||||
protected function createResolverMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Dns\Resolver\Resolver')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
}
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
452
vendor/react/dns/CHANGELOG.md
vendored
Executable file
452
vendor/react/dns/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,452 @@
|
||||
# Changelog
|
||||
|
||||
## 1.13.0 (2024-06-13)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
||||
(#224 by @WyriHaximus)
|
||||
|
||||
## 1.12.0 (2023-11-29)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#217 by @sergiy-petrov)
|
||||
|
||||
* Update test environment and avoid unhandled promise rejections.
|
||||
(#215, #216 and #218 by @clue)
|
||||
|
||||
## 1.11.0 (2023-06-02)
|
||||
|
||||
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
|
||||
(#213 by @clue)
|
||||
|
||||
* Improve test suite and project setup and report failed assertions.
|
||||
(#210 by @clue, #212 by @WyriHaximus and #209 and #211 by @SimonFrings)
|
||||
|
||||
## 1.10.0 (2022-09-08)
|
||||
|
||||
* Feature: Full support for PHP 8.2 release.
|
||||
(#201 by @clue and #207 by @WyriHaximus)
|
||||
|
||||
* Feature: Optimize forward compatibility with Promise v3, avoid hitting autoloader.
|
||||
(#202 by @clue)
|
||||
|
||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
||||
(#197 by @clue)
|
||||
|
||||
* Fix: Fix invalid references in exception stack trace.
|
||||
(#191 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#195 by @SimonFrings and #203 by @nhedger)
|
||||
|
||||
* Improve test suite, update to use default loop and new reactphp/async package.
|
||||
(#204, #205 and #206 by @clue and #196 by @SimonFrings)
|
||||
|
||||
## 1.9.0 (2021-12-20)
|
||||
|
||||
* Feature: Full support for PHP 8.1 release and prepare PHP 8.2 compatibility
|
||||
by refactoring `Parser` to avoid assigning dynamic properties.
|
||||
(#188 and #186 by @clue and #184 by @SimonFrings)
|
||||
|
||||
* Feature: Avoid dependency on `ext-filter`.
|
||||
(#185 by @clue)
|
||||
|
||||
* Feature / Fix: Skip invalid nameserver entries from `resolv.conf` and ignore IPv6 zone IDs.
|
||||
(#187 by @clue)
|
||||
|
||||
* Feature / Fix: Reduce socket read chunk size for queries over TCP/IP.
|
||||
(#189 by @clue)
|
||||
|
||||
## 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).
|
||||
(#182 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->create($config, $loop);
|
||||
|
||||
// new (using default loop)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->create($config);
|
||||
```
|
||||
|
||||
## 1.7.0 (2021-06-25)
|
||||
|
||||
* Feature: Update DNS `Factory` to accept complete `Config` object.
|
||||
Add new `FallbackExecutor` and use fallback DNS servers when `Config` lists multiple servers.
|
||||
(#179 and #180 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
// new
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
$resolver = $factory->create($config, $loop);
|
||||
```
|
||||
|
||||
## 1.6.0 (2021-06-21)
|
||||
|
||||
* Feature: Add support for legacy `SPF` record type.
|
||||
(#178 by @akondas and @clue)
|
||||
|
||||
* Fix: Fix integer overflow for TCP/IP chunk size on 32 bit platforms.
|
||||
(#177 by @clue)
|
||||
|
||||
## 1.5.0 (2021-03-05)
|
||||
|
||||
* Feature: Improve error reporting when query fails, include domain and query type and DNS server address where applicable.
|
||||
(#174 by @clue)
|
||||
|
||||
* Feature: Improve error handling when sending data to DNS server fails (macOS).
|
||||
(#171 and #172 by @clue)
|
||||
|
||||
* Fix: Improve DNS response parser to limit recursion for compressed labels.
|
||||
(#169 by @clue)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
||||
(#170 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2020-09-18)
|
||||
|
||||
* Feature: Support upcoming PHP 8.
|
||||
(#168 by @clue)
|
||||
|
||||
* Improve test suite and update to PHPUnit 9.3.
|
||||
(#164 by @clue, #165 and #166 by @SimonFrings and #167 by @WyriHaximus)
|
||||
|
||||
## 1.3.0 (2020-07-10)
|
||||
|
||||
* Feature: Forward compatibility with react/promise v3.
|
||||
(#153 by @WyriHaximus)
|
||||
|
||||
* Feature: Support parsing `OPT` records (EDNS0).
|
||||
(#157 by @clue)
|
||||
|
||||
* Fix: Avoid PHP warnings due to lack of args in exception trace on PHP 7.4.
|
||||
(#160 by @clue)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Run tests on PHPUnit 9 and PHP 7.4 and clean up test suite.
|
||||
(#154 by @reedy, #156 by @clue and #163 by @SimonFrings)
|
||||
|
||||
## 1.2.0 (2019-08-15)
|
||||
|
||||
* Feature: Add `TcpTransportExecutor` to send DNS queries over TCP/IP connection,
|
||||
add `SelectiveTransportExecutor` to retry with TCP if UDP is truncated and
|
||||
automatically select transport protocol when no explicit `udp://` or `tcp://` scheme is given in `Factory`.
|
||||
(#145, #146, #147 and #148 by @clue)
|
||||
|
||||
* Feature: Support escaping literal dots and special characters in domain names.
|
||||
(#144 by @clue)
|
||||
|
||||
## 1.1.0 (2019-07-18)
|
||||
|
||||
* Feature: Support parsing `CAA` and `SSHFP` records.
|
||||
(#141 and #142 by @clue)
|
||||
|
||||
* Feature: Add `ResolverInterface` as common interface for `Resolver` class.
|
||||
(#139 by @clue)
|
||||
|
||||
* Fix: Add missing private property definitions and
|
||||
remove unneeded dependency on `react/stream`.
|
||||
(#140 and #143 by @clue)
|
||||
|
||||
## 1.0.0 (2019-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.
|
||||
|
||||
This update involves a number of BC breaks due to dropped support for
|
||||
deprecated functionality and some internal API cleanup. We've tried hard to
|
||||
avoid BC breaks where possible and minimize impact otherwise. We expect that
|
||||
most consumers of this package will actually not be affected by any BC
|
||||
breaks, see below for more details:
|
||||
|
||||
* BC break: Delete all deprecated APIs, use `Query` objects for `Message` questions
|
||||
instead of nested arrays and increase code coverage to 100%.
|
||||
(#130 by @clue)
|
||||
|
||||
* BC break: Move `$nameserver` from `ExecutorInterface` to `UdpTransportExecutor`,
|
||||
remove advanced/internal `UdpTransportExecutor` args for `Parser`/`BinaryDumper` and
|
||||
add API documentation for `ExecutorInterface`.
|
||||
(#135, #137 and #138 by @clue)
|
||||
|
||||
* BC break: Replace `HeaderBag` attributes with simple `Message` properties.
|
||||
(#132 by @clue)
|
||||
|
||||
* BC break: Mark all `Record` attributes as required, add documentation vs `Query`.
|
||||
(#136 by @clue)
|
||||
|
||||
* BC break: Mark all classes as final to discourage inheritance
|
||||
(#134 by @WyriHaximus)
|
||||
|
||||
## 0.4.19 (2019-07-10)
|
||||
|
||||
* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
|
||||
(#133 by @clue)
|
||||
|
||||
## 0.4.18 (2019-09-07)
|
||||
|
||||
* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
|
||||
respect TTL from response records when caching and do not cache truncated responses.
|
||||
(#129 by @clue)
|
||||
|
||||
* Feature: Limit cache size to 256 last responses by default.
|
||||
(#127 by @clue)
|
||||
|
||||
* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
|
||||
(#125 by @clue)
|
||||
|
||||
## 0.4.17 (2019-04-01)
|
||||
|
||||
* Feature: Support parsing `authority` and `additional` records from DNS response.
|
||||
(#123 by @clue)
|
||||
|
||||
* Feature: Support dumping records as part of outgoing binary DNS message.
|
||||
(#124 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
|
||||
(#121 by @clue)
|
||||
|
||||
* Improve test suite to add forward compatibility with PHPUnit 7,
|
||||
test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
|
||||
(#122 by @clue)
|
||||
|
||||
## 0.4.16 (2018-11-11)
|
||||
|
||||
* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
|
||||
(#118 by @clue)
|
||||
|
||||
* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
|
||||
malformed record data or malformed compressed domain name labels.
|
||||
(#115 and #117 by @clue)
|
||||
|
||||
* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
|
||||
(#116 by @clue)
|
||||
|
||||
* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
|
||||
(#112 by @clue)
|
||||
|
||||
## 0.4.15 (2018-07-02)
|
||||
|
||||
* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
|
||||
(#110 by @clue and @WyriHaximus)
|
||||
|
||||
```php
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
|
||||
(#104, #105, #106, #107 and #108 by @clue)
|
||||
|
||||
* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
|
||||
(#104 by @clue)
|
||||
|
||||
* Feature: Improve error messages for failed queries and improve documentation.
|
||||
(#109 by @clue)
|
||||
|
||||
* Feature: Add reverse DNS lookup example.
|
||||
(#111 by @clue)
|
||||
|
||||
## 0.4.14 (2018-06-26)
|
||||
|
||||
* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
|
||||
to avoid cache poisoning attacks and deprecate legacy `Executor`.
|
||||
(#101 and #103 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with Cache 0.5
|
||||
(#102 by @clue)
|
||||
|
||||
* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
|
||||
(#99 by @clue)
|
||||
|
||||
## 0.4.13 (2018-02-27)
|
||||
|
||||
* Add `Config::loadSystemConfigBlocking()` to load default system config
|
||||
and support parsing DNS config on all supported platforms
|
||||
(`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
|
||||
(#92, #93, #94 and #95 by @clue)
|
||||
|
||||
```php
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
```
|
||||
|
||||
* Remove unneeded cyclic dependency on react/socket
|
||||
(#96 by @clue)
|
||||
|
||||
## 0.4.12 (2018-01-14)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6,
|
||||
test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
|
||||
add test group to skip integration tests relying on internet connection
|
||||
and add minor documentation improvements.
|
||||
(#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
|
||||
|
||||
## 0.4.11 (2017-08-25)
|
||||
|
||||
* Feature: Support resolving from default hosts file
|
||||
(#75, #76 and #77 by @clue)
|
||||
|
||||
This means that resolving hosts such as `localhost` will now work as
|
||||
expected across all platforms with no changes required:
|
||||
|
||||
```php
|
||||
$resolver->resolve('localhost')->then(function ($ip) {
|
||||
echo 'IP: ' . $ip;
|
||||
});
|
||||
```
|
||||
|
||||
The new `HostsExecutor` exists for advanced usage and is otherwise used
|
||||
internally for this feature.
|
||||
|
||||
## 0.4.10 (2017-08-10)
|
||||
|
||||
* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
|
||||
lock minimum dependencies and work around circular dependency for tests
|
||||
(#70 and #71 by @clue)
|
||||
|
||||
* Fix: Work around DNS timeout issues for Windows users
|
||||
(#74 by @clue)
|
||||
|
||||
* Documentation and examples for advanced usage
|
||||
(#66 by @WyriHaximus)
|
||||
|
||||
* Remove broken TCP code, do not retry with invalid TCP query
|
||||
(#73 by @clue)
|
||||
|
||||
* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
|
||||
lock Travis distro so new defaults will not break the build and
|
||||
fix failing tests for PHP 7.1
|
||||
(#68 by @WyriHaximus and #69 and #72 by @clue)
|
||||
|
||||
## 0.4.9 (2017-05-01)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#61 by @clue)
|
||||
|
||||
## 0.4.8 (2017-04-16)
|
||||
|
||||
* Feature: Add support for the AAAA record type to the protocol parser
|
||||
(#58 by @othillo)
|
||||
|
||||
* Feature: Add support for the PTR record type to the protocol parser
|
||||
(#59 by @othillo)
|
||||
|
||||
## 0.4.7 (2017-03-31)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
|
||||
(#57 by @clue)
|
||||
|
||||
## 0.4.6 (2017-03-11)
|
||||
|
||||
* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
|
||||
with Stream v0.5 and upcoming v0.6
|
||||
(#53 by @clue)
|
||||
|
||||
* Improve test suite by adding PHPUnit to `require-dev`
|
||||
(#54 by @clue)
|
||||
|
||||
## 0.4.5 (2017-03-02)
|
||||
|
||||
* Fix: Ensure we ignore the case of the answer
|
||||
(#51 by @WyriHaximus)
|
||||
|
||||
* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
|
||||
code re-use for upcoming versions.
|
||||
(#48 and #49 by @clue)
|
||||
|
||||
## 0.4.4 (2017-02-13)
|
||||
|
||||
* Fix: Fix handling connection and stream errors
|
||||
(#45 by @clue)
|
||||
|
||||
* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
|
||||
(#46 and #47 by @clue)
|
||||
|
||||
## 0.4.3 (2016-07-31)
|
||||
|
||||
* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
|
||||
|
||||
```php
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
|
||||
$cache = new MyCustomCacheInstance();
|
||||
$resolver = $factory->createCached('8.8.8.8', $loop, $cache);
|
||||
```
|
||||
|
||||
* Feature: Support Promise cancellation (#35 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolve('reactphp.org');
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
## 0.4.2 (2016-02-24)
|
||||
|
||||
* Repository maintenance, split off from main repo, improve test suite and documentation
|
||||
* First class support for PHP7 and HHVM (#34 by @clue)
|
||||
* Adjust compatibility to 5.3 (#30 by @clue)
|
||||
|
||||
## 0.4.1 (2014-04-13)
|
||||
|
||||
* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
|
||||
|
||||
## 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
|
||||
* Bug fix: Properly resolve CNAME aliases
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
* Bump React dependencies to v0.4
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Feature: Support default port for IPv6 addresses (@clue)
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Bump React dependencies to v0.3
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Feature: New cache component, used by DNS
|
||||
|
||||
## 0.2.5 (2012-11-26)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.4 (2012-11-18)
|
||||
|
||||
* Feature: Change to promise-based API (@jsor)
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.2 (2012-10-28)
|
||||
|
||||
* Feature: DNS executor timeout handling (@arnaud-lb)
|
||||
* Feature: DNS retry executor (@arnaud-lb)
|
||||
|
||||
## 0.2.1 (2012-10-14)
|
||||
|
||||
* Minor adjustments to DNS parser
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Feature: DNS resolver
|
||||
21
vendor/react/dns/LICENSE
vendored
Executable file
21
vendor/react/dns/LICENSE
vendored
Executable 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.
|
||||
453
vendor/react/dns/README.md
vendored
Executable file
453
vendor/react/dns/README.md
vendored
Executable file
@@ -0,0 +1,453 @@
|
||||
# DNS
|
||||
|
||||
[](https://github.com/reactphp/dns/actions)
|
||||
[](https://packagist.org/packages/react/dns)
|
||||
|
||||
Async DNS resolver for [ReactPHP](https://reactphp.org/).
|
||||
|
||||
The main point of the DNS component is to provide async DNS resolution.
|
||||
However, it is really a toolkit for working with DNS messages, and could
|
||||
easily be used to create a DNS server.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Basic usage](#basic-usage)
|
||||
* [Caching](#caching)
|
||||
* [Custom cache adapter](#custom-cache-adapter)
|
||||
* [ResolverInterface](#resolverinterface)
|
||||
* [resolve()](#resolve)
|
||||
* [resolveAll()](#resolveall)
|
||||
* [Advanced usage](#advanced-usage)
|
||||
* [UdpTransportExecutor](#udptransportexecutor)
|
||||
* [TcpTransportExecutor](#tcptransportexecutor)
|
||||
* [SelectiveTransportExecutor](#selectivetransportexecutor)
|
||||
* [HostsFileExecutor](#hostsfileexecutor)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
* [References](#references)
|
||||
|
||||
## Basic usage
|
||||
|
||||
The most basic usage is to just create a resolver through the resolver
|
||||
factory. All you need to give it is a nameserver, then you can start resolving
|
||||
names, baby!
|
||||
|
||||
```php
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->create($config);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
```
|
||||
|
||||
See also the [first example](examples).
|
||||
|
||||
The `Config` class can be used to load the system default config. This is an
|
||||
operation that may access the filesystem and block. Ideally, this method should
|
||||
thus be executed only once before the loop starts and not repeatedly while it is
|
||||
running.
|
||||
Note that this class may return an *empty* configuration if the system config
|
||||
can not be loaded. As such, you'll likely want to apply a default nameserver
|
||||
as above if none can be found.
|
||||
|
||||
> Note that the factory loads the hosts file from the filesystem once when
|
||||
creating the resolver instance.
|
||||
Ideally, this method should thus be executed only once before the loop starts
|
||||
and not repeatedly while it is running.
|
||||
|
||||
But there's more.
|
||||
|
||||
## Caching
|
||||
|
||||
You can cache results by configuring the resolver to use a `CachedExecutor`:
|
||||
|
||||
```php
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached($config);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
|
||||
...
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
```
|
||||
|
||||
If the first call returns before the second, only one query will be executed.
|
||||
The second result will be served from an in memory cache.
|
||||
This is particularly useful for long running scripts where the same hostnames
|
||||
have to be looked up multiple times.
|
||||
|
||||
See also the [third example](examples).
|
||||
|
||||
### Custom cache adapter
|
||||
|
||||
By default, the above will use an in memory cache.
|
||||
|
||||
You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
|
||||
|
||||
```php
|
||||
$cache = new React\Cache\ArrayCache();
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached('8.8.8.8', null, $cache);
|
||||
```
|
||||
|
||||
See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
|
||||
|
||||
## ResolverInterface
|
||||
|
||||
<a id="resolver"><!-- legacy reference --></a>
|
||||
|
||||
### resolve()
|
||||
|
||||
The `resolve(string $domain): PromiseInterface<string>` method can be used to
|
||||
resolve the given $domain name to a single IPv4 address (type `A` query).
|
||||
|
||||
```php
|
||||
$resolver->resolve('reactphp.org')->then(function ($ip) {
|
||||
echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This is one of the main methods in this package. It sends a DNS query
|
||||
for the given $domain name to your DNS server and returns a single IP
|
||||
address on success.
|
||||
|
||||
If the DNS server sends a DNS response message that contains more than
|
||||
one IP address for this query, it will randomly pick one of the IP
|
||||
addresses from the response. If you want the full list of IP addresses
|
||||
or want to send a different type of query, you should use the
|
||||
[`resolveAll()`](#resolveall) method instead.
|
||||
|
||||
If the DNS server sends a DNS response message that indicates an error
|
||||
code, this method will reject with a `RecordNotFoundException`. Its
|
||||
message and code can be used to check for the response code.
|
||||
|
||||
If the DNS communication fails and the server does not respond with a
|
||||
valid response message, this message will reject with an `Exception`.
|
||||
|
||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolve('reactphp.org');
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
### resolveAll()
|
||||
|
||||
The `resolveAll(string $host, int $type): PromiseInterface<array>` method can be used to
|
||||
resolve all record values for the given $domain name and query $type.
|
||||
|
||||
```php
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
|
||||
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This is one of the main methods in this package. It sends a DNS query
|
||||
for the given $domain name to your DNS server and returns a list with all
|
||||
record values on success.
|
||||
|
||||
If the DNS server sends a DNS response message that contains one or more
|
||||
records for this query, it will return a list with all record values
|
||||
from the response. You can use the `Message::TYPE_*` constants to control
|
||||
which type of query will be sent. Note that this method always returns a
|
||||
list of record values, but each record value type depends on the query
|
||||
type. For example, it returns the IPv4 addresses for type `A` queries,
|
||||
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
|
||||
`CNAME` and `PTR` queries and structured data for other queries. See also
|
||||
the `Record` documentation for more details.
|
||||
|
||||
If the DNS server sends a DNS response message that indicates an error
|
||||
code, this method will reject with a `RecordNotFoundException`. Its
|
||||
message and code can be used to check for the response code.
|
||||
|
||||
If the DNS communication fails and the server does not respond with a
|
||||
valid response message, this message will reject with an `Exception`.
|
||||
|
||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### UdpTransportExecutor
|
||||
|
||||
The `UdpTransportExecutor` can be used to
|
||||
send DNS queries over a UDP transport.
|
||||
|
||||
This is the main class that sends a DNS query to your DNS server and is used
|
||||
internally by the `Resolver` for the actual message transport.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `igor.io`.
|
||||
|
||||
```php
|
||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
See also the [fourth example](examples).
|
||||
|
||||
Note that this executor does not implement a timeout, so you will very likely
|
||||
want to use this in combination with a `TimeoutExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
);
|
||||
```
|
||||
|
||||
Also note that this executor uses an unreliable UDP transport and that it
|
||||
does not implement any retry logic, so you will likely want to use this in
|
||||
combination with a `RetryExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new RetryExecutor(
|
||||
new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
any number of queries concurrently. You should probably limit the number of
|
||||
concurrent queries in your application or you're very likely going to face
|
||||
rate limitations and bans on the resolver end. For many common applications,
|
||||
you may want to avoid sending the same query multiple times when the first
|
||||
one is still pending, so you will likely want to use this in combination with
|
||||
a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
> Internally, this class uses PHP's UDP sockets and does not take advantage
|
||||
of [react/datagram](https://github.com/reactphp/datagram) purely for
|
||||
organizational reasons to avoid a cyclic dependency between the two
|
||||
packages. Higher-level components should take advantage of the Datagram
|
||||
component instead of reimplementing this socket logic from scratch.
|
||||
|
||||
### TcpTransportExecutor
|
||||
|
||||
The `TcpTransportExecutor` class can be used to
|
||||
send DNS queries over a TCP/IP stream transport.
|
||||
|
||||
This is one of the main classes that send a DNS query to your DNS server.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
|
||||
```php
|
||||
$executor = new TcpTransportExecutor('8.8.8.8:53');
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
See also [example #92](examples).
|
||||
|
||||
Note that this executor does not implement a timeout, so you will very likely
|
||||
want to use this in combination with a `TimeoutExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver),
|
||||
3.0
|
||||
);
|
||||
```
|
||||
|
||||
Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
|
||||
transport, so you do not necessarily have to implement any retry logic.
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
queries concurrently. The first query will establish a TCP/IP socket
|
||||
connection to the DNS server which will be kept open for a short period.
|
||||
Additional queries will automatically reuse this existing socket connection
|
||||
to the DNS server, will pipeline multiple requests over this single
|
||||
connection and will keep an idle connection open for a short period. The
|
||||
initial TCP/IP connection overhead may incur a slight delay if you only send
|
||||
occasional queries – when sending a larger number of concurrent queries over
|
||||
an existing connection, it becomes increasingly more efficient and avoids
|
||||
creating many concurrent sockets like the UDP-based executor. You may still
|
||||
want to limit the number of (concurrent) queries in your application or you
|
||||
may be facing rate limitations and bans on the resolver end. For many common
|
||||
applications, you may want to avoid sending the same query multiple times
|
||||
when the first one is still pending, so you will likely want to use this in
|
||||
combination with a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
> Internally, this class uses PHP's TCP/IP sockets and does not take advantage
|
||||
of [react/socket](https://github.com/reactphp/socket) purely for
|
||||
organizational reasons to avoid a cyclic dependency between the two
|
||||
packages. Higher-level components should take advantage of the Socket
|
||||
component instead of reimplementing this socket logic from scratch.
|
||||
|
||||
### SelectiveTransportExecutor
|
||||
|
||||
The `SelectiveTransportExecutor` class can be used to
|
||||
Send DNS queries over a UDP or TCP/IP stream transport.
|
||||
|
||||
This class will automatically choose the correct transport protocol to send
|
||||
a DNS query to your DNS server. It will always try to send it over the more
|
||||
efficient UDP transport first. If this query yields a size related issue
|
||||
(truncated messages), it will retry over a streaming TCP/IP transport.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
|
||||
```php
|
||||
$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
Note that this executor only implements the logic to select the correct
|
||||
transport for the given DNS query. Implementing the correct transport logic,
|
||||
implementing timeouts and any retry logic is left up to the given executors,
|
||||
see also [`UdpTransportExecutor`](#udptransportexecutor) and
|
||||
[`TcpTransportExecutor`](#tcptransportexecutor) for more details.
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
any number of queries concurrently. You should probably limit the number of
|
||||
concurrent queries in your application or you're very likely going to face
|
||||
rate limitations and bans on the resolver end. For many common applications,
|
||||
you may want to avoid sending the same query multiple times when the first
|
||||
one is still pending, so you will likely want to use this in combination with
|
||||
a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new SelectiveTransportExecutor(
|
||||
$datagramExecutor,
|
||||
$streamExecutor
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### HostsFileExecutor
|
||||
|
||||
Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
|
||||
If you also want to take entries from your hosts file into account, you may
|
||||
use this code:
|
||||
|
||||
```php
|
||||
$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
|
||||
|
||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
$executor = new HostsFileExecutor($hosts, $executor);
|
||||
|
||||
$executor->query(
|
||||
new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
|
||||
);
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](https://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/dns:^1.13
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
The test suite also contains a number of functional integration tests that rely
|
||||
on a stable internet connection.
|
||||
If you do not want to run these, they can simply be skipped like this:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit --exclude-group internet
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
|
||||
## References
|
||||
|
||||
* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
|
||||
* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
|
||||
49
vendor/react/dns/composer.json
vendored
Executable file
49
vendor/react/dns/composer.json
vendored
Executable file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "react/dns",
|
||||
"description": "Async DNS resolver for ReactPHP",
|
||||
"keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
|
||||
"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",
|
||||
"react/cache": "^1.0 || ^0.6 || ^0.5",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4.3 || ^3 || ^2",
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Dns\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Dns\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/BadServerException.php
vendored
Executable file
7
vendor/react/dns/src/BadServerException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
final class BadServerException extends \Exception
|
||||
{
|
||||
}
|
||||
137
vendor/react/dns/src/Config/Config.php
vendored
Executable file
137
vendor/react/dns/src/Config/Config.php
vendored
Executable file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class Config
|
||||
{
|
||||
/**
|
||||
* Loads the system DNS configuration
|
||||
*
|
||||
* Note that this method may block while loading its internal files and/or
|
||||
* commands and should thus be used with care! While this should be
|
||||
* relatively fast for most systems, it remains unknown if this may block
|
||||
* under certain circumstances. In particular, this method should only be
|
||||
* executed before the loop starts, not while it is running.
|
||||
*
|
||||
* Note that this method will try to access its files and/or commands and
|
||||
* try to parse its output. Currently, this will only parse valid nameserver
|
||||
* entries from its output and will ignore all other output without
|
||||
* complaining.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid nameserver entries can be found.
|
||||
*
|
||||
* @return self
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function loadSystemConfigBlocking()
|
||||
{
|
||||
// Use WMIC output on Windows
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
return self::loadWmicBlocking();
|
||||
}
|
||||
|
||||
// otherwise (try to) load from resolv.conf
|
||||
try {
|
||||
return self::loadResolvConfBlocking();
|
||||
} catch (RuntimeException $ignored) {
|
||||
// return empty config if parsing fails (file not found)
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a resolv.conf file (from the given path or default location)
|
||||
*
|
||||
* Note that this method blocks while loading the given path and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* resolv.conf files, this may be an issue if this file is located on a slow
|
||||
* device or contains an excessive number of entries. In particular, this
|
||||
* method should only be executed before the loop starts, not while it is
|
||||
* running.
|
||||
*
|
||||
* Note that this method will throw if the given file can not be loaded,
|
||||
* such as if it is not readable or does not exist. In particular, this file
|
||||
* is not available on Windows.
|
||||
*
|
||||
* Currently, this will only parse valid "nameserver X" lines from the
|
||||
* given file contents. Lines can be commented out with "#" and ";" and
|
||||
* invalid lines will be ignored without complaining. See also
|
||||
* `man resolv.conf` for more details.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid "nameserver X" lines can be found. See also
|
||||
* `man resolv.conf` which suggests that the DNS server on the localhost
|
||||
* should be used in this case. This is left up to higher level consumers
|
||||
* of this API.
|
||||
*
|
||||
* @param ?string $path (optional) path to resolv.conf file or null=load default location
|
||||
* @return self
|
||||
* @throws RuntimeException if the path can not be loaded (does not exist)
|
||||
*/
|
||||
public static function loadResolvConfBlocking($path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = '/etc/resolv.conf';
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($path);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"');
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
|
||||
|
||||
$config = new self();
|
||||
foreach ($matches[1] as $ip) {
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
|
||||
$ip = substr($ip, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($ip) !== false) {
|
||||
$config->nameservers[] = $ip;
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the DNS configurations from Windows's WMIC (from the given command or default command)
|
||||
*
|
||||
* Note that this method blocks while loading the given command and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* WMIC commands, it remains unknown if this may block under certain
|
||||
* circumstances. In particular, this method should only be executed before
|
||||
* the loop starts, not while it is running.
|
||||
*
|
||||
* Note that this method will only try to execute the given command try to
|
||||
* parse its output, irrespective of whether this command exists. In
|
||||
* particular, this command is only available on Windows. Currently, this
|
||||
* will only parse valid nameserver entries from the command output and will
|
||||
* ignore all other output without complaining.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid nameserver entries can be found.
|
||||
*
|
||||
* @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
|
||||
* @return self
|
||||
* @link https://ss64.com/nt/wmic.html
|
||||
*/
|
||||
public static function loadWmicBlocking($command = null)
|
||||
{
|
||||
$contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
|
||||
preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
|
||||
|
||||
$config = new self();
|
||||
$config->nameservers = $matches[1];
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public $nameservers = array();
|
||||
}
|
||||
153
vendor/react/dns/src/Config/HostsFile.php
vendored
Executable file
153
vendor/react/dns/src/Config/HostsFile.php
vendored
Executable file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Represents a static hosts file which maps hostnames to IPs
|
||||
*
|
||||
* Hosts files are used on most systems to avoid actually hitting the DNS for
|
||||
* certain common hostnames.
|
||||
*
|
||||
* Most notably, this file usually contains an entry to map "localhost" to the
|
||||
* local IP. Windows is a notable exception here, as Windows does not actually
|
||||
* include "localhost" in this file by default. To compensate for this, this
|
||||
* class may explicitly be wrapped in another HostsFile instance which
|
||||
* hard-codes these entries for Windows (see also Factory).
|
||||
*
|
||||
* This class mostly exists to abstract the parsing/extraction process so this
|
||||
* can be replaced with a faster alternative in the future.
|
||||
*/
|
||||
class HostsFile
|
||||
{
|
||||
/**
|
||||
* Returns the default path for the hosts file on this system
|
||||
*
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function getDefaultPath()
|
||||
{
|
||||
// use static path for all Unix-based systems
|
||||
if (DIRECTORY_SEPARATOR !== '\\') {
|
||||
return '/etc/hosts';
|
||||
}
|
||||
|
||||
// Windows actually stores the path in the registry under
|
||||
// \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
|
||||
$path = '%SystemRoot%\\system32\drivers\etc\hosts';
|
||||
|
||||
$base = getenv('SystemRoot');
|
||||
if ($base === false) {
|
||||
$base = 'C:\\Windows';
|
||||
}
|
||||
|
||||
return str_replace('%SystemRoot%', $base, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a hosts file (from the given path or default location)
|
||||
*
|
||||
* Note that this method blocks while loading the given path and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* hosts file, this may be an issue if this file is located on a slow device
|
||||
* or contains an excessive number of entries. In particular, this method
|
||||
* should only be executed before the loop starts, not while it is running.
|
||||
*
|
||||
* @param ?string $path (optional) path to hosts file or null=load default location
|
||||
* @return self
|
||||
* @throws RuntimeException if the path can not be loaded (does not exist)
|
||||
*/
|
||||
public static function loadFromPathBlocking($path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = self::getDefaultPath();
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($path);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException('Unable to load hosts file "' . $path . '"');
|
||||
}
|
||||
|
||||
return new self($contents);
|
||||
}
|
||||
|
||||
private $contents;
|
||||
|
||||
/**
|
||||
* Instantiate new hosts file with the given hosts file contents
|
||||
*
|
||||
* @param string $contents
|
||||
*/
|
||||
public function __construct($contents)
|
||||
{
|
||||
// remove all comments from the contents
|
||||
$contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
|
||||
|
||||
$this->contents = $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all IPs for the given hostname
|
||||
*
|
||||
* @param string $name
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIpsForHost($name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
$ips = array();
|
||||
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
|
||||
$parts = preg_split('/\s+/', $line);
|
||||
$ip = array_shift($parts);
|
||||
if ($parts && array_search($name, $parts) !== false) {
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
|
||||
$ip = substr($ip, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($ip) !== false) {
|
||||
$ips[] = $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all hostnames for the given IPv4 or IPv6 address
|
||||
*
|
||||
* @param string $ip
|
||||
* @return string[]
|
||||
*/
|
||||
public function getHostsForIp($ip)
|
||||
{
|
||||
// check binary representation of IP to avoid string case and short notation
|
||||
$ip = @inet_pton($ip);
|
||||
if ($ip === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$names = array();
|
||||
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
|
||||
$parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$addr = (string) array_shift($parts);
|
||||
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
|
||||
$addr = substr($addr, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($addr) === $ip) {
|
||||
foreach ($parts as $part) {
|
||||
$names[] = $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
}
|
||||
230
vendor/react/dns/src/Model/Message.php
vendored
Executable file
230
vendor/react/dns/src/Model/Message.php
vendored
Executable file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
use React\Dns\Query\Query;
|
||||
|
||||
/**
|
||||
* This class represents an outgoing query message or an incoming response message
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
*/
|
||||
final class Message
|
||||
{
|
||||
const TYPE_A = 1;
|
||||
const TYPE_NS = 2;
|
||||
const TYPE_CNAME = 5;
|
||||
const TYPE_SOA = 6;
|
||||
const TYPE_PTR = 12;
|
||||
const TYPE_MX = 15;
|
||||
const TYPE_TXT = 16;
|
||||
const TYPE_AAAA = 28;
|
||||
const TYPE_SRV = 33;
|
||||
const TYPE_SSHFP = 44;
|
||||
|
||||
/**
|
||||
* pseudo-type for EDNS0
|
||||
*
|
||||
* These are included in the additional section and usually not in answer section.
|
||||
* Defined in [RFC 6891](https://tools.ietf.org/html/rfc6891) (or older
|
||||
* [RFC 2671](https://tools.ietf.org/html/rfc2671)).
|
||||
*
|
||||
* The OPT record uses the "class" field to store the maximum size.
|
||||
*
|
||||
* The OPT record uses the "ttl" field to store additional flags.
|
||||
*/
|
||||
const TYPE_OPT = 41;
|
||||
|
||||
/**
|
||||
* Sender Policy Framework (SPF) had a dedicated SPF type which has been
|
||||
* deprecated in favor of reusing the existing TXT type.
|
||||
*
|
||||
* @deprecated https://datatracker.ietf.org/doc/html/rfc7208#section-3.1
|
||||
* @see self::TYPE_TXT
|
||||
*/
|
||||
const TYPE_SPF = 99;
|
||||
|
||||
const TYPE_ANY = 255;
|
||||
const TYPE_CAA = 257;
|
||||
|
||||
const CLASS_IN = 1;
|
||||
|
||||
const OPCODE_QUERY = 0;
|
||||
const OPCODE_IQUERY = 1; // inverse query
|
||||
const OPCODE_STATUS = 2;
|
||||
|
||||
const RCODE_OK = 0;
|
||||
const RCODE_FORMAT_ERROR = 1;
|
||||
const RCODE_SERVER_FAILURE = 2;
|
||||
const RCODE_NAME_ERROR = 3;
|
||||
const RCODE_NOT_IMPLEMENTED = 4;
|
||||
const RCODE_REFUSED = 5;
|
||||
|
||||
/**
|
||||
* The edns-tcp-keepalive EDNS0 Option
|
||||
*
|
||||
* Option value contains a `?float` with timeout in seconds (in 0.1s steps)
|
||||
* for DNS response or `null` for DNS query.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7828
|
||||
*/
|
||||
const OPT_TCP_KEEPALIVE = 11;
|
||||
|
||||
/**
|
||||
* The EDNS(0) Padding Option
|
||||
*
|
||||
* Option value contains a `string` with binary data (usually variable
|
||||
* number of null bytes)
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7830
|
||||
*/
|
||||
const OPT_PADDING = 12;
|
||||
|
||||
/**
|
||||
* Creates a new request message for the given query
|
||||
*
|
||||
* @param Query $query
|
||||
* @return self
|
||||
*/
|
||||
public static function createRequestForQuery(Query $query)
|
||||
{
|
||||
$request = new Message();
|
||||
$request->id = self::generateId();
|
||||
$request->rd = true;
|
||||
$request->questions[] = $query;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new response message for the given query with the given answer records
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Record[] $answers
|
||||
* @return self
|
||||
*/
|
||||
public static function createResponseWithAnswersForQuery(Query $query, array $answers)
|
||||
{
|
||||
$response = new Message();
|
||||
$response->id = self::generateId();
|
||||
$response->qr = true;
|
||||
$response->rd = true;
|
||||
|
||||
$response->questions[] = $query;
|
||||
|
||||
foreach ($answers as $record) {
|
||||
$response->answers[] = $record;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* generates a random 16 bit message ID
|
||||
*
|
||||
* This uses a CSPRNG so that an outside attacker that is sending spoofed
|
||||
* DNS response messages can not guess the message ID to avoid possible
|
||||
* cache poisoning attacks.
|
||||
*
|
||||
* The `random_int()` function is only available on PHP 7+ or when
|
||||
* https://github.com/paragonie/random_compat is installed. As such, using
|
||||
* the latest supported PHP version is highly recommended. This currently
|
||||
* falls back to a less secure random number generator on older PHP versions
|
||||
* in the hope that this system is properly protected against outside
|
||||
* attackers, for example by using one of the common local DNS proxy stubs.
|
||||
*
|
||||
* @return int
|
||||
* @see self::getId()
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private static function generateId()
|
||||
{
|
||||
if (function_exists('random_int')) {
|
||||
return random_int(0, 0xffff);
|
||||
}
|
||||
return mt_rand(0, 0xffff);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 16 bit message ID
|
||||
*
|
||||
* The response message ID has to match the request message ID. This allows
|
||||
* the receiver to verify this is the correct response message. An outside
|
||||
* attacker may try to inject fake responses by "guessing" the message ID,
|
||||
* so this should use a proper CSPRNG to avoid possible cache poisoning.
|
||||
*
|
||||
* @var int 16 bit message ID
|
||||
* @see self::generateId()
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* @var bool Query/Response flag, query=false or response=true
|
||||
*/
|
||||
public $qr = false;
|
||||
|
||||
/**
|
||||
* @var int specifies the kind of query (4 bit), see self::OPCODE_* constants
|
||||
* @see self::OPCODE_QUERY
|
||||
*/
|
||||
public $opcode = self::OPCODE_QUERY;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var bool Authoritative Answer
|
||||
*/
|
||||
public $aa = false;
|
||||
|
||||
/**
|
||||
* @var bool TrunCation
|
||||
*/
|
||||
public $tc = false;
|
||||
|
||||
/**
|
||||
* @var bool Recursion Desired
|
||||
*/
|
||||
public $rd = false;
|
||||
|
||||
/**
|
||||
* @var bool Recursion Available
|
||||
*/
|
||||
public $ra = false;
|
||||
|
||||
/**
|
||||
* @var int response code (4 bit), see self::RCODE_* constants
|
||||
* @see self::RCODE_OK
|
||||
*/
|
||||
public $rcode = Message::RCODE_OK;
|
||||
|
||||
/**
|
||||
* An array of Query objects
|
||||
*
|
||||
* ```php
|
||||
* $questions = array(
|
||||
* new Query(
|
||||
* 'reactphp.org',
|
||||
* Message::TYPE_A,
|
||||
* Message::CLASS_IN
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @var Query[]
|
||||
*/
|
||||
public $questions = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $answers = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $authority = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $additional = array();
|
||||
}
|
||||
153
vendor/react/dns/src/Model/Record.php
vendored
Executable file
153
vendor/react/dns/src/Model/Record.php
vendored
Executable file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
/**
|
||||
* This class represents a single resulting record in a response message
|
||||
*
|
||||
* It uses a structure similar to `\React\Dns\Query\Query`, but does include
|
||||
* fields for resulting TTL and resulting record data (IPs etc.).
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
* @see \React\Dns\Query\Query
|
||||
*/
|
||||
final class Record
|
||||
{
|
||||
/**
|
||||
* @var string hostname without trailing dot, for example "reactphp.org"
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int see Message::TYPE_* constants (UINT16)
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Defines the network class, usually `Message::CLASS_IN`.
|
||||
*
|
||||
* For `OPT` records (EDNS0), this defines the maximum message size instead.
|
||||
*
|
||||
* @var int see Message::CLASS_IN constant (UINT16)
|
||||
* @see Message::CLASS_IN
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* Defines the maximum time-to-live (TTL) in seconds
|
||||
*
|
||||
* For `OPT` records (EDNS0), this defines additional flags instead.
|
||||
*
|
||||
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
|
||||
* @link https://tools.ietf.org/html/rfc2181#section-8
|
||||
* @link https://tools.ietf.org/html/rfc6891#section-6.1.3 for `OPT` records (EDNS0)
|
||||
*/
|
||||
public $ttl;
|
||||
|
||||
/**
|
||||
* The payload data for this record
|
||||
*
|
||||
* The payload data format depends on the record type. As a rule of thumb,
|
||||
* this library will try to express this in a way that can be consumed
|
||||
* easily without having to worry about DNS internals and its binary transport:
|
||||
*
|
||||
* - A:
|
||||
* IPv4 address string, for example "192.168.1.1".
|
||||
*
|
||||
* - AAAA:
|
||||
* IPv6 address string, for example "::1".
|
||||
*
|
||||
* - CNAME / PTR / NS:
|
||||
* The hostname without trailing dot, for example "reactphp.org".
|
||||
*
|
||||
* - TXT:
|
||||
* List of string values, for example `["v=spf1 include:example.com"]`.
|
||||
* This is commonly a list with only a single string value, but this
|
||||
* technically allows multiple strings (0-255 bytes each) in a single
|
||||
* record. This is rarely used and depending on application you may want
|
||||
* to join these together or handle them separately. Each string can
|
||||
* transport any binary data, its character encoding is not defined (often
|
||||
* ASCII/UTF-8 in practice). [RFC 1464](https://tools.ietf.org/html/rfc1464)
|
||||
* suggests using key-value pairs such as `["name=test","version=1"]`, but
|
||||
* interpretation of this is not enforced and left up to consumers of this
|
||||
* library (used for DNS-SD/Zeroconf and others).
|
||||
*
|
||||
* - MX:
|
||||
* Mail server priority (UINT16) and target hostname without trailing dot,
|
||||
* for example `{"priority":10,"target":"mx.example.com"}`.
|
||||
* The payload data uses an associative array with fixed keys "priority"
|
||||
* (also commonly referred to as weight or preference) and "target" (also
|
||||
* referred to as exchange). If a response message contains multiple
|
||||
* records of this type, targets should be sorted by priority (lowest
|
||||
* first) - this is left up to consumers of this library (used for SMTP).
|
||||
*
|
||||
* - SRV:
|
||||
* Service priority (UINT16), service weight (UINT16), service port (UINT16)
|
||||
* and target hostname without trailing dot, for example
|
||||
* `{"priority":10,"weight":50,"port":8080,"target":"example.com"}`.
|
||||
* The payload data uses an associative array with fixed keys "priority",
|
||||
* "weight", "port" and "target" (also referred to as name).
|
||||
* The target may be an empty host name string if the service is decidedly
|
||||
* not available. If a response message contains multiple records of this
|
||||
* type, targets should be sorted by priority (lowest first) and selected
|
||||
* randomly according to their weight - this is left up to consumers of
|
||||
* this library, see also [RFC 2782](https://tools.ietf.org/html/rfc2782)
|
||||
* for more details.
|
||||
*
|
||||
* - SSHFP:
|
||||
* Includes algorithm (UNIT8), fingerprint type (UNIT8) and fingerprint
|
||||
* value as lower case hex string, for example:
|
||||
* `{"algorithm":1,"type":1,"fingerprint":"0123456789abcdef..."}`
|
||||
* See also https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml
|
||||
* for algorithm and fingerprint type assignments.
|
||||
*
|
||||
* - SOA:
|
||||
* Includes master hostname without trailing dot, responsible person email
|
||||
* as hostname without trailing dot and serial, refresh, retry, expire and
|
||||
* minimum times in seconds (UINT32 each), for example:
|
||||
* `{"mname":"ns.example.com","rname":"hostmaster.example.com","serial":
|
||||
* 2018082601,"refresh":3600,"retry":1800,"expire":60000,"minimum":3600}`.
|
||||
*
|
||||
* - CAA:
|
||||
* Includes flag (UNIT8), tag string and value string, for example:
|
||||
* `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
|
||||
*
|
||||
* - OPT:
|
||||
* Special pseudo-type for EDNS0. Includes an array of additional opt codes
|
||||
* with a value according to the respective OPT code. See `Message::OPT_*`
|
||||
* for list of supported OPT codes. Any other OPT code not currently
|
||||
* supported will be an opaque binary string containing the raw data
|
||||
* as transported in the DNS record. For forwards compatibility, you should
|
||||
* not rely on this format for unknown types. Future versions may add
|
||||
* support for new types and this may then parse the payload data
|
||||
* appropriately - this will not be considered a BC break. See also
|
||||
* [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
|
||||
*
|
||||
* - Any other unknown type:
|
||||
* An opaque binary string containing the RDATA as transported in the DNS
|
||||
* record. For forwards compatibility, you should not rely on this format
|
||||
* for unknown types. Future versions may add support for new types and
|
||||
* this may then parse the payload data appropriately - this will not be
|
||||
* considered a BC break. See the format definition of known types above
|
||||
* for more details.
|
||||
*
|
||||
* @var string|string[]|array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @param int $class
|
||||
* @param int $ttl
|
||||
* @param string|string[]|array $data
|
||||
*/
|
||||
public function __construct($name, $type, $class, $ttl, $data)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
$this->ttl = $ttl;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
199
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Executable file
199
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Executable file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\Query;
|
||||
|
||||
final class BinaryDumper
|
||||
{
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return string
|
||||
*/
|
||||
public function toBinary(Message $message)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= $this->headerToBinary($message);
|
||||
$data .= $this->questionToBinary($message->questions);
|
||||
$data .= $this->recordsToBinary($message->answers);
|
||||
$data .= $this->recordsToBinary($message->authority);
|
||||
$data .= $this->recordsToBinary($message->additional);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return string
|
||||
*/
|
||||
private function headerToBinary(Message $message)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= pack('n', $message->id);
|
||||
|
||||
$flags = 0x00;
|
||||
$flags = ($flags << 1) | ($message->qr ? 1 : 0);
|
||||
$flags = ($flags << 4) | $message->opcode;
|
||||
$flags = ($flags << 1) | ($message->aa ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->tc ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->rd ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->ra ? 1 : 0);
|
||||
$flags = ($flags << 3) | 0; // skip unused zero bit
|
||||
$flags = ($flags << 4) | $message->rcode;
|
||||
|
||||
$data .= pack('n', $flags);
|
||||
|
||||
$data .= pack('n', count($message->questions));
|
||||
$data .= pack('n', count($message->answers));
|
||||
$data .= pack('n', count($message->authority));
|
||||
$data .= pack('n', count($message->additional));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query[] $questions
|
||||
* @return string
|
||||
*/
|
||||
private function questionToBinary(array $questions)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$data .= $this->domainNameToBinary($question->name);
|
||||
$data .= pack('n*', $question->type, $question->class);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Record[] $records
|
||||
* @return string
|
||||
*/
|
||||
private function recordsToBinary(array $records)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
foreach ($records as $record) {
|
||||
/* @var $record Record */
|
||||
switch ($record->type) {
|
||||
case Message::TYPE_A:
|
||||
case Message::TYPE_AAAA:
|
||||
$binary = \inet_pton($record->data);
|
||||
break;
|
||||
case Message::TYPE_CNAME:
|
||||
case Message::TYPE_NS:
|
||||
case Message::TYPE_PTR:
|
||||
$binary = $this->domainNameToBinary($record->data);
|
||||
break;
|
||||
case Message::TYPE_TXT:
|
||||
case Message::TYPE_SPF:
|
||||
$binary = $this->textsToBinary($record->data);
|
||||
break;
|
||||
case Message::TYPE_MX:
|
||||
$binary = \pack(
|
||||
'n',
|
||||
$record->data['priority']
|
||||
);
|
||||
$binary .= $this->domainNameToBinary($record->data['target']);
|
||||
break;
|
||||
case Message::TYPE_SRV:
|
||||
$binary = \pack(
|
||||
'n*',
|
||||
$record->data['priority'],
|
||||
$record->data['weight'],
|
||||
$record->data['port']
|
||||
);
|
||||
$binary .= $this->domainNameToBinary($record->data['target']);
|
||||
break;
|
||||
case Message::TYPE_SOA:
|
||||
$binary = $this->domainNameToBinary($record->data['mname']);
|
||||
$binary .= $this->domainNameToBinary($record->data['rname']);
|
||||
$binary .= \pack(
|
||||
'N*',
|
||||
$record->data['serial'],
|
||||
$record->data['refresh'],
|
||||
$record->data['retry'],
|
||||
$record->data['expire'],
|
||||
$record->data['minimum']
|
||||
);
|
||||
break;
|
||||
case Message::TYPE_CAA:
|
||||
$binary = \pack(
|
||||
'C*',
|
||||
$record->data['flag'],
|
||||
\strlen($record->data['tag'])
|
||||
);
|
||||
$binary .= $record->data['tag'];
|
||||
$binary .= $record->data['value'];
|
||||
break;
|
||||
case Message::TYPE_SSHFP:
|
||||
$binary = \pack(
|
||||
'CCH*',
|
||||
$record->data['algorithm'],
|
||||
$record->data['type'],
|
||||
$record->data['fingerprint']
|
||||
);
|
||||
break;
|
||||
case Message::TYPE_OPT:
|
||||
$binary = '';
|
||||
foreach ($record->data as $opt => $value) {
|
||||
if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
|
||||
$value = \pack('n', round($value * 10));
|
||||
}
|
||||
$binary .= \pack('n*', $opt, \strlen((string) $value)) . $value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// RDATA is already stored as binary value for unknown record types
|
||||
$binary = $record->data;
|
||||
}
|
||||
|
||||
$data .= $this->domainNameToBinary($record->name);
|
||||
$data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
|
||||
$data .= $binary;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $texts
|
||||
* @return string
|
||||
*/
|
||||
private function textsToBinary(array $texts)
|
||||
{
|
||||
$data = '';
|
||||
foreach ($texts as $text) {
|
||||
$data .= \chr(\strlen($text)) . $text;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
* @return string
|
||||
*/
|
||||
private function domainNameToBinary($host)
|
||||
{
|
||||
if ($host === '') {
|
||||
return "\0";
|
||||
}
|
||||
|
||||
// break up domain name at each dot that is not preceeded by a backslash (escaped notation)
|
||||
return $this->textsToBinary(
|
||||
\array_map(
|
||||
'stripcslashes',
|
||||
\preg_split(
|
||||
'/(?<!\\\\)\./',
|
||||
$host . '.'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
356
vendor/react/dns/src/Protocol/Parser.php
vendored
Executable file
356
vendor/react/dns/src/Protocol/Parser.php
vendored
Executable file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\Query;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* DNS protocol parser
|
||||
*
|
||||
* Obsolete and uncommon types and classes are not implemented.
|
||||
*/
|
||||
final class Parser
|
||||
{
|
||||
/**
|
||||
* Parses the given raw binary message into a Message object
|
||||
*
|
||||
* @param string $data
|
||||
* @throws InvalidArgumentException
|
||||
* @return Message
|
||||
*/
|
||||
public function parseMessage($data)
|
||||
{
|
||||
$message = $this->parse($data, 0);
|
||||
if ($message === null) {
|
||||
throw new InvalidArgumentException('Unable to parse binary message');
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return ?Message
|
||||
*/
|
||||
private function parse($data, $consumed)
|
||||
{
|
||||
if (!isset($data[12 - 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12)));
|
||||
|
||||
$message = new Message();
|
||||
$message->id = $id;
|
||||
$message->rcode = $fields & 0xf;
|
||||
$message->ra = (($fields >> 7) & 1) === 1;
|
||||
$message->rd = (($fields >> 8) & 1) === 1;
|
||||
$message->tc = (($fields >> 9) & 1) === 1;
|
||||
$message->aa = (($fields >> 10) & 1) === 1;
|
||||
$message->opcode = ($fields >> 11) & 0xf;
|
||||
$message->qr = (($fields >> 15) & 1) === 1;
|
||||
$consumed += 12;
|
||||
|
||||
// parse all questions
|
||||
for ($i = $qdCount; $i > 0; --$i) {
|
||||
list($question, $consumed) = $this->parseQuestion($data, $consumed);
|
||||
if ($question === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->questions[] = $question;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all answer records
|
||||
for ($i = $anCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->answers[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all authority records
|
||||
for ($i = $nsCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->authority[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all additional records
|
||||
for ($i = $arCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->additional[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return array
|
||||
*/
|
||||
private function parseQuestion($data, $consumed)
|
||||
{
|
||||
list($labels, $consumed) = $this->readLabels($data, $consumed);
|
||||
|
||||
if ($labels === null || !isset($data[$consumed + 4 - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
return array(
|
||||
new Query(
|
||||
implode('.', $labels),
|
||||
$type,
|
||||
$class
|
||||
),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return array An array with a parsed Record on success or array with null if data is invalid/incomplete
|
||||
*/
|
||||
private function parseRecord($data, $consumed)
|
||||
{
|
||||
list($name, $consumed) = $this->readDomain($data, $consumed);
|
||||
|
||||
if ($name === null || !isset($data[$consumed + 10 - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
list($ttl) = array_values(unpack('N', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
// TTL is a UINT32 that must not have most significant bit set for BC reasons
|
||||
if ($ttl < 0 || $ttl >= 1 << 31) {
|
||||
$ttl = 0;
|
||||
}
|
||||
|
||||
list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
$consumed += 2;
|
||||
|
||||
if (!isset($data[$consumed + $rdLength - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$rdata = null;
|
||||
$expected = $consumed + $rdLength;
|
||||
|
||||
if (Message::TYPE_A === $type) {
|
||||
if ($rdLength === 4) {
|
||||
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_AAAA === $type) {
|
||||
if ($rdLength === 16) {
|
||||
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
|
||||
list($rdata, $consumed) = $this->readDomain($data, $consumed);
|
||||
} elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) {
|
||||
$rdata = array();
|
||||
while ($consumed < $expected) {
|
||||
$len = ord($data[$consumed]);
|
||||
$rdata[] = (string)substr($data, $consumed + 1, $len);
|
||||
$consumed += $len + 1;
|
||||
}
|
||||
} elseif (Message::TYPE_MX === $type) {
|
||||
if ($rdLength > 2) {
|
||||
list($priority) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
list($target, $consumed) = $this->readDomain($data, $consumed + 2);
|
||||
|
||||
$rdata = array(
|
||||
'priority' => $priority,
|
||||
'target' => $target
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SRV === $type) {
|
||||
if ($rdLength > 6) {
|
||||
list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6)));
|
||||
list($target, $consumed) = $this->readDomain($data, $consumed + 6);
|
||||
|
||||
$rdata = array(
|
||||
'priority' => $priority,
|
||||
'weight' => $weight,
|
||||
'port' => $port,
|
||||
'target' => $target
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SSHFP === $type) {
|
||||
if ($rdLength > 2) {
|
||||
list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2)));
|
||||
$fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2));
|
||||
$consumed += $rdLength;
|
||||
|
||||
$rdata = array(
|
||||
'algorithm' => $algorithm,
|
||||
'type' => $hash,
|
||||
'fingerprint' => $fingerprint
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SOA === $type) {
|
||||
list($mname, $consumed) = $this->readDomain($data, $consumed);
|
||||
list($rname, $consumed) = $this->readDomain($data, $consumed);
|
||||
|
||||
if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) {
|
||||
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20)));
|
||||
$consumed += 20;
|
||||
|
||||
$rdata = array(
|
||||
'mname' => $mname,
|
||||
'rname' => $rname,
|
||||
'serial' => $serial,
|
||||
'refresh' => $refresh,
|
||||
'retry' => $retry,
|
||||
'expire' => $expire,
|
||||
'minimum' => $minimum
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_OPT === $type) {
|
||||
$rdata = array();
|
||||
while (isset($data[$consumed + 4 - 1])) {
|
||||
list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$value = (string) substr($data, $consumed + 4, $length);
|
||||
if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
|
||||
$value = null;
|
||||
} elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
|
||||
list($value) = array_values(unpack('n', $value));
|
||||
$value = round($value * 0.1, 1);
|
||||
} elseif ($code === Message::OPT_TCP_KEEPALIVE) {
|
||||
break;
|
||||
}
|
||||
$rdata[$code] = $value;
|
||||
$consumed += 4 + $length;
|
||||
}
|
||||
} elseif (Message::TYPE_CAA === $type) {
|
||||
if ($rdLength > 3) {
|
||||
list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2)));
|
||||
|
||||
if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) {
|
||||
$tag = substr($data, $consumed + 2, $tagLength);
|
||||
$value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength);
|
||||
$consumed += $rdLength;
|
||||
|
||||
$rdata = array(
|
||||
'flag' => $flag,
|
||||
'tag' => $tag,
|
||||
'value' => $value
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// unknown types simply parse rdata as an opaque binary string
|
||||
$rdata = substr($data, $consumed, $rdLength);
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
|
||||
// ensure parsing record data consumes expact number of bytes indicated in record length
|
||||
if ($consumed !== $expected || $rdata === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
return array(
|
||||
new Record($name, $type, $class, $ttl, $rdata),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
private function readDomain($data, $consumed)
|
||||
{
|
||||
list ($labels, $consumed) = $this->readLabels($data, $consumed);
|
||||
|
||||
if ($labels === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
// use escaped notation for each label part, then join using dots
|
||||
return array(
|
||||
\implode(
|
||||
'.',
|
||||
\array_map(
|
||||
function ($label) {
|
||||
return \addcslashes($label, "\0..\40.\177");
|
||||
},
|
||||
$labels
|
||||
)
|
||||
),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
|
||||
* @return array
|
||||
*/
|
||||
private function readLabels($data, $consumed, $compressionDepth = 127)
|
||||
{
|
||||
$labels = array();
|
||||
|
||||
while (true) {
|
||||
if (!isset($data[$consumed])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$length = \ord($data[$consumed]);
|
||||
|
||||
// end of labels reached
|
||||
if ($length === 0) {
|
||||
$consumed += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// first two bits set? this is a compressed label (14 bit pointer offset)
|
||||
if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1]) && $compressionDepth) {
|
||||
$offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
|
||||
if ($offset >= $consumed) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$consumed += 2;
|
||||
list($newLabels) = $this->readLabels($data, $offset, $compressionDepth - 1);
|
||||
|
||||
if ($newLabels === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$labels = array_merge($labels, $newLabels);
|
||||
break;
|
||||
}
|
||||
|
||||
// length MUST be 0-63 (6 bits only) and data has to be large enough
|
||||
if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$labels[] = substr($data, $consumed + 1, $length);
|
||||
$consumed += $length + 1;
|
||||
}
|
||||
|
||||
return array($labels, $consumed);
|
||||
}
|
||||
}
|
||||
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Executable file
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Executable file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class CachingExecutor implements ExecutorInterface
|
||||
{
|
||||
/**
|
||||
* Default TTL for negative responses (NXDOMAIN etc.).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const TTL = 60;
|
||||
|
||||
private $executor;
|
||||
private $cache;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, CacheInterface $cache)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$id = $query->name . ':' . $query->type . ':' . $query->class;
|
||||
$cache = $this->cache;
|
||||
$that = $this;
|
||||
$executor = $this->executor;
|
||||
|
||||
$pending = $cache->get($id);
|
||||
return new Promise(function ($resolve, $reject) use ($query, $id, $cache, $executor, &$pending, $that) {
|
||||
$pending->then(
|
||||
function ($message) use ($query, $id, $cache, $executor, &$pending, $that) {
|
||||
// return cached response message on cache hit
|
||||
if ($message !== null) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
// perform DNS lookup if not already cached
|
||||
return $pending = $executor->query($query)->then(
|
||||
function (Message $message) use ($cache, $id, $that) {
|
||||
// DNS response message received => store in cache when not truncated and return
|
||||
if (!$message->tc) {
|
||||
$cache->set($id, $message, $that->ttl($message));
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
);
|
||||
}
|
||||
)->then($resolve, function ($e) use ($reject, &$pending) {
|
||||
$reject($e);
|
||||
$pending = null;
|
||||
});
|
||||
}, function ($_, $reject) use (&$pending, $query) {
|
||||
$reject(new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled'));
|
||||
$pending->cancel();
|
||||
$pending = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return int
|
||||
* @internal
|
||||
*/
|
||||
public function ttl(Message $message)
|
||||
{
|
||||
// select TTL from answers (should all be the same), use smallest value if available
|
||||
// @link https://tools.ietf.org/html/rfc2181#section-5.2
|
||||
$ttl = null;
|
||||
foreach ($message->answers as $answer) {
|
||||
if ($ttl === null || $answer->ttl < $ttl) {
|
||||
$ttl = $answer->ttl;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ttl === null) {
|
||||
$ttl = self::TTL;
|
||||
}
|
||||
|
||||
return $ttl;
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Executable file
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
final class CancellationException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
91
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Executable file
91
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Executable file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently
|
||||
*
|
||||
* Wraps an existing `ExecutorInterface` to keep tracking of pending queries
|
||||
* and only starts a new query when the same query is not already pending. Once
|
||||
* the underlying query is fulfilled/rejected, it will forward its value to all
|
||||
* promises awaiting the same query.
|
||||
*
|
||||
* This means it will not limit concurrency for queries that differ, for example
|
||||
* when sending many queries for different host names or types.
|
||||
*
|
||||
* This is useful because all executors are entirely async and as such allow you
|
||||
* to execute any number of queries concurrently. You should probably limit the
|
||||
* number of concurrent queries in your application or you're very likely going
|
||||
* to face rate limitations and bans on the resolver end. For many common
|
||||
* applications, you may want to avoid sending the same query multiple times
|
||||
* when the first one is still pending, so you will likely want to use this in
|
||||
* combination with some other executor like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
final class CoopExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $pending = array();
|
||||
private $counts = array();
|
||||
|
||||
public function __construct(ExecutorInterface $base)
|
||||
{
|
||||
$this->executor = $base;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$key = $this->serializeQueryToIdentity($query);
|
||||
if (isset($this->pending[$key])) {
|
||||
// same query is already pending, so use shared reference to pending query
|
||||
$promise = $this->pending[$key];
|
||||
++$this->counts[$key];
|
||||
} else {
|
||||
// no such query pending, so start new query and keep reference until it's fulfilled or rejected
|
||||
$promise = $this->executor->query($query);
|
||||
$this->pending[$key] = $promise;
|
||||
$this->counts[$key] = 1;
|
||||
|
||||
$pending =& $this->pending;
|
||||
$counts =& $this->counts;
|
||||
$promise->then(function () use ($key, &$pending, &$counts) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
}, function () use ($key, &$pending, &$counts) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
});
|
||||
}
|
||||
|
||||
// Return a child promise awaiting the pending query.
|
||||
// Cancelling this child promise should only cancel the pending query
|
||||
// when no other child promise is awaiting the same query.
|
||||
$pending =& $this->pending;
|
||||
$counts =& $this->counts;
|
||||
return new Promise(function ($resolve, $reject) use ($promise) {
|
||||
$promise->then($resolve, $reject);
|
||||
}, function () use (&$promise, $key, $query, &$pending, &$counts) {
|
||||
if (--$counts[$key] < 1) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
}
|
||||
throw new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled');
|
||||
});
|
||||
}
|
||||
|
||||
private function serializeQueryToIdentity(Query $query)
|
||||
{
|
||||
return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
|
||||
}
|
||||
}
|
||||
43
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Executable file
43
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Executable file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
interface ExecutorInterface
|
||||
{
|
||||
/**
|
||||
* Executes a query and will return a response message
|
||||
*
|
||||
* It returns a Promise which either fulfills with a response
|
||||
* `React\Dns\Model\Message` on success or rejects with an `Exception` if
|
||||
* the query is not successful. A response message may indicate an error
|
||||
* condition in its `rcode`, but this is considered a valid response message.
|
||||
*
|
||||
* ```php
|
||||
* $executor->query($query)->then(
|
||||
* function (React\Dns\Model\Message $response) {
|
||||
* // response message successfully received
|
||||
* var_dump($response->rcode, $response->answers);
|
||||
* },
|
||||
* function (Exception $error) {
|
||||
* // failed to query 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 = $executor->query($query);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param Query $query
|
||||
* @return \React\Promise\PromiseInterface<\React\Dns\Model\Message>
|
||||
* resolves with response message on success or rejects with an Exception on error
|
||||
*/
|
||||
public function query(Query $query);
|
||||
}
|
||||
49
vendor/react/dns/src/Query/FallbackExecutor.php
vendored
Executable file
49
vendor/react/dns/src/Query/FallbackExecutor.php
vendored
Executable file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class FallbackExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $fallback;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$cancelled = false;
|
||||
$fallback = $this->fallback;
|
||||
$promise = $this->executor->query($query);
|
||||
|
||||
return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) {
|
||||
$promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) {
|
||||
// reject if primary resolution rejected due to cancellation
|
||||
if ($cancelled) {
|
||||
$reject($e1);
|
||||
return;
|
||||
}
|
||||
|
||||
// start fallback query if primary query rejected
|
||||
$promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) {
|
||||
$append = $e2->getMessage();
|
||||
if (($pos = strpos($append, ':')) !== false) {
|
||||
$append = substr($append, $pos + 2);
|
||||
}
|
||||
|
||||
// reject with combined error message if both queries fail
|
||||
$reject(new \RuntimeException($e1->getMessage() . '. ' . $append));
|
||||
});
|
||||
});
|
||||
}, function () use (&$promise, &$cancelled) {
|
||||
// cancel pending query (primary or fallback)
|
||||
$cancelled = true;
|
||||
$promise->cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Executable file
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Executable file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
|
||||
/**
|
||||
* Resolves hosts from the given HostsFile or falls back to another executor
|
||||
*
|
||||
* If the host is found in the hosts file, it will not be passed to the actual
|
||||
* DNS executor. If the host is not found in the hosts file, it will be passed
|
||||
* to the DNS executor as a fallback.
|
||||
*/
|
||||
final class HostsFileExecutor implements ExecutorInterface
|
||||
{
|
||||
private $hosts;
|
||||
private $fallback;
|
||||
|
||||
public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
|
||||
{
|
||||
$this->hosts = $hosts;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
|
||||
// forward lookup for type A or AAAA
|
||||
$records = array();
|
||||
$expectsColon = $query->type === Message::TYPE_AAAA;
|
||||
foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
|
||||
// ensure this is an IPv4/IPV6 address according to query type
|
||||
if ((strpos($ip, ':') !== false) === $expectsColon) {
|
||||
$records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
|
||||
}
|
||||
}
|
||||
|
||||
if ($records) {
|
||||
return Promise\resolve(
|
||||
Message::createResponseWithAnswersForQuery($query, $records)
|
||||
);
|
||||
}
|
||||
} elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
|
||||
// reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
|
||||
$ip = $this->getIpFromHost($query->name);
|
||||
|
||||
if ($ip !== null) {
|
||||
$records = array();
|
||||
foreach ($this->hosts->getHostsForIp($ip) as $host) {
|
||||
$records[] = new Record($query->name, $query->type, $query->class, 0, $host);
|
||||
}
|
||||
|
||||
if ($records) {
|
||||
return Promise\resolve(
|
||||
Message::createResponseWithAnswersForQuery($query, $records)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fallback->query($query);
|
||||
}
|
||||
|
||||
private function getIpFromHost($host)
|
||||
{
|
||||
if (substr($host, -13) === '.in-addr.arpa') {
|
||||
// IPv4: read as IP and reverse bytes
|
||||
$ip = @inet_pton(substr($host, 0, -13));
|
||||
if ($ip === false || isset($ip[4])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return inet_ntop(strrev($ip));
|
||||
} elseif (substr($host, -9) === '.ip6.arpa') {
|
||||
// IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
|
||||
$ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
|
||||
if ($ip === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ip;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
vendor/react/dns/src/Query/Query.php
vendored
Executable file
69
vendor/react/dns/src/Query/Query.php
vendored
Executable file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
/**
|
||||
* This class represents a single question in a query/response message
|
||||
*
|
||||
* It uses a structure similar to `\React\Dns\Message\Record`, but does not
|
||||
* contain fields for resulting TTL and resulting record data (IPs etc.).
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
* @see \React\Dns\Message\Record
|
||||
*/
|
||||
final class Query
|
||||
{
|
||||
/**
|
||||
* @var string query name, i.e. hostname to look up
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int query type (aka QTYPE), see Message::TYPE_* constants
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var int query class (aka QCLASS), see Message::CLASS_IN constant
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $name query name, i.e. hostname to look up
|
||||
* @param int $type query type, see Message::TYPE_* constants
|
||||
* @param int $class query class, see Message::CLASS_IN constant
|
||||
*/
|
||||
public function __construct($name, $type, $class)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the hostname and query type/class for this query
|
||||
*
|
||||
* The output format is supposed to be human readable and is subject to change.
|
||||
* The format is inspired by RFC 3597 when handling unkown types/classes.
|
||||
*
|
||||
* @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)"
|
||||
* @link https://tools.ietf.org/html/rfc3597
|
||||
*/
|
||||
public function describe()
|
||||
{
|
||||
$class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : '';
|
||||
|
||||
$type = 'TYPE' . $this->type;
|
||||
$ref = new \ReflectionClass('React\Dns\Model\Message');
|
||||
foreach ($ref->getConstants() as $name => $value) {
|
||||
if ($value === $this->type && \strpos($name, 'TYPE_') === 0) {
|
||||
$type = \substr($name, 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->name . ' (' . $class . $type . ')';
|
||||
}
|
||||
}
|
||||
85
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Executable file
85
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Executable file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
final class RetryExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $retries;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, $retries = 2)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->retries = $retries;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
return $this->tryQuery($query, $this->retries);
|
||||
}
|
||||
|
||||
public function tryQuery(Query $query, $retries)
|
||||
{
|
||||
$deferred = new Deferred(function () use (&$promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$success = function ($value) use ($deferred, &$errorback) {
|
||||
$errorback = null;
|
||||
$deferred->resolve($value);
|
||||
};
|
||||
|
||||
$executor = $this->executor;
|
||||
$errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) {
|
||||
if (!$e instanceof TimeoutException) {
|
||||
$errorback = null;
|
||||
$deferred->reject($e);
|
||||
} elseif ($retries <= 0) {
|
||||
$errorback = null;
|
||||
$deferred->reject($e = new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: too many retries',
|
||||
0,
|
||||
$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);
|
||||
} else {
|
||||
--$retries;
|
||||
$promise = $executor->query($query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$promise = $this->executor->query($query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
85
vendor/react/dns/src/Query/SelectiveTransportExecutor.php
vendored
Executable file
85
vendor/react/dns/src/Query/SelectiveTransportExecutor.php
vendored
Executable file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a UDP or TCP/IP stream transport.
|
||||
*
|
||||
* This class will automatically choose the correct transport protocol to send
|
||||
* a DNS query to your DNS server. It will always try to send it over the more
|
||||
* efficient UDP transport first. If this query yields a size related issue
|
||||
* (truncated messages), it will retry over a streaming TCP/IP transport.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* Note that this executor only implements the logic to select the correct
|
||||
* transport for the given DNS query. Implementing the correct transport logic,
|
||||
* implementing timeouts and any retry logic is left up to the given executors,
|
||||
* see also [`UdpTransportExecutor`](#udptransportexecutor) and
|
||||
* [`TcpTransportExecutor`](#tcptransportexecutor) for more details.
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* any number of queries concurrently. You should probably limit the number of
|
||||
* concurrent queries in your application or you're very likely going to face
|
||||
* rate limitations and bans on the resolver end. For many common applications,
|
||||
* you may want to avoid sending the same query multiple times when the first
|
||||
* one is still pending, so you will likely want to use this in combination with
|
||||
* a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new SelectiveTransportExecutor(
|
||||
* $datagramExecutor,
|
||||
* $streamExecutor
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
class SelectiveTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $datagramExecutor;
|
||||
private $streamExecutor;
|
||||
|
||||
public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor)
|
||||
{
|
||||
$this->datagramExecutor = $datagramExecutor;
|
||||
$this->streamExecutor = $streamExecutor;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$stream = $this->streamExecutor;
|
||||
$pending = $this->datagramExecutor->query($query);
|
||||
|
||||
return new Promise(function ($resolve, $reject) use (&$pending, $stream, $query) {
|
||||
$pending->then(
|
||||
$resolve,
|
||||
function ($e) use (&$pending, $stream, $query, $resolve, $reject) {
|
||||
if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) {
|
||||
$pending = $stream->query($query)->then($resolve, $reject);
|
||||
} else {
|
||||
$reject($e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, function () use (&$pending) {
|
||||
$pending->cancel();
|
||||
$pending = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
382
vendor/react/dns/src/Query/TcpTransportExecutor.php
vendored
Executable file
382
vendor/react/dns/src/Query/TcpTransportExecutor.php
vendored
Executable file
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a TCP/IP stream transport.
|
||||
*
|
||||
* This is one of the main classes that send a DNS query to your DNS server.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TcpTransportExecutor('8.8.8.8:53');
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* See also [example #92](examples).
|
||||
*
|
||||
* Note that this executor does not implement a timeout, so you will very likely
|
||||
* want to use this in combination with a `TimeoutExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TimeoutExecutor(
|
||||
* new TcpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
|
||||
* transport, so you do not necessarily have to implement any retry logic.
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* queries concurrently. The first query will establish a TCP/IP socket
|
||||
* connection to the DNS server which will be kept open for a short period.
|
||||
* Additional queries will automatically reuse this existing socket connection
|
||||
* to the DNS server, will pipeline multiple requests over this single
|
||||
* connection and will keep an idle connection open for a short period. The
|
||||
* initial TCP/IP connection overhead may incur a slight delay if you only send
|
||||
* occasional queries – when sending a larger number of concurrent queries over
|
||||
* an existing connection, it becomes increasingly more efficient and avoids
|
||||
* creating many concurrent sockets like the UDP-based executor. You may still
|
||||
* want to limit the number of (concurrent) queries in your application or you
|
||||
* may be facing rate limitations and bans on the resolver end. For many common
|
||||
* applications, you may want to avoid sending the same query multiple times
|
||||
* when the first one is still pending, so you will likely want to use this in
|
||||
* combination with a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new TcpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > Internally, this class uses PHP's TCP/IP sockets and does not take advantage
|
||||
* of [react/socket](https://github.com/reactphp/socket) purely for
|
||||
* organizational reasons to avoid a cyclic dependency between the two
|
||||
* packages. Higher-level components should take advantage of the Socket
|
||||
* component instead of reimplementing this socket logic from scratch.
|
||||
*/
|
||||
class TcpTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $nameserver;
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
|
||||
/**
|
||||
* @var ?resource
|
||||
*/
|
||||
private $socket;
|
||||
|
||||
/**
|
||||
* @var Deferred[]
|
||||
*/
|
||||
private $pending = array();
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $names = array();
|
||||
|
||||
/**
|
||||
* Maximum idle time when socket is current unused (i.e. no pending queries outstanding)
|
||||
*
|
||||
* If a new query is to be sent during the idle period, we can reuse the
|
||||
* existing socket without having to wait for a new socket connection.
|
||||
* This uses a rather small, hard-coded value to not keep any unneeded
|
||||
* sockets open and to not keep the loop busy longer than needed.
|
||||
*
|
||||
* A future implementation may take advantage of `edns-tcp-keepalive` to keep
|
||||
* the socket open for longer periods. This will likely require explicit
|
||||
* configuration because this may consume additional resources and also keep
|
||||
* the loop busy for longer than expected in some applications.
|
||||
*
|
||||
* @var float
|
||||
* @link https://tools.ietf.org/html/rfc7766#section-6.2.1
|
||||
* @link https://tools.ietf.org/html/rfc7828
|
||||
*/
|
||||
private $idlePeriod = 0.001;
|
||||
|
||||
/**
|
||||
* @var ?\React\EventLoop\TimerInterface
|
||||
*/
|
||||
private $idleTimer;
|
||||
|
||||
private $writeBuffer = '';
|
||||
private $writePending = false;
|
||||
|
||||
private $readBuffer = '';
|
||||
private $readPending = false;
|
||||
|
||||
/** @var string */
|
||||
private $readChunk = 0xffff;
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct($nameserver, $loop = null)
|
||||
{
|
||||
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
|
||||
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
|
||||
$nameserver = '[' . $nameserver . ']';
|
||||
}
|
||||
|
||||
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver);
|
||||
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
|
||||
throw new \InvalidArgumentException('Invalid nameserver address given');
|
||||
}
|
||||
|
||||
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->nameserver = 'tcp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->parser = new Parser();
|
||||
$this->dumper = new BinaryDumper();
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
// keep shuffing message ID to avoid using the same message ID for two pending queries at the same time
|
||||
while (isset($this->pending[$request->id])) {
|
||||
$request->id = \mt_rand(0, 0xffff); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
$length = \strlen($queryData);
|
||||
if ($length > 0xffff) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Query too large for TCP transport'
|
||||
));
|
||||
}
|
||||
|
||||
$queryData = \pack('n', $length) . $queryData;
|
||||
|
||||
if ($this->socket === null) {
|
||||
// create async TCP/IP connection (may take a while)
|
||||
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT);
|
||||
if ($socket === false) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// set socket to non-blocking and wait for it to become writable (connection success/rejected)
|
||||
\stream_set_blocking($socket, false);
|
||||
if (\function_exists('stream_set_chunk_size')) {
|
||||
\stream_set_chunk_size($socket, $this->readChunk); // @codeCoverageIgnore
|
||||
}
|
||||
$this->socket = $socket;
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
// wait for socket to become writable to actually write out data
|
||||
$this->writeBuffer .= $queryData;
|
||||
if (!$this->writePending) {
|
||||
$this->writePending = true;
|
||||
$this->loop->addWriteStream($this->socket, array($this, 'handleWritable'));
|
||||
}
|
||||
|
||||
$names =& $this->names;
|
||||
$that = $this;
|
||||
$deferred = new Deferred(function () use ($that, &$names, $request) {
|
||||
// remove from list of pending names, but remember pending query
|
||||
$name = $names[$request->id];
|
||||
unset($names[$request->id]);
|
||||
$that->checkIdle();
|
||||
|
||||
throw new CancellationException('DNS query for ' . $name . ' has been cancelled');
|
||||
});
|
||||
|
||||
$this->pending[$request->id] = $deferred;
|
||||
$this->names[$request->id] = $query->describe();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function handleWritable()
|
||||
{
|
||||
if ($this->readPending === false) {
|
||||
$name = @\stream_socket_get_name($this->socket, true);
|
||||
if ($name === false) {
|
||||
// Connection failed? Check socket error if available for underlying errno/errstr.
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\function_exists('socket_import_stream')) {
|
||||
$socket = \socket_import_stream($this->socket);
|
||||
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
|
||||
$errstr = \socket_strerror($errno);
|
||||
} else {
|
||||
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
|
||||
$errstr = 'Connection refused';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readPending = true;
|
||||
$this->loop->addReadStream($this->socket, array($this, 'handleRead'));
|
||||
}
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// fwrite(): Send of 327712 bytes failed with errno=32 Broken pipe
|
||||
\preg_match('/errno=(\d+) (.+)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
$written = \fwrite($this->socket, $this->writeBuffer);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($written === false || $written === 0) {
|
||||
$this->closeError(
|
||||
'Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->writeBuffer[$written])) {
|
||||
$this->writeBuffer = \substr($this->writeBuffer, $written);
|
||||
} else {
|
||||
$this->loop->removeWriteStream($this->socket);
|
||||
$this->writePending = false;
|
||||
$this->writeBuffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function handleRead()
|
||||
{
|
||||
// read one chunk of data from the DNS server
|
||||
// any error is fatal, this is a stream of TCP/IP data
|
||||
$chunk = @\fread($this->socket, $this->readChunk);
|
||||
if ($chunk === false || $chunk === '') {
|
||||
$this->closeError('Connection to DNS server ' . $this->nameserver . ' lost');
|
||||
return;
|
||||
}
|
||||
|
||||
// reassemble complete message by concatenating all chunks.
|
||||
$this->readBuffer .= $chunk;
|
||||
|
||||
// response message header contains at least 12 bytes
|
||||
while (isset($this->readBuffer[11])) {
|
||||
// read response message length from first 2 bytes and ensure we have length + data in buffer
|
||||
list(, $length) = \unpack('n', $this->readBuffer);
|
||||
if (!isset($this->readBuffer[$length + 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = \substr($this->readBuffer, 2, $length);
|
||||
$this->readBuffer = (string)substr($this->readBuffer, $length + 2);
|
||||
|
||||
try {
|
||||
$response = $this->parser->parseMessage($data);
|
||||
} catch (\Exception $e) {
|
||||
// reject all pending queries if we received an invalid message from remote server
|
||||
$this->closeError('Invalid message received from DNS server ' . $this->nameserver);
|
||||
return;
|
||||
}
|
||||
|
||||
// reject all pending queries if we received an unexpected response ID or truncated response
|
||||
if (!isset($this->pending[$response->id]) || $response->tc) {
|
||||
$this->closeError('Invalid response message received from DNS server ' . $this->nameserver);
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred = $this->pending[$response->id];
|
||||
unset($this->pending[$response->id], $this->names[$response->id]);
|
||||
|
||||
$deferred->resolve($response);
|
||||
|
||||
$this->checkIdle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param string $reason
|
||||
* @param int $code
|
||||
*/
|
||||
public function closeError($reason, $code = 0)
|
||||
{
|
||||
$this->readBuffer = '';
|
||||
if ($this->readPending) {
|
||||
$this->loop->removeReadStream($this->socket);
|
||||
$this->readPending = false;
|
||||
}
|
||||
|
||||
$this->writeBuffer = '';
|
||||
if ($this->writePending) {
|
||||
$this->loop->removeWriteStream($this->socket);
|
||||
$this->writePending = false;
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
@\fclose($this->socket);
|
||||
$this->socket = null;
|
||||
|
||||
foreach ($this->names as $id => $name) {
|
||||
$this->pending[$id]->reject(new \RuntimeException(
|
||||
'DNS query for ' . $name . ' failed: ' . $reason,
|
||||
$code
|
||||
));
|
||||
}
|
||||
$this->pending = $this->names = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function checkIdle()
|
||||
{
|
||||
if ($this->idleTimer === null && !$this->names) {
|
||||
$that = $this;
|
||||
$this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () use ($that) {
|
||||
$that->closeError('Idle timeout');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Executable file
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
final class TimeoutException extends \Exception
|
||||
{
|
||||
}
|
||||
78
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Executable file
78
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Executable file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class TimeoutExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $loop;
|
||||
private $timeout;
|
||||
|
||||
/**
|
||||
* @param ExecutorInterface $executor
|
||||
* @param float $timeout
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct(ExecutorInterface $executor, $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->executor = $executor;
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$promise = $this->executor->query($query);
|
||||
|
||||
$loop = $this->loop;
|
||||
$time = $this->timeout;
|
||||
return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $query) {
|
||||
$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, $query) {
|
||||
$reject(new TimeoutException(
|
||||
'DNS query for ' . $query->describe() . ' timed out'
|
||||
));
|
||||
|
||||
// Cancel pending query 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 query, 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
221
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Executable file
221
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Executable file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a UDP transport.
|
||||
*
|
||||
* This is the main class that sends a DNS query to your DNS server and is used
|
||||
* internally by the `Resolver` for the actual message transport.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `igor.io`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* See also the [fourth example](examples).
|
||||
*
|
||||
* Note that this executor does not implement a timeout, so you will very likely
|
||||
* want to use this in combination with a `TimeoutExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Also note that this executor uses an unreliable UDP transport and that it
|
||||
* does not implement any retry logic, so you will likely want to use this in
|
||||
* combination with a `RetryExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* any number of queries concurrently. You should probably limit the number of
|
||||
* concurrent queries in your application or you're very likely going to face
|
||||
* rate limitations and bans on the resolver end. For many common applications,
|
||||
* you may want to avoid sending the same query multiple times when the first
|
||||
* one is still pending, so you will likely want to use this in combination with
|
||||
* a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > Internally, this class uses PHP's UDP sockets and does not take advantage
|
||||
* of [react/datagram](https://github.com/reactphp/datagram) purely for
|
||||
* organizational reasons to avoid a cyclic dependency between the two
|
||||
* packages. Higher-level components should take advantage of the Datagram
|
||||
* component instead of reimplementing this socket logic from scratch.
|
||||
*/
|
||||
final class UdpTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $nameserver;
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
|
||||
/**
|
||||
* maximum UDP packet size to send and receive
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxPacketSize = 512;
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct($nameserver, $loop = null)
|
||||
{
|
||||
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
|
||||
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
|
||||
$nameserver = '[' . $nameserver . ']';
|
||||
}
|
||||
|
||||
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver);
|
||||
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
|
||||
throw new \InvalidArgumentException('Invalid nameserver address given');
|
||||
}
|
||||
|
||||
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->nameserver = 'udp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->parser = new Parser();
|
||||
$this->dumper = new BinaryDumper();
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
if (isset($queryData[$this->maxPacketSize])) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Query too large for UDP transport',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
}
|
||||
|
||||
// UDP connections are instant, so try connection without a loop or timeout
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0);
|
||||
if ($socket === false) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// set socket to non-blocking and immediately try to send (fill write buffer)
|
||||
\stream_set_blocking($socket, false);
|
||||
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Write may potentially fail, but most common errors are already caught by connection check above.
|
||||
// Among others, macOS is known to report here when trying to send to broadcast address.
|
||||
// This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
|
||||
// fwrite(): send of 8192 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;
|
||||
});
|
||||
|
||||
$written = \fwrite($socket, $queryData);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($written !== \strlen($queryData)) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
$loop = $this->loop;
|
||||
$deferred = new Deferred(function () use ($loop, $socket, $query) {
|
||||
// cancellation should remove socket from loop and close socket
|
||||
$loop->removeReadStream($socket);
|
||||
\fclose($socket);
|
||||
|
||||
throw new CancellationException('DNS query for ' . $query->describe() . ' has been cancelled');
|
||||
});
|
||||
|
||||
$max = $this->maxPacketSize;
|
||||
$parser = $this->parser;
|
||||
$nameserver = $this->nameserver;
|
||||
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max, $nameserver) {
|
||||
// try to read a single data packet from the DNS server
|
||||
// ignoring any errors, this is uses UDP packets and not a stream of data
|
||||
$data = @\fread($socket, $max);
|
||||
if ($data === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $parser->parseMessage($data);
|
||||
} catch (\Exception $e) {
|
||||
// ignore and await next if we received an invalid message from remote server
|
||||
// this may as well be a fake response from an attacker (possible DOS)
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore and await next if we received an unexpected response ID
|
||||
// this may as well be a fake response from an attacker (possible cache poisoning)
|
||||
if ($response->id !== $request->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we only react to the first valid message, so remove socket from loop and close
|
||||
$loop->removeReadStream($socket);
|
||||
\fclose($socket);
|
||||
|
||||
if ($response->tc) {
|
||||
$deferred->reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: The DNS server ' . $nameserver . ' returned a truncated result for a UDP query',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Executable file
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
final class RecordNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
226
vendor/react/dns/src/Resolver/Factory.php
vendored
Executable file
226
vendor/react/dns/src/Resolver/Factory.php
vendored
Executable file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Cache\ArrayCache;
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Query\CachingExecutor;
|
||||
use React\Dns\Query\CoopExecutor;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\FallbackExecutor;
|
||||
use React\Dns\Query\HostsFileExecutor;
|
||||
use React\Dns\Query\RetryExecutor;
|
||||
use React\Dns\Query\SelectiveTransportExecutor;
|
||||
use React\Dns\Query\TcpTransportExecutor;
|
||||
use React\Dns\Query\TimeoutExecutor;
|
||||
use React\Dns\Query\UdpTransportExecutor;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
final class Factory
|
||||
{
|
||||
/**
|
||||
* Creates a DNS resolver instance for the given DNS config
|
||||
*
|
||||
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
|
||||
* single nameserver address. If the given config contains more than one DNS
|
||||
* nameserver, all DNS nameservers will be used in order. The primary DNS
|
||||
* server will always be used first before falling back to the secondary or
|
||||
* tertiary DNS server.
|
||||
*
|
||||
* @param Config|string $config DNS Config object (recommended) or single nameserver address
|
||||
* @param ?LoopInterface $loop
|
||||
* @return \React\Dns\Resolver\ResolverInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
public function create($config, $loop = null)
|
||||
{
|
||||
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');
|
||||
}
|
||||
|
||||
$executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
|
||||
|
||||
return new Resolver($executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cached DNS resolver instance for the given DNS config and cache
|
||||
*
|
||||
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
|
||||
* single nameserver address. If the given config contains more than one DNS
|
||||
* nameserver, all DNS nameservers will be used in order. The primary DNS
|
||||
* server will always be used first before falling back to the secondary or
|
||||
* tertiary DNS server.
|
||||
*
|
||||
* @param Config|string $config DNS Config object (recommended) or single nameserver address
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?CacheInterface $cache
|
||||
* @return \React\Dns\Resolver\ResolverInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
public function createCached($config, $loop = null, $cache = null)
|
||||
{
|
||||
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 ($cache !== null && !$cache instanceof CacheInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($cache) expected null|React\Cache\CacheInterface');
|
||||
}
|
||||
|
||||
// default to keeping maximum of 256 responses in cache unless explicitly given
|
||||
if (!($cache instanceof CacheInterface)) {
|
||||
$cache = new ArrayCache(256);
|
||||
}
|
||||
|
||||
$executor = $this->createExecutor($config, $loop ?: Loop::get());
|
||||
$executor = new CachingExecutor($executor, $cache);
|
||||
$executor = $this->decorateHostsFileExecutor($executor);
|
||||
|
||||
return new Resolver($executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the hosts file and decorates the given executor on success
|
||||
*
|
||||
* @param ExecutorInterface $executor
|
||||
* @return ExecutorInterface
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function decorateHostsFileExecutor(ExecutorInterface $executor)
|
||||
{
|
||||
try {
|
||||
$executor = new HostsFileExecutor(
|
||||
HostsFile::loadFromPathBlocking(),
|
||||
$executor
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
// ignore this file if it can not be loaded
|
||||
}
|
||||
|
||||
// Windows does not store localhost in hosts file by default but handles this internally
|
||||
// To compensate for this, we explicitly use hard-coded defaults for localhost
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$executor = new HostsFileExecutor(
|
||||
new HostsFile("127.0.0.1 localhost\n::1 localhost"),
|
||||
$executor
|
||||
);
|
||||
}
|
||||
|
||||
return $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config|string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return CoopExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
private function createExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
if ($nameserver instanceof Config) {
|
||||
if (!$nameserver->nameservers) {
|
||||
throw new \UnderflowException('Empty config with no DNS servers');
|
||||
}
|
||||
|
||||
// Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
|
||||
// Note to future self: Recursion isn't too hard, but how deep do we really want to go?
|
||||
$primary = reset($nameserver->nameservers);
|
||||
$secondary = next($nameserver->nameservers);
|
||||
$tertiary = next($nameserver->nameservers);
|
||||
|
||||
if ($tertiary !== false) {
|
||||
// 3 DNS servers given => nest first with fallback for second and third
|
||||
return new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($primary, $loop),
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($secondary, $loop),
|
||||
$this->createSingleExecutor($tertiary, $loop)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} elseif ($secondary !== false) {
|
||||
// 2 DNS servers given => fallback from first to second
|
||||
return new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($primary, $loop),
|
||||
$this->createSingleExecutor($secondary, $loop)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// 1 DNS server given => use single executor
|
||||
$nameserver = $primary;
|
||||
}
|
||||
}
|
||||
|
||||
return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return ExecutorInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createSingleExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
$parts = \parse_url($nameserver);
|
||||
|
||||
if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
|
||||
$executor = $this->createTcpExecutor($nameserver, $loop);
|
||||
} elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
|
||||
$executor = $this->createUdpExecutor($nameserver, $loop);
|
||||
} else {
|
||||
$executor = new SelectiveTransportExecutor(
|
||||
$this->createUdpExecutor($nameserver, $loop),
|
||||
$this->createTcpExecutor($nameserver, $loop)
|
||||
);
|
||||
}
|
||||
|
||||
return $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return TimeoutExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createTcpExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
return new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver, $loop),
|
||||
5.0,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return TimeoutExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createUdpExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
return new TimeoutExecutor(
|
||||
new UdpTransportExecutor(
|
||||
$nameserver,
|
||||
$loop
|
||||
),
|
||||
5.0,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
}
|
||||
147
vendor/react/dns/src/Resolver/Resolver.php
vendored
Executable file
147
vendor/react/dns/src/Resolver/Resolver.php
vendored
Executable file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\RecordNotFoundException;
|
||||
|
||||
/**
|
||||
* @see ResolverInterface for the base interface
|
||||
*/
|
||||
final class Resolver implements ResolverInterface
|
||||
{
|
||||
private $executor;
|
||||
|
||||
public function __construct(ExecutorInterface $executor)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
}
|
||||
|
||||
public function resolve($domain)
|
||||
{
|
||||
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
|
||||
return $ips[array_rand($ips)];
|
||||
});
|
||||
}
|
||||
|
||||
public function resolveAll($domain, $type)
|
||||
{
|
||||
$query = new Query($domain, $type, Message::CLASS_IN);
|
||||
$that = $this;
|
||||
|
||||
return $this->executor->query(
|
||||
$query
|
||||
)->then(function (Message $response) use ($query, $that) {
|
||||
return $that->extractValues($query, $response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] extract all resource record values from response for this query
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Message $response
|
||||
* @return array
|
||||
* @throws RecordNotFoundException when response indicates an error or contains no data
|
||||
* @internal
|
||||
*/
|
||||
public function extractValues(Query $query, Message $response)
|
||||
{
|
||||
// reject if response code indicates this is an error response message
|
||||
$code = $response->rcode;
|
||||
if ($code !== Message::RCODE_OK) {
|
||||
switch ($code) {
|
||||
case Message::RCODE_FORMAT_ERROR:
|
||||
$message = 'Format Error';
|
||||
break;
|
||||
case Message::RCODE_SERVER_FAILURE:
|
||||
$message = 'Server Failure';
|
||||
break;
|
||||
case Message::RCODE_NAME_ERROR:
|
||||
$message = 'Non-Existent Domain / NXDOMAIN';
|
||||
break;
|
||||
case Message::RCODE_NOT_IMPLEMENTED:
|
||||
$message = 'Not Implemented';
|
||||
break;
|
||||
case Message::RCODE_REFUSED:
|
||||
$message = 'Refused';
|
||||
break;
|
||||
default:
|
||||
$message = 'Unknown error response code ' . $code;
|
||||
}
|
||||
throw new RecordNotFoundException(
|
||||
'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')',
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
$answers = $response->answers;
|
||||
$addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
|
||||
|
||||
// reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
|
||||
if (0 === count($addresses)) {
|
||||
throw new RecordNotFoundException(
|
||||
'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)'
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \React\Dns\Model\Record[] $answers
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @return array
|
||||
*/
|
||||
private function valuesByNameAndType(array $answers, $name, $type)
|
||||
{
|
||||
// return all record values for this name and type (if any)
|
||||
$named = $this->filterByName($answers, $name);
|
||||
$records = $this->filterByType($named, $type);
|
||||
if ($records) {
|
||||
return $this->mapRecordData($records);
|
||||
}
|
||||
|
||||
// no matching records found? check if there are any matching CNAMEs instead
|
||||
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
|
||||
if ($cnameRecords) {
|
||||
$cnames = $this->mapRecordData($cnameRecords);
|
||||
foreach ($cnames as $cname) {
|
||||
$records = array_merge(
|
||||
$records,
|
||||
$this->valuesByNameAndType($answers, $cname, $type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
private function filterByName(array $answers, $name)
|
||||
{
|
||||
return $this->filterByField($answers, 'name', $name);
|
||||
}
|
||||
|
||||
private function filterByType(array $answers, $type)
|
||||
{
|
||||
return $this->filterByField($answers, 'type', $type);
|
||||
}
|
||||
|
||||
private function filterByField(array $answers, $field, $value)
|
||||
{
|
||||
$value = strtolower($value);
|
||||
return array_filter($answers, function ($answer) use ($field, $value) {
|
||||
return $value === strtolower($answer->$field);
|
||||
});
|
||||
}
|
||||
|
||||
private function mapRecordData(array $records)
|
||||
{
|
||||
return array_map(function ($record) {
|
||||
return $record->data;
|
||||
}, $records);
|
||||
}
|
||||
}
|
||||
94
vendor/react/dns/src/Resolver/ResolverInterface.php
vendored
Executable file
94
vendor/react/dns/src/Resolver/ResolverInterface.php
vendored
Executable file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
interface ResolverInterface
|
||||
{
|
||||
/**
|
||||
* Resolves the given $domain name to a single IPv4 address (type `A` query).
|
||||
*
|
||||
* ```php
|
||||
* $resolver->resolve('reactphp.org')->then(function ($ip) {
|
||||
* echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is one of the main methods in this package. It sends a DNS query
|
||||
* for the given $domain name to your DNS server and returns a single IP
|
||||
* address on success.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that contains more than
|
||||
* one IP address for this query, it will randomly pick one of the IP
|
||||
* addresses from the response. If you want the full list of IP addresses
|
||||
* or want to send a different type of query, you should use the
|
||||
* [`resolveAll()`](#resolveall) method instead.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that indicates an error
|
||||
* code, this method will reject with a `RecordNotFoundException`. Its
|
||||
* message and code can be used to check for the response code.
|
||||
*
|
||||
* If the DNS communication fails and the server does not respond with a
|
||||
* valid response message, this message will reject with an `Exception`.
|
||||
*
|
||||
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
*
|
||||
* ```php
|
||||
* $promise = $resolver->resolve('reactphp.org');
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $domain
|
||||
* @return \React\Promise\PromiseInterface<string>
|
||||
* resolves with a single IP address on success or rejects with an Exception on error.
|
||||
*/
|
||||
public function resolve($domain);
|
||||
|
||||
/**
|
||||
* Resolves all record values for the given $domain name and query $type.
|
||||
*
|
||||
* ```php
|
||||
* $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
|
||||
* echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
* echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is one of the main methods in this package. It sends a DNS query
|
||||
* for the given $domain name to your DNS server and returns a list with all
|
||||
* record values on success.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that contains one or more
|
||||
* records for this query, it will return a list with all record values
|
||||
* from the response. You can use the `Message::TYPE_*` constants to control
|
||||
* which type of query will be sent. Note that this method always returns a
|
||||
* list of record values, but each record value type depends on the query
|
||||
* type. For example, it returns the IPv4 addresses for type `A` queries,
|
||||
* the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
|
||||
* `CNAME` and `PTR` queries and structured data for other queries. See also
|
||||
* the `Record` documentation for more details.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that indicates an error
|
||||
* code, this method will reject with a `RecordNotFoundException`. Its
|
||||
* message and code can be used to check for the response code.
|
||||
*
|
||||
* If the DNS communication fails and the server does not respond with a
|
||||
* valid response message, this message will reject with an `Exception`.
|
||||
*
|
||||
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
*
|
||||
* ```php
|
||||
* $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $domain
|
||||
* @return \React\Promise\PromiseInterface<array>
|
||||
* Resolves with all record values on success or rejects with an Exception on error.
|
||||
*/
|
||||
public function resolveAll($domain, $type);
|
||||
}
|
||||
468
vendor/react/event-loop/CHANGELOG.md
vendored
Executable file
468
vendor/react/event-loop/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,468 @@
|
||||
# Changelog
|
||||
|
||||
## 1.5.0 (2023-11-13)
|
||||
|
||||
* Feature: Improve performance by using `spl_object_id()` on PHP 7.2+.
|
||||
(#267 by @samsonasik)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#269 by @clue)
|
||||
|
||||
* Update tests for `ext-uv` on PHP 8+ and legacy PHP.
|
||||
(#270 by @clue and #268 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2023-05-05)
|
||||
|
||||
* Feature: Improve performance of `Loop` by avoiding unneeded method calls.
|
||||
(#266 by @clue)
|
||||
|
||||
* Feature: Support checking `EINTR` constant from `ext-pcntl` without `ext-sockets`.
|
||||
(#265 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#254 by @nhedger)
|
||||
|
||||
* Improve test suite, run tests on PHP 8.2 and report failed assertions.
|
||||
(#258 by @WyriHaximus, #264 by @clue and #251, #261 and #262 by @SimonFrings)
|
||||
|
||||
## 1.3.0 (2022-03-17)
|
||||
|
||||
* Feature: Improve default `StreamSelectLoop` to report any warnings for invalid streams.
|
||||
(#245 by @clue)
|
||||
|
||||
* Feature: Improve performance of `StreamSelectLoop` when no timers are scheduled.
|
||||
(#246 by @clue)
|
||||
|
||||
* Fix: Fix periodic timer with zero interval for `ExtEvLoop` and legacy `ExtLibevLoop`.
|
||||
(#243 by @lucasnetau)
|
||||
|
||||
* Minor documentation improvements, update PHP version references.
|
||||
(#240, #248 and #250 by @SimonFrings, #241 by @dbu and #249 by @clue)
|
||||
|
||||
* Improve test suite and test against PHP 8.1.
|
||||
(#238 by @WyriHaximus and #242 by @clue)
|
||||
|
||||
## 1.2.0 (2021-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
||||
|
||||
* Feature: Introduce new concept of default loop with the new `Loop` class.
|
||||
(#226 by @WyriHaximus, #229, #231 and #232 by @clue)
|
||||
|
||||
The `Loop` class exists as a convenient global accessor for the event loop.
|
||||
It provides all methods that exist on the `LoopInterface` as static methods and
|
||||
will automatically execute the loop at the end of the program:
|
||||
|
||||
```php
|
||||
$timer = Loop::addPeriodicTimer(0.1, function () {
|
||||
echo 'Tick' . PHP_EOL;
|
||||
});
|
||||
|
||||
Loop::addTimer(1.0, function () use ($timer) {
|
||||
Loop::cancelTimer($timer);
|
||||
echo 'Done' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
The explicit loop instructions are still valid and may still be useful in some applications,
|
||||
especially for a transition period towards the more concise style.
|
||||
The `Loop::get()` method can be used to get the currently active event loop instance.
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// new
|
||||
$loop = React\EventLoop\Loop::get();
|
||||
```
|
||||
|
||||
* Minor documentation improvements and mark legacy extensions as deprecated.
|
||||
(#234 by @SimonFrings, #214 by @WyriHaximus and #233 and #235 by @nhedger)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI),
|
||||
update PHPUnit config and run tests on PHP 8.
|
||||
(#212 and #215 by @SimonFrings and #230 by @clue)
|
||||
|
||||
## 1.1.1 (2020-01-01)
|
||||
|
||||
* Fix: Fix reporting connection refused errors with `ExtUvLoop` on Linux and `StreamSelectLoop` on Windows.
|
||||
(#207 and #208 by @clue)
|
||||
|
||||
* Fix: Fix unsupported EventConfig and `SEGFAULT` on shutdown with `ExtEventLoop` on Windows.
|
||||
(#205 by @clue)
|
||||
|
||||
* Fix: Prevent interval overflow for timers very far in the future with `ExtUvLoop`.
|
||||
(#196 by @PabloKowalczyk)
|
||||
|
||||
* Fix: Check PCNTL functions for signal support instead of PCNTL extension with `StreamSelectLoop`.
|
||||
(#195 by @clue)
|
||||
|
||||
* Add `.gitattributes` to exclude dev files from exports.
|
||||
(#201 by @reedy)
|
||||
|
||||
* Improve test suite to fix testing `ExtUvLoop` on Travis,
|
||||
fix Travis CI builds, do not install `libuv` on legacy PHP setups,
|
||||
fix failing test cases due to inaccurate timers,
|
||||
run tests on Windows via Travis CI and
|
||||
run tests on PHP 7.4 and simplify test matrix and test setup.
|
||||
(#197 by @WyriHaximus and #202, #203, #204 and #209 by @clue)
|
||||
|
||||
## 1.1.0 (2019-02-07)
|
||||
|
||||
* New UV based event loop (ext-uv).
|
||||
(#112 by @WyriHaximus)
|
||||
|
||||
* Use high resolution timer on PHP 7.3+.
|
||||
(#182 by @clue)
|
||||
|
||||
* Improve PCNTL signals by using async signal dispatching if available.
|
||||
(#179 by @CharlotteDunois)
|
||||
|
||||
* Improve test suite and test suite set up.
|
||||
(#174 by @WyriHaximus, #181 by @clue)
|
||||
|
||||
* Fix PCNTL signals edge case.
|
||||
(#183 by @clue)
|
||||
|
||||
## 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.5.3 release.
|
||||
|
||||
## 0.5.3 (2018-07-09)
|
||||
|
||||
* Improve performance by importing global functions.
|
||||
(#167 by @Ocramius)
|
||||
|
||||
* Improve test suite by simplifying test bootstrap by using dev autoloader.
|
||||
(#169 by @lcobucci)
|
||||
|
||||
* Minor internal changes to improved backward compatibility with PHP 5.3.
|
||||
(#166 by @Donatello-za)
|
||||
|
||||
## 0.5.2 (2018-04-24)
|
||||
|
||||
* Feature: Improve memory consumption and runtime performance for `StreamSelectLoop` timers.
|
||||
(#164 by @clue)
|
||||
|
||||
* Improve test suite by removing I/O dependency at `StreamSelectLoopTest` to fix Mac OS X tests.
|
||||
(#161 by @nawarian)
|
||||
|
||||
## 0.5.1 (2018-04-09)
|
||||
|
||||
* Feature: New `ExtEvLoop` (PECL ext-ev) (#148 by @kaduev13)
|
||||
|
||||
## 0.5.0 (2018-04-05)
|
||||
|
||||
A major feature release with a significant documentation overhaul and long overdue API cleanup!
|
||||
|
||||
This update involves a number of BC breaks due to dropped support for deprecated
|
||||
functionality. We've tried hard to avoid BC breaks where possible and minimize
|
||||
impact otherwise. We expect that most consumers of this package will actually
|
||||
not be affected by any BC breaks, see below for more details.
|
||||
|
||||
We realize that the changes listed below may seem overwhelming, but we've tried
|
||||
to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP
|
||||
components are already compatible and support both this new release as well as
|
||||
providing backwards compatibility with the last release.
|
||||
|
||||
* Feature / BC break: Add support for signal handling via new
|
||||
`LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods.
|
||||
(#104 by @WyriHaximus and #111 and #150 by @clue)
|
||||
|
||||
```php
|
||||
$loop->addSignal(SIGINT, function () {
|
||||
echo 'CTRL-C';
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Significant documentation updates for `LoopInterface` and `Factory`.
|
||||
(#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor)
|
||||
|
||||
* Feature: Add examples to ease getting started
|
||||
(#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor)
|
||||
|
||||
* Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time
|
||||
and high precision timers with millisecond accuracy or below.
|
||||
(#130 and #157 by @clue)
|
||||
|
||||
* Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners
|
||||
and stream buffers and allow throwing Exception if stream resource is not supported.
|
||||
(#129 and #158 by @clue)
|
||||
|
||||
* Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed.
|
||||
(#153 by @WyriHaximus)
|
||||
|
||||
* Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM
|
||||
and remove all `callable` type hints for consistency reasons.
|
||||
(#141 and #151 by @clue)
|
||||
|
||||
* BC break: Documentation for timer API and clean up unneeded timer API.
|
||||
(#102 by @clue)
|
||||
|
||||
Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead:
|
||||
|
||||
```php
|
||||
// old (method invoked on timer instance)
|
||||
$timer->cancel();
|
||||
|
||||
// already supported before: invoke method on loop instance
|
||||
$loop->cancelTimer($timer);
|
||||
```
|
||||
|
||||
Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`,
|
||||
use closure binding to add arbitrary data to timer instead:
|
||||
|
||||
```php
|
||||
// old (limited setData() and getData() only allows single variable)
|
||||
$name = 'Tester';
|
||||
$timer = $loop->addTimer(1.0, function ($timer) {
|
||||
echo 'Hello ' . $timer->getData() . PHP_EOL;
|
||||
});
|
||||
$timer->setData($name);
|
||||
|
||||
// already supported before: closure binding allows any number of variables
|
||||
$name = 'Tester';
|
||||
$loop->addTimer(1.0, function () use ($name) {
|
||||
echo 'Hello ' . $name . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
Remove unneeded `TimerInterface::getLoop()`, use closure binding instead:
|
||||
|
||||
```php
|
||||
// old (getLoop() called on timer instance)
|
||||
$loop->addTimer(0.1, function ($timer) {
|
||||
$timer->getLoop()->stop();
|
||||
});
|
||||
|
||||
// already supported before: use closure binding as usual
|
||||
$loop->addTimer(0.1, function () use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
```
|
||||
|
||||
* BC break: Remove unneeded `LoopInterface::isTimerActive()` and
|
||||
`TimerInterface::isActive()` to reduce API surface.
|
||||
(#133 by @clue)
|
||||
|
||||
```php
|
||||
// old (method on timer instance or on loop instance)
|
||||
$timer->isActive();
|
||||
$loop->isTimerActive($timer);
|
||||
```
|
||||
|
||||
* BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`.
|
||||
(#138 by @WyriHaximus)
|
||||
|
||||
```php
|
||||
// old (notice obsolete "Timer" namespace)
|
||||
assert($timer instanceof React\EventLoop\Timer\TimerInterface);
|
||||
|
||||
// new
|
||||
assert($timer instanceof React\EventLoop\TimerInterface);
|
||||
```
|
||||
|
||||
* BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`),
|
||||
use `LoopInterface::futureTick()` instead.
|
||||
(#30 by @clue)
|
||||
|
||||
```php
|
||||
// old (removed)
|
||||
$loop->nextTick(function () {
|
||||
echo 'tick';
|
||||
});
|
||||
|
||||
// already supported before
|
||||
$loop->futureTick(function () {
|
||||
echo 'tick';
|
||||
});
|
||||
```
|
||||
|
||||
* BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()`
|
||||
(and fix internal cyclic dependency).
|
||||
(#103 by @clue)
|
||||
|
||||
```php
|
||||
// old ($loop gets passed by default)
|
||||
$loop->futureTick(function ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
|
||||
// already supported before: use closure binding as usual
|
||||
$loop->futureTick(function () use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
```
|
||||
|
||||
* BC break: Remove unneeded `LoopInterface::tick()`.
|
||||
(#72 by @jsor)
|
||||
|
||||
```php
|
||||
// old (removed)
|
||||
$loop->tick();
|
||||
|
||||
// suggested work around for testing purposes only
|
||||
$loop->futureTick(function () use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
```
|
||||
|
||||
* BC break: Documentation for advanced stream API and clean up unneeded stream API.
|
||||
(#110 by @clue)
|
||||
|
||||
Remove unneeded `$loop` argument for `LoopInterface::addReadStream()`
|
||||
and `LoopInterface::addWriteStream()`, use closure binding instead:
|
||||
|
||||
```php
|
||||
// old ($loop gets passed by default)
|
||||
$loop->addReadStream($stream, function ($stream, $loop) {
|
||||
$loop->removeReadStream($stream);
|
||||
});
|
||||
|
||||
// already supported before: use closure binding as usual
|
||||
$loop->addReadStream($stream, function ($stream) use ($loop) {
|
||||
$loop->removeReadStream($stream);
|
||||
});
|
||||
```
|
||||
|
||||
* BC break: Remove unneeded `LoopInterface::removeStream()` method,
|
||||
use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead.
|
||||
(#118 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$loop->removeStream($stream);
|
||||
|
||||
// already supported before
|
||||
$loop->removeReadStream($stream);
|
||||
$loop->removeWriteStream($stream);
|
||||
```
|
||||
|
||||
* BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop`
|
||||
for consistent naming for event loop implementations.
|
||||
(#128 by @clue)
|
||||
|
||||
* BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop`
|
||||
and make its `FEATURE_FDS` enabled by default.
|
||||
(#156 by @WyriHaximus)
|
||||
|
||||
* BC break: Mark all classes as final to discourage inheritance.
|
||||
(#131 by @clue)
|
||||
|
||||
* Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount)
|
||||
(#123 by @clue)
|
||||
|
||||
* Fix: Ensure large timer interval does not overflow on 32bit systems
|
||||
(#132 by @clue)
|
||||
|
||||
* Fix: Fix separately removing readable and writable side of stream when closing
|
||||
(#139 by @clue)
|
||||
|
||||
* Fix: Properly clean up event watchers for `ext-event` and `ext-libev`
|
||||
(#149 by @clue)
|
||||
|
||||
* Fix: Minor code cleanup and remove unneeded references
|
||||
(#145 by @seregazhuk)
|
||||
|
||||
* Fix: Discourage outdated `ext-libevent` on PHP 7
|
||||
(#62 by @cboden)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5,
|
||||
lock Travis distro so new defaults will not break the build,
|
||||
improve test suite to be less fragile and increase test timeouts,
|
||||
test against PHP 7.2 and reduce fwrite() call length to one chunk.
|
||||
(#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik)
|
||||
|
||||
* A number of changes were originally planned for this release but have been backported
|
||||
to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93
|
||||
|
||||
## 0.4.3 (2017-04-27)
|
||||
|
||||
* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
|
||||
* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
|
||||
* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
|
||||
* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
|
||||
* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
|
||||
* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
|
||||
* Improvement: Travis improvements (backported from #74) #75 (@clue)
|
||||
* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
|
||||
* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
|
||||
* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
|
||||
* Improvement: Readme cleanup #89 (@jsor)
|
||||
* Improvement: Restructure and improve README #90 (@jsor)
|
||||
* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
|
||||
|
||||
## 0.4.2 (2016-03-07)
|
||||
|
||||
* Bug fix: No longer error when signals sent to StreamSelectLoop
|
||||
* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
|
||||
* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
|
||||
* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
|
||||
|
||||
## 0.4.1 (2014-04-13)
|
||||
|
||||
* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
|
||||
* Bug fix: v0.3.4 changes merged for v0.4.1
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
|
||||
* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
|
||||
* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: New method: `EventLoopInterface::nextTick()`
|
||||
* BC break: New method: `EventLoopInterface::futureTick()`
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
|
||||
## 0.3.5 (2016-12-28)
|
||||
|
||||
This is a compatibility release that eases upgrading to the v0.4 release branch.
|
||||
You should consider upgrading to the v0.4 release branch.
|
||||
|
||||
* Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4
|
||||
(#47 by @clue)
|
||||
|
||||
## 0.3.4 (2014-03-30)
|
||||
|
||||
* Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
|
||||
|
||||
## 0.3.3 (2013-07-08)
|
||||
|
||||
* Bug fix: No error on removing non-existent streams (@clue)
|
||||
* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* BC break: New timers API (@nrk)
|
||||
* BC break: Remove check on return value from stream callbacks (@nrk)
|
||||
|
||||
## 0.2.7 (2013-01-05)
|
||||
|
||||
* Bug fix: Fix libevent timers with PHP 5.3
|
||||
* Bug fix: Fix libevent timer cancellation (@nrk)
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
|
||||
* Bug fix: Correctly pause LibEvLoop on stop()
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Feature: LibEvLoop, integration of `php-libev`
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.1.1 (2012-07-12)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.1.0 (2012-07-11)
|
||||
|
||||
* First tagged release
|
||||
21
vendor/react/event-loop/LICENSE
vendored
Executable file
21
vendor/react/event-loop/LICENSE
vendored
Executable 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.
|
||||
930
vendor/react/event-loop/README.md
vendored
Executable file
930
vendor/react/event-loop/README.md
vendored
Executable file
@@ -0,0 +1,930 @@
|
||||
# EventLoop
|
||||
|
||||
[](https://github.com/reactphp/event-loop/actions)
|
||||
[](https://packagist.org/packages/react/event-loop)
|
||||
|
||||
[ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O.
|
||||
|
||||
In order for async based libraries to be interoperable, they need to use the
|
||||
same event loop. This component provides a common `LoopInterface` that any
|
||||
library can target. This allows them to be used in the same loop, with one
|
||||
single [`run()`](#run) call that is controlled by the user.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [Loop](#loop)
|
||||
* [Loop methods](#loop-methods)
|
||||
* [Loop autorun](#loop-autorun)
|
||||
* [get()](#get)
|
||||
* [~~Factory~~](#factory)
|
||||
* [~~create()~~](#create)
|
||||
* [Loop implementations](#loop-implementations)
|
||||
* [StreamSelectLoop](#streamselectloop)
|
||||
* [ExtEventLoop](#exteventloop)
|
||||
* [ExtEvLoop](#extevloop)
|
||||
* [ExtUvLoop](#extuvloop)
|
||||
* [~~ExtLibeventLoop~~](#extlibeventloop)
|
||||
* [~~ExtLibevLoop~~](#extlibevloop)
|
||||
* [LoopInterface](#loopinterface)
|
||||
* [run()](#run)
|
||||
* [stop()](#stop)
|
||||
* [addTimer()](#addtimer)
|
||||
* [addPeriodicTimer()](#addperiodictimer)
|
||||
* [cancelTimer()](#canceltimer)
|
||||
* [futureTick()](#futuretick)
|
||||
* [addSignal()](#addsignal)
|
||||
* [removeSignal()](#removesignal)
|
||||
* [addReadStream()](#addreadstream)
|
||||
* [addWriteStream()](#addwritestream)
|
||||
* [removeReadStream()](#removereadstream)
|
||||
* [removeWriteStream()](#removewritestream)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
* [More](#more)
|
||||
|
||||
## Quickstart example
|
||||
|
||||
Here is an async HTTP server built with just the event loop.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$server = stream_socket_server('tcp://127.0.0.1:8080');
|
||||
stream_set_blocking($server, false);
|
||||
|
||||
Loop::addReadStream($server, function ($server) {
|
||||
$conn = stream_socket_accept($server);
|
||||
$data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
|
||||
Loop::addWriteStream($conn, function ($conn) use (&$data) {
|
||||
$written = fwrite($conn, $data);
|
||||
if ($written === strlen($data)) {
|
||||
fclose($conn);
|
||||
Loop::removeWriteStream($conn);
|
||||
} else {
|
||||
$data = substr($data, $written);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loop::addPeriodicTimer(5, function () {
|
||||
$memory = memory_get_usage() / 1024;
|
||||
$formatted = number_format($memory, 3).'K';
|
||||
echo "Current memory usage: {$formatted}\n";
|
||||
});
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Usage
|
||||
|
||||
Typical applications would use the [`Loop` class](#loop) to use the default
|
||||
event loop like this:
|
||||
|
||||
```php
|
||||
use React\EventLoop\Loop;
|
||||
|
||||
$timer = Loop::addPeriodicTimer(0.1, function () {
|
||||
echo 'Tick' . PHP_EOL;
|
||||
});
|
||||
|
||||
Loop::addTimer(1.0, function () use ($timer) {
|
||||
Loop::cancelTimer($timer);
|
||||
echo 'Done' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
As an alternative, you can also explicitly create an event loop instance at the
|
||||
beginning, reuse it throughout your program and finally run it at the end of the
|
||||
program like this:
|
||||
|
||||
```php
|
||||
$loop = React\EventLoop\Loop::get(); // or deprecated React\EventLoop\Factory::create();
|
||||
|
||||
$timer = $loop->addPeriodicTimer(0.1, function () {
|
||||
echo 'Tick' . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->addTimer(1.0, function () use ($loop, $timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
echo 'Done' . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
While the former is more concise, the latter is more explicit.
|
||||
In both cases, the program would perform the exact same steps.
|
||||
|
||||
1. The event loop instance is created at the beginning of the program. This is
|
||||
implicitly done the first time you call the [`Loop` class](#loop) or
|
||||
explicitly when using the deprecated [`Factory::create()` method](#create)
|
||||
(or manually instantiating any of the [loop implementations](#loop-implementations)).
|
||||
2. The event loop is used directly or passed as an instance to library and
|
||||
application code. In this example, a periodic timer is registered with the
|
||||
event loop which simply outputs `Tick` every fraction of a second until another
|
||||
timer stops the periodic timer after a second.
|
||||
3. The event loop is run at the end of the program. This is automatically done
|
||||
when using the [`Loop` class](#loop) or explicitly with a single [`run()`](#run)
|
||||
call at the end of the program.
|
||||
|
||||
As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop).
|
||||
The explicit loop instructions are still valid and may still be useful in some
|
||||
applications, especially for a transition period towards the more concise style.
|
||||
|
||||
### Loop
|
||||
|
||||
The `Loop` class exists as a convenient global accessor for the event loop.
|
||||
|
||||
#### Loop methods
|
||||
|
||||
The `Loop` class provides all methods that exist on the [`LoopInterface`](#loopinterface)
|
||||
as static methods:
|
||||
|
||||
* [run()](#run)
|
||||
* [stop()](#stop)
|
||||
* [addTimer()](#addtimer)
|
||||
* [addPeriodicTimer()](#addperiodictimer)
|
||||
* [cancelTimer()](#canceltimer)
|
||||
* [futureTick()](#futuretick)
|
||||
* [addSignal()](#addsignal)
|
||||
* [removeSignal()](#removesignal)
|
||||
* [addReadStream()](#addreadstream)
|
||||
* [addWriteStream()](#addwritestream)
|
||||
* [removeReadStream()](#removereadstream)
|
||||
* [removeWriteStream()](#removewritestream)
|
||||
|
||||
If you're working with the event loop in your application code, it's often
|
||||
easiest to directly interface with the static methods defined on the `Loop` class
|
||||
like this:
|
||||
|
||||
```php
|
||||
use React\EventLoop\Loop;
|
||||
|
||||
$timer = Loop::addPeriodicTimer(0.1, function () {
|
||||
echo 'Tick' . PHP_EOL;
|
||||
});
|
||||
|
||||
Loop::addTimer(1.0, function () use ($timer) {
|
||||
Loop::cancelTimer($timer);
|
||||
echo 'Done' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
On the other hand, if you're familiar with object-oriented programming (OOP) and
|
||||
dependency injection (DI), you may want to inject an event loop instance and
|
||||
invoke instance methods on the `LoopInterface` like this:
|
||||
|
||||
```php
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
class Greeter
|
||||
{
|
||||
private $loop;
|
||||
|
||||
public function __construct(LoopInterface $loop)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function greet(string $name)
|
||||
{
|
||||
$this->loop->addTimer(1.0, function () use ($name) {
|
||||
echo 'Hello ' . $name . '!' . PHP_EOL;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$greeter = new Greeter(Loop::get());
|
||||
$greeter->greet('Alice');
|
||||
$greeter->greet('Bob');
|
||||
```
|
||||
|
||||
Each static method call will be forwarded as-is to the underlying event loop
|
||||
instance by using the [`Loop::get()`](#get) call internally.
|
||||
See [`LoopInterface`](#loopinterface) for more details about available methods.
|
||||
|
||||
#### Loop autorun
|
||||
|
||||
When using the `Loop` class, it will automatically execute the loop at the end of
|
||||
the program. This means the following example will schedule a timer and will
|
||||
automatically execute the program until the timer event fires:
|
||||
|
||||
```php
|
||||
use React\EventLoop\Loop;
|
||||
|
||||
Loop::addTimer(1.0, function () {
|
||||
echo 'Hello' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
As of `v1.2.0`, we highly recommend using the `Loop` class this way and omitting any
|
||||
explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run)
|
||||
method is still valid and may still be useful in some applications, especially
|
||||
for a transition period towards the more concise style.
|
||||
|
||||
If you don't want the `Loop` to run automatically, you can either explicitly
|
||||
[`run()`](#run) or [`stop()`](#stop) it. This can be useful if you're using
|
||||
a global exception handler like this:
|
||||
|
||||
```php
|
||||
use React\EventLoop\Loop;
|
||||
|
||||
Loop::addTimer(10.0, function () {
|
||||
echo 'Never happens';
|
||||
});
|
||||
|
||||
set_exception_handler(function (Throwable $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
Loop::stop();
|
||||
});
|
||||
|
||||
throw new RuntimeException('Demo');
|
||||
```
|
||||
|
||||
#### get()
|
||||
|
||||
The `get(): LoopInterface` method can be used to
|
||||
get the currently active event loop instance.
|
||||
|
||||
This method will always return the same event loop instance throughout the
|
||||
lifetime of your application.
|
||||
|
||||
```php
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
$loop = Loop::get();
|
||||
|
||||
assert($loop instanceof LoopInterface);
|
||||
assert($loop === Loop::get());
|
||||
```
|
||||
|
||||
This is particularly useful if you're using object-oriented programming (OOP)
|
||||
and dependency injection (DI). In this case, you may want to inject an event
|
||||
loop instance and invoke instance methods on the `LoopInterface` like this:
|
||||
|
||||
```php
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
class Greeter
|
||||
{
|
||||
private $loop;
|
||||
|
||||
public function __construct(LoopInterface $loop)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function greet(string $name)
|
||||
{
|
||||
$this->loop->addTimer(1.0, function () use ($name) {
|
||||
echo 'Hello ' . $name . '!' . PHP_EOL;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$greeter = new Greeter(Loop::get());
|
||||
$greeter->greet('Alice');
|
||||
$greeter->greet('Bob');
|
||||
```
|
||||
|
||||
See [`LoopInterface`](#loopinterface) for more details about available methods.
|
||||
|
||||
### ~~Factory~~
|
||||
|
||||
> Deprecated since v1.2.0, see [`Loop` class](#loop) instead.
|
||||
|
||||
The deprecated `Factory` class exists as a convenient way to pick the best available
|
||||
[event loop implementation](#loop-implementations).
|
||||
|
||||
#### ~~create()~~
|
||||
|
||||
> Deprecated since v1.2.0, see [`Loop::get()`](#get) instead.
|
||||
|
||||
The deprecated `create(): LoopInterface` method can be used to
|
||||
create a new event loop instance:
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// new
|
||||
$loop = React\EventLoop\Loop::get();
|
||||
```
|
||||
|
||||
This method always returns an instance implementing [`LoopInterface`](#loopinterface),
|
||||
the actual [event loop implementation](#loop-implementations) is an implementation detail.
|
||||
|
||||
This method should usually only be called once at the beginning of the program.
|
||||
|
||||
### Loop implementations
|
||||
|
||||
In addition to the [`LoopInterface`](#loopinterface), there are a number of
|
||||
event loop implementations provided.
|
||||
|
||||
All of the event loops support these features:
|
||||
|
||||
* File descriptor polling
|
||||
* One-off timers
|
||||
* Periodic timers
|
||||
* Deferred execution on future loop tick
|
||||
|
||||
For most consumers of this package, the underlying event loop implementation is
|
||||
an implementation detail.
|
||||
You should use the [`Loop` class](#loop) to automatically create a new instance.
|
||||
|
||||
Advanced! If you explicitly need a certain event loop implementation, you can
|
||||
manually instantiate one of the following classes.
|
||||
Note that you may have to install the required PHP extensions for the respective
|
||||
event loop implementation first or they will throw a `BadMethodCallException` on creation.
|
||||
|
||||
#### StreamSelectLoop
|
||||
|
||||
A `stream_select()` based event loop.
|
||||
|
||||
This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
|
||||
function and is the only implementation that works out of the box with PHP.
|
||||
|
||||
This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM.
|
||||
This means that no installation is required and this library works on all
|
||||
platforms and supported PHP versions.
|
||||
Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory)
|
||||
will use this event loop by default if you do not install any of the event loop
|
||||
extensions listed below.
|
||||
|
||||
Under the hood, it does a simple `select` system call.
|
||||
This system call is limited to the maximum file descriptor number of
|
||||
`FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
|
||||
(`m` being the maximum file descriptor number passed).
|
||||
This means that you may run into issues when handling thousands of streams
|
||||
concurrently and you may want to look into using one of the alternative
|
||||
event loop implementations listed below in this case.
|
||||
If your use case is among the many common use cases that involve handling only
|
||||
dozens or a few hundred streams at once, then this event loop implementation
|
||||
performs really well.
|
||||
|
||||
If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
|
||||
this event loop implementation requires `ext-pcntl`.
|
||||
This extension is only available for Unix-like platforms and does not support
|
||||
Windows.
|
||||
It is commonly installed as part of many PHP distributions.
|
||||
If this extension is missing (or you're running on Windows), signal handling is
|
||||
not supported and throws a `BadMethodCallException` instead.
|
||||
|
||||
This event loop is known to rely on wall-clock time to schedule future timers
|
||||
when using any version before PHP 7.3, because a monotonic time source is
|
||||
only available as of PHP 7.3 (`hrtime()`).
|
||||
While this does not affect many common use cases, this is an important
|
||||
distinction for programs that rely on a high time precision or on systems
|
||||
that are subject to discontinuous time adjustments (time jumps).
|
||||
This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
|
||||
then adjust your system time forward by 20s, the timer may trigger in 10s.
|
||||
See also [`addTimer()`](#addtimer) for more details.
|
||||
|
||||
#### ExtEventLoop
|
||||
|
||||
An `ext-event` based event loop.
|
||||
|
||||
This uses the [`event` PECL extension](https://pecl.php.net/package/event),
|
||||
that provides an interface to `libevent` library.
|
||||
`libevent` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
|
||||
This loop is known to work with PHP 5.4 through PHP 8+.
|
||||
|
||||
#### ExtEvLoop
|
||||
|
||||
An `ext-ev` based event loop.
|
||||
|
||||
This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
|
||||
that provides an interface to `libev` library.
|
||||
`libev` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
|
||||
|
||||
This loop is known to work with PHP 5.4 through PHP 8+.
|
||||
|
||||
#### ExtUvLoop
|
||||
|
||||
An `ext-uv` based event loop.
|
||||
|
||||
This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
|
||||
that provides an interface to `libuv` library.
|
||||
`libuv` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
|
||||
This loop is known to work with PHP 7+.
|
||||
|
||||
#### ~~ExtLibeventLoop~~
|
||||
|
||||
> Deprecated since v1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
|
||||
|
||||
An `ext-libevent` based event loop.
|
||||
|
||||
This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
|
||||
that provides an interface to `libevent` library.
|
||||
`libevent` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
|
||||
This event loop does only work with PHP 5.
|
||||
An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
|
||||
PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
|
||||
To reiterate: Using this event loop on PHP 7 is not recommended.
|
||||
Accordingly, neither the [`Loop` class](#loop) nor the deprecated
|
||||
[`Factory` class](#factory) will try to use this event loop on PHP 7.
|
||||
|
||||
This event loop is known to trigger a readable listener only if
|
||||
the stream *becomes* readable (edge-triggered) and may not trigger if the
|
||||
stream has already been readable from the beginning.
|
||||
This also implies that a stream may not be recognized as readable when data
|
||||
is still left in PHP's internal stream buffers.
|
||||
As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
|
||||
to disable PHP's internal read buffer in this case.
|
||||
See also [`addReadStream()`](#addreadstream) for more details.
|
||||
|
||||
#### ~~ExtLibevLoop~~
|
||||
|
||||
> Deprecated since v1.2.0, use [`ExtEvLoop`](#extevloop) instead.
|
||||
|
||||
An `ext-libev` based event loop.
|
||||
|
||||
This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
|
||||
that provides an interface to `libev` library.
|
||||
`libev` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
|
||||
This loop does only work with PHP 5.
|
||||
An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
|
||||
to happen any time soon.
|
||||
|
||||
### LoopInterface
|
||||
|
||||
#### run()
|
||||
|
||||
The `run(): void` method can be used to
|
||||
run the event loop until there are no more tasks to perform.
|
||||
|
||||
For many applications, this method is the only directly visible
|
||||
invocation on the event loop.
|
||||
As a rule of thumb, it is usually recommended to attach everything to the
|
||||
same loop instance and then run the loop once at the bottom end of the
|
||||
application.
|
||||
|
||||
```php
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
This method will keep the loop running until there are no more tasks
|
||||
to perform. In other words: This method will block until the last
|
||||
timer, stream and/or signal has been removed.
|
||||
|
||||
Likewise, it is imperative to ensure the application actually invokes
|
||||
this method once. Adding listeners to the loop and missing to actually
|
||||
run it will result in the application exiting without actually waiting
|
||||
for any of the attached listeners.
|
||||
|
||||
This method MUST NOT be called while the loop is already running.
|
||||
This method MAY be called more than once after it has explicitly been
|
||||
[`stop()`ped](#stop) or after it automatically stopped because it
|
||||
previously did no longer have anything to do.
|
||||
|
||||
#### stop()
|
||||
|
||||
The `stop(): void` method can be used to
|
||||
instruct a running event loop to stop.
|
||||
|
||||
This method is considered advanced usage and should be used with care.
|
||||
As a rule of thumb, it is usually recommended to let the loop stop
|
||||
only automatically when it no longer has anything to do.
|
||||
|
||||
This method can be used to explicitly instruct the event loop to stop:
|
||||
|
||||
```php
|
||||
$loop->addTimer(3.0, function () use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
```
|
||||
|
||||
Calling this method on a loop instance that is not currently running or
|
||||
on a loop instance that has already been stopped has no effect.
|
||||
|
||||
#### addTimer()
|
||||
|
||||
The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to
|
||||
enqueue a callback to be invoked once after the given interval.
|
||||
|
||||
The second parameter MUST be a timer callback function that accepts
|
||||
the timer instance as its only parameter.
|
||||
If you don't use the timer instance inside your timer callback function
|
||||
you MAY use a function which has no parameters at all.
|
||||
|
||||
The timer callback function MUST NOT throw an `Exception`.
|
||||
The return value of the timer callback function will be ignored and has
|
||||
no effect, so for performance reasons you're recommended to not return
|
||||
any excessive data structures.
|
||||
|
||||
This method returns a timer instance. The same timer instance will also be
|
||||
passed into the timer callback function as described above.
|
||||
You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
|
||||
Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
|
||||
the callback will be invoked only once after the given interval.
|
||||
|
||||
```php
|
||||
$loop->addTimer(0.8, function () {
|
||||
echo 'world!' . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->addTimer(0.3, function () {
|
||||
echo 'hello ';
|
||||
});
|
||||
```
|
||||
|
||||
See also [example #1](examples).
|
||||
|
||||
If you want to access any variables within your callback function, you
|
||||
can bind arbitrary data to a callback closure like this:
|
||||
|
||||
```php
|
||||
function hello($name, LoopInterface $loop)
|
||||
{
|
||||
$loop->addTimer(1.0, function () use ($name) {
|
||||
echo "hello $name\n";
|
||||
});
|
||||
}
|
||||
|
||||
hello('Tester', $loop);
|
||||
```
|
||||
|
||||
This interface does not enforce any particular timer resolution, so
|
||||
special care may have to be taken if you rely on very high precision with
|
||||
millisecond accuracy or below. Event loop implementations SHOULD work on
|
||||
a best effort basis and SHOULD provide at least millisecond accuracy
|
||||
unless otherwise noted. Many existing event loop implementations are
|
||||
known to provide microsecond accuracy, but it's generally not recommended
|
||||
to rely on this high precision.
|
||||
|
||||
Similarly, the execution order of timers scheduled to execute at the
|
||||
same time (within its possible accuracy) is not guaranteed.
|
||||
|
||||
This interface suggests that event loop implementations SHOULD use a
|
||||
monotonic time source if available. Given that a monotonic time source is
|
||||
only available as of PHP 7.3 by default, event loop implementations MAY
|
||||
fall back to using wall-clock time.
|
||||
While this does not affect many common use cases, this is an important
|
||||
distinction for programs that rely on a high time precision or on systems
|
||||
that are subject to discontinuous time adjustments (time jumps).
|
||||
This means that if you schedule a timer to trigger in 30s and then adjust
|
||||
your system time forward by 20s, the timer SHOULD still trigger in 30s.
|
||||
See also [event loop implementations](#loop-implementations) for more details.
|
||||
|
||||
#### addPeriodicTimer()
|
||||
|
||||
The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to
|
||||
enqueue a callback to be invoked repeatedly after the given interval.
|
||||
|
||||
The second parameter MUST be a timer callback function that accepts
|
||||
the timer instance as its only parameter.
|
||||
If you don't use the timer instance inside your timer callback function
|
||||
you MAY use a function which has no parameters at all.
|
||||
|
||||
The timer callback function MUST NOT throw an `Exception`.
|
||||
The return value of the timer callback function will be ignored and has
|
||||
no effect, so for performance reasons you're recommended to not return
|
||||
any excessive data structures.
|
||||
|
||||
This method returns a timer instance. The same timer instance will also be
|
||||
passed into the timer callback function as described above.
|
||||
Unlike [`addTimer()`](#addtimer), this method will ensure the callback
|
||||
will be invoked infinitely after the given interval or until you invoke
|
||||
[`cancelTimer`](#canceltimer).
|
||||
|
||||
```php
|
||||
$timer = $loop->addPeriodicTimer(0.1, function () {
|
||||
echo 'tick!' . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->addTimer(1.0, function () use ($loop, $timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
echo 'Done' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
See also [example #2](examples).
|
||||
|
||||
If you want to limit the number of executions, you can bind
|
||||
arbitrary data to a callback closure like this:
|
||||
|
||||
```php
|
||||
function hello($name, LoopInterface $loop)
|
||||
{
|
||||
$n = 3;
|
||||
$loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
|
||||
if ($n > 0) {
|
||||
--$n;
|
||||
echo "hello $name\n";
|
||||
} else {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hello('Tester', $loop);
|
||||
```
|
||||
|
||||
This interface does not enforce any particular timer resolution, so
|
||||
special care may have to be taken if you rely on very high precision with
|
||||
millisecond accuracy or below. Event loop implementations SHOULD work on
|
||||
a best effort basis and SHOULD provide at least millisecond accuracy
|
||||
unless otherwise noted. Many existing event loop implementations are
|
||||
known to provide microsecond accuracy, but it's generally not recommended
|
||||
to rely on this high precision.
|
||||
|
||||
Similarly, the execution order of timers scheduled to execute at the
|
||||
same time (within its possible accuracy) is not guaranteed.
|
||||
|
||||
This interface suggests that event loop implementations SHOULD use a
|
||||
monotonic time source if available. Given that a monotonic time source is
|
||||
only available as of PHP 7.3 by default, event loop implementations MAY
|
||||
fall back to using wall-clock time.
|
||||
While this does not affect many common use cases, this is an important
|
||||
distinction for programs that rely on a high time precision or on systems
|
||||
that are subject to discontinuous time adjustments (time jumps).
|
||||
This means that if you schedule a timer to trigger in 30s and then adjust
|
||||
your system time forward by 20s, the timer SHOULD still trigger in 30s.
|
||||
See also [event loop implementations](#loop-implementations) for more details.
|
||||
|
||||
Additionally, periodic timers may be subject to timer drift due to
|
||||
re-scheduling after each invocation. As such, it's generally not
|
||||
recommended to rely on this for high precision intervals with millisecond
|
||||
accuracy or below.
|
||||
|
||||
#### cancelTimer()
|
||||
|
||||
The `cancelTimer(TimerInterface $timer): void` method can be used to
|
||||
cancel a pending timer.
|
||||
|
||||
See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
|
||||
|
||||
Calling this method on a timer instance that has not been added to this
|
||||
loop instance or on a timer that has already been cancelled has no effect.
|
||||
|
||||
#### futureTick()
|
||||
|
||||
The `futureTick(callable $listener): void` method can be used to
|
||||
schedule a callback to be invoked on a future tick of the event loop.
|
||||
|
||||
This works very much similar to timers with an interval of zero seconds,
|
||||
but does not require the overhead of scheduling a timer queue.
|
||||
|
||||
The tick callback function MUST be able to accept zero parameters.
|
||||
|
||||
The tick callback function MUST NOT throw an `Exception`.
|
||||
The return value of the tick callback function will be ignored and has
|
||||
no effect, so for performance reasons you're recommended to not return
|
||||
any excessive data structures.
|
||||
|
||||
If you want to access any variables within your callback function, you
|
||||
can bind arbitrary data to a callback closure like this:
|
||||
|
||||
```php
|
||||
function hello($name, LoopInterface $loop)
|
||||
{
|
||||
$loop->futureTick(function () use ($name) {
|
||||
echo "hello $name\n";
|
||||
});
|
||||
}
|
||||
|
||||
hello('Tester', $loop);
|
||||
```
|
||||
|
||||
Unlike timers, tick callbacks are guaranteed to be executed in the order
|
||||
they are enqueued.
|
||||
Also, once a callback is enqueued, there's no way to cancel this operation.
|
||||
|
||||
This is often used to break down bigger tasks into smaller steps (a form
|
||||
of cooperative multitasking).
|
||||
|
||||
```php
|
||||
$loop->futureTick(function () {
|
||||
echo 'b';
|
||||
});
|
||||
$loop->futureTick(function () {
|
||||
echo 'c';
|
||||
});
|
||||
echo 'a';
|
||||
```
|
||||
|
||||
See also [example #3](examples).
|
||||
|
||||
#### addSignal()
|
||||
|
||||
The `addSignal(int $signal, callable $listener): void` method can be used to
|
||||
register a listener to be notified when a signal has been caught by this process.
|
||||
|
||||
This is useful to catch user interrupt signals or shutdown signals from
|
||||
tools like `supervisor` or `systemd`.
|
||||
|
||||
The second parameter MUST be a listener callback function that accepts
|
||||
the signal as its only parameter.
|
||||
If you don't use the signal inside your listener callback function
|
||||
you MAY use a function which has no parameters at all.
|
||||
|
||||
The listener callback function MUST NOT throw an `Exception`.
|
||||
The return value of the listener callback function will be ignored and has
|
||||
no effect, so for performance reasons you're recommended to not return
|
||||
any excessive data structures.
|
||||
|
||||
```php
|
||||
$loop->addSignal(SIGINT, function (int $signal) {
|
||||
echo 'Caught user interrupt signal' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
See also [example #4](examples).
|
||||
|
||||
Signaling is only available on Unix-like platforms, Windows isn't
|
||||
supported due to operating system limitations.
|
||||
This method may throw a `BadMethodCallException` if signals aren't
|
||||
supported on this platform, for example when required extensions are
|
||||
missing.
|
||||
|
||||
**Note: A listener can only be added once to the same signal, any
|
||||
attempts to add it more than once will be ignored.**
|
||||
|
||||
#### removeSignal()
|
||||
|
||||
The `removeSignal(int $signal, callable $listener): void` method can be used to
|
||||
remove a previously added signal listener.
|
||||
|
||||
```php
|
||||
$loop->removeSignal(SIGINT, $listener);
|
||||
```
|
||||
|
||||
Any attempts to remove listeners that aren't registered will be ignored.
|
||||
|
||||
#### addReadStream()
|
||||
|
||||
> Advanced! Note that this low-level API is considered advanced usage.
|
||||
Most use cases should probably use the higher-level
|
||||
[readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
|
||||
instead.
|
||||
|
||||
The `addReadStream(resource $stream, callable $callback): void` method can be used to
|
||||
register a listener to be notified when a stream is ready to read.
|
||||
|
||||
The first parameter MUST be a valid stream resource that supports
|
||||
checking whether it is ready to read by this loop implementation.
|
||||
A single stream resource MUST NOT be added more than once.
|
||||
Instead, either call [`removeReadStream()`](#removereadstream) first or
|
||||
react to this event with a single listener and then dispatch from this
|
||||
listener. This method MAY throw an `Exception` if the given resource type
|
||||
is not supported by this loop implementation.
|
||||
|
||||
The second parameter MUST be a listener callback function that accepts
|
||||
the stream resource as its only parameter.
|
||||
If you don't use the stream resource inside your listener callback function
|
||||
you MAY use a function which has no parameters at all.
|
||||
|
||||
The listener callback function MUST NOT throw an `Exception`.
|
||||
The return value of the listener callback function will be ignored and has
|
||||
no effect, so for performance reasons you're recommended to not return
|
||||
any excessive data structures.
|
||||
|
||||
If you want to access any variables within your callback function, you
|
||||
can bind arbitrary data to a callback closure like this:
|
||||
|
||||
```php
|
||||
$loop->addReadStream($stream, function ($stream) use ($name) {
|
||||
echo $name . ' said: ' . fread($stream);
|
||||
});
|
||||
```
|
||||
|
||||
See also [example #11](examples).
|
||||
|
||||
You can invoke [`removeReadStream()`](#removereadstream) to remove the
|
||||
read event listener for this stream.
|
||||
|
||||
The execution order of listeners when multiple streams become ready at
|
||||
the same time is not guaranteed.
|
||||
|
||||
Some event loop implementations are known to only trigger the listener if
|
||||
the stream *becomes* readable (edge-triggered) and may not trigger if the
|
||||
stream has already been readable from the beginning.
|
||||
This also implies that a stream may not be recognized as readable when data
|
||||
is still left in PHP's internal stream buffers.
|
||||
As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
|
||||
to disable PHP's internal read buffer in this case.
|
||||
|
||||
#### addWriteStream()
|
||||
|
||||
> Advanced! Note that this low-level API is considered advanced usage.
|
||||
Most use cases should probably use the higher-level
|
||||
[writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
|
||||
instead.
|
||||
|
||||
The `addWriteStream(resource $stream, callable $callback): void` method can be used to
|
||||
register a listener to be notified when a stream is ready to write.
|
||||
|
||||
The first parameter MUST be a valid stream resource that supports
|
||||
checking whether it is ready to write by this loop implementation.
|
||||
A single stream resource MUST NOT be added more than once.
|
||||
Instead, either call [`removeWriteStream()`](#removewritestream) first or
|
||||
react to this event with a single listener and then dispatch from this
|
||||
listener. This method MAY throw an `Exception` if the given resource type
|
||||
is not supported by this loop implementation.
|
||||
|
||||
The second parameter MUST be a listener callback function that accepts
|
||||
the stream resource as its only parameter.
|
||||
If you don't use the stream resource inside your listener callback function
|
||||
you MAY use a function which has no parameters at all.
|
||||
|
||||
The listener callback function MUST NOT throw an `Exception`.
|
||||
The return value of the listener callback function will be ignored and has
|
||||
no effect, so for performance reasons you're recommended to not return
|
||||
any excessive data structures.
|
||||
|
||||
If you want to access any variables within your callback function, you
|
||||
can bind arbitrary data to a callback closure like this:
|
||||
|
||||
```php
|
||||
$loop->addWriteStream($stream, function ($stream) use ($name) {
|
||||
fwrite($stream, 'Hello ' . $name);
|
||||
});
|
||||
```
|
||||
|
||||
See also [example #12](examples).
|
||||
|
||||
You can invoke [`removeWriteStream()`](#removewritestream) to remove the
|
||||
write event listener for this stream.
|
||||
|
||||
The execution order of listeners when multiple streams become ready at
|
||||
the same time is not guaranteed.
|
||||
|
||||
#### removeReadStream()
|
||||
|
||||
The `removeReadStream(resource $stream): void` method can be used to
|
||||
remove the read event listener for the given stream.
|
||||
|
||||
Removing a stream from the loop that has already been removed or trying
|
||||
to remove a stream that was never added or is invalid has no effect.
|
||||
|
||||
#### removeWriteStream()
|
||||
|
||||
The `removeWriteStream(resource $stream): void` method can be used to
|
||||
remove the write event listener for the given stream.
|
||||
|
||||
Removing a stream from the loop that has already been removed or trying
|
||||
to remove a stream that was never added or is invalid has no effect.
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](https://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/event-loop:^1.5
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
Installing any of the event loop extensions is suggested, but entirely optional.
|
||||
See also [event loop implementations](#loop-implementations) for more details.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
|
||||
## More
|
||||
|
||||
* See our [Stream component](https://github.com/reactphp/stream) for more
|
||||
information on how streams are used in real-world applications.
|
||||
* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
|
||||
[dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents)
|
||||
for a list of packages that use the EventLoop in real-world applications.
|
||||
47
vendor/react/event-loop/composer.json
vendored
Executable file
47
vendor/react/event-loop/composer.json
vendored
Executable file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "react/event-loop",
|
||||
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
|
||||
"keywords": ["event-loop", "asynchronous"],
|
||||
"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"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\EventLoop\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\EventLoop\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
253
vendor/react/event-loop/src/ExtEvLoop.php
vendored
Executable file
253
vendor/react/event-loop/src/ExtEvLoop.php
vendored
Executable file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use Ev;
|
||||
use EvIo;
|
||||
use EvLoop;
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* An `ext-ev` based event loop.
|
||||
*
|
||||
* This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
|
||||
* that provides an interface to `libev` library.
|
||||
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
*
|
||||
* This loop is known to work with PHP 5.4 through PHP 8+.
|
||||
*
|
||||
* @see http://php.net/manual/en/book.ev.php
|
||||
* @see https://bitbucket.org/osmanov/pecl-ev/overview
|
||||
*/
|
||||
class ExtEvLoop implements LoopInterface
|
||||
{
|
||||
/**
|
||||
* @var EvLoop
|
||||
*/
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* @var FutureTickQueue
|
||||
*/
|
||||
private $futureTickQueue;
|
||||
|
||||
/**
|
||||
* @var SplObjectStorage
|
||||
*/
|
||||
private $timers;
|
||||
|
||||
/**
|
||||
* @var EvIo[]
|
||||
*/
|
||||
private $readStreams = array();
|
||||
|
||||
/**
|
||||
* @var EvIo[]
|
||||
*/
|
||||
private $writeStreams = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $running;
|
||||
|
||||
/**
|
||||
* @var SignalsHandler
|
||||
*/
|
||||
private $signals;
|
||||
|
||||
/**
|
||||
* @var \EvSignal[]
|
||||
*/
|
||||
private $signalEvents = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->loop = new EvLoop();
|
||||
$this->futureTickQueue = new FutureTickQueue();
|
||||
$this->timers = new SplObjectStorage();
|
||||
$this->signals = new SignalsHandler();
|
||||
}
|
||||
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
$key = (int)$stream;
|
||||
|
||||
if (isset($this->readStreams[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = $this->getStreamListenerClosure($stream, $listener);
|
||||
$event = $this->loop->io($stream, Ev::READ, $callback);
|
||||
$this->readStreams[$key] = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
* @param callable $listener
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
private function getStreamListenerClosure($stream, $listener)
|
||||
{
|
||||
return function () use ($stream, $listener) {
|
||||
\call_user_func($listener, $stream);
|
||||
};
|
||||
}
|
||||
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
$key = (int)$stream;
|
||||
|
||||
if (isset($this->writeStreams[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = $this->getStreamListenerClosure($stream, $listener);
|
||||
$event = $this->loop->io($stream, Ev::WRITE, $callback);
|
||||
$this->writeStreams[$key] = $event;
|
||||
}
|
||||
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int)$stream;
|
||||
|
||||
if (!isset($this->readStreams[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readStreams[$key]->stop();
|
||||
unset($this->readStreams[$key]);
|
||||
}
|
||||
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int)$stream;
|
||||
|
||||
if (!isset($this->writeStreams[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->writeStreams[$key]->stop();
|
||||
unset($this->writeStreams[$key]);
|
||||
}
|
||||
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, false);
|
||||
|
||||
$that = $this;
|
||||
$timers = $this->timers;
|
||||
$callback = function () use ($timer, $timers, $that) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
if ($timers->contains($timer)) {
|
||||
$that->cancelTimer($timer);
|
||||
}
|
||||
};
|
||||
|
||||
$event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
|
||||
$this->timers->attach($timer, $event);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, true);
|
||||
|
||||
$callback = function () use ($timer) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
};
|
||||
|
||||
$event = $this->loop->timer($timer->getInterval(), $timer->getInterval(), $callback);
|
||||
$this->timers->attach($timer, $event);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if (!isset($this->timers[$timer])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = $this->timers[$timer];
|
||||
$event->stop();
|
||||
$this->timers->detach($timer);
|
||||
}
|
||||
|
||||
public function futureTick($listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
|
||||
$wasJustStopped = !$this->running;
|
||||
$nothingLeftToDo = !$this->readStreams
|
||||
&& !$this->writeStreams
|
||||
&& !$this->timers->count()
|
||||
&& $this->signals->isEmpty();
|
||||
|
||||
$flags = Ev::RUN_ONCE;
|
||||
if ($wasJustStopped || $hasPendingCallbacks) {
|
||||
$flags |= Ev::RUN_NOWAIT;
|
||||
} elseif ($nothingLeftToDo) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->loop->run($flags);
|
||||
}
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
/** @var TimerInterface $timer */
|
||||
foreach ($this->timers as $timer) {
|
||||
$this->cancelTimer($timer);
|
||||
}
|
||||
|
||||
foreach ($this->readStreams as $key => $stream) {
|
||||
$this->removeReadStream($key);
|
||||
}
|
||||
|
||||
foreach ($this->writeStreams as $key => $stream) {
|
||||
$this->removeWriteStream($key);
|
||||
}
|
||||
}
|
||||
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->add($signal, $listener);
|
||||
|
||||
if (!isset($this->signalEvents[$signal])) {
|
||||
$this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) {
|
||||
$this->signals->call($signal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->remove($signal, $listener);
|
||||
|
||||
if (isset($this->signalEvents[$signal])) {
|
||||
$this->signalEvents[$signal]->stop();
|
||||
unset($this->signalEvents[$signal]);
|
||||
}
|
||||
}
|
||||
}
|
||||
275
vendor/react/event-loop/src/ExtEventLoop.php
vendored
Executable file
275
vendor/react/event-loop/src/ExtEventLoop.php
vendored
Executable file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Event;
|
||||
use EventBase;
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* An `ext-event` based event loop.
|
||||
*
|
||||
* This uses the [`event` PECL extension](https://pecl.php.net/package/event),
|
||||
* that provides an interface to `libevent` library.
|
||||
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
*
|
||||
* This loop is known to work with PHP 5.4 through PHP 8+.
|
||||
*
|
||||
* @link https://pecl.php.net/package/event
|
||||
*/
|
||||
final class ExtEventLoop implements LoopInterface
|
||||
{
|
||||
private $eventBase;
|
||||
private $futureTickQueue;
|
||||
private $timerCallback;
|
||||
private $timerEvents;
|
||||
private $streamCallback;
|
||||
private $readEvents = array();
|
||||
private $writeEvents = array();
|
||||
private $readListeners = array();
|
||||
private $writeListeners = array();
|
||||
private $readRefs = array();
|
||||
private $writeRefs = array();
|
||||
private $running;
|
||||
private $signals;
|
||||
private $signalEvents = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!\class_exists('EventBase', false)) {
|
||||
throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing');
|
||||
}
|
||||
|
||||
// support arbitrary file descriptors and not just sockets
|
||||
// Windows only has limited file descriptor support, so do not require this (will fail otherwise)
|
||||
// @link http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html#_setting_up_a_complicated_event_base
|
||||
$config = new \EventConfig();
|
||||
if (\DIRECTORY_SEPARATOR !== '\\') {
|
||||
$config->requireFeatures(\EventConfig::FEATURE_FDS);
|
||||
}
|
||||
|
||||
$this->eventBase = new EventBase($config);
|
||||
$this->futureTickQueue = new FutureTickQueue();
|
||||
$this->timerEvents = new SplObjectStorage();
|
||||
$this->signals = new SignalsHandler();
|
||||
|
||||
$this->createTimerCallback();
|
||||
$this->createStreamCallback();
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
// explicitly clear all references to Event objects to prevent SEGFAULTs on Windows
|
||||
foreach ($this->timerEvents as $timer) {
|
||||
$this->timerEvents->detach($timer);
|
||||
}
|
||||
|
||||
$this->readEvents = array();
|
||||
$this->writeEvents = array();
|
||||
}
|
||||
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
if (isset($this->readListeners[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback);
|
||||
$event->add();
|
||||
$this->readEvents[$key] = $event;
|
||||
$this->readListeners[$key] = $listener;
|
||||
|
||||
// ext-event does not increase refcount on stream resources for PHP 7+
|
||||
// manually keep track of stream resource to prevent premature garbage collection
|
||||
if (\PHP_VERSION_ID >= 70000) {
|
||||
$this->readRefs[$key] = $stream;
|
||||
}
|
||||
}
|
||||
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
if (isset($this->writeListeners[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback);
|
||||
$event->add();
|
||||
$this->writeEvents[$key] = $event;
|
||||
$this->writeListeners[$key] = $listener;
|
||||
|
||||
// ext-event does not increase refcount on stream resources for PHP 7+
|
||||
// manually keep track of stream resource to prevent premature garbage collection
|
||||
if (\PHP_VERSION_ID >= 70000) {
|
||||
$this->writeRefs[$key] = $stream;
|
||||
}
|
||||
}
|
||||
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->readEvents[$key])) {
|
||||
$this->readEvents[$key]->free();
|
||||
unset(
|
||||
$this->readEvents[$key],
|
||||
$this->readListeners[$key],
|
||||
$this->readRefs[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeEvents[$key])) {
|
||||
$this->writeEvents[$key]->free();
|
||||
unset(
|
||||
$this->writeEvents[$key],
|
||||
$this->writeListeners[$key],
|
||||
$this->writeRefs[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, false);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, true);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if ($this->timerEvents->contains($timer)) {
|
||||
$this->timerEvents[$timer]->free();
|
||||
$this->timerEvents->detach($timer);
|
||||
}
|
||||
}
|
||||
|
||||
public function futureTick($listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->add($signal, $listener);
|
||||
|
||||
if (!isset($this->signalEvents[$signal])) {
|
||||
$this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call'));
|
||||
$this->signalEvents[$signal]->add();
|
||||
}
|
||||
}
|
||||
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->remove($signal, $listener);
|
||||
|
||||
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
|
||||
$this->signalEvents[$signal]->free();
|
||||
unset($this->signalEvents[$signal]);
|
||||
}
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$flags = EventBase::LOOP_ONCE;
|
||||
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
|
||||
$flags |= EventBase::LOOP_NONBLOCK;
|
||||
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->eventBase->loop($flags);
|
||||
}
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a timer for execution.
|
||||
*
|
||||
* @param TimerInterface $timer
|
||||
*/
|
||||
private function scheduleTimer(TimerInterface $timer)
|
||||
{
|
||||
$flags = Event::TIMEOUT;
|
||||
|
||||
if ($timer->isPeriodic()) {
|
||||
$flags |= Event::PERSIST;
|
||||
}
|
||||
|
||||
$event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
|
||||
$this->timerEvents[$timer] = $event;
|
||||
|
||||
$event->add($timer->getInterval());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback used as the target of timer events.
|
||||
*
|
||||
* A reference is kept to the callback for the lifetime of the loop
|
||||
* to prevent "Cannot destroy active lambda function" fatal error from
|
||||
* the event extension.
|
||||
*/
|
||||
private function createTimerCallback()
|
||||
{
|
||||
$timers = $this->timerEvents;
|
||||
$this->timerCallback = function ($_, $__, $timer) use ($timers) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
if (!$timer->isPeriodic() && $timers->contains($timer)) {
|
||||
$this->cancelTimer($timer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback used as the target of stream events.
|
||||
*
|
||||
* A reference is kept to the callback for the lifetime of the loop
|
||||
* to prevent "Cannot destroy active lambda function" fatal error from
|
||||
* the event extension.
|
||||
*/
|
||||
private function createStreamCallback()
|
||||
{
|
||||
$read =& $this->readListeners;
|
||||
$write =& $this->writeListeners;
|
||||
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
|
||||
$key = (int) $stream;
|
||||
|
||||
if (Event::READ === (Event::READ & $flags) && isset($read[$key])) {
|
||||
\call_user_func($read[$key], $stream);
|
||||
}
|
||||
|
||||
if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) {
|
||||
\call_user_func($write[$key], $stream);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
201
vendor/react/event-loop/src/ExtLibevLoop.php
vendored
Executable file
201
vendor/react/event-loop/src/ExtLibevLoop.php
vendored
Executable file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use BadMethodCallException;
|
||||
use libev\EventLoop;
|
||||
use libev\IOEvent;
|
||||
use libev\SignalEvent;
|
||||
use libev\TimerEvent;
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* [Deprecated] An `ext-libev` based event loop.
|
||||
*
|
||||
* This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
|
||||
* that provides an interface to `libev` library.
|
||||
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
*
|
||||
* This loop does only work with PHP 5.
|
||||
* An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
|
||||
* to happen any time soon.
|
||||
*
|
||||
* @see https://github.com/m4rw3r/php-libev
|
||||
* @see https://gist.github.com/1688204
|
||||
* @deprecated 1.2.0, use [`ExtEvLoop`](#extevloop) instead.
|
||||
*/
|
||||
final class ExtLibevLoop implements LoopInterface
|
||||
{
|
||||
private $loop;
|
||||
private $futureTickQueue;
|
||||
private $timerEvents;
|
||||
private $readEvents = array();
|
||||
private $writeEvents = array();
|
||||
private $running;
|
||||
private $signals;
|
||||
private $signalEvents = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!\class_exists('libev\EventLoop', false)) {
|
||||
throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing');
|
||||
}
|
||||
|
||||
$this->loop = new EventLoop();
|
||||
$this->futureTickQueue = new FutureTickQueue();
|
||||
$this->timerEvents = new SplObjectStorage();
|
||||
$this->signals = new SignalsHandler();
|
||||
}
|
||||
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
if (isset($this->readEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = function () use ($stream, $listener) {
|
||||
\call_user_func($listener, $stream);
|
||||
};
|
||||
|
||||
$event = new IOEvent($callback, $stream, IOEvent::READ);
|
||||
$this->loop->add($event);
|
||||
|
||||
$this->readEvents[(int) $stream] = $event;
|
||||
}
|
||||
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
if (isset($this->writeEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = function () use ($stream, $listener) {
|
||||
\call_user_func($listener, $stream);
|
||||
};
|
||||
|
||||
$event = new IOEvent($callback, $stream, IOEvent::WRITE);
|
||||
$this->loop->add($event);
|
||||
|
||||
$this->writeEvents[(int) $stream] = $event;
|
||||
}
|
||||
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->readEvents[$key])) {
|
||||
$this->readEvents[$key]->stop();
|
||||
$this->loop->remove($this->readEvents[$key]);
|
||||
unset($this->readEvents[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeEvents[$key])) {
|
||||
$this->writeEvents[$key]->stop();
|
||||
$this->loop->remove($this->writeEvents[$key]);
|
||||
unset($this->writeEvents[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer( $interval, $callback, false);
|
||||
|
||||
$that = $this;
|
||||
$timers = $this->timerEvents;
|
||||
$callback = function () use ($timer, $timers, $that) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
if ($timers->contains($timer)) {
|
||||
$that->cancelTimer($timer);
|
||||
}
|
||||
};
|
||||
|
||||
$event = new TimerEvent($callback, $timer->getInterval());
|
||||
$this->timerEvents->attach($timer, $event);
|
||||
$this->loop->add($event);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, true);
|
||||
|
||||
$callback = function () use ($timer) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
};
|
||||
|
||||
$event = new TimerEvent($callback, $timer->getInterval(), $timer->getInterval());
|
||||
$this->timerEvents->attach($timer, $event);
|
||||
$this->loop->add($event);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if (isset($this->timerEvents[$timer])) {
|
||||
$this->loop->remove($this->timerEvents[$timer]);
|
||||
$this->timerEvents->detach($timer);
|
||||
}
|
||||
}
|
||||
|
||||
public function futureTick($listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->add($signal, $listener);
|
||||
|
||||
if (!isset($this->signalEvents[$signal])) {
|
||||
$signals = $this->signals;
|
||||
$this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) {
|
||||
$signals->call($signal);
|
||||
}, $signal);
|
||||
$this->loop->add($this->signalEvents[$signal]);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->remove($signal, $listener);
|
||||
|
||||
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
|
||||
$this->signalEvents[$signal]->stop();
|
||||
$this->loop->remove($this->signalEvents[$signal]);
|
||||
unset($this->signalEvents[$signal]);
|
||||
}
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$flags = EventLoop::RUN_ONCE;
|
||||
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
|
||||
$flags |= EventLoop::RUN_NOWAIT;
|
||||
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->loop->run($flags);
|
||||
}
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
}
|
||||
285
vendor/react/event-loop/src/ExtLibeventLoop.php
vendored
Executable file
285
vendor/react/event-loop/src/ExtLibeventLoop.php
vendored
Executable file
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Event;
|
||||
use EventBase;
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* [Deprecated] An `ext-libevent` based event loop.
|
||||
*
|
||||
* This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
|
||||
* that provides an interface to `libevent` library.
|
||||
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
*
|
||||
* This event loop does only work with PHP 5.
|
||||
* An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
|
||||
* PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
|
||||
* To reiterate: Using this event loop on PHP 7 is not recommended.
|
||||
* Accordingly, neither the [`Loop` class](#loop) nor the deprecated
|
||||
* [`Factory` class](#factory) will try to use this event loop on PHP 7.
|
||||
*
|
||||
* This event loop is known to trigger a readable listener only if
|
||||
* the stream *becomes* readable (edge-triggered) and may not trigger if the
|
||||
* stream has already been readable from the beginning.
|
||||
* This also implies that a stream may not be recognized as readable when data
|
||||
* is still left in PHP's internal stream buffers.
|
||||
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
|
||||
* to disable PHP's internal read buffer in this case.
|
||||
* See also [`addReadStream()`](#addreadstream) for more details.
|
||||
*
|
||||
* @link https://pecl.php.net/package/libevent
|
||||
* @deprecated 1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
|
||||
*/
|
||||
final class ExtLibeventLoop implements LoopInterface
|
||||
{
|
||||
/** @internal */
|
||||
const MICROSECONDS_PER_SECOND = 1000000;
|
||||
|
||||
private $eventBase;
|
||||
private $futureTickQueue;
|
||||
private $timerCallback;
|
||||
private $timerEvents;
|
||||
private $streamCallback;
|
||||
private $readEvents = array();
|
||||
private $writeEvents = array();
|
||||
private $readListeners = array();
|
||||
private $writeListeners = array();
|
||||
private $running;
|
||||
private $signals;
|
||||
private $signalEvents = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!\function_exists('event_base_new')) {
|
||||
throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing');
|
||||
}
|
||||
|
||||
$this->eventBase = \event_base_new();
|
||||
$this->futureTickQueue = new FutureTickQueue();
|
||||
$this->timerEvents = new SplObjectStorage();
|
||||
$this->signals = new SignalsHandler();
|
||||
|
||||
$this->createTimerCallback();
|
||||
$this->createStreamCallback();
|
||||
}
|
||||
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
if (isset($this->readListeners[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = \event_new();
|
||||
\event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback);
|
||||
\event_base_set($event, $this->eventBase);
|
||||
\event_add($event);
|
||||
|
||||
$this->readEvents[$key] = $event;
|
||||
$this->readListeners[$key] = $listener;
|
||||
}
|
||||
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
if (isset($this->writeListeners[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = \event_new();
|
||||
\event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback);
|
||||
\event_base_set($event, $this->eventBase);
|
||||
\event_add($event);
|
||||
|
||||
$this->writeEvents[$key] = $event;
|
||||
$this->writeListeners[$key] = $listener;
|
||||
}
|
||||
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->readListeners[$key])) {
|
||||
$event = $this->readEvents[$key];
|
||||
\event_del($event);
|
||||
\event_free($event);
|
||||
|
||||
unset(
|
||||
$this->readEvents[$key],
|
||||
$this->readListeners[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeListeners[$key])) {
|
||||
$event = $this->writeEvents[$key];
|
||||
\event_del($event);
|
||||
\event_free($event);
|
||||
|
||||
unset(
|
||||
$this->writeEvents[$key],
|
||||
$this->writeListeners[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, false);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, true);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if ($this->timerEvents->contains($timer)) {
|
||||
$event = $this->timerEvents[$timer];
|
||||
\event_del($event);
|
||||
\event_free($event);
|
||||
|
||||
$this->timerEvents->detach($timer);
|
||||
}
|
||||
}
|
||||
|
||||
public function futureTick($listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->add($signal, $listener);
|
||||
|
||||
if (!isset($this->signalEvents[$signal])) {
|
||||
$this->signalEvents[$signal] = \event_new();
|
||||
\event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call'));
|
||||
\event_base_set($this->signalEvents[$signal], $this->eventBase);
|
||||
\event_add($this->signalEvents[$signal]);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->remove($signal, $listener);
|
||||
|
||||
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
|
||||
\event_del($this->signalEvents[$signal]);
|
||||
\event_free($this->signalEvents[$signal]);
|
||||
unset($this->signalEvents[$signal]);
|
||||
}
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$flags = \EVLOOP_ONCE;
|
||||
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
|
||||
$flags |= \EVLOOP_NONBLOCK;
|
||||
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
\event_base_loop($this->eventBase, $flags);
|
||||
}
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a timer for execution.
|
||||
*
|
||||
* @param TimerInterface $timer
|
||||
*/
|
||||
private function scheduleTimer(TimerInterface $timer)
|
||||
{
|
||||
$this->timerEvents[$timer] = $event = \event_timer_new();
|
||||
|
||||
\event_timer_set($event, $this->timerCallback, $timer);
|
||||
\event_base_set($event, $this->eventBase);
|
||||
\event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback used as the target of timer events.
|
||||
*
|
||||
* A reference is kept to the callback for the lifetime of the loop
|
||||
* to prevent "Cannot destroy active lambda function" fatal error from
|
||||
* the event extension.
|
||||
*/
|
||||
private function createTimerCallback()
|
||||
{
|
||||
$that = $this;
|
||||
$timers = $this->timerEvents;
|
||||
$this->timerCallback = function ($_, $__, $timer) use ($timers, $that) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
// Timer already cancelled ...
|
||||
if (!$timers->contains($timer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reschedule periodic timers ...
|
||||
if ($timer->isPeriodic()) {
|
||||
\event_add(
|
||||
$timers[$timer],
|
||||
$timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND
|
||||
);
|
||||
|
||||
// Clean-up one shot timers ...
|
||||
} else {
|
||||
$that->cancelTimer($timer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback used as the target of stream events.
|
||||
*
|
||||
* A reference is kept to the callback for the lifetime of the loop
|
||||
* to prevent "Cannot destroy active lambda function" fatal error from
|
||||
* the event extension.
|
||||
*/
|
||||
private function createStreamCallback()
|
||||
{
|
||||
$read =& $this->readListeners;
|
||||
$write =& $this->writeListeners;
|
||||
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
|
||||
$key = (int) $stream;
|
||||
|
||||
if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) {
|
||||
\call_user_func($read[$key], $stream);
|
||||
}
|
||||
|
||||
if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) {
|
||||
\call_user_func($write[$key], $stream);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
342
vendor/react/event-loop/src/ExtUvLoop.php
vendored
Executable file
342
vendor/react/event-loop/src/ExtUvLoop.php
vendored
Executable file
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* An `ext-uv` based event loop.
|
||||
*
|
||||
* This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
|
||||
* that provides an interface to `libuv` library.
|
||||
* `libuv` itself supports a number of system-specific backends (epoll, kqueue).
|
||||
*
|
||||
* This loop is known to work with PHP 7+.
|
||||
*
|
||||
* @see https://github.com/bwoebi/php-uv
|
||||
*/
|
||||
final class ExtUvLoop implements LoopInterface
|
||||
{
|
||||
private $uv;
|
||||
private $futureTickQueue;
|
||||
private $timers;
|
||||
private $streamEvents = array();
|
||||
private $readStreams = array();
|
||||
private $writeStreams = array();
|
||||
private $running;
|
||||
private $signals;
|
||||
private $signalEvents = array();
|
||||
private $streamListener;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (!\function_exists('uv_loop_new')) {
|
||||
throw new \BadMethodCallException('Cannot create LibUvLoop, ext-uv extension missing');
|
||||
}
|
||||
|
||||
$this->uv = \uv_loop_new();
|
||||
$this->futureTickQueue = new FutureTickQueue();
|
||||
$this->timers = new SplObjectStorage();
|
||||
$this->streamListener = $this->createStreamListener();
|
||||
$this->signals = new SignalsHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying ext-uv event loop. (Internal ReactPHP use only.)
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getUvLoop()
|
||||
{
|
||||
return $this->uv;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
if (isset($this->readStreams[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readStreams[(int) $stream] = $listener;
|
||||
$this->addStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
if (isset($this->writeStreams[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->writeStreams[(int) $stream] = $listener;
|
||||
$this->addStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
if (!isset($this->streamEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->readStreams[(int) $stream]);
|
||||
$this->removeStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
if (!isset($this->streamEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->writeStreams[(int) $stream]);
|
||||
$this->removeStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, false);
|
||||
|
||||
$that = $this;
|
||||
$timers = $this->timers;
|
||||
$callback = function () use ($timer, $timers, $that) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
if ($timers->contains($timer)) {
|
||||
$that->cancelTimer($timer);
|
||||
}
|
||||
};
|
||||
|
||||
$event = \uv_timer_init($this->uv);
|
||||
$this->timers->attach($timer, $event);
|
||||
\uv_timer_start(
|
||||
$event,
|
||||
$this->convertFloatSecondsToMilliseconds($interval),
|
||||
0,
|
||||
$callback
|
||||
);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, true);
|
||||
|
||||
$callback = function () use ($timer) {
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
};
|
||||
|
||||
$interval = $this->convertFloatSecondsToMilliseconds($interval);
|
||||
$event = \uv_timer_init($this->uv);
|
||||
$this->timers->attach($timer, $event);
|
||||
\uv_timer_start(
|
||||
$event,
|
||||
$interval,
|
||||
(int) $interval === 0 ? 1 : $interval,
|
||||
$callback
|
||||
);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if (isset($this->timers[$timer])) {
|
||||
@\uv_timer_stop($this->timers[$timer]);
|
||||
$this->timers->detach($timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function futureTick($listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->add($signal, $listener);
|
||||
|
||||
if (!isset($this->signalEvents[$signal])) {
|
||||
$signals = $this->signals;
|
||||
$this->signalEvents[$signal] = \uv_signal_init($this->uv);
|
||||
\uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) {
|
||||
$signals->call($signal);
|
||||
}, $signal);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
$this->signals->remove($signal, $listener);
|
||||
|
||||
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
|
||||
\uv_signal_stop($this->signalEvents[$signal]);
|
||||
unset($this->signalEvents[$signal]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
|
||||
$wasJustStopped = !$this->running;
|
||||
$nothingLeftToDo = !$this->readStreams
|
||||
&& !$this->writeStreams
|
||||
&& !$this->timers->count()
|
||||
&& $this->signals->isEmpty();
|
||||
|
||||
// Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers,
|
||||
// otherwise use UV::RUN_NOWAIT.
|
||||
// @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run
|
||||
$flags = \UV::RUN_ONCE;
|
||||
if ($wasJustStopped || $hasPendingCallbacks) {
|
||||
$flags = \UV::RUN_NOWAIT;
|
||||
} elseif ($nothingLeftToDo) {
|
||||
break;
|
||||
}
|
||||
|
||||
\uv_run($this->uv, $flags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
private function addStream($stream)
|
||||
{
|
||||
if (!isset($this->streamEvents[(int) $stream])) {
|
||||
$this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream);
|
||||
}
|
||||
|
||||
if ($this->streamEvents[(int) $stream] !== false) {
|
||||
$this->pollStream($stream);
|
||||
}
|
||||
}
|
||||
|
||||
private function removeStream($stream)
|
||||
{
|
||||
if (!isset($this->streamEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($this->readStreams[(int) $stream])
|
||||
&& !isset($this->writeStreams[(int) $stream])) {
|
||||
\uv_poll_stop($this->streamEvents[(int) $stream]);
|
||||
\uv_close($this->streamEvents[(int) $stream]);
|
||||
unset($this->streamEvents[(int) $stream]);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pollStream($stream);
|
||||
}
|
||||
|
||||
private function pollStream($stream)
|
||||
{
|
||||
if (!isset($this->streamEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$flags = 0;
|
||||
if (isset($this->readStreams[(int) $stream])) {
|
||||
$flags |= \UV::READABLE;
|
||||
}
|
||||
|
||||
if (isset($this->writeStreams[(int) $stream])) {
|
||||
$flags |= \UV::WRITABLE;
|
||||
}
|
||||
|
||||
\uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stream listener
|
||||
*
|
||||
* @return callable Returns a callback
|
||||
*/
|
||||
private function createStreamListener()
|
||||
{
|
||||
$callback = function ($event, $status, $events, $stream) {
|
||||
// libuv automatically stops polling on error, re-enable polling to match other loop implementations
|
||||
if ($status !== 0) {
|
||||
$this->pollStream($stream);
|
||||
|
||||
// libuv may report no events on error, but this should still invoke stream listeners to report closed connections
|
||||
// re-enable both readable and writable, correct listeners will be checked below anyway
|
||||
if ($events === 0) {
|
||||
$events = \UV::READABLE | \UV::WRITABLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {
|
||||
\call_user_func($this->readStreams[(int) $stream], $stream);
|
||||
}
|
||||
|
||||
if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) {
|
||||
\call_user_func($this->writeStreams[(int) $stream], $stream);
|
||||
}
|
||||
};
|
||||
|
||||
return $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $interval
|
||||
* @return int
|
||||
*/
|
||||
private function convertFloatSecondsToMilliseconds($interval)
|
||||
{
|
||||
if ($interval < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$maxValue = (int) (\PHP_INT_MAX / 1000);
|
||||
$intInterval = (int) $interval;
|
||||
|
||||
if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed."
|
||||
);
|
||||
}
|
||||
|
||||
return (int) \floor($interval * 1000);
|
||||
}
|
||||
}
|
||||
75
vendor/react/event-loop/src/Factory.php
vendored
Executable file
75
vendor/react/event-loop/src/Factory.php
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
/**
|
||||
* [Deprecated] The `Factory` class exists as a convenient way to pick the best available event loop implementation.
|
||||
*
|
||||
* @deprecated 1.2.0 See Loop instead.
|
||||
* @see Loop
|
||||
*/
|
||||
final class Factory
|
||||
{
|
||||
/**
|
||||
* [Deprecated] Creates a new event loop instance
|
||||
*
|
||||
* ```php
|
||||
* // deprecated
|
||||
* $loop = React\EventLoop\Factory::create();
|
||||
*
|
||||
* // new
|
||||
* $loop = React\EventLoop\Loop::get();
|
||||
* ```
|
||||
*
|
||||
* This method always returns an instance implementing `LoopInterface`,
|
||||
* the actual event loop implementation is an implementation detail.
|
||||
*
|
||||
* This method should usually only be called once at the beginning of the program.
|
||||
*
|
||||
* @deprecated 1.2.0 See Loop::get() instead.
|
||||
* @see Loop::get()
|
||||
*
|
||||
* @return LoopInterface
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
$loop = self::construct();
|
||||
|
||||
Loop::set($loop);
|
||||
|
||||
return $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return LoopInterface
|
||||
*/
|
||||
private static function construct()
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\function_exists('uv_loop_new')) {
|
||||
// only use ext-uv on PHP 7
|
||||
return new ExtUvLoop();
|
||||
}
|
||||
|
||||
if (\class_exists('libev\EventLoop', false)) {
|
||||
return new ExtLibevLoop();
|
||||
}
|
||||
|
||||
if (\class_exists('EvLoop', false)) {
|
||||
return new ExtEvLoop();
|
||||
}
|
||||
|
||||
if (\class_exists('EventBase', false)) {
|
||||
return new ExtEventLoop();
|
||||
}
|
||||
|
||||
if (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) {
|
||||
// only use ext-libevent on PHP 5 for now
|
||||
return new ExtLibeventLoop();
|
||||
}
|
||||
|
||||
return new StreamSelectLoop();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
266
vendor/react/event-loop/src/Loop.php
vendored
Executable file
266
vendor/react/event-loop/src/Loop.php
vendored
Executable file
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
/**
|
||||
* The `Loop` class exists as a convenient way to get the currently relevant loop
|
||||
*/
|
||||
final class Loop
|
||||
{
|
||||
/**
|
||||
* @var ?LoopInterface
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/** @var bool */
|
||||
private static $stopped = false;
|
||||
|
||||
/**
|
||||
* Returns the event loop.
|
||||
* When no loop is set, it will call the factory to create one.
|
||||
*
|
||||
* This method always returns an instance implementing `LoopInterface`,
|
||||
* the actual event loop implementation is an implementation detail.
|
||||
*
|
||||
* This method is the preferred way to get the event loop and using
|
||||
* Factory::create has been deprecated.
|
||||
*
|
||||
* @return LoopInterface
|
||||
*/
|
||||
public static function get()
|
||||
{
|
||||
if (self::$instance instanceof LoopInterface) {
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
self::$instance = $loop = Factory::create();
|
||||
|
||||
// Automatically run loop at end of program, unless already started or stopped explicitly.
|
||||
// This is tested using child processes, so coverage is actually 100%, see BinTest.
|
||||
// @codeCoverageIgnoreStart
|
||||
$hasRun = false;
|
||||
$loop->futureTick(function () use (&$hasRun) {
|
||||
$hasRun = true;
|
||||
});
|
||||
|
||||
$stopped =& self::$stopped;
|
||||
register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) {
|
||||
// Don't run if we're coming from a fatal error (uncaught exception).
|
||||
$error = error_get_last();
|
||||
if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$hasRun && !$stopped) {
|
||||
$loop->run();
|
||||
}
|
||||
});
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal undocumented method, behavior might change or throw in the
|
||||
* future. Use with caution and at your own risk.
|
||||
*
|
||||
* @internal
|
||||
* @return void
|
||||
*/
|
||||
public static function set(LoopInterface $loop)
|
||||
{
|
||||
self::$instance = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Advanced] Register a listener to be notified when a stream is ready to read.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param callable $listener
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @see LoopInterface::addReadStream()
|
||||
*/
|
||||
public static function addReadStream($stream, $listener)
|
||||
{
|
||||
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
|
||||
if (self::$instance === null) {
|
||||
self::get();
|
||||
}
|
||||
self::$instance->addReadStream($stream, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Advanced] Register a listener to be notified when a stream is ready to write.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param callable $listener
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
* @see LoopInterface::addWriteStream()
|
||||
*/
|
||||
public static function addWriteStream($stream, $listener)
|
||||
{
|
||||
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
|
||||
if (self::$instance === null) {
|
||||
self::get();
|
||||
}
|
||||
self::$instance->addWriteStream($stream, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the read event listener for the given stream.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @return void
|
||||
* @see LoopInterface::removeReadStream()
|
||||
*/
|
||||
public static function removeReadStream($stream)
|
||||
{
|
||||
if (self::$instance !== null) {
|
||||
self::$instance->removeReadStream($stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the write event listener for the given stream.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @return void
|
||||
* @see LoopInterface::removeWriteStream()
|
||||
*/
|
||||
public static function removeWriteStream($stream)
|
||||
{
|
||||
if (self::$instance !== null) {
|
||||
self::$instance->removeWriteStream($stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a callback to be invoked once after the given interval.
|
||||
*
|
||||
* @param float $interval
|
||||
* @param callable $callback
|
||||
* @return TimerInterface
|
||||
* @see LoopInterface::addTimer()
|
||||
*/
|
||||
public static function addTimer($interval, $callback)
|
||||
{
|
||||
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
|
||||
if (self::$instance === null) {
|
||||
self::get();
|
||||
}
|
||||
return self::$instance->addTimer($interval, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a callback to be invoked repeatedly after the given interval.
|
||||
*
|
||||
* @param float $interval
|
||||
* @param callable $callback
|
||||
* @return TimerInterface
|
||||
* @see LoopInterface::addPeriodicTimer()
|
||||
*/
|
||||
public static function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
|
||||
if (self::$instance === null) {
|
||||
self::get();
|
||||
}
|
||||
return self::$instance->addPeriodicTimer($interval, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a pending timer.
|
||||
*
|
||||
* @param TimerInterface $timer
|
||||
* @return void
|
||||
* @see LoopInterface::cancelTimer()
|
||||
*/
|
||||
public static function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if (self::$instance !== null) {
|
||||
self::$instance->cancelTimer($timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a callback to be invoked on a future tick of the event loop.
|
||||
*
|
||||
* @param callable $listener
|
||||
* @return void
|
||||
* @see LoopInterface::futureTick()
|
||||
*/
|
||||
public static function futureTick($listener)
|
||||
{
|
||||
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
|
||||
if (self::$instance === null) {
|
||||
self::get();
|
||||
}
|
||||
|
||||
self::$instance->futureTick($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener to be notified when a signal has been caught by this process.
|
||||
*
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
* @return void
|
||||
* @see LoopInterface::addSignal()
|
||||
*/
|
||||
public static function addSignal($signal, $listener)
|
||||
{
|
||||
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
|
||||
if (self::$instance === null) {
|
||||
self::get();
|
||||
}
|
||||
|
||||
self::$instance->addSignal($signal, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a previously added signal listener.
|
||||
*
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
* @return void
|
||||
* @see LoopInterface::removeSignal()
|
||||
*/
|
||||
public static function removeSignal($signal, $listener)
|
||||
{
|
||||
if (self::$instance !== null) {
|
||||
self::$instance->removeSignal($signal, $listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the event loop until there are no more tasks to perform.
|
||||
*
|
||||
* @return void
|
||||
* @see LoopInterface::run()
|
||||
*/
|
||||
public static function run()
|
||||
{
|
||||
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
|
||||
if (self::$instance === null) {
|
||||
self::get();
|
||||
}
|
||||
|
||||
self::$instance->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruct a running event loop to stop.
|
||||
*
|
||||
* @return void
|
||||
* @see LoopInterface::stop()
|
||||
*/
|
||||
public static function stop()
|
||||
{
|
||||
self::$stopped = true;
|
||||
if (self::$instance !== null) {
|
||||
self::$instance->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
472
vendor/react/event-loop/src/LoopInterface.php
vendored
Executable file
472
vendor/react/event-loop/src/LoopInterface.php
vendored
Executable file
@@ -0,0 +1,472 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
interface LoopInterface
|
||||
{
|
||||
/**
|
||||
* [Advanced] Register a listener to be notified when a stream is ready to read.
|
||||
*
|
||||
* Note that this low-level API is considered advanced usage.
|
||||
* Most use cases should probably use the higher-level
|
||||
* [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
|
||||
* instead.
|
||||
*
|
||||
* The first parameter MUST be a valid stream resource that supports
|
||||
* checking whether it is ready to read by this loop implementation.
|
||||
* A single stream resource MUST NOT be added more than once.
|
||||
* Instead, either call [`removeReadStream()`](#removereadstream) first or
|
||||
* react to this event with a single listener and then dispatch from this
|
||||
* listener. This method MAY throw an `Exception` if the given resource type
|
||||
* is not supported by this loop implementation.
|
||||
*
|
||||
* The second parameter MUST be a listener callback function that accepts
|
||||
* the stream resource as its only parameter.
|
||||
* If you don't use the stream resource inside your listener callback function
|
||||
* you MAY use a function which has no parameters at all.
|
||||
*
|
||||
* The listener callback function MUST NOT throw an `Exception`.
|
||||
* The return value of the listener callback function will be ignored and has
|
||||
* no effect, so for performance reasons you're recommended to not return
|
||||
* any excessive data structures.
|
||||
*
|
||||
* If you want to access any variables within your callback function, you
|
||||
* can bind arbitrary data to a callback closure like this:
|
||||
*
|
||||
* ```php
|
||||
* $loop->addReadStream($stream, function ($stream) use ($name) {
|
||||
* echo $name . ' said: ' . fread($stream);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example #11](examples).
|
||||
*
|
||||
* You can invoke [`removeReadStream()`](#removereadstream) to remove the
|
||||
* read event listener for this stream.
|
||||
*
|
||||
* The execution order of listeners when multiple streams become ready at
|
||||
* the same time is not guaranteed.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource to check.
|
||||
* @param callable $listener Invoked when the stream is ready.
|
||||
* @throws \Exception if the given resource type is not supported by this loop implementation
|
||||
* @see self::removeReadStream()
|
||||
*/
|
||||
public function addReadStream($stream, $listener);
|
||||
|
||||
/**
|
||||
* [Advanced] Register a listener to be notified when a stream is ready to write.
|
||||
*
|
||||
* Note that this low-level API is considered advanced usage.
|
||||
* Most use cases should probably use the higher-level
|
||||
* [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
|
||||
* instead.
|
||||
*
|
||||
* The first parameter MUST be a valid stream resource that supports
|
||||
* checking whether it is ready to write by this loop implementation.
|
||||
* A single stream resource MUST NOT be added more than once.
|
||||
* Instead, either call [`removeWriteStream()`](#removewritestream) first or
|
||||
* react to this event with a single listener and then dispatch from this
|
||||
* listener. This method MAY throw an `Exception` if the given resource type
|
||||
* is not supported by this loop implementation.
|
||||
*
|
||||
* The second parameter MUST be a listener callback function that accepts
|
||||
* the stream resource as its only parameter.
|
||||
* If you don't use the stream resource inside your listener callback function
|
||||
* you MAY use a function which has no parameters at all.
|
||||
*
|
||||
* The listener callback function MUST NOT throw an `Exception`.
|
||||
* The return value of the listener callback function will be ignored and has
|
||||
* no effect, so for performance reasons you're recommended to not return
|
||||
* any excessive data structures.
|
||||
*
|
||||
* If you want to access any variables within your callback function, you
|
||||
* can bind arbitrary data to a callback closure like this:
|
||||
*
|
||||
* ```php
|
||||
* $loop->addWriteStream($stream, function ($stream) use ($name) {
|
||||
* fwrite($stream, 'Hello ' . $name);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example #12](examples).
|
||||
*
|
||||
* You can invoke [`removeWriteStream()`](#removewritestream) to remove the
|
||||
* write event listener for this stream.
|
||||
*
|
||||
* The execution order of listeners when multiple streams become ready at
|
||||
* the same time is not guaranteed.
|
||||
*
|
||||
* Some event loop implementations are known to only trigger the listener if
|
||||
* the stream *becomes* readable (edge-triggered) and may not trigger if the
|
||||
* stream has already been readable from the beginning.
|
||||
* This also implies that a stream may not be recognized as readable when data
|
||||
* is still left in PHP's internal stream buffers.
|
||||
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
|
||||
* to disable PHP's internal read buffer in this case.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource to check.
|
||||
* @param callable $listener Invoked when the stream is ready.
|
||||
* @throws \Exception if the given resource type is not supported by this loop implementation
|
||||
* @see self::removeWriteStream()
|
||||
*/
|
||||
public function addWriteStream($stream, $listener);
|
||||
|
||||
/**
|
||||
* Remove the read event listener for the given stream.
|
||||
*
|
||||
* Removing a stream from the loop that has already been removed or trying
|
||||
* to remove a stream that was never added or is invalid has no effect.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource.
|
||||
*/
|
||||
public function removeReadStream($stream);
|
||||
|
||||
/**
|
||||
* Remove the write event listener for the given stream.
|
||||
*
|
||||
* Removing a stream from the loop that has already been removed or trying
|
||||
* to remove a stream that was never added or is invalid has no effect.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource.
|
||||
*/
|
||||
public function removeWriteStream($stream);
|
||||
|
||||
/**
|
||||
* Enqueue a callback to be invoked once after the given interval.
|
||||
*
|
||||
* The second parameter MUST be a timer callback function that accepts
|
||||
* the timer instance as its only parameter.
|
||||
* If you don't use the timer instance inside your timer callback function
|
||||
* you MAY use a function which has no parameters at all.
|
||||
*
|
||||
* The timer callback function MUST NOT throw an `Exception`.
|
||||
* The return value of the timer callback function will be ignored and has
|
||||
* no effect, so for performance reasons you're recommended to not return
|
||||
* any excessive data structures.
|
||||
*
|
||||
* This method returns a timer instance. The same timer instance will also be
|
||||
* passed into the timer callback function as described above.
|
||||
* You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
|
||||
* Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
|
||||
* the callback will be invoked only once after the given interval.
|
||||
*
|
||||
* ```php
|
||||
* $loop->addTimer(0.8, function () {
|
||||
* echo 'world!' . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $loop->addTimer(0.3, function () {
|
||||
* echo 'hello ';
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example #1](examples).
|
||||
*
|
||||
* If you want to access any variables within your callback function, you
|
||||
* can bind arbitrary data to a callback closure like this:
|
||||
*
|
||||
* ```php
|
||||
* function hello($name, LoopInterface $loop)
|
||||
* {
|
||||
* $loop->addTimer(1.0, function () use ($name) {
|
||||
* echo "hello $name\n";
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* hello('Tester', $loop);
|
||||
* ```
|
||||
*
|
||||
* This interface does not enforce any particular timer resolution, so
|
||||
* special care may have to be taken if you rely on very high precision with
|
||||
* millisecond accuracy or below. Event loop implementations SHOULD work on
|
||||
* a best effort basis and SHOULD provide at least millisecond accuracy
|
||||
* unless otherwise noted. Many existing event loop implementations are
|
||||
* known to provide microsecond accuracy, but it's generally not recommended
|
||||
* to rely on this high precision.
|
||||
*
|
||||
* Similarly, the execution order of timers scheduled to execute at the
|
||||
* same time (within its possible accuracy) is not guaranteed.
|
||||
*
|
||||
* This interface suggests that event loop implementations SHOULD use a
|
||||
* monotonic time source if available. Given that a monotonic time source is
|
||||
* only available as of PHP 7.3 by default, event loop implementations MAY
|
||||
* fall back to using wall-clock time.
|
||||
* While this does not affect many common use cases, this is an important
|
||||
* distinction for programs that rely on a high time precision or on systems
|
||||
* that are subject to discontinuous time adjustments (time jumps).
|
||||
* This means that if you schedule a timer to trigger in 30s and then adjust
|
||||
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
|
||||
* See also [event loop implementations](#loop-implementations) for more details.
|
||||
*
|
||||
* @param int|float $interval The number of seconds to wait before execution.
|
||||
* @param callable $callback The callback to invoke.
|
||||
*
|
||||
* @return TimerInterface
|
||||
*/
|
||||
public function addTimer($interval, $callback);
|
||||
|
||||
/**
|
||||
* Enqueue a callback to be invoked repeatedly after the given interval.
|
||||
*
|
||||
* The second parameter MUST be a timer callback function that accepts
|
||||
* the timer instance as its only parameter.
|
||||
* If you don't use the timer instance inside your timer callback function
|
||||
* you MAY use a function which has no parameters at all.
|
||||
*
|
||||
* The timer callback function MUST NOT throw an `Exception`.
|
||||
* The return value of the timer callback function will be ignored and has
|
||||
* no effect, so for performance reasons you're recommended to not return
|
||||
* any excessive data structures.
|
||||
*
|
||||
* This method returns a timer instance. The same timer instance will also be
|
||||
* passed into the timer callback function as described above.
|
||||
* Unlike [`addTimer()`](#addtimer), this method will ensure the callback
|
||||
* will be invoked infinitely after the given interval or until you invoke
|
||||
* [`cancelTimer`](#canceltimer).
|
||||
*
|
||||
* ```php
|
||||
* $timer = $loop->addPeriodicTimer(0.1, function () {
|
||||
* echo 'tick!' . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $loop->addTimer(1.0, function () use ($loop, $timer) {
|
||||
* $loop->cancelTimer($timer);
|
||||
* echo 'Done' . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example #2](examples).
|
||||
*
|
||||
* If you want to limit the number of executions, you can bind
|
||||
* arbitrary data to a callback closure like this:
|
||||
*
|
||||
* ```php
|
||||
* function hello($name, LoopInterface $loop)
|
||||
* {
|
||||
* $n = 3;
|
||||
* $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
|
||||
* if ($n > 0) {
|
||||
* --$n;
|
||||
* echo "hello $name\n";
|
||||
* } else {
|
||||
* $loop->cancelTimer($timer);
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* hello('Tester', $loop);
|
||||
* ```
|
||||
*
|
||||
* This interface does not enforce any particular timer resolution, so
|
||||
* special care may have to be taken if you rely on very high precision with
|
||||
* millisecond accuracy or below. Event loop implementations SHOULD work on
|
||||
* a best effort basis and SHOULD provide at least millisecond accuracy
|
||||
* unless otherwise noted. Many existing event loop implementations are
|
||||
* known to provide microsecond accuracy, but it's generally not recommended
|
||||
* to rely on this high precision.
|
||||
*
|
||||
* Similarly, the execution order of timers scheduled to execute at the
|
||||
* same time (within its possible accuracy) is not guaranteed.
|
||||
*
|
||||
* This interface suggests that event loop implementations SHOULD use a
|
||||
* monotonic time source if available. Given that a monotonic time source is
|
||||
* only available as of PHP 7.3 by default, event loop implementations MAY
|
||||
* fall back to using wall-clock time.
|
||||
* While this does not affect many common use cases, this is an important
|
||||
* distinction for programs that rely on a high time precision or on systems
|
||||
* that are subject to discontinuous time adjustments (time jumps).
|
||||
* This means that if you schedule a timer to trigger in 30s and then adjust
|
||||
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
|
||||
* See also [event loop implementations](#loop-implementations) for more details.
|
||||
*
|
||||
* Additionally, periodic timers may be subject to timer drift due to
|
||||
* re-scheduling after each invocation. As such, it's generally not
|
||||
* recommended to rely on this for high precision intervals with millisecond
|
||||
* accuracy or below.
|
||||
*
|
||||
* @param int|float $interval The number of seconds to wait before execution.
|
||||
* @param callable $callback The callback to invoke.
|
||||
*
|
||||
* @return TimerInterface
|
||||
*/
|
||||
public function addPeriodicTimer($interval, $callback);
|
||||
|
||||
/**
|
||||
* Cancel a pending timer.
|
||||
*
|
||||
* See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
|
||||
*
|
||||
* Calling this method on a timer instance that has not been added to this
|
||||
* loop instance or on a timer that has already been cancelled has no effect.
|
||||
*
|
||||
* @param TimerInterface $timer The timer to cancel.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer);
|
||||
|
||||
/**
|
||||
* Schedule a callback to be invoked on a future tick of the event loop.
|
||||
*
|
||||
* This works very much similar to timers with an interval of zero seconds,
|
||||
* but does not require the overhead of scheduling a timer queue.
|
||||
*
|
||||
* The tick callback function MUST be able to accept zero parameters.
|
||||
*
|
||||
* The tick callback function MUST NOT throw an `Exception`.
|
||||
* The return value of the tick callback function will be ignored and has
|
||||
* no effect, so for performance reasons you're recommended to not return
|
||||
* any excessive data structures.
|
||||
*
|
||||
* If you want to access any variables within your callback function, you
|
||||
* can bind arbitrary data to a callback closure like this:
|
||||
*
|
||||
* ```php
|
||||
* function hello($name, LoopInterface $loop)
|
||||
* {
|
||||
* $loop->futureTick(function () use ($name) {
|
||||
* echo "hello $name\n";
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* hello('Tester', $loop);
|
||||
* ```
|
||||
*
|
||||
* Unlike timers, tick callbacks are guaranteed to be executed in the order
|
||||
* they are enqueued.
|
||||
* Also, once a callback is enqueued, there's no way to cancel this operation.
|
||||
*
|
||||
* This is often used to break down bigger tasks into smaller steps (a form
|
||||
* of cooperative multitasking).
|
||||
*
|
||||
* ```php
|
||||
* $loop->futureTick(function () {
|
||||
* echo 'b';
|
||||
* });
|
||||
* $loop->futureTick(function () {
|
||||
* echo 'c';
|
||||
* });
|
||||
* echo 'a';
|
||||
* ```
|
||||
*
|
||||
* See also [example #3](examples).
|
||||
*
|
||||
* @param callable $listener The callback to invoke.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function futureTick($listener);
|
||||
|
||||
/**
|
||||
* Register a listener to be notified when a signal has been caught by this process.
|
||||
*
|
||||
* This is useful to catch user interrupt signals or shutdown signals from
|
||||
* tools like `supervisor` or `systemd`.
|
||||
*
|
||||
* The second parameter MUST be a listener callback function that accepts
|
||||
* the signal as its only parameter.
|
||||
* If you don't use the signal inside your listener callback function
|
||||
* you MAY use a function which has no parameters at all.
|
||||
*
|
||||
* The listener callback function MUST NOT throw an `Exception`.
|
||||
* The return value of the listener callback function will be ignored and has
|
||||
* no effect, so for performance reasons you're recommended to not return
|
||||
* any excessive data structures.
|
||||
*
|
||||
* ```php
|
||||
* $loop->addSignal(SIGINT, function (int $signal) {
|
||||
* echo 'Caught user interrupt signal' . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [example #4](examples).
|
||||
*
|
||||
* Signaling is only available on Unix-like platforms, Windows isn't
|
||||
* supported due to operating system limitations.
|
||||
* This method may throw a `BadMethodCallException` if signals aren't
|
||||
* supported on this platform, for example when required extensions are
|
||||
* missing.
|
||||
*
|
||||
* **Note: A listener can only be added once to the same signal, any
|
||||
* attempts to add it more than once will be ignored.**
|
||||
*
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
*
|
||||
* @throws \BadMethodCallException when signals aren't supported on this
|
||||
* platform, for example when required extensions are missing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addSignal($signal, $listener);
|
||||
|
||||
/**
|
||||
* Removes a previously added signal listener.
|
||||
*
|
||||
* ```php
|
||||
* $loop->removeSignal(SIGINT, $listener);
|
||||
* ```
|
||||
*
|
||||
* Any attempts to remove listeners that aren't registered will be ignored.
|
||||
*
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeSignal($signal, $listener);
|
||||
|
||||
/**
|
||||
* Run the event loop until there are no more tasks to perform.
|
||||
*
|
||||
* For many applications, this method is the only directly visible
|
||||
* invocation on the event loop.
|
||||
* As a rule of thumb, it is usually recommended to attach everything to the
|
||||
* same loop instance and then run the loop once at the bottom end of the
|
||||
* application.
|
||||
*
|
||||
* ```php
|
||||
* $loop->run();
|
||||
* ```
|
||||
*
|
||||
* This method will keep the loop running until there are no more tasks
|
||||
* to perform. In other words: This method will block until the last
|
||||
* timer, stream and/or signal has been removed.
|
||||
*
|
||||
* Likewise, it is imperative to ensure the application actually invokes
|
||||
* this method once. Adding listeners to the loop and missing to actually
|
||||
* run it will result in the application exiting without actually waiting
|
||||
* for any of the attached listeners.
|
||||
*
|
||||
* This method MUST NOT be called while the loop is already running.
|
||||
* This method MAY be called more than once after it has explicitly been
|
||||
* [`stop()`ped](#stop) or after it automatically stopped because it
|
||||
* previously did no longer have anything to do.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run();
|
||||
|
||||
/**
|
||||
* Instruct a running event loop to stop.
|
||||
*
|
||||
* This method is considered advanced usage and should be used with care.
|
||||
* As a rule of thumb, it is usually recommended to let the loop stop
|
||||
* only automatically when it no longer has anything to do.
|
||||
*
|
||||
* This method can be used to explicitly instruct the event loop to stop:
|
||||
*
|
||||
* ```php
|
||||
* $loop->addTimer(3.0, function () use ($loop) {
|
||||
* $loop->stop();
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Calling this method on a loop instance that is not currently running or
|
||||
* on a loop instance that has already been stopped has no effect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function stop();
|
||||
}
|
||||
63
vendor/react/event-loop/src/SignalsHandler.php
vendored
Executable file
63
vendor/react/event-loop/src/SignalsHandler.php
vendored
Executable file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SignalsHandler
|
||||
{
|
||||
private $signals = array();
|
||||
|
||||
public function add($signal, $listener)
|
||||
{
|
||||
if (!isset($this->signals[$signal])) {
|
||||
$this->signals[$signal] = array();
|
||||
}
|
||||
|
||||
if (\in_array($listener, $this->signals[$signal])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->signals[$signal][] = $listener;
|
||||
}
|
||||
|
||||
public function remove($signal, $listener)
|
||||
{
|
||||
if (!isset($this->signals[$signal])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$index = \array_search($listener, $this->signals[$signal], true);
|
||||
unset($this->signals[$signal][$index]);
|
||||
|
||||
if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) {
|
||||
unset($this->signals[$signal]);
|
||||
}
|
||||
}
|
||||
|
||||
public function call($signal)
|
||||
{
|
||||
if (!isset($this->signals[$signal])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->signals[$signal] as $listener) {
|
||||
\call_user_func($listener, $signal);
|
||||
}
|
||||
}
|
||||
|
||||
public function count($signal)
|
||||
{
|
||||
if (!isset($this->signals[$signal])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return \count($this->signals[$signal]);
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return !$this->signals;
|
||||
}
|
||||
}
|
||||
330
vendor/react/event-loop/src/StreamSelectLoop.php
vendored
Executable file
330
vendor/react/event-loop/src/StreamSelectLoop.php
vendored
Executable file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use React\EventLoop\Timer\Timers;
|
||||
|
||||
/**
|
||||
* A `stream_select()` based event loop.
|
||||
*
|
||||
* This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
|
||||
* function and is the only implementation that works out of the box with PHP.
|
||||
*
|
||||
* This event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM.
|
||||
* This means that no installation is required and this library works on all
|
||||
* platforms and supported PHP versions.
|
||||
* Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory)
|
||||
* will use this event loop by default if you do not install any of the event loop
|
||||
* extensions listed below.
|
||||
*
|
||||
* Under the hood, it does a simple `select` system call.
|
||||
* This system call is limited to the maximum file descriptor number of
|
||||
* `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
|
||||
* (`m` being the maximum file descriptor number passed).
|
||||
* This means that you may run into issues when handling thousands of streams
|
||||
* concurrently and you may want to look into using one of the alternative
|
||||
* event loop implementations listed below in this case.
|
||||
* If your use case is among the many common use cases that involve handling only
|
||||
* dozens or a few hundred streams at once, then this event loop implementation
|
||||
* performs really well.
|
||||
*
|
||||
* If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
|
||||
* this event loop implementation requires `ext-pcntl`.
|
||||
* This extension is only available for Unix-like platforms and does not support
|
||||
* Windows.
|
||||
* It is commonly installed as part of many PHP distributions.
|
||||
* If this extension is missing (or you're running on Windows), signal handling is
|
||||
* not supported and throws a `BadMethodCallException` instead.
|
||||
*
|
||||
* This event loop is known to rely on wall-clock time to schedule future timers
|
||||
* when using any version before PHP 7.3, because a monotonic time source is
|
||||
* only available as of PHP 7.3 (`hrtime()`).
|
||||
* While this does not affect many common use cases, this is an important
|
||||
* distinction for programs that rely on a high time precision or on systems
|
||||
* that are subject to discontinuous time adjustments (time jumps).
|
||||
* This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
|
||||
* then adjust your system time forward by 20s, the timer may trigger in 10s.
|
||||
* See also [`addTimer()`](#addtimer) for more details.
|
||||
*
|
||||
* @link https://www.php.net/manual/en/function.stream-select.php
|
||||
*/
|
||||
final class StreamSelectLoop implements LoopInterface
|
||||
{
|
||||
/** @internal */
|
||||
const MICROSECONDS_PER_SECOND = 1000000;
|
||||
|
||||
private $futureTickQueue;
|
||||
private $timers;
|
||||
private $readStreams = array();
|
||||
private $readListeners = array();
|
||||
private $writeStreams = array();
|
||||
private $writeListeners = array();
|
||||
private $running;
|
||||
private $pcntl = false;
|
||||
private $pcntlPoll = false;
|
||||
private $signals;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->futureTickQueue = new FutureTickQueue();
|
||||
$this->timers = new Timers();
|
||||
$this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch');
|
||||
$this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals');
|
||||
$this->signals = new SignalsHandler();
|
||||
|
||||
// prefer async signals if available (PHP 7.1+) or fall back to dispatching on each tick
|
||||
if ($this->pcntl && !$this->pcntlPoll) {
|
||||
\pcntl_async_signals(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->readStreams[$key])) {
|
||||
$this->readStreams[$key] = $stream;
|
||||
$this->readListeners[$key] = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->writeStreams[$key])) {
|
||||
$this->writeStreams[$key] = $stream;
|
||||
$this->writeListeners[$key] = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
unset(
|
||||
$this->readStreams[$key],
|
||||
$this->readListeners[$key]
|
||||
);
|
||||
}
|
||||
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
unset(
|
||||
$this->writeStreams[$key],
|
||||
$this->writeListeners[$key]
|
||||
);
|
||||
}
|
||||
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, false);
|
||||
|
||||
$this->timers->add($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
$timer = new Timer($interval, $callback, true);
|
||||
|
||||
$this->timers->add($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
$this->timers->cancel($timer);
|
||||
}
|
||||
|
||||
public function futureTick($listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
if ($this->pcntl === false) {
|
||||
throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"');
|
||||
}
|
||||
|
||||
$first = $this->signals->count($signal) === 0;
|
||||
$this->signals->add($signal, $listener);
|
||||
|
||||
if ($first) {
|
||||
\pcntl_signal($signal, array($this->signals, 'call'));
|
||||
}
|
||||
}
|
||||
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
if (!$this->signals->count($signal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->signals->remove($signal, $listener);
|
||||
|
||||
if ($this->signals->count($signal) === 0) {
|
||||
\pcntl_signal($signal, \SIG_DFL);
|
||||
}
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$this->timers->tick();
|
||||
|
||||
// Future-tick queue has pending callbacks ...
|
||||
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
|
||||
$timeout = 0;
|
||||
|
||||
// There is a pending timer, only block until it is due ...
|
||||
} elseif ($scheduledAt = $this->timers->getFirst()) {
|
||||
$timeout = $scheduledAt - $this->timers->getTime();
|
||||
if ($timeout < 0) {
|
||||
$timeout = 0;
|
||||
} else {
|
||||
// Convert float seconds to int microseconds.
|
||||
// Ensure we do not exceed maximum integer size, which may
|
||||
// cause the loop to tick once every ~35min on 32bit systems.
|
||||
$timeout *= self::MICROSECONDS_PER_SECOND;
|
||||
$timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout;
|
||||
}
|
||||
|
||||
// The only possible event is stream or signal activity, so wait forever ...
|
||||
} elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) {
|
||||
$timeout = null;
|
||||
|
||||
// There's nothing left to do ...
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->waitForStreamActivity($timeout);
|
||||
}
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait/check for stream activity, or until the next timer is due.
|
||||
*
|
||||
* @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
|
||||
*/
|
||||
private function waitForStreamActivity($timeout)
|
||||
{
|
||||
$read = $this->readStreams;
|
||||
$write = $this->writeStreams;
|
||||
|
||||
$available = $this->streamSelect($read, $write, $timeout);
|
||||
if ($this->pcntlPoll) {
|
||||
\pcntl_signal_dispatch();
|
||||
}
|
||||
if (false === $available) {
|
||||
// if a system call has been interrupted,
|
||||
// we cannot rely on it's outcome
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($read as $stream) {
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->readListeners[$key])) {
|
||||
\call_user_func($this->readListeners[$key], $stream);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($write as $stream) {
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeListeners[$key])) {
|
||||
\call_user_func($this->writeListeners[$key], $stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate a stream_select() implementation that does not break when passed
|
||||
* empty stream arrays.
|
||||
*
|
||||
* @param array $read An array of read streams to select upon.
|
||||
* @param array $write An array of write streams to select upon.
|
||||
* @param int|null $timeout Activity timeout in microseconds, or null to wait forever.
|
||||
*
|
||||
* @return int|false The total number of streams that are ready for read/write.
|
||||
* Can return false if stream_select() is interrupted by a signal.
|
||||
*/
|
||||
private function streamSelect(array &$read, array &$write, $timeout)
|
||||
{
|
||||
if ($read || $write) {
|
||||
// We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`.
|
||||
// However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms.
|
||||
// Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts.
|
||||
// We work around this by adding all sockets that look like a pending connection attempt to `exceptfds` automatically on Windows and merge it back later.
|
||||
// This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix).
|
||||
// Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state.
|
||||
// @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
|
||||
$except = null;
|
||||
if (\DIRECTORY_SEPARATOR === '\\') {
|
||||
$except = array();
|
||||
foreach ($write as $key => $socket) {
|
||||
if (!isset($read[$key]) && @\ftell($socket) === 0) {
|
||||
$except[$key] = $socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var ?callable $previous */
|
||||
$previous = \set_error_handler(function ($errno, $errstr) use (&$previous) {
|
||||
// suppress warnings that occur when `stream_select()` is interrupted by a signal
|
||||
// PHP defines `EINTR` through `ext-sockets` or `ext-pcntl`, otherwise use common default (Linux & Mac)
|
||||
$eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : (\defined('PCNTL_EINTR') ? \PCNTL_EINTR : 4);
|
||||
if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// forward any other error to registered error handler or print warning
|
||||
return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false;
|
||||
});
|
||||
|
||||
try {
|
||||
$ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
|
||||
\restore_error_handler();
|
||||
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
|
||||
\restore_error_handler();
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
\restore_error_handler();
|
||||
throw $e;
|
||||
} // @codeCoverageIgnoreEnd
|
||||
|
||||
if ($except) {
|
||||
$write = \array_merge($write, $except);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if ($timeout > 0) {
|
||||
\usleep($timeout);
|
||||
} elseif ($timeout === null) {
|
||||
// wait forever (we only reach this if we're only awaiting signals)
|
||||
// this may be interrupted and return earlier when a signal is received
|
||||
\sleep(PHP_INT_MAX);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
60
vendor/react/event-loop/src/Tick/FutureTickQueue.php
vendored
Executable file
60
vendor/react/event-loop/src/Tick/FutureTickQueue.php
vendored
Executable file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Tick;
|
||||
|
||||
use SplQueue;
|
||||
|
||||
/**
|
||||
* A tick queue implementation that can hold multiple callback functions
|
||||
*
|
||||
* This class should only be used internally, see LoopInterface instead.
|
||||
*
|
||||
* @see LoopInterface
|
||||
* @internal
|
||||
*/
|
||||
final class FutureTickQueue
|
||||
{
|
||||
private $queue;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->queue = new SplQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be invoked on a future tick of the event loop.
|
||||
*
|
||||
* Callbacks are guaranteed to be executed in the order they are enqueued.
|
||||
*
|
||||
* @param callable $listener The callback to invoke.
|
||||
*/
|
||||
public function add($listener)
|
||||
{
|
||||
$this->queue->enqueue($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the callback queue.
|
||||
*/
|
||||
public function tick()
|
||||
{
|
||||
// Only invoke as many callbacks as were on the queue when tick() was called.
|
||||
$count = $this->queue->count();
|
||||
|
||||
while ($count--) {
|
||||
\call_user_func(
|
||||
$this->queue->dequeue()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the next tick queue is empty.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return $this->queue->isEmpty();
|
||||
}
|
||||
}
|
||||
55
vendor/react/event-loop/src/Timer/Timer.php
vendored
Executable file
55
vendor/react/event-loop/src/Timer/Timer.php
vendored
Executable file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Timer;
|
||||
|
||||
use React\EventLoop\TimerInterface;
|
||||
|
||||
/**
|
||||
* The actual connection implementation for TimerInterface
|
||||
*
|
||||
* This class should only be used internally, see TimerInterface instead.
|
||||
*
|
||||
* @see TimerInterface
|
||||
* @internal
|
||||
*/
|
||||
final class Timer implements TimerInterface
|
||||
{
|
||||
const MIN_INTERVAL = 0.000001;
|
||||
|
||||
private $interval;
|
||||
private $callback;
|
||||
private $periodic;
|
||||
|
||||
/**
|
||||
* Constructor initializes the fields of the Timer
|
||||
*
|
||||
* @param float $interval The interval after which this timer will execute, in seconds
|
||||
* @param callable $callback The callback that will be executed when this timer elapses
|
||||
* @param bool $periodic Whether the time is periodic
|
||||
*/
|
||||
public function __construct($interval, $callback, $periodic = false)
|
||||
{
|
||||
if ($interval < self::MIN_INTERVAL) {
|
||||
$interval = self::MIN_INTERVAL;
|
||||
}
|
||||
|
||||
$this->interval = (float) $interval;
|
||||
$this->callback = $callback;
|
||||
$this->periodic = (bool) $periodic;
|
||||
}
|
||||
|
||||
public function getInterval()
|
||||
{
|
||||
return $this->interval;
|
||||
}
|
||||
|
||||
public function getCallback()
|
||||
{
|
||||
return $this->callback;
|
||||
}
|
||||
|
||||
public function isPeriodic()
|
||||
{
|
||||
return $this->periodic;
|
||||
}
|
||||
}
|
||||
113
vendor/react/event-loop/src/Timer/Timers.php
vendored
Executable file
113
vendor/react/event-loop/src/Timer/Timers.php
vendored
Executable file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Timer;
|
||||
|
||||
use React\EventLoop\TimerInterface;
|
||||
|
||||
/**
|
||||
* A scheduler implementation that can hold multiple timer instances
|
||||
*
|
||||
* This class should only be used internally, see TimerInterface instead.
|
||||
*
|
||||
* @see TimerInterface
|
||||
* @internal
|
||||
*/
|
||||
final class Timers
|
||||
{
|
||||
private $time;
|
||||
private $timers = array();
|
||||
private $schedule = array();
|
||||
private $sorted = true;
|
||||
private $useHighResolution;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// prefer high-resolution timer, available as of PHP 7.3+
|
||||
$this->useHighResolution = \function_exists('hrtime');
|
||||
}
|
||||
|
||||
public function updateTime()
|
||||
{
|
||||
return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
{
|
||||
return $this->time ?: $this->updateTime();
|
||||
}
|
||||
|
||||
public function add(TimerInterface $timer)
|
||||
{
|
||||
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
|
||||
$this->timers[$id] = $timer;
|
||||
$this->schedule[$id] = $timer->getInterval() + $this->updateTime();
|
||||
$this->sorted = false;
|
||||
}
|
||||
|
||||
public function contains(TimerInterface $timer)
|
||||
{
|
||||
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
|
||||
return isset($this->timers[$id]);
|
||||
}
|
||||
|
||||
public function cancel(TimerInterface $timer)
|
||||
{
|
||||
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
|
||||
unset($this->timers[$id], $this->schedule[$id]);
|
||||
}
|
||||
|
||||
public function getFirst()
|
||||
{
|
||||
// ensure timers are sorted to simply accessing next (first) one
|
||||
if (!$this->sorted) {
|
||||
$this->sorted = true;
|
||||
\asort($this->schedule);
|
||||
}
|
||||
|
||||
return \reset($this->schedule);
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return \count($this->timers) === 0;
|
||||
}
|
||||
|
||||
public function tick()
|
||||
{
|
||||
// hot path: skip timers if nothing is scheduled
|
||||
if (!$this->schedule) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure timers are sorted so we can execute in order
|
||||
if (!$this->sorted) {
|
||||
$this->sorted = true;
|
||||
\asort($this->schedule);
|
||||
}
|
||||
|
||||
$time = $this->updateTime();
|
||||
|
||||
foreach ($this->schedule as $id => $scheduled) {
|
||||
// schedule is ordered, so loop until first timer that is not scheduled for execution now
|
||||
if ($scheduled >= $time) {
|
||||
break;
|
||||
}
|
||||
|
||||
// skip any timers that are removed while we process the current schedule
|
||||
if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timer = $this->timers[$id];
|
||||
\call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
// re-schedule if this is a periodic timer and it has not been cancelled explicitly already
|
||||
if ($timer->isPeriodic() && isset($this->timers[$id])) {
|
||||
$this->schedule[$id] = $timer->getInterval() + $time;
|
||||
$this->sorted = false;
|
||||
} else {
|
||||
unset($this->timers[$id], $this->schedule[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
vendor/react/event-loop/src/TimerInterface.php
vendored
Executable file
27
vendor/react/event-loop/src/TimerInterface.php
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
interface TimerInterface
|
||||
{
|
||||
/**
|
||||
* Get the interval after which this timer will execute, in seconds
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getInterval();
|
||||
|
||||
/**
|
||||
* Get the callback that will be executed when this timer elapses
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public function getCallback();
|
||||
|
||||
/**
|
||||
* Determine whether the time is periodic
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPeriodic();
|
||||
}
|
||||
920
vendor/react/http/CHANGELOG.md
vendored
Executable file
920
vendor/react/http/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,920 @@
|
||||
# Changelog
|
||||
|
||||
## 1.11.0 (2024-11-20)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable types.
|
||||
(#537 by @clue)
|
||||
|
||||
* Feature: Allow underscore character in Uri host.
|
||||
(#524 by @lulhum)
|
||||
|
||||
* Improve test suite to fix expected error code when ext-sockets is not enabled.
|
||||
(#539 by @WyriHaximus)
|
||||
|
||||
## 1.10.0 (2024-03-27)
|
||||
|
||||
* Feature: Add new PSR-7 implementation and remove dated RingCentral PSR-7 dependency.
|
||||
(#518, #519, #520 and #522 by @clue)
|
||||
|
||||
This changeset allows us to maintain our own PSR-7 implementation and reduce
|
||||
dependencies on external projects. It also improves performance slightly and
|
||||
does not otherwise affect our public API. If you want to explicitly install
|
||||
the old RingCentral PSR-7 dependency, you can still install it like this:
|
||||
|
||||
```bash
|
||||
composer require ringcentral/psr7
|
||||
```
|
||||
|
||||
* Feature: Add new `Uri` class for new PSR-7 implementation.
|
||||
(#521 by @clue)
|
||||
|
||||
* Feature: Validate outgoing HTTP message headers and reject invalid messages.
|
||||
(#523 by @clue)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#508 by @clue)
|
||||
|
||||
* Fix: Fix HTTP client to omit `Transfer-Encoding: chunked` when streaming empty request body.
|
||||
(#516 by @clue)
|
||||
|
||||
* Fix: Ensure connection close handler is cleaned up for each request.
|
||||
(#515 by @WyriHaximus)
|
||||
|
||||
* Update test suite and avoid unhandled promise rejections.
|
||||
(#501 and #502 by @clue)
|
||||
|
||||
## 1.9.0 (2023-04-26)
|
||||
|
||||
This is a **SECURITY** and feature release for the 1.x series of ReactPHP's HTTP component.
|
||||
|
||||
* Security fix: This release fixes a medium severity security issue in ReactPHP's HTTP server component
|
||||
that affects all versions between `v0.8.0` and `v1.8.0`. All users are encouraged to upgrade immediately.
|
||||
(CVE-2023-26044 reported and fixed by @WyriHaximus)
|
||||
|
||||
* Feature: Support HTTP keep-alive for HTTP client (reusing persistent connections).
|
||||
(#481, #484, #486 and #495 by @clue)
|
||||
|
||||
This feature offers significant performance improvements when sending many
|
||||
requests to the same host as it avoids recreating the underlying TCP/IP
|
||||
connection and repeating the TLS handshake for secure HTTPS requests.
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
// Up to 300% faster! HTTP keep-alive is enabled by default
|
||||
$response = React\Async\await($browser->get('https://httpbingo.org/redirect/6'));
|
||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
```
|
||||
|
||||
* Feature: Add `Request` class to represent outgoing HTTP request message.
|
||||
(#480 by @clue)
|
||||
|
||||
* Feature: Preserve request method and body for `307 Temporary Redirect` and `308 Permanent Redirect`.
|
||||
(#442 by @dinooo13)
|
||||
|
||||
* Feature: Include buffer logic to avoid dependency on reactphp/promise-stream.
|
||||
(#482 by @clue)
|
||||
|
||||
* Improve test suite and project setup and report failed assertions.
|
||||
(#478 by @clue, #487 and #491 by @WyriHaximus and #475 and #479 by @SimonFrings)
|
||||
|
||||
## 1.8.0 (2022-09-29)
|
||||
|
||||
* Feature: Support for default request headers.
|
||||
(#461 by @51imyy)
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
$browser = $browser->withHeader('User-Agent', 'ACME');
|
||||
|
||||
$browser->get($url)->then(…);
|
||||
```
|
||||
|
||||
* Feature: Forward compatibility with upcoming Promise v3.
|
||||
(#460 by @clue)
|
||||
|
||||
## 1.7.0 (2022-08-23)
|
||||
|
||||
This is a **SECURITY** and feature release for the 1.x series of ReactPHP's HTTP component.
|
||||
|
||||
* Security fix: This release fixes a medium severity security issue in ReactPHP's HTTP server component
|
||||
that affects all versions between `v0.7.0` and `v1.6.0`. All users are encouraged to upgrade immediately.
|
||||
Special thanks to Marco Squarcina (TU Wien) for reporting this and working with us to coordinate this release.
|
||||
(CVE-2022-36032 reported by @lavish and fixed by @clue)
|
||||
|
||||
* Feature: Improve HTTP server performance by ~20%, reuse syscall values for clock time and socket addresses.
|
||||
(#457 and #467 by @clue)
|
||||
|
||||
* Feature: Full PHP 8.2+ compatibility, refactor internal `Transaction` to avoid assigning dynamic properties.
|
||||
(#459 by @clue and #466 by @WyriHaximus)
|
||||
|
||||
* Feature / Fix: Allow explicit `Content-Length` response header on `HEAD` requests.
|
||||
(#444 by @mrsimonbennett)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#452 by @clue, #458 by @nhedger, #448 by @jorrit and #446 by @SimonFrings)
|
||||
|
||||
* Improve test suite, update to use new reactphp/async package instead of clue/reactphp-block,
|
||||
skip memory tests when lowering memory limit fails and fix legacy HHVM build.
|
||||
(#464 and #440 by @clue and #450 by @SimonFrings)
|
||||
|
||||
## 1.6.0 (2022-02-03)
|
||||
|
||||
* Feature: Add factory methods for common HTML/JSON/plaintext/XML response types.
|
||||
(#439 by @clue)
|
||||
|
||||
```php
|
||||
$response = React\Http\Response\html("<h1>Hello wörld!</h1>\n");
|
||||
$response = React\Http\Response\json(['message' => 'Hello wörld!']);
|
||||
$response = React\Http\Response\plaintext("Hello wörld!\n");
|
||||
$response = React\Http\Response\xml("<message>Hello wörld!</message>\n");
|
||||
```
|
||||
|
||||
* Feature: Expose all status code constants via `Response` class.
|
||||
(#432 by @clue)
|
||||
|
||||
```php
|
||||
$response = new React\Http\Message\Response(
|
||||
React\Http\Message\Response::STATUS_OK, // 200 OK
|
||||
…
|
||||
);
|
||||
$response = new React\Http\Message\Response(
|
||||
React\Http\Message\Response::STATUS_NOT_FOUND, // 404 Not Found
|
||||
…
|
||||
);
|
||||
```
|
||||
|
||||
* Feature: Full support for PHP 8.1 release.
|
||||
(#433 by @SimonFrings and #434 by @clue)
|
||||
|
||||
* Feature / Fix: Improve protocol handling for HTTP responses with no body.
|
||||
(#429 and #430 by @clue)
|
||||
|
||||
* Internal refactoring and internal improvements for handling requests and responses.
|
||||
(#422 by @WyriHaximus and #431 by @clue)
|
||||
|
||||
* Improve documentation, update proxy examples, include error reporting in examples.
|
||||
(#420, #424, #426, and #427 by @clue)
|
||||
|
||||
* Update test suite to use default loop.
|
||||
(#438 by @clue)
|
||||
|
||||
## 1.5.0 (2021-08-04)
|
||||
|
||||
* Feature: Update `Browser` signature to take optional `$connector` as first argument and
|
||||
to match new Socket API without nullable loop arguments.
|
||||
(#418 and #419 by @clue)
|
||||
|
||||
```php
|
||||
// unchanged
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
// deprecated
|
||||
$browser = new React\Http\Browser(null, $connector);
|
||||
$browser = new React\Http\Browser($loop, $connector);
|
||||
|
||||
// new
|
||||
$browser = new React\Http\Browser($connector);
|
||||
$browser = new React\Http\Browser($connector, $loop);
|
||||
```
|
||||
|
||||
* Feature: Rename `Server` to `HttpServer` to avoid class name collisions and
|
||||
to avoid any ambiguities with regards to the new `SocketServer` API.
|
||||
(#417 and #419 by @clue)
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$server = new React\Http\Server($handler);
|
||||
$server->listen(new React\Socket\Server(8080));
|
||||
|
||||
// new
|
||||
$http = new React\Http\HttpServer($handler);
|
||||
$http->listen(new React\Socket\SocketServer('127.0.0.1:8080'));
|
||||
```
|
||||
|
||||
## 1.4.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).
|
||||
(#410 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$browser = new React\Http\Browser($loop);
|
||||
$server = new React\Http\Server($loop, $handler);
|
||||
|
||||
// new (using default loop)
|
||||
$browser = new React\Http\Browser();
|
||||
$server = new React\Http\Server($handler);
|
||||
```
|
||||
|
||||
## 1.3.0 (2021-04-11)
|
||||
|
||||
* Feature: Support persistent connections (`Connection: keep-alive`).
|
||||
(#405 by @clue)
|
||||
|
||||
This shows a noticeable performance improvement especially when benchmarking
|
||||
using persistent connections (which is the default pretty much everywhere).
|
||||
Together with other changes in this release, this improves benchmarking
|
||||
performance by around 100%.
|
||||
|
||||
* Feature: Require `Host` request header for HTTP/1.1 requests.
|
||||
(#404 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#398 by @fritz-gerneth and #399 and #400 by @pavog)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
||||
(#402 by @SimonFrings)
|
||||
|
||||
## 1.2.0 (2020-12-04)
|
||||
|
||||
* Feature: Keep request body in memory also after consuming request body.
|
||||
(#395 by @clue)
|
||||
|
||||
This means consumers can now always access the complete request body as
|
||||
detailed in the documentation. This allows building custom parsers and more
|
||||
advanced processing models without having to mess with the default parsers.
|
||||
|
||||
## 1.1.0 (2020-09-11)
|
||||
|
||||
* Feature: Support upcoming PHP 8 release, update to reactphp/socket v1.6 and adjust type checks for invalid chunk headers.
|
||||
(#391 by @clue)
|
||||
|
||||
* Feature: Consistently resolve base URL according to HTTP specs.
|
||||
(#379 by @clue)
|
||||
|
||||
* Feature / Fix: Expose `Transfer-Encoding: chunked` response header and fix chunked responses for `HEAD` requests.
|
||||
(#381 by @clue)
|
||||
|
||||
* Internal refactoring to remove unneeded `MessageFactory` and `Response` classes.
|
||||
(#380 and #389 by @clue)
|
||||
|
||||
* Minor documentation improvements and improve test suite, update to support PHPUnit 9.3.
|
||||
(#385 by @clue and #393 by @SimonFrings)
|
||||
|
||||
## 1.0.0 (2020-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2020/announcing-reactphp-http).
|
||||
|
||||
* 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.
|
||||
|
||||
This update involves some major new features and a number of BC breaks due to
|
||||
some necessary API cleanup. We've tried hard to avoid BC breaks where possible
|
||||
and minimize impact otherwise. We expect that most consumers of this package
|
||||
will be affected by BC breaks, but updating should take no longer than a few
|
||||
minutes. See below for more details:
|
||||
|
||||
* Feature: Add async HTTP client implementation.
|
||||
(#368 by @clue)
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser($loop);
|
||||
$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
echo $response->getBody();
|
||||
});
|
||||
```
|
||||
|
||||
The code has been imported as-is from [clue/reactphp-buzz v2.9.0](https://github.com/clue/reactphp-buzz),
|
||||
with only minor changes to the namespace and we otherwise leave all the existing APIs unchanged.
|
||||
Upgrading from [clue/reactphp-buzz v2.9.0](https://github.com/clue/reactphp-buzz)
|
||||
to this release should be a matter of updating some namespace references only:
|
||||
|
||||
```php
|
||||
// old
|
||||
$browser = new Clue\React\Buzz\Browser($loop);
|
||||
|
||||
// new
|
||||
$browser = new React\Http\Browser($loop);
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `LoopInterface` as required first constructor argument to `Server` and
|
||||
change `Server` to accept variadic middleware handlers instead of `array`.
|
||||
(#361 and #362 by @WyriHaximus)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new React\Http\Server($handler);
|
||||
$server = new React\Http\Server([$middleware, $handler]);
|
||||
|
||||
// new
|
||||
$server = new React\Http\Server($loop, $handler);
|
||||
$server = new React\Http\Server($loop, $middleware, $handler);
|
||||
```
|
||||
|
||||
* Feature / BC break: Move `Response` class to `React\Http\Message\Response` and
|
||||
expose `ServerRequest` class to `React\Http\Message\ServerRequest`.
|
||||
(#370 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$response = new React\Http\Response(200, [], 'Hello!');
|
||||
|
||||
// new
|
||||
$response = new React\Http\Message\Response(200, [], 'Hello!');
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `StreamingRequestMiddleware` to stream incoming requests, mark `StreamingServer` as internal.
|
||||
(#367 by @clue)
|
||||
|
||||
```php
|
||||
// old: advanced StreamingServer is now internal only
|
||||
$server = new React\Http\StreamingServer($handler);
|
||||
|
||||
// new: use StreamingRequestMiddleware instead of StreamingServer
|
||||
$server = new React\Http\Server(
|
||||
$loop,
|
||||
new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
$handler
|
||||
);
|
||||
```
|
||||
|
||||
* Feature / BC break: Improve default concurrency to 1024 requests and cap default request buffer at 64K.
|
||||
(#371 by @clue)
|
||||
|
||||
This improves default concurrency to 1024 requests and caps the default request buffer at 64K.
|
||||
The previous defaults resulted in just 4 concurrent requests with a request buffer of 8M.
|
||||
See [`Server`](README.md#server) for details on how to override these defaults.
|
||||
|
||||
* Feature: Expose ReactPHP in `User-Agent` client-side request header and in `Server` server-side response header.
|
||||
(#374 by @clue)
|
||||
|
||||
* Mark all classes as `final` to discourage inheriting from it.
|
||||
(#373 by @WyriHaximus)
|
||||
|
||||
* Improve documentation and use fully-qualified class names throughout the documentation and
|
||||
add ReactPHP core team as authors to `composer.json` and license file.
|
||||
(#366 and #369 by @WyriHaximus and #375 by @clue)
|
||||
|
||||
* Improve test suite and support skipping all online tests with `--exclude-group internet`.
|
||||
(#372 by @clue)
|
||||
|
||||
## 0.8.7 (2020-07-05)
|
||||
|
||||
* Fix: Fix parsing multipart request body with quoted header parameters (dot net).
|
||||
(#363 by @ebimmel)
|
||||
|
||||
* Fix: Fix calculating concurrency when `post_max_size` ini is unlimited.
|
||||
(#365 by @clue)
|
||||
|
||||
* Improve test suite to run tests on PHPUnit 9 and clean up test suite.
|
||||
(#364 by @SimonFrings)
|
||||
|
||||
## 0.8.6 (2020-01-12)
|
||||
|
||||
* Fix: Fix parsing `Cookie` request header with comma in its values.
|
||||
(#352 by @fiskie)
|
||||
|
||||
* Fix: Avoid unneeded warning when decoding invalid data on PHP 7.4.
|
||||
(#357 by @WyriHaximus)
|
||||
|
||||
* Add .gitattributes to exclude dev files from exports.
|
||||
(#353 by @reedy)
|
||||
|
||||
## 0.8.5 (2019-10-29)
|
||||
|
||||
* Internal refactorings and optimizations to improve request parsing performance.
|
||||
Benchmarks suggest number of requests/s improved by ~30% for common `GET` requests.
|
||||
(#345, #346, #349 and #350 by @clue)
|
||||
|
||||
* Add documentation and example for JSON/XML request body and
|
||||
improve documentation for concurrency and streaming requests and for error handling.
|
||||
(#341 and #342 by @clue)
|
||||
|
||||
## 0.8.4 (2019-01-16)
|
||||
|
||||
* Improvement: Internal refactoring to simplify response header logic.
|
||||
(#321 by @clue)
|
||||
|
||||
* Improvement: Assign Content-Length response header automatically only when size is known.
|
||||
(#329 by @clue)
|
||||
|
||||
* Improvement: Import global functions for better performance.
|
||||
(#330 by @WyriHaximus)
|
||||
|
||||
## 0.8.3 (2018-04-11)
|
||||
|
||||
* Feature: Do not pause connection stream to detect closed connections immediately.
|
||||
(#315 by @clue)
|
||||
|
||||
* Feature: Keep incoming `Transfer-Encoding: chunked` request header.
|
||||
(#316 by @clue)
|
||||
|
||||
* Feature: Reject invalid requests that contain both `Content-Length` and `Transfer-Encoding` request headers.
|
||||
(#318 by @clue)
|
||||
|
||||
* Minor internal refactoring to simplify connection close logic after sending response.
|
||||
(#317 by @clue)
|
||||
|
||||
## 0.8.2 (2018-04-06)
|
||||
|
||||
* Fix: Do not pass `$next` handler to final request handler.
|
||||
(#308 by @clue)
|
||||
|
||||
* Fix: Fix awaiting queued handlers when cancelling a queued handler.
|
||||
(#313 by @clue)
|
||||
|
||||
* Fix: Fix Server to skip `SERVER_ADDR` params for Unix domain sockets (UDS).
|
||||
(#307 by @clue)
|
||||
|
||||
* Documentation for PSR-15 middleware and minor documentation improvements.
|
||||
(#314 by @clue and #297, #298 and #310 by @seregazhuk)
|
||||
|
||||
* Minor code improvements and micro optimizations.
|
||||
(#301 by @seregazhuk and #305 by @kalessil)
|
||||
|
||||
## 0.8.1 (2018-01-05)
|
||||
|
||||
* Major request handler performance improvement. Benchmarks suggest number of
|
||||
requests/s improved by more than 50% for common `GET` requests!
|
||||
We now avoid queuing, buffering and wrapping incoming requests in promises
|
||||
when we're below limits and instead can directly process common requests.
|
||||
(#291, #292, #293, #294 and #296 by @clue)
|
||||
|
||||
* Fix: Fix concurrent invoking next middleware request handlers
|
||||
(#293 by @clue)
|
||||
|
||||
* Small code improvements
|
||||
(#286 by @seregazhuk)
|
||||
|
||||
* Improve test suite to be less fragile when using `ext-event` and
|
||||
fix test suite forward compatibility with upcoming EventLoop releases
|
||||
(#288 and #290 by @clue)
|
||||
|
||||
## 0.8.0 (2017-12-12)
|
||||
|
||||
* Feature / BC break: Add new `Server` facade that buffers and parses incoming
|
||||
HTTP requests. This provides full PSR-7 compatibility, including support for
|
||||
form submissions with POST fields and file uploads.
|
||||
The old `Server` has been renamed to `StreamingServer` for advanced usage
|
||||
and is used internally.
|
||||
(#266, #271, #281, #282, #283 and #284 by @WyriHaximus and @clue)
|
||||
|
||||
```php
|
||||
// old: handle incomplete/streaming requests
|
||||
$server = new Server($handler);
|
||||
|
||||
// new: handle complete, buffered and parsed requests
|
||||
// new: full PSR-7 support, including POST fields and file uploads
|
||||
$server = new Server($handler);
|
||||
|
||||
// new: handle incomplete/streaming requests
|
||||
$server = new StreamingServer($handler);
|
||||
```
|
||||
|
||||
> While this is technically a small BC break, this should in fact not break
|
||||
most consuming code. If you rely on the old request streaming, you can
|
||||
explicitly use the advanced `StreamingServer` to restore old behavior.
|
||||
|
||||
* Feature: Add support for middleware request handler arrays
|
||||
(#215, #228, #229, #236, #237, #238, #246, #247, #277, #279 and #285 by @WyriHaximus, @clue and @jsor)
|
||||
|
||||
```php
|
||||
// new: middleware request handler arrays
|
||||
$server = new Server(array(
|
||||
function (ServerRequestInterface $request, callable $next) {
|
||||
$request = $request->withHeader('Processed', time());
|
||||
return $next($request);
|
||||
},
|
||||
function (ServerRequestInterface $request) {
|
||||
return new Response();
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Add support for limiting how many next request handlers can be
|
||||
executed concurrently (`LimitConcurrentRequestsMiddleware`)
|
||||
(#272 by @clue and @WyriHaximus)
|
||||
|
||||
```php
|
||||
// new: explicitly limit concurrency
|
||||
$server = new Server(array(
|
||||
new LimitConcurrentRequestsMiddleware(10),
|
||||
$handler
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Add support for buffering the incoming request body
|
||||
(`RequestBodyBufferMiddleware`).
|
||||
This feature mimics PHP's default behavior and respects its `post_max_size`
|
||||
ini setting by default and allows explicit configuration.
|
||||
(#216, #224, #263, #276 and #278 by @WyriHaximus and #235 by @andig)
|
||||
|
||||
```php
|
||||
// new: buffer up to 10 requests with 8 MiB each
|
||||
$server = new StreamingServer(array(
|
||||
new LimitConcurrentRequestsMiddleware(10),
|
||||
new RequestBodyBufferMiddleware('8M'),
|
||||
$handler
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Add support for parsing form submissions with POST fields and file
|
||||
uploads (`RequestBodyParserMiddleware`).
|
||||
This feature mimics PHP's default behavior and respects its ini settings and
|
||||
`MAX_FILE_SIZE` POST fields by default and allows explicit configuration.
|
||||
(#220, #226, #252, #261, #264, #265, #267, #268, #274 by @WyriHaximus and @clue)
|
||||
|
||||
```php
|
||||
// new: buffer up to 10 requests with 8 MiB each
|
||||
// and limit to 4 uploads with 2 MiB each
|
||||
$server = new StreamingServer(array(
|
||||
new LimitConcurrentRequestsMiddleware(10),
|
||||
new RequestBodyBufferMiddleware('8M'),
|
||||
new RequestBodyParserMiddleware('2M', 4)
|
||||
$handler
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Update Socket to work around sending secure HTTPS responses with PHP < 7.1.4
|
||||
(#244 by @clue)
|
||||
|
||||
* Feature: Support sending same response header multiple times (e.g. `Set-Cookie`)
|
||||
(#248 by @clue)
|
||||
|
||||
* Feature: Raise maximum request header size to 8k to match common implementations
|
||||
(#253 by @clue)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6, test
|
||||
against PHP 7.1 and PHP 7.2 and refactor and remove risky and duplicate tests.
|
||||
(#243, #269 and #270 by @carusogabriel and #249 by @clue)
|
||||
|
||||
* Minor code refactoring to move internal classes to `React\Http\Io` namespace
|
||||
and clean up minor code and documentation issues
|
||||
(#251 by @clue, #227 by @kalessil, #240 by @christoph-kluge, #230 by @jsor and #280 by @andig)
|
||||
|
||||
## 0.7.4 (2017-08-16)
|
||||
|
||||
* Improvement: Target evenement 3.0 a long side 2.0 and 1.0
|
||||
(#212 by @WyriHaximus)
|
||||
|
||||
## 0.7.3 (2017-08-14)
|
||||
|
||||
* Feature: Support `Throwable` when setting previous exception from server callback
|
||||
(#155 by @jsor)
|
||||
|
||||
* Fix: Fixed URI parsing for origin-form requests that contain scheme separator
|
||||
such as `/path?param=http://example.com`.
|
||||
(#209 by @aaronbonneau)
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build
|
||||
(#211 by @clue)
|
||||
|
||||
## 0.7.2 (2017-07-04)
|
||||
|
||||
* Fix: Stricter check for invalid request-line in HTTP requests
|
||||
(#206 by @clue)
|
||||
|
||||
* Refactor to use HTTP response reason phrases from response object
|
||||
(#205 by @clue)
|
||||
|
||||
## 0.7.1 (2017-06-17)
|
||||
|
||||
* Fix: Fix parsing CONNECT request without `Host` header
|
||||
(#201 by @clue)
|
||||
|
||||
* Internal preparation for future PSR-7 `UploadedFileInterface`
|
||||
(#199 by @WyriHaximus)
|
||||
|
||||
## 0.7.0 (2017-05-29)
|
||||
|
||||
* Feature / BC break: Use PSR-7 (http-message) standard and
|
||||
`Request-In-Response-Out`-style request handler callback.
|
||||
Pass standard PSR-7 `ServerRequestInterface` and expect any standard
|
||||
PSR-7 `ResponseInterface` in return for the request handler callback.
|
||||
(#146 and #152 and #170 by @legionth)
|
||||
|
||||
```php
|
||||
// old
|
||||
$app = function (Request $request, Response $response) {
|
||||
$response->writeHead(200, array('Content-Type' => 'text/plain'));
|
||||
$response->end("Hello world!\n");
|
||||
};
|
||||
|
||||
// new
|
||||
$app = function (ServerRequestInterface $request) {
|
||||
return new Response(
|
||||
200,
|
||||
array('Content-Type' => 'text/plain'),
|
||||
"Hello world!\n"
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
A `Content-Length` header will automatically be included if the size can be
|
||||
determined from the response body.
|
||||
(#164 by @maciejmrozinski)
|
||||
|
||||
The request handler callback will automatically make sure that responses to
|
||||
HEAD requests and certain status codes, such as `204` (No Content), never
|
||||
contain a response body.
|
||||
(#156 by @clue)
|
||||
|
||||
The intermediary `100 Continue` response will automatically be sent if
|
||||
demanded by a HTTP/1.1 client.
|
||||
(#144 by @legionth)
|
||||
|
||||
The request handler callback can now return a standard `Promise` if
|
||||
processing the request needs some time, such as when querying a database.
|
||||
Similarly, the request handler may return a streaming response if the
|
||||
response body comes from a `ReadableStreamInterface` or its size is
|
||||
unknown in advance.
|
||||
|
||||
```php
|
||||
// old
|
||||
$app = function (Request $request, Response $response) use ($db) {
|
||||
$db->query()->then(function ($result) use ($response) {
|
||||
$response->writeHead(200, array('Content-Type' => 'text/plain'));
|
||||
$response->end($result);
|
||||
});
|
||||
};
|
||||
|
||||
// new
|
||||
$app = function (ServerRequestInterface $request) use ($db) {
|
||||
return $db->query()->then(function ($result) {
|
||||
return new Response(
|
||||
200,
|
||||
array('Content-Type' => 'text/plain'),
|
||||
$result
|
||||
);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
Pending promies and response streams will automatically be canceled once the
|
||||
client connection closes.
|
||||
(#187 and #188 by @clue)
|
||||
|
||||
The `ServerRequestInterface` contains the full effective request URI,
|
||||
server-side parameters, query parameters and parsed cookies values as
|
||||
defined in PSR-7.
|
||||
(#167 by @clue and #174, #175 and #180 by @legionth)
|
||||
|
||||
```php
|
||||
$app = function (ServerRequestInterface $request) {
|
||||
return new Response(
|
||||
200,
|
||||
array('Content-Type' => 'text/plain'),
|
||||
$request->getUri()->getScheme()
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Advanced: Support duplex stream response for `Upgrade` requests such as
|
||||
`Upgrade: WebSocket` or custom protocols and `CONNECT` requests
|
||||
(#189 and #190 by @clue)
|
||||
|
||||
> Note that the request body will currently not be buffered and parsed by
|
||||
default, which depending on your particilar use-case, may limit
|
||||
interoperability with the PSR-7 (http-message) ecosystem.
|
||||
The provided streaming request body interfaces allow you to perform
|
||||
buffering and parsing as needed in the request handler callback.
|
||||
See also the README and examples for more details.
|
||||
|
||||
* Feature / BC break: Replace `request` listener with callback function and
|
||||
use `listen()` method to support multiple listening sockets
|
||||
(#97 by @legionth and #193 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($socket);
|
||||
$server->on('request', $app);
|
||||
|
||||
// new
|
||||
$server = new Server($app);
|
||||
$server->listen($socket);
|
||||
```
|
||||
|
||||
* Feature: Support the more advanced HTTP requests, such as
|
||||
`OPTIONS * HTTP/1.1` (`OPTIONS` method in asterisk-form),
|
||||
`GET http://example.com/path HTTP/1.1` (plain proxy requests in absolute-form),
|
||||
`CONNECT example.com:443 HTTP/1.1` (`CONNECT` proxy requests in authority-form)
|
||||
and sanitize `Host` header value across all requests.
|
||||
(#157, #158, #161, #165, #169 and #173 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with Socket v1.0, v0.8, v0.7 and v0.6 and
|
||||
forward compatibility with Stream v1.0 and v0.7
|
||||
(#154, #163, #183, #184 and #191 by @clue)
|
||||
|
||||
* Feature: Simplify examples to ease getting started and
|
||||
add benchmarking example
|
||||
(#151 and #162 by @clue)
|
||||
|
||||
* Improve test suite by adding tests for case insensitive chunked transfer
|
||||
encoding and ignoring HHVM test failures until Travis tests work again.
|
||||
(#150 by @legionth and #185 by @clue)
|
||||
|
||||
## 0.6.0 (2017-03-09)
|
||||
|
||||
* Feature / BC break: The `Request` and `Response` objects now follow strict
|
||||
stream semantics and their respective methods and events.
|
||||
(#116, #129, #133, #135, #136, #137, #138, #140, #141 by @legionth
|
||||
and #122, #123, #130, #131, #132, #142 by @clue)
|
||||
|
||||
This implies that the `Server` now supports proper detection of the request
|
||||
message body stream, such as supporting decoding chunked transfer encoding,
|
||||
delimiting requests with an explicit `Content-Length` header
|
||||
and those with an empty request message body.
|
||||
|
||||
These streaming semantics are compatible with previous Stream v0.5, future
|
||||
compatible with v0.5 and upcoming v0.6 versions and can be used like this:
|
||||
|
||||
```php
|
||||
$http->on('request', function (Request $request, Response $response) {
|
||||
$contentLength = 0;
|
||||
$request->on('data', function ($data) use (&$contentLength) {
|
||||
$contentLength += strlen($data);
|
||||
});
|
||||
|
||||
$request->on('end', function () use ($response, &$contentLength){
|
||||
$response->writeHead(200, array('Content-Type' => 'text/plain'));
|
||||
$response->end("The length of the submitted request body is: " . $contentLength);
|
||||
});
|
||||
|
||||
// an error occured
|
||||
// e.g. on invalid chunked encoded data or an unexpected 'end' event
|
||||
$request->on('error', function (\Exception $exception) use ($response, &$contentLength) {
|
||||
$response->writeHead(400, array('Content-Type' => 'text/plain'));
|
||||
$response->end("An error occured while reading at length: " . $contentLength);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, the `Request` and `Response` now strictly follow the
|
||||
`close()` method and `close` event semantics.
|
||||
Closing the `Request` does not interrupt the underlying TCP/IP in
|
||||
order to allow still sending back a valid response message.
|
||||
Closing the `Response` does terminate the underlying TCP/IP
|
||||
connection in order to clean up resources.
|
||||
|
||||
You should make sure to always attach a `request` event listener
|
||||
like above. The `Server` will not respond to an incoming HTTP
|
||||
request otherwise and keep the TCP/IP connection pending until the
|
||||
other side chooses to close the connection.
|
||||
|
||||
* Feature: Support `HTTP/1.1` and `HTTP/1.0` for `Request` and `Response`.
|
||||
(#124, #125, #126, #127, #128 by @clue and #139 by @legionth)
|
||||
|
||||
The outgoing `Response` will automatically use the same HTTP version as the
|
||||
incoming `Request` message and will only apply `HTTP/1.1` semantics if
|
||||
applicable. This includes that the `Response` will automatically attach a
|
||||
`Date` and `Connection: close` header if applicable.
|
||||
|
||||
This implies that the `Server` now automatically responds with HTTP error
|
||||
messages for invalid requests (status 400) and those exceeding internal
|
||||
request header limits (status 431).
|
||||
|
||||
## 0.5.0 (2017-02-16)
|
||||
|
||||
* Feature / BC break: Change `Request` methods to be in line with PSR-7
|
||||
(#117 by @clue)
|
||||
* Rename `getQuery()` to `getQueryParams()`
|
||||
* Rename `getHttpVersion()` to `getProtocolVersion()`
|
||||
* Change `getHeaders()` to always return an array of string values
|
||||
for each header
|
||||
|
||||
* Feature / BC break: Update Socket component to v0.5 and
|
||||
add secure HTTPS server support
|
||||
(#90 and #119 by @clue)
|
||||
|
||||
```php
|
||||
// old plaintext HTTP server
|
||||
$socket = new React\Socket\Server($loop);
|
||||
$socket->listen(8080, '127.0.0.1');
|
||||
$http = new React\Http\Server($socket);
|
||||
|
||||
// new plaintext HTTP server
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
|
||||
$http = new React\Http\Server($socket);
|
||||
|
||||
// new secure HTTPS server
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
|
||||
$socket = new React\Socket\SecureServer($socket, $loop, array(
|
||||
'local_cert' => __DIR__ . '/localhost.pem'
|
||||
));
|
||||
$http = new React\Http\Server($socket);
|
||||
```
|
||||
|
||||
* BC break: Mark internal APIs as internal or private and
|
||||
remove unneeded `ServerInterface`
|
||||
(#118 by @clue, #95 by @legionth)
|
||||
|
||||
## 0.4.4 (2017-02-13)
|
||||
|
||||
* Feature: Add request header accessors (à la PSR-7)
|
||||
(#103 by @clue)
|
||||
|
||||
```php
|
||||
// get value of host header
|
||||
$host = $request->getHeaderLine('Host');
|
||||
|
||||
// get list of all cookie headers
|
||||
$cookies = $request->getHeader('Cookie');
|
||||
```
|
||||
|
||||
* Feature: Forward `pause()` and `resume()` from `Request` to underlying connection
|
||||
(#110 by @clue)
|
||||
|
||||
```php
|
||||
// support back-pressure when piping request into slower destination
|
||||
$request->pipe($dest);
|
||||
|
||||
// manually pause/resume request
|
||||
$request->pause();
|
||||
$request->resume();
|
||||
```
|
||||
|
||||
* Fix: Fix `100-continue` to be handled case-insensitive and ignore it for HTTP/1.0.
|
||||
Similarly, outgoing response headers are now handled case-insensitive, e.g
|
||||
we no longer apply chunked transfer encoding with mixed-case `Content-Length`.
|
||||
(#107 by @clue)
|
||||
|
||||
```php
|
||||
// now handled case-insensitive
|
||||
$request->expectsContinue();
|
||||
|
||||
// now works just like properly-cased header
|
||||
$response->writeHead($status, array('content-length' => 0));
|
||||
```
|
||||
|
||||
* Fix: Do not emit empty `data` events and ignore empty writes in order to
|
||||
not mess up chunked transfer encoding
|
||||
(#108 and #112 by @clue)
|
||||
|
||||
* Lock and test minimum required dependency versions and support PHPUnit v5
|
||||
(#113, #115 and #114 by @andig)
|
||||
|
||||
## 0.4.3 (2017-02-10)
|
||||
|
||||
* Fix: Do not take start of body into account when checking maximum header size
|
||||
(#88 by @nopolabs)
|
||||
|
||||
* Fix: Remove `data` listener if `HeaderParser` emits an error
|
||||
(#83 by @nick4fake)
|
||||
|
||||
* First class support for PHP 5.3 through PHP 7 and HHVM
|
||||
(#101 and #102 by @clue, #66 by @WyriHaximus)
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev,
|
||||
improving forward compatibility with newer PHPUnit versions
|
||||
and replacing unneeded test stubs
|
||||
(#92 and #93 by @nopolabs, #100 by @legionth)
|
||||
|
||||
## 0.4.2 (2016-11-09)
|
||||
|
||||
* Remove all listeners after emitting error in RequestHeaderParser #68 @WyriHaximus
|
||||
* Catch Guzzle parse request errors #65 @WyriHaximus
|
||||
* Remove branch-alias definition as per reactphp/react#343 #58 @WyriHaximus
|
||||
* Add functional example to ease getting started #64 by @clue
|
||||
* Naming, immutable array manipulation #37 @cboden
|
||||
|
||||
## 0.4.1 (2015-05-21)
|
||||
|
||||
* Replaced guzzle/parser with guzzlehttp/psr7 by @cboden
|
||||
* FIX Continue Header by @iannsp
|
||||
* Missing type hint by @marenzo
|
||||
|
||||
## 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.0 (2013-04-14)
|
||||
|
||||
* Bump React dependencies to v0.3
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Bug fix: Emit end event when Response closes (@beaucollins)
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Bug fix: Forward drain events from HTTP response (@cs278)
|
||||
* Dependency: Updated guzzle deps to `3.0.*`
|
||||
|
||||
## 0.2.2 (2012-10-28)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.1 (2012-10-14)
|
||||
|
||||
* Feature: Support HTTP 1.1 continue
|
||||
|
||||
## 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/http/LICENSE
vendored
Executable file
21
vendor/react/http/LICENSE
vendored
Executable 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.
|
||||
3024
vendor/react/http/README.md
vendored
Executable file
3024
vendor/react/http/README.md
vendored
Executable file
File diff suppressed because it is too large
Load Diff
57
vendor/react/http/composer.json
vendored
Executable file
57
vendor/react/http/composer.json
vendored
Executable file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "react/http",
|
||||
"description": "Event-driven, streaming HTTP client and server implementation for ReactPHP",
|
||||
"keywords": ["HTTP client", "HTTP server", "HTTP", "HTTPS", "event-driven", "streaming", "client", "server", "PSR-7", "async", "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",
|
||||
"fig/http-message-util": "^1.1",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.3 || ^1.2.1",
|
||||
"react/socket": "^1.16",
|
||||
"react/stream": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/http-proxy-react": "^1.8",
|
||||
"clue/reactphp-ssh-proxy": "^1.4",
|
||||
"clue/socks-react": "^1.4",
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4.2 || ^3 || ^2",
|
||||
"react/promise-stream": "^1.4",
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Http\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Http\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
858
vendor/react/http/src/Browser.php
vendored
Executable file
858
vendor/react/http/src/Browser.php
vendored
Executable file
@@ -0,0 +1,858 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Io\Sender;
|
||||
use React\Http\Io\Transaction;
|
||||
use React\Http\Message\Request;
|
||||
use React\Http\Message\Uri;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @final This class is final and shouldn't be extended as it is likely to be marked final in a future release.
|
||||
*/
|
||||
class Browser
|
||||
{
|
||||
private $transaction;
|
||||
private $baseUrl;
|
||||
private $protocolVersion = '1.1';
|
||||
private $defaultHeaders = array(
|
||||
'User-Agent' => 'ReactPHP/1'
|
||||
);
|
||||
|
||||
/**
|
||||
* The `Browser` is responsible for sending HTTP requests to your HTTP server
|
||||
* and keeps track of pending incoming HTTP responses.
|
||||
*
|
||||
* ```php
|
||||
* $browser = new React\Http\Browser();
|
||||
* ```
|
||||
*
|
||||
* This class takes two optional arguments for more advanced usage:
|
||||
*
|
||||
* ```php
|
||||
* // constructor signature as of v1.5.0
|
||||
* $browser = new React\Http\Browser(?ConnectorInterface $connector = null, ?LoopInterface $loop = null);
|
||||
*
|
||||
* // legacy constructor signature before v1.5.0
|
||||
* $browser = new React\Http\Browser(?LoopInterface $loop = null, ?ConnectorInterface $connector = null);
|
||||
* ```
|
||||
*
|
||||
* If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
|
||||
* proxy servers etc.), you can explicitly pass a custom instance of the
|
||||
* [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
|
||||
*
|
||||
* ```php
|
||||
* $connector = new React\Socket\Connector(array(
|
||||
* 'dns' => '127.0.0.1',
|
||||
* 'tcp' => array(
|
||||
* 'bindto' => '192.168.10.1:0'
|
||||
* ),
|
||||
* 'tls' => array(
|
||||
* 'verify_peer' => false,
|
||||
* 'verify_peer_name' => false
|
||||
* )
|
||||
* ));
|
||||
*
|
||||
* $browser = new React\Http\Browser($connector);
|
||||
* ```
|
||||
*
|
||||
* 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 null|ConnectorInterface|LoopInterface $connector
|
||||
* @param null|LoopInterface|ConnectorInterface $loop
|
||||
* @throws \InvalidArgumentException for invalid arguments
|
||||
*/
|
||||
public function __construct($connector = null, $loop = null)
|
||||
{
|
||||
// swap arguments for legacy constructor signature
|
||||
if (($connector instanceof LoopInterface || $connector === null) && ($loop instanceof ConnectorInterface || $loop === null)) {
|
||||
$swap = $loop;
|
||||
$loop = $connector;
|
||||
$connector = $swap;
|
||||
}
|
||||
|
||||
if (($connector !== null && !$connector instanceof ConnectorInterface) || ($loop !== null && !$loop instanceof LoopInterface)) {
|
||||
throw new \InvalidArgumentException('Expected "?ConnectorInterface $connector" and "?LoopInterface $loop" arguments');
|
||||
}
|
||||
|
||||
$loop = $loop ?: Loop::get();
|
||||
$this->transaction = new Transaction(
|
||||
Sender::createFromLoop($loop, $connector ?: new Connector(array(), $loop)),
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP GET request
|
||||
*
|
||||
* ```php
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [GET request client example](../examples/01-client-get-request.php).
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function get($url, array $headers = array())
|
||||
{
|
||||
return $this->requestMayBeStreaming('GET', $url, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP POST request
|
||||
*
|
||||
* ```php
|
||||
* $browser->post(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/json'
|
||||
* ],
|
||||
* json_encode($data)
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump(json_decode((string)$response->getBody()));
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [POST JSON client example](../examples/04-client-post-json.php).
|
||||
*
|
||||
* This method is also commonly used to submit HTML form data:
|
||||
*
|
||||
* ```php
|
||||
* $data = [
|
||||
* 'user' => 'Alice',
|
||||
* 'password' => 'secret'
|
||||
* ];
|
||||
*
|
||||
* $browser->post(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/x-www-form-urlencoded'
|
||||
* ],
|
||||
* http_build_query($data)
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->post($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function post($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('POST', $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP HEAD request
|
||||
*
|
||||
* ```php
|
||||
* $browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump($response->getHeaders());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function head($url, array $headers = array())
|
||||
{
|
||||
return $this->requestMayBeStreaming('HEAD', $url, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP PATCH request
|
||||
*
|
||||
* ```php
|
||||
* $browser->patch(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/json'
|
||||
* ],
|
||||
* json_encode($data)
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump(json_decode((string)$response->getBody()));
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->patch($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function patch($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('PATCH', $url , $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP PUT request
|
||||
*
|
||||
* ```php
|
||||
* $browser->put(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'text/xml'
|
||||
* ],
|
||||
* $xml->asXML()
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [PUT XML client example](../examples/05-client-put-xml.php).
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->put($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function put($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('PUT', $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP DELETE request
|
||||
*
|
||||
* ```php
|
||||
* $browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function delete($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('DELETE', $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an arbitrary HTTP request.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request.
|
||||
*
|
||||
* As an alternative, if you want to use a custom HTTP request method, you
|
||||
* can use this method:
|
||||
*
|
||||
* ```php
|
||||
* $browser->request('OPTIONS', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* If you're using a streaming request body (`ReadableStreamInterface`), it
|
||||
* will default to using `Transfer-Encoding: chunked` or you have to
|
||||
* explicitly pass in a matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->request('POST', $url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
|
||||
* @param string $url URL for the request
|
||||
* @param array $headers Additional request headers
|
||||
* @param string|ReadableStreamInterface $body HTTP request body contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function request($method, $url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->withOptions(array('streaming' => false))->requestMayBeStreaming($method, $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an arbitrary HTTP request and receives a streaming response without buffering the response body.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request. Each of these methods will buffer
|
||||
* the whole response body in memory by default. This is easy to get started
|
||||
* and works reasonably well for smaller responses.
|
||||
*
|
||||
* In some situations, it's a better idea to use a streaming approach, where
|
||||
* only small chunks have to be kept in memory. You can use this method to
|
||||
* send an arbitrary HTTP request and receive a streaming response. It uses
|
||||
* the same HTTP message API, but does not buffer the response body in
|
||||
* memory. It only processes the response body in small chunks as data is
|
||||
* received and forwards this data through [ReactPHP's Stream API](https://github.com/reactphp/stream).
|
||||
* This works for (any number of) responses of arbitrary sizes.
|
||||
*
|
||||
* ```php
|
||||
* $browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* $body = $response->getBody();
|
||||
* assert($body instanceof Psr\Http\Message\StreamInterface);
|
||||
* assert($body instanceof React\Stream\ReadableStreamInterface);
|
||||
*
|
||||
* $body->on('data', function ($chunk) {
|
||||
* echo $chunk;
|
||||
* });
|
||||
*
|
||||
* $body->on('error', function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $body->on('close', function () {
|
||||
* echo '[DONE]' . PHP_EOL;
|
||||
* });
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
|
||||
* and the [streaming response](#streaming-response) for more details,
|
||||
* examples and possible use-cases.
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* If you're using a streaming request body (`ReadableStreamInterface`), it
|
||||
* will default to using `Transfer-Encoding: chunked` or you have to
|
||||
* explicitly pass in a matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->requestStreaming('POST', $url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
|
||||
* @param string $url URL for the request
|
||||
* @param array $headers Additional request headers
|
||||
* @param string|ReadableStreamInterface $body HTTP request body contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function requestStreaming($method, $url, $headers = array(), $body = '')
|
||||
{
|
||||
return $this->withOptions(array('streaming' => true))->requestMayBeStreaming($method, $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum timeout used for waiting for pending requests.
|
||||
*
|
||||
* You can pass in the number of seconds to use as a new timeout value:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(10.0);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `false` to disable any timeouts. In this case,
|
||||
* requests can stay pending forever:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(false);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default timeout handling. This
|
||||
* will respects PHP's `default_socket_timeout` setting (default 60s):
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(true);
|
||||
* ```
|
||||
*
|
||||
* See also [timeouts](#timeouts) for more details about timeout handling.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given timeout value applied.
|
||||
*
|
||||
* @param bool|number $timeout
|
||||
* @return self
|
||||
*/
|
||||
public function withTimeout($timeout)
|
||||
{
|
||||
if ($timeout === true) {
|
||||
$timeout = null;
|
||||
} elseif ($timeout === false) {
|
||||
$timeout = -1;
|
||||
} elseif ($timeout < 0) {
|
||||
$timeout = 0;
|
||||
}
|
||||
|
||||
return $this->withOptions(array(
|
||||
'timeout' => $timeout,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes how HTTP redirects will be followed.
|
||||
*
|
||||
* You can pass in the maximum number of redirects to follow:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(5);
|
||||
* ```
|
||||
*
|
||||
* The request will automatically be rejected when the number of redirects
|
||||
* is exceeded. You can pass in a `0` to reject the request for any
|
||||
* redirects encountered:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(0);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // only non-redirected responses will now end up here
|
||||
* var_dump($response->getHeaders());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `false` to disable following any redirects. In
|
||||
* this case, requests will resolve with the redirection response instead
|
||||
* of following the `Location` response header:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(false);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any redirects will now end up here
|
||||
* var_dump($response->getHeaderLine('Location'));
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default redirect handling.
|
||||
* This defaults to following a maximum of 10 redirects:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(true);
|
||||
* ```
|
||||
*
|
||||
* See also [redirects](#redirects) for more details about redirect handling.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given redirect setting applied.
|
||||
*
|
||||
* @param bool|int $followRedirects
|
||||
* @return self
|
||||
*/
|
||||
public function withFollowRedirects($followRedirects)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'followRedirects' => $followRedirects !== false,
|
||||
'maxRedirects' => \is_bool($followRedirects) ? null : $followRedirects
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.
|
||||
*
|
||||
* You can pass in a bool `false` to disable rejecting incoming responses
|
||||
* that use a 4xx or 5xx response status code. In this case, requests will
|
||||
* resolve with the response message indicating an error condition:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withRejectErrorResponse(false);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any HTTP response will now end up here
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default status code handling.
|
||||
* This defaults to rejecting any response status codes in the 4xx or 5xx
|
||||
* range:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withRejectErrorResponse(true);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any successful HTTP response will now end up here
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* }, function (Exception $e) {
|
||||
* if ($e instanceof React\Http\Message\ResponseException) {
|
||||
* // any HTTP response error message will now end up here
|
||||
* $response = $e->getResponse();
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* } else {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given setting applied.
|
||||
*
|
||||
* @param bool $obeySuccessCode
|
||||
* @return self
|
||||
*/
|
||||
public function withRejectErrorResponse($obeySuccessCode)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'obeySuccessCode' => $obeySuccessCode,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the base URL used to resolve relative URLs to.
|
||||
*
|
||||
* If you configure a base URL, any requests to relative URLs will be
|
||||
* processed by first resolving this relative to the given absolute base
|
||||
* URL. This supports resolving relative path references (like `../` etc.).
|
||||
* This is particularly useful for (RESTful) API calls where all endpoints
|
||||
* (URLs) are located under a common base URL.
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withBase('http://api.example.com/v3/');
|
||||
*
|
||||
* // will request http://api.example.com/v3/users
|
||||
* $browser->get('users')->then(…);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a `null` base URL to return a new instance that does not
|
||||
* use a base URL:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withBase(null);
|
||||
* ```
|
||||
*
|
||||
* Accordingly, any requests using relative URLs to a browser that does not
|
||||
* use a base URL can not be completed and will be rejected without sending
|
||||
* a request.
|
||||
*
|
||||
* This method will throw an `InvalidArgumentException` if the given
|
||||
* `$baseUrl` argument is not a valid URL.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
|
||||
* actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.
|
||||
*
|
||||
* @param string|null $baseUrl absolute base URL
|
||||
* @return self
|
||||
* @throws InvalidArgumentException if the given $baseUrl is not a valid absolute URL
|
||||
* @see self::withoutBase()
|
||||
*/
|
||||
public function withBase($baseUrl)
|
||||
{
|
||||
$browser = clone $this;
|
||||
if ($baseUrl === null) {
|
||||
$browser->baseUrl = null;
|
||||
return $browser;
|
||||
}
|
||||
|
||||
$browser->baseUrl = new Uri($baseUrl);
|
||||
if (!\in_array($browser->baseUrl->getScheme(), array('http', 'https')) || $browser->baseUrl->getHost() === '') {
|
||||
throw new \InvalidArgumentException('Base URL must be absolute');
|
||||
}
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the HTTP protocol version that will be used for all subsequent requests.
|
||||
*
|
||||
* All the above [request methods](#request-methods) default to sending
|
||||
* requests as HTTP/1.1. This is the preferred HTTP protocol version which
|
||||
* also provides decent backwards-compatibility with legacy HTTP/1.0
|
||||
* servers. As such, there should rarely be a need to explicitly change this
|
||||
* protocol version.
|
||||
*
|
||||
* If you want to explicitly use the legacy HTTP/1.0 protocol version, you
|
||||
* can use this method:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withProtocolVersion('1.0');
|
||||
*
|
||||
* $browser->get($url)->then(…);
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* new protocol version applied.
|
||||
*
|
||||
* @param string $protocolVersion HTTP protocol version to use, must be one of "1.1" or "1.0"
|
||||
* @return self
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function withProtocolVersion($protocolVersion)
|
||||
{
|
||||
if (!\in_array($protocolVersion, array('1.0', '1.1'), true)) {
|
||||
throw new InvalidArgumentException('Invalid HTTP protocol version, must be one of "1.1" or "1.0"');
|
||||
}
|
||||
|
||||
$browser = clone $this;
|
||||
$browser->protocolVersion = (string) $protocolVersion;
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum size for buffering a response body.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request. Each of these methods will buffer
|
||||
* the whole response body in memory by default. This is easy to get started
|
||||
* and works reasonably well for smaller responses.
|
||||
*
|
||||
* By default, the response body buffer will be limited to 16 MiB. If the
|
||||
* response body exceeds this maximum size, the request will be rejected.
|
||||
*
|
||||
* You can pass in the maximum number of bytes to buffer:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withResponseBuffer(1024 * 1024);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // response body will not exceed 1 MiB
|
||||
* var_dump($response->getHeaders(), (string) $response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that the response body buffer has to be kept in memory for each
|
||||
* pending request until its transfer is completed and it will only be freed
|
||||
* after a pending request is fulfilled. As such, increasing this maximum
|
||||
* buffer size to allow larger response bodies is usually not recommended.
|
||||
* Instead, you can use the [`requestStreaming()` method](#requeststreaming)
|
||||
* to receive responses with arbitrary sizes without buffering. Accordingly,
|
||||
* this maximum buffer size setting has no effect on streaming responses.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given setting applied.
|
||||
*
|
||||
* @param int $maximumSize
|
||||
* @return self
|
||||
* @see self::requestStreaming()
|
||||
*/
|
||||
public function withResponseBuffer($maximumSize)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'maximumSize' => $maximumSize
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a request header for all following requests.
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withHeader('User-Agent', 'ACME');
|
||||
*
|
||||
* $browser->get($url)->then(…);
|
||||
* ```
|
||||
*
|
||||
* Note that the new header will overwrite any headers previously set with
|
||||
* the same name (case-insensitive). Following requests will use these headers
|
||||
* by default unless they are explicitly set for any requests.
|
||||
*
|
||||
* @param string $header
|
||||
* @param string $value
|
||||
* @return Browser
|
||||
*/
|
||||
public function withHeader($header, $value)
|
||||
{
|
||||
$browser = $this->withoutHeader($header);
|
||||
$browser->defaultHeaders[$header] = $value;
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any default request headers previously set via
|
||||
* the [`withHeader()` method](#withheader).
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withoutHeader('User-Agent');
|
||||
*
|
||||
* $browser->get($url)->then(…);
|
||||
* ```
|
||||
*
|
||||
* Note that this method only affects the headers which were set with the
|
||||
* method `withHeader(string $header, string $value): Browser`
|
||||
*
|
||||
* @param string $header
|
||||
* @return Browser
|
||||
*/
|
||||
public function withoutHeader($header)
|
||||
{
|
||||
$browser = clone $this;
|
||||
|
||||
/** @var string|int $key */
|
||||
foreach (\array_keys($browser->defaultHeaders) as $key) {
|
||||
if (\strcasecmp($key, $header) === 0) {
|
||||
unset($browser->defaultHeaders[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the [options](#options) to use:
|
||||
*
|
||||
* The [`Browser`](#browser) class exposes several options for the handling of
|
||||
* HTTP transactions. These options resemble some of PHP's
|
||||
* [HTTP context options](http://php.net/manual/en/context.http.php) and
|
||||
* can be controlled via the following API (and their defaults):
|
||||
*
|
||||
* ```php
|
||||
* // deprecated
|
||||
* $newBrowser = $browser->withOptions(array(
|
||||
* 'timeout' => null, // see withTimeout() instead
|
||||
* 'followRedirects' => true, // see withFollowRedirects() instead
|
||||
* 'maxRedirects' => 10, // see withFollowRedirects() instead
|
||||
* 'obeySuccessCode' => true, // see withRejectErrorResponse() instead
|
||||
* 'streaming' => false, // deprecated, see requestStreaming() instead
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* See also [timeouts](#timeouts), [redirects](#redirects) and
|
||||
* [streaming](#streaming) for more details.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* options applied.
|
||||
*
|
||||
* @param array $options
|
||||
* @return self
|
||||
* @see self::withTimeout()
|
||||
* @see self::withFollowRedirects()
|
||||
* @see self::withRejectErrorResponse()
|
||||
*/
|
||||
private function withOptions(array $options)
|
||||
{
|
||||
$browser = clone $this;
|
||||
$browser->transaction = $this->transaction->withOptions($options);
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
private function requestMayBeStreaming($method, $url, array $headers = array(), $body = '')
|
||||
{
|
||||
if ($this->baseUrl !== null) {
|
||||
// ensure we're actually below the base URL
|
||||
$url = Uri::resolve($this->baseUrl, new Uri($url));
|
||||
}
|
||||
|
||||
foreach ($this->defaultHeaders as $key => $value) {
|
||||
$explicitHeaderExists = false;
|
||||
foreach (\array_keys($headers) as $headerKey) {
|
||||
if (\strcasecmp($headerKey, $key) === 0) {
|
||||
$explicitHeaderExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$explicitHeaderExists) {
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->transaction->send(
|
||||
new Request($method, $url, $headers, $body, $this->protocolVersion)
|
||||
);
|
||||
}
|
||||
}
|
||||
27
vendor/react/http/src/Client/Client.php
vendored
Executable file
27
vendor/react/http/src/Client/Client.php
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Client;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use React\Http\Io\ClientConnectionManager;
|
||||
use React\Http\Io\ClientRequestStream;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/** @var ClientConnectionManager */
|
||||
private $connectionManager;
|
||||
|
||||
public function __construct(ClientConnectionManager $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
|
||||
/** @return ClientRequestStream */
|
||||
public function request(RequestInterface $request)
|
||||
{
|
||||
return new ClientRequestStream($this->connectionManager, $request);
|
||||
}
|
||||
}
|
||||
351
vendor/react/http/src/HttpServer.php
vendored
Executable file
351
vendor/react/http/src/HttpServer.php
vendored
Executable file
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Io\IniUtil;
|
||||
use React\Http\Io\MiddlewareRunner;
|
||||
use React\Http\Io\StreamingServer;
|
||||
use React\Http\Middleware\LimitConcurrentRequestsMiddleware;
|
||||
use React\Http\Middleware\StreamingRequestMiddleware;
|
||||
use React\Http\Middleware\RequestBodyBufferMiddleware;
|
||||
use React\Http\Middleware\RequestBodyParserMiddleware;
|
||||
use React\Socket\ServerInterface;
|
||||
|
||||
/**
|
||||
* The `React\Http\HttpServer` class is responsible for handling incoming connections and then
|
||||
* processing each incoming HTTP request.
|
||||
*
|
||||
* When a complete HTTP request has been received, it will invoke the given
|
||||
* request handler function. This request handler function needs to be passed to
|
||||
* the constructor and will be invoked with the respective [request](#server-request)
|
||||
* object and expects a [response](#server-response) object in return:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
|
||||
* return new React\Http\Message\Response(
|
||||
* React\Http\Message\Response::STATUS_OK,
|
||||
* array(
|
||||
* 'Content-Type' => 'text/plain'
|
||||
* ),
|
||||
* "Hello World!\n"
|
||||
* );
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Each incoming HTTP request message is always represented by the
|
||||
* [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
|
||||
* see also following [request](#server-request) chapter for more details.
|
||||
*
|
||||
* Each outgoing HTTP response message is always represented by the
|
||||
* [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
|
||||
* see also following [response](#server-response) chapter for more details.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* In order to start listening for any incoming connections, the `HttpServer` needs
|
||||
* to be attached to an instance of
|
||||
* [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface)
|
||||
* through the [`listen()`](#listen) method as described in the following
|
||||
* chapter. In its most simple form, you can attach this to a
|
||||
* [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* in order to start a plaintext HTTP server like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer($handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('0.0.0.0:8080');
|
||||
* $http->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also the [`listen()`](#listen) method and
|
||||
* [hello world server example](../examples/51-server-hello-world.php)
|
||||
* for more details.
|
||||
*
|
||||
* By default, the `HttpServer` buffers and parses the complete incoming HTTP
|
||||
* request in memory. It will invoke the given request handler function when the
|
||||
* complete request headers and request body has been received. This means the
|
||||
* [request](#server-request) object passed to your request handler function will be
|
||||
* fully compatible with PSR-7 (http-message). This provides sane defaults for
|
||||
* 80% of the use cases and is the recommended way to use this library unless
|
||||
* you're sure you know what you're doing.
|
||||
*
|
||||
* On the other hand, buffering complete HTTP requests in memory until they can
|
||||
* be processed by your request handler function means that this class has to
|
||||
* employ a number of limits to avoid consuming too much memory. In order to
|
||||
* take the more advanced configuration out your hand, it respects setting from
|
||||
* your [`php.ini`](https://www.php.net/manual/en/ini.core.php) to apply its
|
||||
* default settings. This is a list of PHP settings this class respects with
|
||||
* their respective default values:
|
||||
*
|
||||
* ```
|
||||
* memory_limit 128M
|
||||
* post_max_size 8M // capped at 64K
|
||||
*
|
||||
* enable_post_data_reading 1
|
||||
* max_input_nesting_level 64
|
||||
* max_input_vars 1000
|
||||
*
|
||||
* file_uploads 1
|
||||
* upload_max_filesize 2M
|
||||
* max_file_uploads 20
|
||||
* ```
|
||||
*
|
||||
* In particular, the `post_max_size` setting limits how much memory a single
|
||||
* HTTP request is allowed to consume while buffering its request body. This
|
||||
* needs to be limited because the server can process a large number of requests
|
||||
* concurrently, so the server may potentially consume a large amount of memory
|
||||
* otherwise. To support higher concurrency by default, this value is capped
|
||||
* at `64K`. If you assign a higher value, it will only allow `64K` by default.
|
||||
* If a request exceeds this limit, its request body will be ignored and it will
|
||||
* be processed like a request with no request body at all. See below for
|
||||
* explicit configuration to override this setting.
|
||||
*
|
||||
* By default, this class will try to avoid consuming more than half of your
|
||||
* `memory_limit` for buffering multiple concurrent HTTP requests. As such, with
|
||||
* the above default settings of `128M` max, it will try to consume no more than
|
||||
* `64M` for buffering multiple concurrent HTTP requests. As a consequence, it
|
||||
* will limit the concurrency to `1024` HTTP requests with the above defaults.
|
||||
*
|
||||
* It is imperative that you assign reasonable values to your PHP ini settings.
|
||||
* It is usually recommended to not support buffering incoming HTTP requests
|
||||
* with a large HTTP request body (e.g. large file uploads). If you want to
|
||||
* increase this buffer size, you will have to also increase the total memory
|
||||
* limit to allow for more concurrent requests (set `memory_limit 512M` or more)
|
||||
* or explicitly limit concurrency.
|
||||
*
|
||||
* In order to override the above buffering defaults, you can configure the
|
||||
* `HttpServer` explicitly. You can use the
|
||||
* [`LimitConcurrentRequestsMiddleware`](#limitconcurrentrequestsmiddleware) and
|
||||
* [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
|
||||
* to explicitly configure the total number of requests that can be handled at
|
||||
* once like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
|
||||
* new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
|
||||
* new React\Http\Middleware\RequestBodyParserMiddleware(),
|
||||
* $handler
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* In this example, we allow processing up to 100 concurrent requests at once
|
||||
* and each request can buffer up to `2M`. This means you may have to keep a
|
||||
* maximum of `200M` of memory for incoming request body buffers. Accordingly,
|
||||
* you need to adjust the `memory_limit` ini setting to allow for these buffers
|
||||
* plus your actual application logic memory requirements (think `512M` or more).
|
||||
*
|
||||
* > Internally, this class automatically assigns these middleware handlers
|
||||
* automatically when no [`StreamingRequestMiddleware`](#streamingrequestmiddleware)
|
||||
* is given. Accordingly, you can use this example to override all default
|
||||
* settings to implement custom limits.
|
||||
*
|
||||
* As an alternative to buffering the complete request body in memory, you can
|
||||
* also use a streaming approach where only small chunks of data have to be kept
|
||||
* in memory:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* $handler
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* In this case, it will invoke the request handler function once the HTTP
|
||||
* request headers have been received, i.e. before receiving the potentially
|
||||
* much larger HTTP request body. This means the [request](#server-request) passed to
|
||||
* your request handler function may not be fully compatible with PSR-7. This is
|
||||
* specifically designed to help with more advanced use cases where you want to
|
||||
* have full control over consuming the incoming HTTP request body and
|
||||
* concurrency settings. See also [streaming incoming request](#streaming-incoming-request)
|
||||
* below for more details.
|
||||
*
|
||||
* > Changelog v1.5.0: This class has been renamed to `HttpServer` from the
|
||||
* previous `Server` class in order to avoid any ambiguities.
|
||||
* The previous name has been deprecated and should not be used anymore.
|
||||
*/
|
||||
final class HttpServer extends EventEmitter
|
||||
{
|
||||
/**
|
||||
* The maximum buffer size used for each request.
|
||||
*
|
||||
* This needs to be limited because the server can process a large number of
|
||||
* requests concurrently, so the server may potentially consume a large
|
||||
* amount of memory otherwise.
|
||||
*
|
||||
* See `RequestBodyBufferMiddleware` to override this setting.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const MAXIMUM_BUFFER_SIZE = 65536; // 64 KiB
|
||||
|
||||
/**
|
||||
* @var StreamingServer
|
||||
*/
|
||||
private $streamingServer;
|
||||
|
||||
/**
|
||||
* Creates an HTTP server that invokes the given callback for each incoming HTTP request
|
||||
*
|
||||
* In order to process any connections, the server needs to be attached to an
|
||||
* instance of `React\Socket\ServerInterface` which emits underlying streaming
|
||||
* connections in order to then parse incoming data as HTTP.
|
||||
* See also [listen()](#listen) for more details.
|
||||
*
|
||||
* @param callable|LoopInterface $requestHandlerOrLoop
|
||||
* @param callable[] ...$requestHandler
|
||||
* @see self::listen()
|
||||
*/
|
||||
public function __construct($requestHandlerOrLoop)
|
||||
{
|
||||
$requestHandlers = \func_get_args();
|
||||
if (reset($requestHandlers) instanceof LoopInterface) {
|
||||
$loop = \array_shift($requestHandlers);
|
||||
} else {
|
||||
$loop = Loop::get();
|
||||
}
|
||||
|
||||
$requestHandlersCount = \count($requestHandlers);
|
||||
if ($requestHandlersCount === 0 || \count(\array_filter($requestHandlers, 'is_callable')) < $requestHandlersCount) {
|
||||
throw new \InvalidArgumentException('Invalid request handler given');
|
||||
}
|
||||
|
||||
$streaming = false;
|
||||
foreach ((array) $requestHandlers as $handler) {
|
||||
if ($handler instanceof StreamingRequestMiddleware) {
|
||||
$streaming = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$middleware = array();
|
||||
if (!$streaming) {
|
||||
$maxSize = $this->getMaxRequestSize();
|
||||
$concurrency = $this->getConcurrentRequestsLimit(\ini_get('memory_limit'), $maxSize);
|
||||
if ($concurrency !== null) {
|
||||
$middleware[] = new LimitConcurrentRequestsMiddleware($concurrency);
|
||||
}
|
||||
$middleware[] = new RequestBodyBufferMiddleware($maxSize);
|
||||
// Checking for an empty string because that is what a boolean
|
||||
// false is returned as by ini_get depending on the PHP version.
|
||||
// @link http://php.net/manual/en/ini.core.php#ini.enable-post-data-reading
|
||||
// @link http://php.net/manual/en/function.ini-get.php#refsect1-function.ini-get-notes
|
||||
// @link https://3v4l.org/qJtsa
|
||||
$enablePostDataReading = \ini_get('enable_post_data_reading');
|
||||
if ($enablePostDataReading !== '') {
|
||||
$middleware[] = new RequestBodyParserMiddleware();
|
||||
}
|
||||
}
|
||||
|
||||
$middleware = \array_merge($middleware, $requestHandlers);
|
||||
|
||||
/**
|
||||
* Filter out any configuration middleware, no need to run requests through something that isn't
|
||||
* doing anything with the request.
|
||||
*/
|
||||
$middleware = \array_filter($middleware, function ($handler) {
|
||||
return !($handler instanceof StreamingRequestMiddleware);
|
||||
});
|
||||
|
||||
$this->streamingServer = new StreamingServer($loop, new MiddlewareRunner($middleware));
|
||||
|
||||
$that = $this;
|
||||
$this->streamingServer->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for HTTP requests on the given socket server instance
|
||||
*
|
||||
* The given [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface)
|
||||
* is responsible for emitting the underlying streaming connections. This
|
||||
* HTTP server needs to be attached to it in order to process any
|
||||
* connections and pase incoming streaming data as incoming HTTP request
|
||||
* messages. In its most common form, you can attach this to a
|
||||
* [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* in order to start a plaintext HTTP server like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer($handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('0.0.0.0:8080');
|
||||
* $http->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also [hello world server example](../examples/51-server-hello-world.php)
|
||||
* for more details.
|
||||
*
|
||||
* This example will start listening for HTTP requests on the alternative
|
||||
* HTTP port `8080` on all interfaces (publicly). As an alternative, it is
|
||||
* very common to use a reverse proxy and let this HTTP server listen on the
|
||||
* localhost (loopback) interface only by using the listen address
|
||||
* `127.0.0.1:8080` instead. This way, you host your application(s) on the
|
||||
* default HTTP port `80` and only route specific requests to this HTTP
|
||||
* server.
|
||||
*
|
||||
* Likewise, it's usually recommended to use a reverse proxy setup to accept
|
||||
* secure HTTPS requests on default HTTPS port `443` (TLS termination) and
|
||||
* only route plaintext requests to this HTTP server. As an alternative, you
|
||||
* can also accept secure HTTPS requests with this HTTP server by attaching
|
||||
* this to a [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* using a secure TLS listen address, a certificate file and optional
|
||||
* `passphrase` like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer($handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('tls://0.0.0.0:8443', array(
|
||||
* 'tls' => array(
|
||||
* 'local_cert' => __DIR__ . '/localhost.pem'
|
||||
* )
|
||||
* ));
|
||||
* $http->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also [hello world HTTPS example](../examples/61-server-hello-world-https.php)
|
||||
* for more details.
|
||||
*
|
||||
* @param ServerInterface $socket
|
||||
*/
|
||||
public function listen(ServerInterface $socket)
|
||||
{
|
||||
$this->streamingServer->listen($socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $memory_limit
|
||||
* @param string $post_max_size
|
||||
* @return ?int
|
||||
*/
|
||||
private function getConcurrentRequestsLimit($memory_limit, $post_max_size)
|
||||
{
|
||||
if ($memory_limit == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$availableMemory = IniUtil::iniSizeToBytes($memory_limit) / 2;
|
||||
$concurrentRequests = (int) \ceil($availableMemory / IniUtil::iniSizeToBytes($post_max_size));
|
||||
|
||||
return $concurrentRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string $post_max_size
|
||||
* @return int
|
||||
*/
|
||||
private function getMaxRequestSize($post_max_size = null)
|
||||
{
|
||||
$maxSize = IniUtil::iniSizeToBytes($post_max_size === null ? \ini_get('post_max_size') : $post_max_size);
|
||||
|
||||
return ($maxSize === 0 || $maxSize >= self::MAXIMUM_BUFFER_SIZE) ? self::MAXIMUM_BUFFER_SIZE : $maxSize;
|
||||
}
|
||||
}
|
||||
172
vendor/react/http/src/Io/AbstractMessage.php
vendored
Executable file
172
vendor/react/http/src/Io/AbstractMessage.php
vendored
Executable file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Abstract HTTP message base class (PSR-7)
|
||||
*
|
||||
* @internal
|
||||
* @see MessageInterface
|
||||
*/
|
||||
abstract class AbstractMessage implements MessageInterface
|
||||
{
|
||||
/**
|
||||
* [Internal] Regex used to match all request header fields into an array, thanks to @kelunik for checking the HTTP specs and coming up with this regex
|
||||
*
|
||||
* @internal
|
||||
* @var string
|
||||
*/
|
||||
const REGEX_HEADERS = '/^([^()<>@,;:\\\"\/\[\]?={}\x00-\x20\x7F]++):[\x20\x09]*+((?:[\x20\x09]*+[\x21-\x7E\x80-\xFF]++)*+)[\x20\x09]*+[\r]?+\n/m';
|
||||
|
||||
/** @var array<string,string[]> */
|
||||
private $headers = array();
|
||||
|
||||
/** @var array<string,string> */
|
||||
private $headerNamesLowerCase = array();
|
||||
|
||||
/** @var string */
|
||||
private $protocolVersion;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $body;
|
||||
|
||||
/**
|
||||
* @param string $protocolVersion
|
||||
* @param array<string,string|string[]> $headers
|
||||
* @param StreamInterface $body
|
||||
*/
|
||||
protected function __construct($protocolVersion, array $headers, StreamInterface $body)
|
||||
{
|
||||
foreach ($headers as $name => $value) {
|
||||
if ($value !== array()) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as &$one) {
|
||||
$one = (string) $one;
|
||||
}
|
||||
} else {
|
||||
$value = array((string) $value);
|
||||
}
|
||||
|
||||
$lower = \strtolower($name);
|
||||
if (isset($this->headerNamesLowerCase[$lower])) {
|
||||
$value = \array_merge($this->headers[$this->headerNamesLowerCase[$lower]], $value);
|
||||
unset($this->headers[$this->headerNamesLowerCase[$lower]]);
|
||||
}
|
||||
|
||||
$this->headers[$name] = $value;
|
||||
$this->headerNamesLowerCase[$lower] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$this->protocolVersion = (string) $protocolVersion;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function getProtocolVersion()
|
||||
{
|
||||
return $this->protocolVersion;
|
||||
}
|
||||
|
||||
public function withProtocolVersion($version)
|
||||
{
|
||||
if ((string) $version === $this->protocolVersion) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
$message->protocolVersion = (string) $version;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function getHeaders()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function hasHeader($name)
|
||||
{
|
||||
return isset($this->headerNamesLowerCase[\strtolower($name)]);
|
||||
}
|
||||
|
||||
public function getHeader($name)
|
||||
{
|
||||
$lower = \strtolower($name);
|
||||
return isset($this->headerNamesLowerCase[$lower]) ? $this->headers[$this->headerNamesLowerCase[$lower]] : array();
|
||||
}
|
||||
|
||||
public function getHeaderLine($name)
|
||||
{
|
||||
return \implode(', ', $this->getHeader($name));
|
||||
}
|
||||
|
||||
public function withHeader($name, $value)
|
||||
{
|
||||
if ($value === array()) {
|
||||
return $this->withoutHeader($name);
|
||||
} elseif (\is_array($value)) {
|
||||
foreach ($value as &$one) {
|
||||
$one = (string) $one;
|
||||
}
|
||||
} else {
|
||||
$value = array((string) $value);
|
||||
}
|
||||
|
||||
$lower = \strtolower($name);
|
||||
if (isset($this->headerNamesLowerCase[$lower]) && $this->headerNamesLowerCase[$lower] === (string) $name && $this->headers[$this->headerNamesLowerCase[$lower]] === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
if (isset($message->headerNamesLowerCase[$lower])) {
|
||||
unset($message->headers[$message->headerNamesLowerCase[$lower]]);
|
||||
}
|
||||
|
||||
$message->headers[$name] = $value;
|
||||
$message->headerNamesLowerCase[$lower] = $name;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function withAddedHeader($name, $value)
|
||||
{
|
||||
if ($value === array()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->withHeader($name, \array_merge($this->getHeader($name), \is_array($value) ? $value : array($value)));
|
||||
}
|
||||
|
||||
public function withoutHeader($name)
|
||||
{
|
||||
$lower = \strtolower($name);
|
||||
if (!isset($this->headerNamesLowerCase[$lower])) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
unset($message->headers[$message->headerNamesLowerCase[$lower]], $message->headerNamesLowerCase[$lower]);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function withBody(StreamInterface $body)
|
||||
{
|
||||
if ($body === $this->body) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
$message->body = $body;
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
156
vendor/react/http/src/Io/AbstractRequest.php
vendored
Executable file
156
vendor/react/http/src/Io/AbstractRequest.php
vendored
Executable file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\Http\Message\Uri;
|
||||
|
||||
/**
|
||||
* [Internal] Abstract HTTP request base class (PSR-7)
|
||||
*
|
||||
* @internal
|
||||
* @see RequestInterface
|
||||
*/
|
||||
abstract class AbstractRequest extends AbstractMessage implements RequestInterface
|
||||
{
|
||||
/** @var ?string */
|
||||
private $requestTarget;
|
||||
|
||||
/** @var string */
|
||||
private $method;
|
||||
|
||||
/** @var UriInterface */
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string|UriInterface $uri
|
||||
* @param array<string,string|string[]> $headers
|
||||
* @param StreamInterface $body
|
||||
* @param string unknown $protocolVersion
|
||||
*/
|
||||
protected function __construct(
|
||||
$method,
|
||||
$uri,
|
||||
array $headers,
|
||||
StreamInterface $body,
|
||||
$protocolVersion
|
||||
) {
|
||||
if (\is_string($uri)) {
|
||||
$uri = new Uri($uri);
|
||||
} elseif (!$uri instanceof UriInterface) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Argument #2 ($uri) expected string|Psr\Http\Message\UriInterface'
|
||||
);
|
||||
}
|
||||
|
||||
// assign default `Host` request header from URI unless already given explicitly
|
||||
$host = $uri->getHost();
|
||||
if ($host !== '') {
|
||||
foreach ($headers as $name => $value) {
|
||||
if (\strtolower($name) === 'host' && $value !== array()) {
|
||||
$host = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($host !== '') {
|
||||
$port = $uri->getPort();
|
||||
if ($port !== null && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
|
||||
$host .= ':' . $port;
|
||||
}
|
||||
|
||||
$headers = array('Host' => $host) + $headers;
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($protocolVersion, $headers, $body);
|
||||
|
||||
$this->method = $method;
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
public function getRequestTarget()
|
||||
{
|
||||
if ($this->requestTarget !== null) {
|
||||
return $this->requestTarget;
|
||||
}
|
||||
|
||||
$target = $this->uri->getPath();
|
||||
if ($target === '') {
|
||||
$target = '/';
|
||||
}
|
||||
if (($query = $this->uri->getQuery()) !== '') {
|
||||
$target .= '?' . $query;
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withRequestTarget($requestTarget)
|
||||
{
|
||||
if ((string) $requestTarget === $this->requestTarget) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->requestTarget = (string) $requestTarget;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function withMethod($method)
|
||||
{
|
||||
if ((string) $method === $this->method) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->method = (string) $method;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function getUri()
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function withUri(UriInterface $uri, $preserveHost = false)
|
||||
{
|
||||
if ($uri === $this->uri) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->uri = $uri;
|
||||
|
||||
$host = $uri->getHost();
|
||||
$port = $uri->getPort();
|
||||
if ($port !== null && $host !== '' && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
|
||||
$host .= ':' . $port;
|
||||
}
|
||||
|
||||
// update `Host` request header if URI contains a new host and `$preserveHost` is false
|
||||
if ($host !== '' && (!$preserveHost || $request->getHeaderLine('Host') === '')) {
|
||||
// first remove all headers before assigning `Host` header to ensure it always comes first
|
||||
foreach (\array_keys($request->getHeaders()) as $name) {
|
||||
$request = $request->withoutHeader($name);
|
||||
}
|
||||
|
||||
// add `Host` header first, then all other original headers
|
||||
$request = $request->withHeader('Host', $host);
|
||||
foreach ($this->withoutHeader('Host')->getHeaders() as $name => $value) {
|
||||
$request = $request->withHeader($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
179
vendor/react/http/src/Io/BufferedBody.php
vendored
Executable file
179
vendor/react/http/src/Io/BufferedBody.php
vendored
Executable file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] PSR-7 message body implementation using an in-memory buffer
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BufferedBody implements StreamInterface
|
||||
{
|
||||
private $buffer = '';
|
||||
private $position = 0;
|
||||
private $closed = false;
|
||||
|
||||
/**
|
||||
* @param string $buffer
|
||||
*/
|
||||
public function __construct($buffer)
|
||||
{
|
||||
$this->buffer = $buffer;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->seek(0);
|
||||
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->buffer = '';
|
||||
$this->position = 0;
|
||||
$this->closed = true;
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
$this->close();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->closed ? null : \strlen($this->buffer);
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to tell position of closed stream');
|
||||
}
|
||||
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return $this->position >= \strlen($this->buffer);
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = \SEEK_SET)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to seek on closed stream');
|
||||
}
|
||||
|
||||
$old = $this->position;
|
||||
|
||||
if ($whence === \SEEK_SET) {
|
||||
$this->position = $offset;
|
||||
} elseif ($whence === \SEEK_CUR) {
|
||||
$this->position += $offset;
|
||||
} elseif ($whence === \SEEK_END) {
|
||||
$this->position = \strlen($this->buffer) + $offset;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid seek mode given');
|
||||
}
|
||||
|
||||
if (!\is_int($this->position) || $this->position < 0) {
|
||||
$this->position = $old;
|
||||
throw new \RuntimeException('Unable to seek to position');
|
||||
}
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to write to closed stream');
|
||||
}
|
||||
|
||||
if ($string === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($this->position > 0 && !isset($this->buffer[$this->position - 1])) {
|
||||
$this->buffer = \str_pad($this->buffer, $this->position, "\0");
|
||||
}
|
||||
|
||||
$len = \strlen($string);
|
||||
$this->buffer = \substr($this->buffer, 0, $this->position) . $string . \substr($this->buffer, $this->position + $len);
|
||||
$this->position += $len;
|
||||
|
||||
return $len;
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to read from closed stream');
|
||||
}
|
||||
|
||||
if ($length < 1) {
|
||||
throw new \InvalidArgumentException('Invalid read length given');
|
||||
}
|
||||
|
||||
if ($this->position + $length > \strlen($this->buffer)) {
|
||||
$length = \strlen($this->buffer) - $this->position;
|
||||
}
|
||||
|
||||
if (!isset($this->buffer[$this->position])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$pos = $this->position;
|
||||
$this->position += $length;
|
||||
|
||||
return \substr($this->buffer, $pos, $length);
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to read from closed stream');
|
||||
}
|
||||
|
||||
if (!isset($this->buffer[$this->position])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$pos = $this->position;
|
||||
$this->position = \strlen($this->buffer);
|
||||
|
||||
return \substr($this->buffer, $pos);
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return $key === null ? array() : null;
|
||||
}
|
||||
}
|
||||
175
vendor/react/http/src/Io/ChunkedDecoder.php
vendored
Executable file
175
vendor/react/http/src/Io/ChunkedDecoder.php
vendored
Executable file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* [Internal] Decodes "Transfer-Encoding: chunked" from given stream and returns only payload data.
|
||||
*
|
||||
* This is used internally to decode incoming requests with this encoding.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ChunkedDecoder extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
const MAX_CHUNK_HEADER_SIZE = 1024;
|
||||
|
||||
private $closed = false;
|
||||
private $input;
|
||||
private $buffer = '';
|
||||
private $chunkSize = 0;
|
||||
private $transferredSize = 0;
|
||||
private $headerCompleted = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->buffer = '';
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->handleError(new Exception('Unexpected end event'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
while ($this->buffer !== '') {
|
||||
if (!$this->headerCompleted) {
|
||||
$positionCrlf = \strpos($this->buffer, static::CRLF);
|
||||
|
||||
if ($positionCrlf === false) {
|
||||
// Header shouldn't be bigger than 1024 bytes
|
||||
if (isset($this->buffer[static::MAX_CHUNK_HEADER_SIZE])) {
|
||||
$this->handleError(new Exception('Chunk header size inclusive extension bigger than' . static::MAX_CHUNK_HEADER_SIZE. ' bytes'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$header = \strtolower((string)\substr($this->buffer, 0, $positionCrlf));
|
||||
$hexValue = $header;
|
||||
|
||||
if (\strpos($header, ';') !== false) {
|
||||
$array = \explode(';', $header);
|
||||
$hexValue = $array[0];
|
||||
}
|
||||
|
||||
if ($hexValue !== '') {
|
||||
$hexValue = \ltrim(\trim($hexValue), "0");
|
||||
if ($hexValue === '') {
|
||||
$hexValue = "0";
|
||||
}
|
||||
}
|
||||
|
||||
$this->chunkSize = @\hexdec($hexValue);
|
||||
if (!\is_int($this->chunkSize) || \dechex($this->chunkSize) !== $hexValue) {
|
||||
$this->handleError(new Exception($hexValue . ' is not a valid hexadecimal number'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->buffer = (string)\substr($this->buffer, $positionCrlf + 2);
|
||||
$this->headerCompleted = true;
|
||||
if ($this->buffer === '') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$chunk = (string)\substr($this->buffer, 0, $this->chunkSize - $this->transferredSize);
|
||||
|
||||
if ($chunk !== '') {
|
||||
$this->transferredSize += \strlen($chunk);
|
||||
$this->emit('data', array($chunk));
|
||||
$this->buffer = (string)\substr($this->buffer, \strlen($chunk));
|
||||
}
|
||||
|
||||
$positionCrlf = \strpos($this->buffer, static::CRLF);
|
||||
|
||||
if ($positionCrlf === 0) {
|
||||
if ($this->chunkSize === 0) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
return;
|
||||
}
|
||||
$this->chunkSize = 0;
|
||||
$this->headerCompleted = false;
|
||||
$this->transferredSize = 0;
|
||||
$this->buffer = (string)\substr($this->buffer, 2);
|
||||
} elseif ($this->chunkSize === 0) {
|
||||
// end chunk received, skip all trailer data
|
||||
$this->buffer = (string)\substr($this->buffer, $positionCrlf);
|
||||
}
|
||||
|
||||
if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) > 2) {
|
||||
// the first 2 characters are not CRLF, send error event
|
||||
$this->handleError(new Exception('Chunk does not end with a CRLF'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($positionCrlf !== 0 && \strlen($this->buffer) < 2) {
|
||||
// No CRLF found, wait for additional data which could be a CRLF
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
vendor/react/http/src/Io/ChunkedEncoder.php
vendored
Executable file
92
vendor/react/http/src/Io/ChunkedEncoder.php
vendored
Executable file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Encodes given payload stream with "Transfer-Encoding: chunked" and emits encoded data
|
||||
*
|
||||
* This is used internally to encode outgoing requests with this encoding.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ChunkedEncoder extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if ($data !== '') {
|
||||
$this->emit('data', array(
|
||||
\dechex(\strlen($data)) . "\r\n" . $data . "\r\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('data', array("0\r\n\r\n"));
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
137
vendor/react/http/src/Io/ClientConnectionManager.php
vendored
Executable file
137
vendor/react/http/src/Io/ClientConnectionManager.php
vendored
Executable file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\TimerInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Manages outgoing HTTP connections for the HTTP client
|
||||
*
|
||||
* @internal
|
||||
* @final
|
||||
*/
|
||||
class ClientConnectionManager
|
||||
{
|
||||
/** @var ConnectorInterface */
|
||||
private $connector;
|
||||
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
|
||||
/** @var string[] */
|
||||
private $idleUris = array();
|
||||
|
||||
/** @var ConnectionInterface[] */
|
||||
private $idleConnections = array();
|
||||
|
||||
/** @var TimerInterface[] */
|
||||
private $idleTimers = array();
|
||||
|
||||
/** @var \Closure[] */
|
||||
private $idleStreamHandlers = array();
|
||||
|
||||
/** @var float */
|
||||
private $maximumTimeToKeepAliveIdleConnection = 0.001;
|
||||
|
||||
public function __construct(ConnectorInterface $connector, LoopInterface $loop)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PromiseInterface<ConnectionInterface>
|
||||
*/
|
||||
public function connect(UriInterface $uri)
|
||||
{
|
||||
$scheme = $uri->getScheme();
|
||||
if ($scheme !== 'https' && $scheme !== 'http') {
|
||||
return \React\Promise\reject(new \InvalidArgumentException(
|
||||
'Invalid request URL given'
|
||||
));
|
||||
}
|
||||
|
||||
$port = $uri->getPort();
|
||||
if ($port === null) {
|
||||
$port = $scheme === 'https' ? 443 : 80;
|
||||
}
|
||||
$uri = ($scheme === 'https' ? 'tls://' : '') . $uri->getHost() . ':' . $port;
|
||||
|
||||
// Reuse idle connection for same URI if available
|
||||
foreach ($this->idleConnections as $id => $connection) {
|
||||
if ($this->idleUris[$id] === $uri) {
|
||||
assert($this->idleStreamHandlers[$id] instanceof \Closure);
|
||||
$connection->removeListener('close', $this->idleStreamHandlers[$id]);
|
||||
$connection->removeListener('data', $this->idleStreamHandlers[$id]);
|
||||
$connection->removeListener('error', $this->idleStreamHandlers[$id]);
|
||||
|
||||
assert($this->idleTimers[$id] instanceof TimerInterface);
|
||||
$this->loop->cancelTimer($this->idleTimers[$id]);
|
||||
unset($this->idleUris[$id], $this->idleConnections[$id], $this->idleTimers[$id], $this->idleStreamHandlers[$id]);
|
||||
|
||||
return \React\Promise\resolve($connection);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new connection if no idle connection to same URI is available
|
||||
return $this->connector->connect($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hands back an idle connection to the connection manager for possible future reuse.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function keepAlive(UriInterface $uri, ConnectionInterface $connection)
|
||||
{
|
||||
$scheme = $uri->getScheme();
|
||||
assert($scheme === 'https' || $scheme === 'http');
|
||||
|
||||
$port = $uri->getPort();
|
||||
if ($port === null) {
|
||||
$port = $scheme === 'https' ? 443 : 80;
|
||||
}
|
||||
|
||||
$this->idleUris[] = ($scheme === 'https' ? 'tls://' : '') . $uri->getHost() . ':' . $port;
|
||||
$this->idleConnections[] = $connection;
|
||||
|
||||
$that = $this;
|
||||
$cleanUp = function () use ($connection, $that) {
|
||||
// call public method to support legacy PHP 5.3
|
||||
$that->cleanUpConnection($connection);
|
||||
};
|
||||
|
||||
// clean up and close connection when maximum time to keep-alive idle connection has passed
|
||||
$this->idleTimers[] = $this->loop->addTimer($this->maximumTimeToKeepAliveIdleConnection, $cleanUp);
|
||||
|
||||
// clean up and close connection when unexpected close/data/error event happens during idle time
|
||||
$this->idleStreamHandlers[] = $cleanUp;
|
||||
$connection->on('close', $cleanUp);
|
||||
$connection->on('data', $cleanUp);
|
||||
$connection->on('error', $cleanUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return void
|
||||
*/
|
||||
public function cleanUpConnection(ConnectionInterface $connection) // private (PHP 5.4+)
|
||||
{
|
||||
$id = \array_search($connection, $this->idleConnections, true);
|
||||
if ($id === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(\is_int($id));
|
||||
assert($this->idleTimers[$id] instanceof TimerInterface);
|
||||
$this->loop->cancelTimer($this->idleTimers[$id]);
|
||||
unset($this->idleUris[$id], $this->idleConnections[$id], $this->idleTimers[$id], $this->idleStreamHandlers[$id]);
|
||||
|
||||
$connection->close();
|
||||
}
|
||||
}
|
||||
16
vendor/react/http/src/Io/ClientRequestState.php
vendored
Executable file
16
vendor/react/http/src/Io/ClientRequestState.php
vendored
Executable file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
/** @internal */
|
||||
class ClientRequestState
|
||||
{
|
||||
/** @var int */
|
||||
public $numRequests = 0;
|
||||
|
||||
/** @var ?\React\Promise\PromiseInterface */
|
||||
public $pending = null;
|
||||
|
||||
/** @var ?\React\EventLoop\TimerInterface */
|
||||
public $timeout = null;
|
||||
}
|
||||
307
vendor/react/http/src/Io/ClientRequestStream.php
vendored
Executable file
307
vendor/react/http/src/Io/ClientRequestStream.php
vendored
Executable file
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @event response
|
||||
* @event drain
|
||||
* @event error
|
||||
* @event close
|
||||
* @internal
|
||||
*/
|
||||
class ClientRequestStream extends EventEmitter implements WritableStreamInterface
|
||||
{
|
||||
const STATE_INIT = 0;
|
||||
const STATE_WRITING_HEAD = 1;
|
||||
const STATE_HEAD_WRITTEN = 2;
|
||||
const STATE_END = 3;
|
||||
|
||||
/** @var ClientConnectionManager */
|
||||
private $connectionManager;
|
||||
|
||||
/** @var RequestInterface */
|
||||
private $request;
|
||||
|
||||
/** @var ?ConnectionInterface */
|
||||
private $connection;
|
||||
|
||||
/** @var string */
|
||||
private $buffer = '';
|
||||
|
||||
private $responseFactory;
|
||||
private $state = self::STATE_INIT;
|
||||
private $ended = false;
|
||||
|
||||
private $pendingWrites = '';
|
||||
|
||||
public function __construct(ClientConnectionManager $connectionManager, RequestInterface $request)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return self::STATE_END > $this->state && !$this->ended;
|
||||
}
|
||||
|
||||
private function writeHead()
|
||||
{
|
||||
$this->state = self::STATE_WRITING_HEAD;
|
||||
|
||||
$expected = 0;
|
||||
$headers = "{$this->request->getMethod()} {$this->request->getRequestTarget()} HTTP/{$this->request->getProtocolVersion()}\r\n";
|
||||
foreach ($this->request->getHeaders() as $name => $values) {
|
||||
if (\strpos($name, ':') !== false) {
|
||||
$expected = -1;
|
||||
break;
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
$headers .= "$name: $value\r\n";
|
||||
++$expected;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array $m legacy PHP 5.3 only */
|
||||
if (!\preg_match('#^\S+ \S+ HTTP/1\.[01]\r\n#m', $headers) || \substr_count($headers, "\n") !== ($expected + 1) || (\PHP_VERSION_ID >= 50400 ? \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers) : \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers, $m)) !== $expected) {
|
||||
$this->closeError(new \InvalidArgumentException('Unable to send request with invalid request headers'));
|
||||
return;
|
||||
}
|
||||
|
||||
$connectionRef = &$this->connection;
|
||||
$stateRef = &$this->state;
|
||||
$pendingWrites = &$this->pendingWrites;
|
||||
$that = $this;
|
||||
|
||||
$promise = $this->connectionManager->connect($this->request->getUri());
|
||||
$promise->then(
|
||||
function (ConnectionInterface $connection) use ($headers, &$connectionRef, &$stateRef, &$pendingWrites, $that) {
|
||||
$connectionRef = $connection;
|
||||
assert($connectionRef instanceof ConnectionInterface);
|
||||
|
||||
$connection->on('drain', array($that, 'handleDrain'));
|
||||
$connection->on('data', array($that, 'handleData'));
|
||||
$connection->on('end', array($that, 'handleEnd'));
|
||||
$connection->on('error', array($that, 'handleError'));
|
||||
$connection->on('close', array($that, 'close'));
|
||||
|
||||
$more = $connection->write($headers . "\r\n" . $pendingWrites);
|
||||
|
||||
assert($stateRef === ClientRequestStream::STATE_WRITING_HEAD);
|
||||
$stateRef = ClientRequestStream::STATE_HEAD_WRITTEN;
|
||||
|
||||
// clear pending writes if non-empty
|
||||
if ($pendingWrites !== '') {
|
||||
$pendingWrites = '';
|
||||
|
||||
if ($more) {
|
||||
$that->emit('drain');
|
||||
}
|
||||
}
|
||||
},
|
||||
array($this, 'closeError')
|
||||
);
|
||||
|
||||
$this->on('close', function() use ($promise) {
|
||||
$promise->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
if (!$this->isWritable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// write directly to connection stream if already available
|
||||
if (self::STATE_HEAD_WRITTEN <= $this->state) {
|
||||
return $this->connection->write($data);
|
||||
}
|
||||
|
||||
// otherwise buffer and try to establish connection
|
||||
$this->pendingWrites .= $data;
|
||||
if (self::STATE_WRITING_HEAD > $this->state) {
|
||||
$this->writeHead();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if (!$this->isWritable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $data) {
|
||||
$this->write($data);
|
||||
} else if (self::STATE_WRITING_HEAD > $this->state) {
|
||||
$this->writeHead();
|
||||
}
|
||||
|
||||
$this->ended = true;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleDrain()
|
||||
{
|
||||
$this->emit('drain');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
// buffer until double CRLF (or double LF for compatibility with legacy servers)
|
||||
$eom = \strpos($this->buffer, "\r\n\r\n");
|
||||
$eomLegacy = \strpos($this->buffer, "\n\n");
|
||||
if ($eom !== false || $eomLegacy !== false) {
|
||||
try {
|
||||
if ($eom !== false && ($eomLegacy === false || $eom < $eomLegacy)) {
|
||||
$response = Response::parseMessage(\substr($this->buffer, 0, $eom + 2));
|
||||
$bodyChunk = (string) \substr($this->buffer, $eom + 4);
|
||||
} else {
|
||||
$response = Response::parseMessage(\substr($this->buffer, 0, $eomLegacy + 1));
|
||||
$bodyChunk = (string) \substr($this->buffer, $eomLegacy + 2);
|
||||
}
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
$this->closeError($exception);
|
||||
return;
|
||||
}
|
||||
|
||||
// response headers successfully received => remove listeners for connection events
|
||||
$connection = $this->connection;
|
||||
assert($connection instanceof ConnectionInterface);
|
||||
$connection->removeListener('drain', array($this, 'handleDrain'));
|
||||
$connection->removeListener('data', array($this, 'handleData'));
|
||||
$connection->removeListener('end', array($this, 'handleEnd'));
|
||||
$connection->removeListener('error', array($this, 'handleError'));
|
||||
$connection->removeListener('close', array($this, 'close'));
|
||||
$this->connection = null;
|
||||
$this->buffer = '';
|
||||
|
||||
// take control over connection handling and check if we can reuse the connection once response body closes
|
||||
$that = $this;
|
||||
$request = $this->request;
|
||||
$connectionManager = $this->connectionManager;
|
||||
$successfulEndReceived = false;
|
||||
$input = $body = new CloseProtectionStream($connection);
|
||||
$input->on('close', function () use ($connection, $that, $connectionManager, $request, $response, &$successfulEndReceived) {
|
||||
// only reuse connection after successful response and both request and response allow keep alive
|
||||
if ($successfulEndReceived && $connection->isReadable() && $that->hasMessageKeepAliveEnabled($response) && $that->hasMessageKeepAliveEnabled($request)) {
|
||||
$connectionManager->keepAlive($request->getUri(), $connection);
|
||||
} else {
|
||||
$connection->close();
|
||||
}
|
||||
|
||||
$that->close();
|
||||
});
|
||||
|
||||
// determine length of response body
|
||||
$length = null;
|
||||
$code = $response->getStatusCode();
|
||||
if ($this->request->getMethod() === 'HEAD' || ($code >= 100 && $code < 200) || $code == Response::STATUS_NO_CONTENT || $code == Response::STATUS_NOT_MODIFIED) {
|
||||
$length = 0;
|
||||
} elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') {
|
||||
$body = new ChunkedDecoder($body);
|
||||
} elseif ($response->hasHeader('Content-Length')) {
|
||||
$length = (int) $response->getHeaderLine('Content-Length');
|
||||
}
|
||||
$response = $response->withBody($body = new ReadableBodyStream($body, $length));
|
||||
$body->on('end', function () use (&$successfulEndReceived) {
|
||||
$successfulEndReceived = true;
|
||||
});
|
||||
|
||||
// emit response with streaming response body (see `Sender`)
|
||||
$this->emit('response', array($response, $body));
|
||||
|
||||
// re-emit HTTP response body to trigger body parsing if parts of it are buffered
|
||||
if ($bodyChunk !== '') {
|
||||
$input->handleData($bodyChunk);
|
||||
} elseif ($length === 0) {
|
||||
$input->handleEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->closeError(new \RuntimeException(
|
||||
"Connection ended before receiving response"
|
||||
));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $error)
|
||||
{
|
||||
$this->closeError(new \RuntimeException(
|
||||
"An error occurred in the underlying stream",
|
||||
0,
|
||||
$error
|
||||
));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function closeError(\Exception $error)
|
||||
{
|
||||
if (self::STATE_END <= $this->state) {
|
||||
return;
|
||||
}
|
||||
$this->emit('error', array($error));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (self::STATE_END <= $this->state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->state = self::STATE_END;
|
||||
$this->pendingWrites = '';
|
||||
$this->buffer = '';
|
||||
|
||||
if ($this->connection instanceof ConnectionInterface) {
|
||||
$this->connection->close();
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return bool
|
||||
* @link https://www.rfc-editor.org/rfc/rfc9112#section-9.3
|
||||
* @link https://www.rfc-editor.org/rfc/rfc7230#section-6.1
|
||||
*/
|
||||
public function hasMessageKeepAliveEnabled(MessageInterface $message)
|
||||
{
|
||||
// @link https://www.rfc-editor.org/rfc/rfc9110#section-7.6.1
|
||||
$connectionOptions = \array_map('trim', \explode(',', \strtolower($message->getHeaderLine('Connection'))));
|
||||
|
||||
if (\in_array('close', $connectionOptions, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($message->getProtocolVersion() === '1.1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (\in_array('keep-alive', $connectionOptions, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
54
vendor/react/http/src/Io/Clock.php
vendored
Executable file
54
vendor/react/http/src/Io/Clock.php
vendored
Executable file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* [internal] Clock source that returns current timestamp and memoize clock for same tick
|
||||
*
|
||||
* This is mostly used as an internal optimization to avoid unneeded syscalls to
|
||||
* get the current system time multiple times within the same loop tick. For the
|
||||
* purpose of the HTTP server, the clock is assumed to not change to a
|
||||
* significant degree within the same loop tick. If you need a high precision
|
||||
* clock source, you may want to use `\hrtime()` instead (PHP 7.3+).
|
||||
*
|
||||
* The API is modelled to resemble the PSR-20 `ClockInterface` (in draft at the
|
||||
* time of writing this), but uses a `float` return value for performance
|
||||
* reasons instead.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about for outside use.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Clock
|
||||
{
|
||||
/** @var LoopInterface $loop */
|
||||
private $loop;
|
||||
|
||||
/** @var ?float */
|
||||
private $now;
|
||||
|
||||
public function __construct(LoopInterface $loop)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/** @return float */
|
||||
public function now()
|
||||
{
|
||||
if ($this->now === null) {
|
||||
$this->now = \microtime(true);
|
||||
|
||||
// remember clock for current loop tick only and update on next tick
|
||||
$now =& $this->now;
|
||||
$this->loop->futureTick(function () use (&$now) {
|
||||
assert($now !== null);
|
||||
$now = null;
|
||||
});
|
||||
}
|
||||
|
||||
return $this->now;
|
||||
}
|
||||
}
|
||||
111
vendor/react/http/src/Io/CloseProtectionStream.php
vendored
Executable file
111
vendor/react/http/src/Io/CloseProtectionStream.php
vendored
Executable file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Protects a given stream from actually closing and only discards its incoming data instead.
|
||||
*
|
||||
* This is used internally to prevent the underlying connection from closing, so
|
||||
* that we can still send back a response over the same stream.
|
||||
*
|
||||
* @internal
|
||||
* */
|
||||
class CloseProtectionStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $paused = false;
|
||||
|
||||
/**
|
||||
* @param ReadableStreamInterface $input stream that will be discarded instead of closing it on an 'close' event.
|
||||
*/
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = true;
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = false;
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
// stop listening for incoming events
|
||||
$this->input->removeListener('data', array($this, 'handleData'));
|
||||
$this->input->removeListener('error', array($this, 'handleError'));
|
||||
$this->input->removeListener('end', array($this, 'handleEnd'));
|
||||
$this->input->removeListener('close', array($this, 'close'));
|
||||
|
||||
// resume the stream to ensure we discard everything from incoming connection
|
||||
if ($this->paused) {
|
||||
$this->paused = false;
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
}
|
||||
}
|
||||
142
vendor/react/http/src/Io/EmptyBodyStream.php
vendored
Executable file
142
vendor/react/http/src/Io/EmptyBodyStream.php
vendored
Executable file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Bridge between an empty StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
|
||||
*
|
||||
* This class is used in the server to represent an empty body stream of an
|
||||
* incoming response from the client. This is similar to the `HttpBodyStream`,
|
||||
* but is specifically designed for the common case of having an empty message
|
||||
* body.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `StreamInterface` and `ReadableStreamInterface` for more
|
||||
* details.
|
||||
*
|
||||
* @see HttpBodyStream
|
||||
* @see StreamInterface
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class EmptyBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
|
||||
{
|
||||
private $closed = false;
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function detach()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function eof()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getContents()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
}
|
||||
182
vendor/react/http/src/Io/HttpBodyStream.php
vendored
Executable file
182
vendor/react/http/src/Io/HttpBodyStream.php
vendored
Executable file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Bridge between StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
|
||||
*
|
||||
* This class is used in the server to stream the body of an incoming response
|
||||
* from the client. This allows us to stream big amounts of data without having
|
||||
* to buffer this data. Similarly, this used to stream the body of an outgoing
|
||||
* request body to the client. The data will be sent directly to the client.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `StreamInterface` and `ReadableStreamInterface` for more
|
||||
* details.
|
||||
*
|
||||
* @see StreamInterface
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class HttpBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
|
||||
{
|
||||
public $input;
|
||||
private $closed = false;
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @param ReadableStreamInterface $input Stream data from $stream as a body of a PSR-7 object4
|
||||
* @param int|null $size size of the data body
|
||||
*/
|
||||
public function __construct(ReadableStreamInterface $input, $size)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->size = $size;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function detach()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function eof()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getContents()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
48
vendor/react/http/src/Io/IniUtil.php
vendored
Executable file
48
vendor/react/http/src/Io/IniUtil.php
vendored
Executable file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class IniUtil
|
||||
{
|
||||
/**
|
||||
* Convert a ini like size to a numeric size in bytes.
|
||||
*
|
||||
* @param string $size
|
||||
* @return int
|
||||
*/
|
||||
public static function iniSizeToBytes($size)
|
||||
{
|
||||
if (\is_numeric($size)) {
|
||||
return (int)$size;
|
||||
}
|
||||
|
||||
$suffix = \strtoupper(\substr($size, -1));
|
||||
$strippedSize = \substr($size, 0, -1);
|
||||
|
||||
if (!\is_numeric($strippedSize)) {
|
||||
throw new \InvalidArgumentException("$size is not a valid ini size");
|
||||
}
|
||||
|
||||
if ($strippedSize <= 0) {
|
||||
throw new \InvalidArgumentException("Expect $size to be higher isn't zero or lower");
|
||||
}
|
||||
|
||||
if ($suffix === 'K') {
|
||||
return $strippedSize * 1024;
|
||||
}
|
||||
if ($suffix === 'M') {
|
||||
return $strippedSize * 1024 * 1024;
|
||||
}
|
||||
if ($suffix === 'G') {
|
||||
return $strippedSize * 1024 * 1024 * 1024;
|
||||
}
|
||||
if ($suffix === 'T') {
|
||||
return $strippedSize * 1024 * 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
return (int)$size;
|
||||
}
|
||||
}
|
||||
108
vendor/react/http/src/Io/LengthLimitedStream.php
vendored
Executable file
108
vendor/react/http/src/Io/LengthLimitedStream.php
vendored
Executable file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Limits the amount of data the given stream can emit
|
||||
*
|
||||
* This is used internally to limit the size of the underlying connection stream
|
||||
* to the size defined by the "Content-Length" header of the incoming request.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LengthLimitedStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $stream;
|
||||
private $closed = false;
|
||||
private $transferredLength = 0;
|
||||
private $maxLength;
|
||||
|
||||
public function __construct(ReadableStreamInterface $stream, $maxLength)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->maxLength = $maxLength;
|
||||
|
||||
$this->stream->on('data', array($this, 'handleData'));
|
||||
$this->stream->on('end', array($this, 'handleEnd'));
|
||||
$this->stream->on('error', array($this, 'handleError'));
|
||||
$this->stream->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->stream->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->stream->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->stream->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->stream->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if (($this->transferredLength + \strlen($data)) > $this->maxLength) {
|
||||
// Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
|
||||
$data = (string)\substr($data, 0, $this->maxLength - $this->transferredLength);
|
||||
}
|
||||
|
||||
if ($data !== '') {
|
||||
$this->transferredLength += \strlen($data);
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
if ($this->transferredLength === $this->maxLength) {
|
||||
// 'Content-Length' reached, stream will end
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
$this->stream->removeListener('data', array($this, 'handleData'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->handleError(new \Exception('Unexpected end event'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
61
vendor/react/http/src/Io/MiddlewareRunner.php
vendored
Executable file
61
vendor/react/http/src/Io/MiddlewareRunner.php
vendored
Executable file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Middleware runner to expose an array of middleware request handlers as a single request handler callable
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MiddlewareRunner
|
||||
{
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
private $middleware;
|
||||
|
||||
/**
|
||||
* @param callable[] $middleware
|
||||
*/
|
||||
public function __construct(array $middleware)
|
||||
{
|
||||
$this->middleware = \array_values($middleware);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request)
|
||||
{
|
||||
if (empty($this->middleware)) {
|
||||
throw new \RuntimeException('No middleware to run');
|
||||
}
|
||||
|
||||
return $this->call($request, 0);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function call(ServerRequestInterface $request, $position)
|
||||
{
|
||||
// final request handler will be invoked without a next handler
|
||||
if (!isset($this->middleware[$position + 1])) {
|
||||
$handler = $this->middleware[$position];
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$next = function (ServerRequestInterface $request) use ($that, $position) {
|
||||
return $that->call($request, $position + 1);
|
||||
};
|
||||
|
||||
// invoke middleware request handler with next handler
|
||||
$handler = $this->middleware[$position];
|
||||
return $handler($request, $next);
|
||||
}
|
||||
}
|
||||
345
vendor/react/http/src/Io/MultipartParser.php
vendored
Executable file
345
vendor/react/http/src/Io/MultipartParser.php
vendored
Executable file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Parses a string body with "Content-Type: multipart/form-data" into structured data
|
||||
*
|
||||
* This is use internally to parse incoming request bodies into structured data
|
||||
* that resembles PHP's `$_POST` and `$_FILES` superglobals.
|
||||
*
|
||||
* @internal
|
||||
* @link https://tools.ietf.org/html/rfc7578
|
||||
* @link https://tools.ietf.org/html/rfc2046#section-5.1.1
|
||||
*/
|
||||
final class MultipartParser
|
||||
{
|
||||
/**
|
||||
* @var ServerRequestInterface|null
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $maxFileSize;
|
||||
|
||||
/**
|
||||
* Based on $maxInputVars and $maxFileUploads
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxMultipartBodyParts;
|
||||
|
||||
/**
|
||||
* ini setting "max_input_vars"
|
||||
*
|
||||
* Does not exist in PHP < 5.3.9 or HHVM, so assume PHP's default 1000 here.
|
||||
*
|
||||
* @var int
|
||||
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-vars
|
||||
*/
|
||||
private $maxInputVars = 1000;
|
||||
|
||||
/**
|
||||
* ini setting "max_input_nesting_level"
|
||||
*
|
||||
* Does not exist in HHVM, but assumes hard coded to 64 (PHP's default).
|
||||
*
|
||||
* @var int
|
||||
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-nesting-level
|
||||
*/
|
||||
private $maxInputNestingLevel = 64;
|
||||
|
||||
/**
|
||||
* ini setting "upload_max_filesize"
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $uploadMaxFilesize;
|
||||
|
||||
/**
|
||||
* ini setting "max_file_uploads"
|
||||
*
|
||||
* Additionally, setting "file_uploads = off" effectively sets this to zero.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxFileUploads;
|
||||
|
||||
private $multipartBodyPartCount = 0;
|
||||
private $postCount = 0;
|
||||
private $filesCount = 0;
|
||||
private $emptyCount = 0;
|
||||
private $cursor = 0;
|
||||
|
||||
/**
|
||||
* @param int|string|null $uploadMaxFilesize
|
||||
* @param int|null $maxFileUploads
|
||||
*/
|
||||
public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
|
||||
{
|
||||
$var = \ini_get('max_input_vars');
|
||||
if ($var !== false) {
|
||||
$this->maxInputVars = (int)$var;
|
||||
}
|
||||
$var = \ini_get('max_input_nesting_level');
|
||||
if ($var !== false) {
|
||||
$this->maxInputNestingLevel = (int)$var;
|
||||
}
|
||||
|
||||
if ($uploadMaxFilesize === null) {
|
||||
$uploadMaxFilesize = \ini_get('upload_max_filesize');
|
||||
}
|
||||
|
||||
$this->uploadMaxFilesize = IniUtil::iniSizeToBytes($uploadMaxFilesize);
|
||||
$this->maxFileUploads = $maxFileUploads === null ? (\ini_get('file_uploads') === '' ? 0 : (int)\ini_get('max_file_uploads')) : (int)$maxFileUploads;
|
||||
|
||||
$this->maxMultipartBodyParts = $this->maxInputVars + $this->maxFileUploads;
|
||||
}
|
||||
|
||||
public function parse(ServerRequestInterface $request)
|
||||
{
|
||||
$contentType = $request->getHeaderLine('content-type');
|
||||
if(!\preg_match('/boundary="?(.*?)"?$/', $contentType, $matches)) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
$this->request = $request;
|
||||
$this->parseBody('--' . $matches[1], (string)$request->getBody());
|
||||
|
||||
$request = $this->request;
|
||||
$this->request = null;
|
||||
$this->multipartBodyPartCount = 0;
|
||||
$this->cursor = 0;
|
||||
$this->postCount = 0;
|
||||
$this->filesCount = 0;
|
||||
$this->emptyCount = 0;
|
||||
$this->maxFileSize = null;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function parseBody($boundary, $buffer)
|
||||
{
|
||||
$len = \strlen($boundary);
|
||||
|
||||
// ignore everything before initial boundary (SHOULD be empty)
|
||||
$this->cursor = \strpos($buffer, $boundary . "\r\n");
|
||||
|
||||
while ($this->cursor !== false) {
|
||||
// search following boundary (preceded by newline)
|
||||
// ignore last if not followed by boundary (SHOULD end with "--")
|
||||
$this->cursor += $len + 2;
|
||||
$end = \strpos($buffer, "\r\n" . $boundary, $this->cursor);
|
||||
if ($end === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
// parse one part and continue searching for next
|
||||
$this->parsePart(\substr($buffer, $this->cursor, $end - $this->cursor));
|
||||
$this->cursor = $end;
|
||||
|
||||
if (++$this->multipartBodyPartCount > $this->maxMultipartBodyParts) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parsePart($chunk)
|
||||
{
|
||||
$pos = \strpos($chunk, "\r\n\r\n");
|
||||
if ($pos === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = $this->parseHeaders((string)substr($chunk, 0, $pos));
|
||||
$body = (string)\substr($chunk, $pos + 4);
|
||||
|
||||
if (!isset($headers['content-disposition'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->getParameterFromHeader($headers['content-disposition'], 'name');
|
||||
if ($name === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = $this->getParameterFromHeader($headers['content-disposition'], 'filename');
|
||||
if ($filename !== null) {
|
||||
$this->parseFile(
|
||||
$name,
|
||||
$filename,
|
||||
isset($headers['content-type'][0]) ? $headers['content-type'][0] : null,
|
||||
$body
|
||||
);
|
||||
} else {
|
||||
$this->parsePost($name, $body);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseFile($name, $filename, $contentType, $contents)
|
||||
{
|
||||
$file = $this->parseUploadedFile($filename, $contentType, $contents);
|
||||
if ($file === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request = $this->request->withUploadedFiles($this->extractPost(
|
||||
$this->request->getUploadedFiles(),
|
||||
$name,
|
||||
$file
|
||||
));
|
||||
}
|
||||
|
||||
private function parseUploadedFile($filename, $contentType, $contents)
|
||||
{
|
||||
$size = \strlen($contents);
|
||||
|
||||
// no file selected (zero size and empty filename)
|
||||
if ($size === 0 && $filename === '') {
|
||||
// ignore excessive number of empty file uploads
|
||||
if (++$this->emptyCount + $this->filesCount > $this->maxInputVars) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_NO_FILE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
// ignore excessive number of file uploads
|
||||
if (++$this->filesCount > $this->maxFileUploads) {
|
||||
return;
|
||||
}
|
||||
|
||||
// file exceeds "upload_max_filesize" ini setting
|
||||
if ($size > $this->uploadMaxFilesize) {
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_INI_SIZE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
// file exceeds MAX_FILE_SIZE value
|
||||
if ($this->maxFileSize !== null && $size > $this->maxFileSize) {
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_FORM_SIZE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
new BufferedBody($contents),
|
||||
$size,
|
||||
\UPLOAD_ERR_OK,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
private function parsePost($name, $value)
|
||||
{
|
||||
// ignore excessive number of post fields
|
||||
if (++$this->postCount > $this->maxInputVars) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request = $this->request->withParsedBody($this->extractPost(
|
||||
$this->request->getParsedBody(),
|
||||
$name,
|
||||
$value
|
||||
));
|
||||
|
||||
if (\strtoupper($name) === 'MAX_FILE_SIZE') {
|
||||
$this->maxFileSize = (int)$value;
|
||||
|
||||
if ($this->maxFileSize === 0) {
|
||||
$this->maxFileSize = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseHeaders($header)
|
||||
{
|
||||
$headers = array();
|
||||
|
||||
foreach (\explode("\r\n", \trim($header)) as $line) {
|
||||
$parts = \explode(':', $line, 2);
|
||||
if (!isset($parts[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = \strtolower(trim($parts[0]));
|
||||
$values = \explode(';', $parts[1]);
|
||||
$values = \array_map('trim', $values);
|
||||
$headers[$key] = $values;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
private function getParameterFromHeader(array $header, $parameter)
|
||||
{
|
||||
foreach ($header as $part) {
|
||||
if (\preg_match('/' . $parameter . '="?(.*?)"?$/', $part, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function extractPost($postFields, $key, $value)
|
||||
{
|
||||
$chunks = \explode('[', $key);
|
||||
if (\count($chunks) == 1) {
|
||||
$postFields[$key] = $value;
|
||||
return $postFields;
|
||||
}
|
||||
|
||||
// ignore this key if maximum nesting level is exceeded
|
||||
if (isset($chunks[$this->maxInputNestingLevel])) {
|
||||
return $postFields;
|
||||
}
|
||||
|
||||
$chunkKey = \rtrim($chunks[0], ']');
|
||||
$parent = &$postFields;
|
||||
for ($i = 1; isset($chunks[$i]); $i++) {
|
||||
$previousChunkKey = $chunkKey;
|
||||
|
||||
if ($previousChunkKey === '') {
|
||||
$parent[] = array();
|
||||
\end($parent);
|
||||
$parent = &$parent[\key($parent)];
|
||||
} else {
|
||||
if (!isset($parent[$previousChunkKey]) || !\is_array($parent[$previousChunkKey])) {
|
||||
$parent[$previousChunkKey] = array();
|
||||
}
|
||||
$parent = &$parent[$previousChunkKey];
|
||||
}
|
||||
|
||||
$chunkKey = \rtrim($chunks[$i], ']');
|
||||
}
|
||||
|
||||
if ($chunkKey === '') {
|
||||
$parent[] = $value;
|
||||
} else {
|
||||
$parent[$chunkKey] = $value;
|
||||
}
|
||||
|
||||
return $postFields;
|
||||
}
|
||||
}
|
||||
188
vendor/react/http/src/Io/PauseBufferStream.php
vendored
Executable file
188
vendor/react/http/src/Io/PauseBufferStream.php
vendored
Executable file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Pauses a given stream and buffers all events while paused
|
||||
*
|
||||
* This class is used to buffer all events that happen on a given stream while
|
||||
* it is paused. This allows you to pause a stream and no longer watch for any
|
||||
* of its events. Once the stream is resumed, all buffered events will be
|
||||
* emitted. Explicitly closing the resulting stream clears all buffers.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about.
|
||||
*
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class PauseBufferStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $paused = false;
|
||||
private $dataPaused = '';
|
||||
private $endPaused = false;
|
||||
private $closePaused = false;
|
||||
private $errorPaused;
|
||||
private $implicit = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'handleClose'));
|
||||
}
|
||||
|
||||
/**
|
||||
* pause and remember this was not explicitly from user control
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function pauseImplicit()
|
||||
{
|
||||
$this->pause();
|
||||
$this->implicit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* resume only if this was previously paused implicitly and not explicitly from user control
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function resumeImplicit()
|
||||
{
|
||||
if ($this->implicit) {
|
||||
$this->resume();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->input->pause();
|
||||
$this->paused = true;
|
||||
$this->implicit = false;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = false;
|
||||
$this->implicit = false;
|
||||
|
||||
if ($this->dataPaused !== '') {
|
||||
$this->emit('data', array($this->dataPaused));
|
||||
$this->dataPaused = '';
|
||||
}
|
||||
|
||||
if ($this->errorPaused) {
|
||||
$this->emit('error', array($this->errorPaused));
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
if ($this->endPaused) {
|
||||
$this->endPaused = false;
|
||||
$this->emit('end');
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
if ($this->closePaused) {
|
||||
$this->closePaused = false;
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->dataPaused = '';
|
||||
$this->endPaused = $this->closePaused = false;
|
||||
$this->errorPaused = null;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->dataPaused .= $data;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->errorPaused = $e;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->endPaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleClose()
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->closePaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
153
vendor/react/http/src/Io/ReadableBodyStream.php
vendored
Executable file
153
vendor/react/http/src/Io/ReadableBodyStream.php
vendored
Executable file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ReadableBodyStream extends EventEmitter implements ReadableStreamInterface, StreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $position = 0;
|
||||
private $size;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input, $size = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->size = $size;
|
||||
|
||||
$that = $this;
|
||||
$pos =& $this->position;
|
||||
$input->on('data', function ($data) use ($that, &$pos, $size) {
|
||||
$that->emit('data', array($data));
|
||||
|
||||
$pos += \strlen($data);
|
||||
if ($size !== null && $pos >= $size) {
|
||||
$that->handleEnd();
|
||||
}
|
||||
});
|
||||
$input->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
});
|
||||
$input->on('end', array($that, 'handleEnd'));
|
||||
$input->on('close', array($that, 'close'));
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return !$this->isReadable();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->position !== $this->size && $this->size !== null) {
|
||||
$this->emit('error', array(new \UnderflowException('Unexpected end of response body after ' . $this->position . '/' . $this->size . ' bytes')));
|
||||
} else {
|
||||
$this->emit('end');
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
179
vendor/react/http/src/Io/RequestHeaderParser.php
vendored
Executable file
179
vendor/react/http/src/Io/RequestHeaderParser.php
vendored
Executable file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ServerRequest;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* [Internal] Parses an incoming request header from an input stream
|
||||
*
|
||||
* This is used internally to parse the request header from the connection and
|
||||
* then process the remaining connection as the request body.
|
||||
*
|
||||
* @event headers
|
||||
* @event error
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RequestHeaderParser extends EventEmitter
|
||||
{
|
||||
private $maxSize = 8192;
|
||||
|
||||
/** @var Clock */
|
||||
private $clock;
|
||||
|
||||
/** @var array<string|int,array<string,string>> */
|
||||
private $connectionParams = array();
|
||||
|
||||
public function __construct(Clock $clock)
|
||||
{
|
||||
$this->clock = $clock;
|
||||
}
|
||||
|
||||
public function handle(ConnectionInterface $conn)
|
||||
{
|
||||
$buffer = '';
|
||||
$maxSize = $this->maxSize;
|
||||
$that = $this;
|
||||
$conn->on('data', $fn = function ($data) use (&$buffer, &$fn, $conn, $maxSize, $that) {
|
||||
// append chunk of data to buffer and look for end of request headers
|
||||
$buffer .= $data;
|
||||
$endOfHeader = \strpos($buffer, "\r\n\r\n");
|
||||
|
||||
// reject request if buffer size is exceeded
|
||||
if ($endOfHeader > $maxSize || ($endOfHeader === false && isset($buffer[$maxSize]))) {
|
||||
$conn->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
$that->emit('error', array(
|
||||
new \OverflowException("Maximum header size of {$maxSize} exceeded.", Response::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE),
|
||||
$conn
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore incomplete requests
|
||||
if ($endOfHeader === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// request headers received => try to parse request
|
||||
$conn->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
try {
|
||||
$request = $that->parseRequest(
|
||||
(string)\substr($buffer, 0, $endOfHeader + 2),
|
||||
$conn
|
||||
);
|
||||
} catch (Exception $exception) {
|
||||
$buffer = '';
|
||||
$that->emit('error', array(
|
||||
$exception,
|
||||
$conn
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$contentLength = 0;
|
||||
if ($request->hasHeader('Transfer-Encoding')) {
|
||||
$contentLength = null;
|
||||
} elseif ($request->hasHeader('Content-Length')) {
|
||||
$contentLength = (int)$request->getHeaderLine('Content-Length');
|
||||
}
|
||||
|
||||
if ($contentLength === 0) {
|
||||
// happy path: request body is known to be empty
|
||||
$stream = new EmptyBodyStream();
|
||||
$request = $request->withBody($stream);
|
||||
} else {
|
||||
// otherwise body is present => delimit using Content-Length or ChunkedDecoder
|
||||
$stream = new CloseProtectionStream($conn);
|
||||
if ($contentLength !== null) {
|
||||
$stream = new LengthLimitedStream($stream, $contentLength);
|
||||
} else {
|
||||
$stream = new ChunkedDecoder($stream);
|
||||
}
|
||||
|
||||
$request = $request->withBody(new HttpBodyStream($stream, $contentLength));
|
||||
}
|
||||
|
||||
$bodyBuffer = isset($buffer[$endOfHeader + 4]) ? \substr($buffer, $endOfHeader + 4) : '';
|
||||
$buffer = '';
|
||||
$that->emit('headers', array($request, $conn));
|
||||
|
||||
if ($bodyBuffer !== '') {
|
||||
$conn->emit('data', array($bodyBuffer));
|
||||
}
|
||||
|
||||
// happy path: request body is known to be empty => immediately end stream
|
||||
if ($contentLength === 0) {
|
||||
$stream->emit('end');
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $headers buffer string containing request headers only
|
||||
* @param ConnectionInterface $connection
|
||||
* @return ServerRequestInterface
|
||||
* @throws \InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public function parseRequest($headers, ConnectionInterface $connection)
|
||||
{
|
||||
// reuse same connection params for all server params for this connection
|
||||
$cid = \PHP_VERSION_ID < 70200 ? \spl_object_hash($connection) : \spl_object_id($connection);
|
||||
if (isset($this->connectionParams[$cid])) {
|
||||
$serverParams = $this->connectionParams[$cid];
|
||||
} else {
|
||||
// assign new server params for new connection
|
||||
$serverParams = array();
|
||||
|
||||
// scheme is `http` unless TLS is used
|
||||
$localSocketUri = $connection->getLocalAddress();
|
||||
$localParts = $localSocketUri === null ? array() : \parse_url($localSocketUri);
|
||||
if (isset($localParts['scheme']) && $localParts['scheme'] === 'tls') {
|
||||
$serverParams['HTTPS'] = 'on';
|
||||
}
|
||||
|
||||
// apply SERVER_ADDR and SERVER_PORT if server address is known
|
||||
// address should always be known, even for Unix domain sockets (UDS)
|
||||
// but skip UDS as it doesn't have a concept of host/port.
|
||||
if ($localSocketUri !== null && isset($localParts['host'], $localParts['port'])) {
|
||||
$serverParams['SERVER_ADDR'] = $localParts['host'];
|
||||
$serverParams['SERVER_PORT'] = $localParts['port'];
|
||||
}
|
||||
|
||||
// apply REMOTE_ADDR and REMOTE_PORT if source address is known
|
||||
// address should always be known, unless this is over Unix domain sockets (UDS)
|
||||
$remoteSocketUri = $connection->getRemoteAddress();
|
||||
if ($remoteSocketUri !== null) {
|
||||
$remoteAddress = \parse_url($remoteSocketUri);
|
||||
$serverParams['REMOTE_ADDR'] = $remoteAddress['host'];
|
||||
$serverParams['REMOTE_PORT'] = $remoteAddress['port'];
|
||||
}
|
||||
|
||||
// remember server params for all requests from this connection, reset on connection close
|
||||
$this->connectionParams[$cid] = $serverParams;
|
||||
$params =& $this->connectionParams;
|
||||
$connection->on('close', function () use (&$params, $cid) {
|
||||
assert(\is_array($params));
|
||||
unset($params[$cid]);
|
||||
});
|
||||
}
|
||||
|
||||
// create new obj implementing ServerRequestInterface by preserving all
|
||||
// previous properties and restoring original request-target
|
||||
$serverParams['REQUEST_TIME'] = (int) ($now = $this->clock->now());
|
||||
$serverParams['REQUEST_TIME_FLOAT'] = $now;
|
||||
|
||||
return ServerRequest::parseMessage($headers, $serverParams);
|
||||
}
|
||||
}
|
||||
152
vendor/react/http/src/Io/Sender.php
vendored
Executable file
152
vendor/react/http/src/Io/Sender.php
vendored
Executable file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Client\Client as HttpClient;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Sends requests and receives responses
|
||||
*
|
||||
* The `Sender` is responsible for passing the [`RequestInterface`](#requestinterface) objects to
|
||||
* the underlying [`HttpClient`](https://github.com/reactphp/http-client) library
|
||||
* and keeps track of its transmission and converts its reponses back to [`ResponseInterface`](#responseinterface) objects.
|
||||
*
|
||||
* It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
|
||||
* and the default [`Connector`](https://github.com/reactphp/socket-client) and [DNS `Resolver`](https://github.com/reactphp/dns).
|
||||
*
|
||||
* The `Sender` class mostly exists in order to abstract changes on the underlying
|
||||
* components away from this package in order to provide backwards and forwards
|
||||
* compatibility.
|
||||
*
|
||||
* @internal You SHOULD NOT rely on this API, it is subject to change without prior notice!
|
||||
* @see Browser
|
||||
*/
|
||||
class Sender
|
||||
{
|
||||
/**
|
||||
* create a new default sender attached to the given event loop
|
||||
*
|
||||
* This method is used internally to create the "default sender".
|
||||
*
|
||||
* You may also use this method if you need custom DNS or connector
|
||||
* settings. You can use this method manually like this:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector(array(), $loop);
|
||||
* $sender = \React\Http\Io\Sender::createFromLoop($loop, $connector);
|
||||
* ```
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector)
|
||||
{
|
||||
return new self(new HttpClient(new ClientConnectionManager($connector, $loop)));
|
||||
}
|
||||
|
||||
private $http;
|
||||
|
||||
/**
|
||||
* [internal] Instantiate Sender
|
||||
*
|
||||
* @param HttpClient $http
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(HttpClient $http)
|
||||
{
|
||||
$this->http = $http;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @internal
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
// support HTTP/1.1 and HTTP/1.0 only, ensured by `Browser` already
|
||||
assert(\in_array($request->getProtocolVersion(), array('1.0', '1.1'), true));
|
||||
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size !== null && $size !== 0) {
|
||||
// automatically assign a "Content-Length" request header if the body size is known and non-empty
|
||||
$request = $request->withHeader('Content-Length', (string)$size);
|
||||
} elseif ($size === 0 && \in_array($request->getMethod(), array('POST', 'PUT', 'PATCH'))) {
|
||||
// only assign a "Content-Length: 0" request header if the body is expected for certain methods
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
} elseif ($body instanceof ReadableStreamInterface && $size !== 0 && $body->isReadable() && !$request->hasHeader('Content-Length')) {
|
||||
// use "Transfer-Encoding: chunked" when this is a streaming body and body size is unknown
|
||||
$request = $request->withHeader('Transfer-Encoding', 'chunked');
|
||||
} else {
|
||||
// do not use chunked encoding if size is known or if this is an empty request body
|
||||
$size = 0;
|
||||
}
|
||||
|
||||
// automatically add `Authorization: Basic …` request header if URL includes `user:pass@host`
|
||||
if ($request->getUri()->getUserInfo() !== '' && !$request->hasHeader('Authorization')) {
|
||||
$request = $request->withHeader('Authorization', 'Basic ' . \base64_encode($request->getUri()->getUserInfo()));
|
||||
}
|
||||
|
||||
$requestStream = $this->http->request($request);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($requestStream) {
|
||||
// close request stream if request is cancelled
|
||||
$reject(new \RuntimeException('Request cancelled'));
|
||||
$requestStream->close();
|
||||
});
|
||||
|
||||
$requestStream->on('error', function($error) use ($deferred) {
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
$requestStream->on('response', function (ResponseInterface $response) use ($deferred, $request) {
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
if ($body->isReadable()) {
|
||||
// length unknown => apply chunked transfer-encoding
|
||||
if ($size === null) {
|
||||
$body = new ChunkedEncoder($body);
|
||||
}
|
||||
|
||||
// pipe body into request stream
|
||||
// add dummy write to immediately start request even if body does not emit any data yet
|
||||
$body->pipe($requestStream);
|
||||
$requestStream->write('');
|
||||
|
||||
$body->on('close', $close = function () use ($deferred, $requestStream) {
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body closed unexpectedly'));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('error', function ($e) use ($deferred, $requestStream, $close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body reported an error', 0, $e));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('end', function () use ($close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
});
|
||||
} else {
|
||||
// stream is not readable => end request without body
|
||||
$requestStream->end();
|
||||
}
|
||||
} else {
|
||||
// body is fully buffered => write as one chunk
|
||||
$requestStream->end((string)$body);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user