init
This commit is contained in:
2
vendor/react/cache/.gitignore
vendored
Executable file
2
vendor/react/cache/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
composer.lock
|
||||
vendor
|
||||
32
vendor/react/cache/.travis.yml
vendored
Executable file
32
vendor/react/cache/.travis.yml
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
# - hhvm # requires legacy phpunit & ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
- php: hhvm
|
||||
install: composer require phpunit/phpunit:^5 --dev --no-interaction
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- ./vendor/bin/phpunit --coverage-text
|
||||
74
vendor/react/cache/CHANGELOG.md
vendored
Executable file
74
vendor/react/cache/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,74 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
19
vendor/react/cache/LICENSE
vendored
Executable file
19
vendor/react/cache/LICENSE
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 Igor Wiedler, Chris Boden
|
||||
|
||||
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.
|
||||
366
vendor/react/cache/README.md
vendored
Executable file
366
vendor/react/cache/README.md
vendored
Executable file
@@ -0,0 +1,366 @@
|
||||
# Cache
|
||||
|
||||
[](https://travis-ci.org/reactphp/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.0
|
||||
```
|
||||
|
||||
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).
|
||||
19
vendor/react/cache/composer.json
vendored
Executable file
19
vendor/react/cache/composer.json
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "react/cache",
|
||||
"description": "Async, Promise-based cache interface for ReactPHP",
|
||||
"keywords": ["cache", "caching", "promise", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"react/promise": "~2.0|~1.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "React\\Cache\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "React\\Tests\\Cache\\": "tests/" }
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
}
|
||||
}
|
||||
20
vendor/react/cache/phpunit.xml.dist
vendored
Executable file
20
vendor/react/cache/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="React Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
181
vendor/react/cache/src/ArrayCache.php
vendored
Executable file
181
vendor/react/cache/src/ArrayCache.php
vendored
Executable file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace React\Cache;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class ArrayCache implements CacheInterface
|
||||
{
|
||||
private $limit;
|
||||
private $data = array();
|
||||
private $expires = array();
|
||||
private $supportsHighResolution;
|
||||
|
||||
/**
|
||||
* The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
|
||||
*
|
||||
* ```php
|
||||
* $cache = new ArrayCache();
|
||||
*
|
||||
* $cache->set('foo', 'bar');
|
||||
* ```
|
||||
*
|
||||
* Its constructor accepts an optional `?int $limit` parameter to limit the
|
||||
* maximum number of entries to store in the LRU cache. If you add more
|
||||
* entries to this instance, it will automatically take care of removing
|
||||
* the one that was least recently used (LRU).
|
||||
*
|
||||
* For example, this snippet will overwrite the first value and only store
|
||||
* the last two entries:
|
||||
*
|
||||
* ```php
|
||||
* $cache = new ArrayCache(2);
|
||||
*
|
||||
* $cache->set('foo', '1');
|
||||
* $cache->set('bar', '2');
|
||||
* $cache->set('baz', '3');
|
||||
* ```
|
||||
*
|
||||
* This cache implementation is known to rely on wall-clock time to schedule
|
||||
* future cache expiration times when using any version before PHP 7.3,
|
||||
* because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
|
||||
* While this does not affect many common use cases, this is an important
|
||||
* distinction for programs that rely on a high time precision or on systems
|
||||
* that are subject to discontinuous time adjustments (time jumps).
|
||||
* This means that if you store a cache item with a TTL of 30s on PHP < 7.3
|
||||
* and then adjust your system time forward by 20s, the cache item may
|
||||
* expire in 10s. See also [`set()`](#set) for more details.
|
||||
*
|
||||
* @param int|null $limit maximum number of entries to store in the LRU cache
|
||||
*/
|
||||
public function __construct($limit = null)
|
||||
{
|
||||
$this->limit = $limit;
|
||||
|
||||
// prefer high-resolution timer, available as of PHP 7.3+
|
||||
$this->supportsHighResolution = \function_exists('hrtime');
|
||||
}
|
||||
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
// delete key if it is already expired => below will detect this as a cache miss
|
||||
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($key, $this->data)) {
|
||||
return Promise\resolve($default);
|
||||
}
|
||||
|
||||
// remove and append to end of array to keep track of LRU info
|
||||
$value = $this->data[$key];
|
||||
unset($this->data[$key]);
|
||||
$this->data[$key] = $value;
|
||||
|
||||
return Promise\resolve($value);
|
||||
}
|
||||
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
// unset before setting to ensure this entry will be added to end of array (LRU info)
|
||||
unset($this->data[$key]);
|
||||
$this->data[$key] = $value;
|
||||
|
||||
// sort expiration times if TTL is given (first will expire first)
|
||||
unset($this->expires[$key]);
|
||||
if ($ttl !== null) {
|
||||
$this->expires[$key] = $this->now() + $ttl;
|
||||
\asort($this->expires);
|
||||
}
|
||||
|
||||
// ensure size limit is not exceeded or remove first entry from array
|
||||
if ($this->limit !== null && \count($this->data) > $this->limit) {
|
||||
// first try to check if there's any expired entry
|
||||
// expiration times are sorted, so we can simply look at the first one
|
||||
\reset($this->expires);
|
||||
$key = \key($this->expires);
|
||||
|
||||
// check to see if the first in the list of expiring keys is already expired
|
||||
// if the first key is not expired, we have to overwrite by using LRU info
|
||||
if ($key === null || $this->now() - $this->expires[$key] < 0) {
|
||||
\reset($this->data);
|
||||
$key = \key($this->data);
|
||||
}
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function delete($key)
|
||||
{
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function getMultiple(array $keys, $default = null)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$values[$key] = $this->get($key, $default);
|
||||
}
|
||||
|
||||
return Promise\all($values);
|
||||
}
|
||||
|
||||
public function setMultiple(array $values, $ttl = null)
|
||||
{
|
||||
foreach ($values as $key => $value) {
|
||||
$this->set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function deleteMultiple(array $keys)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->data = array();
|
||||
$this->expires = array();
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
// delete key if it is already expired
|
||||
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
|
||||
unset($this->data[$key], $this->expires[$key]);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($key, $this->data)) {
|
||||
return Promise\resolve(false);
|
||||
}
|
||||
|
||||
// remove and append to end of array to keep track of LRU info
|
||||
$value = $this->data[$key];
|
||||
unset($this->data[$key]);
|
||||
$this->data[$key] = $value;
|
||||
|
||||
return Promise\resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
private function now()
|
||||
{
|
||||
return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
|
||||
}
|
||||
}
|
||||
194
vendor/react/cache/src/CacheInterface.php
vendored
Executable file
194
vendor/react/cache/src/CacheInterface.php
vendored
Executable file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace React\Cache;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
interface CacheInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves an item from the cache.
|
||||
*
|
||||
* This method will resolve with the cached value on success or with the
|
||||
* given `$default` value when no item can be found or when an error occurs.
|
||||
* Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
* considered a cache miss.
|
||||
*
|
||||
* ```php
|
||||
* $cache
|
||||
* ->get('foo')
|
||||
* ->then('var_dump');
|
||||
* ```
|
||||
*
|
||||
* This example fetches the value of the key `foo` and passes it to the
|
||||
* `var_dump` function. You can use any of the composition provided by
|
||||
* [promises](https://github.com/reactphp/promise).
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default Default value to return for cache miss or null if not given.
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
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 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 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);
|
||||
}
|
||||
322
vendor/react/cache/tests/ArrayCacheTest.php
vendored
Executable file
322
vendor/react/cache/tests/ArrayCacheTest.php
vendored
Executable file
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Cache;
|
||||
|
||||
use React\Cache\ArrayCache;
|
||||
|
||||
class ArrayCacheTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ArrayCache
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function getShouldResolvePromiseWithNullForNonExistentKey()
|
||||
{
|
||||
$success = $this->createCallableMock();
|
||||
$success
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with(null);
|
||||
|
||||
$this->cache
|
||||
->get('foo')
|
||||
->then(
|
||||
$success,
|
||||
$this->expectCallableNever()
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function setShouldSetKey()
|
||||
{
|
||||
$setPromise = $this->cache
|
||||
->set('foo', 'bar');
|
||||
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->identicalTo(true));
|
||||
|
||||
$setPromise->then($mock);
|
||||
|
||||
$success = $this->createCallableMock();
|
||||
$success
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with('bar');
|
||||
|
||||
$this->cache
|
||||
->get('foo')
|
||||
->then($success);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function deleteShouldDeleteKey()
|
||||
{
|
||||
$this->cache
|
||||
->set('foo', 'bar');
|
||||
|
||||
$deletePromise = $this->cache
|
||||
->delete('foo');
|
||||
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->identicalTo(true));
|
||||
|
||||
$deletePromise->then($mock);
|
||||
|
||||
$this->cache
|
||||
->get('foo')
|
||||
->then(
|
||||
$this->expectCallableOnce(),
|
||||
$this->expectCallableNever()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetWillResolveWithNullForCacheMiss()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
|
||||
}
|
||||
|
||||
public function testGetWillResolveWithDefaultValueForCacheMiss()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
|
||||
$this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith('bar'));
|
||||
}
|
||||
|
||||
public function testGetWillResolveWithExplicitNullValueForCacheHit()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
|
||||
$this->cache->set('foo', null);
|
||||
$this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith(null));
|
||||
}
|
||||
|
||||
public function testLimitSizeToZeroDoesNotStoreAnyData()
|
||||
{
|
||||
$this->cache = new ArrayCache(0);
|
||||
|
||||
$this->cache->set('foo', 'bar');
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
|
||||
}
|
||||
|
||||
public function testLimitSizeToOneWillOnlyReturnLastWrite()
|
||||
{
|
||||
$this->cache = new ArrayCache(1);
|
||||
|
||||
$this->cache->set('foo', '1');
|
||||
$this->cache->set('bar', '2');
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
|
||||
$this->cache->get('bar')->then($this->expectCallableOnceWith('2'));
|
||||
}
|
||||
|
||||
public function testOverwriteWithLimitedSizeWillUpdateLRUInfo()
|
||||
{
|
||||
$this->cache = new ArrayCache(2);
|
||||
|
||||
$this->cache->set('foo', '1');
|
||||
$this->cache->set('bar', '2');
|
||||
$this->cache->set('foo', '3');
|
||||
$this->cache->set('baz', '4');
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith('3'));
|
||||
$this->cache->get('bar')->then($this->expectCallableOnceWith(null));
|
||||
$this->cache->get('baz')->then($this->expectCallableOnceWith('4'));
|
||||
}
|
||||
|
||||
public function testGetWithLimitedSizeWillUpdateLRUInfo()
|
||||
{
|
||||
$this->cache = new ArrayCache(2);
|
||||
|
||||
$this->cache->set('foo', '1');
|
||||
$this->cache->set('bar', '2');
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
|
||||
$this->cache->set('baz', '3');
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
|
||||
$this->cache->get('bar')->then($this->expectCallableOnceWith(null));
|
||||
$this->cache->get('baz')->then($this->expectCallableOnceWith('3'));
|
||||
}
|
||||
|
||||
public function testGetWillResolveWithValueIfItemIsNotExpired()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
|
||||
$this->cache->set('foo', '1', 10);
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
|
||||
}
|
||||
|
||||
public function testGetWillResolveWithDefaultIfItemIsExpired()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
|
||||
$this->cache->set('foo', '1', 0);
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
|
||||
}
|
||||
|
||||
public function testSetWillOverwritOldestItemIfNoEntryIsExpired()
|
||||
{
|
||||
$this->cache = new ArrayCache(2);
|
||||
|
||||
$this->cache->set('foo', '1', 10);
|
||||
$this->cache->set('bar', '2', 20);
|
||||
$this->cache->set('baz', '3', 30);
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
|
||||
}
|
||||
|
||||
public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired()
|
||||
{
|
||||
$this->cache = new ArrayCache(2);
|
||||
|
||||
$this->cache->set('foo', '1', 10);
|
||||
$this->cache->set('bar', '2', 0);
|
||||
$this->cache->set('baz', '3', 30);
|
||||
|
||||
$this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
|
||||
$this->cache->get('bar')->then($this->expectCallableOnceWith(null));
|
||||
}
|
||||
|
||||
public function testGetMultiple()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->set('foo', '1');
|
||||
|
||||
$this->cache
|
||||
->getMultiple(array('foo', 'bar'), 'baz')
|
||||
->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => 'baz')));
|
||||
}
|
||||
|
||||
public function testSetMultiple()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->setMultiple(array('foo' => '1', 'bar' => '2'), 10);
|
||||
|
||||
$this->cache
|
||||
->getMultiple(array('foo', 'bar'))
|
||||
->then($this->expectCallableOnceWith(array('foo' => '1', 'bar' => '2')));
|
||||
}
|
||||
|
||||
public function testDeleteMultiple()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
|
||||
|
||||
$this->cache
|
||||
->deleteMultiple(array('foo', 'baz'))
|
||||
->then($this->expectCallableOnceWith(true));
|
||||
|
||||
$this->cache
|
||||
->has('foo')
|
||||
->then($this->expectCallableOnceWith(false));
|
||||
|
||||
$this->cache
|
||||
->has('bar')
|
||||
->then($this->expectCallableOnceWith(true));
|
||||
|
||||
$this->cache
|
||||
->has('baz')
|
||||
->then($this->expectCallableOnceWith(false));
|
||||
}
|
||||
|
||||
public function testClearShouldClearCache()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->setMultiple(array('foo' => 1, 'bar' => 2, 'baz' => 3));
|
||||
|
||||
$this->cache->clear();
|
||||
|
||||
$this->cache
|
||||
->has('foo')
|
||||
->then($this->expectCallableOnceWith(false));
|
||||
|
||||
$this->cache
|
||||
->has('bar')
|
||||
->then($this->expectCallableOnceWith(false));
|
||||
|
||||
$this->cache
|
||||
->has('baz')
|
||||
->then($this->expectCallableOnceWith(false));
|
||||
}
|
||||
|
||||
public function hasShouldResolvePromiseForExistingKey()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->set('foo', 'bar');
|
||||
|
||||
$this->cache
|
||||
->has('foo')
|
||||
->then($this->expectCallableOnceWith(true));
|
||||
}
|
||||
|
||||
public function hasShouldResolvePromiseForNonExistentKey()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->set('foo', 'bar');
|
||||
|
||||
$this->cache
|
||||
->has('foo')
|
||||
->then($this->expectCallableOnceWith(false));
|
||||
}
|
||||
|
||||
public function testHasWillResolveIfItemIsNotExpired()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->set('foo', '1', 10);
|
||||
|
||||
$this->cache
|
||||
->has('foo')
|
||||
->then($this->expectCallableOnceWith(true));
|
||||
}
|
||||
|
||||
public function testHasWillResolveIfItemIsExpired()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->set('foo', '1', 0);
|
||||
|
||||
$this->cache
|
||||
->has('foo')
|
||||
->then($this->expectCallableOnceWith(false));
|
||||
}
|
||||
|
||||
public function testHasWillResolveForExplicitNullValue()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
$this->cache->set('foo', null);
|
||||
|
||||
$this->cache
|
||||
->has('foo')
|
||||
->then($this->expectCallableOnceWith(true));
|
||||
}
|
||||
|
||||
public function testHasWithLimitedSizeWillUpdateLRUInfo()
|
||||
{
|
||||
$this->cache = new ArrayCache(2);
|
||||
|
||||
$this->cache->set('foo', 1);
|
||||
$this->cache->set('bar', 2);
|
||||
$this->cache->has('foo')->then($this->expectCallableOnceWith(true));
|
||||
$this->cache->set('baz', 3);
|
||||
|
||||
$this->cache->has('foo')->then($this->expectCallableOnceWith(1));
|
||||
$this->cache->has('bar')->then($this->expectCallableOnceWith(false));
|
||||
$this->cache->has('baz')->then($this->expectCallableOnceWith(3));
|
||||
}
|
||||
}
|
||||
10
vendor/react/cache/tests/CallableStub.php
vendored
Executable file
10
vendor/react/cache/tests/CallableStub.php
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Cache;
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
54
vendor/react/cache/tests/TestCase.php
vendored
Executable file
54
vendor/react/cache/tests/TestCase.php
vendored
Executable file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Cache;
|
||||
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
class TestCase extends BaseTestCase
|
||||
{
|
||||
protected function expectCallableExactly($amount)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->exactly($amount))
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWith($param)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($param);
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function createCallableMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Tests\Cache\CallableStub')->getMock();
|
||||
}
|
||||
}
|
||||
2
vendor/react/dns/.gitignore
vendored
Executable file
2
vendor/react/dns/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
composer.lock
|
||||
vendor
|
||||
32
vendor/react/dns/.travis.yml
vendored
Executable file
32
vendor/react/dns/.travis.yml
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
# - 5.3 # requires old distro, see below
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
# - hhvm # requires legacy phpunit & ignore errors, see below
|
||||
|
||||
# lock distro so new future defaults will not break the build
|
||||
dist: trusty
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
- php: hhvm
|
||||
install: composer require phpunit/phpunit:^5 --dev --no-interaction
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- composer install --no-interaction
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-text
|
||||
261
vendor/react/dns/CHANGELOG.md
vendored
Executable file
261
vendor/react/dns/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,261 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
19
vendor/react/dns/LICENSE
vendored
Executable file
19
vendor/react/dns/LICENSE
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 Igor Wiedler, Chris Boden
|
||||
|
||||
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.
|
||||
344
vendor/react/dns/README.md
vendored
Executable file
344
vendor/react/dns/README.md
vendored
Executable file
@@ -0,0 +1,344 @@
|
||||
# Dns
|
||||
|
||||
[](https://travis-ci.org/reactphp/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)
|
||||
* [Resolver](#resolver)
|
||||
* [resolve()](#resolve)
|
||||
* [resolveAll()](#resolveall)
|
||||
* [Advanced usage](#advanced-usage)
|
||||
* [UdpTransportExecutor](#udptransportexecutor)
|
||||
* [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
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->create($server, $loop);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
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
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached($server, $loop);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
|
||||
...
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
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();
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached('8.8.8.8', $loop, $cache);
|
||||
```
|
||||
|
||||
See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
|
||||
|
||||
## Resolver
|
||||
|
||||
### resolve()
|
||||
|
||||
The `resolve(string $domain): PromiseInterface<string,Exception>` 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,Exception>` 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
|
||||
$loop = Factory::create();
|
||||
$executor = new UdpTransportExecutor($loop);
|
||||
|
||||
$executor->query(
|
||||
'8.8.8.8:53',
|
||||
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');
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
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($loop),
|
||||
3.0,
|
||||
$loop
|
||||
);
|
||||
```
|
||||
|
||||
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($loop),
|
||||
3.0,
|
||||
$loop
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
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($loop),
|
||||
3.0,
|
||||
$loop
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
> 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.
|
||||
|
||||
### 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($loop);
|
||||
$executor = new HostsFileExecutor($hosts, $executor);
|
||||
|
||||
$executor->query(
|
||||
'8.8.8.8:53',
|
||||
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 will install the latest supported version:
|
||||
|
||||
```bash
|
||||
$ composer require react/dns:^0.4.19
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
$ php 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
|
||||
24
vendor/react/dns/composer.json
vendored
Executable file
24
vendor/react/dns/composer.json
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "react/dns",
|
||||
"description": "Async DNS resolver for ReactPHP",
|
||||
"keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"react/cache": "^1.0 || ^0.6 || ^0.5",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"react/promise": "^2.1 || ^1.2.1",
|
||||
"react/promise-timer": "^1.2",
|
||||
"react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.2",
|
||||
"phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "React\\Dns\\": "src" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "React\\Tests\\Dns\\": "tests" }
|
||||
}
|
||||
}
|
||||
22
vendor/react/dns/examples/01-one.php
vendored
Executable file
22
vendor/react/dns/examples/01-one.php
vendored
Executable file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Resolver\Factory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
|
||||
|
||||
$resolver->resolve($name)->then(function ($ip) use ($name) {
|
||||
echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
27
vendor/react/dns/examples/02-concurrent.php
vendored
Executable file
27
vendor/react/dns/examples/02-concurrent.php
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Resolver\Factory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
$names = array_slice($argv, 1);
|
||||
if (!$names) {
|
||||
$names = array('google.com', 'www.google.com', 'gmail.com');
|
||||
}
|
||||
|
||||
foreach ($names as $name) {
|
||||
$resolver->resolve($name)->then(function ($ip) use ($name) {
|
||||
echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
|
||||
}, 'printf');
|
||||
}
|
||||
|
||||
$loop->run();
|
||||
40
vendor/react/dns/examples/03-cached.php
vendored
Executable file
40
vendor/react/dns/examples/03-cached.php
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Resolver\Factory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->createCached($server, $loop);
|
||||
|
||||
$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
|
||||
|
||||
$resolver->resolve($name)->then(function ($ip) use ($name) {
|
||||
echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
|
||||
}, 'printf');
|
||||
|
||||
$loop->addTimer(1.0, function() use ($name, $resolver) {
|
||||
$resolver->resolve($name)->then(function ($ip) use ($name) {
|
||||
echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
|
||||
}, 'printf');
|
||||
});
|
||||
|
||||
$loop->addTimer(2.0, function() use ($name, $resolver) {
|
||||
$resolver->resolve($name)->then(function ($ip) use ($name) {
|
||||
echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
|
||||
}, 'printf');
|
||||
});
|
||||
|
||||
$loop->addTimer(3.0, function() use ($name, $resolver) {
|
||||
$resolver->resolve($name)->then(function ($ip) use ($name) {
|
||||
echo 'IP for ' . $name . ': ' . $ip . PHP_EOL;
|
||||
}, 'printf');
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
31
vendor/react/dns/examples/11-all-ips.php
vendored
Executable file
31
vendor/react/dns/examples/11-all-ips.php
vendored
Executable file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Resolver\Factory;
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
|
||||
|
||||
$resolver->resolveAll($name, Message::TYPE_A)->then(function (array $ips) use ($name) {
|
||||
echo 'IPv4 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
|
||||
}, function (Exception $e) use ($name) {
|
||||
echo 'No IPv4 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
|
||||
$resolver->resolveAll($name, Message::TYPE_AAAA)->then(function (array $ips) use ($name) {
|
||||
echo 'IPv6 addresses for ' . $name . ': ' . implode(', ', $ips) . PHP_EOL;
|
||||
}, function (Exception $e) use ($name) {
|
||||
echo 'No IPv6 addresses for ' . $name . ': ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
25
vendor/react/dns/examples/12-all-types.php
vendored
Executable file
25
vendor/react/dns/examples/12-all-types.php
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Resolver\Factory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
$name = isset($argv[1]) ? $argv[1] : 'google.com';
|
||||
$type = constant('React\Dns\Model\Message::TYPE_' . (isset($argv[2]) ? $argv[2] : 'TXT'));
|
||||
|
||||
$resolver->resolveAll($name, $type)->then(function (array $values) {
|
||||
var_dump($values);
|
||||
}, function (Exception $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
35
vendor/react/dns/examples/13-reverse-dns.php
vendored
Executable file
35
vendor/react/dns/examples/13-reverse-dns.php
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Resolver\Factory;
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
$ip = isset($argv[1]) ? $argv[1] : '8.8.8.8';
|
||||
|
||||
if (@inet_pton($ip) === false) {
|
||||
exit('Error: Given argument is not a valid IP' . PHP_EOL);
|
||||
}
|
||||
|
||||
if (strpos($ip, ':') === false) {
|
||||
$name = inet_ntop(strrev(inet_pton($ip))) . '.in-addr.arpa';
|
||||
} else {
|
||||
$name = wordwrap(strrev(bin2hex(inet_pton($ip))), 1, '.', true) . '.ip6.arpa';
|
||||
}
|
||||
|
||||
$resolver->resolveAll($name, Message::TYPE_PTR)->then(function (array $names) {
|
||||
var_dump($names);
|
||||
}, function (Exception $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
29
vendor/react/dns/examples/91-query-a-and-aaaa.php
vendored
Executable file
29
vendor/react/dns/examples/91-query-a-and-aaaa.php
vendored
Executable file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Query\UdpTransportExecutor;
|
||||
use React\EventLoop\Factory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = Factory::create();
|
||||
$executor = new UdpTransportExecutor($loop);
|
||||
|
||||
$name = isset($argv[1]) ? $argv[1] : 'www.google.com';
|
||||
|
||||
$ipv4Query = new Query($name, Message::TYPE_A, Message::CLASS_IN);
|
||||
$ipv6Query = new Query($name, Message::TYPE_AAAA, Message::CLASS_IN);
|
||||
|
||||
$executor->query('8.8.8.8:53', $ipv4Query)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv4: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
$executor->query('8.8.8.8:53', $ipv6Query)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
71
vendor/react/dns/examples/92-query-any.php
vendored
Executable file
71
vendor/react/dns/examples/92-query-any.php
vendored
Executable file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Query\UdpTransportExecutor;
|
||||
use React\EventLoop\Factory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$loop = Factory::create();
|
||||
$executor = new UdpTransportExecutor($loop);
|
||||
|
||||
$name = isset($argv[1]) ? $argv[1] : 'google.com';
|
||||
|
||||
$any = new Query($name, Message::TYPE_ANY, Message::CLASS_IN);
|
||||
|
||||
$executor->query('8.8.8.8:53', $any)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
/* @var $answer Record */
|
||||
|
||||
$data = $answer->data;
|
||||
|
||||
switch ($answer->type) {
|
||||
case Message::TYPE_A:
|
||||
$type = 'A';
|
||||
break;
|
||||
case Message::TYPE_AAAA:
|
||||
$type = 'AAAA';
|
||||
break;
|
||||
case Message::TYPE_NS:
|
||||
$type = 'NS';
|
||||
break;
|
||||
case Message::TYPE_PTR:
|
||||
$type = 'PTR';
|
||||
break;
|
||||
case Message::TYPE_CNAME:
|
||||
$type = 'CNAME';
|
||||
break;
|
||||
case Message::TYPE_TXT:
|
||||
// TXT records can contain a list of (binary) strings for each record.
|
||||
// here, we assume this is printable ASCII and simply concatenate output
|
||||
$type = 'TXT';
|
||||
$data = implode('', $data);
|
||||
break;
|
||||
case Message::TYPE_MX:
|
||||
// MX records contain "priority" and "target", only dump its values here
|
||||
$type = 'MX';
|
||||
$data = implode(' ', $data);
|
||||
break;
|
||||
case Message::TYPE_SRV:
|
||||
// SRV records contains priority, weight, port and target, dump structure here
|
||||
$type = 'SRV';
|
||||
$data = json_encode($data);
|
||||
break;
|
||||
case Message::TYPE_SOA:
|
||||
// SOA records contain structured data, dump structure here
|
||||
$type = 'SOA';
|
||||
$data = json_encode($data);
|
||||
break;
|
||||
default:
|
||||
// unknown type uses HEX format
|
||||
$type = 'Type ' . $answer->type;
|
||||
$data = wordwrap(strtoupper(bin2hex($data)), 2, ' ', true);
|
||||
}
|
||||
|
||||
echo $type . ': ' . $data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
|
||||
$loop->run();
|
||||
24
vendor/react/dns/phpunit.xml.dist
vendored
Executable file
24
vendor/react/dns/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="React Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
7
vendor/react/dns/src/BadServerException.php
vendored
Executable file
7
vendor/react/dns/src/BadServerException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
class BadServerException extends \Exception
|
||||
{
|
||||
}
|
||||
127
vendor/react/dns/src/Config/Config.php
vendored
Executable file
127
vendor/react/dns/src/Config/Config.php
vendored
Executable file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
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 . '"');
|
||||
}
|
||||
|
||||
preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
|
||||
|
||||
$config = new self();
|
||||
$config->nameservers = $matches[1];
|
||||
|
||||
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();
|
||||
}
|
||||
73
vendor/react/dns/src/Config/FilesystemFactory.php
vendored
Executable file
73
vendor/react/dns/src/Config/FilesystemFactory.php
vendored
Executable file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use React\Stream\Stream;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see Config see Config class instead.
|
||||
*/
|
||||
class FilesystemFactory
|
||||
{
|
||||
private $loop;
|
||||
|
||||
public function __construct(LoopInterface $loop)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function create($filename)
|
||||
{
|
||||
return $this
|
||||
->loadEtcResolvConf($filename)
|
||||
->then(array($this, 'parseEtcResolvConf'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $contents
|
||||
* @return Promise
|
||||
* @deprecated see Config instead
|
||||
*/
|
||||
public function parseEtcResolvConf($contents)
|
||||
{
|
||||
return Promise\resolve(Config::loadResolvConfBlocking(
|
||||
'data://text/plain;base64,' . base64_encode($contents)
|
||||
));
|
||||
}
|
||||
|
||||
public function loadEtcResolvConf($filename)
|
||||
{
|
||||
if (!file_exists($filename)) {
|
||||
return Promise\reject(new \InvalidArgumentException("The filename for /etc/resolv.conf given does not exist: $filename"));
|
||||
}
|
||||
|
||||
try {
|
||||
$deferred = new Deferred();
|
||||
|
||||
$fd = fopen($filename, 'r');
|
||||
stream_set_blocking($fd, 0);
|
||||
|
||||
$contents = '';
|
||||
|
||||
$stream = class_exists('React\Stream\ReadableResourceStream') ? new ReadableResourceStream($fd, $this->loop) : new Stream($fd, $this->loop);
|
||||
$stream->on('data', function ($data) use (&$contents) {
|
||||
$contents .= $data;
|
||||
});
|
||||
$stream->on('end', function () use (&$contents, $deferred) {
|
||||
$deferred->resolve($contents);
|
||||
});
|
||||
$stream->on('error', function ($error) use ($deferred) {
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
} catch (\Exception $e) {
|
||||
return Promise\reject($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
151
vendor/react/dns/src/Config/HostsFile.php
vendored
Executable file
151
vendor/react/dns/src/Config/HostsFile.php
vendored
Executable file
@@ -0,0 +1,151 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, null, PREG_SPLIT_NO_EMPTY);
|
||||
$addr = 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;
|
||||
}
|
||||
}
|
||||
59
vendor/react/dns/src/Model/HeaderBag.php
vendored
Executable file
59
vendor/react/dns/src/Model/HeaderBag.php
vendored
Executable file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
class HeaderBag
|
||||
{
|
||||
public $attributes = array(
|
||||
'qdCount' => 0,
|
||||
'anCount' => 0,
|
||||
'nsCount' => 0,
|
||||
'arCount' => 0,
|
||||
'qr' => 0,
|
||||
'opcode' => Message::OPCODE_QUERY,
|
||||
'aa' => 0,
|
||||
'tc' => 0,
|
||||
'rd' => 0,
|
||||
'ra' => 0,
|
||||
'z' => 0,
|
||||
'rcode' => Message::RCODE_OK,
|
||||
);
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
*/
|
||||
public $data = '';
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
|
||||
}
|
||||
|
||||
public function set($name, $value)
|
||||
{
|
||||
$this->attributes[$name] = $value;
|
||||
}
|
||||
|
||||
public function isQuery()
|
||||
{
|
||||
return 0 === $this->attributes['qr'];
|
||||
}
|
||||
|
||||
public function isResponse()
|
||||
{
|
||||
return 1 === $this->attributes['qr'];
|
||||
}
|
||||
|
||||
public function isTruncated()
|
||||
{
|
||||
return 1 === $this->attributes['tc'];
|
||||
}
|
||||
|
||||
public function populateCounts(Message $message)
|
||||
{
|
||||
$this->attributes['qdCount'] = count($message->questions);
|
||||
$this->attributes['anCount'] = count($message->answers);
|
||||
$this->attributes['nsCount'] = count($message->authority);
|
||||
$this->attributes['arCount'] = count($message->additional);
|
||||
}
|
||||
}
|
||||
188
vendor/react/dns/src/Model/Message.php
vendored
Executable file
188
vendor/react/dns/src/Model/Message.php
vendored
Executable file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
use React\Dns\Query\Query;
|
||||
|
||||
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_ANY = 255;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Creates a new request message for the given query
|
||||
*
|
||||
* @param Query $query
|
||||
* @return self
|
||||
*/
|
||||
public static function createRequestForQuery(Query $query)
|
||||
{
|
||||
$request = new Message();
|
||||
$request->header->set('id', self::generateId());
|
||||
$request->header->set('rd', 1);
|
||||
$request->questions[] = (array) $query;
|
||||
$request->prepare();
|
||||
|
||||
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->header->set('id', self::generateId());
|
||||
$response->header->set('qr', 1);
|
||||
$response->header->set('opcode', Message::OPCODE_QUERY);
|
||||
$response->header->set('rd', 1);
|
||||
$response->header->set('rcode', Message::RCODE_OK);
|
||||
|
||||
$response->questions[] = (array) $query;
|
||||
|
||||
foreach ($answers as $record) {
|
||||
$response->answers[] = $record;
|
||||
}
|
||||
|
||||
$response->prepare();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var HeaderBag
|
||||
*/
|
||||
public $header;
|
||||
|
||||
/**
|
||||
* This should be an array of Query objects. For BC reasons, this currently
|
||||
* references a nested array with a structure that results from casting the
|
||||
* Query objects to an array:
|
||||
*
|
||||
* ```php
|
||||
* $questions = array(
|
||||
* array(
|
||||
* 'name' => 'reactphp.org',
|
||||
* 'type' => Message::TYPE_A,
|
||||
* 'class' => Message::CLASS_IN
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @var array
|
||||
* @see Query
|
||||
*/
|
||||
public $questions = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $answers = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $authority = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $additional = array();
|
||||
|
||||
/**
|
||||
* @deprecated still used internally for BC reasons, should not be used externally.
|
||||
*/
|
||||
public $data = '';
|
||||
|
||||
/**
|
||||
* @deprecated still used internally for BC reasons, should not be used externally.
|
||||
*/
|
||||
public $consumed = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->header = new HeaderBag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 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.
|
||||
*
|
||||
* @return int
|
||||
* @see self::generateId()
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->header->get('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response code (RCODE)
|
||||
*
|
||||
* @return int see self::RCODE_* constants
|
||||
*/
|
||||
public function getResponseCode()
|
||||
{
|
||||
return $this->header->get('rcode');
|
||||
}
|
||||
|
||||
public function prepare()
|
||||
{
|
||||
$this->header->populateCounts($this);
|
||||
}
|
||||
}
|
||||
105
vendor/react/dns/src/Model/Record.php
vendored
Executable file
105
vendor/react/dns/src/Model/Record.php
vendored
Executable file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
class Record
|
||||
{
|
||||
/**
|
||||
* @var string hostname without trailing dot, for example "reactphp.org"
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int see Message::TYPE_* constants (UINT16)
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var int see Message::CLASS_IN constant (UINT16)
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
|
||||
* @link https://tools.ietf.org/html/rfc2181#section-8
|
||||
*/
|
||||
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.
|
||||
* - 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}`.
|
||||
* - 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 = 0, $data = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
$this->ttl = $ttl;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
163
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Executable file
163
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Executable file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\HeaderBag;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
|
||||
class BinaryDumper
|
||||
{
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return string
|
||||
*/
|
||||
public function toBinary(Message $message)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= $this->headerToBinary($message->header);
|
||||
$data .= $this->questionToBinary($message->questions);
|
||||
$data .= $this->recordsToBinary($message->answers);
|
||||
$data .= $this->recordsToBinary($message->authority);
|
||||
$data .= $this->recordsToBinary($message->additional);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HeaderBag $header
|
||||
* @return string
|
||||
*/
|
||||
private function headerToBinary(HeaderBag $header)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= pack('n', $header->get('id'));
|
||||
|
||||
$flags = 0x00;
|
||||
$flags = ($flags << 1) | $header->get('qr');
|
||||
$flags = ($flags << 4) | $header->get('opcode');
|
||||
$flags = ($flags << 1) | $header->get('aa');
|
||||
$flags = ($flags << 1) | $header->get('tc');
|
||||
$flags = ($flags << 1) | $header->get('rd');
|
||||
$flags = ($flags << 1) | $header->get('ra');
|
||||
$flags = ($flags << 3) | $header->get('z');
|
||||
$flags = ($flags << 4) | $header->get('rcode');
|
||||
|
||||
$data .= pack('n', $flags);
|
||||
|
||||
$data .= pack('n', $header->get('qdCount'));
|
||||
$data .= pack('n', $header->get('anCount'));
|
||||
$data .= pack('n', $header->get('nsCount'));
|
||||
$data .= pack('n', $header->get('arCount'));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $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:
|
||||
$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;
|
||||
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";
|
||||
}
|
||||
|
||||
return $this->textsToBinary(\explode('.', $host . '.'));
|
||||
}
|
||||
}
|
||||
395
vendor/react/dns/src/Protocol/Parser.php
vendored
Executable file
395
vendor/react/dns/src/Protocol/Parser.php
vendored
Executable file
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* DNS protocol parser
|
||||
*
|
||||
* Obsolete and uncommon types and classes are not implemented.
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
/**
|
||||
* Parses the given raw binary message into a Message object
|
||||
*
|
||||
* @param string $data
|
||||
* @throws InvalidArgumentException
|
||||
* @return Message
|
||||
*/
|
||||
public function parseMessage($data)
|
||||
{
|
||||
$message = new Message();
|
||||
if ($this->parse($data, $message) !== $message) {
|
||||
throw new InvalidArgumentException('Unable to parse binary message');
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function parseChunk($data, Message $message)
|
||||
{
|
||||
return $this->parse($data, $message);
|
||||
}
|
||||
|
||||
private function parse($data, Message $message)
|
||||
{
|
||||
$message->data .= $data;
|
||||
|
||||
if (!$message->header->get('id')) {
|
||||
if (!$this->parseHeader($message)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($message->header->get('qdCount') != count($message->questions)) {
|
||||
if (!$this->parseQuestion($message)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all answer records
|
||||
for ($i = $message->header->get('anCount'); $i > 0; --$i) {
|
||||
$record = $this->parseRecord($message);
|
||||
if ($record === null) {
|
||||
return;
|
||||
} else {
|
||||
$message->answers[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all authority records
|
||||
for ($i = $message->header->get('nsCount'); $i > 0; --$i) {
|
||||
$record = $this->parseRecord($message);
|
||||
if ($record === null) {
|
||||
return;
|
||||
} else {
|
||||
$message->authority[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all additional records
|
||||
for ($i = $message->header->get('arCount'); $i > 0; --$i) {
|
||||
$record = $this->parseRecord($message);
|
||||
if ($record === null) {
|
||||
return;
|
||||
} else {
|
||||
$message->additional[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function parseHeader(Message $message)
|
||||
{
|
||||
if (!isset($message->data[12 - 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$header = substr($message->data, 0, 12);
|
||||
$message->consumed += 12;
|
||||
|
||||
list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', $header));
|
||||
|
||||
$rcode = $fields & bindec('1111');
|
||||
$z = ($fields >> 4) & bindec('111');
|
||||
$ra = ($fields >> 7) & 1;
|
||||
$rd = ($fields >> 8) & 1;
|
||||
$tc = ($fields >> 9) & 1;
|
||||
$aa = ($fields >> 10) & 1;
|
||||
$opcode = ($fields >> 11) & bindec('1111');
|
||||
$qr = ($fields >> 15) & 1;
|
||||
|
||||
$vars = compact('id', 'qdCount', 'anCount', 'nsCount', 'arCount',
|
||||
'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', 'rcode');
|
||||
|
||||
foreach ($vars as $name => $value) {
|
||||
$message->header->set($name, $value);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function parseQuestion(Message $message)
|
||||
{
|
||||
$consumed = $message->consumed;
|
||||
|
||||
list($labels, $consumed) = $this->readLabels($message->data, $consumed);
|
||||
|
||||
if ($labels === null || !isset($message->data[$consumed + 4 - 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
$message->consumed = $consumed;
|
||||
|
||||
$message->questions[] = array(
|
||||
'name' => implode('.', $labels),
|
||||
'type' => $type,
|
||||
'class' => $class,
|
||||
);
|
||||
|
||||
if ($message->header->get('qdCount') != count($message->questions)) {
|
||||
return $this->parseQuestion($message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* recursively parse all answers from the message data into message answer records
|
||||
*
|
||||
* @param Message $message
|
||||
* @return ?Message returns the updated message on success or null if the data is invalid/incomplete
|
||||
* @deprecated unused, exists for BC only
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function parseAnswer(Message $message)
|
||||
{
|
||||
$record = $this->parseRecord($message);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$message->answers[] = $record;
|
||||
|
||||
if ($message->header->get('anCount') != count($message->answers)) {
|
||||
return $this->parseAnswer($message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return ?Record returns parsed Record on success or null if data is invalid/incomplete
|
||||
*/
|
||||
private function parseRecord(Message $message)
|
||||
{
|
||||
$consumed = $message->consumed;
|
||||
|
||||
list($name, $consumed) = $this->readDomain($message->data, $consumed);
|
||||
|
||||
if ($name === null || !isset($message->data[$consumed + 10 - 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
list($ttl) = array_values(unpack('N', substr($message->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($message->data, $consumed, 2)));
|
||||
$consumed += 2;
|
||||
|
||||
if (!isset($message->data[$consumed + $rdLength - 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rdata = null;
|
||||
$expected = $consumed + $rdLength;
|
||||
|
||||
if (Message::TYPE_A === $type) {
|
||||
if ($rdLength === 4) {
|
||||
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_AAAA === $type) {
|
||||
if ($rdLength === 16) {
|
||||
$rdata = inet_ntop(substr($message->data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
|
||||
list($rdata, $consumed) = $this->readDomain($message->data, $consumed);
|
||||
} elseif (Message::TYPE_TXT === $type) {
|
||||
$rdata = array();
|
||||
while ($consumed < $expected) {
|
||||
$len = ord($message->data[$consumed]);
|
||||
$rdata[] = (string)substr($message->data, $consumed + 1, $len);
|
||||
$consumed += $len + 1;
|
||||
}
|
||||
} elseif (Message::TYPE_MX === $type) {
|
||||
if ($rdLength > 2) {
|
||||
list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2)));
|
||||
list($target, $consumed) = $this->readDomain($message->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($message->data, $consumed, 6)));
|
||||
list($target, $consumed) = $this->readDomain($message->data, $consumed + 6);
|
||||
|
||||
$rdata = array(
|
||||
'priority' => $priority,
|
||||
'weight' => $weight,
|
||||
'port' => $port,
|
||||
'target' => $target
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SOA === $type) {
|
||||
list($mname, $consumed) = $this->readDomain($message->data, $consumed);
|
||||
list($rname, $consumed) = $this->readDomain($message->data, $consumed);
|
||||
|
||||
if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) {
|
||||
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20)));
|
||||
$consumed += 20;
|
||||
|
||||
$rdata = array(
|
||||
'mname' => $mname,
|
||||
'rname' => $rname,
|
||||
'serial' => $serial,
|
||||
'refresh' => $refresh,
|
||||
'retry' => $retry,
|
||||
'expire' => $expire,
|
||||
'minimum' => $minimum
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// unknown types simply parse rdata as an opaque binary string
|
||||
$rdata = substr($message->data, $consumed, $rdLength);
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
|
||||
// ensure parsing record data consumes expact number of bytes indicated in record length
|
||||
if ($consumed !== $expected || $rdata === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$message->consumed = $consumed;
|
||||
|
||||
return new Record($name, $type, $class, $ttl, $rdata);
|
||||
}
|
||||
|
||||
private function readDomain($data, $consumed)
|
||||
{
|
||||
list ($labels, $consumed) = $this->readLabels($data, $consumed);
|
||||
|
||||
if ($labels === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
return array(implode('.', $labels), $consumed);
|
||||
}
|
||||
|
||||
private function readLabels($data, $consumed)
|
||||
{
|
||||
$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])) {
|
||||
$offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
|
||||
if ($offset >= $consumed) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$consumed += 2;
|
||||
list($newLabels) = $this->readLabels($data, $offset);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function isEndOfLabels($data, $consumed)
|
||||
{
|
||||
$length = ord(substr($data, $consumed, 1));
|
||||
return 0 === $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getCompressedLabel($data, $consumed)
|
||||
{
|
||||
list($nameOffset, $consumed) = $this->getCompressedLabelOffset($data, $consumed);
|
||||
list($labels) = $this->readLabels($data, $nameOffset);
|
||||
|
||||
return array($labels, $consumed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function isCompressedLabel($data, $consumed)
|
||||
{
|
||||
$mask = 0xc000; // 1100000000000000
|
||||
list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
|
||||
return (bool) ($peek & $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getCompressedLabelOffset($data, $consumed)
|
||||
{
|
||||
$mask = 0x3fff; // 0011111111111111
|
||||
list($peek) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
|
||||
return array($peek & $mask, $consumed + 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function signedLongToUnsignedLong($i)
|
||||
{
|
||||
return $i & 0x80000000 ? $i - 0xffffffff : $i;
|
||||
}
|
||||
}
|
||||
59
vendor/react/dns/src/Query/CachedExecutor.php
vendored
Executable file
59
vendor/react/dns/src/Query/CachedExecutor.php
vendored
Executable file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @see CachingExecutor
|
||||
*/
|
||||
class CachedExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $cache;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, RecordCache $cache)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
public function query($nameserver, Query $query)
|
||||
{
|
||||
$executor = $this->executor;
|
||||
$cache = $this->cache;
|
||||
|
||||
return $this->cache
|
||||
->lookup($query)
|
||||
->then(
|
||||
function ($cachedRecords) use ($query) {
|
||||
return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
|
||||
},
|
||||
function () use ($executor, $cache, $nameserver, $query) {
|
||||
return $executor
|
||||
->query($nameserver, $query)
|
||||
->then(function ($response) use ($cache, $query) {
|
||||
$cache->storeResponseMessage($query->currentTime, $response);
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
*/
|
||||
public function buildResponse(Query $query, array $cachedRecords)
|
||||
{
|
||||
return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
*/
|
||||
protected function generateId()
|
||||
{
|
||||
return mt_rand(0, 0xffff);
|
||||
}
|
||||
}
|
||||
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Executable file
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Executable file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Promise\Promise;
|
||||
|
||||
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($nameserver, 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 ($nameserver, $query, $id, $cache, $executor, &$pending, $that) {
|
||||
$pending->then(
|
||||
function ($message) use ($nameserver, $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($nameserver, $query)->then(
|
||||
function (Message $message) use ($cache, $id, $that) {
|
||||
// DNS response message received => store in cache when not truncated and return
|
||||
if (!$message->header->isTruncated()) {
|
||||
$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->name . ' has been cancelled'));
|
||||
$pending->cancel();
|
||||
$pending = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return int
|
||||
* @internal
|
||||
*/
|
||||
public function ttl(Message $message)
|
||||
{
|
||||
// select TTL from answers (should all be the same), use smallest value if available
|
||||
// @link https://tools.ietf.org/html/rfc2181#section-5.2
|
||||
$ttl = null;
|
||||
foreach ($message->answers as $answer) {
|
||||
if ($ttl === null || $answer->ttl < $ttl) {
|
||||
$ttl = $answer->ttl;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ttl === null) {
|
||||
$ttl = self::TTL;
|
||||
}
|
||||
|
||||
return $ttl;
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Executable file
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
class CancellationException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
92
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Executable file
92
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Executable file
@@ -0,0 +1,92 @@
|
||||
<?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($loop),
|
||||
* 3.0,
|
||||
* $loop
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
class CoopExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $pending = array();
|
||||
private $counts = array();
|
||||
|
||||
public function __construct(ExecutorInterface $base)
|
||||
{
|
||||
$this->executor = $base;
|
||||
}
|
||||
|
||||
public function query($nameserver, 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($nameserver, $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->name . ' has been cancelled');
|
||||
});
|
||||
}
|
||||
|
||||
private function serializeQueryToIdentity(Query $query)
|
||||
{
|
||||
return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
|
||||
}
|
||||
}
|
||||
160
vendor/react/dns/src/Query/Executor.php
vendored
Executable file
160
vendor/react/dns/src/Query/Executor.php
vendored
Executable file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\Stream;
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @see UdpTransportExecutor
|
||||
*/
|
||||
class Executor implements ExecutorInterface
|
||||
{
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
private $timeout;
|
||||
|
||||
/**
|
||||
*
|
||||
* Note that albeit supported, the $timeout parameter is deprecated!
|
||||
* You should pass a `null` value here instead. If you need timeout handling,
|
||||
* use the `TimeoutConnector` instead.
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param Parser $parser
|
||||
* @param BinaryDumper $dumper
|
||||
* @param null|float $timeout DEPRECATED: timeout for DNS query or NULL=no timeout
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, Parser $parser, BinaryDumper $dumper, $timeout = 5)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
$this->parser = $parser;
|
||||
$this->dumper = $dumper;
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function query($nameserver, Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
$transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
|
||||
|
||||
return $this->doQuery($nameserver, $transport, $queryData, $query->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
*/
|
||||
public function prepareRequest(Query $query)
|
||||
{
|
||||
return Message::createRequestForQuery($query);
|
||||
}
|
||||
|
||||
public function doQuery($nameserver, $transport, $queryData, $name)
|
||||
{
|
||||
// we only support UDP right now
|
||||
if ($transport !== 'udp') {
|
||||
return Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $name . ' failed: Requested transport "' . $transport . '" not available, only UDP is supported in this version'
|
||||
));
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$parser = $this->parser;
|
||||
$loop = $this->loop;
|
||||
|
||||
// UDP connections are instant, so try this without a timer
|
||||
try {
|
||||
$conn = $this->createConnection($nameserver, $transport);
|
||||
} catch (\Exception $e) {
|
||||
return Promise\reject(new \RuntimeException('DNS query for ' . $name . ' failed: ' . $e->getMessage(), 0, $e));
|
||||
}
|
||||
|
||||
$deferred = new Deferred(function ($resolve, $reject) use (&$timer, $loop, &$conn, $name) {
|
||||
$reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
|
||||
|
||||
if ($timer !== null) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
$conn->close();
|
||||
});
|
||||
|
||||
$timer = null;
|
||||
if ($this->timeout !== null) {
|
||||
$timer = $this->loop->addTimer($this->timeout, function () use (&$conn, $name, $deferred) {
|
||||
$conn->close();
|
||||
$deferred->reject(new TimeoutException(sprintf("DNS query for %s timed out", $name)));
|
||||
});
|
||||
}
|
||||
|
||||
$conn->on('data', function ($data) use ($conn, $parser, $deferred, $timer, $loop, $name) {
|
||||
$conn->end();
|
||||
if ($timer !== null) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $parser->parseMessage($data);
|
||||
} catch (\Exception $e) {
|
||||
$deferred->reject($e);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->header->isTruncated()) {
|
||||
$deferred->reject(new \RuntimeException('DNS query for ' . $name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
$conn->write($queryData);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
*/
|
||||
protected function generateId()
|
||||
{
|
||||
return mt_rand(0, 0xffff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param string $transport
|
||||
* @return \React\Stream\DuplexStreamInterface
|
||||
*/
|
||||
protected function createConnection($nameserver, $transport)
|
||||
{
|
||||
$fd = @stream_socket_client("$transport://$nameserver", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
|
||||
if ($fd === false) {
|
||||
throw new \RuntimeException('Unable to connect to DNS server: ' . $errstr, $errno);
|
||||
}
|
||||
|
||||
// Instantiate stream instance around this stream resource.
|
||||
// This ought to be replaced with a datagram socket in the future.
|
||||
// Temporary work around for Windows 10: buffer whole UDP response
|
||||
// @coverageIgnoreStart
|
||||
if (!class_exists('React\Stream\Stream')) {
|
||||
// prefer DuplexResourceStream as of react/stream v0.7.0
|
||||
$conn = new DuplexResourceStream($fd, $this->loop, -1);
|
||||
} else {
|
||||
// use legacy Stream class for react/stream < v0.7.0
|
||||
$conn = new Stream($fd, $this->loop);
|
||||
$conn->bufferSize = null;
|
||||
}
|
||||
// @coverageIgnoreEnd
|
||||
|
||||
return $conn;
|
||||
}
|
||||
}
|
||||
8
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Executable file
8
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
interface ExecutorInterface
|
||||
{
|
||||
public function query($nameserver, Query $query);
|
||||
}
|
||||
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Executable file
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Executable file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
|
||||
/**
|
||||
* Resolves hosts from the given HostsFile or falls back to another executor
|
||||
*
|
||||
* If the host is found in the hosts file, it will not be passed to the actual
|
||||
* DNS executor. If the host is not found in the hosts file, it will be passed
|
||||
* to the DNS executor as a fallback.
|
||||
*/
|
||||
class HostsFileExecutor implements ExecutorInterface
|
||||
{
|
||||
private $hosts;
|
||||
private $fallback;
|
||||
|
||||
public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
|
||||
{
|
||||
$this->hosts = $hosts;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
public function query($nameserver, 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($nameserver, $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;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
vendor/react/dns/src/Query/Query.php
vendored
Executable file
33
vendor/react/dns/src/Query/Query.php
vendored
Executable file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
class Query
|
||||
{
|
||||
public $name;
|
||||
public $type;
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @deprecated still used internally for BC reasons, should not be used externally.
|
||||
*/
|
||||
public $currentTime;
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @param int|null $currentTime (deprecated) still used internally, should not be passed explicitly anymore.
|
||||
*/
|
||||
public function __construct($name, $type, $class, $currentTime = null)
|
||||
{
|
||||
if($currentTime === null) {
|
||||
$currentTime = time();
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
$this->currentTime = $currentTime;
|
||||
}
|
||||
}
|
||||
30
vendor/react/dns/src/Query/RecordBag.php
vendored
Executable file
30
vendor/react/dns/src/Query/RecordBag.php
vendored
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Record;
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
* @see CachingExecutor
|
||||
*/
|
||||
class RecordBag
|
||||
{
|
||||
private $records = array();
|
||||
|
||||
public function set($currentTime, Record $record)
|
||||
{
|
||||
$this->records[] = array($currentTime + $record->ttl, $record);
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return array_values(array_map(
|
||||
function ($value) {
|
||||
list($expiresAt, $record) = $value;
|
||||
return $record;
|
||||
},
|
||||
$this->records
|
||||
));
|
||||
}
|
||||
}
|
||||
122
vendor/react/dns/src/Query/RecordCache.php
vendored
Executable file
122
vendor/react/dns/src/Query/RecordCache.php
vendored
Executable file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Wraps an underlying cache interface and exposes only cached DNS data
|
||||
*
|
||||
* @deprecated unused, exists for BC only
|
||||
* @see CachingExecutor
|
||||
*/
|
||||
class RecordCache
|
||||
{
|
||||
private $cache;
|
||||
private $expiredAt;
|
||||
|
||||
public function __construct(CacheInterface $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the cache if there's a cached answer for the given query
|
||||
*
|
||||
* @param Query $query
|
||||
* @return PromiseInterface Promise<Record[],mixed> resolves with array of Record objects on sucess
|
||||
* or rejects with mixed values when query is not cached already.
|
||||
*/
|
||||
public function lookup(Query $query)
|
||||
{
|
||||
$id = $this->serializeQueryToIdentity($query);
|
||||
|
||||
$expiredAt = $this->expiredAt;
|
||||
|
||||
return $this->cache
|
||||
->get($id)
|
||||
->then(function ($value) use ($query, $expiredAt) {
|
||||
// reject on cache miss
|
||||
if ($value === null) {
|
||||
return Promise\reject();
|
||||
}
|
||||
|
||||
/* @var $recordBag RecordBag */
|
||||
$recordBag = unserialize($value);
|
||||
|
||||
// reject this cache hit if the query was started before the time we expired the cache?
|
||||
// todo: this is a legacy left over, this value is never actually set, so this never applies.
|
||||
// todo: this should probably validate the cache time instead.
|
||||
if (null !== $expiredAt && $expiredAt <= $query->currentTime) {
|
||||
return Promise\reject();
|
||||
}
|
||||
|
||||
return $recordBag->all();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores all records from this response message in the cache
|
||||
*
|
||||
* @param int $currentTime
|
||||
* @param Message $message
|
||||
* @uses self::storeRecord()
|
||||
*/
|
||||
public function storeResponseMessage($currentTime, Message $message)
|
||||
{
|
||||
foreach ($message->answers as $record) {
|
||||
$this->storeRecord($currentTime, $record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a single record from a response message in the cache
|
||||
*
|
||||
* @param int $currentTime
|
||||
* @param Record $record
|
||||
*/
|
||||
public function storeRecord($currentTime, Record $record)
|
||||
{
|
||||
$id = $this->serializeRecordToIdentity($record);
|
||||
|
||||
$cache = $this->cache;
|
||||
|
||||
$this->cache
|
||||
->get($id)
|
||||
->then(
|
||||
function ($value) {
|
||||
// return empty bag on cache miss
|
||||
if ($value === null) {
|
||||
return new RecordBag();
|
||||
}
|
||||
|
||||
// reuse existing bag on cache hit to append new record to it
|
||||
return unserialize($value);
|
||||
}
|
||||
)
|
||||
->then(function (RecordBag $recordBag) use ($id, $currentTime, $record, $cache) {
|
||||
// add a record to the existing (possibly empty) record bag and save to cache
|
||||
$recordBag->set($currentTime, $record);
|
||||
$cache->set($id, serialize($recordBag));
|
||||
});
|
||||
}
|
||||
|
||||
public function expire($currentTime)
|
||||
{
|
||||
$this->expiredAt = $currentTime;
|
||||
}
|
||||
|
||||
public function serializeQueryToIdentity(Query $query)
|
||||
{
|
||||
return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
|
||||
}
|
||||
|
||||
public function serializeRecordToIdentity(Record $record)
|
||||
{
|
||||
return sprintf('%s:%s:%s', $record->name, $record->type, $record->class);
|
||||
}
|
||||
}
|
||||
79
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Executable file
79
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Executable file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
class RetryExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $retries;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, $retries = 2)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->retries = $retries;
|
||||
}
|
||||
|
||||
public function query($nameserver, Query $query)
|
||||
{
|
||||
return $this->tryQuery($nameserver, $query, $this->retries);
|
||||
}
|
||||
|
||||
public function tryQuery($nameserver, Query $query, $retries)
|
||||
{
|
||||
$deferred = new Deferred(function () use (&$promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$success = function ($value) use ($deferred, &$errorback) {
|
||||
$errorback = null;
|
||||
$deferred->resolve($value);
|
||||
};
|
||||
|
||||
$executor = $this->executor;
|
||||
$errorback = function ($e) use ($deferred, &$promise, $nameserver, $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->name . ' 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);
|
||||
foreach ($trace as &$one) {
|
||||
foreach ($one['args'] as &$arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$arg = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
$r->setValue($e, $trace);
|
||||
} else {
|
||||
--$retries;
|
||||
$promise = $executor->query($nameserver, $query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$promise = $this->executor->query($nameserver, $query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Executable file
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
class TimeoutException extends \Exception
|
||||
{
|
||||
}
|
||||
32
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Executable file
32
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use React\Promise\Timer;
|
||||
|
||||
class TimeoutExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $loop;
|
||||
private $timeout;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, $timeout, LoopInterface $loop)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->loop = $loop;
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function query($nameserver, Query $query)
|
||||
{
|
||||
return Timer\timeout($this->executor->query($nameserver, $query), $this->timeout, $this->loop)->then(null, function ($e) use ($query) {
|
||||
if ($e instanceof Timer\TimeoutException) {
|
||||
$e = new TimeoutException(sprintf("DNS query for %s timed out", $query->name), 0, $e);
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
}
|
||||
181
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Executable file
181
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Executable file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
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
|
||||
* $loop = Factory::create();
|
||||
* $executor = new UdpTransportExecutor($loop);
|
||||
*
|
||||
* $executor->query(
|
||||
* '8.8.8.8:53',
|
||||
* 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');
|
||||
*
|
||||
* $loop->run();
|
||||
* ```
|
||||
*
|
||||
* 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($loop),
|
||||
* 3.0,
|
||||
* $loop
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* 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($loop),
|
||||
* 3.0,
|
||||
* $loop
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* 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($loop),
|
||||
* 3.0,
|
||||
* $loop
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > 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.
|
||||
*/
|
||||
class UdpTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
|
||||
/**
|
||||
* @param LoopInterface $loop
|
||||
* @param null|Parser $parser optional/advanced: DNS protocol parser to use
|
||||
* @param null|BinaryDumper $dumper optional/advanced: DNS protocol dumper to use
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, Parser $parser = null, BinaryDumper $dumper = null)
|
||||
{
|
||||
if ($parser === null) {
|
||||
$parser = new Parser();
|
||||
}
|
||||
if ($dumper === null) {
|
||||
$dumper = new BinaryDumper();
|
||||
}
|
||||
|
||||
$this->loop = $loop;
|
||||
$this->parser = $parser;
|
||||
$this->dumper = $dumper;
|
||||
}
|
||||
|
||||
public function query($nameserver, Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
if (isset($queryData[512])) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->name . ' failed: Query too large for UDP transport'
|
||||
));
|
||||
}
|
||||
|
||||
// UDP connections are instant, so try connection without a loop or timeout
|
||||
$socket = @\stream_socket_client("udp://$nameserver", $errno, $errstr, 0);
|
||||
if ($socket === false) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->name . ' failed: Unable to connect to DNS server (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// set socket to non-blocking and immediately try to send (fill write buffer)
|
||||
\stream_set_blocking($socket, false);
|
||||
\fwrite($socket, $queryData);
|
||||
|
||||
$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->name . ' has been cancelled');
|
||||
});
|
||||
|
||||
$parser = $this->parser;
|
||||
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request) {
|
||||
// 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, 512);
|
||||
|
||||
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->getId() !== $request->getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we only react to the first valid message, so remove socket from loop and close
|
||||
$loop->removeReadStream($socket);
|
||||
\fclose($socket);
|
||||
|
||||
if ($response->header->isTruncated()) {
|
||||
$deferred->reject(new \RuntimeException('DNS query for ' . $query->name . ' failed: The server returned a truncated result for a UDP query, but retrying via TCP is currently not supported'));
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Executable file
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
class RecordNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
102
vendor/react/dns/src/Resolver/Factory.php
vendored
Executable file
102
vendor/react/dns/src/Resolver/Factory.php
vendored
Executable file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Cache\ArrayCache;
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Query\CachingExecutor;
|
||||
use React\Dns\Query\CoopExecutor;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\HostsFileExecutor;
|
||||
use React\Dns\Query\RetryExecutor;
|
||||
use React\Dns\Query\TimeoutExecutor;
|
||||
use React\Dns\Query\UdpTransportExecutor;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
class Factory
|
||||
{
|
||||
public function create($nameserver, LoopInterface $loop)
|
||||
{
|
||||
$nameserver = $this->addPortToServerIfMissing($nameserver);
|
||||
$executor = $this->decorateHostsFileExecutor($this->createRetryExecutor($loop));
|
||||
|
||||
return new Resolver($nameserver, $executor);
|
||||
}
|
||||
|
||||
public function createCached($nameserver, LoopInterface $loop, CacheInterface $cache = null)
|
||||
{
|
||||
// default to keeping maximum of 256 responses in cache unless explicitly given
|
||||
if (!($cache instanceof CacheInterface)) {
|
||||
$cache = new ArrayCache(256);
|
||||
}
|
||||
|
||||
$nameserver = $this->addPortToServerIfMissing($nameserver);
|
||||
$executor = $this->decorateHostsFileExecutor($this->createCachedExecutor($loop, $cache));
|
||||
|
||||
return new Resolver($nameserver, $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;
|
||||
}
|
||||
|
||||
protected function createExecutor(LoopInterface $loop)
|
||||
{
|
||||
return new TimeoutExecutor(
|
||||
new UdpTransportExecutor($loop),
|
||||
5.0,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
protected function createRetryExecutor(LoopInterface $loop)
|
||||
{
|
||||
return new CoopExecutor(new RetryExecutor($this->createExecutor($loop)));
|
||||
}
|
||||
|
||||
protected function createCachedExecutor(LoopInterface $loop, CacheInterface $cache)
|
||||
{
|
||||
return new CachingExecutor($this->createRetryExecutor($loop), $cache);
|
||||
}
|
||||
|
||||
protected function addPortToServerIfMissing($nameserver)
|
||||
{
|
||||
if (strpos($nameserver, '[') === false && substr_count($nameserver, ':') >= 2) {
|
||||
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
|
||||
$nameserver = '[' . $nameserver . ']';
|
||||
}
|
||||
// assume a dummy scheme when checking for the port, otherwise parse_url() fails
|
||||
if (parse_url('dummy://' . $nameserver, PHP_URL_PORT) === null) {
|
||||
$nameserver .= ':53';
|
||||
}
|
||||
|
||||
return $nameserver;
|
||||
}
|
||||
}
|
||||
250
vendor/react/dns/src/Resolver/Resolver.php
vendored
Executable file
250
vendor/react/dns/src/Resolver/Resolver.php
vendored
Executable file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\RecordNotFoundException;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class Resolver
|
||||
{
|
||||
private $nameserver;
|
||||
private $executor;
|
||||
|
||||
public function __construct($nameserver, ExecutorInterface $executor)
|
||||
{
|
||||
$this->nameserver = $nameserver;
|
||||
$this->executor = $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 PromiseInterface Returns a promise which resolves with a single IP address on success or
|
||||
* rejects with an Exception on error.
|
||||
*/
|
||||
public function resolve($domain)
|
||||
{
|
||||
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
|
||||
return $ips[array_rand($ips)];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 PromiseInterface Returns a promise which resolves with all record values on success or
|
||||
* rejects with an Exception on error.
|
||||
*/
|
||||
public function resolveAll($domain, $type)
|
||||
{
|
||||
$query = new Query($domain, $type, Message::CLASS_IN);
|
||||
$that = $this;
|
||||
|
||||
return $this->executor->query(
|
||||
$this->nameserver,
|
||||
$query
|
||||
)->then(function (Message $response) use ($query, $that) {
|
||||
return $that->extractValues($query, $response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
*/
|
||||
public function extractAddress(Query $query, Message $response)
|
||||
{
|
||||
$addresses = $this->extractValues($query, $response);
|
||||
|
||||
return $addresses[array_rand($addresses)];
|
||||
}
|
||||
|
||||
/**
|
||||
* [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->getResponseCode();
|
||||
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->name . ' 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->name . ' did not return a valid answer (NOERROR / NODATA)'
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused, exists for BC only
|
||||
*/
|
||||
public function resolveAliases(array $answers, $name)
|
||||
{
|
||||
return $this->valuesByNameAndType($answers, $name, Message::TYPE_A);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
10
vendor/react/dns/tests/CallableStub.php
vendored
Executable file
10
vendor/react/dns/tests/CallableStub.php
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns;
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
189
vendor/react/dns/tests/Config/ConfigTest.php
vendored
Executable file
189
vendor/react/dns/tests/Config/ConfigTest.php
vendored
Executable file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Config;
|
||||
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Dns\Config\Config;
|
||||
|
||||
class ConfigTest extends TestCase
|
||||
{
|
||||
public function testLoadsSystemDefault()
|
||||
{
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Config\Config', $config);
|
||||
}
|
||||
|
||||
public function testLoadsDefaultPath()
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$this->markTestSkipped('Not supported on Windows');
|
||||
}
|
||||
|
||||
$config = Config::loadResolvConfBlocking();
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Config\Config', $config);
|
||||
}
|
||||
|
||||
public function testLoadsFromExplicitPath()
|
||||
{
|
||||
$config = Config::loadResolvConfBlocking(__DIR__ . '/../Fixtures/etc/resolv.conf');
|
||||
|
||||
$this->assertEquals(array('8.8.8.8'), $config->nameservers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
public function testLoadThrowsWhenPathIsInvalid()
|
||||
{
|
||||
Config::loadResolvConfBlocking(__DIR__ . '/invalid.conf');
|
||||
}
|
||||
|
||||
public function testParsesSingleEntryFile()
|
||||
{
|
||||
$contents = 'nameserver 8.8.8.8';
|
||||
$expected = array('8.8.8.8');
|
||||
|
||||
$config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testParsesNameserverEntriesFromAverageFileCorrectly()
|
||||
{
|
||||
$contents = '#
|
||||
# Mac OS X Notice
|
||||
#
|
||||
# This file is not used by the host name and address resolution
|
||||
# or the DNS query routing mechanisms used by most processes on
|
||||
# this Mac OS X system.
|
||||
#
|
||||
# This file is automatically generated.
|
||||
#
|
||||
domain v.cablecom.net
|
||||
nameserver 127.0.0.1
|
||||
nameserver ::1
|
||||
';
|
||||
$expected = array('127.0.0.1', '::1');
|
||||
|
||||
$config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testParsesEmptyFileWithoutNameserverEntries()
|
||||
{
|
||||
$contents = '';
|
||||
$expected = array();
|
||||
|
||||
$config = Config::loadResolvConfBlocking('data://text/plain;base64,');
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testParsesFileAndIgnoresCommentsAndInvalidNameserverEntries()
|
||||
{
|
||||
$contents = '
|
||||
# nameserver 1.2.3.4
|
||||
; nameserver 2.3.4.5
|
||||
|
||||
nameserver 3.4.5.6 # nope
|
||||
nameserver 4.5.6.7 5.6.7.8
|
||||
nameserver 6.7.8.9
|
||||
NameServer 7.8.9.10
|
||||
';
|
||||
$expected = array();
|
||||
|
||||
$config = Config::loadResolvConfBlocking('data://text/plain;base64,' . base64_encode($contents));
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testLoadsFromWmicOnWindows()
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR !== '\\') {
|
||||
$this->markTestSkipped('Only on Windows');
|
||||
}
|
||||
|
||||
$config = Config::loadWmicBlocking();
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Config\Config', $config);
|
||||
}
|
||||
|
||||
public function testLoadsSingleEntryFromWmicOutput()
|
||||
{
|
||||
$contents = '
|
||||
Node,DNSServerSearchOrder
|
||||
ACE,
|
||||
ACE,{192.168.2.1}
|
||||
ACE,
|
||||
';
|
||||
$expected = array('192.168.2.1');
|
||||
|
||||
$config = Config::loadWmicBlocking($this->echoCommand($contents));
|
||||
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testLoadsEmptyListFromWmicOutput()
|
||||
{
|
||||
$contents = '
|
||||
Node,DNSServerSearchOrder
|
||||
ACE,
|
||||
';
|
||||
$expected = array();
|
||||
|
||||
$config = Config::loadWmicBlocking($this->echoCommand($contents));
|
||||
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testLoadsSingleEntryForMultipleNicsFromWmicOutput()
|
||||
{
|
||||
$contents = '
|
||||
Node,DNSServerSearchOrder
|
||||
ACE,
|
||||
ACE,{192.168.2.1}
|
||||
ACE,
|
||||
ACE,{192.168.2.2}
|
||||
ACE,
|
||||
';
|
||||
$expected = array('192.168.2.1', '192.168.2.2');
|
||||
|
||||
$config = Config::loadWmicBlocking($this->echoCommand($contents));
|
||||
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testLoadsMultipleEntriesForSingleNicWithSemicolonFromWmicOutput()
|
||||
{
|
||||
$contents = '
|
||||
Node,DNSServerSearchOrder
|
||||
ACE,
|
||||
ACE,{192.168.2.1;192.168.2.2}
|
||||
ACE,
|
||||
';
|
||||
$expected = array('192.168.2.1', '192.168.2.2');
|
||||
|
||||
$config = Config::loadWmicBlocking($this->echoCommand($contents));
|
||||
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
public function testLoadsMultipleEntriesForSingleNicWithQuotesFromWmicOutput()
|
||||
{
|
||||
$contents = '
|
||||
Node,DNSServerSearchOrder
|
||||
ACE,
|
||||
ACE,{"192.168.2.1","192.168.2.2"}
|
||||
ACE,
|
||||
';
|
||||
$expected = array('192.168.2.1', '192.168.2.2');
|
||||
|
||||
$config = Config::loadWmicBlocking($this->echoCommand($contents));
|
||||
|
||||
$this->assertEquals($expected, $config->nameservers);
|
||||
}
|
||||
|
||||
private function echoCommand($output)
|
||||
{
|
||||
return 'echo ' . escapeshellarg($output);
|
||||
}
|
||||
}
|
||||
70
vendor/react/dns/tests/Config/FilesystemFactoryTest.php
vendored
Executable file
70
vendor/react/dns/tests/Config/FilesystemFactoryTest.php
vendored
Executable file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace React\Test\Dns\Config;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use React\Dns\Config\FilesystemFactory;
|
||||
|
||||
class FilesystemFactoryTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function parseEtcResolvConfShouldParseCorrectly()
|
||||
{
|
||||
$contents = '#
|
||||
# Mac OS X Notice
|
||||
#
|
||||
# This file is not used by the host name and address resolution
|
||||
# or the DNS query routing mechanisms used by most processes on
|
||||
# this Mac OS X system.
|
||||
#
|
||||
# This file is automatically generated.
|
||||
#
|
||||
domain v.cablecom.net
|
||||
nameserver 127.0.0.1
|
||||
nameserver 8.8.8.8
|
||||
';
|
||||
$expected = array('127.0.0.1', '8.8.8.8');
|
||||
|
||||
$capturedConfig = null;
|
||||
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
$factory = new FilesystemFactory($loop);
|
||||
$factory->parseEtcResolvConf($contents)->then(function ($config) use (&$capturedConfig) {
|
||||
$capturedConfig = $config;
|
||||
});
|
||||
|
||||
$this->assertNotNull($capturedConfig);
|
||||
$this->assertSame($expected, $capturedConfig->nameservers);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function createShouldLoadStuffFromFilesystem()
|
||||
{
|
||||
$this->markTestIncomplete('Filesystem API is incomplete');
|
||||
|
||||
$expected = array('8.8.8.8');
|
||||
|
||||
$triggerListener = null;
|
||||
$capturedConfig = null;
|
||||
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
$loop
|
||||
->expects($this->once())
|
||||
->method('addReadStream')
|
||||
->will($this->returnCallback(function ($stream, $listener) use (&$triggerListener) {
|
||||
$triggerListener = function () use ($stream, $listener) {
|
||||
call_user_func($listener, $stream);
|
||||
};
|
||||
}));
|
||||
|
||||
$factory = new FilesystemFactory($loop);
|
||||
$factory->create(__DIR__.'/../Fixtures/etc/resolv.conf')->then(function ($config) use (&$capturedConfig) {
|
||||
$capturedConfig = $config;
|
||||
});
|
||||
|
||||
$triggerListener();
|
||||
|
||||
$this->assertNotNull($capturedConfig);
|
||||
$this->assertSame($expected, $capturedConfig->nameservers);
|
||||
}
|
||||
}
|
||||
170
vendor/react/dns/tests/Config/HostsFileTest.php
vendored
Executable file
170
vendor/react/dns/tests/Config/HostsFileTest.php
vendored
Executable file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Config;
|
||||
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Dns\Config\HostsFile;
|
||||
|
||||
class HostsFileTest extends TestCase
|
||||
{
|
||||
public function testLoadsFromDefaultPath()
|
||||
{
|
||||
$hosts = HostsFile::loadFromPathBlocking();
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Config\HostsFile', $hosts);
|
||||
}
|
||||
|
||||
public function testDefaultShouldHaveLocalhostMapped()
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$this->markTestSkipped('Not supported on Windows');
|
||||
}
|
||||
|
||||
$hosts = HostsFile::loadFromPathBlocking();
|
||||
|
||||
$this->assertContains('127.0.0.1', $hosts->getIpsForHost('localhost'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
public function testLoadThrowsForInvalidPath()
|
||||
{
|
||||
HostsFile::loadFromPathBlocking('does/not/exist');
|
||||
}
|
||||
|
||||
public function testContainsSingleLocalhostEntry()
|
||||
{
|
||||
$hosts = new HostsFile('127.0.0.1 localhost');
|
||||
|
||||
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
|
||||
$this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
|
||||
}
|
||||
|
||||
public function testNonIpReturnsNothingForInvalidHosts()
|
||||
{
|
||||
$hosts = new HostsFile('a b');
|
||||
|
||||
$this->assertEquals(array(), $hosts->getIpsForHost('a'));
|
||||
$this->assertEquals(array(), $hosts->getIpsForHost('b'));
|
||||
}
|
||||
|
||||
public function testIgnoresIpv6ZoneId()
|
||||
{
|
||||
$hosts = new HostsFile('fe80::1%lo0 localhost');
|
||||
|
||||
$this->assertEquals(array('fe80::1'), $hosts->getIpsForHost('localhost'));
|
||||
}
|
||||
|
||||
public function testSkipsComments()
|
||||
{
|
||||
$hosts = new HostsFile('# start' . PHP_EOL .'#127.0.0.1 localhost' . PHP_EOL . '127.0.0.2 localhost # example.com');
|
||||
|
||||
$this->assertEquals(array('127.0.0.2'), $hosts->getIpsForHost('localhost'));
|
||||
$this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
|
||||
}
|
||||
|
||||
public function testContainsSingleLocalhostEntryWithCaseIgnored()
|
||||
{
|
||||
$hosts = new HostsFile('127.0.0.1 LocalHost');
|
||||
|
||||
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('LOCALHOST'));
|
||||
}
|
||||
|
||||
public function testEmptyFileContainsNothing()
|
||||
{
|
||||
$hosts = new HostsFile('');
|
||||
|
||||
$this->assertEquals(array(), $hosts->getIpsForHost('example.com'));
|
||||
}
|
||||
|
||||
public function testSingleEntryWithMultipleNames()
|
||||
{
|
||||
$hosts = new HostsFile('127.0.0.1 localhost example.com');
|
||||
|
||||
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('example.com'));
|
||||
$this->assertEquals(array('127.0.0.1'), $hosts->getIpsForHost('localhost'));
|
||||
}
|
||||
|
||||
public function testMergesEntriesOverMultipleLines()
|
||||
{
|
||||
$hosts = new HostsFile("127.0.0.1 localhost\n127.0.0.2 localhost\n127.0.0.3 a localhost b\n127.0.0.4 a localhost");
|
||||
|
||||
$this->assertEquals(array('127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'), $hosts->getIpsForHost('localhost'));
|
||||
}
|
||||
|
||||
public function testMergesIpv4AndIpv6EntriesOverMultipleLines()
|
||||
{
|
||||
$hosts = new HostsFile("127.0.0.1 localhost\n::1 localhost");
|
||||
|
||||
$this->assertEquals(array('127.0.0.1', '::1'), $hosts->getIpsForHost('localhost'));
|
||||
}
|
||||
|
||||
public function testReverseLookup()
|
||||
{
|
||||
$hosts = new HostsFile('127.0.0.1 localhost');
|
||||
|
||||
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
|
||||
$this->assertEquals(array(), $hosts->getHostsForIp('192.168.1.1'));
|
||||
}
|
||||
|
||||
public function testReverseSkipsComments()
|
||||
{
|
||||
$hosts = new HostsFile("# start\n#127.0.0.1 localhosted\n127.0.0.2\tlocalhost\t# example.com\n\t127.0.0.3\t\texample.org\t\t");
|
||||
|
||||
$this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1'));
|
||||
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.2'));
|
||||
$this->assertEquals(array('example.org'), $hosts->getHostsForIp('127.0.0.3'));
|
||||
}
|
||||
|
||||
public function testReverseNonIpReturnsNothing()
|
||||
{
|
||||
$hosts = new HostsFile('127.0.0.1 localhost');
|
||||
|
||||
$this->assertEquals(array(), $hosts->getHostsForIp('localhost'));
|
||||
$this->assertEquals(array(), $hosts->getHostsForIp('127.0.0.1.1'));
|
||||
}
|
||||
|
||||
public function testReverseNonIpReturnsNothingForInvalidHosts()
|
||||
{
|
||||
$hosts = new HostsFile('a b');
|
||||
|
||||
$this->assertEquals(array(), $hosts->getHostsForIp('a'));
|
||||
$this->assertEquals(array(), $hosts->getHostsForIp('b'));
|
||||
}
|
||||
|
||||
public function testReverseLookupReturnsLowerCaseHost()
|
||||
{
|
||||
$hosts = new HostsFile('127.0.0.1 LocalHost');
|
||||
|
||||
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('127.0.0.1'));
|
||||
}
|
||||
|
||||
public function testReverseLookupChecksNormalizedIpv6()
|
||||
{
|
||||
$hosts = new HostsFile('FE80::00a1 localhost');
|
||||
|
||||
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::A1'));
|
||||
}
|
||||
|
||||
public function testReverseLookupIgnoresIpv6ZoneId()
|
||||
{
|
||||
$hosts = new HostsFile('fe80::1%lo0 localhost');
|
||||
|
||||
$this->assertEquals(array('localhost'), $hosts->getHostsForIp('fe80::1'));
|
||||
}
|
||||
|
||||
public function testReverseLookupReturnsMultipleHostsOverSingleLine()
|
||||
{
|
||||
$hosts = new HostsFile("::1 ip6-localhost ip6-loopback");
|
||||
|
||||
$this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
|
||||
}
|
||||
|
||||
public function testReverseLookupReturnsMultipleHostsOverMultipleLines()
|
||||
{
|
||||
$hosts = new HostsFile("::1 ip6-localhost\n::1 ip6-loopback");
|
||||
|
||||
$this->assertEquals(array('ip6-localhost', 'ip6-loopback'), $hosts->getHostsForIp('::1'));
|
||||
}
|
||||
}
|
||||
1
vendor/react/dns/tests/Fixtures/etc/resolv.conf
vendored
Executable file
1
vendor/react/dns/tests/Fixtures/etc/resolv.conf
vendored
Executable file
@@ -0,0 +1 @@
|
||||
nameserver 8.8.8.8
|
||||
171
vendor/react/dns/tests/FunctionalResolverTest.php
vendored
Executable file
171
vendor/react/dns/tests/FunctionalResolverTest.php
vendored
Executable file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns;
|
||||
|
||||
use React\EventLoop\Factory as LoopFactory;
|
||||
use React\Dns\Resolver\Factory;
|
||||
use React\Dns\RecordNotFoundException;
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
class FunctionalTest extends TestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = LoopFactory::create();
|
||||
|
||||
$factory = new Factory();
|
||||
$this->resolver = $factory->create('8.8.8.8', $this->loop);
|
||||
}
|
||||
|
||||
public function testResolveLocalhostResolves()
|
||||
{
|
||||
$promise = $this->resolver->resolve('localhost');
|
||||
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testResolveAllLocalhostResolvesWithArray()
|
||||
{
|
||||
$promise = $this->resolver->resolveAll('localhost', Message::TYPE_A);
|
||||
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
*/
|
||||
public function testResolveGoogleResolves()
|
||||
{
|
||||
$promise = $this->resolver->resolve('google.com');
|
||||
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
*/
|
||||
public function testResolveAllGoogleMxResolvesWithCache()
|
||||
{
|
||||
$factory = new Factory();
|
||||
$this->resolver = $factory->createCached('8.8.8.8', $this->loop);
|
||||
|
||||
$promise = $this->resolver->resolveAll('google.com', Message::TYPE_MX);
|
||||
$promise->then($this->expectCallableOnceWith($this->isType('array')), $this->expectCallableNever());
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
*/
|
||||
public function testResolveInvalidRejects()
|
||||
{
|
||||
$ex = $this->callback(function ($param) {
|
||||
return ($param instanceof RecordNotFoundException && $param->getCode() === Message::RCODE_NAME_ERROR);
|
||||
});
|
||||
|
||||
$promise = $this->resolver->resolve('example.invalid');
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testResolveCancelledRejectsImmediately()
|
||||
{
|
||||
$ex = $this->callback(function ($param) {
|
||||
return ($param instanceof \RuntimeException && $param->getMessage() === 'DNS query for google.com has been cancelled');
|
||||
});
|
||||
|
||||
$promise = $this->resolver->resolve('google.com');
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($ex));
|
||||
$promise->cancel();
|
||||
|
||||
$time = microtime(true);
|
||||
$this->loop->run();
|
||||
$time = microtime(true) - $time;
|
||||
|
||||
$this->assertLessThan(0.1, $time);
|
||||
}
|
||||
|
||||
public function testInvalidResolverDoesNotResolveGoogle()
|
||||
{
|
||||
$factory = new Factory();
|
||||
$this->resolver = $factory->create('255.255.255.255', $this->loop);
|
||||
|
||||
$promise = $this->resolver->resolve('google.com');
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testResolveShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$factory = new Factory();
|
||||
$this->resolver = $factory->create('255.255.255.255', $this->loop);
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
$promise = $this->resolver->resolve('google.com');
|
||||
unset($promise);
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
|
||||
public function testResolveCachedShouldNotCauseGarbageReferencesWhenUsingInvalidNameserver()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$factory = new Factory();
|
||||
$this->resolver = $factory->createCached('255.255.255.255', $this->loop);
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
$promise = $this->resolver->resolve('google.com');
|
||||
unset($promise);
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
|
||||
public function testCancelResolveShouldNotCauseGarbageReferences()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$factory = new Factory();
|
||||
$this->resolver = $factory->create('127.0.0.1', $this->loop);
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
$promise = $this->resolver->resolve('google.com');
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
|
||||
public function testCancelResolveCachedShouldNotCauseGarbageReferences()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$factory = new Factory();
|
||||
$this->resolver = $factory->createCached('127.0.0.1', $this->loop);
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
$promise = $this->resolver->resolve('google.com');
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
}
|
||||
31
vendor/react/dns/tests/Model/MessageTest.php
vendored
Executable file
31
vendor/react/dns/tests/Model/MessageTest.php
vendored
Executable file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Model;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
class MessageTest extends TestCase
|
||||
{
|
||||
public function testCreateRequestDesiresRecusion()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
$this->assertTrue($request->header->isQuery());
|
||||
$this->assertSame(1, $request->header->get('rd'));
|
||||
}
|
||||
|
||||
public function testCreateResponseWithNoAnswers()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$answers = array();
|
||||
$request = Message::createResponseWithAnswersForQuery($query, $answers);
|
||||
|
||||
$this->assertFalse($request->header->isQuery());
|
||||
$this->assertTrue($request->header->isResponse());
|
||||
$this->assertEquals(0, $request->header->get('anCount'));
|
||||
$this->assertEquals(Message::RCODE_OK, $request->getResponseCode());
|
||||
}
|
||||
}
|
||||
278
vendor/react/dns/tests/Protocol/BinaryDumperTest.php
vendored
Executable file
278
vendor/react/dns/tests/Protocol/BinaryDumperTest.php
vendored
Executable file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Protocol;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
|
||||
class BinaryDumperTest extends TestCase
|
||||
{
|
||||
public function testToBinaryRequestMessage()
|
||||
{
|
||||
$data = "";
|
||||
$data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
|
||||
$data .= "00 01 00 01"; // question: type A, class IN
|
||||
|
||||
$expected = $this->formatHexDump($data);
|
||||
|
||||
$request = new Message();
|
||||
$request->header->set('id', 0x7262);
|
||||
$request->header->set('rd', 1);
|
||||
|
||||
$request->questions[] = array(
|
||||
'name' => 'igor.io',
|
||||
'type' => Message::TYPE_A,
|
||||
'class' => Message::CLASS_IN,
|
||||
);
|
||||
|
||||
$request->prepare();
|
||||
|
||||
$dumper = new BinaryDumper();
|
||||
$data = $dumper->toBinary($request);
|
||||
$data = $this->convertBinaryToHexDump($data);
|
||||
|
||||
$this->assertSame($expected, $data);
|
||||
}
|
||||
|
||||
public function testToBinaryRequestMessageWithCustomOptForEdns0()
|
||||
{
|
||||
$data = "";
|
||||
$data .= "72 62 01 00 00 01 00 00 00 00 00 01"; // header
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
|
||||
$data .= "00 01 00 01"; // question: type A, class IN
|
||||
$data .= "00"; // additional: (empty hostname)
|
||||
$data .= "00 29 03 e8 00 00 00 00 00 00 "; // additional: type OPT, class UDP size, TTL 0, no RDATA
|
||||
|
||||
$expected = $this->formatHexDump($data);
|
||||
|
||||
$request = new Message();
|
||||
$request->header->set('id', 0x7262);
|
||||
$request->header->set('rd', 1);
|
||||
|
||||
$request->questions[] = array(
|
||||
'name' => 'igor.io',
|
||||
'type' => Message::TYPE_A,
|
||||
'class' => Message::CLASS_IN,
|
||||
);
|
||||
|
||||
$request->additional[] = new Record('', 41, 1000, 0, '');
|
||||
|
||||
$request->prepare();
|
||||
|
||||
$dumper = new BinaryDumper();
|
||||
$data = $dumper->toBinary($request);
|
||||
$data = $this->convertBinaryToHexDump($data);
|
||||
|
||||
$this->assertSame($expected, $data);
|
||||
}
|
||||
|
||||
public function testToBinaryResponseMessageWithoutRecords()
|
||||
{
|
||||
$data = "";
|
||||
$data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
|
||||
$data .= "00 01 00 01"; // question: type A, class IN
|
||||
|
||||
$expected = $this->formatHexDump($data);
|
||||
|
||||
$response = new Message();
|
||||
$response->header->set('id', 0x7262);
|
||||
$response->header->set('rd', 1);
|
||||
$response->header->set('rcode', Message::RCODE_OK);
|
||||
|
||||
$response->questions[] = array(
|
||||
'name' => 'igor.io',
|
||||
'type' => Message::TYPE_A,
|
||||
'class' => Message::CLASS_IN
|
||||
);
|
||||
|
||||
$response->prepare();
|
||||
|
||||
$dumper = new BinaryDumper();
|
||||
$data = $dumper->toBinary($response);
|
||||
$data = $this->convertBinaryToHexDump($data);
|
||||
|
||||
$this->assertSame($expected, $data);
|
||||
}
|
||||
|
||||
public function testToBinaryForResponseWithSRVRecord()
|
||||
{
|
||||
$data = "";
|
||||
$data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
|
||||
$data .= "00 21 00 01"; // question: type SRV, class IN
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
|
||||
$data .= "00 21 00 01"; // answer: type SRV, class IN
|
||||
$data .= "00 01 51 80"; // answer: ttl 86400
|
||||
$data .= "00 0c"; // answer: rdlength 12
|
||||
$data .= "00 0a 00 14 1f 90 04 74 65 73 74 00"; // answer: rdata priority 10, weight 20, port 8080 test
|
||||
|
||||
$expected = $this->formatHexDump($data);
|
||||
|
||||
$response = new Message();
|
||||
$response->header->set('id', 0x7262);
|
||||
$response->header->set('rd', 1);
|
||||
$response->header->set('rcode', Message::RCODE_OK);
|
||||
|
||||
$response->questions[] = array(
|
||||
'name' => 'igor.io',
|
||||
'type' => Message::TYPE_SRV,
|
||||
'class' => Message::CLASS_IN
|
||||
);
|
||||
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_SRV, Message::CLASS_IN, 86400, array(
|
||||
'priority' => 10,
|
||||
'weight' => 20,
|
||||
'port' => 8080,
|
||||
'target' => 'test'
|
||||
));
|
||||
$response->prepare();
|
||||
|
||||
$dumper = new BinaryDumper();
|
||||
$data = $dumper->toBinary($response);
|
||||
$data = $this->convertBinaryToHexDump($data);
|
||||
|
||||
$this->assertSame($expected, $data);
|
||||
}
|
||||
|
||||
public function testToBinaryForResponseWithSOARecord()
|
||||
{
|
||||
$data = "";
|
||||
$data .= "72 62 01 00 00 01 00 01 00 00 00 00"; // header
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
|
||||
$data .= "00 06 00 01"; // question: type SOA, class IN
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
|
||||
$data .= "00 06 00 01"; // answer: type SOA, class IN
|
||||
$data .= "00 01 51 80"; // answer: ttl 86400
|
||||
$data .= "00 27"; // answer: rdlength 39
|
||||
$data .= "02 6e 73 05 68 65 6c 6c 6f 00"; // answer: rdata ns.hello (mname)
|
||||
$data .= "01 65 05 68 65 6c 6c 6f 00"; // answer: rdata e.hello (rname)
|
||||
$data .= "78 49 28 d5 00 00 2a 30 00 00 0e 10"; // answer: rdata 2018060501, 10800, 3600
|
||||
$data .= "00 09 3e 68 00 00 0e 10"; // answer: 605800, 3600
|
||||
|
||||
$expected = $this->formatHexDump($data);
|
||||
|
||||
$response = new Message();
|
||||
$response->header->set('id', 0x7262);
|
||||
$response->header->set('rd', 1);
|
||||
$response->header->set('rcode', Message::RCODE_OK);
|
||||
|
||||
$response->questions[] = array(
|
||||
'name' => 'igor.io',
|
||||
'type' => Message::TYPE_SOA,
|
||||
'class' => Message::CLASS_IN
|
||||
);
|
||||
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_SOA, Message::CLASS_IN, 86400, array(
|
||||
'mname' => 'ns.hello',
|
||||
'rname' => 'e.hello',
|
||||
'serial' => 2018060501,
|
||||
'refresh' => 10800,
|
||||
'retry' => 3600,
|
||||
'expire' => 605800,
|
||||
'minimum' => 3600
|
||||
));
|
||||
$response->prepare();
|
||||
|
||||
$dumper = new BinaryDumper();
|
||||
$data = $dumper->toBinary($response);
|
||||
$data = $this->convertBinaryToHexDump($data);
|
||||
|
||||
$this->assertSame($expected, $data);
|
||||
}
|
||||
|
||||
public function testToBinaryForResponseWithMultipleAnswerRecords()
|
||||
{
|
||||
$data = "";
|
||||
$data .= "72 62 01 00 00 01 00 04 00 00 00 00"; // header
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
|
||||
$data .= "00 ff 00 01"; // question: type ANY, class IN
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
|
||||
$data .= "00 01 00 01 00 00 00 00 00 04"; // answer: type A, class IN, TTL 0, 4 bytes
|
||||
$data .= "7f 00 00 01"; // answer: 127.0.0.1
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
|
||||
$data .= "00 1c 00 01 00 00 00 00 00 10"; // question: type AAAA, class IN, TTL 0, 16 bytes
|
||||
$data .= "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"; // answer: ::1
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
|
||||
$data .= "00 10 00 01 00 00 00 00 00 0c"; // answer: type TXT, class IN, TTL 0, 12 bytes
|
||||
$data .= "05 68 65 6c 6c 6f 05 77 6f 72 6c 64"; // answer: hello, world
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
|
||||
$data .= "00 0f 00 01 00 00 00 00 00 03"; // anwser: type MX, class IN, TTL 0, 3 bytes
|
||||
$data .= "00 00 00"; // priority 0, no target
|
||||
|
||||
$expected = $this->formatHexDump($data);
|
||||
|
||||
$response = new Message();
|
||||
$response->header->set('id', 0x7262);
|
||||
$response->header->set('rd', 1);
|
||||
$response->header->set('rcode', Message::RCODE_OK);
|
||||
|
||||
$response->questions[] = array(
|
||||
'name' => 'igor.io',
|
||||
'type' => Message::TYPE_ANY,
|
||||
'class' => Message::CLASS_IN
|
||||
);
|
||||
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_AAAA, Message::CLASS_IN, 0, '::1');
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_TXT, Message::CLASS_IN, 0, array('hello', 'world'));
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 0, array('priority' => 0, 'target' => ''));
|
||||
$response->prepare();
|
||||
|
||||
$dumper = new BinaryDumper();
|
||||
$data = $dumper->toBinary($response);
|
||||
$data = $this->convertBinaryToHexDump($data);
|
||||
|
||||
$this->assertSame($expected, $data);
|
||||
}
|
||||
|
||||
public function testToBinaryForResponseWithAnswerAndAdditionalRecord()
|
||||
{
|
||||
$data = "";
|
||||
$data .= "72 62 01 00 00 01 00 01 00 00 00 01"; // header
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
|
||||
$data .= "00 02 00 01"; // question: type NS, class IN
|
||||
$data .= "04 69 67 6f 72 02 69 6f 00"; // answer: igor.io
|
||||
$data .= "00 02 00 01 00 00 00 00 00 0d"; // answer: type NS, class IN, TTL 0, 10 bytes
|
||||
$data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // answer: example.com
|
||||
$data .= "07 65 78 61 6d 70 6c 65 03 63 6f 6d 00"; // additional: example.com
|
||||
$data .= "00 01 00 01 00 00 00 00 00 04"; // additional: type A, class IN, TTL 0, 4 bytes
|
||||
$data .= "7f 00 00 01"; // additional: 127.0.0.1
|
||||
|
||||
$expected = $this->formatHexDump($data);
|
||||
|
||||
$response = new Message();
|
||||
$response->header->set('id', 0x7262);
|
||||
$response->header->set('rd', 1);
|
||||
$response->header->set('rcode', Message::RCODE_OK);
|
||||
|
||||
$response->questions[] = array(
|
||||
'name' => 'igor.io',
|
||||
'type' => Message::TYPE_NS,
|
||||
'class' => Message::CLASS_IN
|
||||
);
|
||||
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_NS, Message::CLASS_IN, 0, 'example.com');
|
||||
$response->additional[] = new Record('example.com', Message::TYPE_A, Message::CLASS_IN, 0, '127.0.0.1');
|
||||
$response->prepare();
|
||||
|
||||
$dumper = new BinaryDumper();
|
||||
$data = $dumper->toBinary($response);
|
||||
$data = $this->convertBinaryToHexDump($data);
|
||||
|
||||
$this->assertSame($expected, $data);
|
||||
}
|
||||
|
||||
private function convertBinaryToHexDump($input)
|
||||
{
|
||||
return $this->formatHexDump(implode('', unpack('H*', $input)));
|
||||
}
|
||||
|
||||
private function formatHexDump($input)
|
||||
{
|
||||
return implode(' ', str_split(str_replace(' ', '', $input), 2));
|
||||
}
|
||||
}
|
||||
1033
vendor/react/dns/tests/Protocol/ParserTest.php
vendored
Executable file
1033
vendor/react/dns/tests/Protocol/ParserTest.php
vendored
Executable file
File diff suppressed because it is too large
Load Diff
100
vendor/react/dns/tests/Query/CachedExecutorTest.php
vendored
Executable file
100
vendor/react/dns/tests/Query/CachedExecutorTest.php
vendored
Executable file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Dns\Query\CachedExecutor;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
|
||||
class CachedExecutorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Dns\Query\CachedExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldDelegateToDecoratedExecutor()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnValue($this->createPromiseMock()));
|
||||
|
||||
$cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$cache
|
||||
->expects($this->once())
|
||||
->method('lookup')
|
||||
->will($this->returnValue(Promise\reject()));
|
||||
$cachedExecutor = new CachedExecutor($executor, $cache);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$cachedExecutor->query('8.8.8.8', $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\CachedExecutor
|
||||
* @test
|
||||
*/
|
||||
public function callingQueryTwiceShouldUseCachedResult()
|
||||
{
|
||||
$cachedRecords = array(new Record('igor.io', Message::TYPE_A, Message::CLASS_IN));
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->will($this->callQueryCallbackWithAddress('178.79.169.131'));
|
||||
|
||||
$cache = $this->getMockBuilder('React\Dns\Query\RecordCache')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$cache
|
||||
->expects($this->at(0))
|
||||
->method('lookup')
|
||||
->with($this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnValue(Promise\reject()));
|
||||
$cache
|
||||
->expects($this->at(1))
|
||||
->method('storeResponseMessage')
|
||||
->with($this->isType('integer'), $this->isInstanceOf('React\Dns\Model\Message'));
|
||||
$cache
|
||||
->expects($this->at(2))
|
||||
->method('lookup')
|
||||
->with($this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnValue(Promise\resolve($cachedRecords)));
|
||||
|
||||
$cachedExecutor = new CachedExecutor($executor, $cache);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
|
||||
$cachedExecutor->query('8.8.8.8', $query, function () {}, function () {});
|
||||
}
|
||||
|
||||
private function callQueryCallbackWithAddress($address)
|
||||
{
|
||||
return $this->returnCallback(function ($nameserver, $query) use ($address) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, $address);
|
||||
|
||||
return Promise\resolve($response);
|
||||
});
|
||||
}
|
||||
|
||||
private function createExecutorMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
}
|
||||
|
||||
private function createPromiseMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
|
||||
}
|
||||
}
|
||||
183
vendor/react/dns/tests/Query/CachingExecutorTest.php
vendored
Executable file
183
vendor/react/dns/tests/Query/CachingExecutorTest.php
vendored
Executable file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\CachingExecutor;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Promise\Promise;
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Promise\Deferred;
|
||||
use React\Dns\Model\Record;
|
||||
|
||||
class CachingExecutorTest extends TestCase
|
||||
{
|
||||
public function testQueryWillReturnPendingPromiseWhenCacheIsPendingWithoutSendingQueryToFallbackExecutor()
|
||||
{
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->never())->method('query');
|
||||
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(new Promise(function () { }));
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testQueryWillReturnPendingPromiseWhenCacheReturnsMissAndWillSendSameQueryToFallbackExecutor()
|
||||
{
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn(new Promise(function () { }));
|
||||
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsHitWithoutSendingQueryToFallbackExecutor()
|
||||
{
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->never())->method('query');
|
||||
|
||||
$message = new Message();
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve($message));
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithMinimumTtlFromRecord()
|
||||
{
|
||||
$message = new Message();
|
||||
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3700, '127.0.0.1');
|
||||
$message->answers[] = new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '127.0.0.1');
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
|
||||
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
|
||||
$cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 3600);
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesAndSaveMessageToCacheWithDefaultTtl()
|
||||
{
|
||||
$message = new Message();
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
|
||||
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
|
||||
$cache->expects($this->once())->method('set')->with('reactphp.org:1:1', $message, 60);
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testQueryWillReturnResolvedPromiseWhenCacheReturnsMissAndFallbackExecutorResolvesWithTruncatedResponseButShouldNotSaveTruncatedMessageToCache()
|
||||
{
|
||||
$message = new Message();
|
||||
$message->header->set('tc', 1);
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
|
||||
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn(\React\Promise\resolve(null));
|
||||
$cache->expects($this->never())->method('set');
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableOnceWith($message), $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testQueryWillReturnRejectedPromiseWhenCacheReturnsMissAndFallbackExecutorRejects()
|
||||
{
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->once())->method('query')->willReturn(\React\Promise\reject(new \RuntimeException()));
|
||||
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
|
||||
}
|
||||
|
||||
public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromCache()
|
||||
{
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->never())->method('query');
|
||||
|
||||
$pending = new Promise(function () { }, $this->expectCallableOnce());
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($pending);
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
|
||||
}
|
||||
|
||||
public function testCancelQueryWillReturnRejectedPromiseAndCancelPendingPromiseFromFallbackExecutorWhenCacheReturnsMiss()
|
||||
{
|
||||
$pending = new Promise(function () { }, $this->expectCallableOnce());
|
||||
$fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$fallback->expects($this->once())->method('query')->willReturn($pending);
|
||||
|
||||
$deferred = new Deferred();
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$cache->expects($this->once())->method('get')->with('reactphp.org:1:1')->willReturn($deferred->promise());
|
||||
|
||||
$executor = new CachingExecutor($fallback, $cache);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query('8.8.8.8', $query);
|
||||
$deferred->resolve(null);
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
|
||||
}
|
||||
}
|
||||
233
vendor/react/dns/tests/Query/CoopExecutorTest.php
vendored
Executable file
233
vendor/react/dns/tests/Query/CoopExecutorTest.php
vendored
Executable file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
use React\Dns\Query\CoopExecutor;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Promise\Promise;
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
class CoopExecutorTest extends TestCase
|
||||
{
|
||||
public function testQueryOnceWillPassExactQueryToBaseExecutor()
|
||||
{
|
||||
$pending = new Promise(function () { });
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$connector->query('8.8.8.8', $query);
|
||||
}
|
||||
|
||||
public function testQueryOnceWillResolveWhenBaseExecutorResolves()
|
||||
{
|
||||
$message = new Message();
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->willReturn(\React\Promise\resolve($message));
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise = $connector->query('8.8.8.8', $query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
|
||||
$promise->then($this->expectCallableOnceWith($message));
|
||||
}
|
||||
|
||||
public function testQueryOnceWillRejectWhenBaseExecutorRejects()
|
||||
{
|
||||
$exception = new RuntimeException();
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->willReturn(\React\Promise\reject($exception));
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise = $connector->query('8.8.8.8', $query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
|
||||
$promise->then(null, $this->expectCallableOnceWith($exception));
|
||||
}
|
||||
|
||||
public function testQueryTwoDifferentQueriesWillPassExactQueryToBaseExecutorTwice()
|
||||
{
|
||||
$pending = new Promise(function () { });
|
||||
$query1 = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$query2 = new Query('reactphp.org', Message::TYPE_AAAA, Message::CLASS_IN);
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->exactly(2))->method('query')->withConsecutive(
|
||||
array('8.8.8.8', $query1),
|
||||
array('8.8.8.8', $query2)
|
||||
)->willReturn($pending);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$connector->query('8.8.8.8', $query1);
|
||||
$connector->query('8.8.8.8', $query2);
|
||||
}
|
||||
|
||||
public function testQueryTwiceWillPassExactQueryToBaseExecutorOnceWhenQueryIsStillPending()
|
||||
{
|
||||
$pending = new Promise(function () { });
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->with('8.8.8.8', $query)->willReturn($pending);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$connector->query('8.8.8.8', $query);
|
||||
$connector->query('8.8.8.8', $query);
|
||||
}
|
||||
|
||||
public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyResolved()
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$pending = new Promise(function () { });
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
|
||||
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$connector->query('8.8.8.8', $query);
|
||||
|
||||
$deferred->resolve(new Message());
|
||||
|
||||
$connector->query('8.8.8.8', $query);
|
||||
}
|
||||
|
||||
public function testQueryTwiceWillPassExactQueryToBaseExecutorTwiceWhenFirstQueryIsAlreadyRejected()
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$pending = new Promise(function () { });
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->exactly(2))->method('query')->with('8.8.8.8', $query)->willReturnOnConsecutiveCalls($deferred->promise(), $pending);
|
||||
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$connector->query('8.8.8.8', $query);
|
||||
|
||||
$deferred->reject(new RuntimeException());
|
||||
|
||||
$connector->query('8.8.8.8', $query);
|
||||
}
|
||||
|
||||
public function testCancelQueryWillCancelPromiseFromBaseExecutorAndReject()
|
||||
{
|
||||
$promise = new Promise(function () { }, $this->expectCallableOnce());
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->willReturn($promise);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise = $connector->query('8.8.8.8', $query);
|
||||
|
||||
$promise->cancel();
|
||||
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testCancelOneQueryWhenOtherQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
|
||||
{
|
||||
$promise = new Promise(function () { }, $this->expectCallableNever());
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->willReturn($promise);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise1 = $connector->query('8.8.8.8', $query);
|
||||
$promise2 = $connector->query('8.8.8.8', $query);
|
||||
|
||||
$promise1->cancel();
|
||||
|
||||
$promise1->then(null, $this->expectCallableOnce());
|
||||
$promise2->then(null, $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testCancelSecondQueryWhenFirstQueryIsStillPendingWillNotCancelPromiseFromBaseExecutorAndRejectCancelled()
|
||||
{
|
||||
$promise = new Promise(function () { }, $this->expectCallableNever());
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->willReturn($promise);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise1 = $connector->query('8.8.8.8', $query);
|
||||
$promise2 = $connector->query('8.8.8.8', $query);
|
||||
|
||||
$promise2->cancel();
|
||||
|
||||
$promise2->then(null, $this->expectCallableOnce());
|
||||
$promise1->then(null, $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testCancelAllPendingQueriesWillCancelPromiseFromBaseExecutorAndRejectCancelled()
|
||||
{
|
||||
$promise = new Promise(function () { }, $this->expectCallableOnce());
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->willReturn($promise);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise1 = $connector->query('8.8.8.8', $query);
|
||||
$promise2 = $connector->query('8.8.8.8', $query);
|
||||
|
||||
$promise1->cancel();
|
||||
$promise2->cancel();
|
||||
|
||||
$promise1->then(null, $this->expectCallableOnce());
|
||||
$promise2->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testQueryTwiceWillQueryBaseExecutorTwiceIfFirstQueryHasAlreadyBeenCancelledWhenSecondIsStarted()
|
||||
{
|
||||
$promise = new Promise(function () { }, $this->expectCallableOnce());
|
||||
$pending = new Promise(function () { });
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->exactly(2))->method('query')->willReturnOnConsecutiveCalls($promise, $pending);
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise1 = $connector->query('8.8.8.8', $query);
|
||||
$promise1->cancel();
|
||||
|
||||
$promise2 = $connector->query('8.8.8.8', $query);
|
||||
|
||||
$promise1->then(null, $this->expectCallableOnce());
|
||||
|
||||
$promise2->then(null, $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testCancelQueryShouldNotCauseGarbageReferences()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$deferred = new Deferred(function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
$base = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$base->expects($this->once())->method('query')->willReturn($deferred->promise());
|
||||
$connector = new CoopExecutor($base);
|
||||
|
||||
gc_collect_cycles();
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $connector->query('8.8.8.8', $query);
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
}
|
||||
308
vendor/react/dns/tests/Query/ExecutorTest.php
vendored
Executable file
308
vendor/react/dns/tests/Query/ExecutorTest.php
vendored
Executable file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use Clue\React\Block;
|
||||
use React\Dns\Query\Executor;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Tests\Dns\TestCase;
|
||||
|
||||
class ExecutorTest extends TestCase
|
||||
{
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
private $executor;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
$this->parser = $this->getMockBuilder('React\Dns\Protocol\Parser')->getMock();
|
||||
$this->dumper = new BinaryDumper();
|
||||
|
||||
$this->executor = new Executor($this->loop, $this->parser, $this->dumper);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function queryShouldCreateUdpRequest()
|
||||
{
|
||||
$timer = $this->createTimerMock();
|
||||
$this->loop
|
||||
->expects($this->any())
|
||||
->method('addTimer')
|
||||
->will($this->returnValue($timer));
|
||||
|
||||
$this->executor = $this->createExecutorMock();
|
||||
$this->executor
|
||||
->expects($this->once())
|
||||
->method('createConnection')
|
||||
->with('8.8.8.8:53', 'udp')
|
||||
->will($this->returnNewConnectionMock(false));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$this->executor->query('8.8.8.8:53', $query);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldRejectIfRequestIsLargerThan512Bytes()
|
||||
{
|
||||
$query = new Query(str_repeat('a', 512).'.igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$this->setExpectedException('RuntimeException', 'DNS query for ' . $query->name . ' failed: Requested transport "tcp" not available, only UDP is supported in this version');
|
||||
Block\await($promise, $this->loop);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldCloseConnectionWhenCancelled()
|
||||
{
|
||||
$conn = $this->createConnectionMock(false);
|
||||
$conn->expects($this->once())->method('close');
|
||||
|
||||
$timer = $this->createTimerMock();
|
||||
$this->loop
|
||||
->expects($this->any())
|
||||
->method('addTimer')
|
||||
->will($this->returnValue($timer));
|
||||
|
||||
$this->executor = $this->createExecutorMock();
|
||||
$this->executor
|
||||
->expects($this->once())
|
||||
->method('createConnection')
|
||||
->with('8.8.8.8:53', 'udp')
|
||||
->will($this->returnValue($conn));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$promise->cancel();
|
||||
|
||||
$this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
|
||||
Block\await($promise, $this->loop);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldNotStartOrCancelTimerWhenCancelledWithTimeoutIsNull()
|
||||
{
|
||||
$this->loop
|
||||
->expects($this->never())
|
||||
->method('addTimer');
|
||||
|
||||
$this->executor = new Executor($this->loop, $this->parser, $this->dumper, null);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('127.0.0.1:53', $query);
|
||||
|
||||
$promise->cancel();
|
||||
|
||||
$this->setExpectedException('React\Dns\Query\CancellationException', 'DNS query for igor.io has been cancelled');
|
||||
Block\await($promise, $this->loop);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldRejectIfResponseIsTruncated()
|
||||
{
|
||||
$timer = $this->createTimerMock();
|
||||
|
||||
$this->loop
|
||||
->expects($this->any())
|
||||
->method('addTimer')
|
||||
->will($this->returnValue($timer));
|
||||
|
||||
$this->parser
|
||||
->expects($this->once())
|
||||
->method('parseMessage')
|
||||
->will($this->returnTruncatedResponse());
|
||||
|
||||
$this->executor = $this->createExecutorMock();
|
||||
$this->executor
|
||||
->expects($this->once())
|
||||
->method('createConnection')
|
||||
->with('8.8.8.8:53', 'udp')
|
||||
->will($this->returnNewConnectionMock());
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$this->executor->query('8.8.8.8:53', $query);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldFailIfUdpThrow()
|
||||
{
|
||||
$this->loop
|
||||
->expects($this->never())
|
||||
->method('addTimer');
|
||||
|
||||
$this->parser
|
||||
->expects($this->never())
|
||||
->method('parseMessage');
|
||||
|
||||
$this->executor = $this->createExecutorMock();
|
||||
$this->executor
|
||||
->expects($this->once())
|
||||
->method('createConnection')
|
||||
->with('8.8.8.8:53', 'udp')
|
||||
->will($this->throwException(new \Exception('Nope')));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$this->setExpectedException('RuntimeException', 'DNS query for igor.io failed: Nope');
|
||||
Block\await($promise, $this->loop);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldCancelTimerWhenFullResponseIsReceived()
|
||||
{
|
||||
$conn = $this->createConnectionMock();
|
||||
|
||||
$this->parser
|
||||
->expects($this->once())
|
||||
->method('parseMessage')
|
||||
->will($this->returnStandardResponse());
|
||||
|
||||
$this->executor = $this->createExecutorMock();
|
||||
$this->executor
|
||||
->expects($this->at(0))
|
||||
->method('createConnection')
|
||||
->with('8.8.8.8:53', 'udp')
|
||||
->will($this->returnNewConnectionMock());
|
||||
|
||||
|
||||
$timer = $this->createTimerMock();
|
||||
|
||||
$this->loop
|
||||
->expects($this->once())
|
||||
->method('addTimer')
|
||||
->with(5, $this->isInstanceOf('Closure'))
|
||||
->will($this->returnValue($timer));
|
||||
|
||||
$this->loop
|
||||
->expects($this->once())
|
||||
->method('cancelTimer')
|
||||
->with($timer);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$this->executor->query('8.8.8.8:53', $query);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldCloseConnectionOnTimeout()
|
||||
{
|
||||
$this->executor = $this->createExecutorMock();
|
||||
$this->executor
|
||||
->expects($this->at(0))
|
||||
->method('createConnection')
|
||||
->with('8.8.8.8:53', 'udp')
|
||||
->will($this->returnNewConnectionMock(false));
|
||||
|
||||
$timer = $this->createTimerMock();
|
||||
|
||||
$this->loop
|
||||
->expects($this->never())
|
||||
->method('cancelTimer');
|
||||
|
||||
$this->loop
|
||||
->expects($this->once())
|
||||
->method('addTimer')
|
||||
->with(5, $this->isInstanceOf('Closure'))
|
||||
->will($this->returnCallback(function ($time, $callback) use (&$timerCallback, $timer) {
|
||||
$timerCallback = $callback;
|
||||
return $timer;
|
||||
}));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$this->assertNotNull($timerCallback);
|
||||
$timerCallback();
|
||||
|
||||
$this->setExpectedException('React\Dns\Query\TimeoutException', 'DNS query for igor.io timed out');
|
||||
Block\await($promise, $this->loop);
|
||||
}
|
||||
|
||||
private function returnStandardResponse()
|
||||
{
|
||||
$that = $this;
|
||||
$callback = function ($data) use ($that) {
|
||||
$response = new Message();
|
||||
$that->convertMessageToStandardResponse($response);
|
||||
return $response;
|
||||
};
|
||||
|
||||
return $this->returnCallback($callback);
|
||||
}
|
||||
|
||||
private function returnTruncatedResponse()
|
||||
{
|
||||
$that = $this;
|
||||
$callback = function ($data) use ($that) {
|
||||
$response = new Message();
|
||||
$that->convertMessageToTruncatedResponse($response);
|
||||
return $response;
|
||||
};
|
||||
|
||||
return $this->returnCallback($callback);
|
||||
}
|
||||
|
||||
public function convertMessageToStandardResponse(Message $response)
|
||||
{
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
|
||||
$response->prepare();
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function convertMessageToTruncatedResponse(Message $response)
|
||||
{
|
||||
$this->convertMessageToStandardResponse($response);
|
||||
$response->header->set('tc', 1);
|
||||
$response->prepare();
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function returnNewConnectionMock($emitData = true)
|
||||
{
|
||||
$conn = $this->createConnectionMock($emitData);
|
||||
|
||||
$callback = function () use ($conn) {
|
||||
return $conn;
|
||||
};
|
||||
|
||||
return $this->returnCallback($callback);
|
||||
}
|
||||
|
||||
private function createConnectionMock($emitData = true)
|
||||
{
|
||||
$conn = $this->getMockBuilder('React\Stream\DuplexStreamInterface')->getMock();
|
||||
$conn
|
||||
->expects($this->any())
|
||||
->method('on')
|
||||
->with('data', $this->isInstanceOf('Closure'))
|
||||
->will($this->returnCallback(function ($name, $callback) use ($emitData) {
|
||||
$emitData && $callback(null);
|
||||
}));
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
private function createTimerMock()
|
||||
{
|
||||
return $this->getMockBuilder(
|
||||
interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface'
|
||||
)->getMock();
|
||||
}
|
||||
|
||||
private function createExecutorMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Dns\Query\Executor')
|
||||
->setConstructorArgs(array($this->loop, $this->parser, $this->dumper))
|
||||
->setMethods(array('createConnection'))
|
||||
->getMock();
|
||||
}
|
||||
}
|
||||
126
vendor/react/dns/tests/Query/HostsFileExecutorTest.php
vendored
Executable file
126
vendor/react/dns/tests/Query/HostsFileExecutorTest.php
vendored
Executable file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Dns\Query\HostsFileExecutor;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
class HostsFileExecutorTest extends TestCase
|
||||
{
|
||||
private $hosts;
|
||||
private $fallback;
|
||||
private $executor;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->hosts = $this->getMockBuilder('React\Dns\Config\HostsFile')->disableOriginalConstructor()->getMock();
|
||||
$this->fallback = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
$this->executor = new HostsFileExecutor($this->hosts, $this->fallback);
|
||||
}
|
||||
|
||||
public function testDoesNotTryToGetIpsForMxQuery()
|
||||
{
|
||||
$this->hosts->expects($this->never())->method('getIpsForHost');
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_MX, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testFallsBackIfNoIpsWereFound()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array());
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testReturnsResponseMessageIfIpsWereFound()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
|
||||
$this->fallback->expects($this->never())->method('query');
|
||||
|
||||
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testFallsBackIfNoIpv4Matches()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_A, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testReturnsResponseMessageIfIpv6AddressesWereFound()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('::1'));
|
||||
$this->fallback->expects($this->never())->method('query');
|
||||
|
||||
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testFallsBackIfNoIpv6Matches()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getIpsForHost')->willReturn(array('127.0.0.1'));
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$ret = $this->executor->query('8.8.8.8', new Query('google.com', Message::TYPE_AAAA, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testDoesReturnReverseIpv4Lookup()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array('localhost'));
|
||||
$this->fallback->expects($this->never())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testFallsBackIfNoReverseIpv4Matches()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getHostsForIp')->with('127.0.0.1')->willReturn(array());
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('1.0.0.127.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testDoesReturnReverseIpv6Lookup()
|
||||
{
|
||||
$this->hosts->expects($this->once())->method('getHostsForIp')->with('2a02:2e0:3fe:100::6')->willReturn(array('ip6-localhost'));
|
||||
$this->fallback->expects($this->never())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.e.f.3.0.0.e.2.0.2.0.a.2.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testFallsBackForInvalidAddress()
|
||||
{
|
||||
$this->hosts->expects($this->never())->method('getHostsForIp');
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('example.com', Message::TYPE_PTR, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testReverseFallsBackForInvalidIpv4Address()
|
||||
{
|
||||
$this->hosts->expects($this->never())->method('getHostsForIp');
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('::1.in-addr.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testReverseFallsBackForInvalidLengthIpv6Address()
|
||||
{
|
||||
$this->hosts->expects($this->never())->method('getHostsForIp');
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('abcd.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
|
||||
}
|
||||
|
||||
public function testReverseFallsBackForInvalidHexIpv6Address()
|
||||
{
|
||||
$this->hosts->expects($this->never())->method('getHostsForIp');
|
||||
$this->fallback->expects($this->once())->method('query');
|
||||
|
||||
$this->executor->query('8.8.8.8', new Query('zZz.ip6.arpa', Message::TYPE_PTR, Message::CLASS_IN, 0));
|
||||
}
|
||||
}
|
||||
83
vendor/react/dns/tests/Query/RecordBagTest.php
vendored
Executable file
83
vendor/react/dns/tests/Query/RecordBagTest.php
vendored
Executable file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use React\Dns\Query\RecordBag;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
|
||||
class RecordBagTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordBag
|
||||
* @test
|
||||
*/
|
||||
public function emptyBagShouldBeEmpty()
|
||||
{
|
||||
$recordBag = new RecordBag();
|
||||
|
||||
$this->assertSame(array(), $recordBag->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordBag
|
||||
* @test
|
||||
*/
|
||||
public function setShouldSetTheValue()
|
||||
{
|
||||
$currentTime = 1345656451;
|
||||
|
||||
$recordBag = new RecordBag();
|
||||
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600));
|
||||
|
||||
$records = $recordBag->all();
|
||||
$this->assertCount(1, $records);
|
||||
$this->assertSame('igor.io', $records[0]->name);
|
||||
$this->assertSame(Message::TYPE_A, $records[0]->type);
|
||||
$this->assertSame(Message::CLASS_IN, $records[0]->class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordBag
|
||||
* @test
|
||||
*/
|
||||
public function setShouldAcceptMxRecord()
|
||||
{
|
||||
$currentTime = 1345656451;
|
||||
|
||||
$recordBag = new RecordBag();
|
||||
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_MX, Message::CLASS_IN, 3600, array('priority' => 10, 'target' => 'igor.io')));
|
||||
|
||||
$records = $recordBag->all();
|
||||
$this->assertCount(1, $records);
|
||||
$this->assertSame('igor.io', $records[0]->name);
|
||||
$this->assertSame(Message::TYPE_MX, $records[0]->type);
|
||||
$this->assertSame(Message::CLASS_IN, $records[0]->class);
|
||||
$this->assertSame(array('priority' => 10, 'target' => 'igor.io'), $records[0]->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordBag
|
||||
* @test
|
||||
*/
|
||||
public function setShouldSetManyValues()
|
||||
{
|
||||
$currentTime = 1345656451;
|
||||
|
||||
$recordBag = new RecordBag();
|
||||
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
|
||||
$recordBag->set($currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
|
||||
|
||||
$records = $recordBag->all();
|
||||
$this->assertCount(2, $records);
|
||||
$this->assertSame('igor.io', $records[0]->name);
|
||||
$this->assertSame(Message::TYPE_A, $records[0]->type);
|
||||
$this->assertSame(Message::CLASS_IN, $records[0]->class);
|
||||
$this->assertSame('178.79.169.131', $records[0]->data);
|
||||
$this->assertSame('igor.io', $records[1]->name);
|
||||
$this->assertSame(Message::TYPE_A, $records[1]->type);
|
||||
$this->assertSame(Message::CLASS_IN, $records[1]->class);
|
||||
$this->assertSame('178.79.169.132', $records[1]->data);
|
||||
}
|
||||
}
|
||||
160
vendor/react/dns/tests/Query/RecordCacheTest.php
vendored
Executable file
160
vendor/react/dns/tests/Query/RecordCacheTest.php
vendored
Executable file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use React\Cache\ArrayCache;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\RecordCache;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
class RecordCacheTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordCache
|
||||
* @test
|
||||
*/
|
||||
public function lookupOnCacheMissShouldReturnNull()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
|
||||
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
|
||||
|
||||
$cache = new RecordCache($base);
|
||||
$promise = $cache->lookup($query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordCache
|
||||
* @test
|
||||
*/
|
||||
public function storeRecordPendingCacheDoesNotSetCache()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$pending = new Promise(function () { });
|
||||
|
||||
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$base->expects($this->once())->method('get')->willReturn($pending);
|
||||
$base->expects($this->never())->method('set');
|
||||
|
||||
$cache = new RecordCache($base);
|
||||
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordCache
|
||||
* @test
|
||||
*/
|
||||
public function storeRecordOnCacheMissSetsCache()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
|
||||
$base = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$base->expects($this->once())->method('get')->willReturn(\React\Promise\resolve(null));
|
||||
$base->expects($this->once())->method('set')->with($this->isType('string'), $this->isType('string'));
|
||||
|
||||
$cache = new RecordCache($base);
|
||||
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordCache
|
||||
* @test
|
||||
*/
|
||||
public function storeRecordShouldMakeLookupSucceed()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
|
||||
$cache = new RecordCache(new ArrayCache());
|
||||
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
|
||||
$promise = $cache->lookup($query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
|
||||
$cachedRecords = $this->getPromiseValue($promise);
|
||||
|
||||
$this->assertCount(1, $cachedRecords);
|
||||
$this->assertSame('178.79.169.131', $cachedRecords[0]->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordCache
|
||||
* @test
|
||||
*/
|
||||
public function storeTwoRecordsShouldReturnBoth()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
|
||||
$cache = new RecordCache(new ArrayCache());
|
||||
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
|
||||
$cache->storeRecord($query->currentTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'));
|
||||
$promise = $cache->lookup($query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
|
||||
$cachedRecords = $this->getPromiseValue($promise);
|
||||
|
||||
$this->assertCount(2, $cachedRecords);
|
||||
$this->assertSame('178.79.169.131', $cachedRecords[0]->data);
|
||||
$this->assertSame('178.79.169.132', $cachedRecords[1]->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordCache
|
||||
* @test
|
||||
*/
|
||||
public function storeResponseMessageShouldStoreAllAnswerValues()
|
||||
{
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
|
||||
$response = new Message();
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132');
|
||||
$response->prepare();
|
||||
|
||||
$cache = new RecordCache(new ArrayCache());
|
||||
$cache->storeResponseMessage($query->currentTime, $response);
|
||||
$promise = $cache->lookup($query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\FulfilledPromise', $promise);
|
||||
$cachedRecords = $this->getPromiseValue($promise);
|
||||
|
||||
$this->assertCount(2, $cachedRecords);
|
||||
$this->assertSame('178.79.169.131', $cachedRecords[0]->data);
|
||||
$this->assertSame('178.79.169.132', $cachedRecords[1]->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RecordCache
|
||||
* @test
|
||||
*/
|
||||
public function expireShouldExpireDeadRecords()
|
||||
{
|
||||
$cachedTime = 1345656451;
|
||||
$currentTime = $cachedTime + 3605;
|
||||
|
||||
$cache = new RecordCache(new ArrayCache());
|
||||
$cache->storeRecord($cachedTime, new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'));
|
||||
$cache->expire($currentTime);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, $currentTime);
|
||||
$promise = $cache->lookup($query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\RejectedPromise', $promise);
|
||||
}
|
||||
|
||||
private function getPromiseValue(PromiseInterface $promise)
|
||||
{
|
||||
$capturedValue = null;
|
||||
|
||||
$promise->then(function ($value) use (&$capturedValue) {
|
||||
$capturedValue = $value;
|
||||
});
|
||||
|
||||
return $capturedValue;
|
||||
}
|
||||
}
|
||||
350
vendor/react/dns/tests/Query/RetryExecutorTest.php
vendored
Executable file
350
vendor/react/dns/tests/Query/RetryExecutorTest.php
vendored
Executable file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Dns\Query\RetryExecutor;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\TimeoutException;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Dns\Query\CancellationException;
|
||||
|
||||
class RetryExecutorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldDelegateToDecoratedExecutor()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnValue($this->expectPromiseOnce()));
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 2);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$retryExecutor->query('8.8.8.8', $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldRetryQueryOnTimeout()
|
||||
{
|
||||
$response = $this->createStandardResponse();
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->exactly(2))
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->onConsecutiveCalls(
|
||||
$this->returnCallback(function ($domain, $query) {
|
||||
return Promise\reject(new TimeoutException("timeout"));
|
||||
}),
|
||||
$this->returnCallback(function ($domain, $query) use ($response) {
|
||||
return Promise\resolve($response);
|
||||
})
|
||||
));
|
||||
|
||||
$callback = $this->createCallableMock();
|
||||
$callback
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->isInstanceOf('React\Dns\Model\Message'));
|
||||
|
||||
$errorback = $this->expectCallableNever();
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 2);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldStopRetryingAfterSomeAttempts()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->exactly(3))
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($domain, $query) {
|
||||
return Promise\reject(new TimeoutException("timeout"));
|
||||
}));
|
||||
|
||||
$callback = $this->expectCallableNever();
|
||||
|
||||
$errorback = $this->createCallableMock();
|
||||
$errorback
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->isInstanceOf('RuntimeException'));
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 2);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldForwardNonTimeoutErrors()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($domain, $query) {
|
||||
return Promise\reject(new \Exception);
|
||||
}));
|
||||
|
||||
$callback = $this->expectCallableNever();
|
||||
|
||||
$errorback = $this->createCallableMock();
|
||||
$errorback
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->isInstanceOf('Exception'));
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 2);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$retryExecutor->query('8.8.8.8', $query)->then($callback, $errorback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldCancelQueryOnCancel()
|
||||
{
|
||||
$cancelled = 0;
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
|
||||
$deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
|
||||
++$cancelled;
|
||||
$reject(new CancellationException('Cancelled'));
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
})
|
||||
);
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 2);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $retryExecutor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
|
||||
|
||||
$this->assertEquals(0, $cancelled);
|
||||
$promise->cancel();
|
||||
$this->assertEquals(1, $cancelled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldCancelSecondQueryOnCancel()
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$cancelled = 0;
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->exactly(2))
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->onConsecutiveCalls(
|
||||
$this->returnValue($deferred->promise()),
|
||||
$this->returnCallback(function ($domain, $query) use (&$cancelled) {
|
||||
$deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
|
||||
++$cancelled;
|
||||
$reject(new CancellationException('Cancelled'));
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
})
|
||||
));
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 2);
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $retryExecutor->query('8.8.8.8', $query);
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
|
||||
|
||||
// first query will time out after a while and this sends the next query
|
||||
$deferred->reject(new TimeoutException());
|
||||
|
||||
$this->assertEquals(0, $cancelled);
|
||||
$promise->cancel();
|
||||
$this->assertEquals(1, $cancelled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldNotCauseGarbageReferencesOnSuccess()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->willReturn(Promise\resolve($this->createStandardResponse()));
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 0);
|
||||
|
||||
gc_collect_cycles();
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$retryExecutor->query('8.8.8.8', $query);
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldNotCauseGarbageReferencesOnTimeoutErrors()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->any())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->willReturn(Promise\reject(new TimeoutException("timeout")));
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 0);
|
||||
|
||||
gc_collect_cycles();
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$retryExecutor->query('8.8.8.8', $query);
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldNotCauseGarbageReferencesOnCancellation()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$deferred = new Deferred(function () {
|
||||
throw new \RuntimeException();
|
||||
});
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->willReturn($deferred->promise());
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 0);
|
||||
|
||||
gc_collect_cycles();
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $retryExecutor->query('8.8.8.8', $query);
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers React\Dns\Query\RetryExecutor
|
||||
* @test
|
||||
*/
|
||||
public function queryShouldNotCauseGarbageReferencesOnNonTimeoutErrors()
|
||||
{
|
||||
if (class_exists('React\Promise\When')) {
|
||||
$this->markTestSkipped('Not supported on legacy Promise v1 API');
|
||||
}
|
||||
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with('8.8.8.8', $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($domain, $query) {
|
||||
return Promise\reject(new \Exception);
|
||||
}));
|
||||
|
||||
$retryExecutor = new RetryExecutor($executor, 2);
|
||||
|
||||
gc_collect_cycles();
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$retryExecutor->query('8.8.8.8', $query);
|
||||
|
||||
$this->assertEquals(0, gc_collect_cycles());
|
||||
}
|
||||
|
||||
protected function expectPromiseOnce($return = null)
|
||||
{
|
||||
$mock = $this->createPromiseMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('then')
|
||||
->will($this->returnValue($return));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function createExecutorMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
}
|
||||
|
||||
protected function createPromiseMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Promise\PromiseInterface')->getMock();
|
||||
}
|
||||
|
||||
protected function createStandardResponse()
|
||||
{
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN);
|
||||
$response->answers[] = new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131');
|
||||
$response->prepare();
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
115
vendor/react/dns/tests/Query/TimeoutExecutorTest.php
vendored
Executable file
115
vendor/react/dns/tests/Query/TimeoutExecutorTest.php
vendored
Executable file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use React\Dns\Query\TimeoutExecutor;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Promise\Deferred;
|
||||
use React\Dns\Query\CancellationException;
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\EventLoop\Factory;
|
||||
use React\Promise;
|
||||
|
||||
class TimeoutExecutorTest extends TestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
$this->loop = Factory::create();
|
||||
|
||||
$this->wrapped = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
|
||||
$this->executor = new TimeoutExecutor($this->wrapped, 5.0, $this->loop);
|
||||
}
|
||||
|
||||
public function testCancellingPromiseWillCancelWrapped()
|
||||
{
|
||||
$cancelled = 0;
|
||||
|
||||
$this->wrapped
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
|
||||
$deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
|
||||
++$cancelled;
|
||||
$reject(new CancellationException('Cancelled'));
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$this->assertEquals(0, $cancelled);
|
||||
$promise->cancel();
|
||||
$this->assertEquals(1, $cancelled);
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testResolvesPromiseWhenWrappedResolves()
|
||||
{
|
||||
$this->wrapped
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->willReturn(Promise\resolve('0.0.0.0'));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$promise->then($this->expectCallableOnce(), $this->expectCallableNever());
|
||||
}
|
||||
|
||||
public function testRejectsPromiseWhenWrappedRejects()
|
||||
{
|
||||
$this->wrapped
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->willReturn(Promise\reject(new \RuntimeException()));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$promise = $this->executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$promise->then($this->expectCallableNever(), $this->expectCallableOnceWith(new \RuntimeException()));
|
||||
}
|
||||
|
||||
public function testWrappedWillBeCancelledOnTimeout()
|
||||
{
|
||||
$this->executor = new TimeoutExecutor($this->wrapped, 0, $this->loop);
|
||||
|
||||
$cancelled = 0;
|
||||
|
||||
$this->wrapped
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->will($this->returnCallback(function ($domain, $query) use (&$cancelled) {
|
||||
$deferred = new Deferred(function ($resolve, $reject) use (&$cancelled) {
|
||||
++$cancelled;
|
||||
$reject(new CancellationException('Cancelled'));
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}));
|
||||
|
||||
$callback = $this->expectCallableNever();
|
||||
|
||||
$errorback = $this->createCallableMock();
|
||||
$errorback
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($this->logicalAnd(
|
||||
$this->isInstanceOf('React\Dns\Query\TimeoutException'),
|
||||
$this->attribute($this->equalTo('DNS query for igor.io timed out'), 'message')
|
||||
));
|
||||
|
||||
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
|
||||
$this->executor->query('8.8.8.8:53', $query)->then($callback, $errorback);
|
||||
|
||||
$this->assertEquals(0, $cancelled);
|
||||
|
||||
$this->loop->run();
|
||||
|
||||
$this->assertEquals(1, $cancelled);
|
||||
}
|
||||
}
|
||||
215
vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
vendored
Executable file
215
vendor/react/dns/tests/Query/UdpTransportExecutorTest.php
vendored
Executable file
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Query\UdpTransportExecutor;
|
||||
use React\EventLoop\Factory;
|
||||
use React\Tests\Dns\TestCase;
|
||||
|
||||
class UdpTransportExecutorTest extends TestCase
|
||||
{
|
||||
public function testQueryRejectsIfMessageExceedsUdpSize()
|
||||
{
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
$loop->expects($this->never())->method('addReadStream');
|
||||
|
||||
$dumper = $this->getMockBuilder('React\Dns\Protocol\BinaryDumper')->getMock();
|
||||
$dumper->expects($this->once())->method('toBinary')->willReturn(str_repeat('.', 513));
|
||||
|
||||
$executor = new UdpTransportExecutor($loop, null, $dumper);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise = $executor->query('8.8.8.8:53', $query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testQueryRejectsIfServerConnectionFails()
|
||||
{
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
$loop->expects($this->never())->method('addReadStream');
|
||||
|
||||
$executor = new UdpTransportExecutor($loop);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise = $executor->query('///', $query);
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group internet
|
||||
*/
|
||||
public function testQueryRejectsOnCancellation()
|
||||
{
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
$loop->expects($this->once())->method('addReadStream');
|
||||
$loop->expects($this->once())->method('removeReadStream');
|
||||
|
||||
$executor = new UdpTransportExecutor($loop);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
$promise = $executor->query('8.8.8.8:53', $query);
|
||||
$promise->cancel();
|
||||
|
||||
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
|
||||
$promise->then(null, $this->expectCallableOnce());
|
||||
}
|
||||
|
||||
public function testQueryKeepsPendingIfServerRejectsNetworkPacket()
|
||||
{
|
||||
$loop = Factory::create();
|
||||
|
||||
$executor = new UdpTransportExecutor($loop);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$wait = true;
|
||||
$promise = $executor->query('127.0.0.1:1', $query)->then(
|
||||
null,
|
||||
function ($e) use (&$wait) {
|
||||
$wait = false;
|
||||
throw $e;
|
||||
}
|
||||
);
|
||||
|
||||
\Clue\React\Block\sleep(0.2, $loop);
|
||||
$this->assertTrue($wait);
|
||||
}
|
||||
|
||||
public function testQueryKeepsPendingIfServerSendInvalidMessage()
|
||||
{
|
||||
$loop = Factory::create();
|
||||
|
||||
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
|
||||
$loop->addReadStream($server, function ($server) {
|
||||
$data = stream_socket_recvfrom($server, 512, 0, $peer);
|
||||
stream_socket_sendto($server, 'invalid', 0, $peer);
|
||||
});
|
||||
|
||||
$address = stream_socket_get_name($server, false);
|
||||
$executor = new UdpTransportExecutor($loop);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$wait = true;
|
||||
$promise = $executor->query($address, $query)->then(
|
||||
null,
|
||||
function ($e) use (&$wait) {
|
||||
$wait = false;
|
||||
throw $e;
|
||||
}
|
||||
);
|
||||
|
||||
\Clue\React\Block\sleep(0.2, $loop);
|
||||
$this->assertTrue($wait);
|
||||
}
|
||||
|
||||
public function testQueryKeepsPendingIfServerSendInvalidId()
|
||||
{
|
||||
$parser = new Parser();
|
||||
$dumper = new BinaryDumper();
|
||||
|
||||
$loop = Factory::create();
|
||||
|
||||
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
|
||||
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
|
||||
$data = stream_socket_recvfrom($server, 512, 0, $peer);
|
||||
|
||||
$message = $parser->parseMessage($data);
|
||||
$message->header->set('id', 0);
|
||||
|
||||
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
|
||||
});
|
||||
|
||||
$address = stream_socket_get_name($server, false);
|
||||
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$wait = true;
|
||||
$promise = $executor->query($address, $query)->then(
|
||||
null,
|
||||
function ($e) use (&$wait) {
|
||||
$wait = false;
|
||||
throw $e;
|
||||
}
|
||||
);
|
||||
|
||||
\Clue\React\Block\sleep(0.2, $loop);
|
||||
$this->assertTrue($wait);
|
||||
}
|
||||
|
||||
public function testQueryRejectsIfServerSendsTruncatedResponse()
|
||||
{
|
||||
$parser = new Parser();
|
||||
$dumper = new BinaryDumper();
|
||||
|
||||
$loop = Factory::create();
|
||||
|
||||
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
|
||||
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
|
||||
$data = stream_socket_recvfrom($server, 512, 0, $peer);
|
||||
|
||||
$message = $parser->parseMessage($data);
|
||||
$message->header->set('tc', 1);
|
||||
|
||||
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
|
||||
});
|
||||
|
||||
$address = stream_socket_get_name($server, false);
|
||||
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$wait = true;
|
||||
$promise = $executor->query($address, $query)->then(
|
||||
null,
|
||||
function ($e) use (&$wait) {
|
||||
$wait = false;
|
||||
throw $e;
|
||||
}
|
||||
);
|
||||
|
||||
// run loop for short period to ensure we detect connection ICMP rejection error
|
||||
\Clue\React\Block\sleep(0.01, $loop);
|
||||
if ($wait) {
|
||||
\Clue\React\Block\sleep(0.2, $loop);
|
||||
}
|
||||
|
||||
$this->assertFalse($wait);
|
||||
}
|
||||
|
||||
public function testQueryResolvesIfServerSendsValidResponse()
|
||||
{
|
||||
$parser = new Parser();
|
||||
$dumper = new BinaryDumper();
|
||||
|
||||
$loop = Factory::create();
|
||||
|
||||
$server = stream_socket_server('udp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND);
|
||||
$loop->addReadStream($server, function ($server) use ($parser, $dumper) {
|
||||
$data = stream_socket_recvfrom($server, 512, 0, $peer);
|
||||
|
||||
$message = $parser->parseMessage($data);
|
||||
|
||||
stream_socket_sendto($server, $dumper->toBinary($message), 0, $peer);
|
||||
});
|
||||
|
||||
$address = stream_socket_get_name($server, false);
|
||||
$executor = new UdpTransportExecutor($loop, $parser, $dumper);
|
||||
|
||||
$query = new Query('google.com', Message::TYPE_A, Message::CLASS_IN);
|
||||
|
||||
$promise = $executor->query($address, $query);
|
||||
$response = \Clue\React\Block\await($promise, $loop, 0.2);
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Model\Message', $response);
|
||||
}
|
||||
}
|
||||
120
vendor/react/dns/tests/Resolver/FactoryTest.php
vendored
Executable file
120
vendor/react/dns/tests/Resolver/FactoryTest.php
vendored
Executable file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Resolver;
|
||||
|
||||
use React\Dns\Resolver\Factory;
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Dns\Query\HostsFileExecutor;
|
||||
|
||||
class FactoryTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function createShouldCreateResolver()
|
||||
{
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create('8.8.8.8:53', $loop);
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function createWithoutPortShouldCreateResolverWithDefaultPort()
|
||||
{
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create('8.8.8.8', $loop);
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
|
||||
$this->assertSame('8.8.8.8:53', $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function createCachedShouldCreateResolverWithCachingExecutor()
|
||||
{
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->createCached('8.8.8.8:53', $loop);
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
|
||||
$executor = $this->getResolverPrivateExecutor($resolver);
|
||||
$this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
|
||||
$cache = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
|
||||
$this->assertInstanceOf('React\Cache\ArrayCache', $cache);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function createCachedShouldCreateResolverWithCachingExecutorWithCustomCache()
|
||||
{
|
||||
$cache = $this->getMockBuilder('React\Cache\CacheInterface')->getMock();
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->createCached('8.8.8.8:53', $loop, $cache);
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
|
||||
$executor = $this->getResolverPrivateExecutor($resolver);
|
||||
$this->assertInstanceOf('React\Dns\Query\CachingExecutor', $executor);
|
||||
$cacheProperty = $this->getCachingExecutorPrivateMemberValue($executor, 'cache');
|
||||
$this->assertSame($cache, $cacheProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider factoryShouldAddDefaultPortProvider
|
||||
*/
|
||||
public function factoryShouldAddDefaultPort($input, $expected)
|
||||
{
|
||||
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
|
||||
|
||||
$factory = new Factory();
|
||||
$resolver = $factory->create($input, $loop);
|
||||
|
||||
$this->assertInstanceOf('React\Dns\Resolver\Resolver', $resolver);
|
||||
$this->assertSame($expected, $this->getResolverPrivateMemberValue($resolver, 'nameserver'));
|
||||
}
|
||||
|
||||
public static function factoryShouldAddDefaultPortProvider()
|
||||
{
|
||||
return array(
|
||||
array('8.8.8.8', '8.8.8.8:53'),
|
||||
array('1.2.3.4:5', '1.2.3.4:5'),
|
||||
array('localhost', 'localhost:53'),
|
||||
array('localhost:1234', 'localhost:1234'),
|
||||
array('::1', '[::1]:53'),
|
||||
array('[::1]:53', '[::1]:53')
|
||||
);
|
||||
}
|
||||
|
||||
private function getResolverPrivateExecutor($resolver)
|
||||
{
|
||||
$executor = $this->getResolverPrivateMemberValue($resolver, 'executor');
|
||||
|
||||
// extract underlying executor that may be wrapped in multiple layers of hosts file executors
|
||||
while ($executor instanceof HostsFileExecutor) {
|
||||
$reflector = new \ReflectionProperty('React\Dns\Query\HostsFileExecutor', 'fallback');
|
||||
$reflector->setAccessible(true);
|
||||
|
||||
$executor = $reflector->getValue($executor);
|
||||
}
|
||||
|
||||
return $executor;
|
||||
}
|
||||
|
||||
private function getResolverPrivateMemberValue($resolver, $field)
|
||||
{
|
||||
$reflector = new \ReflectionProperty('React\Dns\Resolver\Resolver', $field);
|
||||
$reflector->setAccessible(true);
|
||||
return $reflector->getValue($resolver);
|
||||
}
|
||||
|
||||
private function getCachingExecutorPrivateMemberValue($resolver, $field)
|
||||
{
|
||||
$reflector = new \ReflectionProperty('React\Dns\Query\CachingExecutor', $field);
|
||||
$reflector->setAccessible(true);
|
||||
return $reflector->getValue($resolver);
|
||||
}
|
||||
}
|
||||
101
vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
vendored
Executable file
101
vendor/react/dns/tests/Resolver/ResolveAliasesTest.php
vendored
Executable file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Resolver;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use React\Dns\Resolver\Resolver;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
|
||||
class ResolveAliasesTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers React\Dns\Resolver\Resolver::resolveAliases
|
||||
* @covers React\Dns\Resolver\Resolver::valuesByNameAndType
|
||||
* @dataProvider provideAliasedAnswers
|
||||
*/
|
||||
public function testResolveAliases(array $expectedAnswers, array $answers, $name)
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
|
||||
$answers = $resolver->resolveAliases($answers, $name);
|
||||
|
||||
$this->assertEquals($expectedAnswers, $answers);
|
||||
}
|
||||
|
||||
public function provideAliasedAnswers()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
array('178.79.169.131'),
|
||||
array(
|
||||
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
),
|
||||
'igor.io',
|
||||
),
|
||||
array(
|
||||
array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
|
||||
array(
|
||||
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
|
||||
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
|
||||
),
|
||||
'igor.io',
|
||||
),
|
||||
array(
|
||||
array('178.79.169.131'),
|
||||
array(
|
||||
new Record('igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
),
|
||||
'igor.io',
|
||||
),
|
||||
array(
|
||||
array(),
|
||||
array(
|
||||
new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN),
|
||||
new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN),
|
||||
),
|
||||
'igor.io',
|
||||
),
|
||||
array(
|
||||
array('178.79.169.131'),
|
||||
array(
|
||||
new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
|
||||
new Record('foo.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
),
|
||||
'igor.io',
|
||||
),
|
||||
array(
|
||||
array('178.79.169.131'),
|
||||
array(
|
||||
new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
|
||||
new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
|
||||
new Record('bar.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
),
|
||||
'igor.io',
|
||||
),
|
||||
array(
|
||||
array('178.79.169.131', '178.79.169.132', '178.79.169.133'),
|
||||
array(
|
||||
new Record('igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'foo.igor.io'),
|
||||
new Record('foo.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'bar.igor.io'),
|
||||
new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'baz.igor.io'),
|
||||
new Record('bar.igor.io', Message::TYPE_CNAME, Message::CLASS_IN, 3600, 'qux.igor.io'),
|
||||
new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.131'),
|
||||
new Record('baz.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.132'),
|
||||
new Record('qux.igor.io', Message::TYPE_A, Message::CLASS_IN, 3600, '178.79.169.133'),
|
||||
),
|
||||
'igor.io',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private function createExecutorMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
}
|
||||
}
|
||||
251
vendor/react/dns/tests/Resolver/ResolverTest.php
vendored
Executable file
251
vendor/react/dns/tests/Resolver/ResolverTest.php
vendored
Executable file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns\Resolver;
|
||||
|
||||
use React\Dns\Resolver\Resolver;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
use React\Tests\Dns\TestCase;
|
||||
use React\Dns\RecordNotFoundException;
|
||||
|
||||
class ResolverTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function resolveShouldQueryARecords()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '178.79.169.131');
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolve('igor.io')->then($this->expectCallableOnceWith('178.79.169.131'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveAllShouldQueryGivenRecords()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveAllShouldIgnoreRecordsWithOtherTypes()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
$response->answers[] = new Record($query->name, Message::TYPE_TXT, $query->class, 3600, array('ignored'));
|
||||
$response->answers[] = new Record($query->name, $query->type, $query->class, 3600, '::1');
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then($this->expectCallableOnceWith(array('::1')));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveAllShouldReturnMultipleValuesForAlias()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
$response->answers[] = new Record($query->name, Message::TYPE_CNAME, $query->class, 3600, 'example.com');
|
||||
$response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::1');
|
||||
$response->answers[] = new Record('example.com', $query->type, $query->class, 3600, '::2');
|
||||
$response->prepare();
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(
|
||||
$this->expectCallableOnceWith($this->equalTo(array('::1', '::2')))
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldQueryARecordsAndIgnoreCase()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class);
|
||||
$response->answers[] = new Record('Blog.wyrihaximus.net', $query->type, $query->class, 3600, '178.79.169.131');
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolve('blog.wyrihaximus.net')->then($this->expectCallableOnceWith('178.79.169.131'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function resolveShouldFilterByName()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
$response->answers[] = new Record('foo.bar', $query->type, $query->class, 3600, '178.79.169.131');
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$errback = $this->expectCallableOnceWith($this->isInstanceOf('React\Dns\RecordNotFoundException'));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function resolveWithNoAnswersShouldCallErrbackIfGiven()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$errback = $this->expectCallableOnceWith($this->callback(function ($param) {
|
||||
return ($param instanceof RecordNotFoundException && $param->getCode() === 0 && $param->getMessage() === 'DNS query for igor.io did not return a valid answer (NOERROR / NODATA)');
|
||||
}));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolve('igor.io')->then($this->expectCallableNever(), $errback);
|
||||
}
|
||||
|
||||
public function provideRcodeErrors()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
Message::RCODE_FORMAT_ERROR,
|
||||
'DNS query for example.com returned an error response (Format Error)',
|
||||
),
|
||||
array(
|
||||
Message::RCODE_SERVER_FAILURE,
|
||||
'DNS query for example.com returned an error response (Server Failure)',
|
||||
),
|
||||
array(
|
||||
Message::RCODE_NAME_ERROR,
|
||||
'DNS query for example.com returned an error response (Non-Existent Domain / NXDOMAIN)'
|
||||
),
|
||||
array(
|
||||
Message::RCODE_NOT_IMPLEMENTED,
|
||||
'DNS query for example.com returned an error response (Not Implemented)'
|
||||
),
|
||||
array(
|
||||
Message::RCODE_REFUSED,
|
||||
'DNS query for example.com returned an error response (Refused)'
|
||||
),
|
||||
array(
|
||||
99,
|
||||
'DNS query for example.com returned an error response (Unknown error response code 99)'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideRcodeErrors
|
||||
*/
|
||||
public function resolveWithRcodeErrorShouldCallErrbackIfGiven($code, $expectedMessage)
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$executor
|
||||
->expects($this->once())
|
||||
->method('query')
|
||||
->with($this->anything(), $this->isInstanceOf('React\Dns\Query\Query'))
|
||||
->will($this->returnCallback(function ($nameserver, $query) use ($code) {
|
||||
$response = new Message();
|
||||
$response->header->set('qr', 1);
|
||||
$response->header->set('rcode', $code);
|
||||
$response->questions[] = new Record($query->name, $query->type, $query->class);
|
||||
|
||||
return Promise\resolve($response);
|
||||
}));
|
||||
|
||||
$errback = $this->expectCallableOnceWith($this->callback(function ($param) use ($code, $expectedMessage) {
|
||||
return ($param instanceof RecordNotFoundException && $param->getCode() === $code && $param->getMessage() === $expectedMessage);
|
||||
}));
|
||||
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
$resolver->resolve('example.com')->then($this->expectCallableNever(), $errback);
|
||||
}
|
||||
|
||||
public function testLegacyExtractAddress()
|
||||
{
|
||||
$executor = $this->createExecutorMock();
|
||||
$resolver = new Resolver('8.8.8.8:53', $executor);
|
||||
|
||||
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
|
||||
$response = Message::createResponseWithAnswersForQuery($query, array(
|
||||
new Record('reactphp.org', Message::TYPE_A, Message::CLASS_IN, 3600, '1.2.3.4')
|
||||
));
|
||||
|
||||
$ret = $resolver->extractAddress($query, $response);
|
||||
$this->assertEquals('1.2.3.4', $ret);
|
||||
}
|
||||
|
||||
private function createExecutorMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
|
||||
}
|
||||
}
|
||||
61
vendor/react/dns/tests/TestCase.php
vendored
Executable file
61
vendor/react/dns/tests/TestCase.php
vendored
Executable file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\Dns;
|
||||
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
protected function expectCallableOnce()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableOnceWith($value)
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->once())
|
||||
->method('__invoke')
|
||||
->with($value);
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function expectCallableNever()
|
||||
{
|
||||
$mock = $this->createCallableMock();
|
||||
$mock
|
||||
->expects($this->never())
|
||||
->method('__invoke');
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
protected function createCallableMock()
|
||||
{
|
||||
return $this->getMockBuilder('React\Tests\Dns\CallableStub')->getMock();
|
||||
}
|
||||
|
||||
public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
|
||||
{
|
||||
if (method_exists($this, 'expectException')) {
|
||||
// PHPUnit 5
|
||||
$this->expectException($exception);
|
||||
if ($exceptionMessage !== '') {
|
||||
$this->expectExceptionMessage($exceptionMessage);
|
||||
}
|
||||
if ($exceptionCode !== null) {
|
||||
$this->expectExceptionCode($exceptionCode);
|
||||
}
|
||||
} else {
|
||||
// legacy PHPUnit 4
|
||||
parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
vendor/react/event-loop/.gitignore
vendored
Executable file
3
vendor/react/event-loop/.gitignore
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
vendor
|
||||
27
vendor/react/event-loop/.travis.yml
vendored
Executable file
27
vendor/react/event-loop/.travis.yml
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libevent-dev # Used by 'event' and 'libevent' PHP extensions
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache/files
|
||||
|
||||
install:
|
||||
- ./travis-init.sh
|
||||
- composer install
|
||||
|
||||
script:
|
||||
- ./vendor/bin/phpunit --coverage-text
|
||||
79
vendor/react/event-loop/CHANGELOG.md
vendored
Executable file
79
vendor/react/event-loop/CHANGELOG.md
vendored
Executable file
@@ -0,0 +1,79 @@
|
||||
# Changelog
|
||||
|
||||
## 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.3.4 (2014-03-30)
|
||||
|
||||
* Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
|
||||
|
||||
## 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.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
|
||||
19
vendor/react/event-loop/LICENSE
vendored
Executable file
19
vendor/react/event-loop/LICENSE
vendored
Executable file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 Igor Wiedler, Chris Boden
|
||||
|
||||
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.
|
||||
143
vendor/react/event-loop/README.md
vendored
Executable file
143
vendor/react/event-loop/README.md
vendored
Executable file
@@ -0,0 +1,143 @@
|
||||
# EventLoop Component
|
||||
|
||||
[](https://travis-ci.org/reactphp/event-loop)
|
||||
[](https://codeclimate.com/github/reactphp/event-loop)
|
||||
|
||||
Event loop abstraction layer 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()` call that is controlled by the user.
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [Loop implementations](#loop-implementations)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Quickstart example
|
||||
|
||||
Here is an async HTTP server built with just the event loop.
|
||||
|
||||
```php
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
$server = stream_socket_server('tcp://127.0.0.1:8080');
|
||||
stream_set_blocking($server, 0);
|
||||
|
||||
$loop->addReadStream($server, function ($server) use ($loop) {
|
||||
$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, $loop) {
|
||||
$written = fwrite($conn, $data);
|
||||
if ($written === strlen($data)) {
|
||||
fclose($conn);
|
||||
$loop->removeStream($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";
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Typical applications use a single event loop which is created at the beginning
|
||||
and run at the end of the program.
|
||||
|
||||
```php
|
||||
// [1]
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
|
||||
// [2]
|
||||
$loop->addPeriodicTimer(1, function () {
|
||||
echo "Tick\n";
|
||||
});
|
||||
|
||||
$stream = new React\Stream\ReadableResourceStream(
|
||||
fopen('file.txt', 'r'),
|
||||
$loop
|
||||
);
|
||||
|
||||
// [3]
|
||||
$loop->run();
|
||||
```
|
||||
|
||||
1. The loop instance is created at the beginning of the program. A convenience
|
||||
factory `React\EventLoop\Factory::create()` is provided by this library which
|
||||
picks the best available [loop implementation](#loop-implementations).
|
||||
2. The loop instance is used directly or passed to library and application code.
|
||||
In this example, a periodic timer is registered with the event loop which
|
||||
simply outputs `Tick` every second and a
|
||||
[readable stream](https://github.com/reactphp/stream#readableresourcestream)
|
||||
is created by using ReactPHP's
|
||||
[stream component](https://github.com/reactphp/stream) for demonstration
|
||||
purposes.
|
||||
3. The loop is run with a single `$loop->run()` call at the end of the program.
|
||||
|
||||
## Loop implementations
|
||||
|
||||
In addition to the interface there are the following implementations provided:
|
||||
|
||||
* `StreamSelectLoop`: This is the only implementation which works out of the
|
||||
box with PHP. It does a simple `select` system call. It's not the most
|
||||
performant of loops, but still does the job quite well.
|
||||
|
||||
* `LibEventLoop`: This uses the `libevent` pecl extension. `libevent` itself
|
||||
supports a number of system-specific backends (epoll, kqueue).
|
||||
|
||||
* `LibEvLoop`: This uses the `libev` pecl extension
|
||||
([github](https://github.com/m4rw3r/php-libev)). It supports the same
|
||||
backends as libevent.
|
||||
|
||||
* `ExtEventLoop`: This uses the `event` pecl extension. It supports the same
|
||||
backends as libevent.
|
||||
|
||||
All of the loops support these features:
|
||||
|
||||
* File descriptor polling
|
||||
* One-off timers
|
||||
* Periodic timers
|
||||
* Deferred execution of callbacks
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](http://getcomposer.org).
|
||||
[New to Composer?](http://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
$ composer require react/event-loop
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](http://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).
|
||||
22
vendor/react/event-loop/composer.json
vendored
Executable file
22
vendor/react/event-loop/composer.json
vendored
Executable file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "react/event-loop",
|
||||
"description": "Event loop abstraction layer that libraries can use for evented I/O.",
|
||||
"keywords": ["event-loop", "asynchronous"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libevent": ">=0.1.0",
|
||||
"ext-event": "~1.0",
|
||||
"ext-libev": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\EventLoop\\": "src"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
vendor/react/event-loop/phpunit.xml.dist
vendored
Executable file
25
vendor/react/event-loop/phpunit.xml.dist
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="React Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
326
vendor/react/event-loop/src/ExtEventLoop.php
vendored
Executable file
326
vendor/react/event-loop/src/ExtEventLoop.php
vendored
Executable file
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use Event;
|
||||
use EventBase;
|
||||
use EventConfig as EventBaseConfig;
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Tick\NextTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use React\EventLoop\Timer\TimerInterface;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* An ext-event based event-loop.
|
||||
*/
|
||||
class ExtEventLoop implements LoopInterface
|
||||
{
|
||||
private $eventBase;
|
||||
private $nextTickQueue;
|
||||
private $futureTickQueue;
|
||||
private $timerCallback;
|
||||
private $timerEvents;
|
||||
private $streamCallback;
|
||||
private $streamEvents = [];
|
||||
private $streamFlags = [];
|
||||
private $readListeners = [];
|
||||
private $writeListeners = [];
|
||||
private $running;
|
||||
|
||||
public function __construct(EventBaseConfig $config = null)
|
||||
{
|
||||
$this->eventBase = new EventBase($config);
|
||||
$this->nextTickQueue = new NextTickQueue($this);
|
||||
$this->futureTickQueue = new FutureTickQueue($this);
|
||||
$this->timerEvents = new SplObjectStorage();
|
||||
|
||||
$this->createTimerCallback();
|
||||
$this->createStreamCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addReadStream($stream, callable $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->readListeners[$key])) {
|
||||
$this->readListeners[$key] = $listener;
|
||||
$this->subscribeStreamEvent($stream, Event::READ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addWriteStream($stream, callable $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->writeListeners[$key])) {
|
||||
$this->writeListeners[$key] = $listener;
|
||||
$this->subscribeStreamEvent($stream, Event::WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->readListeners[$key])) {
|
||||
unset($this->readListeners[$key]);
|
||||
$this->unsubscribeStreamEvent($stream, Event::READ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeListeners[$key])) {
|
||||
unset($this->writeListeners[$key]);
|
||||
$this->unsubscribeStreamEvent($stream, Event::WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->streamEvents[$key])) {
|
||||
$this->streamEvents[$key]->free();
|
||||
|
||||
unset(
|
||||
$this->streamFlags[$key],
|
||||
$this->streamEvents[$key],
|
||||
$this->readListeners[$key],
|
||||
$this->writeListeners[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, false);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPeriodicTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, true);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if ($this->isTimerActive($timer)) {
|
||||
$this->timerEvents[$timer]->free();
|
||||
$this->timerEvents->detach($timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTimerActive(TimerInterface $timer)
|
||||
{
|
||||
return $this->timerEvents->contains($timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nextTick(callable $listener)
|
||||
{
|
||||
$this->nextTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function futureTick(callable $listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function tick()
|
||||
{
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
// @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
|
||||
@$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$flags = EventBase::LOOP_ONCE;
|
||||
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
|
||||
$flags |= EventBase::LOOP_NONBLOCK;
|
||||
} elseif (!$this->streamEvents && !$this->timerEvents->count()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->eventBase->loop($flags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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 new ext-event Event object, or update the existing one.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param integer $flag Event::READ or Event::WRITE
|
||||
*/
|
||||
private function subscribeStreamEvent($stream, $flag)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->streamEvents[$key])) {
|
||||
$event = $this->streamEvents[$key];
|
||||
$flags = ($this->streamFlags[$key] |= $flag);
|
||||
|
||||
$event->del();
|
||||
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
|
||||
} else {
|
||||
$event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback);
|
||||
|
||||
$this->streamEvents[$key] = $event;
|
||||
$this->streamFlags[$key] = $flag;
|
||||
}
|
||||
|
||||
$event->add();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ext-event Event object for this stream to stop listening to
|
||||
* the given event type, or remove it entirely if it's no longer needed.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param integer $flag Event::READ or Event::WRITE
|
||||
*/
|
||||
private function unsubscribeStreamEvent($stream, $flag)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
$flags = $this->streamFlags[$key] &= ~$flag;
|
||||
|
||||
if (0 === $flags) {
|
||||
$this->removeStream($stream);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$event = $this->streamEvents[$key];
|
||||
|
||||
$event->del();
|
||||
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
|
||||
$event->add();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$this->timerCallback = function ($_, $__, $timer) {
|
||||
call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
if (!$timer->isPeriodic() && $this->isTimerActive($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()
|
||||
{
|
||||
$this->streamCallback = function ($stream, $flags) {
|
||||
$key = (int) $stream;
|
||||
|
||||
if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) {
|
||||
call_user_func($this->readListeners[$key], $stream, $this);
|
||||
}
|
||||
|
||||
if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) {
|
||||
call_user_func($this->writeListeners[$key], $stream, $this);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
21
vendor/react/event-loop/src/Factory.php
vendored
Executable file
21
vendor/react/event-loop/src/Factory.php
vendored
Executable file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
class Factory
|
||||
{
|
||||
public static function create()
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (function_exists('event_base_new')) {
|
||||
return new LibEventLoop();
|
||||
} elseif (class_exists('libev\EventLoop', false)) {
|
||||
return new LibEvLoop;
|
||||
} elseif (class_exists('EventBase', false)) {
|
||||
return new ExtEventLoop;
|
||||
}
|
||||
|
||||
return new StreamSelectLoop();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
226
vendor/react/event-loop/src/LibEvLoop.php
vendored
Executable file
226
vendor/react/event-loop/src/LibEvLoop.php
vendored
Executable file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use libev\EventLoop;
|
||||
use libev\IOEvent;
|
||||
use libev\TimerEvent;
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Tick\NextTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use React\EventLoop\Timer\TimerInterface;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* @see https://github.com/m4rw3r/php-libev
|
||||
* @see https://gist.github.com/1688204
|
||||
*/
|
||||
class LibEvLoop implements LoopInterface
|
||||
{
|
||||
private $loop;
|
||||
private $nextTickQueue;
|
||||
private $futureTickQueue;
|
||||
private $timerEvents;
|
||||
private $readEvents = [];
|
||||
private $writeEvents = [];
|
||||
private $running;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->loop = new EventLoop();
|
||||
$this->nextTickQueue = new NextTickQueue($this);
|
||||
$this->futureTickQueue = new FutureTickQueue($this);
|
||||
$this->timerEvents = new SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addReadStream($stream, callable $listener)
|
||||
{
|
||||
if (isset($this->readEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = function () use ($stream, $listener) {
|
||||
call_user_func($listener, $stream, $this);
|
||||
};
|
||||
|
||||
$event = new IOEvent($callback, $stream, IOEvent::READ);
|
||||
$this->loop->add($event);
|
||||
|
||||
$this->readEvents[(int) $stream] = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addWriteStream($stream, callable $listener)
|
||||
{
|
||||
if (isset($this->writeEvents[(int) $stream])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$callback = function () use ($stream, $listener) {
|
||||
call_user_func($listener, $stream, $this);
|
||||
};
|
||||
|
||||
$event = new IOEvent($callback, $stream, IOEvent::WRITE);
|
||||
$this->loop->add($event);
|
||||
|
||||
$this->writeEvents[(int) $stream] = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->readEvents[$key])) {
|
||||
$this->readEvents[$key]->stop();
|
||||
unset($this->readEvents[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeEvents[$key])) {
|
||||
$this->writeEvents[$key]->stop();
|
||||
unset($this->writeEvents[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeStream($stream)
|
||||
{
|
||||
$this->removeReadStream($stream);
|
||||
$this->removeWriteStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, false);
|
||||
|
||||
$callback = function () use ($timer) {
|
||||
call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
if ($this->isTimerActive($timer)) {
|
||||
$this->cancelTimer($timer);
|
||||
}
|
||||
};
|
||||
|
||||
$event = new TimerEvent($callback, $timer->getInterval());
|
||||
$this->timerEvents->attach($timer, $event);
|
||||
$this->loop->add($event);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPeriodicTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, true);
|
||||
|
||||
$callback = function () use ($timer) {
|
||||
call_user_func($timer->getCallback(), $timer);
|
||||
};
|
||||
|
||||
$event = new TimerEvent($callback, $interval, $interval);
|
||||
$this->timerEvents->attach($timer, $event);
|
||||
$this->loop->add($event);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if (isset($this->timerEvents[$timer])) {
|
||||
$this->loop->remove($this->timerEvents[$timer]);
|
||||
$this->timerEvents->detach($timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTimerActive(TimerInterface $timer)
|
||||
{
|
||||
return $this->timerEvents->contains($timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nextTick(callable $listener)
|
||||
{
|
||||
$this->nextTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function futureTick(callable $listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function tick()
|
||||
{
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$flags = EventLoop::RUN_ONCE;
|
||||
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
|
||||
$flags |= EventLoop::RUN_NOWAIT;
|
||||
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->loop->run($flags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
}
|
||||
343
vendor/react/event-loop/src/LibEventLoop.php
vendored
Executable file
343
vendor/react/event-loop/src/LibEventLoop.php
vendored
Executable file
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use Event;
|
||||
use EventBase;
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Tick\NextTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use React\EventLoop\Timer\TimerInterface;
|
||||
use SplObjectStorage;
|
||||
|
||||
/**
|
||||
* An ext-libevent based event-loop.
|
||||
*/
|
||||
class LibEventLoop implements LoopInterface
|
||||
{
|
||||
const MICROSECONDS_PER_SECOND = 1000000;
|
||||
|
||||
private $eventBase;
|
||||
private $nextTickQueue;
|
||||
private $futureTickQueue;
|
||||
private $timerCallback;
|
||||
private $timerEvents;
|
||||
private $streamCallback;
|
||||
private $streamEvents = [];
|
||||
private $streamFlags = [];
|
||||
private $readListeners = [];
|
||||
private $writeListeners = [];
|
||||
private $running;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->eventBase = event_base_new();
|
||||
$this->nextTickQueue = new NextTickQueue($this);
|
||||
$this->futureTickQueue = new FutureTickQueue($this);
|
||||
$this->timerEvents = new SplObjectStorage();
|
||||
|
||||
$this->createTimerCallback();
|
||||
$this->createStreamCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addReadStream($stream, callable $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->readListeners[$key])) {
|
||||
$this->readListeners[$key] = $listener;
|
||||
$this->subscribeStreamEvent($stream, EV_READ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addWriteStream($stream, callable $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->writeListeners[$key])) {
|
||||
$this->writeListeners[$key] = $listener;
|
||||
$this->subscribeStreamEvent($stream, EV_WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->readListeners[$key])) {
|
||||
unset($this->readListeners[$key]);
|
||||
$this->unsubscribeStreamEvent($stream, EV_READ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeListeners[$key])) {
|
||||
unset($this->writeListeners[$key]);
|
||||
$this->unsubscribeStreamEvent($stream, EV_WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->streamEvents[$key])) {
|
||||
$event = $this->streamEvents[$key];
|
||||
|
||||
event_del($event);
|
||||
event_free($event);
|
||||
|
||||
unset(
|
||||
$this->streamFlags[$key],
|
||||
$this->streamEvents[$key],
|
||||
$this->readListeners[$key],
|
||||
$this->writeListeners[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, false);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPeriodicTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, true);
|
||||
|
||||
$this->scheduleTimer($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
if ($this->isTimerActive($timer)) {
|
||||
$event = $this->timerEvents[$timer];
|
||||
|
||||
event_del($event);
|
||||
event_free($event);
|
||||
|
||||
$this->timerEvents->detach($timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTimerActive(TimerInterface $timer)
|
||||
{
|
||||
return $this->timerEvents->contains($timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nextTick(callable $listener)
|
||||
{
|
||||
$this->nextTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function futureTick(callable $listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function tick()
|
||||
{
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$flags = EVLOOP_ONCE;
|
||||
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
|
||||
$flags |= EVLOOP_NONBLOCK;
|
||||
} elseif (!$this->streamEvents && !$this->timerEvents->count()) {
|
||||
break;
|
||||
}
|
||||
|
||||
event_base_loop($this->eventBase, $flags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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 new ext-libevent event resource, or update the existing one.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param integer $flag EV_READ or EV_WRITE
|
||||
*/
|
||||
private function subscribeStreamEvent($stream, $flag)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->streamEvents[$key])) {
|
||||
$event = $this->streamEvents[$key];
|
||||
$flags = $this->streamFlags[$key] |= $flag;
|
||||
|
||||
event_del($event);
|
||||
event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
|
||||
} else {
|
||||
$event = event_new();
|
||||
|
||||
event_set($event, $stream, EV_PERSIST | $flag, $this->streamCallback);
|
||||
event_base_set($event, $this->eventBase);
|
||||
|
||||
$this->streamEvents[$key] = $event;
|
||||
$this->streamFlags[$key] = $flag;
|
||||
}
|
||||
|
||||
event_add($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ext-libevent event resource for this stream to stop listening to
|
||||
* the given event type, or remove it entirely if it's no longer needed.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param integer $flag EV_READ or EV_WRITE
|
||||
*/
|
||||
private function unsubscribeStreamEvent($stream, $flag)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
$flags = $this->streamFlags[$key] &= ~$flag;
|
||||
|
||||
if (0 === $flags) {
|
||||
$this->removeStream($stream);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$event = $this->streamEvents[$key];
|
||||
|
||||
event_del($event);
|
||||
event_set($event, $stream, EV_PERSIST | $flags, $this->streamCallback);
|
||||
event_add($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$this->timerCallback = function ($_, $__, $timer) {
|
||||
call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
// Timer already cancelled ...
|
||||
if (!$this->isTimerActive($timer)) {
|
||||
return;
|
||||
|
||||
// Reschedule periodic timers ...
|
||||
} elseif ($timer->isPeriodic()) {
|
||||
event_add(
|
||||
$this->timerEvents[$timer],
|
||||
$timer->getInterval() * self::MICROSECONDS_PER_SECOND
|
||||
);
|
||||
|
||||
// Clean-up one shot timers ...
|
||||
} else {
|
||||
$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()
|
||||
{
|
||||
$this->streamCallback = function ($stream, $flags) {
|
||||
$key = (int) $stream;
|
||||
|
||||
if (EV_READ === (EV_READ & $flags) && isset($this->readListeners[$key])) {
|
||||
call_user_func($this->readListeners[$key], $stream, $this);
|
||||
}
|
||||
|
||||
if (EV_WRITE === (EV_WRITE & $flags) && isset($this->writeListeners[$key])) {
|
||||
call_user_func($this->writeListeners[$key], $stream, $this);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
121
vendor/react/event-loop/src/LoopInterface.php
vendored
Executable file
121
vendor/react/event-loop/src/LoopInterface.php
vendored
Executable file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use React\EventLoop\Timer\TimerInterface;
|
||||
|
||||
interface LoopInterface
|
||||
{
|
||||
/**
|
||||
* Register a listener to be notified when a stream is ready to read.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource to check.
|
||||
* @param callable $listener Invoked when the stream is ready.
|
||||
*/
|
||||
public function addReadStream($stream, callable $listener);
|
||||
|
||||
/**
|
||||
* Register a listener to be notified when a stream is ready to write.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource to check.
|
||||
* @param callable $listener Invoked when the stream is ready.
|
||||
*/
|
||||
public function addWriteStream($stream, callable $listener);
|
||||
|
||||
/**
|
||||
* Remove the read event listener for the given stream.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource.
|
||||
*/
|
||||
public function removeReadStream($stream);
|
||||
|
||||
/**
|
||||
* Remove the write event listener for the given stream.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource.
|
||||
*/
|
||||
public function removeWriteStream($stream);
|
||||
|
||||
/**
|
||||
* Remove all listeners for the given stream.
|
||||
*
|
||||
* @param resource $stream The PHP stream resource.
|
||||
*/
|
||||
public function removeStream($stream);
|
||||
|
||||
/**
|
||||
* Enqueue a callback to be invoked once after the given interval.
|
||||
*
|
||||
* The execution order of timers scheduled to execute at the same time is
|
||||
* not guaranteed.
|
||||
*
|
||||
* @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, callable $callback);
|
||||
|
||||
/**
|
||||
* Enqueue a callback to be invoked repeatedly after the given interval.
|
||||
*
|
||||
* The execution order of timers scheduled to execute at the same time is
|
||||
* not guaranteed.
|
||||
*
|
||||
* @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, callable $callback);
|
||||
|
||||
/**
|
||||
* Cancel a pending timer.
|
||||
*
|
||||
* @param TimerInterface $timer The timer to cancel.
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer);
|
||||
|
||||
/**
|
||||
* Check if a given timer is active.
|
||||
*
|
||||
* @param TimerInterface $timer The timer to check.
|
||||
*
|
||||
* @return boolean True if the timer is still enqueued for execution.
|
||||
*/
|
||||
public function isTimerActive(TimerInterface $timer);
|
||||
|
||||
/**
|
||||
* Schedule a callback to be invoked on the next tick of the event loop.
|
||||
*
|
||||
* Callbacks are guaranteed to be executed in the order they are enqueued,
|
||||
* before any timer or stream events.
|
||||
*
|
||||
* @param callable $listener The callback to invoke.
|
||||
*/
|
||||
public function nextTick(callable $listener);
|
||||
|
||||
/**
|
||||
* Schedule 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 futureTick(callable $listener);
|
||||
|
||||
/**
|
||||
* Perform a single iteration of the event loop.
|
||||
*/
|
||||
public function tick();
|
||||
|
||||
/**
|
||||
* Run the event loop until there are no more tasks to perform.
|
||||
*/
|
||||
public function run();
|
||||
|
||||
/**
|
||||
* Instruct a running event loop to stop.
|
||||
*/
|
||||
public function stop();
|
||||
}
|
||||
273
vendor/react/event-loop/src/StreamSelectLoop.php
vendored
Executable file
273
vendor/react/event-loop/src/StreamSelectLoop.php
vendored
Executable file
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop;
|
||||
|
||||
use React\EventLoop\Tick\FutureTickQueue;
|
||||
use React\EventLoop\Tick\NextTickQueue;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
use React\EventLoop\Timer\TimerInterface;
|
||||
use React\EventLoop\Timer\Timers;
|
||||
|
||||
/**
|
||||
* A stream_select() based event-loop.
|
||||
*/
|
||||
class StreamSelectLoop implements LoopInterface
|
||||
{
|
||||
const MICROSECONDS_PER_SECOND = 1000000;
|
||||
|
||||
private $nextTickQueue;
|
||||
private $futureTickQueue;
|
||||
private $timers;
|
||||
private $readStreams = [];
|
||||
private $readListeners = [];
|
||||
private $writeStreams = [];
|
||||
private $writeListeners = [];
|
||||
private $running;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->nextTickQueue = new NextTickQueue($this);
|
||||
$this->futureTickQueue = new FutureTickQueue($this);
|
||||
$this->timers = new Timers();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addReadStream($stream, callable $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->readStreams[$key])) {
|
||||
$this->readStreams[$key] = $stream;
|
||||
$this->readListeners[$key] = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addWriteStream($stream, callable $listener)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
if (!isset($this->writeStreams[$key])) {
|
||||
$this->writeStreams[$key] = $stream;
|
||||
$this->writeListeners[$key] = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
unset(
|
||||
$this->readStreams[$key],
|
||||
$this->readListeners[$key]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
$key = (int) $stream;
|
||||
|
||||
unset(
|
||||
$this->writeStreams[$key],
|
||||
$this->writeListeners[$key]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeStream($stream)
|
||||
{
|
||||
$this->removeReadStream($stream);
|
||||
$this->removeWriteStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, false);
|
||||
|
||||
$this->timers->add($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPeriodicTimer($interval, callable $callback)
|
||||
{
|
||||
$timer = new Timer($this, $interval, $callback, true);
|
||||
|
||||
$this->timers->add($timer);
|
||||
|
||||
return $timer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
$this->timers->cancel($timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTimerActive(TimerInterface $timer)
|
||||
{
|
||||
return $this->timers->contains($timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nextTick(callable $listener)
|
||||
{
|
||||
$this->nextTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function futureTick(callable $listener)
|
||||
{
|
||||
$this->futureTickQueue->add($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function tick()
|
||||
{
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$this->timers->tick();
|
||||
|
||||
$this->waitForStreamActivity(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$this->running = true;
|
||||
|
||||
while ($this->running) {
|
||||
$this->nextTickQueue->tick();
|
||||
|
||||
$this->futureTickQueue->tick();
|
||||
|
||||
$this->timers->tick();
|
||||
|
||||
// Next-tick or future-tick queues have pending callbacks ...
|
||||
if (!$this->running || !$this->nextTickQueue->isEmpty() || !$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 {
|
||||
/*
|
||||
* round() needed to correct float error:
|
||||
* https://github.com/reactphp/event-loop/issues/48
|
||||
*/
|
||||
$timeout = round($timeout * self::MICROSECONDS_PER_SECOND);
|
||||
}
|
||||
|
||||
// The only possible event is stream activity, so wait forever ...
|
||||
} elseif ($this->readStreams || $this->writeStreams) {
|
||||
$timeout = null;
|
||||
|
||||
// There's nothing left to do ...
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->waitForStreamActivity($timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait/check for stream activity, or until the next timer is due.
|
||||
*/
|
||||
private function waitForStreamActivity($timeout)
|
||||
{
|
||||
$read = $this->readStreams;
|
||||
$write = $this->writeStreams;
|
||||
|
||||
$available = $this->streamSelect($read, $write, $timeout);
|
||||
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, $this);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($write as $stream) {
|
||||
$key = (int) $stream;
|
||||
|
||||
if (isset($this->writeListeners[$key])) {
|
||||
call_user_func($this->writeListeners[$key], $stream, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 integer|null $timeout Activity timeout in microseconds, or null to wait forever.
|
||||
*
|
||||
* @return integer|false The total number of streams that are ready for read/write.
|
||||
* Can return false if stream_select() is interrupted by a signal.
|
||||
*/
|
||||
protected function streamSelect(array &$read, array &$write, $timeout)
|
||||
{
|
||||
if ($read || $write) {
|
||||
$except = null;
|
||||
|
||||
// suppress warnings that occur, when stream_select is interrupted by a signal
|
||||
return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
|
||||
}
|
||||
|
||||
$timeout && usleep($timeout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
59
vendor/react/event-loop/src/Tick/FutureTickQueue.php
vendored
Executable file
59
vendor/react/event-loop/src/Tick/FutureTickQueue.php
vendored
Executable file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Tick;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use SplQueue;
|
||||
|
||||
class FutureTickQueue
|
||||
{
|
||||
private $eventLoop;
|
||||
private $queue;
|
||||
|
||||
/**
|
||||
* @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks.
|
||||
*/
|
||||
public function __construct(LoopInterface $eventLoop)
|
||||
{
|
||||
$this->eventLoop = $eventLoop;
|
||||
$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(callable $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(),
|
||||
$this->eventLoop
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the next tick queue is empty.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return $this->queue->isEmpty();
|
||||
}
|
||||
}
|
||||
57
vendor/react/event-loop/src/Tick/NextTickQueue.php
vendored
Executable file
57
vendor/react/event-loop/src/Tick/NextTickQueue.php
vendored
Executable file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Tick;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use SplQueue;
|
||||
|
||||
class NextTickQueue
|
||||
{
|
||||
private $eventLoop;
|
||||
private $queue;
|
||||
|
||||
/**
|
||||
* @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks.
|
||||
*/
|
||||
public function __construct(LoopInterface $eventLoop)
|
||||
{
|
||||
$this->eventLoop = $eventLoop;
|
||||
$this->queue = new SplQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be invoked on the next tick of the event loop.
|
||||
*
|
||||
* Callbacks are guaranteed to be executed in the order they are enqueued,
|
||||
* before any timer or stream events.
|
||||
*
|
||||
* @param callable $listener The callback to invoke.
|
||||
*/
|
||||
public function add(callable $listener)
|
||||
{
|
||||
$this->queue->enqueue($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the callback queue.
|
||||
*/
|
||||
public function tick()
|
||||
{
|
||||
while (!$this->queue->isEmpty()) {
|
||||
call_user_func(
|
||||
$this->queue->dequeue(),
|
||||
$this->eventLoop
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the next tick queue is empty.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return $this->queue->isEmpty();
|
||||
}
|
||||
}
|
||||
102
vendor/react/event-loop/src/Timer/Timer.php
vendored
Executable file
102
vendor/react/event-loop/src/Timer/Timer.php
vendored
Executable file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Timer;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
class Timer implements TimerInterface
|
||||
{
|
||||
const MIN_INTERVAL = 0.000001;
|
||||
|
||||
protected $loop;
|
||||
protected $interval;
|
||||
protected $callback;
|
||||
protected $periodic;
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Constructor initializes the fields of the Timer
|
||||
*
|
||||
* @param LoopInterface $loop The loop with which this timer is associated
|
||||
* @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
|
||||
* @param mixed $data Arbitrary data associated with timer
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false, $data = null)
|
||||
{
|
||||
if ($interval < self::MIN_INTERVAL) {
|
||||
$interval = self::MIN_INTERVAL;
|
||||
}
|
||||
|
||||
$this->loop = $loop;
|
||||
$this->interval = (float) $interval;
|
||||
$this->callback = $callback;
|
||||
$this->periodic = (bool) $periodic;
|
||||
$this->data = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLoop()
|
||||
{
|
||||
return $this->loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInterval()
|
||||
{
|
||||
return $this->interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCallback()
|
||||
{
|
||||
return $this->callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setData($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isPeriodic()
|
||||
{
|
||||
return $this->periodic;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isActive()
|
||||
{
|
||||
return $this->loop->isTimerActive($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancel()
|
||||
{
|
||||
$this->loop->cancelTimer($this);
|
||||
}
|
||||
}
|
||||
62
vendor/react/event-loop/src/Timer/TimerInterface.php
vendored
Executable file
62
vendor/react/event-loop/src/Timer/TimerInterface.php
vendored
Executable file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Timer;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
interface TimerInterface
|
||||
{
|
||||
/**
|
||||
* Get the loop with which this timer is associated
|
||||
*
|
||||
* @return LoopInterface
|
||||
*/
|
||||
public function getLoop();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Set arbitrary data associated with timer
|
||||
*
|
||||
* @param mixed $data
|
||||
*/
|
||||
public function setData($data);
|
||||
|
||||
/**
|
||||
* Get arbitrary data associated with timer
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData();
|
||||
|
||||
/**
|
||||
* Determine whether the time is periodic
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPeriodic();
|
||||
|
||||
/**
|
||||
* Determine whether the time is active
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isActive();
|
||||
|
||||
/**
|
||||
* Cancel this timer
|
||||
*/
|
||||
public function cancel();
|
||||
}
|
||||
100
vendor/react/event-loop/src/Timer/Timers.php
vendored
Executable file
100
vendor/react/event-loop/src/Timer/Timers.php
vendored
Executable file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace React\EventLoop\Timer;
|
||||
|
||||
use SplObjectStorage;
|
||||
use SplPriorityQueue;
|
||||
|
||||
class Timers
|
||||
{
|
||||
private $time;
|
||||
private $timers;
|
||||
private $scheduler;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->timers = new SplObjectStorage();
|
||||
$this->scheduler = new SplPriorityQueue();
|
||||
}
|
||||
|
||||
public function updateTime()
|
||||
{
|
||||
return $this->time = microtime(true);
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
{
|
||||
return $this->time ?: $this->updateTime();
|
||||
}
|
||||
|
||||
public function add(TimerInterface $timer)
|
||||
{
|
||||
$interval = $timer->getInterval();
|
||||
$scheduledAt = $interval + microtime(true);
|
||||
|
||||
$this->timers->attach($timer, $scheduledAt);
|
||||
$this->scheduler->insert($timer, -$scheduledAt);
|
||||
}
|
||||
|
||||
public function contains(TimerInterface $timer)
|
||||
{
|
||||
return $this->timers->contains($timer);
|
||||
}
|
||||
|
||||
public function cancel(TimerInterface $timer)
|
||||
{
|
||||
$this->timers->detach($timer);
|
||||
}
|
||||
|
||||
public function getFirst()
|
||||
{
|
||||
while ($this->scheduler->count()) {
|
||||
$timer = $this->scheduler->top();
|
||||
|
||||
if ($this->timers->contains($timer)) {
|
||||
return $this->timers[$timer];
|
||||
}
|
||||
|
||||
$this->scheduler->extract();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return count($this->timers) === 0;
|
||||
}
|
||||
|
||||
public function tick()
|
||||
{
|
||||
$time = $this->updateTime();
|
||||
$timers = $this->timers;
|
||||
$scheduler = $this->scheduler;
|
||||
|
||||
while (!$scheduler->isEmpty()) {
|
||||
$timer = $scheduler->top();
|
||||
|
||||
if (!isset($timers[$timer])) {
|
||||
$scheduler->extract();
|
||||
$timers->detach($timer);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($timers[$timer] >= $time) {
|
||||
break;
|
||||
}
|
||||
|
||||
$scheduler->extract();
|
||||
call_user_func($timer->getCallback(), $timer);
|
||||
|
||||
if ($timer->isPeriodic() && isset($timers[$timer])) {
|
||||
$timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
|
||||
$scheduler->insert($timer, -$scheduledAt);
|
||||
} else {
|
||||
$timers->detach($timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
539
vendor/react/event-loop/tests/AbstractLoopTest.php
vendored
Executable file
539
vendor/react/event-loop/tests/AbstractLoopTest.php
vendored
Executable file
@@ -0,0 +1,539 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\EventLoop;
|
||||
|
||||
abstract class AbstractLoopTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \React\EventLoop\LoopInterface
|
||||
*/
|
||||
protected $loop;
|
||||
|
||||
private $tickTimeout;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
// HHVM is a bit slow, so give it more time
|
||||
$this->tickTimeout = defined('HHVM_VERSION') ? 0.02 : 0.005;
|
||||
$this->loop = $this->createLoop();
|
||||
}
|
||||
|
||||
abstract public function createLoop();
|
||||
|
||||
public function createSocketPair()
|
||||
{
|
||||
$domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX;
|
||||
$sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
|
||||
|
||||
foreach ($sockets as $socket) {
|
||||
if (function_exists('stream_set_read_buffer')) {
|
||||
stream_set_read_buffer($socket, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return $sockets;
|
||||
}
|
||||
|
||||
public function testAddReadStream()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
|
||||
|
||||
fwrite($output, "foo\n");
|
||||
$this->loop->tick();
|
||||
|
||||
fwrite($output, "bar\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testAddReadStreamIgnoresSecondCallable()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
|
||||
$this->loop->addReadStream($input, $this->expectCallableNever());
|
||||
|
||||
fwrite($output, "foo\n");
|
||||
$this->loop->tick();
|
||||
|
||||
fwrite($output, "bar\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testAddWriteStream()
|
||||
{
|
||||
list ($input) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
|
||||
$this->loop->tick();
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testAddWriteStreamIgnoresSecondCallable()
|
||||
{
|
||||
list ($input) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
|
||||
$this->loop->addWriteStream($input, $this->expectCallableNever());
|
||||
$this->loop->tick();
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveReadStreamInstantly()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableNever());
|
||||
$this->loop->removeReadStream($input);
|
||||
|
||||
fwrite($output, "bar\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveReadStreamAfterReading()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableOnce());
|
||||
|
||||
fwrite($output, "foo\n");
|
||||
$this->loop->tick();
|
||||
|
||||
$this->loop->removeReadStream($input);
|
||||
|
||||
fwrite($output, "bar\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveWriteStreamInstantly()
|
||||
{
|
||||
list ($input) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream($input, $this->expectCallableNever());
|
||||
$this->loop->removeWriteStream($input);
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveWriteStreamAfterWriting()
|
||||
{
|
||||
list ($input) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream($input, $this->expectCallableOnce());
|
||||
$this->loop->tick();
|
||||
|
||||
$this->loop->removeWriteStream($input);
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveStreamInstantly()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableNever());
|
||||
$this->loop->addWriteStream($input, $this->expectCallableNever());
|
||||
$this->loop->removeStream($input);
|
||||
|
||||
fwrite($output, "bar\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveStreamForReadOnly()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableNever());
|
||||
$this->loop->addWriteStream($output, $this->expectCallableOnce());
|
||||
$this->loop->removeReadStream($input);
|
||||
|
||||
fwrite($output, "foo\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveStreamForWriteOnly()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
fwrite($output, "foo\n");
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableOnce());
|
||||
$this->loop->addWriteStream($output, $this->expectCallableNever());
|
||||
$this->loop->removeWriteStream($output);
|
||||
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveStream()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableOnce());
|
||||
$this->loop->addWriteStream($input, $this->expectCallableOnce());
|
||||
|
||||
fwrite($output, "bar\n");
|
||||
$this->loop->tick();
|
||||
|
||||
$this->loop->removeStream($input);
|
||||
|
||||
fwrite($output, "bar\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRemoveInvalid()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
// remove a valid stream from the event loop that was never added in the first place
|
||||
$this->loop->removeReadStream($stream);
|
||||
$this->loop->removeWriteStream($stream);
|
||||
$this->loop->removeStream($stream);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function emptyRunShouldSimplyReturn()
|
||||
{
|
||||
$this->assertRunFasterThan($this->tickTimeout);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function runShouldReturnWhenNoMoreFds()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$loop = $this->loop;
|
||||
$this->loop->addReadStream($input, function ($stream) use ($loop) {
|
||||
$loop->removeStream($stream);
|
||||
});
|
||||
|
||||
fwrite($output, "foo\n");
|
||||
|
||||
$this->assertRunFasterThan($this->tickTimeout * 2);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function stopShouldStopRunningLoop()
|
||||
{
|
||||
list ($input, $output) = $this->createSocketPair();
|
||||
|
||||
$loop = $this->loop;
|
||||
$this->loop->addReadStream($input, function ($stream) use ($loop) {
|
||||
$loop->stop();
|
||||
});
|
||||
|
||||
fwrite($output, "foo\n");
|
||||
|
||||
$this->assertRunFasterThan($this->tickTimeout * 2);
|
||||
}
|
||||
|
||||
public function testStopShouldPreventRunFromBlocking()
|
||||
{
|
||||
$this->loop->addTimer(
|
||||
1,
|
||||
function () {
|
||||
$this->fail('Timer was executed.');
|
||||
}
|
||||
);
|
||||
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
$this->loop->stop();
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertRunFasterThan($this->tickTimeout * 2);
|
||||
}
|
||||
|
||||
public function testIgnoreRemovedCallback()
|
||||
{
|
||||
// two independent streams, both should be readable right away
|
||||
list ($input1, $output1) = $this->createSocketPair();
|
||||
list ($input2, $output2) = $this->createSocketPair();
|
||||
|
||||
$called = false;
|
||||
|
||||
$loop = $this->loop;
|
||||
$loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) {
|
||||
// stream1 is readable, remove stream2 as well => this will invalidate its callback
|
||||
$loop->removeReadStream($stream);
|
||||
$loop->removeReadStream($input2);
|
||||
|
||||
$called = true;
|
||||
});
|
||||
|
||||
// this callback would have to be called as well, but the first stream already removed us
|
||||
$loop->addReadStream($input2, function () use (& $called) {
|
||||
if ($called) {
|
||||
$this->fail('Callback 2 must not be called after callback 1 was called');
|
||||
}
|
||||
});
|
||||
|
||||
fwrite($output1, "foo\n");
|
||||
fwrite($output2, "foo\n");
|
||||
|
||||
$loop->run();
|
||||
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testNextTick()
|
||||
{
|
||||
$called = false;
|
||||
|
||||
$callback = function ($loop) use (&$called) {
|
||||
$this->assertSame($this->loop, $loop);
|
||||
$called = true;
|
||||
};
|
||||
|
||||
$this->loop->nextTick($callback);
|
||||
|
||||
$this->assertFalse($called);
|
||||
|
||||
$this->loop->tick();
|
||||
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testNextTickFiresBeforeIO()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream(
|
||||
$stream,
|
||||
function () {
|
||||
echo 'stream' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
echo 'next-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
|
||||
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRecursiveNextTick()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream(
|
||||
$stream,
|
||||
function () {
|
||||
echo 'stream' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
echo 'next-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
|
||||
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRunWaitsForNextTickEvents()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream(
|
||||
$stream,
|
||||
function () use ($stream) {
|
||||
$this->loop->removeStream($stream);
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
echo 'next-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('next-tick' . PHP_EOL);
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testNextTickEventGeneratedByFutureTick()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->futureTick(
|
||||
function () {
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
echo 'next-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('next-tick' . PHP_EOL);
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testNextTickEventGeneratedByTimer()
|
||||
{
|
||||
$this->loop->addTimer(
|
||||
0.001,
|
||||
function () {
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
echo 'next-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('next-tick' . PHP_EOL);
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testFutureTick()
|
||||
{
|
||||
$called = false;
|
||||
|
||||
$callback = function ($loop) use (&$called) {
|
||||
$this->assertSame($this->loop, $loop);
|
||||
$called = true;
|
||||
};
|
||||
|
||||
$this->loop->futureTick($callback);
|
||||
|
||||
$this->assertFalse($called);
|
||||
|
||||
$this->loop->tick();
|
||||
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testFutureTickFiresBeforeIO()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream(
|
||||
$stream,
|
||||
function () {
|
||||
echo 'stream' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
|
||||
$this->loop->futureTick(
|
||||
function () {
|
||||
echo 'future-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
|
||||
|
||||
$this->loop->tick();
|
||||
}
|
||||
|
||||
public function testRecursiveFutureTick()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream(
|
||||
$stream,
|
||||
function () use ($stream) {
|
||||
echo 'stream' . PHP_EOL;
|
||||
$this->loop->removeWriteStream($stream);
|
||||
}
|
||||
);
|
||||
|
||||
$this->loop->futureTick(
|
||||
function () {
|
||||
echo 'future-tick-1' . PHP_EOL;
|
||||
$this->loop->futureTick(
|
||||
function () {
|
||||
echo 'future-tick-2' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testRunWaitsForFutureTickEvents()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->addWriteStream(
|
||||
$stream,
|
||||
function () use ($stream) {
|
||||
$this->loop->removeStream($stream);
|
||||
$this->loop->futureTick(
|
||||
function () {
|
||||
echo 'future-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('future-tick' . PHP_EOL);
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testFutureTickEventGeneratedByNextTick()
|
||||
{
|
||||
list ($stream) = $this->createSocketPair();
|
||||
|
||||
$this->loop->nextTick(
|
||||
function () {
|
||||
$this->loop->futureTick(
|
||||
function () {
|
||||
echo 'future-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('future-tick' . PHP_EOL);
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
public function testFutureTickEventGeneratedByTimer()
|
||||
{
|
||||
$this->loop->addTimer(
|
||||
0.001,
|
||||
function () {
|
||||
$this->loop->futureTick(
|
||||
function () {
|
||||
echo 'future-tick' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectOutputString('future-tick' . PHP_EOL);
|
||||
|
||||
$this->loop->run();
|
||||
}
|
||||
|
||||
private function assertRunFasterThan($maxInterval)
|
||||
{
|
||||
$start = microtime(true);
|
||||
|
||||
$this->loop->run();
|
||||
|
||||
$end = microtime(true);
|
||||
$interval = $end - $start;
|
||||
|
||||
$this->assertLessThan($maxInterval, $interval);
|
||||
}
|
||||
}
|
||||
10
vendor/react/event-loop/tests/CallableStub.php
vendored
Executable file
10
vendor/react/event-loop/tests/CallableStub.php
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\EventLoop;
|
||||
|
||||
class CallableStub
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
}
|
||||
}
|
||||
90
vendor/react/event-loop/tests/ExtEventLoopTest.php
vendored
Executable file
90
vendor/react/event-loop/tests/ExtEventLoopTest.php
vendored
Executable file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\EventLoop;
|
||||
|
||||
use React\EventLoop\ExtEventLoop;
|
||||
|
||||
class ExtEventLoopTest extends AbstractLoopTest
|
||||
{
|
||||
public function createLoop($readStreamCompatible = false)
|
||||
{
|
||||
if ('Linux' === PHP_OS && !extension_loaded('posix')) {
|
||||
$this->markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
|
||||
}
|
||||
|
||||
if (!extension_loaded('event')) {
|
||||
$this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
|
||||
}
|
||||
|
||||
$cfg = null;
|
||||
if ($readStreamCompatible) {
|
||||
$cfg = new \EventConfig();
|
||||
$cfg->requireFeatures(\EventConfig::FEATURE_FDS);
|
||||
}
|
||||
|
||||
return new ExtEventLoop($cfg);
|
||||
}
|
||||
|
||||
public function createStream()
|
||||
{
|
||||
// Use a FIFO on linux to get around lack of support for disk-based file
|
||||
// descriptors when using the EPOLL back-end.
|
||||
if ('Linux' === PHP_OS) {
|
||||
$this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
|
||||
|
||||
unlink($this->fifoPath);
|
||||
|
||||
posix_mkfifo($this->fifoPath, 0600);
|
||||
|
||||
$stream = fopen($this->fifoPath, 'r+');
|
||||
|
||||
// ext-event (as of 1.8.1) does not yet support in-memory temporary
|
||||
// streams. Setting maxmemory:0 and performing a write forces PHP to
|
||||
// back this temporary stream with a real file.
|
||||
//
|
||||
// This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3
|
||||
// but remains unresolved (despite that issue being closed).
|
||||
} else {
|
||||
$stream = fopen('php://temp/maxmemory:0', 'r+');
|
||||
|
||||
fwrite($stream, 'x');
|
||||
ftruncate($stream, 0);
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function writeToStream($stream, $content)
|
||||
{
|
||||
if ('Linux' !== PHP_OS) {
|
||||
return parent::writeToStream($stream, $content);
|
||||
}
|
||||
|
||||
fwrite($stream, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group epoll-readable-error
|
||||
*/
|
||||
public function testCanUseReadableStreamWithFeatureFds()
|
||||
{
|
||||
if (PHP_VERSION_ID > 70000) {
|
||||
$this->markTestSkipped('Memory stream not supported');
|
||||
}
|
||||
|
||||
$this->loop = $this->createLoop(true);
|
||||
|
||||
$input = fopen('php://temp/maxmemory:0', 'r+');
|
||||
|
||||
fwrite($input, 'x');
|
||||
ftruncate($input, 0);
|
||||
|
||||
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
|
||||
|
||||
fwrite($input, "foo\n");
|
||||
$this->loop->tick();
|
||||
|
||||
fwrite($input, "bar\n");
|
||||
$this->loop->tick();
|
||||
}
|
||||
}
|
||||
22
vendor/react/event-loop/tests/LibEvLoopTest.php
vendored
Executable file
22
vendor/react/event-loop/tests/LibEvLoopTest.php
vendored
Executable file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\EventLoop;
|
||||
|
||||
use React\EventLoop\LibEvLoop;
|
||||
|
||||
class LibEvLoopTest extends AbstractLoopTest
|
||||
{
|
||||
public function createLoop()
|
||||
{
|
||||
if (!class_exists('libev\EventLoop')) {
|
||||
$this->markTestSkipped('libev tests skipped because ext-libev is not installed.');
|
||||
}
|
||||
|
||||
return new LibEvLoop();
|
||||
}
|
||||
|
||||
public function testLibEvConstructor()
|
||||
{
|
||||
$loop = new LibEvLoop();
|
||||
}
|
||||
}
|
||||
58
vendor/react/event-loop/tests/LibEventLoopTest.php
vendored
Executable file
58
vendor/react/event-loop/tests/LibEventLoopTest.php
vendored
Executable file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\EventLoop;
|
||||
|
||||
use React\EventLoop\LibEventLoop;
|
||||
|
||||
class LibEventLoopTest extends AbstractLoopTest
|
||||
{
|
||||
private $fifoPath;
|
||||
|
||||
public function createLoop()
|
||||
{
|
||||
if ('Linux' === PHP_OS && !extension_loaded('posix')) {
|
||||
$this->markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
|
||||
}
|
||||
|
||||
if (!function_exists('event_base_new')) {
|
||||
$this->markTestSkipped('libevent tests skipped because ext-libevent is not installed.');
|
||||
}
|
||||
|
||||
return new LibEventLoop();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
if (file_exists($this->fifoPath)) {
|
||||
unlink($this->fifoPath);
|
||||
}
|
||||
}
|
||||
|
||||
public function createStream()
|
||||
{
|
||||
if ('Linux' !== PHP_OS) {
|
||||
return parent::createStream();
|
||||
}
|
||||
|
||||
$this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');
|
||||
|
||||
unlink($this->fifoPath);
|
||||
|
||||
// Use a FIFO on linux to get around lack of support for disk-based file
|
||||
// descriptors when using the EPOLL back-end.
|
||||
posix_mkfifo($this->fifoPath, 0600);
|
||||
|
||||
$stream = fopen($this->fifoPath, 'r+');
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function writeToStream($stream, $content)
|
||||
{
|
||||
if ('Linux' !== PHP_OS) {
|
||||
return parent::writeToStream($stream, $content);
|
||||
}
|
||||
|
||||
fwrite($stream, $content);
|
||||
}
|
||||
}
|
||||
179
vendor/react/event-loop/tests/StreamSelectLoopTest.php
vendored
Executable file
179
vendor/react/event-loop/tests/StreamSelectLoopTest.php
vendored
Executable file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace React\Tests\EventLoop;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\StreamSelectLoop;
|
||||
use React\EventLoop\Timer\Timer;
|
||||
|
||||
class StreamSelectLoopTest extends AbstractLoopTest
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
parent::tearDown();
|
||||
if (strncmp($this->getName(false), 'testSignal', 10) === 0 && extension_loaded('pcntl')) {
|
||||
$this->resetSignalHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
public function createLoop()
|
||||
{
|
||||
return new StreamSelectLoop();
|
||||
}
|
||||
|
||||
public function testStreamSelectTimeoutEmulation()
|
||||
{
|
||||
$this->loop->addTimer(
|
||||
0.05,
|
||||
$this->expectCallableOnce()
|
||||
);
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
$this->loop->run();
|
||||
|
||||
$end = microtime(true);
|
||||
$interval = $end - $start;
|
||||
|
||||
$this->assertGreaterThan(0.04, $interval);
|
||||
}
|
||||
|
||||
public function signalProvider()
|
||||
{
|
||||
return [
|
||||
['SIGUSR1'],
|
||||
['SIGHUP'],
|
||||
['SIGTERM'],
|
||||
];
|
||||
}
|
||||
|
||||
private $_signalHandled = false;
|
||||
|
||||
/**
|
||||
* Test signal interrupt when no stream is attached to the loop
|
||||
* @dataProvider signalProvider
|
||||
*/
|
||||
public function testSignalInterruptNoStream($signal)
|
||||
{
|
||||
if (!extension_loaded('pcntl')) {
|
||||
$this->markTestSkipped('"pcntl" extension is required to run this test.');
|
||||
}
|
||||
|
||||
// dispatch signal handler once before signal is sent and once after
|
||||
$this->loop->addTimer(0.01, function() { pcntl_signal_dispatch(); });
|
||||
$this->loop->addTimer(0.03, function() { pcntl_signal_dispatch(); });
|
||||
if (defined('HHVM_VERSION')) {
|
||||
// hhvm startup is slow so we need to add another handler much later
|
||||
$this->loop->addTimer(0.5, function() { pcntl_signal_dispatch(); });
|
||||
}
|
||||
|
||||
$this->setUpSignalHandler($signal);
|
||||
|
||||
// spawn external process to send signal to current process id
|
||||
$this->forkSendSignal($signal);
|
||||
$this->loop->run();
|
||||
$this->assertTrue($this->_signalHandled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test signal interrupt when a stream is attached to the loop
|
||||
* @dataProvider signalProvider
|
||||
*/
|
||||
public function testSignalInterruptWithStream($signal)
|
||||
{
|
||||
if (!extension_loaded('pcntl')) {
|
||||
$this->markTestSkipped('"pcntl" extension is required to run this test.');
|
||||
}
|
||||
|
||||
// dispatch signal handler every 10ms
|
||||
$this->loop->addPeriodicTimer(0.01, function() { pcntl_signal_dispatch(); });
|
||||
|
||||
// add stream to the loop
|
||||
list($writeStream, $readStream) = $this->createSocketPair();
|
||||
$this->loop->addReadStream($readStream, function($stream, $loop) {
|
||||
/** @var $loop LoopInterface */
|
||||
$read = fgets($stream);
|
||||
if ($read === "end loop\n") {
|
||||
$loop->stop();
|
||||
}
|
||||
});
|
||||
$this->loop->addTimer(0.05, function() use ($writeStream) {
|
||||
fwrite($writeStream, "end loop\n");
|
||||
});
|
||||
|
||||
$this->setUpSignalHandler($signal);
|
||||
|
||||
// spawn external process to send signal to current process id
|
||||
$this->forkSendSignal($signal);
|
||||
|
||||
$this->loop->run();
|
||||
|
||||
$this->assertTrue($this->_signalHandled);
|
||||
}
|
||||
|
||||
/**
|
||||
* add signal handler for signal
|
||||
*/
|
||||
protected function setUpSignalHandler($signal)
|
||||
{
|
||||
$this->_signalHandled = false;
|
||||
$this->assertTrue(pcntl_signal(constant($signal), function() { $this->_signalHandled = true; }));
|
||||
}
|
||||
|
||||
/**
|
||||
* reset all signal handlers to default
|
||||
*/
|
||||
protected function resetSignalHandlers()
|
||||
{
|
||||
foreach($this->signalProvider() as $signal) {
|
||||
pcntl_signal(constant($signal[0]), SIG_DFL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fork child process to send signal to current process id
|
||||
*/
|
||||
protected function forkSendSignal($signal)
|
||||
{
|
||||
$currentPid = posix_getpid();
|
||||
$childPid = pcntl_fork();
|
||||
if ($childPid == -1) {
|
||||
$this->fail("Failed to fork child process!");
|
||||
} else if ($childPid === 0) {
|
||||
// this is executed in the child process
|
||||
usleep(20000);
|
||||
posix_kill($currentPid, constant($signal));
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/reactphp/event-loop/issues/48
|
||||
*
|
||||
* Tests that timer with very small interval uses at least 1 microsecond
|
||||
* timeout.
|
||||
*/
|
||||
public function testSmallTimerInterval()
|
||||
{
|
||||
/** @var StreamSelectLoop|\PHPUnit_Framework_MockObject_MockObject $loop */
|
||||
$loop = $this->getMock('React\EventLoop\StreamSelectLoop', ['streamSelect']);
|
||||
$loop
|
||||
->expects($this->at(0))
|
||||
->method('streamSelect')
|
||||
->with([], [], 1);
|
||||
$loop
|
||||
->expects($this->at(1))
|
||||
->method('streamSelect')
|
||||
->with([], [], 0);
|
||||
|
||||
$callsCount = 0;
|
||||
$loop->addPeriodicTimer(Timer::MIN_INTERVAL, function() use (&$loop, &$callsCount) {
|
||||
$callsCount++;
|
||||
if ($callsCount == 2) {
|
||||
$loop->stop();
|
||||
}
|
||||
});
|
||||
|
||||
$loop->run();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user