Subir todo el proyecto incluyendo vendor y dependencias

This commit is contained in:
Admin
2026-01-16 20:33:13 -06:00
parent cf8ecfcf64
commit 0b3d76822d
2394 changed files with 2382358 additions and 40 deletions

96
vendor/react/cache/CHANGELOG.md vendored Executable file
View 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
View 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
View File

@@ -0,0 +1,367 @@
# Cache
[![CI status](https://github.com/reactphp/cache/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/cache/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/cache?color=blue&label=installs%20on%20Packagist)](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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,619 @@
# ChildProcess
[![CI status](https://github.com/reactphp/child-process/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/child-process/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/child-process?color=blue&label=installs%20on%20Packagist)](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
View 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
View 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
View File

@@ -0,0 +1,2 @@
/vendor
/composer.lock

29
vendor/react/datagram/.travis.yml vendored Executable file
View 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
View 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
View 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
View File

@@ -0,0 +1,70 @@
# Datagram
[![Build Status](https://travis-ci.org/reactphp/datagram.svg?branch=master)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,453 @@
# DNS
[![CI status](https://github.com/reactphp/dns/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/dns/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/dns?color=blue&label=installs%20on%20Packagist)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}
}

View 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;
}
}

View 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
View 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);
}
}

View 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);
}

View 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();
});
}
}

View 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
View 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
View 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();
}
}

View 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;
});
}
}

View 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');
});
}
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns\Query;
final class TimeoutException extends \Exception
{
}

View 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;
});
}
}

View 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();
}
}

View 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
View 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
View 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);
}
}

View 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
View 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
View 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
View File

@@ -0,0 +1,930 @@
# EventLoop
[![CI status](https://github.com/reactphp/event-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/event-loop/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/event-loop?color=blue&label=installs%20on%20Packagist)](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
View 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
View 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
View 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
View 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;
}
}

View 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
View 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
View 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
View 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
View 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();
}

View 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;
}
}

View 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;
}
}

View 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
View 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
View 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]);
}
}
}
}

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

57
vendor/react/http/composer.json vendored Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();
}
}
}

View 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();
}
}

View 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;
}

View 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
View 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;
}
}

View 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
View 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
View 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
View 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;
}
}

View 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
View 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
View 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
View 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();
}
}

View 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();
}
}

View 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
View 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