+
This commit is contained in:
112
vendor/react/async/CHANGELOG.md
vendored
Normal file
112
vendor/react/async/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
# Changelog
|
||||
|
||||
## 4.3.0 (2024-06-04)
|
||||
|
||||
* Feature: Improve performance by avoiding unneeded references in `FiberMap`.
|
||||
(#88 by @clue)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
||||
(#87 by @clue)
|
||||
|
||||
* Improve type safety for test environment.
|
||||
(#86 by @SimonFrings)
|
||||
|
||||
## 4.2.0 (2023-11-22)
|
||||
|
||||
* Feature: Add Promise v3 template types for all public functions.
|
||||
(#40 by @WyriHaximus and @clue)
|
||||
|
||||
All our public APIs now use Promise v3 template types to guide IDEs and static
|
||||
analysis tools (like PHPStan), helping with proper type usage and improving
|
||||
code quality:
|
||||
|
||||
```php
|
||||
assertType('bool', await(resolve(true)));
|
||||
assertType('PromiseInterface<bool>', async(fn(): bool => true)());
|
||||
assertType('PromiseInterface<bool>', coroutine(fn(): bool => true));
|
||||
```
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#81 by @clue)
|
||||
|
||||
* Update test suite to avoid unhandled promise rejections.
|
||||
(#79 by @clue)
|
||||
|
||||
## 4.1.0 (2023-06-22)
|
||||
|
||||
* Feature: Add new `delay()` function to delay program execution.
|
||||
(#69 and #78 by @clue)
|
||||
|
||||
```php
|
||||
echo 'a';
|
||||
Loop::addTimer(1.0, function () {
|
||||
echo 'b';
|
||||
});
|
||||
React\Async\delay(3.0);
|
||||
echo 'c';
|
||||
|
||||
// prints "a" at t=0.0s
|
||||
// prints "b" at t=1.0s
|
||||
// prints "c" at t=3.0s
|
||||
```
|
||||
|
||||
* Update test suite, add PHPStan with `max` level and report failed assertions.
|
||||
(#66 and #76 by @clue and #61 and #73 by @WyriHaximus)
|
||||
|
||||
## 4.0.0 (2022-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2022/announcing-reactphp-async).
|
||||
|
||||
* 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.
|
||||
|
||||
* The v4 release will be the way forward for this package. However, we will still
|
||||
actively support v3 and v2 to provide a smooth upgrade path for those not yet
|
||||
on PHP 8.1+. If you're using an older PHP version, you may use either version
|
||||
which all provide a compatible API but may not take advantage of newer language
|
||||
features. You may target multiple versions at the same time to support a wider range of
|
||||
PHP versions:
|
||||
|
||||
* [`4.x` branch](https://github.com/reactphp/async/tree/4.x) (PHP 8.1+)
|
||||
* [`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+)
|
||||
* [`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+)
|
||||
|
||||
This update involves some major new features and a minor BC break over the
|
||||
`v3.0.0` release. We've tried hard to avoid BC breaks where possible and
|
||||
minimize impact otherwise. We expect that most consumers of this package will be
|
||||
affected by BC breaks, but updating should take no longer than a few minutes.
|
||||
See below for more details:
|
||||
|
||||
* Feature / BC break: Require PHP 8.1+ and add `mixed` type declarations.
|
||||
(#14 by @clue)
|
||||
|
||||
* Feature: Add Fiber-based `async()` and `await()` functions.
|
||||
(#15, #18, #19 and #20 by @WyriHaximus and #26, #28, #30, #32, #34, #55 and #57 by @clue)
|
||||
|
||||
* Project maintenance, rename `main` branch to `4.x` and update installation instructions.
|
||||
(#29 by @clue)
|
||||
|
||||
The following changes had to be ported to this release due to our branching
|
||||
strategy, but also appeared in the `v3.0.0` release:
|
||||
|
||||
* Feature: Support iterable type for `parallel()` + `series()` + `waterfall()`.
|
||||
(#49 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Promise v3.
|
||||
(#48 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#36 by @SimonFrings and #51 by @nhedger)
|
||||
|
||||
## 3.0.0 (2022-07-11)
|
||||
|
||||
See [`3.x` CHANGELOG](https://github.com/reactphp/async/blob/3.x/CHANGELOG.md) for more details.
|
||||
|
||||
## 2.0.0 (2022-07-11)
|
||||
|
||||
See [`2.x` CHANGELOG](https://github.com/reactphp/async/blob/2.x/CHANGELOG.md) for more details.
|
||||
|
||||
## 1.0.0 (2013-02-07)
|
||||
|
||||
* First tagged release
|
||||
19
vendor/react/async/LICENSE
vendored
Normal file
19
vendor/react/async/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
672
vendor/react/async/README.md
vendored
Normal file
672
vendor/react/async/README.md
vendored
Normal file
@@ -0,0 +1,672 @@
|
||||
# Async Utilities
|
||||
|
||||
[](https://github.com/reactphp/async/actions)
|
||||
[](https://packagist.org/packages/react/async)
|
||||
|
||||
Async utilities and fibers for [ReactPHP](https://reactphp.org/).
|
||||
|
||||
This library allows you to manage async control flow. It provides a number of
|
||||
combinators for [Promise](https://github.com/reactphp/promise)-based APIs.
|
||||
Instead of nesting or chaining promise callbacks, you can declare them as a
|
||||
list, which is resolved sequentially in an async manner.
|
||||
React/Async will not automagically change blocking code to be async. You need
|
||||
to have an actual event loop and non-blocking libraries interacting with that
|
||||
event loop for it to work. As long as you have a Promise-based API that runs in
|
||||
an event loop, it can be used with this library.
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
* [Usage](#usage)
|
||||
* [async()](#async)
|
||||
* [await()](#await)
|
||||
* [coroutine()](#coroutine)
|
||||
* [delay()](#delay)
|
||||
* [parallel()](#parallel)
|
||||
* [series()](#series)
|
||||
* [waterfall()](#waterfall)
|
||||
* [Todo](#todo)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Usage
|
||||
|
||||
This lightweight library consists only of a few simple functions.
|
||||
All functions reside under the `React\Async` namespace.
|
||||
|
||||
The below examples refer to all functions with their fully-qualified names like this:
|
||||
|
||||
```php
|
||||
React\Async\await(…);
|
||||
```
|
||||
|
||||
As of PHP 5.6+ you can also import each required function into your code like this:
|
||||
|
||||
```php
|
||||
use function React\Async\await;
|
||||
|
||||
await(…);
|
||||
```
|
||||
|
||||
Alternatively, you can also use an import statement similar to this:
|
||||
|
||||
```php
|
||||
use React\Async;
|
||||
|
||||
Async\await(…);
|
||||
```
|
||||
|
||||
### async()
|
||||
|
||||
The `async(callable():(PromiseInterface<T>|T) $function): (callable():PromiseInterface<T>)` function can be used to
|
||||
return an async function for a function that uses [`await()`](#await) internally.
|
||||
|
||||
This function is specifically designed to complement the [`await()` function](#await).
|
||||
The [`await()` function](#await) can be considered *blocking* from the
|
||||
perspective of the calling code. You can avoid this blocking behavior by
|
||||
wrapping it in an `async()` function call. Everything inside this function
|
||||
will still be blocked, but everything outside this function can be executed
|
||||
asynchronously without blocking:
|
||||
|
||||
```php
|
||||
Loop::addTimer(0.5, React\Async\async(function () {
|
||||
echo 'a';
|
||||
React\Async\await(React\Promise\Timer\sleep(1.0));
|
||||
echo 'c';
|
||||
}));
|
||||
|
||||
Loop::addTimer(1.0, function () {
|
||||
echo 'b';
|
||||
});
|
||||
|
||||
// prints "a" at t=0.5s
|
||||
// prints "b" at t=1.0s
|
||||
// prints "c" at t=1.5s
|
||||
```
|
||||
|
||||
See also the [`await()` function](#await) for more details.
|
||||
|
||||
Note that this function only works in tandem with the [`await()` function](#await).
|
||||
In particular, this function does not "magically" make any blocking function
|
||||
non-blocking:
|
||||
|
||||
```php
|
||||
Loop::addTimer(0.5, React\Async\async(function () {
|
||||
echo 'a';
|
||||
sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
|
||||
echo 'c';
|
||||
}));
|
||||
|
||||
Loop::addTimer(1.0, function () {
|
||||
echo 'b';
|
||||
});
|
||||
|
||||
// prints "a" at t=0.5s
|
||||
// prints "c" at t=1.5s: Correct timing, but wrong order
|
||||
// prints "b" at t=1.5s: Triggered too late because it was blocked
|
||||
```
|
||||
|
||||
As an alternative, you should always make sure to use this function in tandem
|
||||
with the [`await()` function](#await) and an async API returning a promise
|
||||
as shown in the previous example.
|
||||
|
||||
The `async()` function is specifically designed for cases where it is used
|
||||
as a callback (such as an event loop timer, event listener, or promise
|
||||
callback). For this reason, it returns a new function wrapping the given
|
||||
`$function` instead of directly invoking it and returning its value.
|
||||
|
||||
```php
|
||||
use function React\Async\async;
|
||||
|
||||
Loop::addTimer(1.0, async(function () { … }));
|
||||
$connection->on('close', async(function () { … }));
|
||||
$stream->on('data', async(function ($data) { … }));
|
||||
$promise->then(async(function (int $result) { … }));
|
||||
```
|
||||
|
||||
You can invoke this wrapping function to invoke the given `$function` with
|
||||
any arguments given as-is. The function will always return a Promise which
|
||||
will be fulfilled with whatever your `$function` returns. Likewise, it will
|
||||
return a promise that will be rejected if you throw an `Exception` or
|
||||
`Throwable` from your `$function`. This allows you to easily create
|
||||
Promise-based functions:
|
||||
|
||||
```php
|
||||
$promise = React\Async\async(function (): int {
|
||||
$browser = new React\Http\Browser();
|
||||
$urls = [
|
||||
'https://example.com/alice',
|
||||
'https://example.com/bob'
|
||||
];
|
||||
|
||||
$bytes = 0;
|
||||
foreach ($urls as $url) {
|
||||
$response = React\Async\await($browser->get($url));
|
||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
$bytes += $response->getBody()->getSize();
|
||||
}
|
||||
return $bytes;
|
||||
})();
|
||||
|
||||
$promise->then(function (int $bytes) {
|
||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
The previous example uses [`await()`](#await) inside a loop to highlight how
|
||||
this vastly simplifies consuming asynchronous operations. At the same time,
|
||||
this naive example does not leverage concurrent execution, as it will
|
||||
essentially "await" between each operation. In order to take advantage of
|
||||
concurrent execution within the given `$function`, you can "await" multiple
|
||||
promises by using a single [`await()`](#await) together with Promise-based
|
||||
primitives like this:
|
||||
|
||||
```php
|
||||
$promise = React\Async\async(function (): int {
|
||||
$browser = new React\Http\Browser();
|
||||
$urls = [
|
||||
'https://example.com/alice',
|
||||
'https://example.com/bob'
|
||||
];
|
||||
|
||||
$promises = [];
|
||||
foreach ($urls as $url) {
|
||||
$promises[] = $browser->get($url);
|
||||
}
|
||||
|
||||
try {
|
||||
$responses = React\Async\await(React\Promise\all($promises));
|
||||
} catch (Exception $e) {
|
||||
foreach ($promises as $promise) {
|
||||
$promise->cancel();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$bytes = 0;
|
||||
foreach ($responses as $response) {
|
||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
$bytes += $response->getBody()->getSize();
|
||||
}
|
||||
return $bytes;
|
||||
})();
|
||||
|
||||
$promise->then(function (int $bytes) {
|
||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
The returned promise is implemented in such a way that it can be cancelled
|
||||
when it is still pending. Cancelling a pending promise will cancel any awaited
|
||||
promises inside that fiber or any nested fibers. As such, the following example
|
||||
will only output `ab` and cancel the pending [`delay()`](#delay).
|
||||
The [`await()`](#await) calls in this example would throw a `RuntimeException`
|
||||
from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
|
||||
|
||||
```php
|
||||
$promise = async(static function (): int {
|
||||
echo 'a';
|
||||
await(async(static function (): void {
|
||||
echo 'b';
|
||||
delay(2);
|
||||
echo 'c';
|
||||
})());
|
||||
echo 'd';
|
||||
|
||||
return time();
|
||||
})();
|
||||
|
||||
$promise->cancel();
|
||||
await($promise);
|
||||
```
|
||||
|
||||
### await()
|
||||
|
||||
The `await(PromiseInterface<T> $promise): T` function can be used to
|
||||
block waiting for the given `$promise` to be fulfilled.
|
||||
|
||||
```php
|
||||
$result = React\Async\await($promise);
|
||||
```
|
||||
|
||||
This function will only return after the given `$promise` has settled, i.e.
|
||||
either fulfilled or rejected. While the promise is pending, this function
|
||||
can be considered *blocking* from the perspective of the calling code.
|
||||
You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
|
||||
call. Everything inside this function will still be blocked, but everything
|
||||
outside this function can be executed asynchronously without blocking:
|
||||
|
||||
```php
|
||||
Loop::addTimer(0.5, React\Async\async(function () {
|
||||
echo 'a';
|
||||
React\Async\await(React\Promise\Timer\sleep(1.0));
|
||||
echo 'c';
|
||||
}));
|
||||
|
||||
Loop::addTimer(1.0, function () {
|
||||
echo 'b';
|
||||
});
|
||||
|
||||
// prints "a" at t=0.5s
|
||||
// prints "b" at t=1.0s
|
||||
// prints "c" at t=1.5s
|
||||
```
|
||||
|
||||
See also the [`async()` function](#async) for more details.
|
||||
|
||||
Once the promise is fulfilled, this function will return whatever the promise
|
||||
resolved to.
|
||||
|
||||
Once the promise is rejected, this will throw whatever the promise rejected
|
||||
with. If the promise did not reject with an `Exception` or `Throwable`, then
|
||||
this function will throw an `UnexpectedValueException` instead.
|
||||
|
||||
```php
|
||||
try {
|
||||
$result = React\Async\await($promise);
|
||||
// promise successfully fulfilled with $result
|
||||
echo 'Result: ' . $result;
|
||||
} catch (Throwable $e) {
|
||||
// promise rejected with $e
|
||||
echo 'Error: ' . $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
### coroutine()
|
||||
|
||||
The `coroutine(callable(mixed ...$args):(\Generator|PromiseInterface<T>|T) $function, mixed ...$args): PromiseInterface<T>` function can be used to
|
||||
execute a Generator-based coroutine to "await" promises.
|
||||
|
||||
```php
|
||||
React\Async\coroutine(function () {
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
try {
|
||||
$response = yield $browser->get('https://example.com/');
|
||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
echo $response->getBody();
|
||||
} catch (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Using Generator-based coroutines is an alternative to directly using the
|
||||
underlying promise APIs. For many use cases, this makes using promise-based
|
||||
APIs much simpler, as it resembles a synchronous code flow more closely.
|
||||
The above example performs the equivalent of directly using the promise APIs:
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
echo $response->getBody();
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
The `yield` keyword can be used to "await" a promise resolution. Internally,
|
||||
it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
|
||||
This allows the execution to be interrupted and resumed at the same place
|
||||
when the promise is fulfilled. The `yield` statement returns whatever the
|
||||
promise is fulfilled with. If the promise is rejected, it will throw an
|
||||
`Exception` or `Throwable`.
|
||||
|
||||
The `coroutine()` function will always return a Promise which will be
|
||||
fulfilled with whatever your `$function` returns. Likewise, it will return
|
||||
a promise that will be rejected if you throw an `Exception` or `Throwable`
|
||||
from your `$function`. This allows you to easily create Promise-based
|
||||
functions:
|
||||
|
||||
```php
|
||||
$promise = React\Async\coroutine(function () {
|
||||
$browser = new React\Http\Browser();
|
||||
$urls = [
|
||||
'https://example.com/alice',
|
||||
'https://example.com/bob'
|
||||
];
|
||||
|
||||
$bytes = 0;
|
||||
foreach ($urls as $url) {
|
||||
$response = yield $browser->get($url);
|
||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
$bytes += $response->getBody()->getSize();
|
||||
}
|
||||
return $bytes;
|
||||
});
|
||||
|
||||
$promise->then(function (int $bytes) {
|
||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
The previous example uses a `yield` statement inside a loop to highlight how
|
||||
this vastly simplifies consuming asynchronous operations. At the same time,
|
||||
this naive example does not leverage concurrent execution, as it will
|
||||
essentially "await" between each operation. In order to take advantage of
|
||||
concurrent execution within the given `$function`, you can "await" multiple
|
||||
promises by using a single `yield` together with Promise-based primitives
|
||||
like this:
|
||||
|
||||
```php
|
||||
$promise = React\Async\coroutine(function () {
|
||||
$browser = new React\Http\Browser();
|
||||
$urls = [
|
||||
'https://example.com/alice',
|
||||
'https://example.com/bob'
|
||||
];
|
||||
|
||||
$promises = [];
|
||||
foreach ($urls as $url) {
|
||||
$promises[] = $browser->get($url);
|
||||
}
|
||||
|
||||
try {
|
||||
$responses = yield React\Promise\all($promises);
|
||||
} catch (Exception $e) {
|
||||
foreach ($promises as $promise) {
|
||||
$promise->cancel();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$bytes = 0;
|
||||
foreach ($responses as $response) {
|
||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
$bytes += $response->getBody()->getSize();
|
||||
}
|
||||
return $bytes;
|
||||
});
|
||||
|
||||
$promise->then(function (int $bytes) {
|
||||
echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
### delay()
|
||||
|
||||
The `delay(float $seconds): void` function can be used to
|
||||
delay program execution for duration given in `$seconds`.
|
||||
|
||||
```php
|
||||
React\Async\delay($seconds);
|
||||
```
|
||||
|
||||
This function will only return after the given number of `$seconds` have
|
||||
elapsed. If there are no other events attached to this loop, it will behave
|
||||
similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
|
||||
|
||||
```php
|
||||
echo 'a';
|
||||
React\Async\delay(1.0);
|
||||
echo 'b';
|
||||
|
||||
// prints "a" at t=0.0s
|
||||
// prints "b" at t=1.0s
|
||||
```
|
||||
|
||||
Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
|
||||
this function may not necessarily halt execution of the entire process thread.
|
||||
Instead, it allows the event loop to run any other events attached to the
|
||||
same loop until the delay returns:
|
||||
|
||||
```php
|
||||
echo 'a';
|
||||
Loop::addTimer(1.0, function (): void {
|
||||
echo 'b';
|
||||
});
|
||||
React\Async\delay(3.0);
|
||||
echo 'c';
|
||||
|
||||
// prints "a" at t=0.0s
|
||||
// prints "b" at t=1.0s
|
||||
// prints "c" at t=3.0s
|
||||
```
|
||||
|
||||
This behavior is especially useful if you want to delay the program execution
|
||||
of a particular routine, such as when building a simple polling or retry
|
||||
mechanism:
|
||||
|
||||
```php
|
||||
try {
|
||||
something();
|
||||
} catch (Throwable) {
|
||||
// in case of error, retry after a short delay
|
||||
React\Async\delay(1.0);
|
||||
something();
|
||||
}
|
||||
```
|
||||
|
||||
Because this function only returns after some time has passed, it can be
|
||||
considered *blocking* from the perspective of the calling code. You can avoid
|
||||
this blocking behavior by wrapping it in an [`async()` function](#async) call.
|
||||
Everything inside this function will still be blocked, but everything outside
|
||||
this function can be executed asynchronously without blocking:
|
||||
|
||||
```php
|
||||
Loop::addTimer(0.5, React\Async\async(function (): void {
|
||||
echo 'a';
|
||||
React\Async\delay(1.0);
|
||||
echo 'c';
|
||||
}));
|
||||
|
||||
Loop::addTimer(1.0, function (): void {
|
||||
echo 'b';
|
||||
});
|
||||
|
||||
// prints "a" at t=0.5s
|
||||
// prints "b" at t=1.0s
|
||||
// prints "c" at t=1.5s
|
||||
```
|
||||
|
||||
See also the [`async()` function](#async) for more details.
|
||||
|
||||
Internally, the `$seconds` argument will be used as a timer for the loop so that
|
||||
it keeps running until this timer triggers. This implies that if you pass a
|
||||
really small (or negative) value, it will still start a timer and will thus
|
||||
trigger at the earliest possible time in the future.
|
||||
|
||||
The function is implemented in such a way that it can be cancelled when it is
|
||||
running inside an [`async()` function](#async). Cancelling the resulting
|
||||
promise will clean up any pending timers and throw a `RuntimeException` from
|
||||
the pending delay which in turn would reject the resulting promise.
|
||||
|
||||
```php
|
||||
$promise = async(function (): void {
|
||||
echo 'a';
|
||||
delay(3.0);
|
||||
echo 'b';
|
||||
})();
|
||||
|
||||
Loop::addTimer(2.0, function () use ($promise): void {
|
||||
$promise->cancel();
|
||||
});
|
||||
|
||||
// prints "a" at t=0.0s
|
||||
// rejects $promise at t=2.0
|
||||
// never prints "b"
|
||||
```
|
||||
|
||||
### parallel()
|
||||
|
||||
The `parallel(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
|
||||
like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\Promise\Promise;
|
||||
|
||||
React\Async\parallel([
|
||||
function () {
|
||||
return new Promise(function ($resolve) {
|
||||
Loop::addTimer(1, function () use ($resolve) {
|
||||
$resolve('Slept for a whole second');
|
||||
});
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return new Promise(function ($resolve) {
|
||||
Loop::addTimer(1, function () use ($resolve) {
|
||||
$resolve('Slept for another whole second');
|
||||
});
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return new Promise(function ($resolve) {
|
||||
Loop::addTimer(1, function () use ($resolve) {
|
||||
$resolve('Slept for yet another whole second');
|
||||
});
|
||||
});
|
||||
},
|
||||
])->then(function (array $results) {
|
||||
foreach ($results as $result) {
|
||||
var_dump($result);
|
||||
}
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
### series()
|
||||
|
||||
The `series(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
|
||||
like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\Promise\Promise;
|
||||
|
||||
React\Async\series([
|
||||
function () {
|
||||
return new Promise(function ($resolve) {
|
||||
Loop::addTimer(1, function () use ($resolve) {
|
||||
$resolve('Slept for a whole second');
|
||||
});
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return new Promise(function ($resolve) {
|
||||
Loop::addTimer(1, function () use ($resolve) {
|
||||
$resolve('Slept for another whole second');
|
||||
});
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return new Promise(function ($resolve) {
|
||||
Loop::addTimer(1, function () use ($resolve) {
|
||||
$resolve('Slept for yet another whole second');
|
||||
});
|
||||
});
|
||||
},
|
||||
])->then(function (array $results) {
|
||||
foreach ($results as $result) {
|
||||
var_dump($result);
|
||||
}
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
### waterfall()
|
||||
|
||||
The `waterfall(iterable<callable(mixed=):PromiseInterface<T>> $tasks): PromiseInterface<T>` function can be used
|
||||
like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\Promise\Promise;
|
||||
|
||||
$addOne = function ($prev = 0) {
|
||||
return new Promise(function ($resolve) use ($prev) {
|
||||
Loop::addTimer(1, function () use ($prev, $resolve) {
|
||||
$resolve($prev + 1);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
React\Async\waterfall([
|
||||
$addOne,
|
||||
$addOne,
|
||||
$addOne
|
||||
])->then(function ($prev) {
|
||||
echo "Final result is $prev\n";
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
## Todo
|
||||
|
||||
* Implement queue()
|
||||
|
||||
## 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 from this branch:
|
||||
|
||||
```bash
|
||||
composer require react/async:^4.3
|
||||
```
|
||||
|
||||
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 PHP 8.1+.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
We're committed to providing long-term support (LTS) options and to provide a
|
||||
smooth upgrade path. If you're using an older PHP version, you may use the
|
||||
[`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+) or
|
||||
[`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+) which both
|
||||
provide a compatible API but do not take advantage of newer language features.
|
||||
You may target multiple versions at the same time to support a wider range of
|
||||
PHP versions like this:
|
||||
|
||||
```bash
|
||||
composer require "react/async:^4 || ^3 || ^2"
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
On top of this, we use PHPStan on max level to ensure type safety across the project:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpstan
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
|
||||
This project is heavily influenced by [async.js](https://github.com/caolan/async).
|
||||
50
vendor/react/async/composer.json
vendored
Normal file
50
vendor/react/async/composer.json
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "react/async",
|
||||
"description": "Async utilities and fibers for ReactPHP",
|
||||
"keywords": ["async", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.8 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.10.39",
|
||||
"phpunit/phpunit": "^9.6"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Async\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Async\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
vendor/react/async/src/FiberFactory.php
vendored
Normal file
33
vendor/react/async/src/FiberFactory.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace React\Async;
|
||||
|
||||
/**
|
||||
* This factory its only purpose is interoperability. Where with
|
||||
* event loops one could simply wrap another event loop. But with fibers
|
||||
* that has become impossible and as such we provide this factory and the
|
||||
* FiberInterface.
|
||||
*
|
||||
* Usage is not documented and as such not supported and might chang without
|
||||
* notice. Use at your own risk.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class FiberFactory
|
||||
{
|
||||
private static ?\Closure $factory = null;
|
||||
|
||||
public static function create(): FiberInterface
|
||||
{
|
||||
return (self::factory())();
|
||||
}
|
||||
|
||||
public static function factory(?\Closure $factory = null): \Closure
|
||||
{
|
||||
if ($factory !== null) {
|
||||
self::$factory = $factory;
|
||||
}
|
||||
|
||||
return self::$factory ?? static fn (): FiberInterface => new SimpleFiber();
|
||||
}
|
||||
}
|
||||
23
vendor/react/async/src/FiberInterface.php
vendored
Normal file
23
vendor/react/async/src/FiberInterface.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace React\Async;
|
||||
|
||||
/**
|
||||
* This interface its only purpose is interoperability. Where with
|
||||
* event loops one could simply wrap another event loop. But with fibers
|
||||
* that has become impossible and as such we provide this interface and the
|
||||
* FiberFactory.
|
||||
*
|
||||
* Usage is not documented and as such not supported and might chang without
|
||||
* notice. Use at your own risk.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface FiberInterface
|
||||
{
|
||||
public function resume(mixed $value): void;
|
||||
|
||||
public function throw(\Throwable $throwable): void;
|
||||
|
||||
public function suspend(): mixed;
|
||||
}
|
||||
42
vendor/react/async/src/FiberMap.php
vendored
Normal file
42
vendor/react/async/src/FiberMap.php
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace React\Async;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
final class FiberMap
|
||||
{
|
||||
/** @var array<int,PromiseInterface<T>> */
|
||||
private static array $map = [];
|
||||
|
||||
/**
|
||||
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
|
||||
* @param PromiseInterface<T> $promise
|
||||
*/
|
||||
public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void
|
||||
{
|
||||
self::$map[\spl_object_id($fiber)] = $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
|
||||
*/
|
||||
public static function unsetPromise(\Fiber $fiber): void
|
||||
{
|
||||
unset(self::$map[\spl_object_id($fiber)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
|
||||
* @return ?PromiseInterface<T>
|
||||
*/
|
||||
public static function getPromise(\Fiber $fiber): ?PromiseInterface
|
||||
{
|
||||
return self::$map[\spl_object_id($fiber)] ?? null;
|
||||
}
|
||||
}
|
||||
79
vendor/react/async/src/SimpleFiber.php
vendored
Normal file
79
vendor/react/async/src/SimpleFiber.php
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace React\Async;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SimpleFiber implements FiberInterface
|
||||
{
|
||||
/** @var ?\Fiber<void,void,void,callable(): mixed> */
|
||||
private static ?\Fiber $scheduler = null;
|
||||
|
||||
private static ?\Closure $suspend = null;
|
||||
|
||||
/** @var ?\Fiber<mixed,mixed,mixed,mixed> */
|
||||
private ?\Fiber $fiber = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->fiber = \Fiber::getCurrent();
|
||||
}
|
||||
|
||||
public function resume(mixed $value): void
|
||||
{
|
||||
if ($this->fiber !== null) {
|
||||
$this->fiber->resume($value);
|
||||
} else {
|
||||
self::$suspend = static fn() => $value;
|
||||
}
|
||||
|
||||
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
|
||||
$suspend = self::$suspend;
|
||||
self::$suspend = null;
|
||||
|
||||
\Fiber::suspend($suspend);
|
||||
}
|
||||
}
|
||||
|
||||
public function throw(\Throwable $throwable): void
|
||||
{
|
||||
if ($this->fiber !== null) {
|
||||
$this->fiber->throw($throwable);
|
||||
} else {
|
||||
self::$suspend = static fn() => throw $throwable;
|
||||
}
|
||||
|
||||
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
|
||||
$suspend = self::$suspend;
|
||||
self::$suspend = null;
|
||||
|
||||
\Fiber::suspend($suspend);
|
||||
}
|
||||
}
|
||||
|
||||
public function suspend(): mixed
|
||||
{
|
||||
if ($this->fiber === null) {
|
||||
if (self::$scheduler === null || self::$scheduler->isTerminated()) {
|
||||
self::$scheduler = new \Fiber(static fn() => Loop::run());
|
||||
// Run event loop to completion on shutdown.
|
||||
\register_shutdown_function(static function (): void {
|
||||
assert(self::$scheduler instanceof \Fiber);
|
||||
if (self::$scheduler->isSuspended()) {
|
||||
self::$scheduler->resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$ret = (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start());
|
||||
assert(\is_callable($ret));
|
||||
|
||||
return $ret();
|
||||
}
|
||||
|
||||
return \Fiber::suspend();
|
||||
}
|
||||
}
|
||||
846
vendor/react/async/src/functions.php
vendored
Normal file
846
vendor/react/async/src/functions.php
vendored
Normal file
@@ -0,0 +1,846 @@
|
||||
<?php
|
||||
|
||||
namespace React\Async;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\TimerInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use function React\Promise\reject;
|
||||
use function React\Promise\resolve;
|
||||
|
||||
/**
|
||||
* Return an async function for a function that uses [`await()`](#await) internally.
|
||||
*
|
||||
* This function is specifically designed to complement the [`await()` function](#await).
|
||||
* The [`await()` function](#await) can be considered *blocking* from the
|
||||
* perspective of the calling code. You can avoid this blocking behavior by
|
||||
* wrapping it in an `async()` function call. Everything inside this function
|
||||
* will still be blocked, but everything outside this function can be executed
|
||||
* asynchronously without blocking:
|
||||
*
|
||||
* ```php
|
||||
* Loop::addTimer(0.5, React\Async\async(function () {
|
||||
* echo 'a';
|
||||
* React\Async\await(React\Promise\Timer\sleep(1.0));
|
||||
* echo 'c';
|
||||
* }));
|
||||
*
|
||||
* Loop::addTimer(1.0, function () {
|
||||
* echo 'b';
|
||||
* });
|
||||
*
|
||||
* // prints "a" at t=0.5s
|
||||
* // prints "b" at t=1.0s
|
||||
* // prints "c" at t=1.5s
|
||||
* ```
|
||||
*
|
||||
* See also the [`await()` function](#await) for more details.
|
||||
*
|
||||
* Note that this function only works in tandem with the [`await()` function](#await).
|
||||
* In particular, this function does not "magically" make any blocking function
|
||||
* non-blocking:
|
||||
*
|
||||
* ```php
|
||||
* Loop::addTimer(0.5, React\Async\async(function () {
|
||||
* echo 'a';
|
||||
* sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
|
||||
* echo 'c';
|
||||
* }));
|
||||
*
|
||||
* Loop::addTimer(1.0, function () {
|
||||
* echo 'b';
|
||||
* });
|
||||
*
|
||||
* // prints "a" at t=0.5s
|
||||
* // prints "c" at t=1.5s: Correct timing, but wrong order
|
||||
* // prints "b" at t=1.5s: Triggered too late because it was blocked
|
||||
* ```
|
||||
*
|
||||
* As an alternative, you should always make sure to use this function in tandem
|
||||
* with the [`await()` function](#await) and an async API returning a promise
|
||||
* as shown in the previous example.
|
||||
*
|
||||
* The `async()` function is specifically designed for cases where it is used
|
||||
* as a callback (such as an event loop timer, event listener, or promise
|
||||
* callback). For this reason, it returns a new function wrapping the given
|
||||
* `$function` instead of directly invoking it and returning its value.
|
||||
*
|
||||
* ```php
|
||||
* use function React\Async\async;
|
||||
*
|
||||
* Loop::addTimer(1.0, async(function () { … }));
|
||||
* $connection->on('close', async(function () { … }));
|
||||
* $stream->on('data', async(function ($data) { … }));
|
||||
* $promise->then(async(function (int $result) { … }));
|
||||
* ```
|
||||
*
|
||||
* You can invoke this wrapping function to invoke the given `$function` with
|
||||
* any arguments given as-is. The function will always return a Promise which
|
||||
* will be fulfilled with whatever your `$function` returns. Likewise, it will
|
||||
* return a promise that will be rejected if you throw an `Exception` or
|
||||
* `Throwable` from your `$function`. This allows you to easily create
|
||||
* Promise-based functions:
|
||||
*
|
||||
* ```php
|
||||
* $promise = React\Async\async(function (): int {
|
||||
* $browser = new React\Http\Browser();
|
||||
* $urls = [
|
||||
* 'https://example.com/alice',
|
||||
* 'https://example.com/bob'
|
||||
* ];
|
||||
*
|
||||
* $bytes = 0;
|
||||
* foreach ($urls as $url) {
|
||||
* $response = React\Async\await($browser->get($url));
|
||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
* $bytes += $response->getBody()->getSize();
|
||||
* }
|
||||
* return $bytes;
|
||||
* })();
|
||||
*
|
||||
* $promise->then(function (int $bytes) {
|
||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The previous example uses [`await()`](#await) inside a loop to highlight how
|
||||
* this vastly simplifies consuming asynchronous operations. At the same time,
|
||||
* this naive example does not leverage concurrent execution, as it will
|
||||
* essentially "await" between each operation. In order to take advantage of
|
||||
* concurrent execution within the given `$function`, you can "await" multiple
|
||||
* promises by using a single [`await()`](#await) together with Promise-based
|
||||
* primitives like this:
|
||||
*
|
||||
* ```php
|
||||
* $promise = React\Async\async(function (): int {
|
||||
* $browser = new React\Http\Browser();
|
||||
* $urls = [
|
||||
* 'https://example.com/alice',
|
||||
* 'https://example.com/bob'
|
||||
* ];
|
||||
*
|
||||
* $promises = [];
|
||||
* foreach ($urls as $url) {
|
||||
* $promises[] = $browser->get($url);
|
||||
* }
|
||||
*
|
||||
* try {
|
||||
* $responses = React\Async\await(React\Promise\all($promises));
|
||||
* } catch (Exception $e) {
|
||||
* foreach ($promises as $promise) {
|
||||
* $promise->cancel();
|
||||
* }
|
||||
* throw $e;
|
||||
* }
|
||||
*
|
||||
* $bytes = 0;
|
||||
* foreach ($responses as $response) {
|
||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
* $bytes += $response->getBody()->getSize();
|
||||
* }
|
||||
* return $bytes;
|
||||
* })();
|
||||
*
|
||||
* $promise->then(function (int $bytes) {
|
||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The returned promise is implemented in such a way that it can be cancelled
|
||||
* when it is still pending. Cancelling a pending promise will cancel any awaited
|
||||
* promises inside that fiber or any nested fibers. As such, the following example
|
||||
* will only output `ab` and cancel the pending [`delay()`](#delay).
|
||||
* The [`await()`](#await) calls in this example would throw a `RuntimeException`
|
||||
* from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
|
||||
*
|
||||
* ```php
|
||||
* $promise = async(static function (): int {
|
||||
* echo 'a';
|
||||
* await(async(static function (): void {
|
||||
* echo 'b';
|
||||
* delay(2);
|
||||
* echo 'c';
|
||||
* })());
|
||||
* echo 'd';
|
||||
*
|
||||
* return time();
|
||||
* })();
|
||||
*
|
||||
* $promise->cancel();
|
||||
* await($promise);
|
||||
* ```
|
||||
*
|
||||
* @template T
|
||||
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
|
||||
* @template A2
|
||||
* @template A3
|
||||
* @template A4
|
||||
* @template A5
|
||||
* @param callable(A1,A2,A3,A4,A5): (PromiseInterface<T>|T) $function
|
||||
* @return callable(A1=,A2=,A3=,A4=,A5=): PromiseInterface<T>
|
||||
* @since 4.0.0
|
||||
* @see coroutine()
|
||||
*/
|
||||
function async(callable $function): callable
|
||||
{
|
||||
return static function (mixed ...$args) use ($function): PromiseInterface {
|
||||
$fiber = null;
|
||||
/** @var PromiseInterface<T> $promise*/
|
||||
$promise = new Promise(function (callable $resolve, callable $reject) use ($function, $args, &$fiber): void {
|
||||
$fiber = new \Fiber(function () use ($resolve, $reject, $function, $args, &$fiber): void {
|
||||
try {
|
||||
$resolve($function(...$args));
|
||||
} catch (\Throwable $exception) {
|
||||
$reject($exception);
|
||||
} finally {
|
||||
assert($fiber instanceof \Fiber);
|
||||
FiberMap::unsetPromise($fiber);
|
||||
}
|
||||
});
|
||||
|
||||
$fiber->start();
|
||||
}, function () use (&$fiber): void {
|
||||
assert($fiber instanceof \Fiber);
|
||||
$promise = FiberMap::getPromise($fiber);
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$lowLevelFiber = \Fiber::getCurrent();
|
||||
if ($lowLevelFiber !== null) {
|
||||
FiberMap::setPromise($lowLevelFiber, $promise);
|
||||
}
|
||||
|
||||
return $promise;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Block waiting for the given `$promise` to be fulfilled.
|
||||
*
|
||||
* ```php
|
||||
* $result = React\Async\await($promise);
|
||||
* ```
|
||||
*
|
||||
* This function will only return after the given `$promise` has settled, i.e.
|
||||
* either fulfilled or rejected. While the promise is pending, this function
|
||||
* can be considered *blocking* from the perspective of the calling code.
|
||||
* You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
|
||||
* call. Everything inside this function will still be blocked, but everything
|
||||
* outside this function can be executed asynchronously without blocking:
|
||||
*
|
||||
* ```php
|
||||
* Loop::addTimer(0.5, React\Async\async(function () {
|
||||
* echo 'a';
|
||||
* React\Async\await(React\Promise\Timer\sleep(1.0));
|
||||
* echo 'c';
|
||||
* }));
|
||||
*
|
||||
* Loop::addTimer(1.0, function () {
|
||||
* echo 'b';
|
||||
* });
|
||||
*
|
||||
* // prints "a" at t=0.5s
|
||||
* // prints "b" at t=1.0s
|
||||
* // prints "c" at t=1.5s
|
||||
* ```
|
||||
*
|
||||
* See also the [`async()` function](#async) for more details.
|
||||
*
|
||||
* Once the promise is fulfilled, this function will return whatever the promise
|
||||
* resolved to.
|
||||
*
|
||||
* Once the promise is rejected, this will throw whatever the promise rejected
|
||||
* with. If the promise did not reject with an `Exception` or `Throwable`, then
|
||||
* this function will throw an `UnexpectedValueException` instead.
|
||||
*
|
||||
* ```php
|
||||
* try {
|
||||
* $result = React\Async\await($promise);
|
||||
* // promise successfully fulfilled with $result
|
||||
* echo 'Result: ' . $result;
|
||||
* } catch (Throwable $e) {
|
||||
* // promise rejected with $e
|
||||
* echo 'Error: ' . $e->getMessage();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @template T
|
||||
* @param PromiseInterface<T> $promise
|
||||
* @return T returns whatever the promise resolves to
|
||||
* @throws \Exception when the promise is rejected with an `Exception`
|
||||
* @throws \Throwable when the promise is rejected with a `Throwable`
|
||||
* @throws \UnexpectedValueException when the promise is rejected with an unexpected value (Promise API v1 or v2 only)
|
||||
*/
|
||||
function await(PromiseInterface $promise): mixed
|
||||
{
|
||||
$fiber = null;
|
||||
$resolved = false;
|
||||
$rejected = false;
|
||||
|
||||
/** @var T $resolvedValue */
|
||||
$resolvedValue = null;
|
||||
$rejectedThrowable = null;
|
||||
$lowLevelFiber = \Fiber::getCurrent();
|
||||
|
||||
$promise->then(
|
||||
function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFiber): void {
|
||||
if ($lowLevelFiber !== null) {
|
||||
FiberMap::unsetPromise($lowLevelFiber);
|
||||
}
|
||||
|
||||
/** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */
|
||||
if ($fiber === null) {
|
||||
$resolved = true;
|
||||
/** @var T $resolvedValue */
|
||||
$resolvedValue = $value;
|
||||
return;
|
||||
}
|
||||
|
||||
$fiber->resume($value);
|
||||
},
|
||||
function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowLevelFiber): void {
|
||||
if ($lowLevelFiber !== null) {
|
||||
FiberMap::unsetPromise($lowLevelFiber);
|
||||
}
|
||||
|
||||
if (!$throwable instanceof \Throwable) {
|
||||
$throwable = new \UnexpectedValueException(
|
||||
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable)) /** @phpstan-ignore-line */
|
||||
);
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$trace = $r->getValue($throwable);
|
||||
assert(\is_array($trace));
|
||||
|
||||
// Exception trace arguments only available when zend.exception_ignore_args is not set
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as $ti => $one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as $ai => $arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($throwable, $trace);
|
||||
}
|
||||
|
||||
if ($fiber === null) {
|
||||
$rejected = true;
|
||||
$rejectedThrowable = $throwable;
|
||||
return;
|
||||
}
|
||||
|
||||
$fiber->throw($throwable);
|
||||
}
|
||||
);
|
||||
|
||||
if ($resolved) {
|
||||
return $resolvedValue;
|
||||
}
|
||||
|
||||
if ($rejected) {
|
||||
assert($rejectedThrowable instanceof \Throwable);
|
||||
throw $rejectedThrowable;
|
||||
}
|
||||
|
||||
if ($lowLevelFiber !== null) {
|
||||
FiberMap::setPromise($lowLevelFiber, $promise);
|
||||
}
|
||||
|
||||
$fiber = FiberFactory::create();
|
||||
|
||||
return $fiber->suspend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delay program execution for duration given in `$seconds`.
|
||||
*
|
||||
* ```php
|
||||
* React\Async\delay($seconds);
|
||||
* ```
|
||||
*
|
||||
* This function will only return after the given number of `$seconds` have
|
||||
* elapsed. If there are no other events attached to this loop, it will behave
|
||||
* similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
|
||||
*
|
||||
* ```php
|
||||
* echo 'a';
|
||||
* React\Async\delay(1.0);
|
||||
* echo 'b';
|
||||
*
|
||||
* // prints "a" at t=0.0s
|
||||
* // prints "b" at t=1.0s
|
||||
* ```
|
||||
*
|
||||
* Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
|
||||
* this function may not necessarily halt execution of the entire process thread.
|
||||
* Instead, it allows the event loop to run any other events attached to the
|
||||
* same loop until the delay returns:
|
||||
*
|
||||
* ```php
|
||||
* echo 'a';
|
||||
* Loop::addTimer(1.0, function (): void {
|
||||
* echo 'b';
|
||||
* });
|
||||
* React\Async\delay(3.0);
|
||||
* echo 'c';
|
||||
*
|
||||
* // prints "a" at t=0.0s
|
||||
* // prints "b" at t=1.0s
|
||||
* // prints "c" at t=3.0s
|
||||
* ```
|
||||
*
|
||||
* This behavior is especially useful if you want to delay the program execution
|
||||
* of a particular routine, such as when building a simple polling or retry
|
||||
* mechanism:
|
||||
*
|
||||
* ```php
|
||||
* try {
|
||||
* something();
|
||||
* } catch (Throwable) {
|
||||
* // in case of error, retry after a short delay
|
||||
* React\Async\delay(1.0);
|
||||
* something();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Because this function only returns after some time has passed, it can be
|
||||
* considered *blocking* from the perspective of the calling code. You can avoid
|
||||
* this blocking behavior by wrapping it in an [`async()` function](#async) call.
|
||||
* Everything inside this function will still be blocked, but everything outside
|
||||
* this function can be executed asynchronously without blocking:
|
||||
*
|
||||
* ```php
|
||||
* Loop::addTimer(0.5, React\Async\async(function (): void {
|
||||
* echo 'a';
|
||||
* React\Async\delay(1.0);
|
||||
* echo 'c';
|
||||
* }));
|
||||
*
|
||||
* Loop::addTimer(1.0, function (): void {
|
||||
* echo 'b';
|
||||
* });
|
||||
*
|
||||
* // prints "a" at t=0.5s
|
||||
* // prints "b" at t=1.0s
|
||||
* // prints "c" at t=1.5s
|
||||
* ```
|
||||
*
|
||||
* See also the [`async()` function](#async) for more details.
|
||||
*
|
||||
* Internally, the `$seconds` argument will be used as a timer for the loop so that
|
||||
* it keeps running until this timer triggers. This implies that if you pass a
|
||||
* really small (or negative) value, it will still start a timer and will thus
|
||||
* trigger at the earliest possible time in the future.
|
||||
*
|
||||
* The function is implemented in such a way that it can be cancelled when it is
|
||||
* running inside an [`async()` function](#async). Cancelling the resulting
|
||||
* promise will clean up any pending timers and throw a `RuntimeException` from
|
||||
* the pending delay which in turn would reject the resulting promise.
|
||||
*
|
||||
* ```php
|
||||
* $promise = async(function (): void {
|
||||
* echo 'a';
|
||||
* delay(3.0);
|
||||
* echo 'b';
|
||||
* })();
|
||||
*
|
||||
* Loop::addTimer(2.0, function () use ($promise): void {
|
||||
* $promise->cancel();
|
||||
* });
|
||||
*
|
||||
* // prints "a" at t=0.0s
|
||||
* // rejects $promise at t=2.0
|
||||
* // never prints "b"
|
||||
* ```
|
||||
*
|
||||
* @return void
|
||||
* @throws \RuntimeException when the function is cancelled inside an `async()` function
|
||||
* @see async()
|
||||
* @uses await()
|
||||
*/
|
||||
function delay(float $seconds): void
|
||||
{
|
||||
/** @var ?TimerInterface $timer */
|
||||
$timer = null;
|
||||
|
||||
await(new Promise(function (callable $resolve) use ($seconds, &$timer): void {
|
||||
$timer = Loop::addTimer($seconds, fn() => $resolve(null));
|
||||
}, function () use (&$timer): void {
|
||||
assert($timer instanceof TimerInterface);
|
||||
Loop::cancelTimer($timer);
|
||||
throw new \RuntimeException('Delay cancelled');
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Generator-based coroutine to "await" promises.
|
||||
*
|
||||
* ```php
|
||||
* React\Async\coroutine(function () {
|
||||
* $browser = new React\Http\Browser();
|
||||
*
|
||||
* try {
|
||||
* $response = yield $browser->get('https://example.com/');
|
||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
* echo $response->getBody();
|
||||
* } catch (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Using Generator-based coroutines is an alternative to directly using the
|
||||
* underlying promise APIs. For many use cases, this makes using promise-based
|
||||
* APIs much simpler, as it resembles a synchronous code flow more closely.
|
||||
* The above example performs the equivalent of directly using the promise APIs:
|
||||
*
|
||||
* ```php
|
||||
* $browser = new React\Http\Browser();
|
||||
*
|
||||
* $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* echo $response->getBody();
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The `yield` keyword can be used to "await" a promise resolution. Internally,
|
||||
* it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
|
||||
* This allows the execution to be interrupted and resumed at the same place
|
||||
* when the promise is fulfilled. The `yield` statement returns whatever the
|
||||
* promise is fulfilled with. If the promise is rejected, it will throw an
|
||||
* `Exception` or `Throwable`.
|
||||
*
|
||||
* The `coroutine()` function will always return a Promise which will be
|
||||
* fulfilled with whatever your `$function` returns. Likewise, it will return
|
||||
* a promise that will be rejected if you throw an `Exception` or `Throwable`
|
||||
* from your `$function`. This allows you to easily create Promise-based
|
||||
* functions:
|
||||
*
|
||||
* ```php
|
||||
* $promise = React\Async\coroutine(function () {
|
||||
* $browser = new React\Http\Browser();
|
||||
* $urls = [
|
||||
* 'https://example.com/alice',
|
||||
* 'https://example.com/bob'
|
||||
* ];
|
||||
*
|
||||
* $bytes = 0;
|
||||
* foreach ($urls as $url) {
|
||||
* $response = yield $browser->get($url);
|
||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
* $bytes += $response->getBody()->getSize();
|
||||
* }
|
||||
* return $bytes;
|
||||
* });
|
||||
*
|
||||
* $promise->then(function (int $bytes) {
|
||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The previous example uses a `yield` statement inside a loop to highlight how
|
||||
* this vastly simplifies consuming asynchronous operations. At the same time,
|
||||
* this naive example does not leverage concurrent execution, as it will
|
||||
* essentially "await" between each operation. In order to take advantage of
|
||||
* concurrent execution within the given `$function`, you can "await" multiple
|
||||
* promises by using a single `yield` together with Promise-based primitives
|
||||
* like this:
|
||||
*
|
||||
* ```php
|
||||
* $promise = React\Async\coroutine(function () {
|
||||
* $browser = new React\Http\Browser();
|
||||
* $urls = [
|
||||
* 'https://example.com/alice',
|
||||
* 'https://example.com/bob'
|
||||
* ];
|
||||
*
|
||||
* $promises = [];
|
||||
* foreach ($urls as $url) {
|
||||
* $promises[] = $browser->get($url);
|
||||
* }
|
||||
*
|
||||
* try {
|
||||
* $responses = yield React\Promise\all($promises);
|
||||
* } catch (Exception $e) {
|
||||
* foreach ($promises as $promise) {
|
||||
* $promise->cancel();
|
||||
* }
|
||||
* throw $e;
|
||||
* }
|
||||
*
|
||||
* $bytes = 0;
|
||||
* foreach ($responses as $response) {
|
||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
* $bytes += $response->getBody()->getSize();
|
||||
* }
|
||||
* return $bytes;
|
||||
* });
|
||||
*
|
||||
* $promise->then(function (int $bytes) {
|
||||
* echo 'Total size: ' . $bytes . PHP_EOL;
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @template T
|
||||
* @template TYield
|
||||
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
|
||||
* @template A2
|
||||
* @template A3
|
||||
* @template A4
|
||||
* @template A5
|
||||
* @param callable(A1, A2, A3, A4, A5):(\Generator<mixed, PromiseInterface<TYield>, TYield, PromiseInterface<T>|T>|PromiseInterface<T>|T) $function
|
||||
* @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is
|
||||
* @return PromiseInterface<T>
|
||||
* @since 3.0.0
|
||||
*/
|
||||
function coroutine(callable $function, mixed ...$args): PromiseInterface
|
||||
{
|
||||
try {
|
||||
$generator = $function(...$args);
|
||||
} catch (\Throwable $e) {
|
||||
return reject($e);
|
||||
}
|
||||
|
||||
if (!$generator instanceof \Generator) {
|
||||
return resolve($generator);
|
||||
}
|
||||
|
||||
$promise = null;
|
||||
/** @var Deferred<T> $deferred*/
|
||||
$deferred = new Deferred(function () use (&$promise) {
|
||||
/** @var ?PromiseInterface<T> $promise */
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
$promise = null;
|
||||
});
|
||||
|
||||
/** @var callable $next */
|
||||
$next = function () use ($deferred, $generator, &$next, &$promise) {
|
||||
try {
|
||||
if (!$generator->valid()) {
|
||||
$next = null;
|
||||
$deferred->resolve($generator->getReturn());
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$next = null;
|
||||
$deferred->reject($e);
|
||||
return;
|
||||
}
|
||||
|
||||
$promise = $generator->current();
|
||||
if (!$promise instanceof PromiseInterface) {
|
||||
$next = null;
|
||||
$deferred->reject(new \UnexpectedValueException(
|
||||
'Expected coroutine to yield ' . PromiseInterface::class . ', but got ' . (is_object($promise) ? get_class($promise) : gettype($promise))
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var PromiseInterface<TYield> $promise */
|
||||
assert($next instanceof \Closure);
|
||||
$promise->then(function ($value) use ($generator, $next) {
|
||||
$generator->send($value);
|
||||
$next();
|
||||
}, function (\Throwable $reason) use ($generator, $next) {
|
||||
$generator->throw($reason);
|
||||
$next();
|
||||
})->then(null, function (\Throwable $reason) use ($deferred, &$next) {
|
||||
$next = null;
|
||||
$deferred->reject($reason);
|
||||
});
|
||||
};
|
||||
$next();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
|
||||
* @return PromiseInterface<array<T>>
|
||||
*/
|
||||
function parallel(iterable $tasks): PromiseInterface
|
||||
{
|
||||
/** @var array<int,PromiseInterface<T>> $pending */
|
||||
$pending = [];
|
||||
/** @var Deferred<array<T>> $deferred */
|
||||
$deferred = new Deferred(function () use (&$pending) {
|
||||
foreach ($pending as $promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
$pending = [];
|
||||
});
|
||||
$results = [];
|
||||
$continue = true;
|
||||
|
||||
$taskErrback = function ($error) use (&$pending, $deferred, &$continue) {
|
||||
$continue = false;
|
||||
$deferred->reject($error);
|
||||
|
||||
foreach ($pending as $promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
$pending = [];
|
||||
};
|
||||
|
||||
foreach ($tasks as $i => $task) {
|
||||
$taskCallback = function ($result) use (&$results, &$pending, &$continue, $i, $deferred) {
|
||||
$results[$i] = $result;
|
||||
unset($pending[$i]);
|
||||
|
||||
if (!$pending && !$continue) {
|
||||
$deferred->resolve($results);
|
||||
}
|
||||
};
|
||||
|
||||
$promise = \call_user_func($task);
|
||||
assert($promise instanceof PromiseInterface);
|
||||
$pending[$i] = $promise;
|
||||
|
||||
$promise->then($taskCallback, $taskErrback);
|
||||
|
||||
if (!$continue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$continue = false;
|
||||
if (!$pending) {
|
||||
$deferred->resolve($results);
|
||||
}
|
||||
|
||||
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
|
||||
* @return PromiseInterface<array<T>>
|
||||
*/
|
||||
function series(iterable $tasks): PromiseInterface
|
||||
{
|
||||
$pending = null;
|
||||
/** @var Deferred<array<T>> $deferred */
|
||||
$deferred = new Deferred(function () use (&$pending) {
|
||||
/** @var ?PromiseInterface<T> $pending */
|
||||
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
|
||||
$pending->cancel();
|
||||
}
|
||||
$pending = null;
|
||||
});
|
||||
$results = [];
|
||||
|
||||
if ($tasks instanceof \IteratorAggregate) {
|
||||
$tasks = $tasks->getIterator();
|
||||
assert($tasks instanceof \Iterator);
|
||||
}
|
||||
|
||||
$taskCallback = function ($result) use (&$results, &$next) {
|
||||
$results[] = $result;
|
||||
/** @var \Closure $next */
|
||||
$next();
|
||||
};
|
||||
|
||||
$next = function () use (&$tasks, $taskCallback, $deferred, &$results, &$pending) {
|
||||
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
|
||||
$deferred->resolve($results);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($tasks instanceof \Iterator) {
|
||||
$task = $tasks->current();
|
||||
$tasks->next();
|
||||
} else {
|
||||
assert(\is_array($tasks));
|
||||
$task = \array_shift($tasks);
|
||||
}
|
||||
|
||||
assert(\is_callable($task));
|
||||
$promise = \call_user_func($task);
|
||||
assert($promise instanceof PromiseInterface);
|
||||
$pending = $promise;
|
||||
|
||||
$promise->then($taskCallback, array($deferred, 'reject'));
|
||||
};
|
||||
|
||||
$next();
|
||||
|
||||
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param iterable<(callable():(PromiseInterface<T>|T))|(callable(mixed):(PromiseInterface<T>|T))> $tasks
|
||||
* @return PromiseInterface<($tasks is non-empty-array|\Traversable ? T : null)>
|
||||
*/
|
||||
function waterfall(iterable $tasks): PromiseInterface
|
||||
{
|
||||
$pending = null;
|
||||
/** @var Deferred<T> $deferred*/
|
||||
$deferred = new Deferred(function () use (&$pending) {
|
||||
/** @var ?PromiseInterface<T> $pending */
|
||||
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
|
||||
$pending->cancel();
|
||||
}
|
||||
$pending = null;
|
||||
});
|
||||
|
||||
if ($tasks instanceof \IteratorAggregate) {
|
||||
$tasks = $tasks->getIterator();
|
||||
assert($tasks instanceof \Iterator);
|
||||
}
|
||||
|
||||
/** @var callable $next */
|
||||
$next = function ($value = null) use (&$tasks, &$next, $deferred, &$pending) {
|
||||
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
|
||||
$deferred->resolve($value);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($tasks instanceof \Iterator) {
|
||||
$task = $tasks->current();
|
||||
$tasks->next();
|
||||
} else {
|
||||
assert(\is_array($tasks));
|
||||
$task = \array_shift($tasks);
|
||||
}
|
||||
|
||||
assert(\is_callable($task));
|
||||
$promise = \call_user_func_array($task, func_get_args());
|
||||
assert($promise instanceof PromiseInterface);
|
||||
$pending = $promise;
|
||||
|
||||
$promise->then($next, array($deferred, 'reject'));
|
||||
};
|
||||
|
||||
$next();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
9
vendor/react/async/src/functions_include.php
vendored
Normal file
9
vendor/react/async/src/functions_include.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace React\Async;
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!\function_exists(__NAMESPACE__ . '\\parallel')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
96
vendor/react/cache/CHANGELOG.md
vendored
Normal file
96
vendor/react/cache/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
# Changelog
|
||||
|
||||
## 1.2.0 (2022-11-30)
|
||||
|
||||
* Feature: Support PHP 8.1 and PHP 8.2.
|
||||
(#47 by @SimonFrings and #52 by @WyriHaximus)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#48 by @SimonFrings and #51 by @nhedger)
|
||||
|
||||
* Update test suite and use GitHub actions for continuous integration (CI).
|
||||
(#45 and #49 by @SimonFrings and #54 by @clue)
|
||||
|
||||
## 1.1.0 (2020-09-18)
|
||||
|
||||
* Feature: Forward compatibility with react/promise 3.
|
||||
(#39 by @WyriHaximus)
|
||||
|
||||
* Add `.gitattributes` to exclude dev files from exports.
|
||||
(#40 by @reedy)
|
||||
|
||||
* Improve test suite, update to support PHP 8 and PHPUnit 9.3.
|
||||
(#41 and #43 by @SimonFrings and #42 by @WyriHaximus)
|
||||
|
||||
## 1.0.0 (2019-07-11)
|
||||
|
||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
||||
We'd like to emphasize that this component is production ready and battle-tested.
|
||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
||||
so you have a rock-solid foundation to build on top of.
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.6.0 release.
|
||||
|
||||
## 0.6.0 (2019-07-04)
|
||||
|
||||
* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()`
|
||||
supporting multiple cache items (inspired by PSR-16).
|
||||
(#32 by @krlv and #37 by @clue)
|
||||
|
||||
* Documentation for TTL precision with millisecond accuracy or below and
|
||||
use high-resolution timer for cache TTL on PHP 7.3+.
|
||||
(#35 and #38 by @clue)
|
||||
|
||||
* Improve API documentation and allow legacy HHVM to fail in Travis CI config.
|
||||
(#34 and #36 by @clue)
|
||||
|
||||
* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
|
||||
(#31 by @WyriHaximus)
|
||||
|
||||
## 0.5.0 (2018-06-25)
|
||||
|
||||
* Improve documentation by describing what is expected of a class implementing `CacheInterface`.
|
||||
(#21, #22, #23, #27 by @WyriHaximus)
|
||||
|
||||
* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`.
|
||||
(#26 by @clue)
|
||||
|
||||
* Added support for cache expiration (TTL).
|
||||
(#29 by @clue and @WyriHaximus)
|
||||
|
||||
* Renamed `remove` to `delete` making it more in line with `PSR-16`.
|
||||
(#30 by @clue)
|
||||
|
||||
## 0.4.2 (2017-12-20)
|
||||
|
||||
* Improve documentation with usage and installation instructions
|
||||
(#10 by @clue)
|
||||
|
||||
* Improve test suite by adding PHPUnit to `require-dev` and
|
||||
add forward compatibility with PHPUnit 5 and PHPUnit 6 and
|
||||
sanitize Composer autoload paths
|
||||
(#14 by @shaunbramley and #12 and #18 by @clue)
|
||||
|
||||
## 0.4.1 (2016-02-25)
|
||||
|
||||
* Repository maintenance, split off from main repo, improve test suite and documentation
|
||||
* First class support for PHP7 and HHVM (#9 by @clue)
|
||||
* Adjust compatibility to 5.3 (#7 by @clue)
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: Update to React/Promise 2.0
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Feature: New cache component, used by DNS
|
||||
21
vendor/react/cache/LICENSE
vendored
Normal file
21
vendor/react/cache/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
367
vendor/react/cache/README.md
vendored
Normal file
367
vendor/react/cache/README.md
vendored
Normal file
@@ -0,0 +1,367 @@
|
||||
# Cache
|
||||
|
||||
[](https://github.com/reactphp/cache/actions)
|
||||
[](https://packagist.org/packages/react/cache)
|
||||
|
||||
Async, [Promise](https://github.com/reactphp/promise)-based cache interface
|
||||
for [ReactPHP](https://reactphp.org/).
|
||||
|
||||
The cache component provides a
|
||||
[Promise](https://github.com/reactphp/promise)-based
|
||||
[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
|
||||
implementation of that.
|
||||
This allows consumers to type hint against the interface and third parties to
|
||||
provide alternate implementations.
|
||||
This project is heavily inspired by
|
||||
[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/),
|
||||
but uses an interface more suited for async, non-blocking applications.
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
* [Usage](#usage)
|
||||
* [CacheInterface](#cacheinterface)
|
||||
* [get()](#get)
|
||||
* [set()](#set)
|
||||
* [delete()](#delete)
|
||||
* [getMultiple()](#getmultiple)
|
||||
* [setMultiple()](#setmultiple)
|
||||
* [deleteMultiple()](#deletemultiple)
|
||||
* [clear()](#clear)
|
||||
* [has()](#has)
|
||||
* [ArrayCache](#arraycache)
|
||||
* [Common usage](#common-usage)
|
||||
* [Fallback get](#fallback-get)
|
||||
* [Fallback-get-and-set](#fallback-get-and-set)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Usage
|
||||
|
||||
### CacheInterface
|
||||
|
||||
The `CacheInterface` describes the main interface of this component.
|
||||
This allows consumers to type hint against the interface and third parties to
|
||||
provide alternate implementations.
|
||||
|
||||
#### get()
|
||||
|
||||
The `get(string $key, mixed $default = null): PromiseInterface<mixed>` method can be used to
|
||||
retrieve an item from the cache.
|
||||
|
||||
This method will resolve with the cached value on success or with the
|
||||
given `$default` value when no item can be found or when an error occurs.
|
||||
Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
considered a cache miss.
|
||||
|
||||
```php
|
||||
$cache
|
||||
->get('foo')
|
||||
->then('var_dump');
|
||||
```
|
||||
|
||||
This example fetches the value of the key `foo` and passes it to the
|
||||
`var_dump` function. You can use any of the composition provided by
|
||||
[promises](https://github.com/reactphp/promise).
|
||||
|
||||
#### set()
|
||||
|
||||
The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface<bool>` method can be used to
|
||||
store an item in the cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. If the cache implementation has to go over the network to store
|
||||
it, it may take a while.
|
||||
|
||||
The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
for this cache item. If this parameter is omitted (or `null`), the item
|
||||
will stay in the cache for as long as the underlying implementation
|
||||
supports. Trying to access an expired cache item results in a cache miss,
|
||||
see also [`get()`](#get).
|
||||
|
||||
```php
|
||||
$cache->set('foo', 'bar', 60);
|
||||
```
|
||||
|
||||
This example eventually sets the value of the key `foo` to `bar`. If it
|
||||
already exists, it is overridden.
|
||||
|
||||
This interface does not enforce any particular TTL resolution, so special
|
||||
care may have to be taken if you rely on very high precision with
|
||||
millisecond accuracy or below. Cache implementations SHOULD work on a
|
||||
best effort basis and SHOULD provide at least second accuracy unless
|
||||
otherwise noted. Many existing cache implementations are known to provide
|
||||
microsecond or millisecond accuracy, but it's generally not recommended
|
||||
to rely on this high precision.
|
||||
|
||||
This interface suggests that cache implementations SHOULD use a monotonic
|
||||
time source if available. Given that a monotonic time source is only
|
||||
available as of PHP 7.3 by default, cache implementations MAY fall back
|
||||
to using wall-clock time.
|
||||
While this does not affect many common use cases, this is an important
|
||||
distinction for programs that rely on a high time precision or on systems
|
||||
that are subject to discontinuous time adjustments (time jumps).
|
||||
This means that if you store a cache item with a TTL of 30s and then
|
||||
adjust your system time forward by 20s, the cache item SHOULD still
|
||||
expire in 30s.
|
||||
|
||||
#### delete()
|
||||
|
||||
The `delete(string $key): PromiseInterface<bool>` method can be used to
|
||||
delete an item from the cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. When no item for `$key` is found in the cache, it also resolves
|
||||
to `true`. If the cache implementation has to go over the network to
|
||||
delete it, it may take a while.
|
||||
|
||||
```php
|
||||
$cache->delete('foo');
|
||||
```
|
||||
|
||||
This example eventually deletes the key `foo` from the cache. As with
|
||||
`set()`, this may not happen instantly and a promise is returned to
|
||||
provide guarantees whether or not the item has been removed from cache.
|
||||
|
||||
#### getMultiple()
|
||||
|
||||
The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface<array>` method can be used to
|
||||
retrieve multiple cache items by their unique keys.
|
||||
|
||||
This method will resolve with an array of cached values on success or with the
|
||||
given `$default` value when an item can not be found or when an error occurs.
|
||||
Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
considered a cache miss.
|
||||
|
||||
```php
|
||||
$cache->getMultiple(array('name', 'age'))->then(function (array $values) {
|
||||
$name = $values['name'] ?? 'User';
|
||||
$age = $values['age'] ?? 'n/a';
|
||||
|
||||
echo $name . ' is ' . $age . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This example fetches the cache items for the `name` and `age` keys and
|
||||
prints some example output. You can use any of the composition provided
|
||||
by [promises](https://github.com/reactphp/promise).
|
||||
|
||||
#### setMultiple()
|
||||
|
||||
The `setMultiple(array $values, ?float $ttl = null): PromiseInterface<bool>` method can be used to
|
||||
persist a set of key => value pairs in the cache, with an optional TTL.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. If the cache implementation has to go over the network to store
|
||||
it, it may take a while.
|
||||
|
||||
The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
for these cache items. If this parameter is omitted (or `null`), these items
|
||||
will stay in the cache for as long as the underlying implementation
|
||||
supports. Trying to access an expired cache items results in a cache miss,
|
||||
see also [`getMultiple()`](#getmultiple).
|
||||
|
||||
```php
|
||||
$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
|
||||
```
|
||||
|
||||
This example eventually sets the list of values - the key `foo` to `1` value
|
||||
and the key `bar` to `2`. If some of the keys already exist, they are overridden.
|
||||
|
||||
#### deleteMultiple()
|
||||
|
||||
The `setMultiple(string[] $keys): PromiseInterface<bool>` method can be used to
|
||||
delete multiple cache items in a single operation.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. When no items for `$keys` are found in the cache, it also resolves
|
||||
to `true`. If the cache implementation has to go over the network to
|
||||
delete it, it may take a while.
|
||||
|
||||
```php
|
||||
$cache->deleteMultiple(array('foo', 'bar, 'baz'));
|
||||
```
|
||||
|
||||
This example eventually deletes keys `foo`, `bar` and `baz` from the cache.
|
||||
As with `setMultiple()`, this may not happen instantly and a promise is returned to
|
||||
provide guarantees whether or not the item has been removed from cache.
|
||||
|
||||
#### clear()
|
||||
|
||||
The `clear(): PromiseInterface<bool>` method can be used to
|
||||
wipe clean the entire cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when an error
|
||||
occurs. If the cache implementation has to go over the network to
|
||||
delete it, it may take a while.
|
||||
|
||||
```php
|
||||
$cache->clear();
|
||||
```
|
||||
|
||||
This example eventually deletes all keys from the cache. As with `deleteMultiple()`,
|
||||
this may not happen instantly and a promise is returned to provide guarantees
|
||||
whether or not all the items have been removed from cache.
|
||||
|
||||
#### has()
|
||||
|
||||
The `has(string $key): PromiseInterface<bool>` method can be used to
|
||||
determine whether an item is present in the cache.
|
||||
|
||||
This method will resolve with `true` on success or `false` when no item can be found
|
||||
or when an error occurs. Similarly, an expired cache item (once the time-to-live
|
||||
is expired) is considered a cache miss.
|
||||
|
||||
```php
|
||||
$cache
|
||||
->has('foo')
|
||||
->then('var_dump');
|
||||
```
|
||||
|
||||
This example checks if the value of the key `foo` is set in the cache and passes
|
||||
the result to the `var_dump` function. You can use any of the composition provided by
|
||||
[promises](https://github.com/reactphp/promise).
|
||||
|
||||
NOTE: It is recommended that has() is only to be used for cache warming type purposes
|
||||
and not to be used within your live applications operations for get/set, as this method
|
||||
is subject to a race condition where your has() will return true and immediately after,
|
||||
another script can remove it making the state of your app out of date.
|
||||
|
||||
### ArrayCache
|
||||
|
||||
The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
|
||||
|
||||
```php
|
||||
$cache = new ArrayCache();
|
||||
|
||||
$cache->set('foo', 'bar');
|
||||
```
|
||||
|
||||
Its constructor accepts an optional `?int $limit` parameter to limit the
|
||||
maximum number of entries to store in the LRU cache. If you add more
|
||||
entries to this instance, it will automatically take care of removing
|
||||
the one that was least recently used (LRU).
|
||||
|
||||
For example, this snippet will overwrite the first value and only store
|
||||
the last two entries:
|
||||
|
||||
```php
|
||||
$cache = new ArrayCache(2);
|
||||
|
||||
$cache->set('foo', '1');
|
||||
$cache->set('bar', '2');
|
||||
$cache->set('baz', '3');
|
||||
```
|
||||
|
||||
This cache implementation is known to rely on wall-clock time to schedule
|
||||
future cache expiration times when using any version before PHP 7.3,
|
||||
because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
|
||||
While this does not affect many common use cases, this is an important
|
||||
distinction for programs that rely on a high time precision or on systems
|
||||
that are subject to discontinuous time adjustments (time jumps).
|
||||
This means that if you store a cache item with a TTL of 30s on PHP < 7.3
|
||||
and then adjust your system time forward by 20s, the cache item may
|
||||
expire in 10s. See also [`set()`](#set) for more details.
|
||||
|
||||
## Common usage
|
||||
|
||||
### Fallback get
|
||||
|
||||
A common use case of caches is to attempt fetching a cached value and as a
|
||||
fallback retrieve it from the original data source if not found. Here is an
|
||||
example of that:
|
||||
|
||||
```php
|
||||
$cache
|
||||
->get('foo')
|
||||
->then(function ($result) {
|
||||
if ($result === null) {
|
||||
return getFooFromDb();
|
||||
}
|
||||
|
||||
return $result;
|
||||
})
|
||||
->then('var_dump');
|
||||
```
|
||||
|
||||
First an attempt is made to retrieve the value of `foo`. A callback function is
|
||||
registered that will call `getFooFromDb` when the resulting value is null.
|
||||
`getFooFromDb` is a function (can be any PHP callable) that will be called if the
|
||||
key does not exist in the cache.
|
||||
|
||||
`getFooFromDb` can handle the missing key by returning a promise for the
|
||||
actual value from the database (or any other data source). As a result, this
|
||||
chain will correctly fall back, and provide the value in both cases.
|
||||
|
||||
### Fallback get and set
|
||||
|
||||
To expand on the fallback get example, often you want to set the value on the
|
||||
cache after fetching it from the data source.
|
||||
|
||||
```php
|
||||
$cache
|
||||
->get('foo')
|
||||
->then(function ($result) {
|
||||
if ($result === null) {
|
||||
return $this->getAndCacheFooFromDb();
|
||||
}
|
||||
|
||||
return $result;
|
||||
})
|
||||
->then('var_dump');
|
||||
|
||||
public function getAndCacheFooFromDb()
|
||||
{
|
||||
return $this->db
|
||||
->get('foo')
|
||||
->then(array($this, 'cacheFooFromDb'));
|
||||
}
|
||||
|
||||
public function cacheFooFromDb($foo)
|
||||
{
|
||||
$this->cache->set('foo', $foo);
|
||||
|
||||
return $foo;
|
||||
}
|
||||
```
|
||||
|
||||
By using chaining you can easily conditionally cache the value if it is
|
||||
fetched from the database.
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](https://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/cache:^1.2
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use PHP 7+* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
45
vendor/react/cache/composer.json
vendored
Normal file
45
vendor/react/cache/composer.json
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "react/cache",
|
||||
"description": "Async, Promise-based cache interface for ReactPHP",
|
||||
"keywords": ["cache", "caching", "promise", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"react/promise": "^3.0 || ^2.0 || ^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Cache\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Cache\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
181
vendor/react/cache/src/ArrayCache.php
vendored
Normal file
181
vendor/react/cache/src/ArrayCache.php
vendored
Normal 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
Normal file
194
vendor/react/cache/src/CacheInterface.php
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace React\Cache;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
interface CacheInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves an item from the cache.
|
||||
*
|
||||
* This method will resolve with the cached value on success or with the
|
||||
* given `$default` value when no item can be found or when an error occurs.
|
||||
* Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
* considered a cache miss.
|
||||
*
|
||||
* ```php
|
||||
* $cache
|
||||
* ->get('foo')
|
||||
* ->then('var_dump');
|
||||
* ```
|
||||
*
|
||||
* This example fetches the value of the key `foo` and passes it to the
|
||||
* `var_dump` function. You can use any of the composition provided by
|
||||
* [promises](https://github.com/reactphp/promise).
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default Default value to return for cache miss or null if not given.
|
||||
* @return PromiseInterface<mixed>
|
||||
*/
|
||||
public function get($key, $default = null);
|
||||
|
||||
/**
|
||||
* Stores an item in the cache.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when an error
|
||||
* occurs. If the cache implementation has to go over the network to store
|
||||
* it, it may take a while.
|
||||
*
|
||||
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
* for this cache item. If this parameter is omitted (or `null`), the item
|
||||
* will stay in the cache for as long as the underlying implementation
|
||||
* supports. Trying to access an expired cache item results in a cache miss,
|
||||
* see also [`get()`](#get).
|
||||
*
|
||||
* ```php
|
||||
* $cache->set('foo', 'bar', 60);
|
||||
* ```
|
||||
*
|
||||
* This example eventually sets the value of the key `foo` to `bar`. If it
|
||||
* already exists, it is overridden.
|
||||
*
|
||||
* This interface does not enforce any particular TTL resolution, so special
|
||||
* care may have to be taken if you rely on very high precision with
|
||||
* millisecond accuracy or below. Cache implementations SHOULD work on a
|
||||
* best effort basis and SHOULD provide at least second accuracy unless
|
||||
* otherwise noted. Many existing cache implementations are known to provide
|
||||
* microsecond or millisecond accuracy, but it's generally not recommended
|
||||
* to rely on this high precision.
|
||||
*
|
||||
* This interface suggests that cache implementations SHOULD use a monotonic
|
||||
* time source if available. Given that a monotonic time source is only
|
||||
* available as of PHP 7.3 by default, cache implementations MAY fall back
|
||||
* to using wall-clock time.
|
||||
* While this does not affect many common use cases, this is an important
|
||||
* distinction for programs that rely on a high time precision or on systems
|
||||
* that are subject to discontinuous time adjustments (time jumps).
|
||||
* This means that if you store a cache item with a TTL of 30s and then
|
||||
* adjust your system time forward by 20s, the cache item SHOULD still
|
||||
* expire in 30s.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param ?float $ttl
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function set($key, $value, $ttl = null);
|
||||
|
||||
/**
|
||||
* Deletes an item from the cache.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when an error
|
||||
* occurs. When no item for `$key` is found in the cache, it also resolves
|
||||
* to `true`. If the cache implementation has to go over the network to
|
||||
* delete it, it may take a while.
|
||||
*
|
||||
* ```php
|
||||
* $cache->delete('foo');
|
||||
* ```
|
||||
*
|
||||
* This example eventually deletes the key `foo` from the cache. As with
|
||||
* `set()`, this may not happen instantly and a promise is returned to
|
||||
* provide guarantees whether or not the item has been removed from cache.
|
||||
*
|
||||
* @param string $key
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function delete($key);
|
||||
|
||||
/**
|
||||
* Retrieves multiple cache items by their unique keys.
|
||||
*
|
||||
* This method will resolve with an array of cached values on success or with the
|
||||
* given `$default` value when an item can not be found or when an error occurs.
|
||||
* Similarly, an expired cache item (once the time-to-live is expired) is
|
||||
* considered a cache miss.
|
||||
*
|
||||
* ```php
|
||||
* $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
|
||||
* $name = $values['name'] ?? 'User';
|
||||
* $age = $values['age'] ?? 'n/a';
|
||||
*
|
||||
* echo $name . ' is ' . $age . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This example fetches the cache items for the `name` and `age` keys and
|
||||
* prints some example output. You can use any of the composition provided
|
||||
* by [promises](https://github.com/reactphp/promise).
|
||||
*
|
||||
* @param string[] $keys A list of keys that can obtained in a single operation.
|
||||
* @param mixed $default Default value to return for keys that do not exist.
|
||||
* @return PromiseInterface<array> Returns a promise which resolves to an `array` of cached values
|
||||
*/
|
||||
public function getMultiple(array $keys, $default = null);
|
||||
|
||||
/**
|
||||
* Persists a set of key => value pairs in the cache, with an optional TTL.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when an error
|
||||
* occurs. If the cache implementation has to go over the network to store
|
||||
* it, it may take a while.
|
||||
*
|
||||
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
|
||||
* for these cache items. If this parameter is omitted (or `null`), these items
|
||||
* will stay in the cache for as long as the underlying implementation
|
||||
* supports. Trying to access an expired cache items results in a cache miss,
|
||||
* see also [`get()`](#get).
|
||||
*
|
||||
* ```php
|
||||
* $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
|
||||
* ```
|
||||
*
|
||||
* This example eventually sets the list of values - the key `foo` to 1 value
|
||||
* and the key `bar` to 2. If some of the keys already exist, they are overridden.
|
||||
*
|
||||
* @param array $values A list of key => value pairs for a multiple-set operation.
|
||||
* @param ?float $ttl Optional. The TTL value of this item.
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function setMultiple(array $values, $ttl = null);
|
||||
|
||||
/**
|
||||
* Deletes multiple cache items in a single operation.
|
||||
*
|
||||
* @param string[] $keys A list of string-based keys to be deleted.
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function deleteMultiple(array $keys);
|
||||
|
||||
/**
|
||||
* Wipes clean the entire cache.
|
||||
*
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function clear();
|
||||
|
||||
/**
|
||||
* Determines whether an item is present in the cache.
|
||||
*
|
||||
* This method will resolve with `true` on success or `false` when no item can be found
|
||||
* or when an error occurs. Similarly, an expired cache item (once the time-to-live
|
||||
* is expired) is considered a cache miss.
|
||||
*
|
||||
* ```php
|
||||
* $cache
|
||||
* ->has('foo')
|
||||
* ->then('var_dump');
|
||||
* ```
|
||||
*
|
||||
* This example checks if the value of the key `foo` is set in the cache and passes
|
||||
* the result to the `var_dump` function. You can use any of the composition provided by
|
||||
* [promises](https://github.com/reactphp/promise).
|
||||
*
|
||||
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
|
||||
* and not to be used within your live applications operations for get/set, as this method
|
||||
* is subject to a race condition where your has() will return true and immediately after,
|
||||
* another script can remove it making the state of your app out of date.
|
||||
*
|
||||
* @param string $key The cache item key.
|
||||
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
|
||||
*/
|
||||
public function has($key);
|
||||
}
|
||||
452
vendor/react/dns/CHANGELOG.md
vendored
Normal file
452
vendor/react/dns/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
# Changelog
|
||||
|
||||
## 1.13.0 (2024-06-13)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
||||
(#224 by @WyriHaximus)
|
||||
|
||||
## 1.12.0 (2023-11-29)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#217 by @sergiy-petrov)
|
||||
|
||||
* Update test environment and avoid unhandled promise rejections.
|
||||
(#215, #216 and #218 by @clue)
|
||||
|
||||
## 1.11.0 (2023-06-02)
|
||||
|
||||
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
|
||||
(#213 by @clue)
|
||||
|
||||
* Improve test suite and project setup and report failed assertions.
|
||||
(#210 by @clue, #212 by @WyriHaximus and #209 and #211 by @SimonFrings)
|
||||
|
||||
## 1.10.0 (2022-09-08)
|
||||
|
||||
* Feature: Full support for PHP 8.2 release.
|
||||
(#201 by @clue and #207 by @WyriHaximus)
|
||||
|
||||
* Feature: Optimize forward compatibility with Promise v3, avoid hitting autoloader.
|
||||
(#202 by @clue)
|
||||
|
||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
||||
(#197 by @clue)
|
||||
|
||||
* Fix: Fix invalid references in exception stack trace.
|
||||
(#191 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#195 by @SimonFrings and #203 by @nhedger)
|
||||
|
||||
* Improve test suite, update to use default loop and new reactphp/async package.
|
||||
(#204, #205 and #206 by @clue and #196 by @SimonFrings)
|
||||
|
||||
## 1.9.0 (2021-12-20)
|
||||
|
||||
* Feature: Full support for PHP 8.1 release and prepare PHP 8.2 compatibility
|
||||
by refactoring `Parser` to avoid assigning dynamic properties.
|
||||
(#188 and #186 by @clue and #184 by @SimonFrings)
|
||||
|
||||
* Feature: Avoid dependency on `ext-filter`.
|
||||
(#185 by @clue)
|
||||
|
||||
* Feature / Fix: Skip invalid nameserver entries from `resolv.conf` and ignore IPv6 zone IDs.
|
||||
(#187 by @clue)
|
||||
|
||||
* Feature / Fix: Reduce socket read chunk size for queries over TCP/IP.
|
||||
(#189 by @clue)
|
||||
|
||||
## 1.8.0 (2021-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
|
||||
(#182 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->create($config, $loop);
|
||||
|
||||
// new (using default loop)
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$resolver = $factory->create($config);
|
||||
```
|
||||
|
||||
## 1.7.0 (2021-06-25)
|
||||
|
||||
* Feature: Update DNS `Factory` to accept complete `Config` object.
|
||||
Add new `FallbackExecutor` and use fallback DNS servers when `Config` lists multiple servers.
|
||||
(#179 and #180 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
$resolver = $factory->create($server, $loop);
|
||||
|
||||
// new
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
$resolver = $factory->create($config, $loop);
|
||||
```
|
||||
|
||||
## 1.6.0 (2021-06-21)
|
||||
|
||||
* Feature: Add support for legacy `SPF` record type.
|
||||
(#178 by @akondas and @clue)
|
||||
|
||||
* Fix: Fix integer overflow for TCP/IP chunk size on 32 bit platforms.
|
||||
(#177 by @clue)
|
||||
|
||||
## 1.5.0 (2021-03-05)
|
||||
|
||||
* Feature: Improve error reporting when query fails, include domain and query type and DNS server address where applicable.
|
||||
(#174 by @clue)
|
||||
|
||||
* Feature: Improve error handling when sending data to DNS server fails (macOS).
|
||||
(#171 and #172 by @clue)
|
||||
|
||||
* Fix: Improve DNS response parser to limit recursion for compressed labels.
|
||||
(#169 by @clue)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
||||
(#170 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2020-09-18)
|
||||
|
||||
* Feature: Support upcoming PHP 8.
|
||||
(#168 by @clue)
|
||||
|
||||
* Improve test suite and update to PHPUnit 9.3.
|
||||
(#164 by @clue, #165 and #166 by @SimonFrings and #167 by @WyriHaximus)
|
||||
|
||||
## 1.3.0 (2020-07-10)
|
||||
|
||||
* Feature: Forward compatibility with react/promise v3.
|
||||
(#153 by @WyriHaximus)
|
||||
|
||||
* Feature: Support parsing `OPT` records (EDNS0).
|
||||
(#157 by @clue)
|
||||
|
||||
* Fix: Avoid PHP warnings due to lack of args in exception trace on PHP 7.4.
|
||||
(#160 by @clue)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Run tests on PHPUnit 9 and PHP 7.4 and clean up test suite.
|
||||
(#154 by @reedy, #156 by @clue and #163 by @SimonFrings)
|
||||
|
||||
## 1.2.0 (2019-08-15)
|
||||
|
||||
* Feature: Add `TcpTransportExecutor` to send DNS queries over TCP/IP connection,
|
||||
add `SelectiveTransportExecutor` to retry with TCP if UDP is truncated and
|
||||
automatically select transport protocol when no explicit `udp://` or `tcp://` scheme is given in `Factory`.
|
||||
(#145, #146, #147 and #148 by @clue)
|
||||
|
||||
* Feature: Support escaping literal dots and special characters in domain names.
|
||||
(#144 by @clue)
|
||||
|
||||
## 1.1.0 (2019-07-18)
|
||||
|
||||
* Feature: Support parsing `CAA` and `SSHFP` records.
|
||||
(#141 and #142 by @clue)
|
||||
|
||||
* Feature: Add `ResolverInterface` as common interface for `Resolver` class.
|
||||
(#139 by @clue)
|
||||
|
||||
* Fix: Add missing private property definitions and
|
||||
remove unneeded dependency on `react/stream`.
|
||||
(#140 and #143 by @clue)
|
||||
|
||||
## 1.0.0 (2019-07-11)
|
||||
|
||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
||||
We'd like to emphasize that this component is production ready and battle-tested.
|
||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
||||
so you have a rock-solid foundation to build on top of.
|
||||
|
||||
This update involves a number of BC breaks due to dropped support for
|
||||
deprecated functionality and some internal API cleanup. We've tried hard to
|
||||
avoid BC breaks where possible and minimize impact otherwise. We expect that
|
||||
most consumers of this package will actually not be affected by any BC
|
||||
breaks, see below for more details:
|
||||
|
||||
* BC break: Delete all deprecated APIs, use `Query` objects for `Message` questions
|
||||
instead of nested arrays and increase code coverage to 100%.
|
||||
(#130 by @clue)
|
||||
|
||||
* BC break: Move `$nameserver` from `ExecutorInterface` to `UdpTransportExecutor`,
|
||||
remove advanced/internal `UdpTransportExecutor` args for `Parser`/`BinaryDumper` and
|
||||
add API documentation for `ExecutorInterface`.
|
||||
(#135, #137 and #138 by @clue)
|
||||
|
||||
* BC break: Replace `HeaderBag` attributes with simple `Message` properties.
|
||||
(#132 by @clue)
|
||||
|
||||
* BC break: Mark all `Record` attributes as required, add documentation vs `Query`.
|
||||
(#136 by @clue)
|
||||
|
||||
* BC break: Mark all classes as final to discourage inheritance
|
||||
(#134 by @WyriHaximus)
|
||||
|
||||
## 0.4.19 (2019-07-10)
|
||||
|
||||
* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
|
||||
(#133 by @clue)
|
||||
|
||||
## 0.4.18 (2019-09-07)
|
||||
|
||||
* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
|
||||
respect TTL from response records when caching and do not cache truncated responses.
|
||||
(#129 by @clue)
|
||||
|
||||
* Feature: Limit cache size to 256 last responses by default.
|
||||
(#127 by @clue)
|
||||
|
||||
* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
|
||||
(#125 by @clue)
|
||||
|
||||
## 0.4.17 (2019-04-01)
|
||||
|
||||
* Feature: Support parsing `authority` and `additional` records from DNS response.
|
||||
(#123 by @clue)
|
||||
|
||||
* Feature: Support dumping records as part of outgoing binary DNS message.
|
||||
(#124 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
|
||||
(#121 by @clue)
|
||||
|
||||
* Improve test suite to add forward compatibility with PHPUnit 7,
|
||||
test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
|
||||
(#122 by @clue)
|
||||
|
||||
## 0.4.16 (2018-11-11)
|
||||
|
||||
* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
|
||||
(#118 by @clue)
|
||||
|
||||
* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
|
||||
malformed record data or malformed compressed domain name labels.
|
||||
(#115 and #117 by @clue)
|
||||
|
||||
* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
|
||||
(#116 by @clue)
|
||||
|
||||
* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
|
||||
(#112 by @clue)
|
||||
|
||||
## 0.4.15 (2018-07-02)
|
||||
|
||||
* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
|
||||
(#110 by @clue and @WyriHaximus)
|
||||
|
||||
```php
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
|
||||
(#104, #105, #106, #107 and #108 by @clue)
|
||||
|
||||
* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
|
||||
(#104 by @clue)
|
||||
|
||||
* Feature: Improve error messages for failed queries and improve documentation.
|
||||
(#109 by @clue)
|
||||
|
||||
* Feature: Add reverse DNS lookup example.
|
||||
(#111 by @clue)
|
||||
|
||||
## 0.4.14 (2018-06-26)
|
||||
|
||||
* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
|
||||
to avoid cache poisoning attacks and deprecate legacy `Executor`.
|
||||
(#101 and #103 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with Cache 0.5
|
||||
(#102 by @clue)
|
||||
|
||||
* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
|
||||
(#99 by @clue)
|
||||
|
||||
## 0.4.13 (2018-02-27)
|
||||
|
||||
* Add `Config::loadSystemConfigBlocking()` to load default system config
|
||||
and support parsing DNS config on all supported platforms
|
||||
(`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
|
||||
(#92, #93, #94 and #95 by @clue)
|
||||
|
||||
```php
|
||||
$config = Config::loadSystemConfigBlocking();
|
||||
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
|
||||
```
|
||||
|
||||
* Remove unneeded cyclic dependency on react/socket
|
||||
(#96 by @clue)
|
||||
|
||||
## 0.4.12 (2018-01-14)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6,
|
||||
test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
|
||||
add test group to skip integration tests relying on internet connection
|
||||
and add minor documentation improvements.
|
||||
(#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
|
||||
|
||||
## 0.4.11 (2017-08-25)
|
||||
|
||||
* Feature: Support resolving from default hosts file
|
||||
(#75, #76 and #77 by @clue)
|
||||
|
||||
This means that resolving hosts such as `localhost` will now work as
|
||||
expected across all platforms with no changes required:
|
||||
|
||||
```php
|
||||
$resolver->resolve('localhost')->then(function ($ip) {
|
||||
echo 'IP: ' . $ip;
|
||||
});
|
||||
```
|
||||
|
||||
The new `HostsExecutor` exists for advanced usage and is otherwise used
|
||||
internally for this feature.
|
||||
|
||||
## 0.4.10 (2017-08-10)
|
||||
|
||||
* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
|
||||
lock minimum dependencies and work around circular dependency for tests
|
||||
(#70 and #71 by @clue)
|
||||
|
||||
* Fix: Work around DNS timeout issues for Windows users
|
||||
(#74 by @clue)
|
||||
|
||||
* Documentation and examples for advanced usage
|
||||
(#66 by @WyriHaximus)
|
||||
|
||||
* Remove broken TCP code, do not retry with invalid TCP query
|
||||
(#73 by @clue)
|
||||
|
||||
* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
|
||||
lock Travis distro so new defaults will not break the build and
|
||||
fix failing tests for PHP 7.1
|
||||
(#68 by @WyriHaximus and #69 and #72 by @clue)
|
||||
|
||||
## 0.4.9 (2017-05-01)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#61 by @clue)
|
||||
|
||||
## 0.4.8 (2017-04-16)
|
||||
|
||||
* Feature: Add support for the AAAA record type to the protocol parser
|
||||
(#58 by @othillo)
|
||||
|
||||
* Feature: Add support for the PTR record type to the protocol parser
|
||||
(#59 by @othillo)
|
||||
|
||||
## 0.4.7 (2017-03-31)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
|
||||
(#57 by @clue)
|
||||
|
||||
## 0.4.6 (2017-03-11)
|
||||
|
||||
* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
|
||||
with Stream v0.5 and upcoming v0.6
|
||||
(#53 by @clue)
|
||||
|
||||
* Improve test suite by adding PHPUnit to `require-dev`
|
||||
(#54 by @clue)
|
||||
|
||||
## 0.4.5 (2017-03-02)
|
||||
|
||||
* Fix: Ensure we ignore the case of the answer
|
||||
(#51 by @WyriHaximus)
|
||||
|
||||
* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
|
||||
code re-use for upcoming versions.
|
||||
(#48 and #49 by @clue)
|
||||
|
||||
## 0.4.4 (2017-02-13)
|
||||
|
||||
* Fix: Fix handling connection and stream errors
|
||||
(#45 by @clue)
|
||||
|
||||
* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
|
||||
(#46 and #47 by @clue)
|
||||
|
||||
## 0.4.3 (2016-07-31)
|
||||
|
||||
* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
|
||||
|
||||
```php
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
|
||||
$cache = new MyCustomCacheInstance();
|
||||
$resolver = $factory->createCached('8.8.8.8', $loop, $cache);
|
||||
```
|
||||
|
||||
* Feature: Support Promise cancellation (#35 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolve('reactphp.org');
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
## 0.4.2 (2016-02-24)
|
||||
|
||||
* Repository maintenance, split off from main repo, improve test suite and documentation
|
||||
* First class support for PHP7 and HHVM (#34 by @clue)
|
||||
* Adjust compatibility to 5.3 (#30 by @clue)
|
||||
|
||||
## 0.4.1 (2014-04-13)
|
||||
|
||||
* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: Update to React/Promise 2.0
|
||||
* Bug fix: Properly resolve CNAME aliases
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
* Bump React dependencies to v0.4
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Feature: Support default port for IPv6 addresses (@clue)
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Bump React dependencies to v0.3
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Feature: New cache component, used by DNS
|
||||
|
||||
## 0.2.5 (2012-11-26)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.4 (2012-11-18)
|
||||
|
||||
* Feature: Change to promise-based API (@jsor)
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.2 (2012-10-28)
|
||||
|
||||
* Feature: DNS executor timeout handling (@arnaud-lb)
|
||||
* Feature: DNS retry executor (@arnaud-lb)
|
||||
|
||||
## 0.2.1 (2012-10-14)
|
||||
|
||||
* Minor adjustments to DNS parser
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Feature: DNS resolver
|
||||
21
vendor/react/dns/LICENSE
vendored
Normal file
21
vendor/react/dns/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
453
vendor/react/dns/README.md
vendored
Normal file
453
vendor/react/dns/README.md
vendored
Normal file
@@ -0,0 +1,453 @@
|
||||
# DNS
|
||||
|
||||
[](https://github.com/reactphp/dns/actions)
|
||||
[](https://packagist.org/packages/react/dns)
|
||||
|
||||
Async DNS resolver for [ReactPHP](https://reactphp.org/).
|
||||
|
||||
The main point of the DNS component is to provide async DNS resolution.
|
||||
However, it is really a toolkit for working with DNS messages, and could
|
||||
easily be used to create a DNS server.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Basic usage](#basic-usage)
|
||||
* [Caching](#caching)
|
||||
* [Custom cache adapter](#custom-cache-adapter)
|
||||
* [ResolverInterface](#resolverinterface)
|
||||
* [resolve()](#resolve)
|
||||
* [resolveAll()](#resolveall)
|
||||
* [Advanced usage](#advanced-usage)
|
||||
* [UdpTransportExecutor](#udptransportexecutor)
|
||||
* [TcpTransportExecutor](#tcptransportexecutor)
|
||||
* [SelectiveTransportExecutor](#selectivetransportexecutor)
|
||||
* [HostsFileExecutor](#hostsfileexecutor)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
* [References](#references)
|
||||
|
||||
## Basic usage
|
||||
|
||||
The most basic usage is to just create a resolver through the resolver
|
||||
factory. All you need to give it is a nameserver, then you can start resolving
|
||||
names, baby!
|
||||
|
||||
```php
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->create($config);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
```
|
||||
|
||||
See also the [first example](examples).
|
||||
|
||||
The `Config` class can be used to load the system default config. This is an
|
||||
operation that may access the filesystem and block. Ideally, this method should
|
||||
thus be executed only once before the loop starts and not repeatedly while it is
|
||||
running.
|
||||
Note that this class may return an *empty* configuration if the system config
|
||||
can not be loaded. As such, you'll likely want to apply a default nameserver
|
||||
as above if none can be found.
|
||||
|
||||
> Note that the factory loads the hosts file from the filesystem once when
|
||||
creating the resolver instance.
|
||||
Ideally, this method should thus be executed only once before the loop starts
|
||||
and not repeatedly while it is running.
|
||||
|
||||
But there's more.
|
||||
|
||||
## Caching
|
||||
|
||||
You can cache results by configuring the resolver to use a `CachedExecutor`:
|
||||
|
||||
```php
|
||||
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8';
|
||||
}
|
||||
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached($config);
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
|
||||
...
|
||||
|
||||
$dns->resolve('igor.io')->then(function ($ip) {
|
||||
echo "Host: $ip\n";
|
||||
});
|
||||
```
|
||||
|
||||
If the first call returns before the second, only one query will be executed.
|
||||
The second result will be served from an in memory cache.
|
||||
This is particularly useful for long running scripts where the same hostnames
|
||||
have to be looked up multiple times.
|
||||
|
||||
See also the [third example](examples).
|
||||
|
||||
### Custom cache adapter
|
||||
|
||||
By default, the above will use an in memory cache.
|
||||
|
||||
You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
|
||||
|
||||
```php
|
||||
$cache = new React\Cache\ArrayCache();
|
||||
$factory = new React\Dns\Resolver\Factory();
|
||||
$dns = $factory->createCached('8.8.8.8', null, $cache);
|
||||
```
|
||||
|
||||
See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
|
||||
|
||||
## ResolverInterface
|
||||
|
||||
<a id="resolver"><!-- legacy reference --></a>
|
||||
|
||||
### resolve()
|
||||
|
||||
The `resolve(string $domain): PromiseInterface<string>` method can be used to
|
||||
resolve the given $domain name to a single IPv4 address (type `A` query).
|
||||
|
||||
```php
|
||||
$resolver->resolve('reactphp.org')->then(function ($ip) {
|
||||
echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This is one of the main methods in this package. It sends a DNS query
|
||||
for the given $domain name to your DNS server and returns a single IP
|
||||
address on success.
|
||||
|
||||
If the DNS server sends a DNS response message that contains more than
|
||||
one IP address for this query, it will randomly pick one of the IP
|
||||
addresses from the response. If you want the full list of IP addresses
|
||||
or want to send a different type of query, you should use the
|
||||
[`resolveAll()`](#resolveall) method instead.
|
||||
|
||||
If the DNS server sends a DNS response message that indicates an error
|
||||
code, this method will reject with a `RecordNotFoundException`. Its
|
||||
message and code can be used to check for the response code.
|
||||
|
||||
If the DNS communication fails and the server does not respond with a
|
||||
valid response message, this message will reject with an `Exception`.
|
||||
|
||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolve('reactphp.org');
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
### resolveAll()
|
||||
|
||||
The `resolveAll(string $host, int $type): PromiseInterface<array>` method can be used to
|
||||
resolve all record values for the given $domain name and query $type.
|
||||
|
||||
```php
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
|
||||
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
|
||||
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This is one of the main methods in this package. It sends a DNS query
|
||||
for the given $domain name to your DNS server and returns a list with all
|
||||
record values on success.
|
||||
|
||||
If the DNS server sends a DNS response message that contains one or more
|
||||
records for this query, it will return a list with all record values
|
||||
from the response. You can use the `Message::TYPE_*` constants to control
|
||||
which type of query will be sent. Note that this method always returns a
|
||||
list of record values, but each record value type depends on the query
|
||||
type. For example, it returns the IPv4 addresses for type `A` queries,
|
||||
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
|
||||
`CNAME` and `PTR` queries and structured data for other queries. See also
|
||||
the `Record` documentation for more details.
|
||||
|
||||
If the DNS server sends a DNS response message that indicates an error
|
||||
code, this method will reject with a `RecordNotFoundException`. Its
|
||||
message and code can be used to check for the response code.
|
||||
|
||||
If the DNS communication fails and the server does not respond with a
|
||||
valid response message, this message will reject with an `Exception`.
|
||||
|
||||
Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
|
||||
```php
|
||||
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
|
||||
|
||||
$promise->cancel();
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### UdpTransportExecutor
|
||||
|
||||
The `UdpTransportExecutor` can be used to
|
||||
send DNS queries over a UDP transport.
|
||||
|
||||
This is the main class that sends a DNS query to your DNS server and is used
|
||||
internally by the `Resolver` for the actual message transport.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `igor.io`.
|
||||
|
||||
```php
|
||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
See also the [fourth example](examples).
|
||||
|
||||
Note that this executor does not implement a timeout, so you will very likely
|
||||
want to use this in combination with a `TimeoutExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
);
|
||||
```
|
||||
|
||||
Also note that this executor uses an unreliable UDP transport and that it
|
||||
does not implement any retry logic, so you will likely want to use this in
|
||||
combination with a `RetryExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new RetryExecutor(
|
||||
new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
any number of queries concurrently. You should probably limit the number of
|
||||
concurrent queries in your application or you're very likely going to face
|
||||
rate limitations and bans on the resolver end. For many common applications,
|
||||
you may want to avoid sending the same query multiple times when the first
|
||||
one is still pending, so you will likely want to use this in combination with
|
||||
a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new TimeoutExecutor(
|
||||
new UdpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
> Internally, this class uses PHP's UDP sockets and does not take advantage
|
||||
of [react/datagram](https://github.com/reactphp/datagram) purely for
|
||||
organizational reasons to avoid a cyclic dependency between the two
|
||||
packages. Higher-level components should take advantage of the Datagram
|
||||
component instead of reimplementing this socket logic from scratch.
|
||||
|
||||
### TcpTransportExecutor
|
||||
|
||||
The `TcpTransportExecutor` class can be used to
|
||||
send DNS queries over a TCP/IP stream transport.
|
||||
|
||||
This is one of the main classes that send a DNS query to your DNS server.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
|
||||
```php
|
||||
$executor = new TcpTransportExecutor('8.8.8.8:53');
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
See also [example #92](examples).
|
||||
|
||||
Note that this executor does not implement a timeout, so you will very likely
|
||||
want to use this in combination with a `TimeoutExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver),
|
||||
3.0
|
||||
);
|
||||
```
|
||||
|
||||
Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
|
||||
transport, so you do not necessarily have to implement any retry logic.
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
queries concurrently. The first query will establish a TCP/IP socket
|
||||
connection to the DNS server which will be kept open for a short period.
|
||||
Additional queries will automatically reuse this existing socket connection
|
||||
to the DNS server, will pipeline multiple requests over this single
|
||||
connection and will keep an idle connection open for a short period. The
|
||||
initial TCP/IP connection overhead may incur a slight delay if you only send
|
||||
occasional queries – when sending a larger number of concurrent queries over
|
||||
an existing connection, it becomes increasingly more efficient and avoids
|
||||
creating many concurrent sockets like the UDP-based executor. You may still
|
||||
want to limit the number of (concurrent) queries in your application or you
|
||||
may be facing rate limitations and bans on the resolver end. For many common
|
||||
applications, you may want to avoid sending the same query multiple times
|
||||
when the first one is still pending, so you will likely want to use this in
|
||||
combination with a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver),
|
||||
3.0
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
> Internally, this class uses PHP's TCP/IP sockets and does not take advantage
|
||||
of [react/socket](https://github.com/reactphp/socket) purely for
|
||||
organizational reasons to avoid a cyclic dependency between the two
|
||||
packages. Higher-level components should take advantage of the Socket
|
||||
component instead of reimplementing this socket logic from scratch.
|
||||
|
||||
### SelectiveTransportExecutor
|
||||
|
||||
The `SelectiveTransportExecutor` class can be used to
|
||||
Send DNS queries over a UDP or TCP/IP stream transport.
|
||||
|
||||
This class will automatically choose the correct transport protocol to send
|
||||
a DNS query to your DNS server. It will always try to send it over the more
|
||||
efficient UDP transport first. If this query yields a size related issue
|
||||
(truncated messages), it will retry over a streaming TCP/IP transport.
|
||||
|
||||
For more advanced usages one can utilize this class directly.
|
||||
The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
|
||||
```php
|
||||
$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
|
||||
|
||||
$executor->query(
|
||||
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
)->then(function (Message $message) {
|
||||
foreach ($message->answers as $answer) {
|
||||
echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
}
|
||||
}, 'printf');
|
||||
```
|
||||
|
||||
Note that this executor only implements the logic to select the correct
|
||||
transport for the given DNS query. Implementing the correct transport logic,
|
||||
implementing timeouts and any retry logic is left up to the given executors,
|
||||
see also [`UdpTransportExecutor`](#udptransportexecutor) and
|
||||
[`TcpTransportExecutor`](#tcptransportexecutor) for more details.
|
||||
|
||||
Note that this executor is entirely async and as such allows you to execute
|
||||
any number of queries concurrently. You should probably limit the number of
|
||||
concurrent queries in your application or you're very likely going to face
|
||||
rate limitations and bans on the resolver end. For many common applications,
|
||||
you may want to avoid sending the same query multiple times when the first
|
||||
one is still pending, so you will likely want to use this in combination with
|
||||
a `CoopExecutor` like this:
|
||||
|
||||
```php
|
||||
$executor = new CoopExecutor(
|
||||
new SelectiveTransportExecutor(
|
||||
$datagramExecutor,
|
||||
$streamExecutor
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### HostsFileExecutor
|
||||
|
||||
Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
|
||||
If you also want to take entries from your hosts file into account, you may
|
||||
use this code:
|
||||
|
||||
```php
|
||||
$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
|
||||
|
||||
$executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
$executor = new HostsFileExecutor($hosts, $executor);
|
||||
|
||||
$executor->query(
|
||||
new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
|
||||
);
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
The recommended way to install this library is [through Composer](https://getcomposer.org/).
|
||||
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
|
||||
|
||||
This project follows [SemVer](https://semver.org/).
|
||||
This will install the latest supported version:
|
||||
|
||||
```bash
|
||||
composer require react/dns:^1.13
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit
|
||||
```
|
||||
|
||||
The test suite also contains a number of functional integration tests that rely
|
||||
on a stable internet connection.
|
||||
If you do not want to run these, they can simply be skipped like this:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit --exclude-group internet
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT, see [LICENSE file](LICENSE).
|
||||
|
||||
## References
|
||||
|
||||
* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
|
||||
* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification
|
||||
49
vendor/react/dns/composer.json
vendored
Normal file
49
vendor/react/dns/composer.json
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "react/dns",
|
||||
"description": "Async DNS resolver for ReactPHP",
|
||||
"keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"react/cache": "^1.0 || ^0.6 || ^0.5",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4.3 || ^3 || ^2",
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Dns\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Dns\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/BadServerException.php
vendored
Normal file
7
vendor/react/dns/src/BadServerException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
final class BadServerException extends \Exception
|
||||
{
|
||||
}
|
||||
137
vendor/react/dns/src/Config/Config.php
vendored
Normal file
137
vendor/react/dns/src/Config/Config.php
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class Config
|
||||
{
|
||||
/**
|
||||
* Loads the system DNS configuration
|
||||
*
|
||||
* Note that this method may block while loading its internal files and/or
|
||||
* commands and should thus be used with care! While this should be
|
||||
* relatively fast for most systems, it remains unknown if this may block
|
||||
* under certain circumstances. In particular, this method should only be
|
||||
* executed before the loop starts, not while it is running.
|
||||
*
|
||||
* Note that this method will try to access its files and/or commands and
|
||||
* try to parse its output. Currently, this will only parse valid nameserver
|
||||
* entries from its output and will ignore all other output without
|
||||
* complaining.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid nameserver entries can be found.
|
||||
*
|
||||
* @return self
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function loadSystemConfigBlocking()
|
||||
{
|
||||
// Use WMIC output on Windows
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
return self::loadWmicBlocking();
|
||||
}
|
||||
|
||||
// otherwise (try to) load from resolv.conf
|
||||
try {
|
||||
return self::loadResolvConfBlocking();
|
||||
} catch (RuntimeException $ignored) {
|
||||
// return empty config if parsing fails (file not found)
|
||||
return new self();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a resolv.conf file (from the given path or default location)
|
||||
*
|
||||
* Note that this method blocks while loading the given path and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* resolv.conf files, this may be an issue if this file is located on a slow
|
||||
* device or contains an excessive number of entries. In particular, this
|
||||
* method should only be executed before the loop starts, not while it is
|
||||
* running.
|
||||
*
|
||||
* Note that this method will throw if the given file can not be loaded,
|
||||
* such as if it is not readable or does not exist. In particular, this file
|
||||
* is not available on Windows.
|
||||
*
|
||||
* Currently, this will only parse valid "nameserver X" lines from the
|
||||
* given file contents. Lines can be commented out with "#" and ";" and
|
||||
* invalid lines will be ignored without complaining. See also
|
||||
* `man resolv.conf` for more details.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid "nameserver X" lines can be found. See also
|
||||
* `man resolv.conf` which suggests that the DNS server on the localhost
|
||||
* should be used in this case. This is left up to higher level consumers
|
||||
* of this API.
|
||||
*
|
||||
* @param ?string $path (optional) path to resolv.conf file or null=load default location
|
||||
* @return self
|
||||
* @throws RuntimeException if the path can not be loaded (does not exist)
|
||||
*/
|
||||
public static function loadResolvConfBlocking($path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = '/etc/resolv.conf';
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($path);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"');
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
|
||||
|
||||
$config = new self();
|
||||
foreach ($matches[1] as $ip) {
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
|
||||
$ip = substr($ip, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($ip) !== false) {
|
||||
$config->nameservers[] = $ip;
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the DNS configurations from Windows's WMIC (from the given command or default command)
|
||||
*
|
||||
* Note that this method blocks while loading the given command and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* WMIC commands, it remains unknown if this may block under certain
|
||||
* circumstances. In particular, this method should only be executed before
|
||||
* the loop starts, not while it is running.
|
||||
*
|
||||
* Note that this method will only try to execute the given command try to
|
||||
* parse its output, irrespective of whether this command exists. In
|
||||
* particular, this command is only available on Windows. Currently, this
|
||||
* will only parse valid nameserver entries from the command output and will
|
||||
* ignore all other output without complaining.
|
||||
*
|
||||
* Note that the previous section implies that this may return an empty
|
||||
* `Config` object if no valid nameserver entries can be found.
|
||||
*
|
||||
* @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
|
||||
* @return self
|
||||
* @link https://ss64.com/nt/wmic.html
|
||||
*/
|
||||
public static function loadWmicBlocking($command = null)
|
||||
{
|
||||
$contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
|
||||
preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
|
||||
|
||||
$config = new self();
|
||||
$config->nameservers = $matches[1];
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public $nameservers = array();
|
||||
}
|
||||
153
vendor/react/dns/src/Config/HostsFile.php
vendored
Normal file
153
vendor/react/dns/src/Config/HostsFile.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Config;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Represents a static hosts file which maps hostnames to IPs
|
||||
*
|
||||
* Hosts files are used on most systems to avoid actually hitting the DNS for
|
||||
* certain common hostnames.
|
||||
*
|
||||
* Most notably, this file usually contains an entry to map "localhost" to the
|
||||
* local IP. Windows is a notable exception here, as Windows does not actually
|
||||
* include "localhost" in this file by default. To compensate for this, this
|
||||
* class may explicitly be wrapped in another HostsFile instance which
|
||||
* hard-codes these entries for Windows (see also Factory).
|
||||
*
|
||||
* This class mostly exists to abstract the parsing/extraction process so this
|
||||
* can be replaced with a faster alternative in the future.
|
||||
*/
|
||||
class HostsFile
|
||||
{
|
||||
/**
|
||||
* Returns the default path for the hosts file on this system
|
||||
*
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function getDefaultPath()
|
||||
{
|
||||
// use static path for all Unix-based systems
|
||||
if (DIRECTORY_SEPARATOR !== '\\') {
|
||||
return '/etc/hosts';
|
||||
}
|
||||
|
||||
// Windows actually stores the path in the registry under
|
||||
// \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
|
||||
$path = '%SystemRoot%\\system32\drivers\etc\hosts';
|
||||
|
||||
$base = getenv('SystemRoot');
|
||||
if ($base === false) {
|
||||
$base = 'C:\\Windows';
|
||||
}
|
||||
|
||||
return str_replace('%SystemRoot%', $base, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a hosts file (from the given path or default location)
|
||||
*
|
||||
* Note that this method blocks while loading the given path and should
|
||||
* thus be used with care! While this should be relatively fast for normal
|
||||
* hosts file, this may be an issue if this file is located on a slow device
|
||||
* or contains an excessive number of entries. In particular, this method
|
||||
* should only be executed before the loop starts, not while it is running.
|
||||
*
|
||||
* @param ?string $path (optional) path to hosts file or null=load default location
|
||||
* @return self
|
||||
* @throws RuntimeException if the path can not be loaded (does not exist)
|
||||
*/
|
||||
public static function loadFromPathBlocking($path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = self::getDefaultPath();
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($path);
|
||||
if ($contents === false) {
|
||||
throw new RuntimeException('Unable to load hosts file "' . $path . '"');
|
||||
}
|
||||
|
||||
return new self($contents);
|
||||
}
|
||||
|
||||
private $contents;
|
||||
|
||||
/**
|
||||
* Instantiate new hosts file with the given hosts file contents
|
||||
*
|
||||
* @param string $contents
|
||||
*/
|
||||
public function __construct($contents)
|
||||
{
|
||||
// remove all comments from the contents
|
||||
$contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
|
||||
|
||||
$this->contents = $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all IPs for the given hostname
|
||||
*
|
||||
* @param string $name
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIpsForHost($name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
||||
$ips = array();
|
||||
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
|
||||
$parts = preg_split('/\s+/', $line);
|
||||
$ip = array_shift($parts);
|
||||
if ($parts && array_search($name, $parts) !== false) {
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
|
||||
$ip = substr($ip, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($ip) !== false) {
|
||||
$ips[] = $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all hostnames for the given IPv4 or IPv6 address
|
||||
*
|
||||
* @param string $ip
|
||||
* @return string[]
|
||||
*/
|
||||
public function getHostsForIp($ip)
|
||||
{
|
||||
// check binary representation of IP to avoid string case and short notation
|
||||
$ip = @inet_pton($ip);
|
||||
if ($ip === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$names = array();
|
||||
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
|
||||
$parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$addr = (string) array_shift($parts);
|
||||
|
||||
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
|
||||
if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
|
||||
$addr = substr($addr, 0, $pos);
|
||||
}
|
||||
|
||||
if (@inet_pton($addr) === $ip) {
|
||||
foreach ($parts as $part) {
|
||||
$names[] = $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
}
|
||||
230
vendor/react/dns/src/Model/Message.php
vendored
Normal file
230
vendor/react/dns/src/Model/Message.php
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
use React\Dns\Query\Query;
|
||||
|
||||
/**
|
||||
* This class represents an outgoing query message or an incoming response message
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
*/
|
||||
final class Message
|
||||
{
|
||||
const TYPE_A = 1;
|
||||
const TYPE_NS = 2;
|
||||
const TYPE_CNAME = 5;
|
||||
const TYPE_SOA = 6;
|
||||
const TYPE_PTR = 12;
|
||||
const TYPE_MX = 15;
|
||||
const TYPE_TXT = 16;
|
||||
const TYPE_AAAA = 28;
|
||||
const TYPE_SRV = 33;
|
||||
const TYPE_SSHFP = 44;
|
||||
|
||||
/**
|
||||
* pseudo-type for EDNS0
|
||||
*
|
||||
* These are included in the additional section and usually not in answer section.
|
||||
* Defined in [RFC 6891](https://tools.ietf.org/html/rfc6891) (or older
|
||||
* [RFC 2671](https://tools.ietf.org/html/rfc2671)).
|
||||
*
|
||||
* The OPT record uses the "class" field to store the maximum size.
|
||||
*
|
||||
* The OPT record uses the "ttl" field to store additional flags.
|
||||
*/
|
||||
const TYPE_OPT = 41;
|
||||
|
||||
/**
|
||||
* Sender Policy Framework (SPF) had a dedicated SPF type which has been
|
||||
* deprecated in favor of reusing the existing TXT type.
|
||||
*
|
||||
* @deprecated https://datatracker.ietf.org/doc/html/rfc7208#section-3.1
|
||||
* @see self::TYPE_TXT
|
||||
*/
|
||||
const TYPE_SPF = 99;
|
||||
|
||||
const TYPE_ANY = 255;
|
||||
const TYPE_CAA = 257;
|
||||
|
||||
const CLASS_IN = 1;
|
||||
|
||||
const OPCODE_QUERY = 0;
|
||||
const OPCODE_IQUERY = 1; // inverse query
|
||||
const OPCODE_STATUS = 2;
|
||||
|
||||
const RCODE_OK = 0;
|
||||
const RCODE_FORMAT_ERROR = 1;
|
||||
const RCODE_SERVER_FAILURE = 2;
|
||||
const RCODE_NAME_ERROR = 3;
|
||||
const RCODE_NOT_IMPLEMENTED = 4;
|
||||
const RCODE_REFUSED = 5;
|
||||
|
||||
/**
|
||||
* The edns-tcp-keepalive EDNS0 Option
|
||||
*
|
||||
* Option value contains a `?float` with timeout in seconds (in 0.1s steps)
|
||||
* for DNS response or `null` for DNS query.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7828
|
||||
*/
|
||||
const OPT_TCP_KEEPALIVE = 11;
|
||||
|
||||
/**
|
||||
* The EDNS(0) Padding Option
|
||||
*
|
||||
* Option value contains a `string` with binary data (usually variable
|
||||
* number of null bytes)
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7830
|
||||
*/
|
||||
const OPT_PADDING = 12;
|
||||
|
||||
/**
|
||||
* Creates a new request message for the given query
|
||||
*
|
||||
* @param Query $query
|
||||
* @return self
|
||||
*/
|
||||
public static function createRequestForQuery(Query $query)
|
||||
{
|
||||
$request = new Message();
|
||||
$request->id = self::generateId();
|
||||
$request->rd = true;
|
||||
$request->questions[] = $query;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new response message for the given query with the given answer records
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Record[] $answers
|
||||
* @return self
|
||||
*/
|
||||
public static function createResponseWithAnswersForQuery(Query $query, array $answers)
|
||||
{
|
||||
$response = new Message();
|
||||
$response->id = self::generateId();
|
||||
$response->qr = true;
|
||||
$response->rd = true;
|
||||
|
||||
$response->questions[] = $query;
|
||||
|
||||
foreach ($answers as $record) {
|
||||
$response->answers[] = $record;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* generates a random 16 bit message ID
|
||||
*
|
||||
* This uses a CSPRNG so that an outside attacker that is sending spoofed
|
||||
* DNS response messages can not guess the message ID to avoid possible
|
||||
* cache poisoning attacks.
|
||||
*
|
||||
* The `random_int()` function is only available on PHP 7+ or when
|
||||
* https://github.com/paragonie/random_compat is installed. As such, using
|
||||
* the latest supported PHP version is highly recommended. This currently
|
||||
* falls back to a less secure random number generator on older PHP versions
|
||||
* in the hope that this system is properly protected against outside
|
||||
* attackers, for example by using one of the common local DNS proxy stubs.
|
||||
*
|
||||
* @return int
|
||||
* @see self::getId()
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private static function generateId()
|
||||
{
|
||||
if (function_exists('random_int')) {
|
||||
return random_int(0, 0xffff);
|
||||
}
|
||||
return mt_rand(0, 0xffff);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 16 bit message ID
|
||||
*
|
||||
* The response message ID has to match the request message ID. This allows
|
||||
* the receiver to verify this is the correct response message. An outside
|
||||
* attacker may try to inject fake responses by "guessing" the message ID,
|
||||
* so this should use a proper CSPRNG to avoid possible cache poisoning.
|
||||
*
|
||||
* @var int 16 bit message ID
|
||||
* @see self::generateId()
|
||||
*/
|
||||
public $id = 0;
|
||||
|
||||
/**
|
||||
* @var bool Query/Response flag, query=false or response=true
|
||||
*/
|
||||
public $qr = false;
|
||||
|
||||
/**
|
||||
* @var int specifies the kind of query (4 bit), see self::OPCODE_* constants
|
||||
* @see self::OPCODE_QUERY
|
||||
*/
|
||||
public $opcode = self::OPCODE_QUERY;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var bool Authoritative Answer
|
||||
*/
|
||||
public $aa = false;
|
||||
|
||||
/**
|
||||
* @var bool TrunCation
|
||||
*/
|
||||
public $tc = false;
|
||||
|
||||
/**
|
||||
* @var bool Recursion Desired
|
||||
*/
|
||||
public $rd = false;
|
||||
|
||||
/**
|
||||
* @var bool Recursion Available
|
||||
*/
|
||||
public $ra = false;
|
||||
|
||||
/**
|
||||
* @var int response code (4 bit), see self::RCODE_* constants
|
||||
* @see self::RCODE_OK
|
||||
*/
|
||||
public $rcode = Message::RCODE_OK;
|
||||
|
||||
/**
|
||||
* An array of Query objects
|
||||
*
|
||||
* ```php
|
||||
* $questions = array(
|
||||
* new Query(
|
||||
* 'reactphp.org',
|
||||
* Message::TYPE_A,
|
||||
* Message::CLASS_IN
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @var Query[]
|
||||
*/
|
||||
public $questions = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $answers = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $authority = array();
|
||||
|
||||
/**
|
||||
* @var Record[]
|
||||
*/
|
||||
public $additional = array();
|
||||
}
|
||||
153
vendor/react/dns/src/Model/Record.php
vendored
Normal file
153
vendor/react/dns/src/Model/Record.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Model;
|
||||
|
||||
/**
|
||||
* This class represents a single resulting record in a response message
|
||||
*
|
||||
* It uses a structure similar to `\React\Dns\Query\Query`, but does include
|
||||
* fields for resulting TTL and resulting record data (IPs etc.).
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
* @see \React\Dns\Query\Query
|
||||
*/
|
||||
final class Record
|
||||
{
|
||||
/**
|
||||
* @var string hostname without trailing dot, for example "reactphp.org"
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int see Message::TYPE_* constants (UINT16)
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Defines the network class, usually `Message::CLASS_IN`.
|
||||
*
|
||||
* For `OPT` records (EDNS0), this defines the maximum message size instead.
|
||||
*
|
||||
* @var int see Message::CLASS_IN constant (UINT16)
|
||||
* @see Message::CLASS_IN
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* Defines the maximum time-to-live (TTL) in seconds
|
||||
*
|
||||
* For `OPT` records (EDNS0), this defines additional flags instead.
|
||||
*
|
||||
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
|
||||
* @link https://tools.ietf.org/html/rfc2181#section-8
|
||||
* @link https://tools.ietf.org/html/rfc6891#section-6.1.3 for `OPT` records (EDNS0)
|
||||
*/
|
||||
public $ttl;
|
||||
|
||||
/**
|
||||
* The payload data for this record
|
||||
*
|
||||
* The payload data format depends on the record type. As a rule of thumb,
|
||||
* this library will try to express this in a way that can be consumed
|
||||
* easily without having to worry about DNS internals and its binary transport:
|
||||
*
|
||||
* - A:
|
||||
* IPv4 address string, for example "192.168.1.1".
|
||||
*
|
||||
* - AAAA:
|
||||
* IPv6 address string, for example "::1".
|
||||
*
|
||||
* - CNAME / PTR / NS:
|
||||
* The hostname without trailing dot, for example "reactphp.org".
|
||||
*
|
||||
* - TXT:
|
||||
* List of string values, for example `["v=spf1 include:example.com"]`.
|
||||
* This is commonly a list with only a single string value, but this
|
||||
* technically allows multiple strings (0-255 bytes each) in a single
|
||||
* record. This is rarely used and depending on application you may want
|
||||
* to join these together or handle them separately. Each string can
|
||||
* transport any binary data, its character encoding is not defined (often
|
||||
* ASCII/UTF-8 in practice). [RFC 1464](https://tools.ietf.org/html/rfc1464)
|
||||
* suggests using key-value pairs such as `["name=test","version=1"]`, but
|
||||
* interpretation of this is not enforced and left up to consumers of this
|
||||
* library (used for DNS-SD/Zeroconf and others).
|
||||
*
|
||||
* - MX:
|
||||
* Mail server priority (UINT16) and target hostname without trailing dot,
|
||||
* for example `{"priority":10,"target":"mx.example.com"}`.
|
||||
* The payload data uses an associative array with fixed keys "priority"
|
||||
* (also commonly referred to as weight or preference) and "target" (also
|
||||
* referred to as exchange). If a response message contains multiple
|
||||
* records of this type, targets should be sorted by priority (lowest
|
||||
* first) - this is left up to consumers of this library (used for SMTP).
|
||||
*
|
||||
* - SRV:
|
||||
* Service priority (UINT16), service weight (UINT16), service port (UINT16)
|
||||
* and target hostname without trailing dot, for example
|
||||
* `{"priority":10,"weight":50,"port":8080,"target":"example.com"}`.
|
||||
* The payload data uses an associative array with fixed keys "priority",
|
||||
* "weight", "port" and "target" (also referred to as name).
|
||||
* The target may be an empty host name string if the service is decidedly
|
||||
* not available. If a response message contains multiple records of this
|
||||
* type, targets should be sorted by priority (lowest first) and selected
|
||||
* randomly according to their weight - this is left up to consumers of
|
||||
* this library, see also [RFC 2782](https://tools.ietf.org/html/rfc2782)
|
||||
* for more details.
|
||||
*
|
||||
* - SSHFP:
|
||||
* Includes algorithm (UNIT8), fingerprint type (UNIT8) and fingerprint
|
||||
* value as lower case hex string, for example:
|
||||
* `{"algorithm":1,"type":1,"fingerprint":"0123456789abcdef..."}`
|
||||
* See also https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml
|
||||
* for algorithm and fingerprint type assignments.
|
||||
*
|
||||
* - SOA:
|
||||
* Includes master hostname without trailing dot, responsible person email
|
||||
* as hostname without trailing dot and serial, refresh, retry, expire and
|
||||
* minimum times in seconds (UINT32 each), for example:
|
||||
* `{"mname":"ns.example.com","rname":"hostmaster.example.com","serial":
|
||||
* 2018082601,"refresh":3600,"retry":1800,"expire":60000,"minimum":3600}`.
|
||||
*
|
||||
* - CAA:
|
||||
* Includes flag (UNIT8), tag string and value string, for example:
|
||||
* `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
|
||||
*
|
||||
* - OPT:
|
||||
* Special pseudo-type for EDNS0. Includes an array of additional opt codes
|
||||
* with a value according to the respective OPT code. See `Message::OPT_*`
|
||||
* for list of supported OPT codes. Any other OPT code not currently
|
||||
* supported will be an opaque binary string containing the raw data
|
||||
* as transported in the DNS record. For forwards compatibility, you should
|
||||
* not rely on this format for unknown types. Future versions may add
|
||||
* support for new types and this may then parse the payload data
|
||||
* appropriately - this will not be considered a BC break. See also
|
||||
* [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
|
||||
*
|
||||
* - Any other unknown type:
|
||||
* An opaque binary string containing the RDATA as transported in the DNS
|
||||
* record. For forwards compatibility, you should not rely on this format
|
||||
* for unknown types. Future versions may add support for new types and
|
||||
* this may then parse the payload data appropriately - this will not be
|
||||
* considered a BC break. See the format definition of known types above
|
||||
* for more details.
|
||||
*
|
||||
* @var string|string[]|array
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @param int $class
|
||||
* @param int $ttl
|
||||
* @param string|string[]|array $data
|
||||
*/
|
||||
public function __construct($name, $type, $class, $ttl, $data)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
$this->ttl = $ttl;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
199
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Normal file
199
vendor/react/dns/src/Protocol/BinaryDumper.php
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\Query;
|
||||
|
||||
final class BinaryDumper
|
||||
{
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return string
|
||||
*/
|
||||
public function toBinary(Message $message)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= $this->headerToBinary($message);
|
||||
$data .= $this->questionToBinary($message->questions);
|
||||
$data .= $this->recordsToBinary($message->answers);
|
||||
$data .= $this->recordsToBinary($message->authority);
|
||||
$data .= $this->recordsToBinary($message->additional);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return string
|
||||
*/
|
||||
private function headerToBinary(Message $message)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
$data .= pack('n', $message->id);
|
||||
|
||||
$flags = 0x00;
|
||||
$flags = ($flags << 1) | ($message->qr ? 1 : 0);
|
||||
$flags = ($flags << 4) | $message->opcode;
|
||||
$flags = ($flags << 1) | ($message->aa ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->tc ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->rd ? 1 : 0);
|
||||
$flags = ($flags << 1) | ($message->ra ? 1 : 0);
|
||||
$flags = ($flags << 3) | 0; // skip unused zero bit
|
||||
$flags = ($flags << 4) | $message->rcode;
|
||||
|
||||
$data .= pack('n', $flags);
|
||||
|
||||
$data .= pack('n', count($message->questions));
|
||||
$data .= pack('n', count($message->answers));
|
||||
$data .= pack('n', count($message->authority));
|
||||
$data .= pack('n', count($message->additional));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Query[] $questions
|
||||
* @return string
|
||||
*/
|
||||
private function questionToBinary(array $questions)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$data .= $this->domainNameToBinary($question->name);
|
||||
$data .= pack('n*', $question->type, $question->class);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Record[] $records
|
||||
* @return string
|
||||
*/
|
||||
private function recordsToBinary(array $records)
|
||||
{
|
||||
$data = '';
|
||||
|
||||
foreach ($records as $record) {
|
||||
/* @var $record Record */
|
||||
switch ($record->type) {
|
||||
case Message::TYPE_A:
|
||||
case Message::TYPE_AAAA:
|
||||
$binary = \inet_pton($record->data);
|
||||
break;
|
||||
case Message::TYPE_CNAME:
|
||||
case Message::TYPE_NS:
|
||||
case Message::TYPE_PTR:
|
||||
$binary = $this->domainNameToBinary($record->data);
|
||||
break;
|
||||
case Message::TYPE_TXT:
|
||||
case Message::TYPE_SPF:
|
||||
$binary = $this->textsToBinary($record->data);
|
||||
break;
|
||||
case Message::TYPE_MX:
|
||||
$binary = \pack(
|
||||
'n',
|
||||
$record->data['priority']
|
||||
);
|
||||
$binary .= $this->domainNameToBinary($record->data['target']);
|
||||
break;
|
||||
case Message::TYPE_SRV:
|
||||
$binary = \pack(
|
||||
'n*',
|
||||
$record->data['priority'],
|
||||
$record->data['weight'],
|
||||
$record->data['port']
|
||||
);
|
||||
$binary .= $this->domainNameToBinary($record->data['target']);
|
||||
break;
|
||||
case Message::TYPE_SOA:
|
||||
$binary = $this->domainNameToBinary($record->data['mname']);
|
||||
$binary .= $this->domainNameToBinary($record->data['rname']);
|
||||
$binary .= \pack(
|
||||
'N*',
|
||||
$record->data['serial'],
|
||||
$record->data['refresh'],
|
||||
$record->data['retry'],
|
||||
$record->data['expire'],
|
||||
$record->data['minimum']
|
||||
);
|
||||
break;
|
||||
case Message::TYPE_CAA:
|
||||
$binary = \pack(
|
||||
'C*',
|
||||
$record->data['flag'],
|
||||
\strlen($record->data['tag'])
|
||||
);
|
||||
$binary .= $record->data['tag'];
|
||||
$binary .= $record->data['value'];
|
||||
break;
|
||||
case Message::TYPE_SSHFP:
|
||||
$binary = \pack(
|
||||
'CCH*',
|
||||
$record->data['algorithm'],
|
||||
$record->data['type'],
|
||||
$record->data['fingerprint']
|
||||
);
|
||||
break;
|
||||
case Message::TYPE_OPT:
|
||||
$binary = '';
|
||||
foreach ($record->data as $opt => $value) {
|
||||
if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
|
||||
$value = \pack('n', round($value * 10));
|
||||
}
|
||||
$binary .= \pack('n*', $opt, \strlen((string) $value)) . $value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// RDATA is already stored as binary value for unknown record types
|
||||
$binary = $record->data;
|
||||
}
|
||||
|
||||
$data .= $this->domainNameToBinary($record->name);
|
||||
$data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
|
||||
$data .= $binary;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $texts
|
||||
* @return string
|
||||
*/
|
||||
private function textsToBinary(array $texts)
|
||||
{
|
||||
$data = '';
|
||||
foreach ($texts as $text) {
|
||||
$data .= \chr(\strlen($text)) . $text;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
* @return string
|
||||
*/
|
||||
private function domainNameToBinary($host)
|
||||
{
|
||||
if ($host === '') {
|
||||
return "\0";
|
||||
}
|
||||
|
||||
// break up domain name at each dot that is not preceeded by a backslash (escaped notation)
|
||||
return $this->textsToBinary(
|
||||
\array_map(
|
||||
'stripcslashes',
|
||||
\preg_split(
|
||||
'/(?<!\\\\)\./',
|
||||
$host . '.'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
356
vendor/react/dns/src/Protocol/Parser.php
vendored
Normal file
356
vendor/react/dns/src/Protocol/Parser.php
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Protocol;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Dns\Query\Query;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* DNS protocol parser
|
||||
*
|
||||
* Obsolete and uncommon types and classes are not implemented.
|
||||
*/
|
||||
final class Parser
|
||||
{
|
||||
/**
|
||||
* Parses the given raw binary message into a Message object
|
||||
*
|
||||
* @param string $data
|
||||
* @throws InvalidArgumentException
|
||||
* @return Message
|
||||
*/
|
||||
public function parseMessage($data)
|
||||
{
|
||||
$message = $this->parse($data, 0);
|
||||
if ($message === null) {
|
||||
throw new InvalidArgumentException('Unable to parse binary message');
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return ?Message
|
||||
*/
|
||||
private function parse($data, $consumed)
|
||||
{
|
||||
if (!isset($data[12 - 1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12)));
|
||||
|
||||
$message = new Message();
|
||||
$message->id = $id;
|
||||
$message->rcode = $fields & 0xf;
|
||||
$message->ra = (($fields >> 7) & 1) === 1;
|
||||
$message->rd = (($fields >> 8) & 1) === 1;
|
||||
$message->tc = (($fields >> 9) & 1) === 1;
|
||||
$message->aa = (($fields >> 10) & 1) === 1;
|
||||
$message->opcode = ($fields >> 11) & 0xf;
|
||||
$message->qr = (($fields >> 15) & 1) === 1;
|
||||
$consumed += 12;
|
||||
|
||||
// parse all questions
|
||||
for ($i = $qdCount; $i > 0; --$i) {
|
||||
list($question, $consumed) = $this->parseQuestion($data, $consumed);
|
||||
if ($question === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->questions[] = $question;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all answer records
|
||||
for ($i = $anCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->answers[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all authority records
|
||||
for ($i = $nsCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->authority[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
// parse all additional records
|
||||
for ($i = $arCount; $i > 0; --$i) {
|
||||
list($record, $consumed) = $this->parseRecord($data, $consumed);
|
||||
if ($record === null) {
|
||||
return null;
|
||||
} else {
|
||||
$message->additional[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return array
|
||||
*/
|
||||
private function parseQuestion($data, $consumed)
|
||||
{
|
||||
list($labels, $consumed) = $this->readLabels($data, $consumed);
|
||||
|
||||
if ($labels === null || !isset($data[$consumed + 4 - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
return array(
|
||||
new Query(
|
||||
implode('.', $labels),
|
||||
$type,
|
||||
$class
|
||||
),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @return array An array with a parsed Record on success or array with null if data is invalid/incomplete
|
||||
*/
|
||||
private function parseRecord($data, $consumed)
|
||||
{
|
||||
list($name, $consumed) = $this->readDomain($data, $consumed);
|
||||
|
||||
if ($name === null || !isset($data[$consumed + 10 - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
list($ttl) = array_values(unpack('N', substr($data, $consumed, 4)));
|
||||
$consumed += 4;
|
||||
|
||||
// TTL is a UINT32 that must not have most significant bit set for BC reasons
|
||||
if ($ttl < 0 || $ttl >= 1 << 31) {
|
||||
$ttl = 0;
|
||||
}
|
||||
|
||||
list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
$consumed += 2;
|
||||
|
||||
if (!isset($data[$consumed + $rdLength - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$rdata = null;
|
||||
$expected = $consumed + $rdLength;
|
||||
|
||||
if (Message::TYPE_A === $type) {
|
||||
if ($rdLength === 4) {
|
||||
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_AAAA === $type) {
|
||||
if ($rdLength === 16) {
|
||||
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
|
||||
list($rdata, $consumed) = $this->readDomain($data, $consumed);
|
||||
} elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) {
|
||||
$rdata = array();
|
||||
while ($consumed < $expected) {
|
||||
$len = ord($data[$consumed]);
|
||||
$rdata[] = (string)substr($data, $consumed + 1, $len);
|
||||
$consumed += $len + 1;
|
||||
}
|
||||
} elseif (Message::TYPE_MX === $type) {
|
||||
if ($rdLength > 2) {
|
||||
list($priority) = array_values(unpack('n', substr($data, $consumed, 2)));
|
||||
list($target, $consumed) = $this->readDomain($data, $consumed + 2);
|
||||
|
||||
$rdata = array(
|
||||
'priority' => $priority,
|
||||
'target' => $target
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SRV === $type) {
|
||||
if ($rdLength > 6) {
|
||||
list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6)));
|
||||
list($target, $consumed) = $this->readDomain($data, $consumed + 6);
|
||||
|
||||
$rdata = array(
|
||||
'priority' => $priority,
|
||||
'weight' => $weight,
|
||||
'port' => $port,
|
||||
'target' => $target
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SSHFP === $type) {
|
||||
if ($rdLength > 2) {
|
||||
list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2)));
|
||||
$fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2));
|
||||
$consumed += $rdLength;
|
||||
|
||||
$rdata = array(
|
||||
'algorithm' => $algorithm,
|
||||
'type' => $hash,
|
||||
'fingerprint' => $fingerprint
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_SOA === $type) {
|
||||
list($mname, $consumed) = $this->readDomain($data, $consumed);
|
||||
list($rname, $consumed) = $this->readDomain($data, $consumed);
|
||||
|
||||
if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) {
|
||||
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20)));
|
||||
$consumed += 20;
|
||||
|
||||
$rdata = array(
|
||||
'mname' => $mname,
|
||||
'rname' => $rname,
|
||||
'serial' => $serial,
|
||||
'refresh' => $refresh,
|
||||
'retry' => $retry,
|
||||
'expire' => $expire,
|
||||
'minimum' => $minimum
|
||||
);
|
||||
}
|
||||
} elseif (Message::TYPE_OPT === $type) {
|
||||
$rdata = array();
|
||||
while (isset($data[$consumed + 4 - 1])) {
|
||||
list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4)));
|
||||
$value = (string) substr($data, $consumed + 4, $length);
|
||||
if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
|
||||
$value = null;
|
||||
} elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
|
||||
list($value) = array_values(unpack('n', $value));
|
||||
$value = round($value * 0.1, 1);
|
||||
} elseif ($code === Message::OPT_TCP_KEEPALIVE) {
|
||||
break;
|
||||
}
|
||||
$rdata[$code] = $value;
|
||||
$consumed += 4 + $length;
|
||||
}
|
||||
} elseif (Message::TYPE_CAA === $type) {
|
||||
if ($rdLength > 3) {
|
||||
list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2)));
|
||||
|
||||
if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) {
|
||||
$tag = substr($data, $consumed + 2, $tagLength);
|
||||
$value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength);
|
||||
$consumed += $rdLength;
|
||||
|
||||
$rdata = array(
|
||||
'flag' => $flag,
|
||||
'tag' => $tag,
|
||||
'value' => $value
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// unknown types simply parse rdata as an opaque binary string
|
||||
$rdata = substr($data, $consumed, $rdLength);
|
||||
$consumed += $rdLength;
|
||||
}
|
||||
|
||||
// ensure parsing record data consumes expact number of bytes indicated in record length
|
||||
if ($consumed !== $expected || $rdata === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
return array(
|
||||
new Record($name, $type, $class, $ttl, $rdata),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
private function readDomain($data, $consumed)
|
||||
{
|
||||
list ($labels, $consumed) = $this->readLabels($data, $consumed);
|
||||
|
||||
if ($labels === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
// use escaped notation for each label part, then join using dots
|
||||
return array(
|
||||
\implode(
|
||||
'.',
|
||||
\array_map(
|
||||
function ($label) {
|
||||
return \addcslashes($label, "\0..\40.\177");
|
||||
},
|
||||
$labels
|
||||
)
|
||||
),
|
||||
$consumed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param int $consumed
|
||||
* @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
|
||||
* @return array
|
||||
*/
|
||||
private function readLabels($data, $consumed, $compressionDepth = 127)
|
||||
{
|
||||
$labels = array();
|
||||
|
||||
while (true) {
|
||||
if (!isset($data[$consumed])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$length = \ord($data[$consumed]);
|
||||
|
||||
// end of labels reached
|
||||
if ($length === 0) {
|
||||
$consumed += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// first two bits set? this is a compressed label (14 bit pointer offset)
|
||||
if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1]) && $compressionDepth) {
|
||||
$offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
|
||||
if ($offset >= $consumed) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$consumed += 2;
|
||||
list($newLabels) = $this->readLabels($data, $offset, $compressionDepth - 1);
|
||||
|
||||
if ($newLabels === null) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$labels = array_merge($labels, $newLabels);
|
||||
break;
|
||||
}
|
||||
|
||||
// length MUST be 0-63 (6 bits only) and data has to be large enough
|
||||
if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
$labels[] = substr($data, $consumed + 1, $length);
|
||||
$consumed += $length + 1;
|
||||
}
|
||||
|
||||
return array($labels, $consumed);
|
||||
}
|
||||
}
|
||||
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Normal file
88
vendor/react/dns/src/Query/CachingExecutor.php
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class CachingExecutor implements ExecutorInterface
|
||||
{
|
||||
/**
|
||||
* Default TTL for negative responses (NXDOMAIN etc.).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const TTL = 60;
|
||||
|
||||
private $executor;
|
||||
private $cache;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, CacheInterface $cache)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$id = $query->name . ':' . $query->type . ':' . $query->class;
|
||||
$cache = $this->cache;
|
||||
$that = $this;
|
||||
$executor = $this->executor;
|
||||
|
||||
$pending = $cache->get($id);
|
||||
return new Promise(function ($resolve, $reject) use ($query, $id, $cache, $executor, &$pending, $that) {
|
||||
$pending->then(
|
||||
function ($message) use ($query, $id, $cache, $executor, &$pending, $that) {
|
||||
// return cached response message on cache hit
|
||||
if ($message !== null) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
// perform DNS lookup if not already cached
|
||||
return $pending = $executor->query($query)->then(
|
||||
function (Message $message) use ($cache, $id, $that) {
|
||||
// DNS response message received => store in cache when not truncated and return
|
||||
if (!$message->tc) {
|
||||
$cache->set($id, $message, $that->ttl($message));
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
);
|
||||
}
|
||||
)->then($resolve, function ($e) use ($reject, &$pending) {
|
||||
$reject($e);
|
||||
$pending = null;
|
||||
});
|
||||
}, function ($_, $reject) use (&$pending, $query) {
|
||||
$reject(new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled'));
|
||||
$pending->cancel();
|
||||
$pending = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @return int
|
||||
* @internal
|
||||
*/
|
||||
public function ttl(Message $message)
|
||||
{
|
||||
// select TTL from answers (should all be the same), use smallest value if available
|
||||
// @link https://tools.ietf.org/html/rfc2181#section-5.2
|
||||
$ttl = null;
|
||||
foreach ($message->answers as $answer) {
|
||||
if ($ttl === null || $answer->ttl < $ttl) {
|
||||
$ttl = $answer->ttl;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ttl === null) {
|
||||
$ttl = self::TTL;
|
||||
}
|
||||
|
||||
return $ttl;
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Normal file
7
vendor/react/dns/src/Query/CancellationException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
final class CancellationException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
91
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Normal file
91
vendor/react/dns/src/Query/CoopExecutor.php
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently
|
||||
*
|
||||
* Wraps an existing `ExecutorInterface` to keep tracking of pending queries
|
||||
* and only starts a new query when the same query is not already pending. Once
|
||||
* the underlying query is fulfilled/rejected, it will forward its value to all
|
||||
* promises awaiting the same query.
|
||||
*
|
||||
* This means it will not limit concurrency for queries that differ, for example
|
||||
* when sending many queries for different host names or types.
|
||||
*
|
||||
* This is useful because all executors are entirely async and as such allow you
|
||||
* to execute any number of queries concurrently. You should probably limit the
|
||||
* number of concurrent queries in your application or you're very likely going
|
||||
* to face rate limitations and bans on the resolver end. For many common
|
||||
* applications, you may want to avoid sending the same query multiple times
|
||||
* when the first one is still pending, so you will likely want to use this in
|
||||
* combination with some other executor like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
final class CoopExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $pending = array();
|
||||
private $counts = array();
|
||||
|
||||
public function __construct(ExecutorInterface $base)
|
||||
{
|
||||
$this->executor = $base;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$key = $this->serializeQueryToIdentity($query);
|
||||
if (isset($this->pending[$key])) {
|
||||
// same query is already pending, so use shared reference to pending query
|
||||
$promise = $this->pending[$key];
|
||||
++$this->counts[$key];
|
||||
} else {
|
||||
// no such query pending, so start new query and keep reference until it's fulfilled or rejected
|
||||
$promise = $this->executor->query($query);
|
||||
$this->pending[$key] = $promise;
|
||||
$this->counts[$key] = 1;
|
||||
|
||||
$pending =& $this->pending;
|
||||
$counts =& $this->counts;
|
||||
$promise->then(function () use ($key, &$pending, &$counts) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
}, function () use ($key, &$pending, &$counts) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
});
|
||||
}
|
||||
|
||||
// Return a child promise awaiting the pending query.
|
||||
// Cancelling this child promise should only cancel the pending query
|
||||
// when no other child promise is awaiting the same query.
|
||||
$pending =& $this->pending;
|
||||
$counts =& $this->counts;
|
||||
return new Promise(function ($resolve, $reject) use ($promise) {
|
||||
$promise->then($resolve, $reject);
|
||||
}, function () use (&$promise, $key, $query, &$pending, &$counts) {
|
||||
if (--$counts[$key] < 1) {
|
||||
unset($pending[$key], $counts[$key]);
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
}
|
||||
throw new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled');
|
||||
});
|
||||
}
|
||||
|
||||
private function serializeQueryToIdentity(Query $query)
|
||||
{
|
||||
return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
|
||||
}
|
||||
}
|
||||
43
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Normal file
43
vendor/react/dns/src/Query/ExecutorInterface.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
interface ExecutorInterface
|
||||
{
|
||||
/**
|
||||
* Executes a query and will return a response message
|
||||
*
|
||||
* It returns a Promise which either fulfills with a response
|
||||
* `React\Dns\Model\Message` on success or rejects with an `Exception` if
|
||||
* the query is not successful. A response message may indicate an error
|
||||
* condition in its `rcode`, but this is considered a valid response message.
|
||||
*
|
||||
* ```php
|
||||
* $executor->query($query)->then(
|
||||
* function (React\Dns\Model\Message $response) {
|
||||
* // response message successfully received
|
||||
* var_dump($response->rcode, $response->answers);
|
||||
* },
|
||||
* function (Exception $error) {
|
||||
* // failed to query due to $error
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* The returned Promise MUST be implemented in such a way that it can be
|
||||
* cancelled when it is still pending. Cancelling a pending promise MUST
|
||||
* reject its value with an Exception. It SHOULD clean up any underlying
|
||||
* resources and references as applicable.
|
||||
*
|
||||
* ```php
|
||||
* $promise = $executor->query($query);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param Query $query
|
||||
* @return \React\Promise\PromiseInterface<\React\Dns\Model\Message>
|
||||
* resolves with response message on success or rejects with an Exception on error
|
||||
*/
|
||||
public function query(Query $query);
|
||||
}
|
||||
49
vendor/react/dns/src/Query/FallbackExecutor.php
vendored
Normal file
49
vendor/react/dns/src/Query/FallbackExecutor.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class FallbackExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $fallback;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$cancelled = false;
|
||||
$fallback = $this->fallback;
|
||||
$promise = $this->executor->query($query);
|
||||
|
||||
return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) {
|
||||
$promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) {
|
||||
// reject if primary resolution rejected due to cancellation
|
||||
if ($cancelled) {
|
||||
$reject($e1);
|
||||
return;
|
||||
}
|
||||
|
||||
// start fallback query if primary query rejected
|
||||
$promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) {
|
||||
$append = $e2->getMessage();
|
||||
if (($pos = strpos($append, ':')) !== false) {
|
||||
$append = substr($append, $pos + 2);
|
||||
}
|
||||
|
||||
// reject with combined error message if both queries fail
|
||||
$reject(new \RuntimeException($e1->getMessage() . '. ' . $append));
|
||||
});
|
||||
});
|
||||
}, function () use (&$promise, &$cancelled) {
|
||||
// cancel pending query (primary or fallback)
|
||||
$cancelled = true;
|
||||
$promise->cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Normal file
89
vendor/react/dns/src/Query/HostsFileExecutor.php
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Model\Record;
|
||||
use React\Promise;
|
||||
|
||||
/**
|
||||
* Resolves hosts from the given HostsFile or falls back to another executor
|
||||
*
|
||||
* If the host is found in the hosts file, it will not be passed to the actual
|
||||
* DNS executor. If the host is not found in the hosts file, it will be passed
|
||||
* to the DNS executor as a fallback.
|
||||
*/
|
||||
final class HostsFileExecutor implements ExecutorInterface
|
||||
{
|
||||
private $hosts;
|
||||
private $fallback;
|
||||
|
||||
public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
|
||||
{
|
||||
$this->hosts = $hosts;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
|
||||
// forward lookup for type A or AAAA
|
||||
$records = array();
|
||||
$expectsColon = $query->type === Message::TYPE_AAAA;
|
||||
foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
|
||||
// ensure this is an IPv4/IPV6 address according to query type
|
||||
if ((strpos($ip, ':') !== false) === $expectsColon) {
|
||||
$records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
|
||||
}
|
||||
}
|
||||
|
||||
if ($records) {
|
||||
return Promise\resolve(
|
||||
Message::createResponseWithAnswersForQuery($query, $records)
|
||||
);
|
||||
}
|
||||
} elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
|
||||
// reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
|
||||
$ip = $this->getIpFromHost($query->name);
|
||||
|
||||
if ($ip !== null) {
|
||||
$records = array();
|
||||
foreach ($this->hosts->getHostsForIp($ip) as $host) {
|
||||
$records[] = new Record($query->name, $query->type, $query->class, 0, $host);
|
||||
}
|
||||
|
||||
if ($records) {
|
||||
return Promise\resolve(
|
||||
Message::createResponseWithAnswersForQuery($query, $records)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fallback->query($query);
|
||||
}
|
||||
|
||||
private function getIpFromHost($host)
|
||||
{
|
||||
if (substr($host, -13) === '.in-addr.arpa') {
|
||||
// IPv4: read as IP and reverse bytes
|
||||
$ip = @inet_pton(substr($host, 0, -13));
|
||||
if ($ip === false || isset($ip[4])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return inet_ntop(strrev($ip));
|
||||
} elseif (substr($host, -9) === '.ip6.arpa') {
|
||||
// IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
|
||||
$ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
|
||||
if ($ip === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $ip;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
vendor/react/dns/src/Query/Query.php
vendored
Normal file
69
vendor/react/dns/src/Query/Query.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
|
||||
/**
|
||||
* This class represents a single question in a query/response message
|
||||
*
|
||||
* It uses a structure similar to `\React\Dns\Message\Record`, but does not
|
||||
* contain fields for resulting TTL and resulting record data (IPs etc.).
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
* @see \React\Dns\Message\Record
|
||||
*/
|
||||
final class Query
|
||||
{
|
||||
/**
|
||||
* @var string query name, i.e. hostname to look up
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int query type (aka QTYPE), see Message::TYPE_* constants
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var int query class (aka QCLASS), see Message::CLASS_IN constant
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $name query name, i.e. hostname to look up
|
||||
* @param int $type query type, see Message::TYPE_* constants
|
||||
* @param int $class query class, see Message::CLASS_IN constant
|
||||
*/
|
||||
public function __construct($name, $type, $class)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the hostname and query type/class for this query
|
||||
*
|
||||
* The output format is supposed to be human readable and is subject to change.
|
||||
* The format is inspired by RFC 3597 when handling unkown types/classes.
|
||||
*
|
||||
* @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)"
|
||||
* @link https://tools.ietf.org/html/rfc3597
|
||||
*/
|
||||
public function describe()
|
||||
{
|
||||
$class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : '';
|
||||
|
||||
$type = 'TYPE' . $this->type;
|
||||
$ref = new \ReflectionClass('React\Dns\Model\Message');
|
||||
foreach ($ref->getConstants() as $name => $value) {
|
||||
if ($value === $this->type && \strpos($name, 'TYPE_') === 0) {
|
||||
$type = \substr($name, 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->name . ' (' . $class . $type . ')';
|
||||
}
|
||||
}
|
||||
85
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Normal file
85
vendor/react/dns/src/Query/RetryExecutor.php
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
final class RetryExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $retries;
|
||||
|
||||
public function __construct(ExecutorInterface $executor, $retries = 2)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->retries = $retries;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
return $this->tryQuery($query, $this->retries);
|
||||
}
|
||||
|
||||
public function tryQuery(Query $query, $retries)
|
||||
{
|
||||
$deferred = new Deferred(function () use (&$promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
});
|
||||
|
||||
$success = function ($value) use ($deferred, &$errorback) {
|
||||
$errorback = null;
|
||||
$deferred->resolve($value);
|
||||
};
|
||||
|
||||
$executor = $this->executor;
|
||||
$errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) {
|
||||
if (!$e instanceof TimeoutException) {
|
||||
$errorback = null;
|
||||
$deferred->reject($e);
|
||||
} elseif ($retries <= 0) {
|
||||
$errorback = null;
|
||||
$deferred->reject($e = new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: too many retries',
|
||||
0,
|
||||
$e
|
||||
));
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as $ti => $one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as $ai => $arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
} else {
|
||||
--$retries;
|
||||
$promise = $executor->query($query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
$promise = $this->executor->query($query)->then(
|
||||
$success,
|
||||
$errorback
|
||||
);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
85
vendor/react/dns/src/Query/SelectiveTransportExecutor.php
vendored
Normal file
85
vendor/react/dns/src/Query/SelectiveTransportExecutor.php
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Promise\Promise;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a UDP or TCP/IP stream transport.
|
||||
*
|
||||
* This class will automatically choose the correct transport protocol to send
|
||||
* a DNS query to your DNS server. It will always try to send it over the more
|
||||
* efficient UDP transport first. If this query yields a size related issue
|
||||
* (truncated messages), it will retry over a streaming TCP/IP transport.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* Note that this executor only implements the logic to select the correct
|
||||
* transport for the given DNS query. Implementing the correct transport logic,
|
||||
* implementing timeouts and any retry logic is left up to the given executors,
|
||||
* see also [`UdpTransportExecutor`](#udptransportexecutor) and
|
||||
* [`TcpTransportExecutor`](#tcptransportexecutor) for more details.
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* any number of queries concurrently. You should probably limit the number of
|
||||
* concurrent queries in your application or you're very likely going to face
|
||||
* rate limitations and bans on the resolver end. For many common applications,
|
||||
* you may want to avoid sending the same query multiple times when the first
|
||||
* one is still pending, so you will likely want to use this in combination with
|
||||
* a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new SelectiveTransportExecutor(
|
||||
* $datagramExecutor,
|
||||
* $streamExecutor
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
class SelectiveTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $datagramExecutor;
|
||||
private $streamExecutor;
|
||||
|
||||
public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor)
|
||||
{
|
||||
$this->datagramExecutor = $datagramExecutor;
|
||||
$this->streamExecutor = $streamExecutor;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$stream = $this->streamExecutor;
|
||||
$pending = $this->datagramExecutor->query($query);
|
||||
|
||||
return new Promise(function ($resolve, $reject) use (&$pending, $stream, $query) {
|
||||
$pending->then(
|
||||
$resolve,
|
||||
function ($e) use (&$pending, $stream, $query, $resolve, $reject) {
|
||||
if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) {
|
||||
$pending = $stream->query($query)->then($resolve, $reject);
|
||||
} else {
|
||||
$reject($e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, function () use (&$pending) {
|
||||
$pending->cancel();
|
||||
$pending = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
382
vendor/react/dns/src/Query/TcpTransportExecutor.php
vendored
Normal file
382
vendor/react/dns/src/Query/TcpTransportExecutor.php
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a TCP/IP stream transport.
|
||||
*
|
||||
* This is one of the main classes that send a DNS query to your DNS server.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `reactphp.org`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TcpTransportExecutor('8.8.8.8:53');
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* See also [example #92](examples).
|
||||
*
|
||||
* Note that this executor does not implement a timeout, so you will very likely
|
||||
* want to use this in combination with a `TimeoutExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TimeoutExecutor(
|
||||
* new TcpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
|
||||
* transport, so you do not necessarily have to implement any retry logic.
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* queries concurrently. The first query will establish a TCP/IP socket
|
||||
* connection to the DNS server which will be kept open for a short period.
|
||||
* Additional queries will automatically reuse this existing socket connection
|
||||
* to the DNS server, will pipeline multiple requests over this single
|
||||
* connection and will keep an idle connection open for a short period. The
|
||||
* initial TCP/IP connection overhead may incur a slight delay if you only send
|
||||
* occasional queries – when sending a larger number of concurrent queries over
|
||||
* an existing connection, it becomes increasingly more efficient and avoids
|
||||
* creating many concurrent sockets like the UDP-based executor. You may still
|
||||
* want to limit the number of (concurrent) queries in your application or you
|
||||
* may be facing rate limitations and bans on the resolver end. For many common
|
||||
* applications, you may want to avoid sending the same query multiple times
|
||||
* when the first one is still pending, so you will likely want to use this in
|
||||
* combination with a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new TcpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > Internally, this class uses PHP's TCP/IP sockets and does not take advantage
|
||||
* of [react/socket](https://github.com/reactphp/socket) purely for
|
||||
* organizational reasons to avoid a cyclic dependency between the two
|
||||
* packages. Higher-level components should take advantage of the Socket
|
||||
* component instead of reimplementing this socket logic from scratch.
|
||||
*/
|
||||
class TcpTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $nameserver;
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
|
||||
/**
|
||||
* @var ?resource
|
||||
*/
|
||||
private $socket;
|
||||
|
||||
/**
|
||||
* @var Deferred[]
|
||||
*/
|
||||
private $pending = array();
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $names = array();
|
||||
|
||||
/**
|
||||
* Maximum idle time when socket is current unused (i.e. no pending queries outstanding)
|
||||
*
|
||||
* If a new query is to be sent during the idle period, we can reuse the
|
||||
* existing socket without having to wait for a new socket connection.
|
||||
* This uses a rather small, hard-coded value to not keep any unneeded
|
||||
* sockets open and to not keep the loop busy longer than needed.
|
||||
*
|
||||
* A future implementation may take advantage of `edns-tcp-keepalive` to keep
|
||||
* the socket open for longer periods. This will likely require explicit
|
||||
* configuration because this may consume additional resources and also keep
|
||||
* the loop busy for longer than expected in some applications.
|
||||
*
|
||||
* @var float
|
||||
* @link https://tools.ietf.org/html/rfc7766#section-6.2.1
|
||||
* @link https://tools.ietf.org/html/rfc7828
|
||||
*/
|
||||
private $idlePeriod = 0.001;
|
||||
|
||||
/**
|
||||
* @var ?\React\EventLoop\TimerInterface
|
||||
*/
|
||||
private $idleTimer;
|
||||
|
||||
private $writeBuffer = '';
|
||||
private $writePending = false;
|
||||
|
||||
private $readBuffer = '';
|
||||
private $readPending = false;
|
||||
|
||||
/** @var string */
|
||||
private $readChunk = 0xffff;
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct($nameserver, $loop = null)
|
||||
{
|
||||
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
|
||||
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
|
||||
$nameserver = '[' . $nameserver . ']';
|
||||
}
|
||||
|
||||
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver);
|
||||
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
|
||||
throw new \InvalidArgumentException('Invalid nameserver address given');
|
||||
}
|
||||
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->nameserver = 'tcp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->parser = new Parser();
|
||||
$this->dumper = new BinaryDumper();
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
// keep shuffing message ID to avoid using the same message ID for two pending queries at the same time
|
||||
while (isset($this->pending[$request->id])) {
|
||||
$request->id = \mt_rand(0, 0xffff); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
$length = \strlen($queryData);
|
||||
if ($length > 0xffff) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Query too large for TCP transport'
|
||||
));
|
||||
}
|
||||
|
||||
$queryData = \pack('n', $length) . $queryData;
|
||||
|
||||
if ($this->socket === null) {
|
||||
// create async TCP/IP connection (may take a while)
|
||||
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT);
|
||||
if ($socket === false) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// set socket to non-blocking and wait for it to become writable (connection success/rejected)
|
||||
\stream_set_blocking($socket, false);
|
||||
if (\function_exists('stream_set_chunk_size')) {
|
||||
\stream_set_chunk_size($socket, $this->readChunk); // @codeCoverageIgnore
|
||||
}
|
||||
$this->socket = $socket;
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
// wait for socket to become writable to actually write out data
|
||||
$this->writeBuffer .= $queryData;
|
||||
if (!$this->writePending) {
|
||||
$this->writePending = true;
|
||||
$this->loop->addWriteStream($this->socket, array($this, 'handleWritable'));
|
||||
}
|
||||
|
||||
$names =& $this->names;
|
||||
$that = $this;
|
||||
$deferred = new Deferred(function () use ($that, &$names, $request) {
|
||||
// remove from list of pending names, but remember pending query
|
||||
$name = $names[$request->id];
|
||||
unset($names[$request->id]);
|
||||
$that->checkIdle();
|
||||
|
||||
throw new CancellationException('DNS query for ' . $name . ' has been cancelled');
|
||||
});
|
||||
|
||||
$this->pending[$request->id] = $deferred;
|
||||
$this->names[$request->id] = $query->describe();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function handleWritable()
|
||||
{
|
||||
if ($this->readPending === false) {
|
||||
$name = @\stream_socket_get_name($this->socket, true);
|
||||
if ($name === false) {
|
||||
// Connection failed? Check socket error if available for underlying errno/errstr.
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\function_exists('socket_import_stream')) {
|
||||
$socket = \socket_import_stream($this->socket);
|
||||
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
|
||||
$errstr = \socket_strerror($errno);
|
||||
} else {
|
||||
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
|
||||
$errstr = 'Connection refused';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readPending = true;
|
||||
$this->loop->addReadStream($this->socket, array($this, 'handleRead'));
|
||||
}
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// fwrite(): Send of 327712 bytes failed with errno=32 Broken pipe
|
||||
\preg_match('/errno=(\d+) (.+)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
$written = \fwrite($this->socket, $this->writeBuffer);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($written === false || $written === 0) {
|
||||
$this->closeError(
|
||||
'Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->writeBuffer[$written])) {
|
||||
$this->writeBuffer = \substr($this->writeBuffer, $written);
|
||||
} else {
|
||||
$this->loop->removeWriteStream($this->socket);
|
||||
$this->writePending = false;
|
||||
$this->writeBuffer = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function handleRead()
|
||||
{
|
||||
// read one chunk of data from the DNS server
|
||||
// any error is fatal, this is a stream of TCP/IP data
|
||||
$chunk = @\fread($this->socket, $this->readChunk);
|
||||
if ($chunk === false || $chunk === '') {
|
||||
$this->closeError('Connection to DNS server ' . $this->nameserver . ' lost');
|
||||
return;
|
||||
}
|
||||
|
||||
// reassemble complete message by concatenating all chunks.
|
||||
$this->readBuffer .= $chunk;
|
||||
|
||||
// response message header contains at least 12 bytes
|
||||
while (isset($this->readBuffer[11])) {
|
||||
// read response message length from first 2 bytes and ensure we have length + data in buffer
|
||||
list(, $length) = \unpack('n', $this->readBuffer);
|
||||
if (!isset($this->readBuffer[$length + 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = \substr($this->readBuffer, 2, $length);
|
||||
$this->readBuffer = (string)substr($this->readBuffer, $length + 2);
|
||||
|
||||
try {
|
||||
$response = $this->parser->parseMessage($data);
|
||||
} catch (\Exception $e) {
|
||||
// reject all pending queries if we received an invalid message from remote server
|
||||
$this->closeError('Invalid message received from DNS server ' . $this->nameserver);
|
||||
return;
|
||||
}
|
||||
|
||||
// reject all pending queries if we received an unexpected response ID or truncated response
|
||||
if (!isset($this->pending[$response->id]) || $response->tc) {
|
||||
$this->closeError('Invalid response message received from DNS server ' . $this->nameserver);
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred = $this->pending[$response->id];
|
||||
unset($this->pending[$response->id], $this->names[$response->id]);
|
||||
|
||||
$deferred->resolve($response);
|
||||
|
||||
$this->checkIdle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param string $reason
|
||||
* @param int $code
|
||||
*/
|
||||
public function closeError($reason, $code = 0)
|
||||
{
|
||||
$this->readBuffer = '';
|
||||
if ($this->readPending) {
|
||||
$this->loop->removeReadStream($this->socket);
|
||||
$this->readPending = false;
|
||||
}
|
||||
|
||||
$this->writeBuffer = '';
|
||||
if ($this->writePending) {
|
||||
$this->loop->removeWriteStream($this->socket);
|
||||
$this->writePending = false;
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
@\fclose($this->socket);
|
||||
$this->socket = null;
|
||||
|
||||
foreach ($this->names as $id => $name) {
|
||||
$this->pending[$id]->reject(new \RuntimeException(
|
||||
'DNS query for ' . $name . ' failed: ' . $reason,
|
||||
$code
|
||||
));
|
||||
}
|
||||
$this->pending = $this->names = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function checkIdle()
|
||||
{
|
||||
if ($this->idleTimer === null && !$this->names) {
|
||||
$that = $this;
|
||||
$this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () use ($that) {
|
||||
$that->closeError('Idle timeout');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Normal file
7
vendor/react/dns/src/Query/TimeoutException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
final class TimeoutException extends \Exception
|
||||
{
|
||||
}
|
||||
78
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Normal file
78
vendor/react/dns/src/Query/TimeoutExecutor.php
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
final class TimeoutExecutor implements ExecutorInterface
|
||||
{
|
||||
private $executor;
|
||||
private $loop;
|
||||
private $timeout;
|
||||
|
||||
/**
|
||||
* @param ExecutorInterface $executor
|
||||
* @param float $timeout
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct(ExecutorInterface $executor, $timeout, $loop = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->executor = $executor;
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$promise = $this->executor->query($query);
|
||||
|
||||
$loop = $this->loop;
|
||||
$time = $this->timeout;
|
||||
return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $query) {
|
||||
$timer = null;
|
||||
$promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
|
||||
if ($timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
$timer = false;
|
||||
$resolve($v);
|
||||
}, function ($v) use (&$timer, $loop, $reject) {
|
||||
if ($timer) {
|
||||
$loop->cancelTimer($timer);
|
||||
}
|
||||
$timer = false;
|
||||
$reject($v);
|
||||
});
|
||||
|
||||
// promise already resolved => no need to start timer
|
||||
if ($timer === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start timeout timer which will cancel the pending promise
|
||||
$timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $query) {
|
||||
$reject(new TimeoutException(
|
||||
'DNS query for ' . $query->describe() . ' timed out'
|
||||
));
|
||||
|
||||
// Cancel pending query to clean up any underlying resources and references.
|
||||
// Avoid garbage references in call stack by passing pending promise by reference.
|
||||
assert(\method_exists($promise, 'cancel'));
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
});
|
||||
}, function () use (&$promise) {
|
||||
// Cancelling this promise will cancel the pending query, thus triggering the rejection logic above.
|
||||
// Avoid garbage references in call stack by passing pending promise by reference.
|
||||
assert(\method_exists($promise, 'cancel'));
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
221
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Normal file
221
vendor/react/dns/src/Query/UdpTransportExecutor.php
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Query;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Protocol\BinaryDumper;
|
||||
use React\Dns\Protocol\Parser;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
|
||||
/**
|
||||
* Send DNS queries over a UDP transport.
|
||||
*
|
||||
* This is the main class that sends a DNS query to your DNS server and is used
|
||||
* internally by the `Resolver` for the actual message transport.
|
||||
*
|
||||
* For more advanced usages one can utilize this class directly.
|
||||
* The following example looks up the `IPv6` address for `igor.io`.
|
||||
*
|
||||
* ```php
|
||||
* $executor = new UdpTransportExecutor('8.8.8.8:53');
|
||||
*
|
||||
* $executor->query(
|
||||
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
|
||||
* )->then(function (Message $message) {
|
||||
* foreach ($message->answers as $answer) {
|
||||
* echo 'IPv6: ' . $answer->data . PHP_EOL;
|
||||
* }
|
||||
* }, 'printf');
|
||||
* ```
|
||||
*
|
||||
* See also the [fourth example](examples).
|
||||
*
|
||||
* Note that this executor does not implement a timeout, so you will very likely
|
||||
* want to use this in combination with a `TimeoutExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Also note that this executor uses an unreliable UDP transport and that it
|
||||
* does not implement any retry logic, so you will likely want to use this in
|
||||
* combination with a `RetryExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Note that this executor is entirely async and as such allows you to execute
|
||||
* any number of queries concurrently. You should probably limit the number of
|
||||
* concurrent queries in your application or you're very likely going to face
|
||||
* rate limitations and bans on the resolver end. For many common applications,
|
||||
* you may want to avoid sending the same query multiple times when the first
|
||||
* one is still pending, so you will likely want to use this in combination with
|
||||
* a `CoopExecutor` like this:
|
||||
*
|
||||
* ```php
|
||||
* $executor = new CoopExecutor(
|
||||
* new RetryExecutor(
|
||||
* new TimeoutExecutor(
|
||||
* new UdpTransportExecutor($nameserver),
|
||||
* 3.0
|
||||
* )
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > Internally, this class uses PHP's UDP sockets and does not take advantage
|
||||
* of [react/datagram](https://github.com/reactphp/datagram) purely for
|
||||
* organizational reasons to avoid a cyclic dependency between the two
|
||||
* packages. Higher-level components should take advantage of the Datagram
|
||||
* component instead of reimplementing this socket logic from scratch.
|
||||
*/
|
||||
final class UdpTransportExecutor implements ExecutorInterface
|
||||
{
|
||||
private $nameserver;
|
||||
private $loop;
|
||||
private $parser;
|
||||
private $dumper;
|
||||
|
||||
/**
|
||||
* maximum UDP packet size to send and receive
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxPacketSize = 512;
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct($nameserver, $loop = null)
|
||||
{
|
||||
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
|
||||
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
|
||||
$nameserver = '[' . $nameserver . ']';
|
||||
}
|
||||
|
||||
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver);
|
||||
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
|
||||
throw new \InvalidArgumentException('Invalid nameserver address given');
|
||||
}
|
||||
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->nameserver = 'udp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->parser = new Parser();
|
||||
$this->dumper = new BinaryDumper();
|
||||
}
|
||||
|
||||
public function query(Query $query)
|
||||
{
|
||||
$request = Message::createRequestForQuery($query);
|
||||
|
||||
$queryData = $this->dumper->toBinary($request);
|
||||
if (isset($queryData[$this->maxPacketSize])) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Query too large for UDP transport',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
}
|
||||
|
||||
// UDP connections are instant, so try connection without a loop or timeout
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0);
|
||||
if ($socket === false) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
// set socket to non-blocking and immediately try to send (fill write buffer)
|
||||
\stream_set_blocking($socket, false);
|
||||
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Write may potentially fail, but most common errors are already caught by connection check above.
|
||||
// Among others, macOS is known to report here when trying to send to broadcast address.
|
||||
// This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
|
||||
// fwrite(): send of 8192 bytes failed with errno=111 Connection refused
|
||||
\preg_match('/errno=(\d+) (.+)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
$written = \fwrite($socket, $queryData);
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($written !== \strlen($queryData)) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
|
||||
$errno
|
||||
));
|
||||
}
|
||||
|
||||
$loop = $this->loop;
|
||||
$deferred = new Deferred(function () use ($loop, $socket, $query) {
|
||||
// cancellation should remove socket from loop and close socket
|
||||
$loop->removeReadStream($socket);
|
||||
\fclose($socket);
|
||||
|
||||
throw new CancellationException('DNS query for ' . $query->describe() . ' has been cancelled');
|
||||
});
|
||||
|
||||
$max = $this->maxPacketSize;
|
||||
$parser = $this->parser;
|
||||
$nameserver = $this->nameserver;
|
||||
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max, $nameserver) {
|
||||
// try to read a single data packet from the DNS server
|
||||
// ignoring any errors, this is uses UDP packets and not a stream of data
|
||||
$data = @\fread($socket, $max);
|
||||
if ($data === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $parser->parseMessage($data);
|
||||
} catch (\Exception $e) {
|
||||
// ignore and await next if we received an invalid message from remote server
|
||||
// this may as well be a fake response from an attacker (possible DOS)
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore and await next if we received an unexpected response ID
|
||||
// this may as well be a fake response from an attacker (possible cache poisoning)
|
||||
if ($response->id !== $request->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we only react to the first valid message, so remove socket from loop and close
|
||||
$loop->removeReadStream($socket);
|
||||
\fclose($socket);
|
||||
|
||||
if ($response->tc) {
|
||||
$deferred->reject(new \RuntimeException(
|
||||
'DNS query for ' . $query->describe() . ' failed: The DNS server ' . $nameserver . ' returned a truncated result for a UDP query',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Normal file
7
vendor/react/dns/src/RecordNotFoundException.php
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns;
|
||||
|
||||
final class RecordNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
226
vendor/react/dns/src/Resolver/Factory.php
vendored
Normal file
226
vendor/react/dns/src/Resolver/Factory.php
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Cache\ArrayCache;
|
||||
use React\Cache\CacheInterface;
|
||||
use React\Dns\Config\Config;
|
||||
use React\Dns\Config\HostsFile;
|
||||
use React\Dns\Query\CachingExecutor;
|
||||
use React\Dns\Query\CoopExecutor;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\FallbackExecutor;
|
||||
use React\Dns\Query\HostsFileExecutor;
|
||||
use React\Dns\Query\RetryExecutor;
|
||||
use React\Dns\Query\SelectiveTransportExecutor;
|
||||
use React\Dns\Query\TcpTransportExecutor;
|
||||
use React\Dns\Query\TimeoutExecutor;
|
||||
use React\Dns\Query\UdpTransportExecutor;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
final class Factory
|
||||
{
|
||||
/**
|
||||
* Creates a DNS resolver instance for the given DNS config
|
||||
*
|
||||
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
|
||||
* single nameserver address. If the given config contains more than one DNS
|
||||
* nameserver, all DNS nameservers will be used in order. The primary DNS
|
||||
* server will always be used first before falling back to the secondary or
|
||||
* tertiary DNS server.
|
||||
*
|
||||
* @param Config|string $config DNS Config object (recommended) or single nameserver address
|
||||
* @param ?LoopInterface $loop
|
||||
* @return \React\Dns\Resolver\ResolverInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
public function create($config, $loop = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
|
||||
|
||||
return new Resolver($executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cached DNS resolver instance for the given DNS config and cache
|
||||
*
|
||||
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
|
||||
* single nameserver address. If the given config contains more than one DNS
|
||||
* nameserver, all DNS nameservers will be used in order. The primary DNS
|
||||
* server will always be used first before falling back to the secondary or
|
||||
* tertiary DNS server.
|
||||
*
|
||||
* @param Config|string $config DNS Config object (recommended) or single nameserver address
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?CacheInterface $cache
|
||||
* @return \React\Dns\Resolver\ResolverInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
public function createCached($config, $loop = null, $cache = null)
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
if ($cache !== null && !$cache instanceof CacheInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($cache) expected null|React\Cache\CacheInterface');
|
||||
}
|
||||
|
||||
// default to keeping maximum of 256 responses in cache unless explicitly given
|
||||
if (!($cache instanceof CacheInterface)) {
|
||||
$cache = new ArrayCache(256);
|
||||
}
|
||||
|
||||
$executor = $this->createExecutor($config, $loop ?: Loop::get());
|
||||
$executor = new CachingExecutor($executor, $cache);
|
||||
$executor = $this->decorateHostsFileExecutor($executor);
|
||||
|
||||
return new Resolver($executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the hosts file and decorates the given executor on success
|
||||
*
|
||||
* @param ExecutorInterface $executor
|
||||
* @return ExecutorInterface
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function decorateHostsFileExecutor(ExecutorInterface $executor)
|
||||
{
|
||||
try {
|
||||
$executor = new HostsFileExecutor(
|
||||
HostsFile::loadFromPathBlocking(),
|
||||
$executor
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
// ignore this file if it can not be loaded
|
||||
}
|
||||
|
||||
// Windows does not store localhost in hosts file by default but handles this internally
|
||||
// To compensate for this, we explicitly use hard-coded defaults for localhost
|
||||
if (DIRECTORY_SEPARATOR === '\\') {
|
||||
$executor = new HostsFileExecutor(
|
||||
new HostsFile("127.0.0.1 localhost\n::1 localhost"),
|
||||
$executor
|
||||
);
|
||||
}
|
||||
|
||||
return $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config|string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return CoopExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
|
||||
*/
|
||||
private function createExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
if ($nameserver instanceof Config) {
|
||||
if (!$nameserver->nameservers) {
|
||||
throw new \UnderflowException('Empty config with no DNS servers');
|
||||
}
|
||||
|
||||
// Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
|
||||
// Note to future self: Recursion isn't too hard, but how deep do we really want to go?
|
||||
$primary = reset($nameserver->nameservers);
|
||||
$secondary = next($nameserver->nameservers);
|
||||
$tertiary = next($nameserver->nameservers);
|
||||
|
||||
if ($tertiary !== false) {
|
||||
// 3 DNS servers given => nest first with fallback for second and third
|
||||
return new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($primary, $loop),
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($secondary, $loop),
|
||||
$this->createSingleExecutor($tertiary, $loop)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} elseif ($secondary !== false) {
|
||||
// 2 DNS servers given => fallback from first to second
|
||||
return new CoopExecutor(
|
||||
new RetryExecutor(
|
||||
new FallbackExecutor(
|
||||
$this->createSingleExecutor($primary, $loop),
|
||||
$this->createSingleExecutor($secondary, $loop)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// 1 DNS server given => use single executor
|
||||
$nameserver = $primary;
|
||||
}
|
||||
}
|
||||
|
||||
return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return ExecutorInterface
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createSingleExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
$parts = \parse_url($nameserver);
|
||||
|
||||
if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
|
||||
$executor = $this->createTcpExecutor($nameserver, $loop);
|
||||
} elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
|
||||
$executor = $this->createUdpExecutor($nameserver, $loop);
|
||||
} else {
|
||||
$executor = new SelectiveTransportExecutor(
|
||||
$this->createUdpExecutor($nameserver, $loop),
|
||||
$this->createTcpExecutor($nameserver, $loop)
|
||||
);
|
||||
}
|
||||
|
||||
return $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return TimeoutExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createTcpExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
return new TimeoutExecutor(
|
||||
new TcpTransportExecutor($nameserver, $loop),
|
||||
5.0,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nameserver
|
||||
* @param LoopInterface $loop
|
||||
* @return TimeoutExecutor
|
||||
* @throws \InvalidArgumentException for invalid DNS server address
|
||||
*/
|
||||
private function createUdpExecutor($nameserver, LoopInterface $loop)
|
||||
{
|
||||
return new TimeoutExecutor(
|
||||
new UdpTransportExecutor(
|
||||
$nameserver,
|
||||
$loop
|
||||
),
|
||||
5.0,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
}
|
||||
147
vendor/react/dns/src/Resolver/Resolver.php
vendored
Normal file
147
vendor/react/dns/src/Resolver/Resolver.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Query\ExecutorInterface;
|
||||
use React\Dns\Query\Query;
|
||||
use React\Dns\RecordNotFoundException;
|
||||
|
||||
/**
|
||||
* @see ResolverInterface for the base interface
|
||||
*/
|
||||
final class Resolver implements ResolverInterface
|
||||
{
|
||||
private $executor;
|
||||
|
||||
public function __construct(ExecutorInterface $executor)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
}
|
||||
|
||||
public function resolve($domain)
|
||||
{
|
||||
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
|
||||
return $ips[array_rand($ips)];
|
||||
});
|
||||
}
|
||||
|
||||
public function resolveAll($domain, $type)
|
||||
{
|
||||
$query = new Query($domain, $type, Message::CLASS_IN);
|
||||
$that = $this;
|
||||
|
||||
return $this->executor->query(
|
||||
$query
|
||||
)->then(function (Message $response) use ($query, $that) {
|
||||
return $that->extractValues($query, $response);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] extract all resource record values from response for this query
|
||||
*
|
||||
* @param Query $query
|
||||
* @param Message $response
|
||||
* @return array
|
||||
* @throws RecordNotFoundException when response indicates an error or contains no data
|
||||
* @internal
|
||||
*/
|
||||
public function extractValues(Query $query, Message $response)
|
||||
{
|
||||
// reject if response code indicates this is an error response message
|
||||
$code = $response->rcode;
|
||||
if ($code !== Message::RCODE_OK) {
|
||||
switch ($code) {
|
||||
case Message::RCODE_FORMAT_ERROR:
|
||||
$message = 'Format Error';
|
||||
break;
|
||||
case Message::RCODE_SERVER_FAILURE:
|
||||
$message = 'Server Failure';
|
||||
break;
|
||||
case Message::RCODE_NAME_ERROR:
|
||||
$message = 'Non-Existent Domain / NXDOMAIN';
|
||||
break;
|
||||
case Message::RCODE_NOT_IMPLEMENTED:
|
||||
$message = 'Not Implemented';
|
||||
break;
|
||||
case Message::RCODE_REFUSED:
|
||||
$message = 'Refused';
|
||||
break;
|
||||
default:
|
||||
$message = 'Unknown error response code ' . $code;
|
||||
}
|
||||
throw new RecordNotFoundException(
|
||||
'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')',
|
||||
$code
|
||||
);
|
||||
}
|
||||
|
||||
$answers = $response->answers;
|
||||
$addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
|
||||
|
||||
// reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
|
||||
if (0 === count($addresses)) {
|
||||
throw new RecordNotFoundException(
|
||||
'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)'
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \React\Dns\Model\Record[] $answers
|
||||
* @param string $name
|
||||
* @param int $type
|
||||
* @return array
|
||||
*/
|
||||
private function valuesByNameAndType(array $answers, $name, $type)
|
||||
{
|
||||
// return all record values for this name and type (if any)
|
||||
$named = $this->filterByName($answers, $name);
|
||||
$records = $this->filterByType($named, $type);
|
||||
if ($records) {
|
||||
return $this->mapRecordData($records);
|
||||
}
|
||||
|
||||
// no matching records found? check if there are any matching CNAMEs instead
|
||||
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
|
||||
if ($cnameRecords) {
|
||||
$cnames = $this->mapRecordData($cnameRecords);
|
||||
foreach ($cnames as $cname) {
|
||||
$records = array_merge(
|
||||
$records,
|
||||
$this->valuesByNameAndType($answers, $cname, $type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
private function filterByName(array $answers, $name)
|
||||
{
|
||||
return $this->filterByField($answers, 'name', $name);
|
||||
}
|
||||
|
||||
private function filterByType(array $answers, $type)
|
||||
{
|
||||
return $this->filterByField($answers, 'type', $type);
|
||||
}
|
||||
|
||||
private function filterByField(array $answers, $field, $value)
|
||||
{
|
||||
$value = strtolower($value);
|
||||
return array_filter($answers, function ($answer) use ($field, $value) {
|
||||
return $value === strtolower($answer->$field);
|
||||
});
|
||||
}
|
||||
|
||||
private function mapRecordData(array $records)
|
||||
{
|
||||
return array_map(function ($record) {
|
||||
return $record->data;
|
||||
}, $records);
|
||||
}
|
||||
}
|
||||
94
vendor/react/dns/src/Resolver/ResolverInterface.php
vendored
Normal file
94
vendor/react/dns/src/Resolver/ResolverInterface.php
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace React\Dns\Resolver;
|
||||
|
||||
interface ResolverInterface
|
||||
{
|
||||
/**
|
||||
* Resolves the given $domain name to a single IPv4 address (type `A` query).
|
||||
*
|
||||
* ```php
|
||||
* $resolver->resolve('reactphp.org')->then(function ($ip) {
|
||||
* echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is one of the main methods in this package. It sends a DNS query
|
||||
* for the given $domain name to your DNS server and returns a single IP
|
||||
* address on success.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that contains more than
|
||||
* one IP address for this query, it will randomly pick one of the IP
|
||||
* addresses from the response. If you want the full list of IP addresses
|
||||
* or want to send a different type of query, you should use the
|
||||
* [`resolveAll()`](#resolveall) method instead.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that indicates an error
|
||||
* code, this method will reject with a `RecordNotFoundException`. Its
|
||||
* message and code can be used to check for the response code.
|
||||
*
|
||||
* If the DNS communication fails and the server does not respond with a
|
||||
* valid response message, this message will reject with an `Exception`.
|
||||
*
|
||||
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
*
|
||||
* ```php
|
||||
* $promise = $resolver->resolve('reactphp.org');
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $domain
|
||||
* @return \React\Promise\PromiseInterface<string>
|
||||
* resolves with a single IP address on success or rejects with an Exception on error.
|
||||
*/
|
||||
public function resolve($domain);
|
||||
|
||||
/**
|
||||
* Resolves all record values for the given $domain name and query $type.
|
||||
*
|
||||
* ```php
|
||||
* $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
|
||||
* echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
|
||||
* echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This is one of the main methods in this package. It sends a DNS query
|
||||
* for the given $domain name to your DNS server and returns a list with all
|
||||
* record values on success.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that contains one or more
|
||||
* records for this query, it will return a list with all record values
|
||||
* from the response. You can use the `Message::TYPE_*` constants to control
|
||||
* which type of query will be sent. Note that this method always returns a
|
||||
* list of record values, but each record value type depends on the query
|
||||
* type. For example, it returns the IPv4 addresses for type `A` queries,
|
||||
* the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
|
||||
* `CNAME` and `PTR` queries and structured data for other queries. See also
|
||||
* the `Record` documentation for more details.
|
||||
*
|
||||
* If the DNS server sends a DNS response message that indicates an error
|
||||
* code, this method will reject with a `RecordNotFoundException`. Its
|
||||
* message and code can be used to check for the response code.
|
||||
*
|
||||
* If the DNS communication fails and the server does not respond with a
|
||||
* valid response message, this message will reject with an `Exception`.
|
||||
*
|
||||
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
|
||||
*
|
||||
* ```php
|
||||
* $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $domain
|
||||
* @return \React\Promise\PromiseInterface<array>
|
||||
* Resolves with all record values on success or rejects with an Exception on error.
|
||||
*/
|
||||
public function resolveAll($domain, $type);
|
||||
}
|
||||
920
vendor/react/http/CHANGELOG.md
vendored
Normal file
920
vendor/react/http/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,920 @@
|
||||
# Changelog
|
||||
|
||||
## 1.11.0 (2024-11-20)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable types.
|
||||
(#537 by @clue)
|
||||
|
||||
* Feature: Allow underscore character in Uri host.
|
||||
(#524 by @lulhum)
|
||||
|
||||
* Improve test suite to fix expected error code when ext-sockets is not enabled.
|
||||
(#539 by @WyriHaximus)
|
||||
|
||||
## 1.10.0 (2024-03-27)
|
||||
|
||||
* Feature: Add new PSR-7 implementation and remove dated RingCentral PSR-7 dependency.
|
||||
(#518, #519, #520 and #522 by @clue)
|
||||
|
||||
This changeset allows us to maintain our own PSR-7 implementation and reduce
|
||||
dependencies on external projects. It also improves performance slightly and
|
||||
does not otherwise affect our public API. If you want to explicitly install
|
||||
the old RingCentral PSR-7 dependency, you can still install it like this:
|
||||
|
||||
```bash
|
||||
composer require ringcentral/psr7
|
||||
```
|
||||
|
||||
* Feature: Add new `Uri` class for new PSR-7 implementation.
|
||||
(#521 by @clue)
|
||||
|
||||
* Feature: Validate outgoing HTTP message headers and reject invalid messages.
|
||||
(#523 by @clue)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#508 by @clue)
|
||||
|
||||
* Fix: Fix HTTP client to omit `Transfer-Encoding: chunked` when streaming empty request body.
|
||||
(#516 by @clue)
|
||||
|
||||
* Fix: Ensure connection close handler is cleaned up for each request.
|
||||
(#515 by @WyriHaximus)
|
||||
|
||||
* Update test suite and avoid unhandled promise rejections.
|
||||
(#501 and #502 by @clue)
|
||||
|
||||
## 1.9.0 (2023-04-26)
|
||||
|
||||
This is a **SECURITY** and feature release for the 1.x series of ReactPHP's HTTP component.
|
||||
|
||||
* Security fix: This release fixes a medium severity security issue in ReactPHP's HTTP server component
|
||||
that affects all versions between `v0.8.0` and `v1.8.0`. All users are encouraged to upgrade immediately.
|
||||
(CVE-2023-26044 reported and fixed by @WyriHaximus)
|
||||
|
||||
* Feature: Support HTTP keep-alive for HTTP client (reusing persistent connections).
|
||||
(#481, #484, #486 and #495 by @clue)
|
||||
|
||||
This feature offers significant performance improvements when sending many
|
||||
requests to the same host as it avoids recreating the underlying TCP/IP
|
||||
connection and repeating the TLS handshake for secure HTTPS requests.
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
// Up to 300% faster! HTTP keep-alive is enabled by default
|
||||
$response = React\Async\await($browser->get('https://httpbingo.org/redirect/6'));
|
||||
assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
```
|
||||
|
||||
* Feature: Add `Request` class to represent outgoing HTTP request message.
|
||||
(#480 by @clue)
|
||||
|
||||
* Feature: Preserve request method and body for `307 Temporary Redirect` and `308 Permanent Redirect`.
|
||||
(#442 by @dinooo13)
|
||||
|
||||
* Feature: Include buffer logic to avoid dependency on reactphp/promise-stream.
|
||||
(#482 by @clue)
|
||||
|
||||
* Improve test suite and project setup and report failed assertions.
|
||||
(#478 by @clue, #487 and #491 by @WyriHaximus and #475 and #479 by @SimonFrings)
|
||||
|
||||
## 1.8.0 (2022-09-29)
|
||||
|
||||
* Feature: Support for default request headers.
|
||||
(#461 by @51imyy)
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
$browser = $browser->withHeader('User-Agent', 'ACME');
|
||||
|
||||
$browser->get($url)->then(…);
|
||||
```
|
||||
|
||||
* Feature: Forward compatibility with upcoming Promise v3.
|
||||
(#460 by @clue)
|
||||
|
||||
## 1.7.0 (2022-08-23)
|
||||
|
||||
This is a **SECURITY** and feature release for the 1.x series of ReactPHP's HTTP component.
|
||||
|
||||
* Security fix: This release fixes a medium severity security issue in ReactPHP's HTTP server component
|
||||
that affects all versions between `v0.7.0` and `v1.6.0`. All users are encouraged to upgrade immediately.
|
||||
Special thanks to Marco Squarcina (TU Wien) for reporting this and working with us to coordinate this release.
|
||||
(CVE-2022-36032 reported by @lavish and fixed by @clue)
|
||||
|
||||
* Feature: Improve HTTP server performance by ~20%, reuse syscall values for clock time and socket addresses.
|
||||
(#457 and #467 by @clue)
|
||||
|
||||
* Feature: Full PHP 8.2+ compatibility, refactor internal `Transaction` to avoid assigning dynamic properties.
|
||||
(#459 by @clue and #466 by @WyriHaximus)
|
||||
|
||||
* Feature / Fix: Allow explicit `Content-Length` response header on `HEAD` requests.
|
||||
(#444 by @mrsimonbennett)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#452 by @clue, #458 by @nhedger, #448 by @jorrit and #446 by @SimonFrings)
|
||||
|
||||
* Improve test suite, update to use new reactphp/async package instead of clue/reactphp-block,
|
||||
skip memory tests when lowering memory limit fails and fix legacy HHVM build.
|
||||
(#464 and #440 by @clue and #450 by @SimonFrings)
|
||||
|
||||
## 1.6.0 (2022-02-03)
|
||||
|
||||
* Feature: Add factory methods for common HTML/JSON/plaintext/XML response types.
|
||||
(#439 by @clue)
|
||||
|
||||
```php
|
||||
$response = React\Http\Response\html("<h1>Hello wörld!</h1>\n");
|
||||
$response = React\Http\Response\json(['message' => 'Hello wörld!']);
|
||||
$response = React\Http\Response\plaintext("Hello wörld!\n");
|
||||
$response = React\Http\Response\xml("<message>Hello wörld!</message>\n");
|
||||
```
|
||||
|
||||
* Feature: Expose all status code constants via `Response` class.
|
||||
(#432 by @clue)
|
||||
|
||||
```php
|
||||
$response = new React\Http\Message\Response(
|
||||
React\Http\Message\Response::STATUS_OK, // 200 OK
|
||||
…
|
||||
);
|
||||
$response = new React\Http\Message\Response(
|
||||
React\Http\Message\Response::STATUS_NOT_FOUND, // 404 Not Found
|
||||
…
|
||||
);
|
||||
```
|
||||
|
||||
* Feature: Full support for PHP 8.1 release.
|
||||
(#433 by @SimonFrings and #434 by @clue)
|
||||
|
||||
* Feature / Fix: Improve protocol handling for HTTP responses with no body.
|
||||
(#429 and #430 by @clue)
|
||||
|
||||
* Internal refactoring and internal improvements for handling requests and responses.
|
||||
(#422 by @WyriHaximus and #431 by @clue)
|
||||
|
||||
* Improve documentation, update proxy examples, include error reporting in examples.
|
||||
(#420, #424, #426, and #427 by @clue)
|
||||
|
||||
* Update test suite to use default loop.
|
||||
(#438 by @clue)
|
||||
|
||||
## 1.5.0 (2021-08-04)
|
||||
|
||||
* Feature: Update `Browser` signature to take optional `$connector` as first argument and
|
||||
to match new Socket API without nullable loop arguments.
|
||||
(#418 and #419 by @clue)
|
||||
|
||||
```php
|
||||
// unchanged
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
// deprecated
|
||||
$browser = new React\Http\Browser(null, $connector);
|
||||
$browser = new React\Http\Browser($loop, $connector);
|
||||
|
||||
// new
|
||||
$browser = new React\Http\Browser($connector);
|
||||
$browser = new React\Http\Browser($connector, $loop);
|
||||
```
|
||||
|
||||
* Feature: Rename `Server` to `HttpServer` to avoid class name collisions and
|
||||
to avoid any ambiguities with regards to the new `SocketServer` API.
|
||||
(#417 and #419 by @clue)
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$server = new React\Http\Server($handler);
|
||||
$server->listen(new React\Socket\Server(8080));
|
||||
|
||||
// new
|
||||
$http = new React\Http\HttpServer($handler);
|
||||
$http->listen(new React\Socket\SocketServer('127.0.0.1:8080'));
|
||||
```
|
||||
|
||||
## 1.4.0 (2021-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
|
||||
(#410 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$browser = new React\Http\Browser($loop);
|
||||
$server = new React\Http\Server($loop, $handler);
|
||||
|
||||
// new (using default loop)
|
||||
$browser = new React\Http\Browser();
|
||||
$server = new React\Http\Server($handler);
|
||||
```
|
||||
|
||||
## 1.3.0 (2021-04-11)
|
||||
|
||||
* Feature: Support persistent connections (`Connection: keep-alive`).
|
||||
(#405 by @clue)
|
||||
|
||||
This shows a noticeable performance improvement especially when benchmarking
|
||||
using persistent connections (which is the default pretty much everywhere).
|
||||
Together with other changes in this release, this improves benchmarking
|
||||
performance by around 100%.
|
||||
|
||||
* Feature: Require `Host` request header for HTTP/1.1 requests.
|
||||
(#404 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#398 by @fritz-gerneth and #399 and #400 by @pavog)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
||||
(#402 by @SimonFrings)
|
||||
|
||||
## 1.2.0 (2020-12-04)
|
||||
|
||||
* Feature: Keep request body in memory also after consuming request body.
|
||||
(#395 by @clue)
|
||||
|
||||
This means consumers can now always access the complete request body as
|
||||
detailed in the documentation. This allows building custom parsers and more
|
||||
advanced processing models without having to mess with the default parsers.
|
||||
|
||||
## 1.1.0 (2020-09-11)
|
||||
|
||||
* Feature: Support upcoming PHP 8 release, update to reactphp/socket v1.6 and adjust type checks for invalid chunk headers.
|
||||
(#391 by @clue)
|
||||
|
||||
* Feature: Consistently resolve base URL according to HTTP specs.
|
||||
(#379 by @clue)
|
||||
|
||||
* Feature / Fix: Expose `Transfer-Encoding: chunked` response header and fix chunked responses for `HEAD` requests.
|
||||
(#381 by @clue)
|
||||
|
||||
* Internal refactoring to remove unneeded `MessageFactory` and `Response` classes.
|
||||
(#380 and #389 by @clue)
|
||||
|
||||
* Minor documentation improvements and improve test suite, update to support PHPUnit 9.3.
|
||||
(#385 by @clue and #393 by @SimonFrings)
|
||||
|
||||
## 1.0.0 (2020-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2020/announcing-reactphp-http).
|
||||
|
||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
||||
We'd like to emphasize that this component is production ready and battle-tested.
|
||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
||||
so you have a rock-solid foundation to build on top of.
|
||||
|
||||
This update involves some major new features and a number of BC breaks due to
|
||||
some necessary API cleanup. We've tried hard to avoid BC breaks where possible
|
||||
and minimize impact otherwise. We expect that most consumers of this package
|
||||
will be affected by BC breaks, but updating should take no longer than a few
|
||||
minutes. See below for more details:
|
||||
|
||||
* Feature: Add async HTTP client implementation.
|
||||
(#368 by @clue)
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser($loop);
|
||||
$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
echo $response->getBody();
|
||||
});
|
||||
```
|
||||
|
||||
The code has been imported as-is from [clue/reactphp-buzz v2.9.0](https://github.com/clue/reactphp-buzz),
|
||||
with only minor changes to the namespace and we otherwise leave all the existing APIs unchanged.
|
||||
Upgrading from [clue/reactphp-buzz v2.9.0](https://github.com/clue/reactphp-buzz)
|
||||
to this release should be a matter of updating some namespace references only:
|
||||
|
||||
```php
|
||||
// old
|
||||
$browser = new Clue\React\Buzz\Browser($loop);
|
||||
|
||||
// new
|
||||
$browser = new React\Http\Browser($loop);
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `LoopInterface` as required first constructor argument to `Server` and
|
||||
change `Server` to accept variadic middleware handlers instead of `array`.
|
||||
(#361 and #362 by @WyriHaximus)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new React\Http\Server($handler);
|
||||
$server = new React\Http\Server([$middleware, $handler]);
|
||||
|
||||
// new
|
||||
$server = new React\Http\Server($loop, $handler);
|
||||
$server = new React\Http\Server($loop, $middleware, $handler);
|
||||
```
|
||||
|
||||
* Feature / BC break: Move `Response` class to `React\Http\Message\Response` and
|
||||
expose `ServerRequest` class to `React\Http\Message\ServerRequest`.
|
||||
(#370 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$response = new React\Http\Response(200, [], 'Hello!');
|
||||
|
||||
// new
|
||||
$response = new React\Http\Message\Response(200, [], 'Hello!');
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `StreamingRequestMiddleware` to stream incoming requests, mark `StreamingServer` as internal.
|
||||
(#367 by @clue)
|
||||
|
||||
```php
|
||||
// old: advanced StreamingServer is now internal only
|
||||
$server = new React\Http\StreamingServer($handler);
|
||||
|
||||
// new: use StreamingRequestMiddleware instead of StreamingServer
|
||||
$server = new React\Http\Server(
|
||||
$loop,
|
||||
new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
$handler
|
||||
);
|
||||
```
|
||||
|
||||
* Feature / BC break: Improve default concurrency to 1024 requests and cap default request buffer at 64K.
|
||||
(#371 by @clue)
|
||||
|
||||
This improves default concurrency to 1024 requests and caps the default request buffer at 64K.
|
||||
The previous defaults resulted in just 4 concurrent requests with a request buffer of 8M.
|
||||
See [`Server`](README.md#server) for details on how to override these defaults.
|
||||
|
||||
* Feature: Expose ReactPHP in `User-Agent` client-side request header and in `Server` server-side response header.
|
||||
(#374 by @clue)
|
||||
|
||||
* Mark all classes as `final` to discourage inheriting from it.
|
||||
(#373 by @WyriHaximus)
|
||||
|
||||
* Improve documentation and use fully-qualified class names throughout the documentation and
|
||||
add ReactPHP core team as authors to `composer.json` and license file.
|
||||
(#366 and #369 by @WyriHaximus and #375 by @clue)
|
||||
|
||||
* Improve test suite and support skipping all online tests with `--exclude-group internet`.
|
||||
(#372 by @clue)
|
||||
|
||||
## 0.8.7 (2020-07-05)
|
||||
|
||||
* Fix: Fix parsing multipart request body with quoted header parameters (dot net).
|
||||
(#363 by @ebimmel)
|
||||
|
||||
* Fix: Fix calculating concurrency when `post_max_size` ini is unlimited.
|
||||
(#365 by @clue)
|
||||
|
||||
* Improve test suite to run tests on PHPUnit 9 and clean up test suite.
|
||||
(#364 by @SimonFrings)
|
||||
|
||||
## 0.8.6 (2020-01-12)
|
||||
|
||||
* Fix: Fix parsing `Cookie` request header with comma in its values.
|
||||
(#352 by @fiskie)
|
||||
|
||||
* Fix: Avoid unneeded warning when decoding invalid data on PHP 7.4.
|
||||
(#357 by @WyriHaximus)
|
||||
|
||||
* Add .gitattributes to exclude dev files from exports.
|
||||
(#353 by @reedy)
|
||||
|
||||
## 0.8.5 (2019-10-29)
|
||||
|
||||
* Internal refactorings and optimizations to improve request parsing performance.
|
||||
Benchmarks suggest number of requests/s improved by ~30% for common `GET` requests.
|
||||
(#345, #346, #349 and #350 by @clue)
|
||||
|
||||
* Add documentation and example for JSON/XML request body and
|
||||
improve documentation for concurrency and streaming requests and for error handling.
|
||||
(#341 and #342 by @clue)
|
||||
|
||||
## 0.8.4 (2019-01-16)
|
||||
|
||||
* Improvement: Internal refactoring to simplify response header logic.
|
||||
(#321 by @clue)
|
||||
|
||||
* Improvement: Assign Content-Length response header automatically only when size is known.
|
||||
(#329 by @clue)
|
||||
|
||||
* Improvement: Import global functions for better performance.
|
||||
(#330 by @WyriHaximus)
|
||||
|
||||
## 0.8.3 (2018-04-11)
|
||||
|
||||
* Feature: Do not pause connection stream to detect closed connections immediately.
|
||||
(#315 by @clue)
|
||||
|
||||
* Feature: Keep incoming `Transfer-Encoding: chunked` request header.
|
||||
(#316 by @clue)
|
||||
|
||||
* Feature: Reject invalid requests that contain both `Content-Length` and `Transfer-Encoding` request headers.
|
||||
(#318 by @clue)
|
||||
|
||||
* Minor internal refactoring to simplify connection close logic after sending response.
|
||||
(#317 by @clue)
|
||||
|
||||
## 0.8.2 (2018-04-06)
|
||||
|
||||
* Fix: Do not pass `$next` handler to final request handler.
|
||||
(#308 by @clue)
|
||||
|
||||
* Fix: Fix awaiting queued handlers when cancelling a queued handler.
|
||||
(#313 by @clue)
|
||||
|
||||
* Fix: Fix Server to skip `SERVER_ADDR` params for Unix domain sockets (UDS).
|
||||
(#307 by @clue)
|
||||
|
||||
* Documentation for PSR-15 middleware and minor documentation improvements.
|
||||
(#314 by @clue and #297, #298 and #310 by @seregazhuk)
|
||||
|
||||
* Minor code improvements and micro optimizations.
|
||||
(#301 by @seregazhuk and #305 by @kalessil)
|
||||
|
||||
## 0.8.1 (2018-01-05)
|
||||
|
||||
* Major request handler performance improvement. Benchmarks suggest number of
|
||||
requests/s improved by more than 50% for common `GET` requests!
|
||||
We now avoid queuing, buffering and wrapping incoming requests in promises
|
||||
when we're below limits and instead can directly process common requests.
|
||||
(#291, #292, #293, #294 and #296 by @clue)
|
||||
|
||||
* Fix: Fix concurrent invoking next middleware request handlers
|
||||
(#293 by @clue)
|
||||
|
||||
* Small code improvements
|
||||
(#286 by @seregazhuk)
|
||||
|
||||
* Improve test suite to be less fragile when using `ext-event` and
|
||||
fix test suite forward compatibility with upcoming EventLoop releases
|
||||
(#288 and #290 by @clue)
|
||||
|
||||
## 0.8.0 (2017-12-12)
|
||||
|
||||
* Feature / BC break: Add new `Server` facade that buffers and parses incoming
|
||||
HTTP requests. This provides full PSR-7 compatibility, including support for
|
||||
form submissions with POST fields and file uploads.
|
||||
The old `Server` has been renamed to `StreamingServer` for advanced usage
|
||||
and is used internally.
|
||||
(#266, #271, #281, #282, #283 and #284 by @WyriHaximus and @clue)
|
||||
|
||||
```php
|
||||
// old: handle incomplete/streaming requests
|
||||
$server = new Server($handler);
|
||||
|
||||
// new: handle complete, buffered and parsed requests
|
||||
// new: full PSR-7 support, including POST fields and file uploads
|
||||
$server = new Server($handler);
|
||||
|
||||
// new: handle incomplete/streaming requests
|
||||
$server = new StreamingServer($handler);
|
||||
```
|
||||
|
||||
> While this is technically a small BC break, this should in fact not break
|
||||
most consuming code. If you rely on the old request streaming, you can
|
||||
explicitly use the advanced `StreamingServer` to restore old behavior.
|
||||
|
||||
* Feature: Add support for middleware request handler arrays
|
||||
(#215, #228, #229, #236, #237, #238, #246, #247, #277, #279 and #285 by @WyriHaximus, @clue and @jsor)
|
||||
|
||||
```php
|
||||
// new: middleware request handler arrays
|
||||
$server = new Server(array(
|
||||
function (ServerRequestInterface $request, callable $next) {
|
||||
$request = $request->withHeader('Processed', time());
|
||||
return $next($request);
|
||||
},
|
||||
function (ServerRequestInterface $request) {
|
||||
return new Response();
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Add support for limiting how many next request handlers can be
|
||||
executed concurrently (`LimitConcurrentRequestsMiddleware`)
|
||||
(#272 by @clue and @WyriHaximus)
|
||||
|
||||
```php
|
||||
// new: explicitly limit concurrency
|
||||
$server = new Server(array(
|
||||
new LimitConcurrentRequestsMiddleware(10),
|
||||
$handler
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Add support for buffering the incoming request body
|
||||
(`RequestBodyBufferMiddleware`).
|
||||
This feature mimics PHP's default behavior and respects its `post_max_size`
|
||||
ini setting by default and allows explicit configuration.
|
||||
(#216, #224, #263, #276 and #278 by @WyriHaximus and #235 by @andig)
|
||||
|
||||
```php
|
||||
// new: buffer up to 10 requests with 8 MiB each
|
||||
$server = new StreamingServer(array(
|
||||
new LimitConcurrentRequestsMiddleware(10),
|
||||
new RequestBodyBufferMiddleware('8M'),
|
||||
$handler
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Add support for parsing form submissions with POST fields and file
|
||||
uploads (`RequestBodyParserMiddleware`).
|
||||
This feature mimics PHP's default behavior and respects its ini settings and
|
||||
`MAX_FILE_SIZE` POST fields by default and allows explicit configuration.
|
||||
(#220, #226, #252, #261, #264, #265, #267, #268, #274 by @WyriHaximus and @clue)
|
||||
|
||||
```php
|
||||
// new: buffer up to 10 requests with 8 MiB each
|
||||
// and limit to 4 uploads with 2 MiB each
|
||||
$server = new StreamingServer(array(
|
||||
new LimitConcurrentRequestsMiddleware(10),
|
||||
new RequestBodyBufferMiddleware('8M'),
|
||||
new RequestBodyParserMiddleware('2M', 4)
|
||||
$handler
|
||||
));
|
||||
```
|
||||
|
||||
* Feature: Update Socket to work around sending secure HTTPS responses with PHP < 7.1.4
|
||||
(#244 by @clue)
|
||||
|
||||
* Feature: Support sending same response header multiple times (e.g. `Set-Cookie`)
|
||||
(#248 by @clue)
|
||||
|
||||
* Feature: Raise maximum request header size to 8k to match common implementations
|
||||
(#253 by @clue)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6, test
|
||||
against PHP 7.1 and PHP 7.2 and refactor and remove risky and duplicate tests.
|
||||
(#243, #269 and #270 by @carusogabriel and #249 by @clue)
|
||||
|
||||
* Minor code refactoring to move internal classes to `React\Http\Io` namespace
|
||||
and clean up minor code and documentation issues
|
||||
(#251 by @clue, #227 by @kalessil, #240 by @christoph-kluge, #230 by @jsor and #280 by @andig)
|
||||
|
||||
## 0.7.4 (2017-08-16)
|
||||
|
||||
* Improvement: Target evenement 3.0 a long side 2.0 and 1.0
|
||||
(#212 by @WyriHaximus)
|
||||
|
||||
## 0.7.3 (2017-08-14)
|
||||
|
||||
* Feature: Support `Throwable` when setting previous exception from server callback
|
||||
(#155 by @jsor)
|
||||
|
||||
* Fix: Fixed URI parsing for origin-form requests that contain scheme separator
|
||||
such as `/path?param=http://example.com`.
|
||||
(#209 by @aaronbonneau)
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build
|
||||
(#211 by @clue)
|
||||
|
||||
## 0.7.2 (2017-07-04)
|
||||
|
||||
* Fix: Stricter check for invalid request-line in HTTP requests
|
||||
(#206 by @clue)
|
||||
|
||||
* Refactor to use HTTP response reason phrases from response object
|
||||
(#205 by @clue)
|
||||
|
||||
## 0.7.1 (2017-06-17)
|
||||
|
||||
* Fix: Fix parsing CONNECT request without `Host` header
|
||||
(#201 by @clue)
|
||||
|
||||
* Internal preparation for future PSR-7 `UploadedFileInterface`
|
||||
(#199 by @WyriHaximus)
|
||||
|
||||
## 0.7.0 (2017-05-29)
|
||||
|
||||
* Feature / BC break: Use PSR-7 (http-message) standard and
|
||||
`Request-In-Response-Out`-style request handler callback.
|
||||
Pass standard PSR-7 `ServerRequestInterface` and expect any standard
|
||||
PSR-7 `ResponseInterface` in return for the request handler callback.
|
||||
(#146 and #152 and #170 by @legionth)
|
||||
|
||||
```php
|
||||
// old
|
||||
$app = function (Request $request, Response $response) {
|
||||
$response->writeHead(200, array('Content-Type' => 'text/plain'));
|
||||
$response->end("Hello world!\n");
|
||||
};
|
||||
|
||||
// new
|
||||
$app = function (ServerRequestInterface $request) {
|
||||
return new Response(
|
||||
200,
|
||||
array('Content-Type' => 'text/plain'),
|
||||
"Hello world!\n"
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
A `Content-Length` header will automatically be included if the size can be
|
||||
determined from the response body.
|
||||
(#164 by @maciejmrozinski)
|
||||
|
||||
The request handler callback will automatically make sure that responses to
|
||||
HEAD requests and certain status codes, such as `204` (No Content), never
|
||||
contain a response body.
|
||||
(#156 by @clue)
|
||||
|
||||
The intermediary `100 Continue` response will automatically be sent if
|
||||
demanded by a HTTP/1.1 client.
|
||||
(#144 by @legionth)
|
||||
|
||||
The request handler callback can now return a standard `Promise` if
|
||||
processing the request needs some time, such as when querying a database.
|
||||
Similarly, the request handler may return a streaming response if the
|
||||
response body comes from a `ReadableStreamInterface` or its size is
|
||||
unknown in advance.
|
||||
|
||||
```php
|
||||
// old
|
||||
$app = function (Request $request, Response $response) use ($db) {
|
||||
$db->query()->then(function ($result) use ($response) {
|
||||
$response->writeHead(200, array('Content-Type' => 'text/plain'));
|
||||
$response->end($result);
|
||||
});
|
||||
};
|
||||
|
||||
// new
|
||||
$app = function (ServerRequestInterface $request) use ($db) {
|
||||
return $db->query()->then(function ($result) {
|
||||
return new Response(
|
||||
200,
|
||||
array('Content-Type' => 'text/plain'),
|
||||
$result
|
||||
);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
Pending promies and response streams will automatically be canceled once the
|
||||
client connection closes.
|
||||
(#187 and #188 by @clue)
|
||||
|
||||
The `ServerRequestInterface` contains the full effective request URI,
|
||||
server-side parameters, query parameters and parsed cookies values as
|
||||
defined in PSR-7.
|
||||
(#167 by @clue and #174, #175 and #180 by @legionth)
|
||||
|
||||
```php
|
||||
$app = function (ServerRequestInterface $request) {
|
||||
return new Response(
|
||||
200,
|
||||
array('Content-Type' => 'text/plain'),
|
||||
$request->getUri()->getScheme()
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
Advanced: Support duplex stream response for `Upgrade` requests such as
|
||||
`Upgrade: WebSocket` or custom protocols and `CONNECT` requests
|
||||
(#189 and #190 by @clue)
|
||||
|
||||
> Note that the request body will currently not be buffered and parsed by
|
||||
default, which depending on your particilar use-case, may limit
|
||||
interoperability with the PSR-7 (http-message) ecosystem.
|
||||
The provided streaming request body interfaces allow you to perform
|
||||
buffering and parsing as needed in the request handler callback.
|
||||
See also the README and examples for more details.
|
||||
|
||||
* Feature / BC break: Replace `request` listener with callback function and
|
||||
use `listen()` method to support multiple listening sockets
|
||||
(#97 by @legionth and #193 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($socket);
|
||||
$server->on('request', $app);
|
||||
|
||||
// new
|
||||
$server = new Server($app);
|
||||
$server->listen($socket);
|
||||
```
|
||||
|
||||
* Feature: Support the more advanced HTTP requests, such as
|
||||
`OPTIONS * HTTP/1.1` (`OPTIONS` method in asterisk-form),
|
||||
`GET http://example.com/path HTTP/1.1` (plain proxy requests in absolute-form),
|
||||
`CONNECT example.com:443 HTTP/1.1` (`CONNECT` proxy requests in authority-form)
|
||||
and sanitize `Host` header value across all requests.
|
||||
(#157, #158, #161, #165, #169 and #173 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with Socket v1.0, v0.8, v0.7 and v0.6 and
|
||||
forward compatibility with Stream v1.0 and v0.7
|
||||
(#154, #163, #183, #184 and #191 by @clue)
|
||||
|
||||
* Feature: Simplify examples to ease getting started and
|
||||
add benchmarking example
|
||||
(#151 and #162 by @clue)
|
||||
|
||||
* Improve test suite by adding tests for case insensitive chunked transfer
|
||||
encoding and ignoring HHVM test failures until Travis tests work again.
|
||||
(#150 by @legionth and #185 by @clue)
|
||||
|
||||
## 0.6.0 (2017-03-09)
|
||||
|
||||
* Feature / BC break: The `Request` and `Response` objects now follow strict
|
||||
stream semantics and their respective methods and events.
|
||||
(#116, #129, #133, #135, #136, #137, #138, #140, #141 by @legionth
|
||||
and #122, #123, #130, #131, #132, #142 by @clue)
|
||||
|
||||
This implies that the `Server` now supports proper detection of the request
|
||||
message body stream, such as supporting decoding chunked transfer encoding,
|
||||
delimiting requests with an explicit `Content-Length` header
|
||||
and those with an empty request message body.
|
||||
|
||||
These streaming semantics are compatible with previous Stream v0.5, future
|
||||
compatible with v0.5 and upcoming v0.6 versions and can be used like this:
|
||||
|
||||
```php
|
||||
$http->on('request', function (Request $request, Response $response) {
|
||||
$contentLength = 0;
|
||||
$request->on('data', function ($data) use (&$contentLength) {
|
||||
$contentLength += strlen($data);
|
||||
});
|
||||
|
||||
$request->on('end', function () use ($response, &$contentLength){
|
||||
$response->writeHead(200, array('Content-Type' => 'text/plain'));
|
||||
$response->end("The length of the submitted request body is: " . $contentLength);
|
||||
});
|
||||
|
||||
// an error occured
|
||||
// e.g. on invalid chunked encoded data or an unexpected 'end' event
|
||||
$request->on('error', function (\Exception $exception) use ($response, &$contentLength) {
|
||||
$response->writeHead(400, array('Content-Type' => 'text/plain'));
|
||||
$response->end("An error occured while reading at length: " . $contentLength);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, the `Request` and `Response` now strictly follow the
|
||||
`close()` method and `close` event semantics.
|
||||
Closing the `Request` does not interrupt the underlying TCP/IP in
|
||||
order to allow still sending back a valid response message.
|
||||
Closing the `Response` does terminate the underlying TCP/IP
|
||||
connection in order to clean up resources.
|
||||
|
||||
You should make sure to always attach a `request` event listener
|
||||
like above. The `Server` will not respond to an incoming HTTP
|
||||
request otherwise and keep the TCP/IP connection pending until the
|
||||
other side chooses to close the connection.
|
||||
|
||||
* Feature: Support `HTTP/1.1` and `HTTP/1.0` for `Request` and `Response`.
|
||||
(#124, #125, #126, #127, #128 by @clue and #139 by @legionth)
|
||||
|
||||
The outgoing `Response` will automatically use the same HTTP version as the
|
||||
incoming `Request` message and will only apply `HTTP/1.1` semantics if
|
||||
applicable. This includes that the `Response` will automatically attach a
|
||||
`Date` and `Connection: close` header if applicable.
|
||||
|
||||
This implies that the `Server` now automatically responds with HTTP error
|
||||
messages for invalid requests (status 400) and those exceeding internal
|
||||
request header limits (status 431).
|
||||
|
||||
## 0.5.0 (2017-02-16)
|
||||
|
||||
* Feature / BC break: Change `Request` methods to be in line with PSR-7
|
||||
(#117 by @clue)
|
||||
* Rename `getQuery()` to `getQueryParams()`
|
||||
* Rename `getHttpVersion()` to `getProtocolVersion()`
|
||||
* Change `getHeaders()` to always return an array of string values
|
||||
for each header
|
||||
|
||||
* Feature / BC break: Update Socket component to v0.5 and
|
||||
add secure HTTPS server support
|
||||
(#90 and #119 by @clue)
|
||||
|
||||
```php
|
||||
// old plaintext HTTP server
|
||||
$socket = new React\Socket\Server($loop);
|
||||
$socket->listen(8080, '127.0.0.1');
|
||||
$http = new React\Http\Server($socket);
|
||||
|
||||
// new plaintext HTTP server
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
|
||||
$http = new React\Http\Server($socket);
|
||||
|
||||
// new secure HTTPS server
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
|
||||
$socket = new React\Socket\SecureServer($socket, $loop, array(
|
||||
'local_cert' => __DIR__ . '/localhost.pem'
|
||||
));
|
||||
$http = new React\Http\Server($socket);
|
||||
```
|
||||
|
||||
* BC break: Mark internal APIs as internal or private and
|
||||
remove unneeded `ServerInterface`
|
||||
(#118 by @clue, #95 by @legionth)
|
||||
|
||||
## 0.4.4 (2017-02-13)
|
||||
|
||||
* Feature: Add request header accessors (à la PSR-7)
|
||||
(#103 by @clue)
|
||||
|
||||
```php
|
||||
// get value of host header
|
||||
$host = $request->getHeaderLine('Host');
|
||||
|
||||
// get list of all cookie headers
|
||||
$cookies = $request->getHeader('Cookie');
|
||||
```
|
||||
|
||||
* Feature: Forward `pause()` and `resume()` from `Request` to underlying connection
|
||||
(#110 by @clue)
|
||||
|
||||
```php
|
||||
// support back-pressure when piping request into slower destination
|
||||
$request->pipe($dest);
|
||||
|
||||
// manually pause/resume request
|
||||
$request->pause();
|
||||
$request->resume();
|
||||
```
|
||||
|
||||
* Fix: Fix `100-continue` to be handled case-insensitive and ignore it for HTTP/1.0.
|
||||
Similarly, outgoing response headers are now handled case-insensitive, e.g
|
||||
we no longer apply chunked transfer encoding with mixed-case `Content-Length`.
|
||||
(#107 by @clue)
|
||||
|
||||
```php
|
||||
// now handled case-insensitive
|
||||
$request->expectsContinue();
|
||||
|
||||
// now works just like properly-cased header
|
||||
$response->writeHead($status, array('content-length' => 0));
|
||||
```
|
||||
|
||||
* Fix: Do not emit empty `data` events and ignore empty writes in order to
|
||||
not mess up chunked transfer encoding
|
||||
(#108 and #112 by @clue)
|
||||
|
||||
* Lock and test minimum required dependency versions and support PHPUnit v5
|
||||
(#113, #115 and #114 by @andig)
|
||||
|
||||
## 0.4.3 (2017-02-10)
|
||||
|
||||
* Fix: Do not take start of body into account when checking maximum header size
|
||||
(#88 by @nopolabs)
|
||||
|
||||
* Fix: Remove `data` listener if `HeaderParser` emits an error
|
||||
(#83 by @nick4fake)
|
||||
|
||||
* First class support for PHP 5.3 through PHP 7 and HHVM
|
||||
(#101 and #102 by @clue, #66 by @WyriHaximus)
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev,
|
||||
improving forward compatibility with newer PHPUnit versions
|
||||
and replacing unneeded test stubs
|
||||
(#92 and #93 by @nopolabs, #100 by @legionth)
|
||||
|
||||
## 0.4.2 (2016-11-09)
|
||||
|
||||
* Remove all listeners after emitting error in RequestHeaderParser #68 @WyriHaximus
|
||||
* Catch Guzzle parse request errors #65 @WyriHaximus
|
||||
* Remove branch-alias definition as per reactphp/react#343 #58 @WyriHaximus
|
||||
* Add functional example to ease getting started #64 by @clue
|
||||
* Naming, immutable array manipulation #37 @cboden
|
||||
|
||||
## 0.4.1 (2015-05-21)
|
||||
|
||||
* Replaced guzzle/parser with guzzlehttp/psr7 by @cboden
|
||||
* FIX Continue Header by @iannsp
|
||||
* Missing type hint by @marenzo
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: Update to React/Promise 2.0
|
||||
* BC break: Update to Evenement 2.0
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
* Bump React dependencies to v0.4
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Bump React dependencies to v0.3
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Bug fix: Emit end event when Response closes (@beaucollins)
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Bug fix: Forward drain events from HTTP response (@cs278)
|
||||
* Dependency: Updated guzzle deps to `3.0.*`
|
||||
|
||||
## 0.2.2 (2012-10-28)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.1 (2012-10-14)
|
||||
|
||||
* Feature: Support HTTP 1.1 continue
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Bump React dependencies to v0.2
|
||||
|
||||
## 0.1.1 (2012-07-12)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.1.0 (2012-07-11)
|
||||
|
||||
* First tagged release
|
||||
21
vendor/react/http/LICENSE
vendored
Normal file
21
vendor/react/http/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
3024
vendor/react/http/README.md
vendored
Normal file
3024
vendor/react/http/README.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
57
vendor/react/http/composer.json
vendored
Normal file
57
vendor/react/http/composer.json
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "react/http",
|
||||
"description": "Event-driven, streaming HTTP client and server implementation for ReactPHP",
|
||||
"keywords": ["HTTP client", "HTTP server", "HTTP", "HTTPS", "event-driven", "streaming", "client", "server", "PSR-7", "async", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"fig/http-message-util": "^1.1",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.3 || ^1.2.1",
|
||||
"react/socket": "^1.16",
|
||||
"react/stream": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/http-proxy-react": "^1.8",
|
||||
"clue/reactphp-ssh-proxy": "^1.4",
|
||||
"clue/socks-react": "^1.4",
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4.2 || ^3 || ^2",
|
||||
"react/promise-stream": "^1.4",
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Http\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Http\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
858
vendor/react/http/src/Browser.php
vendored
Normal file
858
vendor/react/http/src/Browser.php
vendored
Normal file
@@ -0,0 +1,858 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Io\Sender;
|
||||
use React\Http\Io\Transaction;
|
||||
use React\Http\Message\Request;
|
||||
use React\Http\Message\Uri;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @final This class is final and shouldn't be extended as it is likely to be marked final in a future release.
|
||||
*/
|
||||
class Browser
|
||||
{
|
||||
private $transaction;
|
||||
private $baseUrl;
|
||||
private $protocolVersion = '1.1';
|
||||
private $defaultHeaders = array(
|
||||
'User-Agent' => 'ReactPHP/1'
|
||||
);
|
||||
|
||||
/**
|
||||
* The `Browser` is responsible for sending HTTP requests to your HTTP server
|
||||
* and keeps track of pending incoming HTTP responses.
|
||||
*
|
||||
* ```php
|
||||
* $browser = new React\Http\Browser();
|
||||
* ```
|
||||
*
|
||||
* This class takes two optional arguments for more advanced usage:
|
||||
*
|
||||
* ```php
|
||||
* // constructor signature as of v1.5.0
|
||||
* $browser = new React\Http\Browser(?ConnectorInterface $connector = null, ?LoopInterface $loop = null);
|
||||
*
|
||||
* // legacy constructor signature before v1.5.0
|
||||
* $browser = new React\Http\Browser(?LoopInterface $loop = null, ?ConnectorInterface $connector = null);
|
||||
* ```
|
||||
*
|
||||
* If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
|
||||
* proxy servers etc.), you can explicitly pass a custom instance of the
|
||||
* [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
|
||||
*
|
||||
* ```php
|
||||
* $connector = new React\Socket\Connector(array(
|
||||
* 'dns' => '127.0.0.1',
|
||||
* 'tcp' => array(
|
||||
* 'bindto' => '192.168.10.1:0'
|
||||
* ),
|
||||
* 'tls' => array(
|
||||
* 'verify_peer' => false,
|
||||
* 'verify_peer_name' => false
|
||||
* )
|
||||
* ));
|
||||
*
|
||||
* $browser = new React\Http\Browser($connector);
|
||||
* ```
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param null|ConnectorInterface|LoopInterface $connector
|
||||
* @param null|LoopInterface|ConnectorInterface $loop
|
||||
* @throws \InvalidArgumentException for invalid arguments
|
||||
*/
|
||||
public function __construct($connector = null, $loop = null)
|
||||
{
|
||||
// swap arguments for legacy constructor signature
|
||||
if (($connector instanceof LoopInterface || $connector === null) && ($loop instanceof ConnectorInterface || $loop === null)) {
|
||||
$swap = $loop;
|
||||
$loop = $connector;
|
||||
$connector = $swap;
|
||||
}
|
||||
|
||||
if (($connector !== null && !$connector instanceof ConnectorInterface) || ($loop !== null && !$loop instanceof LoopInterface)) {
|
||||
throw new \InvalidArgumentException('Expected "?ConnectorInterface $connector" and "?LoopInterface $loop" arguments');
|
||||
}
|
||||
|
||||
$loop = $loop ?: Loop::get();
|
||||
$this->transaction = new Transaction(
|
||||
Sender::createFromLoop($loop, $connector ?: new Connector(array(), $loop)),
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP GET request
|
||||
*
|
||||
* ```php
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [GET request client example](../examples/01-client-get-request.php).
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function get($url, array $headers = array())
|
||||
{
|
||||
return $this->requestMayBeStreaming('GET', $url, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP POST request
|
||||
*
|
||||
* ```php
|
||||
* $browser->post(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/json'
|
||||
* ],
|
||||
* json_encode($data)
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump(json_decode((string)$response->getBody()));
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [POST JSON client example](../examples/04-client-post-json.php).
|
||||
*
|
||||
* This method is also commonly used to submit HTML form data:
|
||||
*
|
||||
* ```php
|
||||
* $data = [
|
||||
* 'user' => 'Alice',
|
||||
* 'password' => 'secret'
|
||||
* ];
|
||||
*
|
||||
* $browser->post(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/x-www-form-urlencoded'
|
||||
* ],
|
||||
* http_build_query($data)
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->post($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function post($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('POST', $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP HEAD request
|
||||
*
|
||||
* ```php
|
||||
* $browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump($response->getHeaders());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function head($url, array $headers = array())
|
||||
{
|
||||
return $this->requestMayBeStreaming('HEAD', $url, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP PATCH request
|
||||
*
|
||||
* ```php
|
||||
* $browser->patch(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'application/json'
|
||||
* ],
|
||||
* json_encode($data)
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump(json_decode((string)$response->getBody()));
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->patch($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function patch($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('PATCH', $url , $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP PUT request
|
||||
*
|
||||
* ```php
|
||||
* $browser->put(
|
||||
* $url,
|
||||
* [
|
||||
* 'Content-Type' => 'text/xml'
|
||||
* ],
|
||||
* $xml->asXML()
|
||||
* )->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [PUT XML client example](../examples/05-client-put-xml.php).
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->put($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function put($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('PUT', $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP DELETE request
|
||||
*
|
||||
* ```php
|
||||
* $browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param string $url URL for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function delete($url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->requestMayBeStreaming('DELETE', $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an arbitrary HTTP request.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request.
|
||||
*
|
||||
* As an alternative, if you want to use a custom HTTP request method, you
|
||||
* can use this method:
|
||||
*
|
||||
* ```php
|
||||
* $browser->request('OPTIONS', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* var_dump((string)$response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* If you're using a streaming request body (`ReadableStreamInterface`), it
|
||||
* will default to using `Transfer-Encoding: chunked` or you have to
|
||||
* explicitly pass in a matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->request('POST', $url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
|
||||
* @param string $url URL for the request
|
||||
* @param array $headers Additional request headers
|
||||
* @param string|ReadableStreamInterface $body HTTP request body contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function request($method, $url, array $headers = array(), $body = '')
|
||||
{
|
||||
return $this->withOptions(array('streaming' => false))->requestMayBeStreaming($method, $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an arbitrary HTTP request and receives a streaming response without buffering the response body.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request. Each of these methods will buffer
|
||||
* the whole response body in memory by default. This is easy to get started
|
||||
* and works reasonably well for smaller responses.
|
||||
*
|
||||
* In some situations, it's a better idea to use a streaming approach, where
|
||||
* only small chunks have to be kept in memory. You can use this method to
|
||||
* send an arbitrary HTTP request and receive a streaming response. It uses
|
||||
* the same HTTP message API, but does not buffer the response body in
|
||||
* memory. It only processes the response body in small chunks as data is
|
||||
* received and forwards this data through [ReactPHP's Stream API](https://github.com/reactphp/stream).
|
||||
* This works for (any number of) responses of arbitrary sizes.
|
||||
*
|
||||
* ```php
|
||||
* $browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* $body = $response->getBody();
|
||||
* assert($body instanceof Psr\Http\Message\StreamInterface);
|
||||
* assert($body instanceof React\Stream\ReadableStreamInterface);
|
||||
*
|
||||
* $body->on('data', function ($chunk) {
|
||||
* echo $chunk;
|
||||
* });
|
||||
*
|
||||
* $body->on('error', function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
*
|
||||
* $body->on('close', function () {
|
||||
* echo '[DONE]' . PHP_EOL;
|
||||
* });
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
|
||||
* and the [streaming response](#streaming-response) for more details,
|
||||
* examples and possible use-cases.
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* If you're using a streaming request body (`ReadableStreamInterface`), it
|
||||
* will default to using `Transfer-Encoding: chunked` or you have to
|
||||
* explicitly pass in a matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new React\Stream\ThroughStream();
|
||||
* Loop::addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->requestStreaming('POST', $url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
|
||||
* @param string $url URL for the request
|
||||
* @param array $headers Additional request headers
|
||||
* @param string|ReadableStreamInterface $body HTTP request body contents
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
public function requestStreaming($method, $url, $headers = array(), $body = '')
|
||||
{
|
||||
return $this->withOptions(array('streaming' => true))->requestMayBeStreaming($method, $url, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum timeout used for waiting for pending requests.
|
||||
*
|
||||
* You can pass in the number of seconds to use as a new timeout value:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(10.0);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `false` to disable any timeouts. In this case,
|
||||
* requests can stay pending forever:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(false);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default timeout handling. This
|
||||
* will respects PHP's `default_socket_timeout` setting (default 60s):
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withTimeout(true);
|
||||
* ```
|
||||
*
|
||||
* See also [timeouts](#timeouts) for more details about timeout handling.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given timeout value applied.
|
||||
*
|
||||
* @param bool|number $timeout
|
||||
* @return self
|
||||
*/
|
||||
public function withTimeout($timeout)
|
||||
{
|
||||
if ($timeout === true) {
|
||||
$timeout = null;
|
||||
} elseif ($timeout === false) {
|
||||
$timeout = -1;
|
||||
} elseif ($timeout < 0) {
|
||||
$timeout = 0;
|
||||
}
|
||||
|
||||
return $this->withOptions(array(
|
||||
'timeout' => $timeout,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes how HTTP redirects will be followed.
|
||||
*
|
||||
* You can pass in the maximum number of redirects to follow:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(5);
|
||||
* ```
|
||||
*
|
||||
* The request will automatically be rejected when the number of redirects
|
||||
* is exceeded. You can pass in a `0` to reject the request for any
|
||||
* redirects encountered:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(0);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // only non-redirected responses will now end up here
|
||||
* var_dump($response->getHeaders());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `false` to disable following any redirects. In
|
||||
* this case, requests will resolve with the redirection response instead
|
||||
* of following the `Location` response header:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(false);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any redirects will now end up here
|
||||
* var_dump($response->getHeaderLine('Location'));
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default redirect handling.
|
||||
* This defaults to following a maximum of 10 redirects:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withFollowRedirects(true);
|
||||
* ```
|
||||
*
|
||||
* See also [redirects](#redirects) for more details about redirect handling.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given redirect setting applied.
|
||||
*
|
||||
* @param bool|int $followRedirects
|
||||
* @return self
|
||||
*/
|
||||
public function withFollowRedirects($followRedirects)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'followRedirects' => $followRedirects !== false,
|
||||
'maxRedirects' => \is_bool($followRedirects) ? null : $followRedirects
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.
|
||||
*
|
||||
* You can pass in a bool `false` to disable rejecting incoming responses
|
||||
* that use a 4xx or 5xx response status code. In this case, requests will
|
||||
* resolve with the response message indicating an error condition:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withRejectErrorResponse(false);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any HTTP response will now end up here
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You can pass in a bool `true` to re-enable default status code handling.
|
||||
* This defaults to rejecting any response status codes in the 4xx or 5xx
|
||||
* range:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withRejectErrorResponse(true);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // any successful HTTP response will now end up here
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* }, function (Exception $e) {
|
||||
* if ($e instanceof React\Http\Message\ResponseException) {
|
||||
* // any HTTP response error message will now end up here
|
||||
* $response = $e->getResponse();
|
||||
* var_dump($response->getStatusCode(), $response->getReasonPhrase());
|
||||
* } else {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given setting applied.
|
||||
*
|
||||
* @param bool $obeySuccessCode
|
||||
* @return self
|
||||
*/
|
||||
public function withRejectErrorResponse($obeySuccessCode)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'obeySuccessCode' => $obeySuccessCode,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the base URL used to resolve relative URLs to.
|
||||
*
|
||||
* If you configure a base URL, any requests to relative URLs will be
|
||||
* processed by first resolving this relative to the given absolute base
|
||||
* URL. This supports resolving relative path references (like `../` etc.).
|
||||
* This is particularly useful for (RESTful) API calls where all endpoints
|
||||
* (URLs) are located under a common base URL.
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withBase('http://api.example.com/v3/');
|
||||
*
|
||||
* // will request http://api.example.com/v3/users
|
||||
* $browser->get('users')->then(…);
|
||||
* ```
|
||||
*
|
||||
* You can pass in a `null` base URL to return a new instance that does not
|
||||
* use a base URL:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withBase(null);
|
||||
* ```
|
||||
*
|
||||
* Accordingly, any requests using relative URLs to a browser that does not
|
||||
* use a base URL can not be completed and will be rejected without sending
|
||||
* a request.
|
||||
*
|
||||
* This method will throw an `InvalidArgumentException` if the given
|
||||
* `$baseUrl` argument is not a valid URL.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
|
||||
* actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.
|
||||
*
|
||||
* @param string|null $baseUrl absolute base URL
|
||||
* @return self
|
||||
* @throws InvalidArgumentException if the given $baseUrl is not a valid absolute URL
|
||||
* @see self::withoutBase()
|
||||
*/
|
||||
public function withBase($baseUrl)
|
||||
{
|
||||
$browser = clone $this;
|
||||
if ($baseUrl === null) {
|
||||
$browser->baseUrl = null;
|
||||
return $browser;
|
||||
}
|
||||
|
||||
$browser->baseUrl = new Uri($baseUrl);
|
||||
if (!\in_array($browser->baseUrl->getScheme(), array('http', 'https')) || $browser->baseUrl->getHost() === '') {
|
||||
throw new \InvalidArgumentException('Base URL must be absolute');
|
||||
}
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the HTTP protocol version that will be used for all subsequent requests.
|
||||
*
|
||||
* All the above [request methods](#request-methods) default to sending
|
||||
* requests as HTTP/1.1. This is the preferred HTTP protocol version which
|
||||
* also provides decent backwards-compatibility with legacy HTTP/1.0
|
||||
* servers. As such, there should rarely be a need to explicitly change this
|
||||
* protocol version.
|
||||
*
|
||||
* If you want to explicitly use the legacy HTTP/1.0 protocol version, you
|
||||
* can use this method:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withProtocolVersion('1.0');
|
||||
*
|
||||
* $browser->get($url)->then(…);
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* new protocol version applied.
|
||||
*
|
||||
* @param string $protocolVersion HTTP protocol version to use, must be one of "1.1" or "1.0"
|
||||
* @return self
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function withProtocolVersion($protocolVersion)
|
||||
{
|
||||
if (!\in_array($protocolVersion, array('1.0', '1.1'), true)) {
|
||||
throw new InvalidArgumentException('Invalid HTTP protocol version, must be one of "1.1" or "1.0"');
|
||||
}
|
||||
|
||||
$browser = clone $this;
|
||||
$browser->protocolVersion = (string) $protocolVersion;
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the maximum size for buffering a response body.
|
||||
*
|
||||
* The preferred way to send an HTTP request is by using the above
|
||||
* [request methods](#request-methods), for example the [`get()`](#get)
|
||||
* method to send an HTTP `GET` request. Each of these methods will buffer
|
||||
* the whole response body in memory by default. This is easy to get started
|
||||
* and works reasonably well for smaller responses.
|
||||
*
|
||||
* By default, the response body buffer will be limited to 16 MiB. If the
|
||||
* response body exceeds this maximum size, the request will be rejected.
|
||||
*
|
||||
* You can pass in the maximum number of bytes to buffer:
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withResponseBuffer(1024 * 1024);
|
||||
*
|
||||
* $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
* // response body will not exceed 1 MiB
|
||||
* var_dump($response->getHeaders(), (string) $response->getBody());
|
||||
* }, function (Exception $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Note that the response body buffer has to be kept in memory for each
|
||||
* pending request until its transfer is completed and it will only be freed
|
||||
* after a pending request is fulfilled. As such, increasing this maximum
|
||||
* buffer size to allow larger response bodies is usually not recommended.
|
||||
* Instead, you can use the [`requestStreaming()` method](#requeststreaming)
|
||||
* to receive responses with arbitrary sizes without buffering. Accordingly,
|
||||
* this maximum buffer size setting has no effect on streaming responses.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* given setting applied.
|
||||
*
|
||||
* @param int $maximumSize
|
||||
* @return self
|
||||
* @see self::requestStreaming()
|
||||
*/
|
||||
public function withResponseBuffer($maximumSize)
|
||||
{
|
||||
return $this->withOptions(array(
|
||||
'maximumSize' => $maximumSize
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a request header for all following requests.
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withHeader('User-Agent', 'ACME');
|
||||
*
|
||||
* $browser->get($url)->then(…);
|
||||
* ```
|
||||
*
|
||||
* Note that the new header will overwrite any headers previously set with
|
||||
* the same name (case-insensitive). Following requests will use these headers
|
||||
* by default unless they are explicitly set for any requests.
|
||||
*
|
||||
* @param string $header
|
||||
* @param string $value
|
||||
* @return Browser
|
||||
*/
|
||||
public function withHeader($header, $value)
|
||||
{
|
||||
$browser = $this->withoutHeader($header);
|
||||
$browser->defaultHeaders[$header] = $value;
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any default request headers previously set via
|
||||
* the [`withHeader()` method](#withheader).
|
||||
*
|
||||
* ```php
|
||||
* $browser = $browser->withoutHeader('User-Agent');
|
||||
*
|
||||
* $browser->get($url)->then(…);
|
||||
* ```
|
||||
*
|
||||
* Note that this method only affects the headers which were set with the
|
||||
* method `withHeader(string $header, string $value): Browser`
|
||||
*
|
||||
* @param string $header
|
||||
* @return Browser
|
||||
*/
|
||||
public function withoutHeader($header)
|
||||
{
|
||||
$browser = clone $this;
|
||||
|
||||
/** @var string|int $key */
|
||||
foreach (\array_keys($browser->defaultHeaders) as $key) {
|
||||
if (\strcasecmp($key, $header) === 0) {
|
||||
unset($browser->defaultHeaders[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the [options](#options) to use:
|
||||
*
|
||||
* The [`Browser`](#browser) class exposes several options for the handling of
|
||||
* HTTP transactions. These options resemble some of PHP's
|
||||
* [HTTP context options](http://php.net/manual/en/context.http.php) and
|
||||
* can be controlled via the following API (and their defaults):
|
||||
*
|
||||
* ```php
|
||||
* // deprecated
|
||||
* $newBrowser = $browser->withOptions(array(
|
||||
* 'timeout' => null, // see withTimeout() instead
|
||||
* 'followRedirects' => true, // see withFollowRedirects() instead
|
||||
* 'maxRedirects' => 10, // see withFollowRedirects() instead
|
||||
* 'obeySuccessCode' => true, // see withRejectErrorResponse() instead
|
||||
* 'streaming' => false, // deprecated, see requestStreaming() instead
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* See also [timeouts](#timeouts), [redirects](#redirects) and
|
||||
* [streaming](#streaming) for more details.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* options applied.
|
||||
*
|
||||
* @param array $options
|
||||
* @return self
|
||||
* @see self::withTimeout()
|
||||
* @see self::withFollowRedirects()
|
||||
* @see self::withRejectErrorResponse()
|
||||
*/
|
||||
private function withOptions(array $options)
|
||||
{
|
||||
$browser = clone $this;
|
||||
$browser->transaction = $this->transaction->withOptions($options);
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $url
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $body
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
private function requestMayBeStreaming($method, $url, array $headers = array(), $body = '')
|
||||
{
|
||||
if ($this->baseUrl !== null) {
|
||||
// ensure we're actually below the base URL
|
||||
$url = Uri::resolve($this->baseUrl, new Uri($url));
|
||||
}
|
||||
|
||||
foreach ($this->defaultHeaders as $key => $value) {
|
||||
$explicitHeaderExists = false;
|
||||
foreach (\array_keys($headers) as $headerKey) {
|
||||
if (\strcasecmp($headerKey, $key) === 0) {
|
||||
$explicitHeaderExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$explicitHeaderExists) {
|
||||
$headers[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->transaction->send(
|
||||
new Request($method, $url, $headers, $body, $this->protocolVersion)
|
||||
);
|
||||
}
|
||||
}
|
||||
27
vendor/react/http/src/Client/Client.php
vendored
Normal file
27
vendor/react/http/src/Client/Client.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Client;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use React\Http\Io\ClientConnectionManager;
|
||||
use React\Http\Io\ClientRequestStream;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/** @var ClientConnectionManager */
|
||||
private $connectionManager;
|
||||
|
||||
public function __construct(ClientConnectionManager $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
|
||||
/** @return ClientRequestStream */
|
||||
public function request(RequestInterface $request)
|
||||
{
|
||||
return new ClientRequestStream($this->connectionManager, $request);
|
||||
}
|
||||
}
|
||||
351
vendor/react/http/src/HttpServer.php
vendored
Normal file
351
vendor/react/http/src/HttpServer.php
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Io\IniUtil;
|
||||
use React\Http\Io\MiddlewareRunner;
|
||||
use React\Http\Io\StreamingServer;
|
||||
use React\Http\Middleware\LimitConcurrentRequestsMiddleware;
|
||||
use React\Http\Middleware\StreamingRequestMiddleware;
|
||||
use React\Http\Middleware\RequestBodyBufferMiddleware;
|
||||
use React\Http\Middleware\RequestBodyParserMiddleware;
|
||||
use React\Socket\ServerInterface;
|
||||
|
||||
/**
|
||||
* The `React\Http\HttpServer` class is responsible for handling incoming connections and then
|
||||
* processing each incoming HTTP request.
|
||||
*
|
||||
* When a complete HTTP request has been received, it will invoke the given
|
||||
* request handler function. This request handler function needs to be passed to
|
||||
* the constructor and will be invoked with the respective [request](#server-request)
|
||||
* object and expects a [response](#server-response) object in return:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
|
||||
* return new React\Http\Message\Response(
|
||||
* React\Http\Message\Response::STATUS_OK,
|
||||
* array(
|
||||
* 'Content-Type' => 'text/plain'
|
||||
* ),
|
||||
* "Hello World!\n"
|
||||
* );
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Each incoming HTTP request message is always represented by the
|
||||
* [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
|
||||
* see also following [request](#server-request) chapter for more details.
|
||||
*
|
||||
* Each outgoing HTTP response message is always represented by the
|
||||
* [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
|
||||
* see also following [response](#server-response) chapter for more details.
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* In order to start listening for any incoming connections, the `HttpServer` needs
|
||||
* to be attached to an instance of
|
||||
* [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface)
|
||||
* through the [`listen()`](#listen) method as described in the following
|
||||
* chapter. In its most simple form, you can attach this to a
|
||||
* [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* in order to start a plaintext HTTP server like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer($handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('0.0.0.0:8080');
|
||||
* $http->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also the [`listen()`](#listen) method and
|
||||
* [hello world server example](../examples/51-server-hello-world.php)
|
||||
* for more details.
|
||||
*
|
||||
* By default, the `HttpServer` buffers and parses the complete incoming HTTP
|
||||
* request in memory. It will invoke the given request handler function when the
|
||||
* complete request headers and request body has been received. This means the
|
||||
* [request](#server-request) object passed to your request handler function will be
|
||||
* fully compatible with PSR-7 (http-message). This provides sane defaults for
|
||||
* 80% of the use cases and is the recommended way to use this library unless
|
||||
* you're sure you know what you're doing.
|
||||
*
|
||||
* On the other hand, buffering complete HTTP requests in memory until they can
|
||||
* be processed by your request handler function means that this class has to
|
||||
* employ a number of limits to avoid consuming too much memory. In order to
|
||||
* take the more advanced configuration out your hand, it respects setting from
|
||||
* your [`php.ini`](https://www.php.net/manual/en/ini.core.php) to apply its
|
||||
* default settings. This is a list of PHP settings this class respects with
|
||||
* their respective default values:
|
||||
*
|
||||
* ```
|
||||
* memory_limit 128M
|
||||
* post_max_size 8M // capped at 64K
|
||||
*
|
||||
* enable_post_data_reading 1
|
||||
* max_input_nesting_level 64
|
||||
* max_input_vars 1000
|
||||
*
|
||||
* file_uploads 1
|
||||
* upload_max_filesize 2M
|
||||
* max_file_uploads 20
|
||||
* ```
|
||||
*
|
||||
* In particular, the `post_max_size` setting limits how much memory a single
|
||||
* HTTP request is allowed to consume while buffering its request body. This
|
||||
* needs to be limited because the server can process a large number of requests
|
||||
* concurrently, so the server may potentially consume a large amount of memory
|
||||
* otherwise. To support higher concurrency by default, this value is capped
|
||||
* at `64K`. If you assign a higher value, it will only allow `64K` by default.
|
||||
* If a request exceeds this limit, its request body will be ignored and it will
|
||||
* be processed like a request with no request body at all. See below for
|
||||
* explicit configuration to override this setting.
|
||||
*
|
||||
* By default, this class will try to avoid consuming more than half of your
|
||||
* `memory_limit` for buffering multiple concurrent HTTP requests. As such, with
|
||||
* the above default settings of `128M` max, it will try to consume no more than
|
||||
* `64M` for buffering multiple concurrent HTTP requests. As a consequence, it
|
||||
* will limit the concurrency to `1024` HTTP requests with the above defaults.
|
||||
*
|
||||
* It is imperative that you assign reasonable values to your PHP ini settings.
|
||||
* It is usually recommended to not support buffering incoming HTTP requests
|
||||
* with a large HTTP request body (e.g. large file uploads). If you want to
|
||||
* increase this buffer size, you will have to also increase the total memory
|
||||
* limit to allow for more concurrent requests (set `memory_limit 512M` or more)
|
||||
* or explicitly limit concurrency.
|
||||
*
|
||||
* In order to override the above buffering defaults, you can configure the
|
||||
* `HttpServer` explicitly. You can use the
|
||||
* [`LimitConcurrentRequestsMiddleware`](#limitconcurrentrequestsmiddleware) and
|
||||
* [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
|
||||
* to explicitly configure the total number of requests that can be handled at
|
||||
* once like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
|
||||
* new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
|
||||
* new React\Http\Middleware\RequestBodyParserMiddleware(),
|
||||
* $handler
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* In this example, we allow processing up to 100 concurrent requests at once
|
||||
* and each request can buffer up to `2M`. This means you may have to keep a
|
||||
* maximum of `200M` of memory for incoming request body buffers. Accordingly,
|
||||
* you need to adjust the `memory_limit` ini setting to allow for these buffers
|
||||
* plus your actual application logic memory requirements (think `512M` or more).
|
||||
*
|
||||
* > Internally, this class automatically assigns these middleware handlers
|
||||
* automatically when no [`StreamingRequestMiddleware`](#streamingrequestmiddleware)
|
||||
* is given. Accordingly, you can use this example to override all default
|
||||
* settings to implement custom limits.
|
||||
*
|
||||
* As an alternative to buffering the complete request body in memory, you can
|
||||
* also use a streaming approach where only small chunks of data have to be kept
|
||||
* in memory:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* $handler
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* In this case, it will invoke the request handler function once the HTTP
|
||||
* request headers have been received, i.e. before receiving the potentially
|
||||
* much larger HTTP request body. This means the [request](#server-request) passed to
|
||||
* your request handler function may not be fully compatible with PSR-7. This is
|
||||
* specifically designed to help with more advanced use cases where you want to
|
||||
* have full control over consuming the incoming HTTP request body and
|
||||
* concurrency settings. See also [streaming incoming request](#streaming-incoming-request)
|
||||
* below for more details.
|
||||
*
|
||||
* > Changelog v1.5.0: This class has been renamed to `HttpServer` from the
|
||||
* previous `Server` class in order to avoid any ambiguities.
|
||||
* The previous name has been deprecated and should not be used anymore.
|
||||
*/
|
||||
final class HttpServer extends EventEmitter
|
||||
{
|
||||
/**
|
||||
* The maximum buffer size used for each request.
|
||||
*
|
||||
* This needs to be limited because the server can process a large number of
|
||||
* requests concurrently, so the server may potentially consume a large
|
||||
* amount of memory otherwise.
|
||||
*
|
||||
* See `RequestBodyBufferMiddleware` to override this setting.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const MAXIMUM_BUFFER_SIZE = 65536; // 64 KiB
|
||||
|
||||
/**
|
||||
* @var StreamingServer
|
||||
*/
|
||||
private $streamingServer;
|
||||
|
||||
/**
|
||||
* Creates an HTTP server that invokes the given callback for each incoming HTTP request
|
||||
*
|
||||
* In order to process any connections, the server needs to be attached to an
|
||||
* instance of `React\Socket\ServerInterface` which emits underlying streaming
|
||||
* connections in order to then parse incoming data as HTTP.
|
||||
* See also [listen()](#listen) for more details.
|
||||
*
|
||||
* @param callable|LoopInterface $requestHandlerOrLoop
|
||||
* @param callable[] ...$requestHandler
|
||||
* @see self::listen()
|
||||
*/
|
||||
public function __construct($requestHandlerOrLoop)
|
||||
{
|
||||
$requestHandlers = \func_get_args();
|
||||
if (reset($requestHandlers) instanceof LoopInterface) {
|
||||
$loop = \array_shift($requestHandlers);
|
||||
} else {
|
||||
$loop = Loop::get();
|
||||
}
|
||||
|
||||
$requestHandlersCount = \count($requestHandlers);
|
||||
if ($requestHandlersCount === 0 || \count(\array_filter($requestHandlers, 'is_callable')) < $requestHandlersCount) {
|
||||
throw new \InvalidArgumentException('Invalid request handler given');
|
||||
}
|
||||
|
||||
$streaming = false;
|
||||
foreach ((array) $requestHandlers as $handler) {
|
||||
if ($handler instanceof StreamingRequestMiddleware) {
|
||||
$streaming = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$middleware = array();
|
||||
if (!$streaming) {
|
||||
$maxSize = $this->getMaxRequestSize();
|
||||
$concurrency = $this->getConcurrentRequestsLimit(\ini_get('memory_limit'), $maxSize);
|
||||
if ($concurrency !== null) {
|
||||
$middleware[] = new LimitConcurrentRequestsMiddleware($concurrency);
|
||||
}
|
||||
$middleware[] = new RequestBodyBufferMiddleware($maxSize);
|
||||
// Checking for an empty string because that is what a boolean
|
||||
// false is returned as by ini_get depending on the PHP version.
|
||||
// @link http://php.net/manual/en/ini.core.php#ini.enable-post-data-reading
|
||||
// @link http://php.net/manual/en/function.ini-get.php#refsect1-function.ini-get-notes
|
||||
// @link https://3v4l.org/qJtsa
|
||||
$enablePostDataReading = \ini_get('enable_post_data_reading');
|
||||
if ($enablePostDataReading !== '') {
|
||||
$middleware[] = new RequestBodyParserMiddleware();
|
||||
}
|
||||
}
|
||||
|
||||
$middleware = \array_merge($middleware, $requestHandlers);
|
||||
|
||||
/**
|
||||
* Filter out any configuration middleware, no need to run requests through something that isn't
|
||||
* doing anything with the request.
|
||||
*/
|
||||
$middleware = \array_filter($middleware, function ($handler) {
|
||||
return !($handler instanceof StreamingRequestMiddleware);
|
||||
});
|
||||
|
||||
$this->streamingServer = new StreamingServer($loop, new MiddlewareRunner($middleware));
|
||||
|
||||
$that = $this;
|
||||
$this->streamingServer->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for HTTP requests on the given socket server instance
|
||||
*
|
||||
* The given [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface)
|
||||
* is responsible for emitting the underlying streaming connections. This
|
||||
* HTTP server needs to be attached to it in order to process any
|
||||
* connections and pase incoming streaming data as incoming HTTP request
|
||||
* messages. In its most common form, you can attach this to a
|
||||
* [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* in order to start a plaintext HTTP server like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer($handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('0.0.0.0:8080');
|
||||
* $http->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also [hello world server example](../examples/51-server-hello-world.php)
|
||||
* for more details.
|
||||
*
|
||||
* This example will start listening for HTTP requests on the alternative
|
||||
* HTTP port `8080` on all interfaces (publicly). As an alternative, it is
|
||||
* very common to use a reverse proxy and let this HTTP server listen on the
|
||||
* localhost (loopback) interface only by using the listen address
|
||||
* `127.0.0.1:8080` instead. This way, you host your application(s) on the
|
||||
* default HTTP port `80` and only route specific requests to this HTTP
|
||||
* server.
|
||||
*
|
||||
* Likewise, it's usually recommended to use a reverse proxy setup to accept
|
||||
* secure HTTPS requests on default HTTPS port `443` (TLS termination) and
|
||||
* only route plaintext requests to this HTTP server. As an alternative, you
|
||||
* can also accept secure HTTPS requests with this HTTP server by attaching
|
||||
* this to a [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* using a secure TLS listen address, a certificate file and optional
|
||||
* `passphrase` like this:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer($handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('tls://0.0.0.0:8443', array(
|
||||
* 'tls' => array(
|
||||
* 'local_cert' => __DIR__ . '/localhost.pem'
|
||||
* )
|
||||
* ));
|
||||
* $http->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also [hello world HTTPS example](../examples/61-server-hello-world-https.php)
|
||||
* for more details.
|
||||
*
|
||||
* @param ServerInterface $socket
|
||||
*/
|
||||
public function listen(ServerInterface $socket)
|
||||
{
|
||||
$this->streamingServer->listen($socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $memory_limit
|
||||
* @param string $post_max_size
|
||||
* @return ?int
|
||||
*/
|
||||
private function getConcurrentRequestsLimit($memory_limit, $post_max_size)
|
||||
{
|
||||
if ($memory_limit == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$availableMemory = IniUtil::iniSizeToBytes($memory_limit) / 2;
|
||||
$concurrentRequests = (int) \ceil($availableMemory / IniUtil::iniSizeToBytes($post_max_size));
|
||||
|
||||
return $concurrentRequests;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?string $post_max_size
|
||||
* @return int
|
||||
*/
|
||||
private function getMaxRequestSize($post_max_size = null)
|
||||
{
|
||||
$maxSize = IniUtil::iniSizeToBytes($post_max_size === null ? \ini_get('post_max_size') : $post_max_size);
|
||||
|
||||
return ($maxSize === 0 || $maxSize >= self::MAXIMUM_BUFFER_SIZE) ? self::MAXIMUM_BUFFER_SIZE : $maxSize;
|
||||
}
|
||||
}
|
||||
172
vendor/react/http/src/Io/AbstractMessage.php
vendored
Normal file
172
vendor/react/http/src/Io/AbstractMessage.php
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Abstract HTTP message base class (PSR-7)
|
||||
*
|
||||
* @internal
|
||||
* @see MessageInterface
|
||||
*/
|
||||
abstract class AbstractMessage implements MessageInterface
|
||||
{
|
||||
/**
|
||||
* [Internal] Regex used to match all request header fields into an array, thanks to @kelunik for checking the HTTP specs and coming up with this regex
|
||||
*
|
||||
* @internal
|
||||
* @var string
|
||||
*/
|
||||
const REGEX_HEADERS = '/^([^()<>@,;:\\\"\/\[\]?={}\x00-\x20\x7F]++):[\x20\x09]*+((?:[\x20\x09]*+[\x21-\x7E\x80-\xFF]++)*+)[\x20\x09]*+[\r]?+\n/m';
|
||||
|
||||
/** @var array<string,string[]> */
|
||||
private $headers = array();
|
||||
|
||||
/** @var array<string,string> */
|
||||
private $headerNamesLowerCase = array();
|
||||
|
||||
/** @var string */
|
||||
private $protocolVersion;
|
||||
|
||||
/** @var StreamInterface */
|
||||
private $body;
|
||||
|
||||
/**
|
||||
* @param string $protocolVersion
|
||||
* @param array<string,string|string[]> $headers
|
||||
* @param StreamInterface $body
|
||||
*/
|
||||
protected function __construct($protocolVersion, array $headers, StreamInterface $body)
|
||||
{
|
||||
foreach ($headers as $name => $value) {
|
||||
if ($value !== array()) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as &$one) {
|
||||
$one = (string) $one;
|
||||
}
|
||||
} else {
|
||||
$value = array((string) $value);
|
||||
}
|
||||
|
||||
$lower = \strtolower($name);
|
||||
if (isset($this->headerNamesLowerCase[$lower])) {
|
||||
$value = \array_merge($this->headers[$this->headerNamesLowerCase[$lower]], $value);
|
||||
unset($this->headers[$this->headerNamesLowerCase[$lower]]);
|
||||
}
|
||||
|
||||
$this->headers[$name] = $value;
|
||||
$this->headerNamesLowerCase[$lower] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$this->protocolVersion = (string) $protocolVersion;
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function getProtocolVersion()
|
||||
{
|
||||
return $this->protocolVersion;
|
||||
}
|
||||
|
||||
public function withProtocolVersion($version)
|
||||
{
|
||||
if ((string) $version === $this->protocolVersion) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
$message->protocolVersion = (string) $version;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function getHeaders()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function hasHeader($name)
|
||||
{
|
||||
return isset($this->headerNamesLowerCase[\strtolower($name)]);
|
||||
}
|
||||
|
||||
public function getHeader($name)
|
||||
{
|
||||
$lower = \strtolower($name);
|
||||
return isset($this->headerNamesLowerCase[$lower]) ? $this->headers[$this->headerNamesLowerCase[$lower]] : array();
|
||||
}
|
||||
|
||||
public function getHeaderLine($name)
|
||||
{
|
||||
return \implode(', ', $this->getHeader($name));
|
||||
}
|
||||
|
||||
public function withHeader($name, $value)
|
||||
{
|
||||
if ($value === array()) {
|
||||
return $this->withoutHeader($name);
|
||||
} elseif (\is_array($value)) {
|
||||
foreach ($value as &$one) {
|
||||
$one = (string) $one;
|
||||
}
|
||||
} else {
|
||||
$value = array((string) $value);
|
||||
}
|
||||
|
||||
$lower = \strtolower($name);
|
||||
if (isset($this->headerNamesLowerCase[$lower]) && $this->headerNamesLowerCase[$lower] === (string) $name && $this->headers[$this->headerNamesLowerCase[$lower]] === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
if (isset($message->headerNamesLowerCase[$lower])) {
|
||||
unset($message->headers[$message->headerNamesLowerCase[$lower]]);
|
||||
}
|
||||
|
||||
$message->headers[$name] = $value;
|
||||
$message->headerNamesLowerCase[$lower] = $name;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function withAddedHeader($name, $value)
|
||||
{
|
||||
if ($value === array()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->withHeader($name, \array_merge($this->getHeader($name), \is_array($value) ? $value : array($value)));
|
||||
}
|
||||
|
||||
public function withoutHeader($name)
|
||||
{
|
||||
$lower = \strtolower($name);
|
||||
if (!isset($this->headerNamesLowerCase[$lower])) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
unset($message->headers[$message->headerNamesLowerCase[$lower]], $message->headerNamesLowerCase[$lower]);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function withBody(StreamInterface $body)
|
||||
{
|
||||
if ($body === $this->body) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$message = clone $this;
|
||||
$message->body = $body;
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
156
vendor/react/http/src/Io/AbstractRequest.php
vendored
Normal file
156
vendor/react/http/src/Io/AbstractRequest.php
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\Http\Message\Uri;
|
||||
|
||||
/**
|
||||
* [Internal] Abstract HTTP request base class (PSR-7)
|
||||
*
|
||||
* @internal
|
||||
* @see RequestInterface
|
||||
*/
|
||||
abstract class AbstractRequest extends AbstractMessage implements RequestInterface
|
||||
{
|
||||
/** @var ?string */
|
||||
private $requestTarget;
|
||||
|
||||
/** @var string */
|
||||
private $method;
|
||||
|
||||
/** @var UriInterface */
|
||||
private $uri;
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string|UriInterface $uri
|
||||
* @param array<string,string|string[]> $headers
|
||||
* @param StreamInterface $body
|
||||
* @param string unknown $protocolVersion
|
||||
*/
|
||||
protected function __construct(
|
||||
$method,
|
||||
$uri,
|
||||
array $headers,
|
||||
StreamInterface $body,
|
||||
$protocolVersion
|
||||
) {
|
||||
if (\is_string($uri)) {
|
||||
$uri = new Uri($uri);
|
||||
} elseif (!$uri instanceof UriInterface) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Argument #2 ($uri) expected string|Psr\Http\Message\UriInterface'
|
||||
);
|
||||
}
|
||||
|
||||
// assign default `Host` request header from URI unless already given explicitly
|
||||
$host = $uri->getHost();
|
||||
if ($host !== '') {
|
||||
foreach ($headers as $name => $value) {
|
||||
if (\strtolower($name) === 'host' && $value !== array()) {
|
||||
$host = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($host !== '') {
|
||||
$port = $uri->getPort();
|
||||
if ($port !== null && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
|
||||
$host .= ':' . $port;
|
||||
}
|
||||
|
||||
$headers = array('Host' => $host) + $headers;
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($protocolVersion, $headers, $body);
|
||||
|
||||
$this->method = $method;
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
public function getRequestTarget()
|
||||
{
|
||||
if ($this->requestTarget !== null) {
|
||||
return $this->requestTarget;
|
||||
}
|
||||
|
||||
$target = $this->uri->getPath();
|
||||
if ($target === '') {
|
||||
$target = '/';
|
||||
}
|
||||
if (($query = $this->uri->getQuery()) !== '') {
|
||||
$target .= '?' . $query;
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
public function withRequestTarget($requestTarget)
|
||||
{
|
||||
if ((string) $requestTarget === $this->requestTarget) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->requestTarget = (string) $requestTarget;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function withMethod($method)
|
||||
{
|
||||
if ((string) $method === $this->method) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->method = (string) $method;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
public function getUri()
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function withUri(UriInterface $uri, $preserveHost = false)
|
||||
{
|
||||
if ($uri === $this->uri) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$request = clone $this;
|
||||
$request->uri = $uri;
|
||||
|
||||
$host = $uri->getHost();
|
||||
$port = $uri->getPort();
|
||||
if ($port !== null && $host !== '' && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
|
||||
$host .= ':' . $port;
|
||||
}
|
||||
|
||||
// update `Host` request header if URI contains a new host and `$preserveHost` is false
|
||||
if ($host !== '' && (!$preserveHost || $request->getHeaderLine('Host') === '')) {
|
||||
// first remove all headers before assigning `Host` header to ensure it always comes first
|
||||
foreach (\array_keys($request->getHeaders()) as $name) {
|
||||
$request = $request->withoutHeader($name);
|
||||
}
|
||||
|
||||
// add `Host` header first, then all other original headers
|
||||
$request = $request->withHeader('Host', $host);
|
||||
foreach ($this->withoutHeader('Host')->getHeaders() as $name => $value) {
|
||||
$request = $request->withHeader($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
179
vendor/react/http/src/Io/BufferedBody.php
vendored
Normal file
179
vendor/react/http/src/Io/BufferedBody.php
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] PSR-7 message body implementation using an in-memory buffer
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BufferedBody implements StreamInterface
|
||||
{
|
||||
private $buffer = '';
|
||||
private $position = 0;
|
||||
private $closed = false;
|
||||
|
||||
/**
|
||||
* @param string $buffer
|
||||
*/
|
||||
public function __construct($buffer)
|
||||
{
|
||||
$this->buffer = $buffer;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->seek(0);
|
||||
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->buffer = '';
|
||||
$this->position = 0;
|
||||
$this->closed = true;
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
$this->close();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->closed ? null : \strlen($this->buffer);
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to tell position of closed stream');
|
||||
}
|
||||
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return $this->position >= \strlen($this->buffer);
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = \SEEK_SET)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to seek on closed stream');
|
||||
}
|
||||
|
||||
$old = $this->position;
|
||||
|
||||
if ($whence === \SEEK_SET) {
|
||||
$this->position = $offset;
|
||||
} elseif ($whence === \SEEK_CUR) {
|
||||
$this->position += $offset;
|
||||
} elseif ($whence === \SEEK_END) {
|
||||
$this->position = \strlen($this->buffer) + $offset;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid seek mode given');
|
||||
}
|
||||
|
||||
if (!\is_int($this->position) || $this->position < 0) {
|
||||
$this->position = $old;
|
||||
throw new \RuntimeException('Unable to seek to position');
|
||||
}
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to write to closed stream');
|
||||
}
|
||||
|
||||
if ($string === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($this->position > 0 && !isset($this->buffer[$this->position - 1])) {
|
||||
$this->buffer = \str_pad($this->buffer, $this->position, "\0");
|
||||
}
|
||||
|
||||
$len = \strlen($string);
|
||||
$this->buffer = \substr($this->buffer, 0, $this->position) . $string . \substr($this->buffer, $this->position + $len);
|
||||
$this->position += $len;
|
||||
|
||||
return $len;
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to read from closed stream');
|
||||
}
|
||||
|
||||
if ($length < 1) {
|
||||
throw new \InvalidArgumentException('Invalid read length given');
|
||||
}
|
||||
|
||||
if ($this->position + $length > \strlen($this->buffer)) {
|
||||
$length = \strlen($this->buffer) - $this->position;
|
||||
}
|
||||
|
||||
if (!isset($this->buffer[$this->position])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$pos = $this->position;
|
||||
$this->position += $length;
|
||||
|
||||
return \substr($this->buffer, $pos, $length);
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
if ($this->closed) {
|
||||
throw new \RuntimeException('Unable to read from closed stream');
|
||||
}
|
||||
|
||||
if (!isset($this->buffer[$this->position])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$pos = $this->position;
|
||||
$this->position = \strlen($this->buffer);
|
||||
|
||||
return \substr($this->buffer, $pos);
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return $key === null ? array() : null;
|
||||
}
|
||||
}
|
||||
175
vendor/react/http/src/Io/ChunkedDecoder.php
vendored
Normal file
175
vendor/react/http/src/Io/ChunkedDecoder.php
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* [Internal] Decodes "Transfer-Encoding: chunked" from given stream and returns only payload data.
|
||||
*
|
||||
* This is used internally to decode incoming requests with this encoding.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ChunkedDecoder extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
const MAX_CHUNK_HEADER_SIZE = 1024;
|
||||
|
||||
private $closed = false;
|
||||
private $input;
|
||||
private $buffer = '';
|
||||
private $chunkSize = 0;
|
||||
private $transferredSize = 0;
|
||||
private $headerCompleted = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->buffer = '';
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->handleError(new Exception('Unexpected end event'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
while ($this->buffer !== '') {
|
||||
if (!$this->headerCompleted) {
|
||||
$positionCrlf = \strpos($this->buffer, static::CRLF);
|
||||
|
||||
if ($positionCrlf === false) {
|
||||
// Header shouldn't be bigger than 1024 bytes
|
||||
if (isset($this->buffer[static::MAX_CHUNK_HEADER_SIZE])) {
|
||||
$this->handleError(new Exception('Chunk header size inclusive extension bigger than' . static::MAX_CHUNK_HEADER_SIZE. ' bytes'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$header = \strtolower((string)\substr($this->buffer, 0, $positionCrlf));
|
||||
$hexValue = $header;
|
||||
|
||||
if (\strpos($header, ';') !== false) {
|
||||
$array = \explode(';', $header);
|
||||
$hexValue = $array[0];
|
||||
}
|
||||
|
||||
if ($hexValue !== '') {
|
||||
$hexValue = \ltrim(\trim($hexValue), "0");
|
||||
if ($hexValue === '') {
|
||||
$hexValue = "0";
|
||||
}
|
||||
}
|
||||
|
||||
$this->chunkSize = @\hexdec($hexValue);
|
||||
if (!\is_int($this->chunkSize) || \dechex($this->chunkSize) !== $hexValue) {
|
||||
$this->handleError(new Exception($hexValue . ' is not a valid hexadecimal number'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->buffer = (string)\substr($this->buffer, $positionCrlf + 2);
|
||||
$this->headerCompleted = true;
|
||||
if ($this->buffer === '') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$chunk = (string)\substr($this->buffer, 0, $this->chunkSize - $this->transferredSize);
|
||||
|
||||
if ($chunk !== '') {
|
||||
$this->transferredSize += \strlen($chunk);
|
||||
$this->emit('data', array($chunk));
|
||||
$this->buffer = (string)\substr($this->buffer, \strlen($chunk));
|
||||
}
|
||||
|
||||
$positionCrlf = \strpos($this->buffer, static::CRLF);
|
||||
|
||||
if ($positionCrlf === 0) {
|
||||
if ($this->chunkSize === 0) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
return;
|
||||
}
|
||||
$this->chunkSize = 0;
|
||||
$this->headerCompleted = false;
|
||||
$this->transferredSize = 0;
|
||||
$this->buffer = (string)\substr($this->buffer, 2);
|
||||
} elseif ($this->chunkSize === 0) {
|
||||
// end chunk received, skip all trailer data
|
||||
$this->buffer = (string)\substr($this->buffer, $positionCrlf);
|
||||
}
|
||||
|
||||
if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) > 2) {
|
||||
// the first 2 characters are not CRLF, send error event
|
||||
$this->handleError(new Exception('Chunk does not end with a CRLF'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($positionCrlf !== 0 && \strlen($this->buffer) < 2) {
|
||||
// No CRLF found, wait for additional data which could be a CRLF
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
vendor/react/http/src/Io/ChunkedEncoder.php
vendored
Normal file
92
vendor/react/http/src/Io/ChunkedEncoder.php
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Encodes given payload stream with "Transfer-Encoding: chunked" and emits encoded data
|
||||
*
|
||||
* This is used internally to encode outgoing requests with this encoding.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ChunkedEncoder extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return Util::pipe($this, $dest, $options);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if ($data !== '') {
|
||||
$this->emit('data', array(
|
||||
\dechex(\strlen($data)) . "\r\n" . $data . "\r\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('data', array("0\r\n\r\n"));
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
137
vendor/react/http/src/Io/ClientConnectionManager.php
vendored
Normal file
137
vendor/react/http/src/Io/ClientConnectionManager.php
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\TimerInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Manages outgoing HTTP connections for the HTTP client
|
||||
*
|
||||
* @internal
|
||||
* @final
|
||||
*/
|
||||
class ClientConnectionManager
|
||||
{
|
||||
/** @var ConnectorInterface */
|
||||
private $connector;
|
||||
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
|
||||
/** @var string[] */
|
||||
private $idleUris = array();
|
||||
|
||||
/** @var ConnectionInterface[] */
|
||||
private $idleConnections = array();
|
||||
|
||||
/** @var TimerInterface[] */
|
||||
private $idleTimers = array();
|
||||
|
||||
/** @var \Closure[] */
|
||||
private $idleStreamHandlers = array();
|
||||
|
||||
/** @var float */
|
||||
private $maximumTimeToKeepAliveIdleConnection = 0.001;
|
||||
|
||||
public function __construct(ConnectorInterface $connector, LoopInterface $loop)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PromiseInterface<ConnectionInterface>
|
||||
*/
|
||||
public function connect(UriInterface $uri)
|
||||
{
|
||||
$scheme = $uri->getScheme();
|
||||
if ($scheme !== 'https' && $scheme !== 'http') {
|
||||
return \React\Promise\reject(new \InvalidArgumentException(
|
||||
'Invalid request URL given'
|
||||
));
|
||||
}
|
||||
|
||||
$port = $uri->getPort();
|
||||
if ($port === null) {
|
||||
$port = $scheme === 'https' ? 443 : 80;
|
||||
}
|
||||
$uri = ($scheme === 'https' ? 'tls://' : '') . $uri->getHost() . ':' . $port;
|
||||
|
||||
// Reuse idle connection for same URI if available
|
||||
foreach ($this->idleConnections as $id => $connection) {
|
||||
if ($this->idleUris[$id] === $uri) {
|
||||
assert($this->idleStreamHandlers[$id] instanceof \Closure);
|
||||
$connection->removeListener('close', $this->idleStreamHandlers[$id]);
|
||||
$connection->removeListener('data', $this->idleStreamHandlers[$id]);
|
||||
$connection->removeListener('error', $this->idleStreamHandlers[$id]);
|
||||
|
||||
assert($this->idleTimers[$id] instanceof TimerInterface);
|
||||
$this->loop->cancelTimer($this->idleTimers[$id]);
|
||||
unset($this->idleUris[$id], $this->idleConnections[$id], $this->idleTimers[$id], $this->idleStreamHandlers[$id]);
|
||||
|
||||
return \React\Promise\resolve($connection);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new connection if no idle connection to same URI is available
|
||||
return $this->connector->connect($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hands back an idle connection to the connection manager for possible future reuse.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function keepAlive(UriInterface $uri, ConnectionInterface $connection)
|
||||
{
|
||||
$scheme = $uri->getScheme();
|
||||
assert($scheme === 'https' || $scheme === 'http');
|
||||
|
||||
$port = $uri->getPort();
|
||||
if ($port === null) {
|
||||
$port = $scheme === 'https' ? 443 : 80;
|
||||
}
|
||||
|
||||
$this->idleUris[] = ($scheme === 'https' ? 'tls://' : '') . $uri->getHost() . ':' . $port;
|
||||
$this->idleConnections[] = $connection;
|
||||
|
||||
$that = $this;
|
||||
$cleanUp = function () use ($connection, $that) {
|
||||
// call public method to support legacy PHP 5.3
|
||||
$that->cleanUpConnection($connection);
|
||||
};
|
||||
|
||||
// clean up and close connection when maximum time to keep-alive idle connection has passed
|
||||
$this->idleTimers[] = $this->loop->addTimer($this->maximumTimeToKeepAliveIdleConnection, $cleanUp);
|
||||
|
||||
// clean up and close connection when unexpected close/data/error event happens during idle time
|
||||
$this->idleStreamHandlers[] = $cleanUp;
|
||||
$connection->on('close', $cleanUp);
|
||||
$connection->on('data', $cleanUp);
|
||||
$connection->on('error', $cleanUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return void
|
||||
*/
|
||||
public function cleanUpConnection(ConnectionInterface $connection) // private (PHP 5.4+)
|
||||
{
|
||||
$id = \array_search($connection, $this->idleConnections, true);
|
||||
if ($id === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(\is_int($id));
|
||||
assert($this->idleTimers[$id] instanceof TimerInterface);
|
||||
$this->loop->cancelTimer($this->idleTimers[$id]);
|
||||
unset($this->idleUris[$id], $this->idleConnections[$id], $this->idleTimers[$id], $this->idleStreamHandlers[$id]);
|
||||
|
||||
$connection->close();
|
||||
}
|
||||
}
|
||||
16
vendor/react/http/src/Io/ClientRequestState.php
vendored
Normal file
16
vendor/react/http/src/Io/ClientRequestState.php
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
/** @internal */
|
||||
class ClientRequestState
|
||||
{
|
||||
/** @var int */
|
||||
public $numRequests = 0;
|
||||
|
||||
/** @var ?\React\Promise\PromiseInterface */
|
||||
public $pending = null;
|
||||
|
||||
/** @var ?\React\EventLoop\TimerInterface */
|
||||
public $timeout = null;
|
||||
}
|
||||
307
vendor/react/http/src/Io/ClientRequestStream.php
vendored
Normal file
307
vendor/react/http/src/Io/ClientRequestStream.php
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @event response
|
||||
* @event drain
|
||||
* @event error
|
||||
* @event close
|
||||
* @internal
|
||||
*/
|
||||
class ClientRequestStream extends EventEmitter implements WritableStreamInterface
|
||||
{
|
||||
const STATE_INIT = 0;
|
||||
const STATE_WRITING_HEAD = 1;
|
||||
const STATE_HEAD_WRITTEN = 2;
|
||||
const STATE_END = 3;
|
||||
|
||||
/** @var ClientConnectionManager */
|
||||
private $connectionManager;
|
||||
|
||||
/** @var RequestInterface */
|
||||
private $request;
|
||||
|
||||
/** @var ?ConnectionInterface */
|
||||
private $connection;
|
||||
|
||||
/** @var string */
|
||||
private $buffer = '';
|
||||
|
||||
private $responseFactory;
|
||||
private $state = self::STATE_INIT;
|
||||
private $ended = false;
|
||||
|
||||
private $pendingWrites = '';
|
||||
|
||||
public function __construct(ClientConnectionManager $connectionManager, RequestInterface $request)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return self::STATE_END > $this->state && !$this->ended;
|
||||
}
|
||||
|
||||
private function writeHead()
|
||||
{
|
||||
$this->state = self::STATE_WRITING_HEAD;
|
||||
|
||||
$expected = 0;
|
||||
$headers = "{$this->request->getMethod()} {$this->request->getRequestTarget()} HTTP/{$this->request->getProtocolVersion()}\r\n";
|
||||
foreach ($this->request->getHeaders() as $name => $values) {
|
||||
if (\strpos($name, ':') !== false) {
|
||||
$expected = -1;
|
||||
break;
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
$headers .= "$name: $value\r\n";
|
||||
++$expected;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array $m legacy PHP 5.3 only */
|
||||
if (!\preg_match('#^\S+ \S+ HTTP/1\.[01]\r\n#m', $headers) || \substr_count($headers, "\n") !== ($expected + 1) || (\PHP_VERSION_ID >= 50400 ? \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers) : \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers, $m)) !== $expected) {
|
||||
$this->closeError(new \InvalidArgumentException('Unable to send request with invalid request headers'));
|
||||
return;
|
||||
}
|
||||
|
||||
$connectionRef = &$this->connection;
|
||||
$stateRef = &$this->state;
|
||||
$pendingWrites = &$this->pendingWrites;
|
||||
$that = $this;
|
||||
|
||||
$promise = $this->connectionManager->connect($this->request->getUri());
|
||||
$promise->then(
|
||||
function (ConnectionInterface $connection) use ($headers, &$connectionRef, &$stateRef, &$pendingWrites, $that) {
|
||||
$connectionRef = $connection;
|
||||
assert($connectionRef instanceof ConnectionInterface);
|
||||
|
||||
$connection->on('drain', array($that, 'handleDrain'));
|
||||
$connection->on('data', array($that, 'handleData'));
|
||||
$connection->on('end', array($that, 'handleEnd'));
|
||||
$connection->on('error', array($that, 'handleError'));
|
||||
$connection->on('close', array($that, 'close'));
|
||||
|
||||
$more = $connection->write($headers . "\r\n" . $pendingWrites);
|
||||
|
||||
assert($stateRef === ClientRequestStream::STATE_WRITING_HEAD);
|
||||
$stateRef = ClientRequestStream::STATE_HEAD_WRITTEN;
|
||||
|
||||
// clear pending writes if non-empty
|
||||
if ($pendingWrites !== '') {
|
||||
$pendingWrites = '';
|
||||
|
||||
if ($more) {
|
||||
$that->emit('drain');
|
||||
}
|
||||
}
|
||||
},
|
||||
array($this, 'closeError')
|
||||
);
|
||||
|
||||
$this->on('close', function() use ($promise) {
|
||||
$promise->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
if (!$this->isWritable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// write directly to connection stream if already available
|
||||
if (self::STATE_HEAD_WRITTEN <= $this->state) {
|
||||
return $this->connection->write($data);
|
||||
}
|
||||
|
||||
// otherwise buffer and try to establish connection
|
||||
$this->pendingWrites .= $data;
|
||||
if (self::STATE_WRITING_HEAD > $this->state) {
|
||||
$this->writeHead();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if (!$this->isWritable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $data) {
|
||||
$this->write($data);
|
||||
} else if (self::STATE_WRITING_HEAD > $this->state) {
|
||||
$this->writeHead();
|
||||
}
|
||||
|
||||
$this->ended = true;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleDrain()
|
||||
{
|
||||
$this->emit('drain');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
// buffer until double CRLF (or double LF for compatibility with legacy servers)
|
||||
$eom = \strpos($this->buffer, "\r\n\r\n");
|
||||
$eomLegacy = \strpos($this->buffer, "\n\n");
|
||||
if ($eom !== false || $eomLegacy !== false) {
|
||||
try {
|
||||
if ($eom !== false && ($eomLegacy === false || $eom < $eomLegacy)) {
|
||||
$response = Response::parseMessage(\substr($this->buffer, 0, $eom + 2));
|
||||
$bodyChunk = (string) \substr($this->buffer, $eom + 4);
|
||||
} else {
|
||||
$response = Response::parseMessage(\substr($this->buffer, 0, $eomLegacy + 1));
|
||||
$bodyChunk = (string) \substr($this->buffer, $eomLegacy + 2);
|
||||
}
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
$this->closeError($exception);
|
||||
return;
|
||||
}
|
||||
|
||||
// response headers successfully received => remove listeners for connection events
|
||||
$connection = $this->connection;
|
||||
assert($connection instanceof ConnectionInterface);
|
||||
$connection->removeListener('drain', array($this, 'handleDrain'));
|
||||
$connection->removeListener('data', array($this, 'handleData'));
|
||||
$connection->removeListener('end', array($this, 'handleEnd'));
|
||||
$connection->removeListener('error', array($this, 'handleError'));
|
||||
$connection->removeListener('close', array($this, 'close'));
|
||||
$this->connection = null;
|
||||
$this->buffer = '';
|
||||
|
||||
// take control over connection handling and check if we can reuse the connection once response body closes
|
||||
$that = $this;
|
||||
$request = $this->request;
|
||||
$connectionManager = $this->connectionManager;
|
||||
$successfulEndReceived = false;
|
||||
$input = $body = new CloseProtectionStream($connection);
|
||||
$input->on('close', function () use ($connection, $that, $connectionManager, $request, $response, &$successfulEndReceived) {
|
||||
// only reuse connection after successful response and both request and response allow keep alive
|
||||
if ($successfulEndReceived && $connection->isReadable() && $that->hasMessageKeepAliveEnabled($response) && $that->hasMessageKeepAliveEnabled($request)) {
|
||||
$connectionManager->keepAlive($request->getUri(), $connection);
|
||||
} else {
|
||||
$connection->close();
|
||||
}
|
||||
|
||||
$that->close();
|
||||
});
|
||||
|
||||
// determine length of response body
|
||||
$length = null;
|
||||
$code = $response->getStatusCode();
|
||||
if ($this->request->getMethod() === 'HEAD' || ($code >= 100 && $code < 200) || $code == Response::STATUS_NO_CONTENT || $code == Response::STATUS_NOT_MODIFIED) {
|
||||
$length = 0;
|
||||
} elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') {
|
||||
$body = new ChunkedDecoder($body);
|
||||
} elseif ($response->hasHeader('Content-Length')) {
|
||||
$length = (int) $response->getHeaderLine('Content-Length');
|
||||
}
|
||||
$response = $response->withBody($body = new ReadableBodyStream($body, $length));
|
||||
$body->on('end', function () use (&$successfulEndReceived) {
|
||||
$successfulEndReceived = true;
|
||||
});
|
||||
|
||||
// emit response with streaming response body (see `Sender`)
|
||||
$this->emit('response', array($response, $body));
|
||||
|
||||
// re-emit HTTP response body to trigger body parsing if parts of it are buffered
|
||||
if ($bodyChunk !== '') {
|
||||
$input->handleData($bodyChunk);
|
||||
} elseif ($length === 0) {
|
||||
$input->handleEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->closeError(new \RuntimeException(
|
||||
"Connection ended before receiving response"
|
||||
));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $error)
|
||||
{
|
||||
$this->closeError(new \RuntimeException(
|
||||
"An error occurred in the underlying stream",
|
||||
0,
|
||||
$error
|
||||
));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function closeError(\Exception $error)
|
||||
{
|
||||
if (self::STATE_END <= $this->state) {
|
||||
return;
|
||||
}
|
||||
$this->emit('error', array($error));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (self::STATE_END <= $this->state) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->state = self::STATE_END;
|
||||
$this->pendingWrites = '';
|
||||
$this->buffer = '';
|
||||
|
||||
if ($this->connection instanceof ConnectionInterface) {
|
||||
$this->connection->close();
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return bool
|
||||
* @link https://www.rfc-editor.org/rfc/rfc9112#section-9.3
|
||||
* @link https://www.rfc-editor.org/rfc/rfc7230#section-6.1
|
||||
*/
|
||||
public function hasMessageKeepAliveEnabled(MessageInterface $message)
|
||||
{
|
||||
// @link https://www.rfc-editor.org/rfc/rfc9110#section-7.6.1
|
||||
$connectionOptions = \array_map('trim', \explode(',', \strtolower($message->getHeaderLine('Connection'))));
|
||||
|
||||
if (\in_array('close', $connectionOptions, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($message->getProtocolVersion() === '1.1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (\in_array('keep-alive', $connectionOptions, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
54
vendor/react/http/src/Io/Clock.php
vendored
Normal file
54
vendor/react/http/src/Io/Clock.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* [internal] Clock source that returns current timestamp and memoize clock for same tick
|
||||
*
|
||||
* This is mostly used as an internal optimization to avoid unneeded syscalls to
|
||||
* get the current system time multiple times within the same loop tick. For the
|
||||
* purpose of the HTTP server, the clock is assumed to not change to a
|
||||
* significant degree within the same loop tick. If you need a high precision
|
||||
* clock source, you may want to use `\hrtime()` instead (PHP 7.3+).
|
||||
*
|
||||
* The API is modelled to resemble the PSR-20 `ClockInterface` (in draft at the
|
||||
* time of writing this), but uses a `float` return value for performance
|
||||
* reasons instead.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about for outside use.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Clock
|
||||
{
|
||||
/** @var LoopInterface $loop */
|
||||
private $loop;
|
||||
|
||||
/** @var ?float */
|
||||
private $now;
|
||||
|
||||
public function __construct(LoopInterface $loop)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/** @return float */
|
||||
public function now()
|
||||
{
|
||||
if ($this->now === null) {
|
||||
$this->now = \microtime(true);
|
||||
|
||||
// remember clock for current loop tick only and update on next tick
|
||||
$now =& $this->now;
|
||||
$this->loop->futureTick(function () use (&$now) {
|
||||
assert($now !== null);
|
||||
$now = null;
|
||||
});
|
||||
}
|
||||
|
||||
return $this->now;
|
||||
}
|
||||
}
|
||||
111
vendor/react/http/src/Io/CloseProtectionStream.php
vendored
Normal file
111
vendor/react/http/src/Io/CloseProtectionStream.php
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Protects a given stream from actually closing and only discards its incoming data instead.
|
||||
*
|
||||
* This is used internally to prevent the underlying connection from closing, so
|
||||
* that we can still send back a response over the same stream.
|
||||
*
|
||||
* @internal
|
||||
* */
|
||||
class CloseProtectionStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $paused = false;
|
||||
|
||||
/**
|
||||
* @param ReadableStreamInterface $input stream that will be discarded instead of closing it on an 'close' event.
|
||||
*/
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = true;
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = false;
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
// stop listening for incoming events
|
||||
$this->input->removeListener('data', array($this, 'handleData'));
|
||||
$this->input->removeListener('error', array($this, 'handleError'));
|
||||
$this->input->removeListener('end', array($this, 'handleEnd'));
|
||||
$this->input->removeListener('close', array($this, 'close'));
|
||||
|
||||
// resume the stream to ensure we discard everything from incoming connection
|
||||
if ($this->paused) {
|
||||
$this->paused = false;
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
}
|
||||
}
|
||||
142
vendor/react/http/src/Io/EmptyBodyStream.php
vendored
Normal file
142
vendor/react/http/src/Io/EmptyBodyStream.php
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Bridge between an empty StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
|
||||
*
|
||||
* This class is used in the server to represent an empty body stream of an
|
||||
* incoming response from the client. This is similar to the `HttpBodyStream`,
|
||||
* but is specifically designed for the common case of having an empty message
|
||||
* body.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `StreamInterface` and `ReadableStreamInterface` for more
|
||||
* details.
|
||||
*
|
||||
* @see HttpBodyStream
|
||||
* @see StreamInterface
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class EmptyBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
|
||||
{
|
||||
private $closed = false;
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function detach()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function eof()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getContents()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
}
|
||||
182
vendor/react/http/src/Io/HttpBodyStream.php
vendored
Normal file
182
vendor/react/http/src/Io/HttpBodyStream.php
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Bridge between StreamInterface from PSR-7 and ReadableStreamInterface from ReactPHP
|
||||
*
|
||||
* This class is used in the server to stream the body of an incoming response
|
||||
* from the client. This allows us to stream big amounts of data without having
|
||||
* to buffer this data. Similarly, this used to stream the body of an outgoing
|
||||
* request body to the client. The data will be sent directly to the client.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `StreamInterface` and `ReadableStreamInterface` for more
|
||||
* details.
|
||||
*
|
||||
* @see StreamInterface
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class HttpBodyStream extends EventEmitter implements StreamInterface, ReadableStreamInterface
|
||||
{
|
||||
public $input;
|
||||
private $closed = false;
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @param ReadableStreamInterface $input Stream data from $stream as a body of a PSR-7 object4
|
||||
* @param int|null $size size of the data body
|
||||
*/
|
||||
public function __construct(ReadableStreamInterface $input, $size)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->size = $size;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function detach()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function eof()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getContents()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
48
vendor/react/http/src/Io/IniUtil.php
vendored
Normal file
48
vendor/react/http/src/Io/IniUtil.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class IniUtil
|
||||
{
|
||||
/**
|
||||
* Convert a ini like size to a numeric size in bytes.
|
||||
*
|
||||
* @param string $size
|
||||
* @return int
|
||||
*/
|
||||
public static function iniSizeToBytes($size)
|
||||
{
|
||||
if (\is_numeric($size)) {
|
||||
return (int)$size;
|
||||
}
|
||||
|
||||
$suffix = \strtoupper(\substr($size, -1));
|
||||
$strippedSize = \substr($size, 0, -1);
|
||||
|
||||
if (!\is_numeric($strippedSize)) {
|
||||
throw new \InvalidArgumentException("$size is not a valid ini size");
|
||||
}
|
||||
|
||||
if ($strippedSize <= 0) {
|
||||
throw new \InvalidArgumentException("Expect $size to be higher isn't zero or lower");
|
||||
}
|
||||
|
||||
if ($suffix === 'K') {
|
||||
return $strippedSize * 1024;
|
||||
}
|
||||
if ($suffix === 'M') {
|
||||
return $strippedSize * 1024 * 1024;
|
||||
}
|
||||
if ($suffix === 'G') {
|
||||
return $strippedSize * 1024 * 1024 * 1024;
|
||||
}
|
||||
if ($suffix === 'T') {
|
||||
return $strippedSize * 1024 * 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
return (int)$size;
|
||||
}
|
||||
}
|
||||
108
vendor/react/http/src/Io/LengthLimitedStream.php
vendored
Normal file
108
vendor/react/http/src/Io/LengthLimitedStream.php
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Limits the amount of data the given stream can emit
|
||||
*
|
||||
* This is used internally to limit the size of the underlying connection stream
|
||||
* to the size defined by the "Content-Length" header of the incoming request.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LengthLimitedStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $stream;
|
||||
private $closed = false;
|
||||
private $transferredLength = 0;
|
||||
private $maxLength;
|
||||
|
||||
public function __construct(ReadableStreamInterface $stream, $maxLength)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->maxLength = $maxLength;
|
||||
|
||||
$this->stream->on('data', array($this, 'handleData'));
|
||||
$this->stream->on('end', array($this, 'handleEnd'));
|
||||
$this->stream->on('error', array($this, 'handleError'));
|
||||
$this->stream->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->stream->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->stream->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->stream->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
$this->stream->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if (($this->transferredLength + \strlen($data)) > $this->maxLength) {
|
||||
// Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
|
||||
$data = (string)\substr($data, 0, $this->maxLength - $this->transferredLength);
|
||||
}
|
||||
|
||||
if ($data !== '') {
|
||||
$this->transferredLength += \strlen($data);
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
if ($this->transferredLength === $this->maxLength) {
|
||||
// 'Content-Length' reached, stream will end
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
$this->stream->removeListener('data', array($this, 'handleData'));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->handleError(new \Exception('Unexpected end event'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
61
vendor/react/http/src/Io/MiddlewareRunner.php
vendored
Normal file
61
vendor/react/http/src/Io/MiddlewareRunner.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Middleware runner to expose an array of middleware request handlers as a single request handler callable
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MiddlewareRunner
|
||||
{
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
private $middleware;
|
||||
|
||||
/**
|
||||
* @param callable[] $middleware
|
||||
*/
|
||||
public function __construct(array $middleware)
|
||||
{
|
||||
$this->middleware = \array_values($middleware);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request)
|
||||
{
|
||||
if (empty($this->middleware)) {
|
||||
throw new \RuntimeException('No middleware to run');
|
||||
}
|
||||
|
||||
return $this->call($request, 0);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function call(ServerRequestInterface $request, $position)
|
||||
{
|
||||
// final request handler will be invoked without a next handler
|
||||
if (!isset($this->middleware[$position + 1])) {
|
||||
$handler = $this->middleware[$position];
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$next = function (ServerRequestInterface $request) use ($that, $position) {
|
||||
return $that->call($request, $position + 1);
|
||||
};
|
||||
|
||||
// invoke middleware request handler with next handler
|
||||
$handler = $this->middleware[$position];
|
||||
return $handler($request, $next);
|
||||
}
|
||||
}
|
||||
345
vendor/react/http/src/Io/MultipartParser.php
vendored
Normal file
345
vendor/react/http/src/Io/MultipartParser.php
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Parses a string body with "Content-Type: multipart/form-data" into structured data
|
||||
*
|
||||
* This is use internally to parse incoming request bodies into structured data
|
||||
* that resembles PHP's `$_POST` and `$_FILES` superglobals.
|
||||
*
|
||||
* @internal
|
||||
* @link https://tools.ietf.org/html/rfc7578
|
||||
* @link https://tools.ietf.org/html/rfc2046#section-5.1.1
|
||||
*/
|
||||
final class MultipartParser
|
||||
{
|
||||
/**
|
||||
* @var ServerRequestInterface|null
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $maxFileSize;
|
||||
|
||||
/**
|
||||
* Based on $maxInputVars and $maxFileUploads
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxMultipartBodyParts;
|
||||
|
||||
/**
|
||||
* ini setting "max_input_vars"
|
||||
*
|
||||
* Does not exist in PHP < 5.3.9 or HHVM, so assume PHP's default 1000 here.
|
||||
*
|
||||
* @var int
|
||||
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-vars
|
||||
*/
|
||||
private $maxInputVars = 1000;
|
||||
|
||||
/**
|
||||
* ini setting "max_input_nesting_level"
|
||||
*
|
||||
* Does not exist in HHVM, but assumes hard coded to 64 (PHP's default).
|
||||
*
|
||||
* @var int
|
||||
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-nesting-level
|
||||
*/
|
||||
private $maxInputNestingLevel = 64;
|
||||
|
||||
/**
|
||||
* ini setting "upload_max_filesize"
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $uploadMaxFilesize;
|
||||
|
||||
/**
|
||||
* ini setting "max_file_uploads"
|
||||
*
|
||||
* Additionally, setting "file_uploads = off" effectively sets this to zero.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxFileUploads;
|
||||
|
||||
private $multipartBodyPartCount = 0;
|
||||
private $postCount = 0;
|
||||
private $filesCount = 0;
|
||||
private $emptyCount = 0;
|
||||
private $cursor = 0;
|
||||
|
||||
/**
|
||||
* @param int|string|null $uploadMaxFilesize
|
||||
* @param int|null $maxFileUploads
|
||||
*/
|
||||
public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
|
||||
{
|
||||
$var = \ini_get('max_input_vars');
|
||||
if ($var !== false) {
|
||||
$this->maxInputVars = (int)$var;
|
||||
}
|
||||
$var = \ini_get('max_input_nesting_level');
|
||||
if ($var !== false) {
|
||||
$this->maxInputNestingLevel = (int)$var;
|
||||
}
|
||||
|
||||
if ($uploadMaxFilesize === null) {
|
||||
$uploadMaxFilesize = \ini_get('upload_max_filesize');
|
||||
}
|
||||
|
||||
$this->uploadMaxFilesize = IniUtil::iniSizeToBytes($uploadMaxFilesize);
|
||||
$this->maxFileUploads = $maxFileUploads === null ? (\ini_get('file_uploads') === '' ? 0 : (int)\ini_get('max_file_uploads')) : (int)$maxFileUploads;
|
||||
|
||||
$this->maxMultipartBodyParts = $this->maxInputVars + $this->maxFileUploads;
|
||||
}
|
||||
|
||||
public function parse(ServerRequestInterface $request)
|
||||
{
|
||||
$contentType = $request->getHeaderLine('content-type');
|
||||
if(!\preg_match('/boundary="?(.*?)"?$/', $contentType, $matches)) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
$this->request = $request;
|
||||
$this->parseBody('--' . $matches[1], (string)$request->getBody());
|
||||
|
||||
$request = $this->request;
|
||||
$this->request = null;
|
||||
$this->multipartBodyPartCount = 0;
|
||||
$this->cursor = 0;
|
||||
$this->postCount = 0;
|
||||
$this->filesCount = 0;
|
||||
$this->emptyCount = 0;
|
||||
$this->maxFileSize = null;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function parseBody($boundary, $buffer)
|
||||
{
|
||||
$len = \strlen($boundary);
|
||||
|
||||
// ignore everything before initial boundary (SHOULD be empty)
|
||||
$this->cursor = \strpos($buffer, $boundary . "\r\n");
|
||||
|
||||
while ($this->cursor !== false) {
|
||||
// search following boundary (preceded by newline)
|
||||
// ignore last if not followed by boundary (SHOULD end with "--")
|
||||
$this->cursor += $len + 2;
|
||||
$end = \strpos($buffer, "\r\n" . $boundary, $this->cursor);
|
||||
if ($end === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
// parse one part and continue searching for next
|
||||
$this->parsePart(\substr($buffer, $this->cursor, $end - $this->cursor));
|
||||
$this->cursor = $end;
|
||||
|
||||
if (++$this->multipartBodyPartCount > $this->maxMultipartBodyParts) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parsePart($chunk)
|
||||
{
|
||||
$pos = \strpos($chunk, "\r\n\r\n");
|
||||
if ($pos === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = $this->parseHeaders((string)substr($chunk, 0, $pos));
|
||||
$body = (string)\substr($chunk, $pos + 4);
|
||||
|
||||
if (!isset($headers['content-disposition'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->getParameterFromHeader($headers['content-disposition'], 'name');
|
||||
if ($name === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = $this->getParameterFromHeader($headers['content-disposition'], 'filename');
|
||||
if ($filename !== null) {
|
||||
$this->parseFile(
|
||||
$name,
|
||||
$filename,
|
||||
isset($headers['content-type'][0]) ? $headers['content-type'][0] : null,
|
||||
$body
|
||||
);
|
||||
} else {
|
||||
$this->parsePost($name, $body);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseFile($name, $filename, $contentType, $contents)
|
||||
{
|
||||
$file = $this->parseUploadedFile($filename, $contentType, $contents);
|
||||
if ($file === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request = $this->request->withUploadedFiles($this->extractPost(
|
||||
$this->request->getUploadedFiles(),
|
||||
$name,
|
||||
$file
|
||||
));
|
||||
}
|
||||
|
||||
private function parseUploadedFile($filename, $contentType, $contents)
|
||||
{
|
||||
$size = \strlen($contents);
|
||||
|
||||
// no file selected (zero size and empty filename)
|
||||
if ($size === 0 && $filename === '') {
|
||||
// ignore excessive number of empty file uploads
|
||||
if (++$this->emptyCount + $this->filesCount > $this->maxInputVars) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_NO_FILE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
// ignore excessive number of file uploads
|
||||
if (++$this->filesCount > $this->maxFileUploads) {
|
||||
return;
|
||||
}
|
||||
|
||||
// file exceeds "upload_max_filesize" ini setting
|
||||
if ($size > $this->uploadMaxFilesize) {
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_INI_SIZE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
// file exceeds MAX_FILE_SIZE value
|
||||
if ($this->maxFileSize !== null && $size > $this->maxFileSize) {
|
||||
return new UploadedFile(
|
||||
new BufferedBody(''),
|
||||
$size,
|
||||
\UPLOAD_ERR_FORM_SIZE,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
return new UploadedFile(
|
||||
new BufferedBody($contents),
|
||||
$size,
|
||||
\UPLOAD_ERR_OK,
|
||||
$filename,
|
||||
$contentType
|
||||
);
|
||||
}
|
||||
|
||||
private function parsePost($name, $value)
|
||||
{
|
||||
// ignore excessive number of post fields
|
||||
if (++$this->postCount > $this->maxInputVars) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request = $this->request->withParsedBody($this->extractPost(
|
||||
$this->request->getParsedBody(),
|
||||
$name,
|
||||
$value
|
||||
));
|
||||
|
||||
if (\strtoupper($name) === 'MAX_FILE_SIZE') {
|
||||
$this->maxFileSize = (int)$value;
|
||||
|
||||
if ($this->maxFileSize === 0) {
|
||||
$this->maxFileSize = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseHeaders($header)
|
||||
{
|
||||
$headers = array();
|
||||
|
||||
foreach (\explode("\r\n", \trim($header)) as $line) {
|
||||
$parts = \explode(':', $line, 2);
|
||||
if (!isset($parts[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = \strtolower(trim($parts[0]));
|
||||
$values = \explode(';', $parts[1]);
|
||||
$values = \array_map('trim', $values);
|
||||
$headers[$key] = $values;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
private function getParameterFromHeader(array $header, $parameter)
|
||||
{
|
||||
foreach ($header as $part) {
|
||||
if (\preg_match('/' . $parameter . '="?(.*?)"?$/', $part, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function extractPost($postFields, $key, $value)
|
||||
{
|
||||
$chunks = \explode('[', $key);
|
||||
if (\count($chunks) == 1) {
|
||||
$postFields[$key] = $value;
|
||||
return $postFields;
|
||||
}
|
||||
|
||||
// ignore this key if maximum nesting level is exceeded
|
||||
if (isset($chunks[$this->maxInputNestingLevel])) {
|
||||
return $postFields;
|
||||
}
|
||||
|
||||
$chunkKey = \rtrim($chunks[0], ']');
|
||||
$parent = &$postFields;
|
||||
for ($i = 1; isset($chunks[$i]); $i++) {
|
||||
$previousChunkKey = $chunkKey;
|
||||
|
||||
if ($previousChunkKey === '') {
|
||||
$parent[] = array();
|
||||
\end($parent);
|
||||
$parent = &$parent[\key($parent)];
|
||||
} else {
|
||||
if (!isset($parent[$previousChunkKey]) || !\is_array($parent[$previousChunkKey])) {
|
||||
$parent[$previousChunkKey] = array();
|
||||
}
|
||||
$parent = &$parent[$previousChunkKey];
|
||||
}
|
||||
|
||||
$chunkKey = \rtrim($chunks[$i], ']');
|
||||
}
|
||||
|
||||
if ($chunkKey === '') {
|
||||
$parent[] = $value;
|
||||
} else {
|
||||
$parent[$chunkKey] = $value;
|
||||
}
|
||||
|
||||
return $postFields;
|
||||
}
|
||||
}
|
||||
188
vendor/react/http/src/Io/PauseBufferStream.php
vendored
Normal file
188
vendor/react/http/src/Io/PauseBufferStream.php
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Pauses a given stream and buffers all events while paused
|
||||
*
|
||||
* This class is used to buffer all events that happen on a given stream while
|
||||
* it is paused. This allows you to pause a stream and no longer watch for any
|
||||
* of its events. Once the stream is resumed, all buffered events will be
|
||||
* emitted. Explicitly closing the resulting stream clears all buffers.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about.
|
||||
*
|
||||
* @see ReadableStreamInterface
|
||||
* @internal
|
||||
*/
|
||||
class PauseBufferStream extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $paused = false;
|
||||
private $dataPaused = '';
|
||||
private $endPaused = false;
|
||||
private $closePaused = false;
|
||||
private $errorPaused;
|
||||
private $implicit = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'handleClose'));
|
||||
}
|
||||
|
||||
/**
|
||||
* pause and remember this was not explicitly from user control
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function pauseImplicit()
|
||||
{
|
||||
$this->pause();
|
||||
$this->implicit = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* resume only if this was previously paused implicitly and not explicitly from user control
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function resumeImplicit()
|
||||
{
|
||||
if ($this->implicit) {
|
||||
$this->resume();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->input->pause();
|
||||
$this->paused = true;
|
||||
$this->implicit = false;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->paused = false;
|
||||
$this->implicit = false;
|
||||
|
||||
if ($this->dataPaused !== '') {
|
||||
$this->emit('data', array($this->dataPaused));
|
||||
$this->dataPaused = '';
|
||||
}
|
||||
|
||||
if ($this->errorPaused) {
|
||||
$this->emit('error', array($this->errorPaused));
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
if ($this->endPaused) {
|
||||
$this->endPaused = false;
|
||||
$this->emit('end');
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
if ($this->closePaused) {
|
||||
$this->closePaused = false;
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->dataPaused = '';
|
||||
$this->endPaused = $this->closePaused = false;
|
||||
$this->errorPaused = null;
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->dataPaused .= $data;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->errorPaused = $e;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->endPaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleClose()
|
||||
{
|
||||
if ($this->paused) {
|
||||
$this->closePaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
153
vendor/react/http/src/Io/ReadableBodyStream.php
vendored
Normal file
153
vendor/react/http/src/Io/ReadableBodyStream.php
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ReadableBodyStream extends EventEmitter implements ReadableStreamInterface, StreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $position = 0;
|
||||
private $size;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input, $size = null)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->size = $size;
|
||||
|
||||
$that = $this;
|
||||
$pos =& $this->position;
|
||||
$input->on('data', function ($data) use ($that, &$pos, $size) {
|
||||
$that->emit('data', array($data));
|
||||
|
||||
$pos += \strlen($data);
|
||||
if ($size !== null && $pos >= $size) {
|
||||
$that->handleEnd();
|
||||
}
|
||||
});
|
||||
$input->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
});
|
||||
$input->on('end', array($that, 'handleEnd'));
|
||||
$input->on('close', array($that, 'close'));
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return !$this->isReadable();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->position !== $this->size && $this->size !== null) {
|
||||
$this->emit('error', array(new \UnderflowException('Unexpected end of response body after ' . $this->position . '/' . $this->size . ' bytes')));
|
||||
} else {
|
||||
$this->emit('end');
|
||||
}
|
||||
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
179
vendor/react/http/src/Io/RequestHeaderParser.php
vendored
Normal file
179
vendor/react/http/src/Io/RequestHeaderParser.php
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ServerRequest;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* [Internal] Parses an incoming request header from an input stream
|
||||
*
|
||||
* This is used internally to parse the request header from the connection and
|
||||
* then process the remaining connection as the request body.
|
||||
*
|
||||
* @event headers
|
||||
* @event error
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RequestHeaderParser extends EventEmitter
|
||||
{
|
||||
private $maxSize = 8192;
|
||||
|
||||
/** @var Clock */
|
||||
private $clock;
|
||||
|
||||
/** @var array<string|int,array<string,string>> */
|
||||
private $connectionParams = array();
|
||||
|
||||
public function __construct(Clock $clock)
|
||||
{
|
||||
$this->clock = $clock;
|
||||
}
|
||||
|
||||
public function handle(ConnectionInterface $conn)
|
||||
{
|
||||
$buffer = '';
|
||||
$maxSize = $this->maxSize;
|
||||
$that = $this;
|
||||
$conn->on('data', $fn = function ($data) use (&$buffer, &$fn, $conn, $maxSize, $that) {
|
||||
// append chunk of data to buffer and look for end of request headers
|
||||
$buffer .= $data;
|
||||
$endOfHeader = \strpos($buffer, "\r\n\r\n");
|
||||
|
||||
// reject request if buffer size is exceeded
|
||||
if ($endOfHeader > $maxSize || ($endOfHeader === false && isset($buffer[$maxSize]))) {
|
||||
$conn->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
$that->emit('error', array(
|
||||
new \OverflowException("Maximum header size of {$maxSize} exceeded.", Response::STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE),
|
||||
$conn
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore incomplete requests
|
||||
if ($endOfHeader === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// request headers received => try to parse request
|
||||
$conn->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
try {
|
||||
$request = $that->parseRequest(
|
||||
(string)\substr($buffer, 0, $endOfHeader + 2),
|
||||
$conn
|
||||
);
|
||||
} catch (Exception $exception) {
|
||||
$buffer = '';
|
||||
$that->emit('error', array(
|
||||
$exception,
|
||||
$conn
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$contentLength = 0;
|
||||
if ($request->hasHeader('Transfer-Encoding')) {
|
||||
$contentLength = null;
|
||||
} elseif ($request->hasHeader('Content-Length')) {
|
||||
$contentLength = (int)$request->getHeaderLine('Content-Length');
|
||||
}
|
||||
|
||||
if ($contentLength === 0) {
|
||||
// happy path: request body is known to be empty
|
||||
$stream = new EmptyBodyStream();
|
||||
$request = $request->withBody($stream);
|
||||
} else {
|
||||
// otherwise body is present => delimit using Content-Length or ChunkedDecoder
|
||||
$stream = new CloseProtectionStream($conn);
|
||||
if ($contentLength !== null) {
|
||||
$stream = new LengthLimitedStream($stream, $contentLength);
|
||||
} else {
|
||||
$stream = new ChunkedDecoder($stream);
|
||||
}
|
||||
|
||||
$request = $request->withBody(new HttpBodyStream($stream, $contentLength));
|
||||
}
|
||||
|
||||
$bodyBuffer = isset($buffer[$endOfHeader + 4]) ? \substr($buffer, $endOfHeader + 4) : '';
|
||||
$buffer = '';
|
||||
$that->emit('headers', array($request, $conn));
|
||||
|
||||
if ($bodyBuffer !== '') {
|
||||
$conn->emit('data', array($bodyBuffer));
|
||||
}
|
||||
|
||||
// happy path: request body is known to be empty => immediately end stream
|
||||
if ($contentLength === 0) {
|
||||
$stream->emit('end');
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $headers buffer string containing request headers only
|
||||
* @param ConnectionInterface $connection
|
||||
* @return ServerRequestInterface
|
||||
* @throws \InvalidArgumentException
|
||||
* @internal
|
||||
*/
|
||||
public function parseRequest($headers, ConnectionInterface $connection)
|
||||
{
|
||||
// reuse same connection params for all server params for this connection
|
||||
$cid = \PHP_VERSION_ID < 70200 ? \spl_object_hash($connection) : \spl_object_id($connection);
|
||||
if (isset($this->connectionParams[$cid])) {
|
||||
$serverParams = $this->connectionParams[$cid];
|
||||
} else {
|
||||
// assign new server params for new connection
|
||||
$serverParams = array();
|
||||
|
||||
// scheme is `http` unless TLS is used
|
||||
$localSocketUri = $connection->getLocalAddress();
|
||||
$localParts = $localSocketUri === null ? array() : \parse_url($localSocketUri);
|
||||
if (isset($localParts['scheme']) && $localParts['scheme'] === 'tls') {
|
||||
$serverParams['HTTPS'] = 'on';
|
||||
}
|
||||
|
||||
// apply SERVER_ADDR and SERVER_PORT if server address is known
|
||||
// address should always be known, even for Unix domain sockets (UDS)
|
||||
// but skip UDS as it doesn't have a concept of host/port.
|
||||
if ($localSocketUri !== null && isset($localParts['host'], $localParts['port'])) {
|
||||
$serverParams['SERVER_ADDR'] = $localParts['host'];
|
||||
$serverParams['SERVER_PORT'] = $localParts['port'];
|
||||
}
|
||||
|
||||
// apply REMOTE_ADDR and REMOTE_PORT if source address is known
|
||||
// address should always be known, unless this is over Unix domain sockets (UDS)
|
||||
$remoteSocketUri = $connection->getRemoteAddress();
|
||||
if ($remoteSocketUri !== null) {
|
||||
$remoteAddress = \parse_url($remoteSocketUri);
|
||||
$serverParams['REMOTE_ADDR'] = $remoteAddress['host'];
|
||||
$serverParams['REMOTE_PORT'] = $remoteAddress['port'];
|
||||
}
|
||||
|
||||
// remember server params for all requests from this connection, reset on connection close
|
||||
$this->connectionParams[$cid] = $serverParams;
|
||||
$params =& $this->connectionParams;
|
||||
$connection->on('close', function () use (&$params, $cid) {
|
||||
assert(\is_array($params));
|
||||
unset($params[$cid]);
|
||||
});
|
||||
}
|
||||
|
||||
// create new obj implementing ServerRequestInterface by preserving all
|
||||
// previous properties and restoring original request-target
|
||||
$serverParams['REQUEST_TIME'] = (int) ($now = $this->clock->now());
|
||||
$serverParams['REQUEST_TIME_FLOAT'] = $now;
|
||||
|
||||
return ServerRequest::parseMessage($headers, $serverParams);
|
||||
}
|
||||
}
|
||||
152
vendor/react/http/src/Io/Sender.php
vendored
Normal file
152
vendor/react/http/src/Io/Sender.php
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Client\Client as HttpClient;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Sends requests and receives responses
|
||||
*
|
||||
* The `Sender` is responsible for passing the [`RequestInterface`](#requestinterface) objects to
|
||||
* the underlying [`HttpClient`](https://github.com/reactphp/http-client) library
|
||||
* and keeps track of its transmission and converts its reponses back to [`ResponseInterface`](#responseinterface) objects.
|
||||
*
|
||||
* It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
|
||||
* and the default [`Connector`](https://github.com/reactphp/socket-client) and [DNS `Resolver`](https://github.com/reactphp/dns).
|
||||
*
|
||||
* The `Sender` class mostly exists in order to abstract changes on the underlying
|
||||
* components away from this package in order to provide backwards and forwards
|
||||
* compatibility.
|
||||
*
|
||||
* @internal You SHOULD NOT rely on this API, it is subject to change without prior notice!
|
||||
* @see Browser
|
||||
*/
|
||||
class Sender
|
||||
{
|
||||
/**
|
||||
* create a new default sender attached to the given event loop
|
||||
*
|
||||
* This method is used internally to create the "default sender".
|
||||
*
|
||||
* You may also use this method if you need custom DNS or connector
|
||||
* settings. You can use this method manually like this:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector(array(), $loop);
|
||||
* $sender = \React\Http\Io\Sender::createFromLoop($loop, $connector);
|
||||
* ```
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector)
|
||||
{
|
||||
return new self(new HttpClient(new ClientConnectionManager($connector, $loop)));
|
||||
}
|
||||
|
||||
private $http;
|
||||
|
||||
/**
|
||||
* [internal] Instantiate Sender
|
||||
*
|
||||
* @param HttpClient $http
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(HttpClient $http)
|
||||
{
|
||||
$this->http = $http;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @internal
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
// support HTTP/1.1 and HTTP/1.0 only, ensured by `Browser` already
|
||||
assert(\in_array($request->getProtocolVersion(), array('1.0', '1.1'), true));
|
||||
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size !== null && $size !== 0) {
|
||||
// automatically assign a "Content-Length" request header if the body size is known and non-empty
|
||||
$request = $request->withHeader('Content-Length', (string)$size);
|
||||
} elseif ($size === 0 && \in_array($request->getMethod(), array('POST', 'PUT', 'PATCH'))) {
|
||||
// only assign a "Content-Length: 0" request header if the body is expected for certain methods
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
} elseif ($body instanceof ReadableStreamInterface && $size !== 0 && $body->isReadable() && !$request->hasHeader('Content-Length')) {
|
||||
// use "Transfer-Encoding: chunked" when this is a streaming body and body size is unknown
|
||||
$request = $request->withHeader('Transfer-Encoding', 'chunked');
|
||||
} else {
|
||||
// do not use chunked encoding if size is known or if this is an empty request body
|
||||
$size = 0;
|
||||
}
|
||||
|
||||
// automatically add `Authorization: Basic …` request header if URL includes `user:pass@host`
|
||||
if ($request->getUri()->getUserInfo() !== '' && !$request->hasHeader('Authorization')) {
|
||||
$request = $request->withHeader('Authorization', 'Basic ' . \base64_encode($request->getUri()->getUserInfo()));
|
||||
}
|
||||
|
||||
$requestStream = $this->http->request($request);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($requestStream) {
|
||||
// close request stream if request is cancelled
|
||||
$reject(new \RuntimeException('Request cancelled'));
|
||||
$requestStream->close();
|
||||
});
|
||||
|
||||
$requestStream->on('error', function($error) use ($deferred) {
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
$requestStream->on('response', function (ResponseInterface $response) use ($deferred, $request) {
|
||||
$deferred->resolve($response);
|
||||
});
|
||||
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
if ($body->isReadable()) {
|
||||
// length unknown => apply chunked transfer-encoding
|
||||
if ($size === null) {
|
||||
$body = new ChunkedEncoder($body);
|
||||
}
|
||||
|
||||
// pipe body into request stream
|
||||
// add dummy write to immediately start request even if body does not emit any data yet
|
||||
$body->pipe($requestStream);
|
||||
$requestStream->write('');
|
||||
|
||||
$body->on('close', $close = function () use ($deferred, $requestStream) {
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body closed unexpectedly'));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('error', function ($e) use ($deferred, $requestStream, $close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
$deferred->reject(new \RuntimeException('Request failed because request body reported an error', 0, $e));
|
||||
$requestStream->close();
|
||||
});
|
||||
$body->on('end', function () use ($close, $body) {
|
||||
$body->removeListener('close', $close);
|
||||
});
|
||||
} else {
|
||||
// stream is not readable => end request without body
|
||||
$requestStream->end();
|
||||
}
|
||||
} else {
|
||||
// body is fully buffered => write as one chunk
|
||||
$requestStream->end((string)$body);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
405
vendor/react/http/src/Io/StreamingServer.php
vendored
Normal file
405
vendor/react/http/src/Io/StreamingServer.php
vendored
Normal file
@@ -0,0 +1,405 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ServerRequest;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ServerInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* The internal `StreamingServer` class is responsible for handling incoming connections and then
|
||||
* processing each incoming HTTP request.
|
||||
*
|
||||
* Unlike the [`HttpServer`](#httpserver) class, it does not buffer and parse the incoming
|
||||
* HTTP request body by default. This means that the request handler will be
|
||||
* invoked with a streaming request body. Once the request headers have been
|
||||
* received, it will invoke the request handler function. This request handler
|
||||
* function needs to be passed to the constructor and will be invoked with the
|
||||
* respective [request](#request) object and expects a [response](#response)
|
||||
* object in return:
|
||||
*
|
||||
* ```php
|
||||
* $server = new StreamingServer($loop, function (ServerRequestInterface $request) {
|
||||
* return new Response(
|
||||
* Response::STATUS_OK,
|
||||
* array(
|
||||
* 'Content-Type' => 'text/plain'
|
||||
* ),
|
||||
* "Hello World!\n"
|
||||
* );
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Each incoming HTTP request message is always represented by the
|
||||
* [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface),
|
||||
* see also following [request](#request) chapter for more details.
|
||||
* Each outgoing HTTP response message is always represented by the
|
||||
* [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface),
|
||||
* see also following [response](#response) chapter for more details.
|
||||
*
|
||||
* In order to process any connections, the server needs to be attached to an
|
||||
* instance of `React\Socket\ServerInterface` through the [`listen()`](#listen) method
|
||||
* as described in the following chapter. In its most simple form, you can attach
|
||||
* this to a [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver)
|
||||
* in order to start a plaintext HTTP server like this:
|
||||
*
|
||||
* ```php
|
||||
* $server = new StreamingServer($loop, $handler);
|
||||
*
|
||||
* $socket = new React\Socket\SocketServer('0.0.0.0:8080', array(), $loop);
|
||||
* $server->listen($socket);
|
||||
* ```
|
||||
*
|
||||
* See also the [`listen()`](#listen) method and the [first example](examples) for more details.
|
||||
*
|
||||
* The `StreamingServer` class is considered advanced usage and unless you know
|
||||
* what you're doing, you're recommended to use the [`HttpServer`](#httpserver) class
|
||||
* instead. The `StreamingServer` class is specifically designed to help with
|
||||
* more advanced use cases where you want to have full control over consuming
|
||||
* the incoming HTTP request body and concurrency settings.
|
||||
*
|
||||
* In particular, this class does not buffer and parse the incoming HTTP request
|
||||
* in memory. It will invoke the request handler function once the HTTP request
|
||||
* headers have been received, i.e. before receiving the potentially much larger
|
||||
* HTTP request body. This means the [request](#request) passed to your request
|
||||
* handler function may not be fully compatible with PSR-7. See also
|
||||
* [streaming request](#streaming-request) below for more details.
|
||||
*
|
||||
* @see \React\Http\HttpServer
|
||||
* @see \React\Http\Message\Response
|
||||
* @see self::listen()
|
||||
* @internal
|
||||
*/
|
||||
final class StreamingServer extends EventEmitter
|
||||
{
|
||||
private $callback;
|
||||
private $parser;
|
||||
|
||||
/** @var Clock */
|
||||
private $clock;
|
||||
|
||||
/**
|
||||
* Creates an HTTP server that invokes the given callback for each incoming HTTP request
|
||||
*
|
||||
* In order to process any connections, the server needs to be attached to an
|
||||
* instance of `React\Socket\ServerInterface` which emits underlying streaming
|
||||
* connections in order to then parse incoming data as HTTP.
|
||||
* See also [listen()](#listen) for more details.
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param callable $requestHandler
|
||||
* @see self::listen()
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, $requestHandler)
|
||||
{
|
||||
if (!\is_callable($requestHandler)) {
|
||||
throw new \InvalidArgumentException('Invalid request handler given');
|
||||
}
|
||||
|
||||
$this->callback = $requestHandler;
|
||||
$this->clock = new Clock($loop);
|
||||
$this->parser = new RequestHeaderParser($this->clock);
|
||||
|
||||
$that = $this;
|
||||
$this->parser->on('headers', function (ServerRequestInterface $request, ConnectionInterface $conn) use ($that) {
|
||||
$that->handleRequest($conn, $request);
|
||||
});
|
||||
|
||||
$this->parser->on('error', function(\Exception $e, ConnectionInterface $conn) use ($that) {
|
||||
$that->emit('error', array($e));
|
||||
|
||||
// parsing failed => assume dummy request and send appropriate error
|
||||
$that->writeError(
|
||||
$conn,
|
||||
$e->getCode() !== 0 ? $e->getCode() : Response::STATUS_BAD_REQUEST,
|
||||
new ServerRequest('GET', '/')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for HTTP requests on the given socket server instance
|
||||
*
|
||||
* @param ServerInterface $socket
|
||||
* @see \React\Http\HttpServer::listen()
|
||||
*/
|
||||
public function listen(ServerInterface $socket)
|
||||
{
|
||||
$socket->on('connection', array($this->parser, 'handle'));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleRequest(ConnectionInterface $conn, ServerRequestInterface $request)
|
||||
{
|
||||
if ($request->getProtocolVersion() !== '1.0' && '100-continue' === \strtolower($request->getHeaderLine('Expect'))) {
|
||||
$conn->write("HTTP/1.1 100 Continue\r\n\r\n");
|
||||
}
|
||||
|
||||
// execute request handler callback
|
||||
$callback = $this->callback;
|
||||
try {
|
||||
$response = $callback($request);
|
||||
} catch (\Exception $error) {
|
||||
// request handler callback throws an Exception
|
||||
$response = Promise\reject($error);
|
||||
} catch (\Throwable $error) { // @codeCoverageIgnoreStart
|
||||
// request handler callback throws a PHP7+ Error
|
||||
$response = Promise\reject($error); // @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// cancel pending promise once connection closes
|
||||
$connectionOnCloseResponseCancelerHandler = function () {};
|
||||
if ($response instanceof PromiseInterface && \method_exists($response, 'cancel')) {
|
||||
$connectionOnCloseResponseCanceler = function () use ($response) {
|
||||
$response->cancel();
|
||||
};
|
||||
$connectionOnCloseResponseCancelerHandler = function () use ($connectionOnCloseResponseCanceler, $conn) {
|
||||
if ($connectionOnCloseResponseCanceler !== null) {
|
||||
$conn->removeListener('close', $connectionOnCloseResponseCanceler);
|
||||
}
|
||||
};
|
||||
$conn->on('close', $connectionOnCloseResponseCanceler);
|
||||
}
|
||||
|
||||
// happy path: response returned, handle and return immediately
|
||||
if ($response instanceof ResponseInterface) {
|
||||
return $this->handleResponse($conn, $request, $response);
|
||||
}
|
||||
|
||||
// did not return a promise? this is an error, convert into one for rejection below.
|
||||
if (!$response instanceof PromiseInterface) {
|
||||
$response = Promise\resolve($response);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$response->then(
|
||||
function ($response) use ($that, $conn, $request) {
|
||||
if (!$response instanceof ResponseInterface) {
|
||||
$message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but resolved with "%s" instead.';
|
||||
$message = \sprintf($message, \is_object($response) ? \get_class($response) : \gettype($response));
|
||||
$exception = new \RuntimeException($message);
|
||||
|
||||
$that->emit('error', array($exception));
|
||||
return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
|
||||
}
|
||||
$that->handleResponse($conn, $request, $response);
|
||||
},
|
||||
function ($error) use ($that, $conn, $request) {
|
||||
$message = 'The response callback is expected to resolve with an object implementing Psr\Http\Message\ResponseInterface, but rejected with "%s" instead.';
|
||||
$message = \sprintf($message, \is_object($error) ? \get_class($error) : \gettype($error));
|
||||
|
||||
$previous = null;
|
||||
|
||||
if ($error instanceof \Throwable || $error instanceof \Exception) {
|
||||
$previous = $error;
|
||||
}
|
||||
|
||||
$exception = new \RuntimeException($message, 0, $previous);
|
||||
|
||||
$that->emit('error', array($exception));
|
||||
return $that->writeError($conn, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
|
||||
}
|
||||
)->then($connectionOnCloseResponseCancelerHandler, $connectionOnCloseResponseCancelerHandler);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function writeError(ConnectionInterface $conn, $code, ServerRequestInterface $request)
|
||||
{
|
||||
$response = new Response(
|
||||
$code,
|
||||
array(
|
||||
'Content-Type' => 'text/plain',
|
||||
'Connection' => 'close' // we do not want to keep the connection open after an error
|
||||
),
|
||||
'Error ' . $code
|
||||
);
|
||||
|
||||
// append reason phrase to response body if known
|
||||
$reason = $response->getReasonPhrase();
|
||||
if ($reason !== '') {
|
||||
$body = $response->getBody();
|
||||
$body->seek(0, SEEK_END);
|
||||
$body->write(': ' . $reason);
|
||||
}
|
||||
|
||||
$this->handleResponse($conn, $request, $response);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function handleResponse(ConnectionInterface $connection, ServerRequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
// return early and close response body if connection is already closed
|
||||
$body = $response->getBody();
|
||||
if (!$connection->isWritable()) {
|
||||
$body->close();
|
||||
return;
|
||||
}
|
||||
|
||||
$code = $response->getStatusCode();
|
||||
$method = $request->getMethod();
|
||||
|
||||
// assign HTTP protocol version from request automatically
|
||||
$version = $request->getProtocolVersion();
|
||||
$response = $response->withProtocolVersion($version);
|
||||
|
||||
// assign default "Server" header automatically
|
||||
if (!$response->hasHeader('Server')) {
|
||||
$response = $response->withHeader('Server', 'ReactPHP/1');
|
||||
} elseif ($response->getHeaderLine('Server') === ''){
|
||||
$response = $response->withoutHeader('Server');
|
||||
}
|
||||
|
||||
// assign default "Date" header from current time automatically
|
||||
if (!$response->hasHeader('Date')) {
|
||||
// IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT
|
||||
$response = $response->withHeader('Date', gmdate('D, d M Y H:i:s', (int) $this->clock->now()) . ' GMT');
|
||||
} elseif ($response->getHeaderLine('Date') === ''){
|
||||
$response = $response->withoutHeader('Date');
|
||||
}
|
||||
|
||||
// assign "Content-Length" header automatically
|
||||
$chunked = false;
|
||||
if (($method === 'CONNECT' && $code >= 200 && $code < 300) || ($code >= 100 && $code < 200) || $code === Response::STATUS_NO_CONTENT) {
|
||||
// 2xx response to CONNECT and 1xx and 204 MUST NOT include Content-Length or Transfer-Encoding header
|
||||
$response = $response->withoutHeader('Content-Length');
|
||||
} elseif ($method === 'HEAD' && $response->hasHeader('Content-Length')) {
|
||||
// HEAD Request: preserve explicit Content-Length
|
||||
} elseif ($code === Response::STATUS_NOT_MODIFIED && ($response->hasHeader('Content-Length') || $body->getSize() === 0)) {
|
||||
// 304 Not Modified: preserve explicit Content-Length and preserve missing header if body is empty
|
||||
} elseif ($body->getSize() !== null) {
|
||||
// assign Content-Length header when using a "normal" buffered body string
|
||||
$response = $response->withHeader('Content-Length', (string)$body->getSize());
|
||||
} elseif (!$response->hasHeader('Content-Length') && $version === '1.1') {
|
||||
// assign chunked transfer-encoding if no 'content-length' is given for HTTP/1.1 responses
|
||||
$chunked = true;
|
||||
}
|
||||
|
||||
// assign "Transfer-Encoding" header automatically
|
||||
if ($chunked) {
|
||||
$response = $response->withHeader('Transfer-Encoding', 'chunked');
|
||||
} else {
|
||||
// remove any Transfer-Encoding headers unless automatically enabled above
|
||||
$response = $response->withoutHeader('Transfer-Encoding');
|
||||
}
|
||||
|
||||
// assign "Connection" header automatically
|
||||
$persist = false;
|
||||
if ($code === Response::STATUS_SWITCHING_PROTOCOLS) {
|
||||
// 101 (Switching Protocols) response uses Connection: upgrade header
|
||||
// This implies that this stream now uses another protocol and we
|
||||
// may not persist this connection for additional requests.
|
||||
$response = $response->withHeader('Connection', 'upgrade');
|
||||
} elseif (\strtolower($request->getHeaderLine('Connection')) === 'close' || \strtolower($response->getHeaderLine('Connection')) === 'close') {
|
||||
// obey explicit "Connection: close" request header or response header if present
|
||||
$response = $response->withHeader('Connection', 'close');
|
||||
} elseif ($version === '1.1') {
|
||||
// HTTP/1.1 assumes persistent connection support by default, so we don't need to inform client
|
||||
$persist = true;
|
||||
} elseif (strtolower($request->getHeaderLine('Connection')) === 'keep-alive') {
|
||||
// obey explicit "Connection: keep-alive" request header and inform client
|
||||
$persist = true;
|
||||
$response = $response->withHeader('Connection', 'keep-alive');
|
||||
} else {
|
||||
// remove any Connection headers unless automatically enabled above
|
||||
$response = $response->withoutHeader('Connection');
|
||||
}
|
||||
|
||||
// 101 (Switching Protocols) response (for Upgrade request) forwards upgraded data through duplex stream
|
||||
// 2xx (Successful) response to CONNECT forwards tunneled application data through duplex stream
|
||||
if (($code === Response::STATUS_SWITCHING_PROTOCOLS || ($method === 'CONNECT' && $code >= 200 && $code < 300)) && $body instanceof HttpBodyStream && $body->input instanceof WritableStreamInterface) {
|
||||
if ($request->getBody()->isReadable()) {
|
||||
// request is still streaming => wait for request close before forwarding following data from connection
|
||||
$request->getBody()->on('close', function () use ($connection, $body) {
|
||||
if ($body->input->isWritable()) {
|
||||
$connection->pipe($body->input);
|
||||
$connection->resume();
|
||||
}
|
||||
});
|
||||
} elseif ($body->input->isWritable()) {
|
||||
// request already closed => forward following data from connection
|
||||
$connection->pipe($body->input);
|
||||
$connection->resume();
|
||||
}
|
||||
}
|
||||
|
||||
// build HTTP response header by appending status line and header fields
|
||||
$expected = 0;
|
||||
$headers = "HTTP/" . $version . " " . $code . " " . $response->getReasonPhrase() . "\r\n";
|
||||
foreach ($response->getHeaders() as $name => $values) {
|
||||
if (\strpos($name, ':') !== false) {
|
||||
$expected = -1;
|
||||
break;
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
$headers .= $name . ": " . $value . "\r\n";
|
||||
++$expected;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var array $m legacy PHP 5.3 only */
|
||||
if ($code < 100 || $code > 999 || \substr_count($headers, "\n") !== ($expected + 1) || (\PHP_VERSION_ID >= 50400 ? \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers) : \preg_match_all(AbstractMessage::REGEX_HEADERS, $headers, $m)) !== $expected) {
|
||||
$this->emit('error', array(new \InvalidArgumentException('Unable to send response with invalid response headers')));
|
||||
$this->writeError($connection, Response::STATUS_INTERNAL_SERVER_ERROR, $request);
|
||||
return;
|
||||
}
|
||||
|
||||
// response to HEAD and 1xx, 204 and 304 responses MUST NOT include a body
|
||||
// exclude status 101 (Switching Protocols) here for Upgrade request handling above
|
||||
if ($method === 'HEAD' || ($code >= 100 && $code < 200 && $code !== Response::STATUS_SWITCHING_PROTOCOLS) || $code === Response::STATUS_NO_CONTENT || $code === Response::STATUS_NOT_MODIFIED) {
|
||||
$body->close();
|
||||
$body = '';
|
||||
}
|
||||
|
||||
// this is a non-streaming response body or the body stream already closed?
|
||||
if (!$body instanceof ReadableStreamInterface || !$body->isReadable()) {
|
||||
// add final chunk if a streaming body is already closed and uses `Transfer-Encoding: chunked`
|
||||
if ($body instanceof ReadableStreamInterface && $chunked) {
|
||||
$body = "0\r\n\r\n";
|
||||
}
|
||||
|
||||
// write response headers and body
|
||||
$connection->write($headers . "\r\n" . $body);
|
||||
|
||||
// either wait for next request over persistent connection or end connection
|
||||
if ($persist) {
|
||||
$this->parser->handle($connection);
|
||||
} else {
|
||||
$connection->end();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$connection->write($headers . "\r\n");
|
||||
|
||||
if ($chunked) {
|
||||
$body = new ChunkedEncoder($body);
|
||||
}
|
||||
|
||||
// Close response stream once connection closes.
|
||||
// Note that this TCP/IP close detection may take some time,
|
||||
// in particular this may only fire on a later read/write attempt.
|
||||
$connection->on('close', array($body, 'close'));
|
||||
|
||||
// write streaming body and then wait for next request over persistent connection
|
||||
if ($persist) {
|
||||
$body->pipe($connection, array('end' => false));
|
||||
$parser = $this->parser;
|
||||
$body->on('end', function () use ($connection, $parser, $body) {
|
||||
$connection->removeListener('close', array($body, 'close'));
|
||||
$parser->handle($connection);
|
||||
});
|
||||
} else {
|
||||
$body->pipe($connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
330
vendor/react/http/src/Io/Transaction.php
vendored
Normal file
330
vendor/react/http/src/Io/Transaction.php
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ResponseException;
|
||||
use React\Http\Message\Uri;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Transaction
|
||||
{
|
||||
private $sender;
|
||||
private $loop;
|
||||
|
||||
// context: http.timeout (ini_get('default_socket_timeout'): 60)
|
||||
private $timeout;
|
||||
|
||||
// context: http.follow_location (true)
|
||||
private $followRedirects = true;
|
||||
|
||||
// context: http.max_redirects (10)
|
||||
private $maxRedirects = 10;
|
||||
|
||||
// context: http.ignore_errors (false)
|
||||
private $obeySuccessCode = true;
|
||||
|
||||
private $streaming = false;
|
||||
|
||||
private $maximumSize = 16777216; // 16 MiB = 2^24 bytes
|
||||
|
||||
public function __construct(Sender $sender, LoopInterface $loop)
|
||||
{
|
||||
$this->sender = $sender;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return self returns new instance, without modifying existing instance
|
||||
*/
|
||||
public function withOptions(array $options)
|
||||
{
|
||||
$transaction = clone $this;
|
||||
foreach ($options as $name => $value) {
|
||||
if (property_exists($transaction, $name)) {
|
||||
// restore default value if null is given
|
||||
if ($value === null) {
|
||||
$default = new self($this->sender, $this->loop);
|
||||
$value = $default->$name;
|
||||
}
|
||||
|
||||
$transaction->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$state = new ClientRequestState();
|
||||
$deferred = new Deferred(function () use ($state) {
|
||||
if ($state->pending !== null) {
|
||||
$state->pending->cancel();
|
||||
$state->pending = null;
|
||||
}
|
||||
});
|
||||
|
||||
// use timeout from options or default to PHP's default_socket_timeout (60)
|
||||
$timeout = (float)($this->timeout !== null ? $this->timeout : ini_get("default_socket_timeout"));
|
||||
|
||||
$loop = $this->loop;
|
||||
$this->next($request, $deferred, $state)->then(
|
||||
function (ResponseInterface $response) use ($state, $deferred, $loop, &$timeout) {
|
||||
if ($state->timeout !== null) {
|
||||
$loop->cancelTimer($state->timeout);
|
||||
$state->timeout = null;
|
||||
}
|
||||
$timeout = -1;
|
||||
$deferred->resolve($response);
|
||||
},
|
||||
function ($e) use ($state, $deferred, $loop, &$timeout) {
|
||||
if ($state->timeout !== null) {
|
||||
$loop->cancelTimer($state->timeout);
|
||||
$state->timeout = null;
|
||||
}
|
||||
$timeout = -1;
|
||||
$deferred->reject($e);
|
||||
}
|
||||
);
|
||||
|
||||
if ($timeout < 0) {
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
$body = $request->getBody();
|
||||
if ($body instanceof ReadableStreamInterface && $body->isReadable()) {
|
||||
$that = $this;
|
||||
$body->on('close', function () use ($that, $deferred, $state, &$timeout) {
|
||||
if ($timeout >= 0) {
|
||||
$that->applyTimeout($deferred, $state, $timeout);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$this->applyTimeout($deferred, $state, $timeout);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param number $timeout
|
||||
* @return void
|
||||
*/
|
||||
public function applyTimeout(Deferred $deferred, ClientRequestState $state, $timeout)
|
||||
{
|
||||
$state->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred, $state) {
|
||||
$deferred->reject(new \RuntimeException(
|
||||
'Request timed out after ' . $timeout . ' seconds'
|
||||
));
|
||||
if ($state->pending !== null) {
|
||||
$state->pending->cancel();
|
||||
$state->pending = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function next(RequestInterface $request, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
$this->progress('request', array($request));
|
||||
|
||||
$that = $this;
|
||||
++$state->numRequests;
|
||||
|
||||
$promise = $this->sender->send($request);
|
||||
|
||||
if (!$this->streaming) {
|
||||
$promise = $promise->then(function ($response) use ($deferred, $state, $that) {
|
||||
return $that->bufferResponse($response, $deferred, $state);
|
||||
});
|
||||
}
|
||||
|
||||
$state->pending = $promise;
|
||||
|
||||
return $promise->then(
|
||||
function (ResponseInterface $response) use ($request, $that, $deferred, $state) {
|
||||
return $that->onResponse($response, $request, $deferred, $state);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function bufferResponse(ResponseInterface $response, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
$body = $response->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size !== null && $size > $this->maximumSize) {
|
||||
$body->close();
|
||||
return \React\Promise\reject(new \OverflowException(
|
||||
'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
}
|
||||
|
||||
// body is not streaming => already buffered
|
||||
if (!$body instanceof ReadableStreamInterface) {
|
||||
return \React\Promise\resolve($response);
|
||||
}
|
||||
|
||||
/** @var ?\Closure $closer */
|
||||
$closer = null;
|
||||
$maximumSize = $this->maximumSize;
|
||||
|
||||
return $state->pending = new Promise(function ($resolve, $reject) use ($body, $maximumSize, $response, &$closer) {
|
||||
// resolve with current buffer when stream closes successfully
|
||||
$buffer = '';
|
||||
$body->on('close', $closer = function () use (&$buffer, $response, $maximumSize, $resolve, $reject) {
|
||||
$resolve($response->withBody(new BufferedBody($buffer)));
|
||||
});
|
||||
|
||||
// buffer response body data in memory
|
||||
$body->on('data', function ($data) use (&$buffer, $maximumSize, $body, $closer, $reject) {
|
||||
$buffer .= $data;
|
||||
|
||||
// close stream and reject promise if limit is exceeded
|
||||
if (isset($buffer[$maximumSize])) {
|
||||
$buffer = '';
|
||||
assert($closer instanceof \Closure);
|
||||
$body->removeListener('close', $closer);
|
||||
$body->close();
|
||||
|
||||
$reject(new \OverflowException(
|
||||
'Response body size exceeds maximum of ' . $maximumSize . ' bytes',
|
||||
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
// reject buffering if body emits error
|
||||
$body->on('error', function (\Exception $e) use ($reject) {
|
||||
$reject(new \RuntimeException(
|
||||
'Error while buffering response body: ' . $e->getMessage(),
|
||||
$e->getCode(),
|
||||
$e
|
||||
));
|
||||
});
|
||||
}, function () use ($body, &$closer) {
|
||||
// cancelled buffering: remove close handler to avoid resolving, then close and reject
|
||||
assert($closer instanceof \Closure);
|
||||
$body->removeListener('close', $closer);
|
||||
$body->close();
|
||||
|
||||
throw new \RuntimeException('Cancelled buffering response body');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @throws ResponseException
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function onResponse(ResponseInterface $response, RequestInterface $request, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
$this->progress('response', array($response, $request));
|
||||
|
||||
// follow 3xx (Redirection) response status codes if Location header is present and not explicitly disabled
|
||||
// @link https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
if ($this->followRedirects && ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) && $response->hasHeader('Location')) {
|
||||
return $this->onResponseRedirect($response, $request, $deferred, $state);
|
||||
}
|
||||
|
||||
// only status codes 200-399 are considered to be valid, reject otherwise
|
||||
if ($this->obeySuccessCode && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400)) {
|
||||
throw new ResponseException($response);
|
||||
}
|
||||
|
||||
// resolve our initial promise
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @param Deferred $deferred
|
||||
* @param ClientRequestState $state
|
||||
* @return PromiseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred, ClientRequestState $state)
|
||||
{
|
||||
// resolve location relative to last request URI
|
||||
$location = Uri::resolve($request->getUri(), new Uri($response->getHeaderLine('Location')));
|
||||
|
||||
$request = $this->makeRedirectRequest($request, $location, $response->getStatusCode());
|
||||
$this->progress('redirect', array($request));
|
||||
|
||||
if ($state->numRequests >= $this->maxRedirects) {
|
||||
throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
|
||||
}
|
||||
|
||||
return $this->next($request, $deferred, $state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param UriInterface $location
|
||||
* @param int $statusCode
|
||||
* @return RequestInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function makeRedirectRequest(RequestInterface $request, UriInterface $location, $statusCode)
|
||||
{
|
||||
// Remove authorization if changing hostnames (but not if just changing ports or protocols).
|
||||
$originalHost = $request->getUri()->getHost();
|
||||
if ($location->getHost() !== $originalHost) {
|
||||
$request = $request->withoutHeader('Authorization');
|
||||
}
|
||||
|
||||
$request = $request->withoutHeader('Host')->withUri($location);
|
||||
|
||||
if ($statusCode === Response::STATUS_TEMPORARY_REDIRECT || $statusCode === Response::STATUS_PERMANENT_REDIRECT) {
|
||||
if ($request->getBody() instanceof ReadableStreamInterface) {
|
||||
throw new \RuntimeException('Unable to redirect request with streaming body');
|
||||
}
|
||||
} else {
|
||||
$request = $request
|
||||
->withMethod($request->getMethod() === 'HEAD' ? 'HEAD' : 'GET')
|
||||
->withoutHeader('Content-Type')
|
||||
->withoutHeader('Content-Length')
|
||||
->withBody(new BufferedBody(''));
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function progress($name, array $args = array())
|
||||
{
|
||||
return;
|
||||
|
||||
echo $name;
|
||||
|
||||
foreach ($args as $arg) {
|
||||
echo ' ';
|
||||
if ($arg instanceof ResponseInterface) {
|
||||
echo 'HTTP/' . $arg->getProtocolVersion() . ' ' . $arg->getStatusCode() . ' ' . $arg->getReasonPhrase();
|
||||
} elseif ($arg instanceof RequestInterface) {
|
||||
echo $arg->getMethod() . ' ' . $arg->getRequestTarget() . ' HTTP/' . $arg->getProtocolVersion();
|
||||
} else {
|
||||
echo $arg;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
130
vendor/react/http/src/Io/UploadedFile.php
vendored
Normal file
130
vendor/react/http/src/Io/UploadedFile.php
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Io;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* [Internal] Implementation of the PSR-7 `UploadedFileInterface`
|
||||
*
|
||||
* This is used internally to represent each incoming file upload.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually care
|
||||
* about. See the `UploadedFileInterface` for more details.
|
||||
*
|
||||
* @see UploadedFileInterface
|
||||
* @internal
|
||||
*/
|
||||
final class UploadedFile implements UploadedFileInterface
|
||||
{
|
||||
/**
|
||||
* @var StreamInterface
|
||||
*/
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $error;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $filename;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $mediaType;
|
||||
|
||||
/**
|
||||
* @param StreamInterface $stream
|
||||
* @param int $size
|
||||
* @param int $error
|
||||
* @param string $filename
|
||||
* @param string $mediaType
|
||||
*/
|
||||
public function __construct(StreamInterface $stream, $size, $error, $filename, $mediaType)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
$this->size = $size;
|
||||
|
||||
if (!\is_int($error) || !\in_array($error, array(
|
||||
\UPLOAD_ERR_OK,
|
||||
\UPLOAD_ERR_INI_SIZE,
|
||||
\UPLOAD_ERR_FORM_SIZE,
|
||||
\UPLOAD_ERR_PARTIAL,
|
||||
\UPLOAD_ERR_NO_FILE,
|
||||
\UPLOAD_ERR_NO_TMP_DIR,
|
||||
\UPLOAD_ERR_CANT_WRITE,
|
||||
\UPLOAD_ERR_EXTENSION,
|
||||
))) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid error code, must be an UPLOAD_ERR_* constant'
|
||||
);
|
||||
}
|
||||
$this->error = $error;
|
||||
$this->filename = $filename;
|
||||
$this->mediaType = $mediaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
if ($this->error !== \UPLOAD_ERR_OK) {
|
||||
throw new RuntimeException('Cannot retrieve stream due to upload error');
|
||||
}
|
||||
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function moveTo($targetPath)
|
||||
{
|
||||
throw new RuntimeException('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClientFilename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClientMediaType()
|
||||
{
|
||||
return $this->mediaType;
|
||||
}
|
||||
}
|
||||
57
vendor/react/http/src/Message/Request.php
vendored
Normal file
57
vendor/react/http/src/Message/Request.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Message;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\Http\Io\AbstractRequest;
|
||||
use React\Http\Io\BufferedBody;
|
||||
use React\Http\Io\ReadableBodyStream;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* Respresents an outgoing HTTP request message.
|
||||
*
|
||||
* This class implements the
|
||||
* [PSR-7 `RequestInterface`](https://www.php-fig.org/psr/psr-7/#32-psrhttpmessagerequestinterface)
|
||||
* which extends the
|
||||
* [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface).
|
||||
*
|
||||
* This is mostly used internally to represent each outgoing HTTP request
|
||||
* message for the HTTP client implementation. Likewise, you can also use this
|
||||
* class with other HTTP client implementations and for tests.
|
||||
*
|
||||
* > Internally, this implementation builds on top of a base class which is
|
||||
* considered an implementation detail that may change in the future.
|
||||
*
|
||||
* @see RequestInterface
|
||||
*/
|
||||
final class Request extends AbstractRequest implements RequestInterface
|
||||
{
|
||||
/**
|
||||
* @param string $method HTTP method for the request.
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array<string,string|string[]> $headers Headers for the message.
|
||||
* @param string|ReadableStreamInterface|StreamInterface $body Message body.
|
||||
* @param string $version HTTP protocol version.
|
||||
* @throws \InvalidArgumentException for an invalid URL or body
|
||||
*/
|
||||
public function __construct(
|
||||
$method,
|
||||
$url,
|
||||
array $headers = array(),
|
||||
$body = '',
|
||||
$version = '1.1'
|
||||
) {
|
||||
if (\is_string($body)) {
|
||||
$body = new BufferedBody($body);
|
||||
} elseif ($body instanceof ReadableStreamInterface && !$body instanceof StreamInterface) {
|
||||
$body = new ReadableBodyStream($body);
|
||||
} elseif (!$body instanceof StreamInterface) {
|
||||
throw new \InvalidArgumentException('Invalid request body given');
|
||||
}
|
||||
|
||||
parent::__construct($method, $url, $headers, $body, $version);
|
||||
}
|
||||
}
|
||||
414
vendor/react/http/src/Message/Response.php
vendored
Normal file
414
vendor/react/http/src/Message/Response.php
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Message;
|
||||
|
||||
use Fig\Http\Message\StatusCodeInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Http\Io\AbstractMessage;
|
||||
use React\Http\Io\BufferedBody;
|
||||
use React\Http\Io\HttpBodyStream;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* Represents an outgoing server response message.
|
||||
*
|
||||
* ```php
|
||||
* $response = new React\Http\Message\Response(
|
||||
* React\Http\Message\Response::STATUS_OK,
|
||||
* array(
|
||||
* 'Content-Type' => 'text/html'
|
||||
* ),
|
||||
* "<html>Hello world!</html>\n"
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This class implements the
|
||||
* [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface)
|
||||
* which in turn extends the
|
||||
* [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface).
|
||||
*
|
||||
* On top of this, this class implements the
|
||||
* [PSR-7 Message Util `StatusCodeInterface`](https://github.com/php-fig/http-message-util/blob/master/src/StatusCodeInterface.php)
|
||||
* which means that most common HTTP status codes are available as class
|
||||
* constants with the `STATUS_*` prefix. For instance, the `200 OK` and
|
||||
* `404 Not Found` status codes can used as `Response::STATUS_OK` and
|
||||
* `Response::STATUS_NOT_FOUND` respectively.
|
||||
*
|
||||
* > Internally, this implementation builds on top a base class which is
|
||||
* considered an implementation detail that may change in the future.
|
||||
*
|
||||
* @see \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
final class Response extends AbstractMessage implements ResponseInterface, StatusCodeInterface
|
||||
{
|
||||
/**
|
||||
* Create an HTML response
|
||||
*
|
||||
* ```php
|
||||
* $html = <<<HTML
|
||||
* <!doctype html>
|
||||
* <html>
|
||||
* <body>Hello wörld!</body>
|
||||
* </html>
|
||||
*
|
||||
* HTML;
|
||||
*
|
||||
* $response = React\Http\Message\Response::html($html);
|
||||
* ```
|
||||
*
|
||||
* This is a convenient shortcut method that returns the equivalent of this:
|
||||
*
|
||||
* ```
|
||||
* $response = new React\Http\Message\Response(
|
||||
* React\Http\Message\Response::STATUS_OK,
|
||||
* [
|
||||
* 'Content-Type' => 'text/html; charset=utf-8'
|
||||
* ],
|
||||
* $html
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This method always returns a response with a `200 OK` status code and
|
||||
* the appropriate `Content-Type` response header for the given HTTP source
|
||||
* string encoded in UTF-8 (Unicode). It's generally recommended to end the
|
||||
* given plaintext string with a trailing newline.
|
||||
*
|
||||
* If you want to use a different status code or custom HTTP response
|
||||
* headers, you can manipulate the returned response object using the
|
||||
* provided PSR-7 methods or directly instantiate a custom HTTP response
|
||||
* object using the `Response` constructor:
|
||||
*
|
||||
* ```php
|
||||
* $response = React\Http\Message\Response::html(
|
||||
* "<h1>Error</h1>\n<p>Invalid user name given.</p>\n"
|
||||
* )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
|
||||
* ```
|
||||
*
|
||||
* @param string $html
|
||||
* @return self
|
||||
*/
|
||||
public static function html($html)
|
||||
{
|
||||
return new self(self::STATUS_OK, array('Content-Type' => 'text/html; charset=utf-8'), $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSON response
|
||||
*
|
||||
* ```php
|
||||
* $response = React\Http\Message\Response::json(['name' => 'Alice']);
|
||||
* ```
|
||||
*
|
||||
* This is a convenient shortcut method that returns the equivalent of this:
|
||||
*
|
||||
* ```
|
||||
* $response = new React\Http\Message\Response(
|
||||
* React\Http\Message\Response::STATUS_OK,
|
||||
* [
|
||||
* 'Content-Type' => 'application/json'
|
||||
* ],
|
||||
* json_encode(
|
||||
* ['name' => 'Alice'],
|
||||
* JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION
|
||||
* ) . "\n"
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This method always returns a response with a `200 OK` status code and
|
||||
* the appropriate `Content-Type` response header for the given structured
|
||||
* data encoded as a JSON text.
|
||||
*
|
||||
* The given structured data will be encoded as a JSON text. Any `string`
|
||||
* values in the data must be encoded in UTF-8 (Unicode). If the encoding
|
||||
* fails, this method will throw an `InvalidArgumentException`.
|
||||
*
|
||||
* By default, the given structured data will be encoded with the flags as
|
||||
* shown above. This includes pretty printing (PHP 5.4+) and preserving
|
||||
* zero fractions for `float` values (PHP 5.6.6+) to ease debugging. It is
|
||||
* assumed any additional data overhead is usually compensated by using HTTP
|
||||
* response compression.
|
||||
*
|
||||
* If you want to use a different status code or custom HTTP response
|
||||
* headers, you can manipulate the returned response object using the
|
||||
* provided PSR-7 methods or directly instantiate a custom HTTP response
|
||||
* object using the `Response` constructor:
|
||||
*
|
||||
* ```php
|
||||
* $response = React\Http\Message\Response::json(
|
||||
* ['error' => 'Invalid user name given']
|
||||
* )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
|
||||
* ```
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException when encoding fails
|
||||
*/
|
||||
public static function json($data)
|
||||
{
|
||||
$json = @\json_encode(
|
||||
$data,
|
||||
(\defined('JSON_PRETTY_PRINT') ? \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE : 0) | (\defined('JSON_PRESERVE_ZERO_FRACTION') ? \JSON_PRESERVE_ZERO_FRACTION : 0)
|
||||
);
|
||||
|
||||
// throw on error, now `false` but used to be `(string) "null"` before PHP 5.5
|
||||
if ($json === false || (\PHP_VERSION_ID < 50500 && \json_last_error() !== \JSON_ERROR_NONE)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Unable to encode given data as JSON' . (\function_exists('json_last_error_msg') ? ': ' . \json_last_error_msg() : ''),
|
||||
\json_last_error()
|
||||
);
|
||||
}
|
||||
|
||||
return new self(self::STATUS_OK, array('Content-Type' => 'application/json'), $json . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plaintext response
|
||||
*
|
||||
* ```php
|
||||
* $response = React\Http\Message\Response::plaintext("Hello wörld!\n");
|
||||
* ```
|
||||
*
|
||||
* This is a convenient shortcut method that returns the equivalent of this:
|
||||
*
|
||||
* ```
|
||||
* $response = new React\Http\Message\Response(
|
||||
* React\Http\Message\Response::STATUS_OK,
|
||||
* [
|
||||
* 'Content-Type' => 'text/plain; charset=utf-8'
|
||||
* ],
|
||||
* "Hello wörld!\n"
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This method always returns a response with a `200 OK` status code and
|
||||
* the appropriate `Content-Type` response header for the given plaintext
|
||||
* string encoded in UTF-8 (Unicode). It's generally recommended to end the
|
||||
* given plaintext string with a trailing newline.
|
||||
*
|
||||
* If you want to use a different status code or custom HTTP response
|
||||
* headers, you can manipulate the returned response object using the
|
||||
* provided PSR-7 methods or directly instantiate a custom HTTP response
|
||||
* object using the `Response` constructor:
|
||||
*
|
||||
* ```php
|
||||
* $response = React\Http\Message\Response::plaintext(
|
||||
* "Error: Invalid user name given.\n"
|
||||
* )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
|
||||
* ```
|
||||
*
|
||||
* @param string $text
|
||||
* @return self
|
||||
*/
|
||||
public static function plaintext($text)
|
||||
{
|
||||
return new self(self::STATUS_OK, array('Content-Type' => 'text/plain; charset=utf-8'), $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an XML response
|
||||
*
|
||||
* ```php
|
||||
* $xml = <<<XML
|
||||
* <?xml version="1.0" encoding="utf-8"?>
|
||||
* <body>
|
||||
* <greeting>Hello wörld!</greeting>
|
||||
* </body>
|
||||
*
|
||||
* XML;
|
||||
*
|
||||
* $response = React\Http\Message\Response::xml($xml);
|
||||
* ```
|
||||
*
|
||||
* This is a convenient shortcut method that returns the equivalent of this:
|
||||
*
|
||||
* ```
|
||||
* $response = new React\Http\Message\Response(
|
||||
* React\Http\Message\Response::STATUS_OK,
|
||||
* [
|
||||
* 'Content-Type' => 'application/xml'
|
||||
* ],
|
||||
* $xml
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* This method always returns a response with a `200 OK` status code and
|
||||
* the appropriate `Content-Type` response header for the given XML source
|
||||
* string. It's generally recommended to use UTF-8 (Unicode) and specify
|
||||
* this as part of the leading XML declaration and to end the given XML
|
||||
* source string with a trailing newline.
|
||||
*
|
||||
* If you want to use a different status code or custom HTTP response
|
||||
* headers, you can manipulate the returned response object using the
|
||||
* provided PSR-7 methods or directly instantiate a custom HTTP response
|
||||
* object using the `Response` constructor:
|
||||
*
|
||||
* ```php
|
||||
* $response = React\Http\Message\Response::xml(
|
||||
* "<error><message>Invalid user name given.</message></error>\n"
|
||||
* )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
|
||||
* ```
|
||||
*
|
||||
* @param string $xml
|
||||
* @return self
|
||||
*/
|
||||
public static function xml($xml)
|
||||
{
|
||||
return new self(self::STATUS_OK, array('Content-Type' => 'application/xml'), $xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* @see self::$phrasesMap
|
||||
*/
|
||||
private static $phrasesInitialized = false;
|
||||
|
||||
/**
|
||||
* Map of standard HTTP status codes to standard reason phrases.
|
||||
*
|
||||
* This map will be fully populated with all standard reason phrases on
|
||||
* first access. By default, it only contains a subset of HTTP status codes
|
||||
* that have a custom mapping to reason phrases (such as those with dashes
|
||||
* and all caps words). See `self::STATUS_*` for all possible status code
|
||||
* constants.
|
||||
*
|
||||
* @var array<int,string>
|
||||
* @see self::STATUS_*
|
||||
* @see self::getReasonPhraseForStatusCode()
|
||||
*/
|
||||
private static $phrasesMap = array(
|
||||
200 => 'OK',
|
||||
203 => 'Non-Authoritative Information',
|
||||
207 => 'Multi-Status',
|
||||
226 => 'IM Used',
|
||||
414 => 'URI Too Large',
|
||||
418 => 'I\'m a teapot',
|
||||
505 => 'HTTP Version Not Supported'
|
||||
);
|
||||
|
||||
/** @var int */
|
||||
private $statusCode;
|
||||
|
||||
/** @var string */
|
||||
private $reasonPhrase;
|
||||
|
||||
/**
|
||||
* @param int $status HTTP status code (e.g. 200/404), see `self::STATUS_*` constants
|
||||
* @param array<string,string|string[]> $headers additional response headers
|
||||
* @param string|ReadableStreamInterface|StreamInterface $body response body
|
||||
* @param string $version HTTP protocol version (e.g. 1.1/1.0)
|
||||
* @param ?string $reason custom HTTP response phrase
|
||||
* @throws \InvalidArgumentException for an invalid body
|
||||
*/
|
||||
public function __construct(
|
||||
$status = self::STATUS_OK,
|
||||
array $headers = array(),
|
||||
$body = '',
|
||||
$version = '1.1',
|
||||
$reason = null
|
||||
) {
|
||||
if (\is_string($body)) {
|
||||
$body = new BufferedBody($body);
|
||||
} elseif ($body instanceof ReadableStreamInterface && !$body instanceof StreamInterface) {
|
||||
$body = new HttpBodyStream($body, null);
|
||||
} elseif (!$body instanceof StreamInterface) {
|
||||
throw new \InvalidArgumentException('Invalid response body given');
|
||||
}
|
||||
|
||||
parent::__construct($version, $headers, $body);
|
||||
|
||||
$this->statusCode = (int) $status;
|
||||
$this->reasonPhrase = ($reason !== '' && $reason !== null) ? (string) $reason : self::getReasonPhraseForStatusCode($status);
|
||||
}
|
||||
|
||||
public function getStatusCode()
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
public function withStatus($code, $reasonPhrase = '')
|
||||
{
|
||||
if ((string) $reasonPhrase === '') {
|
||||
$reasonPhrase = self::getReasonPhraseForStatusCode($code);
|
||||
}
|
||||
|
||||
if ($this->statusCode === (int) $code && $this->reasonPhrase === (string) $reasonPhrase) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$response = clone $this;
|
||||
$response->statusCode = (int) $code;
|
||||
$response->reasonPhrase = (string) $reasonPhrase;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function getReasonPhrase()
|
||||
{
|
||||
return $this->reasonPhrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
* @return string default reason phrase for given status code or empty string if unknown
|
||||
*/
|
||||
private static function getReasonPhraseForStatusCode($code)
|
||||
{
|
||||
if (!self::$phrasesInitialized) {
|
||||
self::$phrasesInitialized = true;
|
||||
|
||||
// map all `self::STATUS_` constants from status code to reason phrase
|
||||
// e.g. `self::STATUS_NOT_FOUND = 404` will be mapped to `404 Not Found`
|
||||
$ref = new \ReflectionClass(__CLASS__);
|
||||
foreach ($ref->getConstants() as $name => $value) {
|
||||
if (!isset(self::$phrasesMap[$value]) && \strpos($name, 'STATUS_') === 0) {
|
||||
self::$phrasesMap[$value] = \ucwords(\strtolower(\str_replace('_', ' ', \substr($name, 7))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isset(self::$phrasesMap[$code]) ? self::$phrasesMap[$code] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] Parse incoming HTTP protocol message
|
||||
*
|
||||
* @internal
|
||||
* @param string $message
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException if given $message is not a valid HTTP response message
|
||||
*/
|
||||
public static function parseMessage($message)
|
||||
{
|
||||
$start = array();
|
||||
if (!\preg_match('#^HTTP/(?<version>\d\.\d) (?<status>\d{3})(?: (?<reason>[^\r\n]*+))?[\r]?+\n#m', $message, $start)) {
|
||||
throw new \InvalidArgumentException('Unable to parse invalid status-line');
|
||||
}
|
||||
|
||||
// only support HTTP/1.1 and HTTP/1.0 requests
|
||||
if ($start['version'] !== '1.1' && $start['version'] !== '1.0') {
|
||||
throw new \InvalidArgumentException('Received response with invalid protocol version');
|
||||
}
|
||||
|
||||
// check number of valid header fields matches number of lines + status line
|
||||
$matches = array();
|
||||
$n = \preg_match_all(self::REGEX_HEADERS, $message, $matches, \PREG_SET_ORDER);
|
||||
if (\substr_count($message, "\n") !== $n + 1) {
|
||||
throw new \InvalidArgumentException('Unable to parse invalid response header fields');
|
||||
}
|
||||
|
||||
// format all header fields into associative array
|
||||
$headers = array();
|
||||
foreach ($matches as $match) {
|
||||
$headers[$match[1]][] = $match[2];
|
||||
}
|
||||
|
||||
return new self(
|
||||
(int) $start['status'],
|
||||
$headers,
|
||||
'',
|
||||
$start['version'],
|
||||
isset($start['reason']) ? $start['reason'] : ''
|
||||
);
|
||||
}
|
||||
}
|
||||
43
vendor/react/http/src/Message/ResponseException.php
vendored
Normal file
43
vendor/react/http/src/Message/ResponseException.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Message;
|
||||
|
||||
use RuntimeException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* The `React\Http\Message\ResponseException` is an `Exception` sub-class that will be used to reject
|
||||
* a request promise if the remote server returns a non-success status code
|
||||
* (anything but 2xx or 3xx).
|
||||
* You can control this behavior via the [`withRejectErrorResponse()` method](#withrejecterrorresponse).
|
||||
*
|
||||
* The `getCode(): int` method can be used to
|
||||
* return the HTTP response status code.
|
||||
*/
|
||||
final class ResponseException extends RuntimeException
|
||||
{
|
||||
private $response;
|
||||
|
||||
public function __construct(ResponseInterface $response, $message = null, $code = null, $previous = null)
|
||||
{
|
||||
if ($message === null) {
|
||||
$message = 'HTTP status code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ')';
|
||||
}
|
||||
if ($code === null) {
|
||||
$code = $response->getStatusCode();
|
||||
}
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access its underlying response object.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
331
vendor/react/http/src/Message/ServerRequest.php
vendored
Normal file
331
vendor/react/http/src/Message/ServerRequest.php
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Message;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\Http\Io\AbstractRequest;
|
||||
use React\Http\Io\BufferedBody;
|
||||
use React\Http\Io\HttpBodyStream;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* Respresents an incoming server request message.
|
||||
*
|
||||
* This class implements the
|
||||
* [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface)
|
||||
* which extends the
|
||||
* [PSR-7 `RequestInterface`](https://www.php-fig.org/psr/psr-7/#32-psrhttpmessagerequestinterface)
|
||||
* which in turn extends the
|
||||
* [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface).
|
||||
*
|
||||
* This is mostly used internally to represent each incoming request message.
|
||||
* Likewise, you can also use this class in test cases to test how your web
|
||||
* application reacts to certain HTTP requests.
|
||||
*
|
||||
* > Internally, this implementation builds on top of a base class which is
|
||||
* considered an implementation detail that may change in the future.
|
||||
*
|
||||
* @see ServerRequestInterface
|
||||
*/
|
||||
final class ServerRequest extends AbstractRequest implements ServerRequestInterface
|
||||
{
|
||||
private $attributes = array();
|
||||
|
||||
private $serverParams;
|
||||
private $fileParams = array();
|
||||
private $cookies = array();
|
||||
private $queryParams = array();
|
||||
private $parsedBody;
|
||||
|
||||
/**
|
||||
* @param string $method HTTP method for the request.
|
||||
* @param string|UriInterface $url URL for the request.
|
||||
* @param array<string,string|string[]> $headers Headers for the message.
|
||||
* @param string|ReadableStreamInterface|StreamInterface $body Message body.
|
||||
* @param string $version HTTP protocol version.
|
||||
* @param array<string,string> $serverParams server-side parameters
|
||||
* @throws \InvalidArgumentException for an invalid URL or body
|
||||
*/
|
||||
public function __construct(
|
||||
$method,
|
||||
$url,
|
||||
array $headers = array(),
|
||||
$body = '',
|
||||
$version = '1.1',
|
||||
$serverParams = array()
|
||||
) {
|
||||
if (\is_string($body)) {
|
||||
$body = new BufferedBody($body);
|
||||
} elseif ($body instanceof ReadableStreamInterface && !$body instanceof StreamInterface) {
|
||||
$temp = new self($method, '', $headers);
|
||||
$size = (int) $temp->getHeaderLine('Content-Length');
|
||||
if (\strtolower($temp->getHeaderLine('Transfer-Encoding')) === 'chunked') {
|
||||
$size = null;
|
||||
}
|
||||
$body = new HttpBodyStream($body, $size);
|
||||
} elseif (!$body instanceof StreamInterface) {
|
||||
throw new \InvalidArgumentException('Invalid server request body given');
|
||||
}
|
||||
|
||||
parent::__construct($method, $url, $headers, $body, $version);
|
||||
|
||||
$this->serverParams = $serverParams;
|
||||
|
||||
$query = $this->getUri()->getQuery();
|
||||
if ($query !== '') {
|
||||
\parse_str($query, $this->queryParams);
|
||||
}
|
||||
|
||||
// Multiple cookie headers are not allowed according
|
||||
// to https://tools.ietf.org/html/rfc6265#section-5.4
|
||||
$cookieHeaders = $this->getHeader("Cookie");
|
||||
|
||||
if (count($cookieHeaders) === 1) {
|
||||
$this->cookies = $this->parseCookie($cookieHeaders[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getServerParams()
|
||||
{
|
||||
return $this->serverParams;
|
||||
}
|
||||
|
||||
public function getCookieParams()
|
||||
{
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
public function withCookieParams(array $cookies)
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->cookies = $cookies;
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function getQueryParams()
|
||||
{
|
||||
return $this->queryParams;
|
||||
}
|
||||
|
||||
public function withQueryParams(array $query)
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->queryParams = $query;
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function getUploadedFiles()
|
||||
{
|
||||
return $this->fileParams;
|
||||
}
|
||||
|
||||
public function withUploadedFiles(array $uploadedFiles)
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->fileParams = $uploadedFiles;
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function getParsedBody()
|
||||
{
|
||||
return $this->parsedBody;
|
||||
}
|
||||
|
||||
public function withParsedBody($data)
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->parsedBody = $data;
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public function getAttribute($name, $default = null)
|
||||
{
|
||||
if (!\array_key_exists($name, $this->attributes)) {
|
||||
return $default;
|
||||
}
|
||||
return $this->attributes[$name];
|
||||
}
|
||||
|
||||
public function withAttribute($name, $value)
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->attributes[$name] = $value;
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withoutAttribute($name)
|
||||
{
|
||||
$new = clone $this;
|
||||
unset($new->attributes[$name]);
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cookie
|
||||
* @return array
|
||||
*/
|
||||
private function parseCookie($cookie)
|
||||
{
|
||||
$cookieArray = \explode(';', $cookie);
|
||||
$result = array();
|
||||
|
||||
foreach ($cookieArray as $pair) {
|
||||
$pair = \trim($pair);
|
||||
$nameValuePair = \explode('=', $pair, 2);
|
||||
|
||||
if (\count($nameValuePair) === 2) {
|
||||
$key = $nameValuePair[0];
|
||||
$value = \urldecode($nameValuePair[1]);
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] Parse incoming HTTP protocol message
|
||||
*
|
||||
* @internal
|
||||
* @param string $message
|
||||
* @param array<string,string|int|float> $serverParams
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException if given $message is not a valid HTTP request message
|
||||
*/
|
||||
public static function parseMessage($message, array $serverParams)
|
||||
{
|
||||
// parse request line like "GET /path HTTP/1.1"
|
||||
$start = array();
|
||||
if (!\preg_match('#^(?<method>[^ ]+) (?<target>[^ ]+) HTTP/(?<version>\d\.\d)#m', $message, $start)) {
|
||||
throw new \InvalidArgumentException('Unable to parse invalid request-line');
|
||||
}
|
||||
|
||||
// only support HTTP/1.1 and HTTP/1.0 requests
|
||||
if ($start['version'] !== '1.1' && $start['version'] !== '1.0') {
|
||||
throw new \InvalidArgumentException('Received request with invalid protocol version', Response::STATUS_VERSION_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
// check number of valid header fields matches number of lines + request line
|
||||
$matches = array();
|
||||
$n = \preg_match_all(self::REGEX_HEADERS, $message, $matches, \PREG_SET_ORDER);
|
||||
if (\substr_count($message, "\n") !== $n + 1) {
|
||||
throw new \InvalidArgumentException('Unable to parse invalid request header fields');
|
||||
}
|
||||
|
||||
// format all header fields into associative array
|
||||
$host = null;
|
||||
$headers = array();
|
||||
foreach ($matches as $match) {
|
||||
$headers[$match[1]][] = $match[2];
|
||||
|
||||
// match `Host` request header
|
||||
if ($host === null && \strtolower($match[1]) === 'host') {
|
||||
$host = $match[2];
|
||||
}
|
||||
}
|
||||
|
||||
// scheme is `http` unless TLS is used
|
||||
$scheme = isset($serverParams['HTTPS']) ? 'https://' : 'http://';
|
||||
|
||||
// default host if unset comes from local socket address or defaults to localhost
|
||||
$hasHost = $host !== null;
|
||||
if ($host === null) {
|
||||
$host = isset($serverParams['SERVER_ADDR'], $serverParams['SERVER_PORT']) ? $serverParams['SERVER_ADDR'] . ':' . $serverParams['SERVER_PORT'] : '127.0.0.1';
|
||||
}
|
||||
|
||||
if ($start['method'] === 'OPTIONS' && $start['target'] === '*') {
|
||||
// support asterisk-form for `OPTIONS *` request line only
|
||||
$uri = $scheme . $host;
|
||||
} elseif ($start['method'] === 'CONNECT') {
|
||||
$parts = \parse_url('tcp://' . $start['target']);
|
||||
|
||||
// check this is a valid authority-form request-target (host:port)
|
||||
if (!isset($parts['scheme'], $parts['host'], $parts['port']) || \count($parts) !== 3) {
|
||||
throw new \InvalidArgumentException('CONNECT method MUST use authority-form request target');
|
||||
}
|
||||
$uri = $scheme . $start['target'];
|
||||
} else {
|
||||
// support absolute-form or origin-form for proxy requests
|
||||
if ($start['target'][0] === '/') {
|
||||
$uri = $scheme . $host . $start['target'];
|
||||
} else {
|
||||
// ensure absolute-form request-target contains a valid URI
|
||||
$parts = \parse_url($start['target']);
|
||||
|
||||
// make sure value contains valid host component (IP or hostname), but no fragment
|
||||
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'http' || isset($parts['fragment'])) {
|
||||
throw new \InvalidArgumentException('Invalid absolute-form request-target');
|
||||
}
|
||||
|
||||
$uri = $start['target'];
|
||||
}
|
||||
}
|
||||
|
||||
$request = new self(
|
||||
$start['method'],
|
||||
$uri,
|
||||
$headers,
|
||||
'',
|
||||
$start['version'],
|
||||
$serverParams
|
||||
);
|
||||
|
||||
// only assign request target if it is not in origin-form (happy path for most normal requests)
|
||||
if ($start['target'][0] !== '/') {
|
||||
$request = $request->withRequestTarget($start['target']);
|
||||
}
|
||||
|
||||
if ($hasHost) {
|
||||
// Optional Host request header value MUST be valid (host and optional port)
|
||||
$parts = \parse_url('http://' . $request->getHeaderLine('Host'));
|
||||
|
||||
// make sure value contains valid host component (IP or hostname)
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
|
||||
$parts = false;
|
||||
}
|
||||
|
||||
// make sure value does not contain any other URI component
|
||||
if (\is_array($parts)) {
|
||||
unset($parts['scheme'], $parts['host'], $parts['port']);
|
||||
}
|
||||
if ($parts === false || $parts) {
|
||||
throw new \InvalidArgumentException('Invalid Host header value');
|
||||
}
|
||||
} elseif (!$hasHost && $start['version'] === '1.1' && $start['method'] !== 'CONNECT') {
|
||||
// require Host request header for HTTP/1.1 (except for CONNECT method)
|
||||
throw new \InvalidArgumentException('Missing required Host request header');
|
||||
} elseif (!$hasHost) {
|
||||
// remove default Host request header for HTTP/1.0 when not explicitly given
|
||||
$request = $request->withoutHeader('Host');
|
||||
}
|
||||
|
||||
// ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers
|
||||
if ($request->hasHeader('Transfer-Encoding')) {
|
||||
if (\strtolower($request->getHeaderLine('Transfer-Encoding')) !== 'chunked') {
|
||||
throw new \InvalidArgumentException('Only chunked-encoding is allowed for Transfer-Encoding', Response::STATUS_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
// Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time
|
||||
// as per https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||
if ($request->hasHeader('Content-Length')) {
|
||||
throw new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed', Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
} elseif ($request->hasHeader('Content-Length')) {
|
||||
$string = $request->getHeaderLine('Content-Length');
|
||||
|
||||
if ((string)(int)$string !== $string) {
|
||||
// Content-Length value is not an integer or not a single integer
|
||||
throw new \InvalidArgumentException('The value of `Content-Length` is not valid', Response::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
356
vendor/react/http/src/Message/Uri.php
vendored
Normal file
356
vendor/react/http/src/Message/Uri.php
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Message;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Respresents a URI (or URL).
|
||||
*
|
||||
* This class implements the
|
||||
* [PSR-7 `UriInterface`](https://www.php-fig.org/psr/psr-7/#35-psrhttpmessageuriinterface).
|
||||
*
|
||||
* This is mostly used internally to represent the URI of each HTTP request
|
||||
* message for our HTTP client and server implementations. Likewise, you may
|
||||
* also use this class with other HTTP implementations and for tests.
|
||||
*
|
||||
* @see UriInterface
|
||||
*/
|
||||
final class Uri implements UriInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $scheme = '';
|
||||
|
||||
/** @var string */
|
||||
private $userInfo = '';
|
||||
|
||||
/** @var string */
|
||||
private $host = '';
|
||||
|
||||
/** @var ?int */
|
||||
private $port = null;
|
||||
|
||||
/** @var string */
|
||||
private $path = '';
|
||||
|
||||
/** @var string */
|
||||
private $query = '';
|
||||
|
||||
/** @var string */
|
||||
private $fragment = '';
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @throws \InvalidArgumentException if given $uri is invalid
|
||||
*/
|
||||
public function __construct($uri)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\PHP_VERSION_ID < 50407 && \strpos($uri, '//') === 0) {
|
||||
// @link https://3v4l.org/UrAQP
|
||||
$parts = \parse_url('http:' . $uri);
|
||||
unset($parts['schema']);
|
||||
} else {
|
||||
$parts = \parse_url($uri);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
if ($parts === false || (isset($parts['scheme']) && !\preg_match('#^[a-z]+$#i', $parts['scheme'])) || (isset($parts['host']) && \preg_match('#[\s%+]#', $parts['host']))) {
|
||||
throw new \InvalidArgumentException('Invalid URI given');
|
||||
}
|
||||
|
||||
if (isset($parts['scheme'])) {
|
||||
$this->scheme = \strtolower($parts['scheme']);
|
||||
}
|
||||
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
$this->userInfo = $this->encode(isset($parts['user']) ? $parts['user'] : '', \PHP_URL_USER) . (isset($parts['pass']) ? ':' . $this->encode($parts['pass'], \PHP_URL_PASS) : '');
|
||||
}
|
||||
|
||||
if (isset($parts['host'])) {
|
||||
$this->host = \strtolower($parts['host']);
|
||||
}
|
||||
|
||||
if (isset($parts['port']) && !(($parts['port'] === 80 && $this->scheme === 'http') || ($parts['port'] === 443 && $this->scheme === 'https'))) {
|
||||
$this->port = $parts['port'];
|
||||
}
|
||||
|
||||
if (isset($parts['path'])) {
|
||||
$this->path = $this->encode($parts['path'], \PHP_URL_PATH);
|
||||
}
|
||||
|
||||
if (isset($parts['query'])) {
|
||||
$this->query = $this->encode($parts['query'], \PHP_URL_QUERY);
|
||||
}
|
||||
|
||||
if (isset($parts['fragment'])) {
|
||||
$this->fragment = $this->encode($parts['fragment'], \PHP_URL_FRAGMENT);
|
||||
}
|
||||
}
|
||||
|
||||
public function getScheme()
|
||||
{
|
||||
return $this->scheme;
|
||||
}
|
||||
|
||||
public function getAuthority()
|
||||
{
|
||||
if ($this->host === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return ($this->userInfo !== '' ? $this->userInfo . '@' : '') . $this->host . ($this->port !== null ? ':' . $this->port : '');
|
||||
}
|
||||
|
||||
public function getUserInfo()
|
||||
{
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
public function getHost()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function getPort()
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function getFragment()
|
||||
{
|
||||
return $this->fragment;
|
||||
}
|
||||
|
||||
public function withScheme($scheme)
|
||||
{
|
||||
$scheme = \strtolower($scheme);
|
||||
if ($scheme === $this->scheme) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!\preg_match('#^[a-z]*$#', $scheme)) {
|
||||
throw new \InvalidArgumentException('Invalid URI scheme given');
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->scheme = $scheme;
|
||||
|
||||
if (($this->port === 80 && $scheme === 'http') || ($this->port === 443 && $scheme === 'https')) {
|
||||
$new->port = null;
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withUserInfo($user, $password = null)
|
||||
{
|
||||
$userInfo = $this->encode($user, \PHP_URL_USER) . ($password !== null ? ':' . $this->encode($password, \PHP_URL_PASS) : '');
|
||||
if ($userInfo === $this->userInfo) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->userInfo = $userInfo;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withHost($host)
|
||||
{
|
||||
$host = \strtolower($host);
|
||||
if ($host === $this->host) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (\preg_match('#[\s%+]#', $host) || ($host !== '' && \parse_url('http://' . $host, \PHP_URL_HOST) !== $host)) {
|
||||
throw new \InvalidArgumentException('Invalid URI host given');
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->host = $host;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withPort($port)
|
||||
{
|
||||
$port = $port === null ? null : (int) $port;
|
||||
if (($port === 80 && $this->scheme === 'http') || ($port === 443 && $this->scheme === 'https')) {
|
||||
$port = null;
|
||||
}
|
||||
|
||||
if ($port === $this->port) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($port !== null && ($port < 1 || $port > 0xffff)) {
|
||||
throw new \InvalidArgumentException('Invalid URI port given');
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->port = $port;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withPath($path)
|
||||
{
|
||||
$path = $this->encode($path, \PHP_URL_PATH);
|
||||
if ($path === $this->path) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->path = $path;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withQuery($query)
|
||||
{
|
||||
$query = $this->encode($query, \PHP_URL_QUERY);
|
||||
if ($query === $this->query) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->query = $query;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function withFragment($fragment)
|
||||
{
|
||||
$fragment = $this->encode($fragment, \PHP_URL_FRAGMENT);
|
||||
if ($fragment === $this->fragment) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->fragment = $fragment;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$uri = '';
|
||||
if ($this->scheme !== '') {
|
||||
$uri .= $this->scheme . ':';
|
||||
}
|
||||
|
||||
$authority = $this->getAuthority();
|
||||
if ($authority !== '') {
|
||||
$uri .= '//' . $authority;
|
||||
}
|
||||
|
||||
if ($authority !== '' && isset($this->path[0]) && $this->path[0] !== '/') {
|
||||
$uri .= '/' . $this->path;
|
||||
} elseif ($authority === '' && isset($this->path[0]) && $this->path[0] === '/') {
|
||||
$uri .= '/' . \ltrim($this->path, '/');
|
||||
} else {
|
||||
$uri .= $this->path;
|
||||
}
|
||||
|
||||
if ($this->query !== '') {
|
||||
$uri .= '?' . $this->query;
|
||||
}
|
||||
|
||||
if ($this->fragment !== '') {
|
||||
$uri .= '#' . $this->fragment;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $part
|
||||
* @param int $component
|
||||
* @return string
|
||||
*/
|
||||
private function encode($part, $component)
|
||||
{
|
||||
return \preg_replace_callback(
|
||||
'/(?:[^a-z0-9_\-\.~!\$&\'\(\)\*\+,;=' . ($component === \PHP_URL_PATH ? ':@\/' : ($component === \PHP_URL_QUERY || $component === \PHP_URL_FRAGMENT ? ':@\/\?' : '')) . '%]++|%(?![a-f0-9]{2}))/i',
|
||||
function (array $match) {
|
||||
return \rawurlencode($match[0]);
|
||||
},
|
||||
$part
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] Resolve URI relative to base URI and return new absolute URI
|
||||
*
|
||||
* @internal
|
||||
* @param UriInterface $base
|
||||
* @param UriInterface $rel
|
||||
* @return UriInterface
|
||||
* @throws void
|
||||
*/
|
||||
public static function resolve(UriInterface $base, UriInterface $rel)
|
||||
{
|
||||
if ($rel->getScheme() !== '') {
|
||||
return $rel->getPath() === '' ? $rel : $rel->withPath(self::removeDotSegments($rel->getPath()));
|
||||
}
|
||||
|
||||
$reset = false;
|
||||
$new = $base;
|
||||
if ($rel->getAuthority() !== '') {
|
||||
$reset = true;
|
||||
$userInfo = \explode(':', $rel->getUserInfo(), 2);
|
||||
$new = $base->withUserInfo($userInfo[0], isset($userInfo[1]) ? $userInfo[1]: null)->withHost($rel->getHost())->withPort($rel->getPort());
|
||||
}
|
||||
|
||||
if ($reset && $rel->getPath() === '') {
|
||||
$new = $new->withPath('');
|
||||
} elseif (($path = $rel->getPath()) !== '') {
|
||||
$start = '';
|
||||
if ($path === '' || $path[0] !== '/') {
|
||||
$start = $base->getPath();
|
||||
if (\substr($start, -1) !== '/') {
|
||||
$start .= '/../';
|
||||
}
|
||||
}
|
||||
$reset = true;
|
||||
$new = $new->withPath(self::removeDotSegments($start . $path));
|
||||
}
|
||||
if ($reset || $rel->getQuery() !== '') {
|
||||
$reset = true;
|
||||
$new = $new->withQuery($rel->getQuery());
|
||||
}
|
||||
if ($reset || $rel->getFragment() !== '') {
|
||||
$new = $new->withFragment($rel->getFragment());
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
private static function removeDotSegments($path)
|
||||
{
|
||||
$segments = array();
|
||||
foreach (\explode('/', $path) as $segment) {
|
||||
if ($segment === '..') {
|
||||
\array_pop($segments);
|
||||
} elseif ($segment !== '.' && $segment !== '') {
|
||||
$segments[] = $segment;
|
||||
}
|
||||
}
|
||||
return '/' . \implode('/', $segments) . ($path !== '/' && \substr($path, -1) === '/' ? '/' : '');
|
||||
}
|
||||
}
|
||||
211
vendor/react/http/src/Middleware/LimitConcurrentRequestsMiddleware.php
vendored
Normal file
211
vendor/react/http/src/Middleware/LimitConcurrentRequestsMiddleware.php
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Io\HttpBodyStream;
|
||||
use React\Http\Io\PauseBufferStream;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* Limits how many next handlers can be executed concurrently.
|
||||
*
|
||||
* If this middleware is invoked, it will check if the number of pending
|
||||
* handlers is below the allowed limit and then simply invoke the next handler
|
||||
* and it will return whatever the next handler returns (or throws).
|
||||
*
|
||||
* If the number of pending handlers exceeds the allowed limit, the request will
|
||||
* be queued (and its streaming body will be paused) and it will return a pending
|
||||
* promise.
|
||||
* Once a pending handler returns (or throws), it will pick the oldest request
|
||||
* from this queue and invokes the next handler (and its streaming body will be
|
||||
* resumed).
|
||||
*
|
||||
* The following example shows how this middleware can be used to ensure no more
|
||||
* than 10 handlers will be invoked at once:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* new React\Http\Middleware\LimitConcurrentRequestsMiddleware(10),
|
||||
* $handler
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Similarly, this middleware is often used in combination with the
|
||||
* [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
|
||||
* to limit the total number of requests that can be buffered at once:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
|
||||
* new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
|
||||
* new React\Http\Middleware\RequestBodyParserMiddleware(),
|
||||
* $handler
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* More sophisticated examples include limiting the total number of requests
|
||||
* that can be buffered at once and then ensure the actual request handler only
|
||||
* processes one request after another without any concurrency:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
|
||||
* new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
|
||||
* new React\Http\Middleware\RequestBodyParserMiddleware(),
|
||||
* new React\Http\Middleware\LimitConcurrentRequestsMiddleware(1), // only execute 1 handler (no concurrency)
|
||||
* $handler
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @see RequestBodyBufferMiddleware
|
||||
*/
|
||||
final class LimitConcurrentRequestsMiddleware
|
||||
{
|
||||
private $limit;
|
||||
private $pending = 0;
|
||||
private $queue = array();
|
||||
|
||||
/**
|
||||
* @param int $limit Maximum amount of concurrent requests handled.
|
||||
*
|
||||
* For example when $limit is set to 10, 10 requests will flow to $next
|
||||
* while more incoming requests have to wait until one is done.
|
||||
*/
|
||||
public function __construct($limit)
|
||||
{
|
||||
$this->limit = $limit;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, $next)
|
||||
{
|
||||
// happy path: simply invoke next request handler if we're below limit
|
||||
if ($this->pending < $this->limit) {
|
||||
++$this->pending;
|
||||
|
||||
try {
|
||||
$response = $next($request);
|
||||
} catch (\Exception $e) {
|
||||
$this->processQueue();
|
||||
throw $e;
|
||||
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
|
||||
// handle Errors just like Exceptions (PHP 7+ only)
|
||||
$this->processQueue();
|
||||
throw $e; // @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// happy path: if next request handler returned immediately,
|
||||
// we can simply try to invoke the next queued request
|
||||
if ($response instanceof ResponseInterface) {
|
||||
$this->processQueue();
|
||||
return $response;
|
||||
}
|
||||
|
||||
// if the next handler returns a pending promise, we have to
|
||||
// await its resolution before invoking next queued request
|
||||
return $this->await(Promise\resolve($response));
|
||||
}
|
||||
|
||||
// if we reach this point, then this request will need to be queued
|
||||
// check if the body is streaming, in which case we need to buffer everything
|
||||
$body = $request->getBody();
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
// pause actual body to stop emitting data until the handler is called
|
||||
$size = $body->getSize();
|
||||
$body = new PauseBufferStream($body);
|
||||
$body->pauseImplicit();
|
||||
|
||||
// replace with buffering body to ensure any readable events will be buffered
|
||||
$request = $request->withBody(new HttpBodyStream(
|
||||
$body,
|
||||
$size
|
||||
));
|
||||
}
|
||||
|
||||
// get next queue position
|
||||
$queue =& $this->queue;
|
||||
$queue[] = null;
|
||||
\end($queue);
|
||||
$id = \key($queue);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use (&$queue, $id) {
|
||||
// queued promise cancelled before its next handler is invoked
|
||||
// remove from queue and reject explicitly
|
||||
unset($queue[$id]);
|
||||
$reject(new \RuntimeException('Cancelled queued next handler'));
|
||||
});
|
||||
|
||||
// queue request and process queue if pending does not exceed limit
|
||||
$queue[$id] = $deferred;
|
||||
|
||||
$pending = &$this->pending;
|
||||
$that = $this;
|
||||
return $deferred->promise()->then(function () use ($request, $next, $body, &$pending, $that) {
|
||||
// invoke next request handler
|
||||
++$pending;
|
||||
|
||||
try {
|
||||
$response = $next($request);
|
||||
} catch (\Exception $e) {
|
||||
$that->processQueue();
|
||||
throw $e;
|
||||
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
|
||||
// handle Errors just like Exceptions (PHP 7+ only)
|
||||
$that->processQueue();
|
||||
throw $e; // @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// resume readable stream and replay buffered events
|
||||
if ($body instanceof PauseBufferStream) {
|
||||
$body->resumeImplicit();
|
||||
}
|
||||
|
||||
// if the next handler returns a pending promise, we have to
|
||||
// await its resolution before invoking next queued request
|
||||
return $that->await(Promise\resolve($response));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param PromiseInterface $promise
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function await(PromiseInterface $promise)
|
||||
{
|
||||
$that = $this;
|
||||
|
||||
return $promise->then(function ($response) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return $response;
|
||||
}, function ($error) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return Promise\reject($error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function processQueue()
|
||||
{
|
||||
// skip if we're still above concurrency limit or there's no queued request waiting
|
||||
if (--$this->pending >= $this->limit || !$this->queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$first = \reset($this->queue);
|
||||
unset($this->queue[key($this->queue)]);
|
||||
|
||||
$first->resolve(null);
|
||||
}
|
||||
}
|
||||
109
vendor/react/http/src/Middleware/RequestBodyBufferMiddleware.php
vendored
Normal file
109
vendor/react/http/src/Middleware/RequestBodyBufferMiddleware.php
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Middleware;
|
||||
|
||||
use OverflowException;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Io\BufferedBody;
|
||||
use React\Http\Io\IniUtil;
|
||||
use React\Promise\Promise;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
final class RequestBodyBufferMiddleware
|
||||
{
|
||||
private $sizeLimit;
|
||||
|
||||
/**
|
||||
* @param int|string|null $sizeLimit Either an int with the max request body size
|
||||
* in bytes or an ini like size string
|
||||
* or null to use post_max_size from PHP's
|
||||
* configuration. (Note that the value from
|
||||
* the CLI configuration will be used.)
|
||||
*/
|
||||
public function __construct($sizeLimit = null)
|
||||
{
|
||||
if ($sizeLimit === null) {
|
||||
$sizeLimit = \ini_get('post_max_size');
|
||||
}
|
||||
|
||||
$this->sizeLimit = IniUtil::iniSizeToBytes($sizeLimit);
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, $next)
|
||||
{
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
// happy path: skip if body is known to be empty (or is already buffered)
|
||||
if ($size === 0 || !$body instanceof ReadableStreamInterface || !$body->isReadable()) {
|
||||
// replace with empty body if body is streaming (or buffered size exceeds limit)
|
||||
if ($body instanceof ReadableStreamInterface || $size > $this->sizeLimit) {
|
||||
$request = $request->withBody(new BufferedBody(''));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// request body of known size exceeding limit
|
||||
$sizeLimit = $this->sizeLimit;
|
||||
if ($size > $this->sizeLimit) {
|
||||
$sizeLimit = 0;
|
||||
}
|
||||
|
||||
/** @var ?\Closure $closer */
|
||||
$closer = null;
|
||||
|
||||
return new Promise(function ($resolve, $reject) use ($body, &$closer, $sizeLimit, $request, $next) {
|
||||
// buffer request body data in memory, discard but keep buffering if limit is reached
|
||||
$buffer = '';
|
||||
$bufferer = null;
|
||||
$body->on('data', $bufferer = function ($data) use (&$buffer, $sizeLimit, $body, &$bufferer) {
|
||||
$buffer .= $data;
|
||||
|
||||
// On buffer overflow keep the request body stream in,
|
||||
// but ignore the contents and wait for the close event
|
||||
// before passing the request on to the next middleware.
|
||||
if (isset($buffer[$sizeLimit])) {
|
||||
assert($bufferer instanceof \Closure);
|
||||
$body->removeListener('data', $bufferer);
|
||||
$bufferer = null;
|
||||
$buffer = '';
|
||||
}
|
||||
});
|
||||
|
||||
// call $next with current buffer and resolve or reject with its results
|
||||
$body->on('close', $closer = function () use (&$buffer, $request, $resolve, $reject, $next) {
|
||||
try {
|
||||
// resolve with result of next handler
|
||||
$resolve($next($request->withBody(new BufferedBody($buffer))));
|
||||
} catch (\Exception $e) {
|
||||
$reject($e);
|
||||
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
|
||||
// reject Errors just like Exceptions (PHP 7+)
|
||||
$reject($e); // @codeCoverageIgnoreEnd
|
||||
}
|
||||
});
|
||||
|
||||
// reject buffering if body emits error
|
||||
$body->on('error', function (\Exception $e) use ($reject, $body, $closer) {
|
||||
// remove close handler to avoid resolving, then close and reject
|
||||
assert($closer instanceof \Closure);
|
||||
$body->removeListener('close', $closer);
|
||||
$body->close();
|
||||
|
||||
$reject(new \RuntimeException(
|
||||
'Error while buffering request body: ' . $e->getMessage(),
|
||||
$e->getCode(),
|
||||
$e
|
||||
));
|
||||
});
|
||||
}, function () use ($body, &$closer) {
|
||||
// cancelled buffering: remove close handler to avoid resolving, then close and reject
|
||||
assert($closer instanceof \Closure);
|
||||
$body->removeListener('close', $closer);
|
||||
$body->close();
|
||||
|
||||
throw new \RuntimeException('Cancelled buffering request body');
|
||||
});
|
||||
}
|
||||
}
|
||||
46
vendor/react/http/src/Middleware/RequestBodyParserMiddleware.php
vendored
Normal file
46
vendor/react/http/src/Middleware/RequestBodyParserMiddleware.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Middleware;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Io\MultipartParser;
|
||||
|
||||
final class RequestBodyParserMiddleware
|
||||
{
|
||||
private $multipart;
|
||||
|
||||
/**
|
||||
* @param int|string|null $uploadMaxFilesize
|
||||
* @param int|null $maxFileUploads
|
||||
*/
|
||||
public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
|
||||
{
|
||||
$this->multipart = new MultipartParser($uploadMaxFilesize, $maxFileUploads);
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, $next)
|
||||
{
|
||||
$type = \strtolower($request->getHeaderLine('Content-Type'));
|
||||
list ($type) = \explode(';', $type);
|
||||
|
||||
if ($type === 'application/x-www-form-urlencoded') {
|
||||
return $next($this->parseFormUrlencoded($request));
|
||||
}
|
||||
|
||||
if ($type === 'multipart/form-data') {
|
||||
return $next($this->multipart->parse($request));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function parseFormUrlencoded(ServerRequestInterface $request)
|
||||
{
|
||||
// parse string into array structure
|
||||
// ignore warnings due to excessive data structures (max_input_vars and max_input_nesting_level)
|
||||
$ret = array();
|
||||
@\parse_str((string)$request->getBody(), $ret);
|
||||
|
||||
return $request->withParsedBody($ret);
|
||||
}
|
||||
}
|
||||
69
vendor/react/http/src/Middleware/StreamingRequestMiddleware.php
vendored
Normal file
69
vendor/react/http/src/Middleware/StreamingRequestMiddleware.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http\Middleware;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Process incoming requests with a streaming request body (without buffering).
|
||||
*
|
||||
* This allows you to process requests of any size without buffering the request
|
||||
* body in memory. Instead, it will represent the request body as a
|
||||
* [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
|
||||
* that emit chunks of incoming data as it is received:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* function (Psr\Http\Message\ServerRequestInterface $request) {
|
||||
* $body = $request->getBody();
|
||||
* assert($body instanceof Psr\Http\Message\StreamInterface);
|
||||
* assert($body instanceof React\Stream\ReadableStreamInterface);
|
||||
*
|
||||
* return new React\Promise\Promise(function ($resolve) use ($body) {
|
||||
* $bytes = 0;
|
||||
* $body->on('data', function ($chunk) use (&$bytes) {
|
||||
* $bytes += \count($chunk);
|
||||
* });
|
||||
* $body->on('close', function () use (&$bytes, $resolve) {
|
||||
* $resolve(new React\Http\Response(
|
||||
* 200,
|
||||
* [],
|
||||
* "Received $bytes bytes\n"
|
||||
* ));
|
||||
* });
|
||||
* });
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* See also [streaming incoming request](../../README.md#streaming-incoming-request)
|
||||
* for more details.
|
||||
*
|
||||
* Additionally, this middleware can be used in combination with the
|
||||
* [`LimitConcurrentRequestsMiddleware`](#limitconcurrentrequestsmiddleware) and
|
||||
* [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below)
|
||||
* to explicitly configure the total number of requests that can be handled at
|
||||
* once:
|
||||
*
|
||||
* ```php
|
||||
* $http = new React\Http\HttpServer(
|
||||
* new React\Http\Middleware\StreamingRequestMiddleware(),
|
||||
* new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
|
||||
* new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
|
||||
* new React\Http\Middleware\RequestBodyParserMiddleware(),
|
||||
* $handler
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* > Internally, this class is used as a "marker" to not trigger the default
|
||||
* request buffering behavior in the `HttpServer`. It does not implement any logic
|
||||
* on its own.
|
||||
*/
|
||||
final class StreamingRequestMiddleware
|
||||
{
|
||||
public function __invoke(ServerRequestInterface $request, $next)
|
||||
{
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
18
vendor/react/http/src/Server.php
vendored
Normal file
18
vendor/react/http/src/Server.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace React\Http;
|
||||
|
||||
// Deprecated `Server` is an alias for new `HttpServer` to ensure existing code continues to work as-is.
|
||||
\class_alias(__NAMESPACE__ . '\\HttpServer', __NAMESPACE__ . '\\Server', true);
|
||||
|
||||
// Aid static analysis and IDE autocompletion about this deprecation,
|
||||
// but don't actually execute during runtime because `HttpServer` is final.
|
||||
if (!\class_exists(__NAMESPACE__ . '\\Server', false)) {
|
||||
/**
|
||||
* @deprecated 1.5.0 See HttpServer instead
|
||||
* @see HttpServer
|
||||
*/
|
||||
final class Server extends HttpServer
|
||||
{
|
||||
}
|
||||
}
|
||||
785
vendor/react/socket/CHANGELOG.md
vendored
Normal file
785
vendor/react/socket/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,785 @@
|
||||
# Changelog
|
||||
|
||||
## 1.16.0 (2024-07-26)
|
||||
|
||||
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
|
||||
(#318 by @clue)
|
||||
|
||||
## 1.15.0 (2023-12-15)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#310 by @clue)
|
||||
|
||||
* Fix: Fix cancelling during the 50ms resolution delay when DNS is still pending.
|
||||
(#311 by @clue)
|
||||
|
||||
## 1.14.0 (2023-08-25)
|
||||
|
||||
* Feature: Improve Promise v3 support and use template types.
|
||||
(#307 and #309 by @clue)
|
||||
|
||||
* Improve test suite and update to collect all garbage cycles.
|
||||
(#308 by @clue)
|
||||
|
||||
## 1.13.0 (2023-06-07)
|
||||
|
||||
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
|
||||
(#305 by @clue)
|
||||
|
||||
* Feature: Improve errno detection for failed connections without `ext-sockets`.
|
||||
(#304 by @clue)
|
||||
|
||||
* Improve test suite, clean up leftover `.sock` files and report failed assertions.
|
||||
(#299, #300, #301 and #306 by @clue)
|
||||
|
||||
## 1.12.0 (2022-08-25)
|
||||
|
||||
* Feature: Forward compatibility with react/promise 3.
|
||||
(#214 by @WyriHaximus and @clue)
|
||||
|
||||
* Feature: Full support for PHP 8.2 release.
|
||||
(#298 by @WyriHaximus)
|
||||
|
||||
* Feature: Avoid unneeded syscall on socket close.
|
||||
(#292 by @clue)
|
||||
|
||||
* Feature / Fix: Improve error reporting when custom error handler is used.
|
||||
(#290 by @clue)
|
||||
|
||||
* Fix: Fix invalid references in exception stack trace.
|
||||
(#284 by @clue)
|
||||
|
||||
* Minor documentation improvements, update to use new reactphp/async package instead of clue/reactphp-block.
|
||||
(#296 by @clue, #285 by @SimonFrings and #295 by @nhedger)
|
||||
|
||||
* Improve test suite, update macOS and HHVM environment, fix optional tests for `ENETUNREACH`.
|
||||
(#288, #289 and #297 by @clue)
|
||||
|
||||
## 1.11.0 (2022-01-14)
|
||||
|
||||
* Feature: Full support for PHP 8.1 release.
|
||||
(#277 by @clue)
|
||||
|
||||
* Feature: Avoid dependency on `ext-filter`.
|
||||
(#279 by @clue)
|
||||
|
||||
* Improve test suite to skip FD test when hitting memory limit
|
||||
and skip legacy TLS 1.0 tests if disabled by system.
|
||||
(#278 and #281 by @clue and #283 by @SimonFrings)
|
||||
|
||||
## 1.10.0 (2021-11-29)
|
||||
|
||||
* Feature: Support listening on existing file descriptors (FDs) with `SocketServer`.
|
||||
(#269 by @clue)
|
||||
|
||||
```php
|
||||
$socket = new React\Socket\SocketSever('php://fd/3');
|
||||
```
|
||||
|
||||
This is particularly useful when using [systemd socket activation](https://www.freedesktop.org/software/systemd/man/systemd.socket.html) like this:
|
||||
|
||||
```bash
|
||||
$ systemd-socket-activate -l 8000 php examples/03-http-server.php php://fd/3
|
||||
```
|
||||
|
||||
* Feature: Improve error messages for failed connection attempts with `errno` and `errstr`.
|
||||
(#265, #266, #267, #270 and #271 by @clue and #268 by @SimonFrings)
|
||||
|
||||
All error messages now always include the appropriate `errno` and `errstr` to
|
||||
give more details about the error reason when available. Along with these
|
||||
error details exposed by the underlying system functions, it will also
|
||||
include the appropriate error constant name (such as `ECONNREFUSED`) when
|
||||
available. Accordingly, failed TCP/IP connections will now report the actual
|
||||
underlying error condition instead of a generic "Connection refused" error.
|
||||
Higher-level error messages will now consistently report the connection URI
|
||||
scheme and hostname used in all error messages.
|
||||
|
||||
For most common use cases this means that simply reporting the `Exception`
|
||||
message should give the most relevant details for any connection issues:
|
||||
|
||||
```php
|
||||
$connector = new React\Socket\Connector();
|
||||
$connector->connect($uri)->then(function (React\Socket\ConnectionInterface $conn) {
|
||||
// …
|
||||
}, function (Exception $e) {
|
||||
echo 'Error:' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite, test against PHP 8.1 release.
|
||||
(#274 by @SimonFrings)
|
||||
|
||||
## 1.9.0 (2021-08-03)
|
||||
|
||||
* Feature: Add new `SocketServer` and deprecate `Server` to avoid class name collisions.
|
||||
(#263 by @clue)
|
||||
|
||||
The new `SocketServer` class has been added with an improved constructor signature
|
||||
as a replacement for the previous `Server` class in order to avoid any ambiguities.
|
||||
The previous name has been deprecated and should not be used anymore.
|
||||
In its most basic form, the deprecated `Server` can now be considered an alias for new `SocketServer`.
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$socket = new React\Socket\Server(0);
|
||||
$socket = new React\Socket\Server('127.0.0.1:8000');
|
||||
$socket = new React\Socket\Server('127.0.0.1:8000', null, $context);
|
||||
$socket = new React\Socket\Server('127.0.0.1:8000', $loop, $context);
|
||||
|
||||
// new
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:0');
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:8000');
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
|
||||
$socket = new React\Socket\SocketServer('127.0.0.1:8000', $context, $loop);
|
||||
```
|
||||
|
||||
* Feature: Update `Connector` signature to take optional `$context` as first argument.
|
||||
(#264 by @clue)
|
||||
|
||||
The new signature has been added to match the new `SocketServer` and
|
||||
consistently move the now commonly unneeded loop argument to the last argument.
|
||||
The previous signature has been deprecated and should not be used anymore.
|
||||
In its most basic form, both signatures are compatible.
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
$connector = new React\Socket\Connector(null, $context);
|
||||
$connector = new React\Socket\Connector($loop, $context);
|
||||
|
||||
// new
|
||||
$connector = new React\Socket\Connector($context);
|
||||
$connector = new React\Socket\Connector($context, $loop);
|
||||
```
|
||||
|
||||
## 1.8.0 (2021-07-11)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
|
||||
(#260 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
|
||||
$connector = new React\Socket\Connector($loop);
|
||||
|
||||
// new (using default loop)
|
||||
$socket = new React\Socket\Server('127.0.0.1:8080');
|
||||
$connector = new React\Socket\Connector();
|
||||
```
|
||||
|
||||
## 1.7.0 (2021-06-25)
|
||||
|
||||
* Feature: Support falling back to multiple DNS servers from DNS config.
|
||||
(#257 by @clue)
|
||||
|
||||
If you're using the default `Connector`, it will now use all DNS servers
|
||||
configured on your system. If you have multiple DNS servers configured and
|
||||
connectivity to the primary DNS server is broken, it will now fall back to
|
||||
your other DNS servers, thus providing improved connectivity and redundancy
|
||||
for broken DNS configurations.
|
||||
|
||||
* Feature: Use round robin for happy eyeballs DNS responses (load balancing).
|
||||
(#247 by @clue)
|
||||
|
||||
If you're using the default `Connector`, it will now randomize the order of
|
||||
the IP addresses resolved via DNS when connecting. This allows the load to
|
||||
be distributed more evenly across all returned IP addresses. This can be
|
||||
used as a very basic DNS load balancing mechanism.
|
||||
|
||||
* Internal improvement to avoid unhandled rejection for future Promise API.
|
||||
(#258 by @clue)
|
||||
|
||||
* Improve test suite, use GitHub actions for continuous integration (CI).
|
||||
(#254 by @SimonFrings)
|
||||
|
||||
## 1.6.0 (2020-08-28)
|
||||
|
||||
* Feature: Support upcoming PHP 8 release.
|
||||
(#246 by @clue)
|
||||
|
||||
* Feature: Change default socket backlog size to 511.
|
||||
(#242 by @clue)
|
||||
|
||||
* Fix: Fix closing connection when cancelling during TLS handshake.
|
||||
(#241 by @clue)
|
||||
|
||||
* Fix: Fix blocking during possible `accept()` race condition
|
||||
when multiple socket servers listen on same socket address.
|
||||
(#244 by @clue)
|
||||
|
||||
* Improve test suite, update PHPUnit config and add full core team to the license.
|
||||
(#243 by @SimonFrings and #245 by @WyriHaximus)
|
||||
|
||||
## 1.5.0 (2020-07-01)
|
||||
|
||||
* Feature / Fix: Improve error handling and reporting for happy eyeballs and
|
||||
immediately try next connection when one connection attempt fails.
|
||||
(#230, #231, #232 and #233 by @clue)
|
||||
|
||||
Error messages for failed connection attempts now include more details to
|
||||
ease debugging. Additionally, the happy eyeballs algorithm has been improved
|
||||
to avoid having to wait for some timers to expire which significantly
|
||||
improves connection setup times (in particular when IPv6 isn't available).
|
||||
|
||||
* Improve test suite, minor code cleanup and improve code coverage to 100%.
|
||||
Update to PHPUnit 9 and skip legacy TLS 1.0 / TLS 1.1 tests if disabled by
|
||||
system. Run tests on Windows and simplify Travis CI test matrix for Mac OS X
|
||||
setup and skip all TLS tests on legacy HHVM.
|
||||
(#229, #235, #236 and #238 by @clue and #239 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2020-03-12)
|
||||
|
||||
A major new feature release, see [**release announcement**](https://clue.engineering/2020/introducing-ipv6-for-reactphp).
|
||||
|
||||
* Feature: Add IPv6 support to `Connector` (implement "Happy Eyeballs" algorithm to support IPv6 probing).
|
||||
IPv6 support is turned on by default, use new `happy_eyeballs` option in `Connector` to toggle behavior.
|
||||
(#196, #224 and #225 by @WyriHaximus and @clue)
|
||||
|
||||
* Feature: Default to using DNS cache (with max 256 entries) for `Connector`.
|
||||
(#226 by @clue)
|
||||
|
||||
* Add `.gitattributes` to exclude dev files from exports and some minor code style fixes.
|
||||
(#219 by @reedy and #218 by @mmoreram)
|
||||
|
||||
* Improve test suite to fix failing test cases when using new DNS component,
|
||||
significantly improve test performance by awaiting events instead of sleeping,
|
||||
exclude TLS 1.3 test on PHP 7.3, run tests on PHP 7.4 and simplify test matrix.
|
||||
(#208, #209, #210, #217 and #223 by @clue)
|
||||
|
||||
## 1.3.0 (2019-07-10)
|
||||
|
||||
* Feature: Forward compatibility with upcoming stable DNS component.
|
||||
(#206 by @clue)
|
||||
|
||||
## 1.2.1 (2019-06-03)
|
||||
|
||||
* Avoid uneeded fragmented TLS work around for PHP 7.3.3+ and
|
||||
work around failing test case detecting EOF on TLS 1.3 socket streams.
|
||||
(#201 and #202 by @clue)
|
||||
|
||||
* Improve TLS certificate/passphrase example.
|
||||
(#190 by @jsor)
|
||||
|
||||
## 1.2.0 (2019-01-07)
|
||||
|
||||
* Feature / Fix: Improve TLS 1.3 support.
|
||||
(#186 by @clue)
|
||||
|
||||
TLS 1.3 is now an official standard as of August 2018! :tada:
|
||||
The protocol has major improvements in the areas of security, performance, and privacy.
|
||||
TLS 1.3 is supported by default as of [OpenSSL 1.1.1](https://www.openssl.org/blog/blog/2018/09/11/release111/).
|
||||
For example, this version ships with Ubuntu 18.10 (and newer) by default, meaning that recent installations support TLS 1.3 out of the box :shipit:
|
||||
|
||||
* Fix: Avoid possibility of missing remote address when TLS handshake fails.
|
||||
(#188 by @clue)
|
||||
|
||||
* Improve performance by prefixing all global functions calls with `\` to skip the look up and resolve process and go straight to the global function.
|
||||
(#183 by @WyriHaximus)
|
||||
|
||||
* Update documentation to use full class names with namespaces.
|
||||
(#187 by @clue)
|
||||
|
||||
* Improve test suite to avoid some possible race conditions,
|
||||
test against PHP 7.3 on Travis and
|
||||
use dedicated `assertInstanceOf()` assertions.
|
||||
(#185 by @clue, #178 by @WyriHaximus and #181 by @carusogabriel)
|
||||
|
||||
## 1.1.0 (2018-10-01)
|
||||
|
||||
* Feature: Improve error reporting for failed connection attempts and improve
|
||||
cancellation forwarding during DNS lookup, TCP/IP connection or TLS handshake.
|
||||
(#168, #169, #170, #171, #176 and #177 by @clue)
|
||||
|
||||
All error messages now always contain a reference to the remote URI to give
|
||||
more details which connection actually failed and the reason for this error.
|
||||
Accordingly, failures during DNS lookup will now mention both the remote URI
|
||||
as well as the DNS error reason. TCP/IP connection issues and errors during
|
||||
a secure TLS handshake will both mention the remote URI as well as the
|
||||
underlying socket error. Similarly, lost/dropped connections during a TLS
|
||||
handshake will now report a lost connection instead of an empty error reason.
|
||||
|
||||
For most common use cases this means that simply reporting the `Exception`
|
||||
message should give the most relevant details for any connection issues:
|
||||
|
||||
```php
|
||||
$promise = $connector->connect('tls://example.com:443');
|
||||
$promise->then(function (ConnectionInterface $conn) use ($loop) {
|
||||
// …
|
||||
}, function (Exception $e) {
|
||||
echo $e->getMessage();
|
||||
});
|
||||
```
|
||||
|
||||
## 1.0.0 (2018-07-11)
|
||||
|
||||
* First stable LTS release, now following [SemVer](https://semver.org/).
|
||||
We'd like to emphasize that this component is production ready and battle-tested.
|
||||
We plan to support all long-term support (LTS) releases for at least 24 months,
|
||||
so you have a rock-solid foundation to build on top of.
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.8.12 release.
|
||||
|
||||
## 0.8.12 (2018-06-11)
|
||||
|
||||
* Feature: Improve memory consumption for failed and cancelled connection attempts.
|
||||
(#161 by @clue)
|
||||
|
||||
* Improve test suite to fix Travis config to test against legacy PHP 5.3 again.
|
||||
(#162 by @clue)
|
||||
|
||||
## 0.8.11 (2018-04-24)
|
||||
|
||||
* Feature: Improve memory consumption for cancelled connection attempts and
|
||||
simplify skipping DNS lookup when connecting to IP addresses.
|
||||
(#159 and #160 by @clue)
|
||||
|
||||
## 0.8.10 (2018-02-28)
|
||||
|
||||
* Feature: Update DNS dependency to support loading system default DNS
|
||||
nameserver config on all supported platforms
|
||||
(`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows)
|
||||
(#152 by @clue)
|
||||
|
||||
This means that connecting to hosts that are managed by a local DNS server,
|
||||
such as a corporate DNS server or when using Docker containers, will now
|
||||
work as expected across all platforms with no changes required:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$connector->connect('intranet.example:80')->then(function ($connection) {
|
||||
// …
|
||||
});
|
||||
```
|
||||
|
||||
## 0.8.9 (2018-01-18)
|
||||
|
||||
* Feature: Support explicitly choosing TLS version to negotiate with remote side
|
||||
by respecting `crypto_method` context parameter for all classes.
|
||||
(#149 by @clue)
|
||||
|
||||
By default, all connector and server classes support TLSv1.0+ and exclude
|
||||
support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly
|
||||
choose the TLS version you want to negotiate with the remote side:
|
||||
|
||||
```php
|
||||
// new: now supports 'crypto_method` context parameter for all classes
|
||||
$connector = new Connector($loop, array(
|
||||
'tls' => array(
|
||||
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
|
||||
)
|
||||
));
|
||||
```
|
||||
|
||||
* Minor internal clean up to unify class imports
|
||||
(#148 by @clue)
|
||||
|
||||
## 0.8.8 (2018-01-06)
|
||||
|
||||
* Improve test suite by adding test group to skip integration tests relying on
|
||||
internet connection and fix minor documentation typo.
|
||||
(#146 by @clue and #145 by @cn007b)
|
||||
|
||||
## 0.8.7 (2017-12-24)
|
||||
|
||||
* Fix: Fix closing socket resource before removing from loop
|
||||
(#141 by @clue)
|
||||
|
||||
This fixes the root cause of an uncaught `Exception` that only manifested
|
||||
itself after the recent Stream v0.7.4 component update and only if you're
|
||||
using `ext-event` (`ExtEventLoop`).
|
||||
|
||||
* Improve test suite by testing against PHP 7.2
|
||||
(#140 by @carusogabriel)
|
||||
|
||||
## 0.8.6 (2017-11-18)
|
||||
|
||||
* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme
|
||||
and add advanced `UnixServer` class.
|
||||
(#120 by @andig)
|
||||
|
||||
```php
|
||||
// new: Server now supports "unix://" scheme
|
||||
$server = new Server('unix:///tmp/server.sock', $loop);
|
||||
|
||||
// new: advanced usage
|
||||
$server = new UnixServer('/tmp/server.sock', $loop);
|
||||
```
|
||||
|
||||
* Restructure examples to ease getting started
|
||||
(#136 by @clue)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit 6 and
|
||||
ignore Mac OS X test failures for now until Travis tests work again
|
||||
(#133 by @gabriel-caruso and #134 by @clue)
|
||||
|
||||
## 0.8.5 (2017-10-23)
|
||||
|
||||
* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X
|
||||
(#123 by @andig)
|
||||
|
||||
* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed
|
||||
(#129 by @clue)
|
||||
|
||||
* Improve test suite by adding forward compatibility with PHPUnit v5 and
|
||||
forward compatibility with upcoming EventLoop releases in tests and
|
||||
test Mac OS X on Travis
|
||||
(#122 by @andig and #125, #127 and #130 by @clue)
|
||||
|
||||
* Readme improvements
|
||||
(#118 by @jsor)
|
||||
|
||||
## 0.8.4 (2017-09-16)
|
||||
|
||||
* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead
|
||||
(#117 by @clue)
|
||||
|
||||
This can be useful for consumers that do not support certain URIs, such as
|
||||
when you want to explicitly connect to a Unix domain socket (UDS) path
|
||||
instead of connecting to a default address assumed by an higher-level API:
|
||||
|
||||
```php
|
||||
$connector = new FixedUriConnector(
|
||||
'unix:///var/run/docker.sock',
|
||||
new UnixConnector($loop)
|
||||
);
|
||||
|
||||
// destination will be ignored, actually connects to Unix domain socket
|
||||
$promise = $connector->connect('localhost:80');
|
||||
```
|
||||
|
||||
## 0.8.3 (2017-09-08)
|
||||
|
||||
* Feature: Reduce memory consumption for failed connections
|
||||
(#113 by @valga)
|
||||
|
||||
* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14
|
||||
(#114 by @clue)
|
||||
|
||||
## 0.8.2 (2017-08-25)
|
||||
|
||||
* Feature: Update DNS dependency to support hosts file on all platforms
|
||||
(#112 by @clue)
|
||||
|
||||
This means that connecting to hosts such as `localhost` will now work as
|
||||
expected across all platforms with no changes required:
|
||||
|
||||
```php
|
||||
$connector = new Connector($loop);
|
||||
$connector->connect('localhost:8080')->then(function ($connection) {
|
||||
// …
|
||||
});
|
||||
```
|
||||
|
||||
## 0.8.1 (2017-08-15)
|
||||
|
||||
* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and
|
||||
target evenement 3.0 a long side 2.0 and 1.0
|
||||
(#104 by @clue and #111 by @WyriHaximus)
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build and
|
||||
fix HHVM build for now again and ignore future HHVM build errors
|
||||
(#109 and #110 by @clue)
|
||||
|
||||
* Minor documentation fixes
|
||||
(#103 by @christiaan and #108 by @hansott)
|
||||
|
||||
## 0.8.0 (2017-05-09)
|
||||
|
||||
* Feature: New `Server` class now acts as a facade for existing server classes
|
||||
and renamed old `Server` to `TcpServer` for advanced usage.
|
||||
(#96 and #97 by @clue)
|
||||
|
||||
The `Server` class is now the main class in this package that implements the
|
||||
`ServerInterface` and allows you to accept incoming streaming connections,
|
||||
such as plaintext TCP/IP or secure TLS connection streams.
|
||||
|
||||
> This is not a BC break and consumer code does not have to be updated.
|
||||
|
||||
* Feature / BC break: All addresses are now URIs that include the URI scheme
|
||||
(#98 by @clue)
|
||||
|
||||
```diff
|
||||
- $parts = parse_url('tcp://' . $conn->getRemoteAddress());
|
||||
+ $parts = parse_url($conn->getRemoteAddress());
|
||||
```
|
||||
|
||||
* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths
|
||||
(#100 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with Stream v1.0 and v0.7
|
||||
(#99 by @clue)
|
||||
|
||||
## 0.7.2 (2017-04-24)
|
||||
|
||||
* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs
|
||||
(#94 by @clue)
|
||||
|
||||
## 0.7.1 (2017-04-10)
|
||||
|
||||
* Fix: Ignore HHVM errors when closing connection that is already closing
|
||||
(#91 by @clue)
|
||||
|
||||
## 0.7.0 (2017-04-10)
|
||||
|
||||
* Feature: Merge SocketClient component into this component
|
||||
(#87 by @clue)
|
||||
|
||||
This means that this package now provides async, streaming plaintext TCP/IP
|
||||
and secure TLS socket server and client connections for ReactPHP.
|
||||
|
||||
```
|
||||
$connector = new React\Socket\Connector($loop);
|
||||
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$connection->write('…');
|
||||
});
|
||||
```
|
||||
|
||||
Accordingly, the `ConnectionInterface` is now used to represent both incoming
|
||||
server side connections as well as outgoing client side connections.
|
||||
|
||||
If you've previously used the SocketClient component to establish outgoing
|
||||
client connections, upgrading should take no longer than a few minutes.
|
||||
All classes have been merged as-is from the latest `v0.7.0` release with no
|
||||
other changes, so you can simply update your code to use the updated namespace
|
||||
like this:
|
||||
|
||||
```php
|
||||
// old from SocketClient component and namespace
|
||||
$connector = new React\SocketClient\Connector($loop);
|
||||
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$connection->write('…');
|
||||
});
|
||||
|
||||
// new
|
||||
$connector = new React\Socket\Connector($loop);
|
||||
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
|
||||
$connection->write('…');
|
||||
});
|
||||
```
|
||||
|
||||
## 0.6.0 (2017-04-04)
|
||||
|
||||
* Feature: Add `LimitingServer` to limit and keep track of open connections
|
||||
(#86 by @clue)
|
||||
|
||||
```php
|
||||
$server = new Server(0, $loop);
|
||||
$server = new LimitingServer($server, 100);
|
||||
|
||||
$server->on('connection', function (ConnectionInterface $connection) {
|
||||
$connection->write('hello there!' . PHP_EOL);
|
||||
…
|
||||
});
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `pause()` and `resume()` methods to limit active
|
||||
connections
|
||||
(#84 by @clue)
|
||||
|
||||
```php
|
||||
$server = new Server(0, $loop);
|
||||
$server->pause();
|
||||
|
||||
$loop->addTimer(1.0, function() use ($server) {
|
||||
$server->resume();
|
||||
});
|
||||
```
|
||||
|
||||
## 0.5.1 (2017-03-09)
|
||||
|
||||
* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6
|
||||
(#79 by @clue)
|
||||
|
||||
## 0.5.0 (2017-02-14)
|
||||
|
||||
* Feature / BC break: Replace `listen()` call with URIs passed to constructor
|
||||
and reject listening on hostnames with `InvalidArgumentException`
|
||||
and replace `ConnectionException` with `RuntimeException` for consistency
|
||||
(#61, #66 and #72 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($loop);
|
||||
$server->listen(8080);
|
||||
|
||||
// new
|
||||
$server = new Server(8080, $loop);
|
||||
```
|
||||
|
||||
Similarly, you can now pass a full listening URI to the constructor to change
|
||||
the listening host:
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($loop);
|
||||
$server->listen(8080, '127.0.0.1');
|
||||
|
||||
// new
|
||||
$server = new Server('127.0.0.1:8080', $loop);
|
||||
```
|
||||
|
||||
Trying to start listening on (DNS) host names will now throw an
|
||||
`InvalidArgumentException`, use IP addresses instead:
|
||||
|
||||
```php
|
||||
// old
|
||||
$server = new Server($loop);
|
||||
$server->listen(8080, 'localhost');
|
||||
|
||||
// new
|
||||
$server = new Server('127.0.0.1:8080', $loop);
|
||||
```
|
||||
|
||||
If trying to listen fails (such as if port is already in use or port below
|
||||
1024 may require root access etc.), it will now throw a `RuntimeException`,
|
||||
the `ConnectionException` class has been removed:
|
||||
|
||||
```php
|
||||
// old: throws React\Socket\ConnectionException
|
||||
$server = new Server($loop);
|
||||
$server->listen(80);
|
||||
|
||||
// new: throws RuntimeException
|
||||
$server = new Server(80, $loop);
|
||||
```
|
||||
|
||||
* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React
|
||||
(#62 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$server->shutdown();
|
||||
|
||||
// new
|
||||
$server->close();
|
||||
```
|
||||
|
||||
* Feature / BC break: Replace `getPort()` with `getAddress()`
|
||||
(#67 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
echo $server->getPort(); // 8080
|
||||
|
||||
// new
|
||||
echo $server->getAddress(); // 127.0.0.1:8080
|
||||
```
|
||||
|
||||
* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP
|
||||
(#65 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
echo $connection->getRemoteAddress(); // 192.168.0.1
|
||||
|
||||
// new
|
||||
echo $connection->getRemoteAddress(); // 192.168.0.1:51743
|
||||
```
|
||||
|
||||
* Feature / BC break: Add `getLocalAddress()` method
|
||||
(#68 by @clue)
|
||||
|
||||
```php
|
||||
echo $connection->getLocalAddress(); // 127.0.0.1:8080
|
||||
```
|
||||
|
||||
* BC break: The `Server` and `SecureServer` class are now marked `final`
|
||||
and you can no longer `extend` them
|
||||
(which was never documented or recommended anyway).
|
||||
Public properties and event handlers are now internal only.
|
||||
Please use composition instead of extension.
|
||||
(#71, #70 and #69 by @clue)
|
||||
|
||||
## 0.4.6 (2017-01-26)
|
||||
|
||||
* Feature: Support socket context options passed to `Server`
|
||||
(#64 by @clue)
|
||||
|
||||
* Fix: Properly return `null` for unknown addresses
|
||||
(#63 by @clue)
|
||||
|
||||
* Improve documentation for `ServerInterface` and lock test suite requirements
|
||||
(#60 by @clue, #57 by @shaunbramley)
|
||||
|
||||
## 0.4.5 (2017-01-08)
|
||||
|
||||
* Feature: Add `SecureServer` for secure TLS connections
|
||||
(#55 by @clue)
|
||||
|
||||
* Add functional integration tests
|
||||
(#54 by @clue)
|
||||
|
||||
## 0.4.4 (2016-12-19)
|
||||
|
||||
* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation
|
||||
(#50 by @clue)
|
||||
|
||||
* Feature / Fix: Improve test suite and switch to normal stream handler
|
||||
(#51 by @clue)
|
||||
|
||||
* Feature: Add examples
|
||||
(#49 by @clue)
|
||||
|
||||
## 0.4.3 (2016-03-01)
|
||||
|
||||
* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing
|
||||
* Support for PHP7 and HHVM
|
||||
* Support PHP 5.3 again
|
||||
|
||||
## 0.4.2 (2014-05-25)
|
||||
|
||||
* Verify stream is a valid resource in Connection
|
||||
|
||||
## 0.4.1 (2014-04-13)
|
||||
|
||||
* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev)
|
||||
* Bug fix: v0.3.4 changes merged for v0.4.1
|
||||
|
||||
## 0.3.4 (2014-03-30)
|
||||
|
||||
* Bug fix: Reset socket to non-blocking after shutting down (PHP bug)
|
||||
|
||||
## 0.4.0 (2014-02-02)
|
||||
|
||||
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
|
||||
* BC break: Update to React/Promise 2.0
|
||||
* BC break: Update to Evenement 2.0
|
||||
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
|
||||
* Bump React dependencies to v0.4
|
||||
|
||||
## 0.3.3 (2013-07-08)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.3.2 (2013-05-10)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.3.1 (2013-04-21)
|
||||
|
||||
* Feature: Support binding to IPv6 addresses (@clue)
|
||||
|
||||
## 0.3.0 (2013-04-14)
|
||||
|
||||
* Bump React dependencies to v0.3
|
||||
|
||||
## 0.2.6 (2012-12-26)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.3 (2012-11-14)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.2.0 (2012-09-10)
|
||||
|
||||
* Bump React dependencies to v0.2
|
||||
|
||||
## 0.1.1 (2012-07-12)
|
||||
|
||||
* Version bump
|
||||
|
||||
## 0.1.0 (2012-07-11)
|
||||
|
||||
* First tagged release
|
||||
21
vendor/react/socket/LICENSE
vendored
Normal file
21
vendor/react/socket/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
1564
vendor/react/socket/README.md
vendored
Normal file
1564
vendor/react/socket/README.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
52
vendor/react/socket/composer.json
vendored
Normal file
52
vendor/react/socket/composer.json
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "react/socket",
|
||||
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
|
||||
"keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"homepage": "https://clue.engineering/",
|
||||
"email": "christian@clue.engineering"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"homepage": "https://wyrihaximus.net/",
|
||||
"email": "reactphp@ceesjankiewiet.nl"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"homepage": "https://sorgalla.com/",
|
||||
"email": "jsorgalla@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"homepage": "https://cboden.dev/",
|
||||
"email": "cboden@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"react/dns": "^1.13",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
|
||||
"react/stream": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4.3 || ^3.3 || ^2",
|
||||
"react/promise-stream": "^1.4",
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"React\\Socket\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"React\\Tests\\Socket\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
183
vendor/react/socket/src/Connection.php
vendored
Normal file
183
vendor/react/socket/src/Connection.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Stream\DuplexResourceStream;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableResourceStream;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* The actual connection implementation for ConnectionInterface
|
||||
*
|
||||
* This class should only be used internally, see ConnectionInterface instead.
|
||||
*
|
||||
* @see ConnectionInterface
|
||||
* @internal
|
||||
*/
|
||||
class Connection extends EventEmitter implements ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Internal flag whether this is a Unix domain socket (UDS) connection
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public $unix = false;
|
||||
|
||||
/**
|
||||
* Internal flag whether encryption has been enabled on this connection
|
||||
*
|
||||
* Mostly used by internal StreamEncryption so that connection returns
|
||||
* `tls://` scheme for encrypted connections instead of `tcp://`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public $encryptionEnabled = false;
|
||||
|
||||
/** @internal */
|
||||
public $stream;
|
||||
|
||||
private $input;
|
||||
|
||||
public function __construct($resource, LoopInterface $loop)
|
||||
{
|
||||
// PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might
|
||||
// block with 100% CPU usage on fragmented TLS records.
|
||||
// We try to work around this by always consuming the complete receive
|
||||
// buffer at once to avoid stale data in TLS buffers. This is known to
|
||||
// work around high CPU usage for well-behaving peers, but this may
|
||||
// cause very large data chunks for high throughput scenarios. The buggy
|
||||
// behavior can still be triggered due to network I/O buffers or
|
||||
// malicious peers on affected versions, upgrading is highly recommended.
|
||||
// @link https://bugs.php.net/bug.php?id=77390
|
||||
$clearCompleteBuffer = \PHP_VERSION_ID < 70215 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70303);
|
||||
|
||||
// PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
|
||||
// chunks of data over TLS streams at once.
|
||||
// We try to work around this by limiting the write chunk size to 8192
|
||||
// bytes for older PHP versions only.
|
||||
// This is only a work-around and has a noticable performance penalty on
|
||||
// affected versions. Please update your PHP version.
|
||||
// This applies to all streams because TLS may be enabled later on.
|
||||
// See https://github.com/reactphp/socket/issues/105
|
||||
$limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104));
|
||||
|
||||
$this->input = new DuplexResourceStream(
|
||||
$resource,
|
||||
$loop,
|
||||
$clearCompleteBuffer ? -1 : null,
|
||||
new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
|
||||
);
|
||||
|
||||
$this->stream = $resource;
|
||||
|
||||
Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
|
||||
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->input->isWritable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
return $this->input->pipe($dest, $options);
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
return $this->input->write($data);
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
$this->input->end($data);
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->input->close();
|
||||
$this->handleClose();
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function handleClose()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to cleanly shut down socket and ignore any errors in case other
|
||||
// side already closed. Underlying Stream implementation will take care
|
||||
// of closing stream resource, so we otherwise keep this open here.
|
||||
@\stream_socket_shutdown($this->stream, \STREAM_SHUT_RDWR);
|
||||
}
|
||||
|
||||
public function getRemoteAddress()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->parseAddress(\stream_socket_get_name($this->stream, true));
|
||||
}
|
||||
|
||||
public function getLocalAddress()
|
||||
{
|
||||
if (!\is_resource($this->stream)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->parseAddress(\stream_socket_get_name($this->stream, false));
|
||||
}
|
||||
|
||||
private function parseAddress($address)
|
||||
{
|
||||
if ($address === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->unix) {
|
||||
// remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
|
||||
// note that technically ":" is a valid address, so keep this in place otherwise
|
||||
if (\substr($address, -1) === ':' && \defined('HHVM_VERSION_ID') && \HHVM_VERSION_ID < 31900) {
|
||||
$address = (string)\substr($address, 0, -1); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
|
||||
// PHP uses "\0" string and HHVM uses empty string (colon removed above)
|
||||
if ($address === '' || $address[0] === "\x00" ) {
|
||||
return null; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return 'unix://' . $address;
|
||||
}
|
||||
|
||||
// check if this is an IPv6 address which includes multiple colons but no square brackets
|
||||
$pos = \strrpos($address, ':');
|
||||
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
|
||||
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
|
||||
}
|
||||
}
|
||||
119
vendor/react/socket/src/ConnectionInterface.php
vendored
Normal file
119
vendor/react/socket/src/ConnectionInterface.php
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
* Any incoming and outgoing connection is represented by this interface,
|
||||
* such as a normal TCP/IP connection.
|
||||
*
|
||||
* An incoming or outgoing connection is a duplex stream (both readable and
|
||||
* writable) that implements React's
|
||||
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
|
||||
* It contains additional properties for the local and remote address (client IP)
|
||||
* where this connection has been established to/from.
|
||||
*
|
||||
* Most commonly, instances implementing this `ConnectionInterface` are emitted
|
||||
* by all classes implementing the [`ServerInterface`](#serverinterface) and
|
||||
* used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
|
||||
*
|
||||
* Because the `ConnectionInterface` implements the underlying
|
||||
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
|
||||
* you can use any of its events and methods as usual:
|
||||
*
|
||||
* ```php
|
||||
* $connection->on('data', function ($chunk) {
|
||||
* echo $chunk;
|
||||
* });
|
||||
*
|
||||
* $connection->on('end', function () {
|
||||
* echo 'ended';
|
||||
* });
|
||||
*
|
||||
* $connection->on('error', function (Exception $e) {
|
||||
* echo 'error: ' . $e->getMessage();
|
||||
* });
|
||||
*
|
||||
* $connection->on('close', function () {
|
||||
* echo 'closed';
|
||||
* });
|
||||
*
|
||||
* $connection->write($data);
|
||||
* $connection->end($data = null);
|
||||
* $connection->close();
|
||||
* // …
|
||||
* ```
|
||||
*
|
||||
* For more details, see the
|
||||
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
|
||||
*
|
||||
* @see DuplexStreamInterface
|
||||
* @see ServerInterface
|
||||
* @see ConnectorInterface
|
||||
*/
|
||||
interface ConnectionInterface extends DuplexStreamInterface
|
||||
{
|
||||
/**
|
||||
* Returns the full remote address (URI) where this connection has been established with
|
||||
*
|
||||
* ```php
|
||||
* $address = $connection->getRemoteAddress();
|
||||
* echo 'Connection with ' . $address . PHP_EOL;
|
||||
* ```
|
||||
*
|
||||
* If the remote address can not be determined or is unknown at this time (such as
|
||||
* after the connection has been closed), it MAY return a `NULL` value instead.
|
||||
*
|
||||
* Otherwise, it will return the full address (URI) as a string value, such
|
||||
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
|
||||
* `unix://example.sock` or `unix:///path/to/example.sock`.
|
||||
* Note that individual URI components are application specific and depend
|
||||
* on the underlying transport protocol.
|
||||
*
|
||||
* If this is a TCP/IP based connection and you only want the remote IP, you may
|
||||
* use something like this:
|
||||
*
|
||||
* ```php
|
||||
* $address = $connection->getRemoteAddress();
|
||||
* $ip = trim(parse_url($address, PHP_URL_HOST), '[]');
|
||||
* echo 'Connection with ' . $ip . PHP_EOL;
|
||||
* ```
|
||||
*
|
||||
* @return ?string remote address (URI) or null if unknown
|
||||
*/
|
||||
public function getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
|
||||
*
|
||||
* ```php
|
||||
* $address = $connection->getLocalAddress();
|
||||
* echo 'Connection with ' . $address . PHP_EOL;
|
||||
* ```
|
||||
*
|
||||
* If the local address can not be determined or is unknown at this time (such as
|
||||
* after the connection has been closed), it MAY return a `NULL` value instead.
|
||||
*
|
||||
* Otherwise, it will return the full address (URI) as a string value, such
|
||||
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
|
||||
* `unix://example.sock` or `unix:///path/to/example.sock`.
|
||||
* Note that individual URI components are application specific and depend
|
||||
* on the underlying transport protocol.
|
||||
*
|
||||
* This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
|
||||
* so they should not be confused.
|
||||
*
|
||||
* If your `TcpServer` instance is listening on multiple interfaces (e.g. using
|
||||
* the address `0.0.0.0`), you can use this method to find out which interface
|
||||
* actually accepted this connection (such as a public or local interface).
|
||||
*
|
||||
* If your system has multiple interfaces (e.g. a WAN and a LAN interface),
|
||||
* you can use this method to find out which interface was actually
|
||||
* used for this connection.
|
||||
*
|
||||
* @return ?string local address (URI) or null if unknown
|
||||
* @see self::getRemoteAddress()
|
||||
*/
|
||||
public function getLocalAddress();
|
||||
}
|
||||
236
vendor/react/socket/src/Connector.php
vendored
Normal file
236
vendor/react/socket/src/Connector.php
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Config\Config as DnsConfig;
|
||||
use React\Dns\Resolver\Factory as DnsFactory;
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* The `Connector` class is the main class in this package that implements the
|
||||
* `ConnectorInterface` and allows you to create streaming connections.
|
||||
*
|
||||
* You can use this connector to create any kind of streaming connections, such
|
||||
* as plaintext TCP/IP, secure TLS or local Unix connection streams.
|
||||
*
|
||||
* Under the hood, the `Connector` is implemented as a *higher-level facade*
|
||||
* for the lower-level connectors implemented in this package. This means it
|
||||
* also shares all of their features and implementation details.
|
||||
* If you want to typehint in your higher-level protocol implementation, you SHOULD
|
||||
* use the generic [`ConnectorInterface`](#connectorinterface) instead.
|
||||
*
|
||||
* @see ConnectorInterface for the base interface
|
||||
*/
|
||||
final class Connector implements ConnectorInterface
|
||||
{
|
||||
private $connectors = array();
|
||||
|
||||
/**
|
||||
* Instantiate new `Connector`
|
||||
*
|
||||
* ```php
|
||||
* $connector = new React\Socket\Connector();
|
||||
* ```
|
||||
*
|
||||
* This class takes two optional arguments for more advanced usage:
|
||||
*
|
||||
* ```php
|
||||
* // constructor signature as of v1.9.0
|
||||
* $connector = new React\Socket\Connector(array $context = [], ?LoopInterface $loop = null);
|
||||
*
|
||||
* // legacy constructor signature before v1.9.0
|
||||
* $connector = new React\Socket\Connector(?LoopInterface $loop = null, array $context = []);
|
||||
* ```
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* @param array|LoopInterface|null $context
|
||||
* @param null|LoopInterface|array $loop
|
||||
* @throws \InvalidArgumentException for invalid arguments
|
||||
*/
|
||||
public function __construct($context = array(), $loop = null)
|
||||
{
|
||||
// swap arguments for legacy constructor signature
|
||||
if (($context instanceof LoopInterface || $context === null) && (\func_num_args() <= 1 || \is_array($loop))) {
|
||||
$swap = $loop === null ? array(): $loop;
|
||||
$loop = $context;
|
||||
$context = $swap;
|
||||
}
|
||||
|
||||
if (!\is_array($context) || ($loop !== null && !$loop instanceof LoopInterface)) {
|
||||
throw new \InvalidArgumentException('Expected "array $context" and "?LoopInterface $loop" arguments');
|
||||
}
|
||||
|
||||
// apply default options if not explicitly given
|
||||
$context += array(
|
||||
'tcp' => true,
|
||||
'tls' => true,
|
||||
'unix' => true,
|
||||
|
||||
'dns' => true,
|
||||
'timeout' => true,
|
||||
'happy_eyeballs' => true,
|
||||
);
|
||||
|
||||
if ($context['timeout'] === true) {
|
||||
$context['timeout'] = (float)\ini_get("default_socket_timeout");
|
||||
}
|
||||
|
||||
if ($context['tcp'] instanceof ConnectorInterface) {
|
||||
$tcp = $context['tcp'];
|
||||
} else {
|
||||
$tcp = new TcpConnector(
|
||||
$loop,
|
||||
\is_array($context['tcp']) ? $context['tcp'] : array()
|
||||
);
|
||||
}
|
||||
|
||||
if ($context['dns'] !== false) {
|
||||
if ($context['dns'] instanceof ResolverInterface) {
|
||||
$resolver = $context['dns'];
|
||||
} else {
|
||||
if ($context['dns'] !== true) {
|
||||
$config = $context['dns'];
|
||||
} else {
|
||||
// try to load nameservers from system config or default to Google's public DNS
|
||||
$config = DnsConfig::loadSystemConfigBlocking();
|
||||
if (!$config->nameservers) {
|
||||
$config->nameservers[] = '8.8.8.8'; // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
||||
$factory = new DnsFactory();
|
||||
$resolver = $factory->createCached(
|
||||
$config,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
if ($context['happy_eyeballs'] === true) {
|
||||
$tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver);
|
||||
} else {
|
||||
$tcp = new DnsConnector($tcp, $resolver);
|
||||
}
|
||||
}
|
||||
|
||||
if ($context['tcp'] !== false) {
|
||||
$context['tcp'] = $tcp;
|
||||
|
||||
if ($context['timeout'] !== false) {
|
||||
$context['tcp'] = new TimeoutConnector(
|
||||
$context['tcp'],
|
||||
$context['timeout'],
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
$this->connectors['tcp'] = $context['tcp'];
|
||||
}
|
||||
|
||||
if ($context['tls'] !== false) {
|
||||
if (!$context['tls'] instanceof ConnectorInterface) {
|
||||
$context['tls'] = new SecureConnector(
|
||||
$tcp,
|
||||
$loop,
|
||||
\is_array($context['tls']) ? $context['tls'] : array()
|
||||
);
|
||||
}
|
||||
|
||||
if ($context['timeout'] !== false) {
|
||||
$context['tls'] = new TimeoutConnector(
|
||||
$context['tls'],
|
||||
$context['timeout'],
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
$this->connectors['tls'] = $context['tls'];
|
||||
}
|
||||
|
||||
if ($context['unix'] !== false) {
|
||||
if (!$context['unix'] instanceof ConnectorInterface) {
|
||||
$context['unix'] = new UnixConnector($loop);
|
||||
}
|
||||
$this->connectors['unix'] = $context['unix'];
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$scheme = 'tcp';
|
||||
if (\strpos($uri, '://') !== false) {
|
||||
$scheme = (string)\substr($uri, 0, \strpos($uri, '://'));
|
||||
}
|
||||
|
||||
if (!isset($this->connectors[$scheme])) {
|
||||
return \React\Promise\reject(new \RuntimeException(
|
||||
'No connector available for URI scheme "' . $scheme . '" (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
return $this->connectors[$scheme]->connect($uri);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [internal] Builds on URI from the given URI parts and ip address with original hostname as query
|
||||
*
|
||||
* @param array $parts
|
||||
* @param string $host
|
||||
* @param string $ip
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public static function uri(array $parts, $host, $ip)
|
||||
{
|
||||
$uri = '';
|
||||
|
||||
// prepend original scheme if known
|
||||
if (isset($parts['scheme'])) {
|
||||
$uri .= $parts['scheme'] . '://';
|
||||
}
|
||||
|
||||
if (\strpos($ip, ':') !== false) {
|
||||
// enclose IPv6 addresses in square brackets before appending port
|
||||
$uri .= '[' . $ip . ']';
|
||||
} else {
|
||||
$uri .= $ip;
|
||||
}
|
||||
|
||||
// append original port if known
|
||||
if (isset($parts['port'])) {
|
||||
$uri .= ':' . $parts['port'];
|
||||
}
|
||||
|
||||
// append orignal path if known
|
||||
if (isset($parts['path'])) {
|
||||
$uri .= $parts['path'];
|
||||
}
|
||||
|
||||
// append original query if known
|
||||
if (isset($parts['query'])) {
|
||||
$uri .= '?' . $parts['query'];
|
||||
}
|
||||
|
||||
// append original hostname as query if resolved via DNS and if
|
||||
// destination URI does not contain "hostname" query param already
|
||||
$args = array();
|
||||
\parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
|
||||
if ($host !== $ip && !isset($args['hostname'])) {
|
||||
$uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($host);
|
||||
}
|
||||
|
||||
// append original fragment if known
|
||||
if (isset($parts['fragment'])) {
|
||||
$uri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
||||
59
vendor/react/socket/src/ConnectorInterface.php
vendored
Normal file
59
vendor/react/socket/src/ConnectorInterface.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
/**
|
||||
* The `ConnectorInterface` is responsible for providing an interface for
|
||||
* establishing streaming connections, such as a normal TCP/IP connection.
|
||||
*
|
||||
* This is the main interface defined in this package and it is used throughout
|
||||
* React's vast ecosystem.
|
||||
*
|
||||
* Most higher-level components (such as HTTP, database or other networking
|
||||
* service clients) accept an instance implementing this interface to create their
|
||||
* TCP/IP connection to the underlying networking service.
|
||||
* This is usually done via dependency injection, so it's fairly simple to actually
|
||||
* swap this implementation against any other implementation of this interface.
|
||||
*
|
||||
* The interface only offers a single `connect()` method.
|
||||
*
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
interface ConnectorInterface
|
||||
{
|
||||
/**
|
||||
* Creates a streaming connection to the given remote address
|
||||
*
|
||||
* If returns a Promise which either fulfills with a stream implementing
|
||||
* `ConnectionInterface` on success or rejects with an `Exception` if the
|
||||
* connection is not successful.
|
||||
*
|
||||
* ```php
|
||||
* $connector->connect('google.com:443')->then(
|
||||
* function (React\Socket\ConnectionInterface $connection) {
|
||||
* // connection successfully established
|
||||
* },
|
||||
* function (Exception $error) {
|
||||
* // failed to connect due to $error
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* The returned Promise MUST be implemented in such a way that it can be
|
||||
* cancelled when it is still pending. Cancelling a pending promise MUST
|
||||
* reject its value with an Exception. It SHOULD clean up any underlying
|
||||
* resources and references as applicable.
|
||||
*
|
||||
* ```php
|
||||
* $promise = $connector->connect($uri);
|
||||
*
|
||||
* $promise->cancel();
|
||||
* ```
|
||||
*
|
||||
* @param string $uri
|
||||
* @return \React\Promise\PromiseInterface<ConnectionInterface>
|
||||
* Resolves with a `ConnectionInterface` on success or rejects with an `Exception` on error.
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
public function connect($uri);
|
||||
}
|
||||
117
vendor/react/socket/src/DnsConnector.php
vendored
Normal file
117
vendor/react/socket/src/DnsConnector.php
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
final class DnsConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $resolver;
|
||||
|
||||
public function __construct(ConnectorInterface $connector, ResolverInterface $resolver)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$original = $uri;
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
$parts = \parse_url($uri);
|
||||
if (isset($parts['scheme'])) {
|
||||
unset($parts['scheme']);
|
||||
}
|
||||
} else {
|
||||
$parts = \parse_url($uri);
|
||||
}
|
||||
|
||||
if (!$parts || !isset($parts['host'])) {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $original . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$host = \trim($parts['host'], '[]');
|
||||
$connector = $this->connector;
|
||||
|
||||
// skip DNS lookup / URI manipulation if this URI already contains an IP
|
||||
if (@\inet_pton($host) !== false) {
|
||||
return $connector->connect($original);
|
||||
}
|
||||
|
||||
$promise = $this->resolver->resolve($host);
|
||||
$resolved = null;
|
||||
|
||||
return new Promise\Promise(
|
||||
function ($resolve, $reject) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
|
||||
// resolve/reject with result of DNS lookup
|
||||
$promise->then(function ($ip) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
|
||||
$resolved = $ip;
|
||||
|
||||
return $promise = $connector->connect(
|
||||
Connector::uri($parts, $host, $ip)
|
||||
)->then(null, function (\Exception $e) use ($uri) {
|
||||
if ($e instanceof \RuntimeException) {
|
||||
$message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
|
||||
$e = new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed: ' . $message,
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as $ti => $one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as $ai => $arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
});
|
||||
}, function ($e) use ($uri, $reject) {
|
||||
$reject(new \RuntimeException('Connection to ' . $uri .' failed during DNS lookup: ' . $e->getMessage(), 0, $e));
|
||||
})->then($resolve, $reject);
|
||||
},
|
||||
function ($_, $reject) use (&$promise, &$resolved, $uri) {
|
||||
// cancellation should reject connection attempt
|
||||
// reject DNS resolution with custom reason, otherwise rely on connection cancellation below
|
||||
if ($resolved === null) {
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled during DNS lookup (ECONNABORTED)',
|
||||
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
}
|
||||
|
||||
// (try to) cancel pending DNS lookup / connection attempt
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
// overwrite callback arguments for PHP7+ only, so they do not show
|
||||
// up in the Exception trace and do not cause a possible cyclic reference.
|
||||
$_ = $reject = null;
|
||||
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
222
vendor/react/socket/src/FdServer.php
vendored
Normal file
222
vendor/react/socket/src/FdServer.php
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* [Internal] The `FdServer` class implements the `ServerInterface` and
|
||||
* is responsible for accepting connections from an existing file descriptor.
|
||||
*
|
||||
* ```php
|
||||
* $socket = new React\Socket\FdServer(3);
|
||||
* ```
|
||||
*
|
||||
* Whenever a client connects, it will emit a `connection` event with a connection
|
||||
* instance implementing `ConnectionInterface`:
|
||||
*
|
||||
* ```php
|
||||
* $socket->on('connection', function (ConnectionInterface $connection) {
|
||||
* echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
* @internal
|
||||
*/
|
||||
final class FdServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $master;
|
||||
private $loop;
|
||||
private $unix = false;
|
||||
private $listening = false;
|
||||
|
||||
/**
|
||||
* Creates a socket server and starts listening on the given file descriptor
|
||||
*
|
||||
* This starts accepting new incoming connections on the given file descriptor.
|
||||
* See also the `connection event` documented in the `ServerInterface`
|
||||
* for more details.
|
||||
*
|
||||
* ```php
|
||||
* $socket = new React\Socket\FdServer(3);
|
||||
* ```
|
||||
*
|
||||
* If the given FD is invalid or out of range, it will throw an `InvalidArgumentException`:
|
||||
*
|
||||
* ```php
|
||||
* // throws InvalidArgumentException
|
||||
* $socket = new React\Socket\FdServer(-1);
|
||||
* ```
|
||||
*
|
||||
* If the given FD appears to be valid, but listening on it fails (such as
|
||||
* if the FD does not exist or does not refer to a socket server), it will
|
||||
* throw a `RuntimeException`:
|
||||
*
|
||||
* ```php
|
||||
* // throws RuntimeException because FD does not reference a socket server
|
||||
* $socket = new React\Socket\FdServer(0, $loop);
|
||||
* ```
|
||||
*
|
||||
* Note that these error conditions may vary depending on your system and/or
|
||||
* configuration.
|
||||
* See the exception message and code for more details about the actual error
|
||||
* condition.
|
||||
*
|
||||
* @param int|string $fd FD number such as `3` or as URL in the form of `php://fd/3`
|
||||
* @param ?LoopInterface $loop
|
||||
* @throws \InvalidArgumentException if the listening address is invalid
|
||||
* @throws \RuntimeException if listening on this address fails (already in use etc.)
|
||||
*/
|
||||
public function __construct($fd, $loop = null)
|
||||
{
|
||||
if (\preg_match('#^php://fd/(\d+)$#', $fd, $m)) {
|
||||
$fd = (int) $m[1];
|
||||
}
|
||||
if (!\is_int($fd) || $fd < 0 || $fd >= \PHP_INT_MAX) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Invalid FD number given (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
);
|
||||
}
|
||||
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
|
||||
// Match errstr from PHP's warning message.
|
||||
// fopen(php://fd/3): Failed to open stream: Error duping file descriptor 3; possibly it doesn't exist: [9]: Bad file descriptor
|
||||
\preg_match('/\[(\d+)\]: (.*)/', $error, $m);
|
||||
$errno = isset($m[1]) ? (int) $m[1] : 0;
|
||||
$errstr = isset($m[2]) ? $m[2] : $error;
|
||||
});
|
||||
|
||||
$this->master = \fopen('php://fd/' . $fd, 'r+');
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if (false === $this->master) {
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on FD ' . $fd . ': ' . $errstr . SocketServer::errconst($errno),
|
||||
$errno
|
||||
);
|
||||
}
|
||||
|
||||
$meta = \stream_get_meta_data($this->master);
|
||||
if (!isset($meta['stream_type']) || $meta['stream_type'] !== 'tcp_socket') {
|
||||
\fclose($this->master);
|
||||
|
||||
$errno = \defined('SOCKET_ENOTSOCK') ? \SOCKET_ENOTSOCK : 88;
|
||||
$errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Not a socket';
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (ENOTSOCK)',
|
||||
$errno
|
||||
);
|
||||
}
|
||||
|
||||
// Socket should not have a peer address if this is a listening socket.
|
||||
// Looks like this work-around is the closest we can get because PHP doesn't expose SO_ACCEPTCONN even with ext-sockets.
|
||||
if (\stream_socket_get_name($this->master, true) !== false) {
|
||||
\fclose($this->master);
|
||||
|
||||
$errno = \defined('SOCKET_EISCONN') ? \SOCKET_EISCONN : 106;
|
||||
$errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Socket is connected';
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (EISCONN)',
|
||||
$errno
|
||||
);
|
||||
}
|
||||
|
||||
// Assume this is a Unix domain socket (UDS) when its listening address doesn't parse as a valid URL with a port.
|
||||
// Looks like this work-around is the closest we can get because PHP doesn't expose SO_DOMAIN even with ext-sockets.
|
||||
$this->unix = \parse_url($this->getAddress(), \PHP_URL_PORT) === false;
|
||||
|
||||
\stream_set_blocking($this->master, false);
|
||||
|
||||
$this->resume();
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$address = \stream_socket_get_name($this->master, false);
|
||||
|
||||
if ($this->unix === true) {
|
||||
return 'unix://' . $address;
|
||||
}
|
||||
|
||||
// check if this is an IPv6 address which includes multiple colons but no square brackets
|
||||
$pos = \strrpos($address, ':');
|
||||
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
|
||||
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return 'tcp://' . $address;
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if (!$this->listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loop->removeReadStream($this->master);
|
||||
$this->listening = false;
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->listening || !\is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$this->loop->addReadStream($this->master, function ($master) use ($that) {
|
||||
try {
|
||||
$newSocket = SocketServer::accept($master);
|
||||
} catch (\RuntimeException $e) {
|
||||
$that->emit('error', array($e));
|
||||
return;
|
||||
}
|
||||
$that->handleConnection($newSocket);
|
||||
});
|
||||
$this->listening = true;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!\is_resource($this->master)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pause();
|
||||
\fclose($this->master);
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection($socket)
|
||||
{
|
||||
$connection = new Connection($socket, $this->loop);
|
||||
$connection->unix = $this->unix;
|
||||
|
||||
$this->emit('connection', array($connection));
|
||||
}
|
||||
}
|
||||
41
vendor/react/socket/src/FixedUriConnector.php
vendored
Normal file
41
vendor/react/socket/src/FixedUriConnector.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
/**
|
||||
* Decorates an existing Connector to always use a fixed, preconfigured URI
|
||||
*
|
||||
* This can be useful for consumers that do not support certain URIs, such as
|
||||
* when you want to explicitly connect to a Unix domain socket (UDS) path
|
||||
* instead of connecting to a default address assumed by an higher-level API:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new React\Socket\FixedUriConnector(
|
||||
* 'unix:///var/run/docker.sock',
|
||||
* new React\Socket\UnixConnector()
|
||||
* );
|
||||
*
|
||||
* // destination will be ignored, actually connects to Unix domain socket
|
||||
* $promise = $connector->connect('localhost:80');
|
||||
* ```
|
||||
*/
|
||||
class FixedUriConnector implements ConnectorInterface
|
||||
{
|
||||
private $uri;
|
||||
private $connector;
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param ConnectorInterface $connector
|
||||
*/
|
||||
public function __construct($uri, ConnectorInterface $connector)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
public function connect($_)
|
||||
{
|
||||
return $this->connector->connect($this->uri);
|
||||
}
|
||||
}
|
||||
334
vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php
vendored
Normal file
334
vendor/react/socket/src/HappyEyeBallsConnectionBuilder.php
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Model\Message;
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\TimerInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HappyEyeBallsConnectionBuilder
|
||||
{
|
||||
/**
|
||||
* As long as we haven't connected yet keep popping an IP address of the connect queue until one of them
|
||||
* succeeds or they all fail. We will wait 100ms between connection attempts as per RFC.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc8305#section-5
|
||||
*/
|
||||
const CONNECTION_ATTEMPT_DELAY = 0.1;
|
||||
|
||||
/**
|
||||
* Delay `A` lookup by 50ms sending out connection to IPv4 addresses when IPv6 records haven't
|
||||
* resolved yet as per RFC.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc8305#section-3
|
||||
*/
|
||||
const RESOLUTION_DELAY = 0.05;
|
||||
|
||||
public $loop;
|
||||
public $connector;
|
||||
public $resolver;
|
||||
public $uri;
|
||||
public $host;
|
||||
public $resolved = array(
|
||||
Message::TYPE_A => false,
|
||||
Message::TYPE_AAAA => false,
|
||||
);
|
||||
public $resolverPromises = array();
|
||||
public $connectionPromises = array();
|
||||
public $connectQueue = array();
|
||||
public $nextAttemptTimer;
|
||||
public $parts;
|
||||
public $ipsCount = 0;
|
||||
public $failureCount = 0;
|
||||
public $resolve;
|
||||
public $reject;
|
||||
|
||||
public $lastErrorFamily;
|
||||
public $lastError6;
|
||||
public $lastError4;
|
||||
|
||||
public function __construct(LoopInterface $loop, ConnectorInterface $connector, ResolverInterface $resolver, $uri, $host, $parts)
|
||||
{
|
||||
$this->loop = $loop;
|
||||
$this->connector = $connector;
|
||||
$this->resolver = $resolver;
|
||||
$this->uri = $uri;
|
||||
$this->host = $host;
|
||||
$this->parts = $parts;
|
||||
}
|
||||
|
||||
public function connect()
|
||||
{
|
||||
$that = $this;
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($that) {
|
||||
$lookupResolve = function ($type) use ($that, $resolve, $reject) {
|
||||
return function (array $ips) use ($that, $type, $resolve, $reject) {
|
||||
unset($that->resolverPromises[$type]);
|
||||
$that->resolved[$type] = true;
|
||||
|
||||
$that->mixIpsIntoConnectQueue($ips);
|
||||
|
||||
// start next connection attempt if not already awaiting next
|
||||
if ($that->nextAttemptTimer === null && $that->connectQueue) {
|
||||
$that->check($resolve, $reject);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$that->resolverPromises[Message::TYPE_AAAA] = $that->resolve(Message::TYPE_AAAA, $reject)->then($lookupResolve(Message::TYPE_AAAA));
|
||||
$that->resolverPromises[Message::TYPE_A] = $that->resolve(Message::TYPE_A, $reject)->then(function (array $ips) use ($that) {
|
||||
// happy path: IPv6 has resolved already (or could not resolve), continue with IPv4 addresses
|
||||
if ($that->resolved[Message::TYPE_AAAA] === true || !$ips) {
|
||||
return $ips;
|
||||
}
|
||||
|
||||
// Otherwise delay processing IPv4 lookup until short timer passes or IPv6 resolves in the meantime
|
||||
$deferred = new Promise\Deferred(function () use (&$ips) {
|
||||
// discard all IPv4 addresses if cancelled
|
||||
$ips = array();
|
||||
});
|
||||
$timer = $that->loop->addTimer($that::RESOLUTION_DELAY, function () use ($deferred, $ips) {
|
||||
$deferred->resolve($ips);
|
||||
});
|
||||
|
||||
$that->resolverPromises[Message::TYPE_AAAA]->then(function () use ($that, $timer, $deferred, &$ips) {
|
||||
$that->loop->cancelTimer($timer);
|
||||
$deferred->resolve($ips);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
})->then($lookupResolve(Message::TYPE_A));
|
||||
}, function ($_, $reject) use ($that) {
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $that->uri . ' cancelled' . (!$that->connectionPromises ? ' during DNS lookup' : '') . ' (ECONNABORTED)',
|
||||
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
$_ = $reject = null;
|
||||
|
||||
$that->cleanUp();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param int $type DNS query type
|
||||
* @param callable $reject
|
||||
* @return \React\Promise\PromiseInterface<string[]> Returns a promise that
|
||||
* always resolves with a list of IP addresses on success or an empty
|
||||
* list on error.
|
||||
*/
|
||||
public function resolve($type, $reject)
|
||||
{
|
||||
$that = $this;
|
||||
return $that->resolver->resolveAll($that->host, $type)->then(null, function (\Exception $e) use ($type, $reject, $that) {
|
||||
unset($that->resolverPromises[$type]);
|
||||
$that->resolved[$type] = true;
|
||||
|
||||
if ($type === Message::TYPE_A) {
|
||||
$that->lastError4 = $e->getMessage();
|
||||
$that->lastErrorFamily = 4;
|
||||
} else {
|
||||
$that->lastError6 = $e->getMessage();
|
||||
$that->lastErrorFamily = 6;
|
||||
}
|
||||
|
||||
// cancel next attempt timer when there are no more IPs to connect to anymore
|
||||
if ($that->nextAttemptTimer !== null && !$that->connectQueue) {
|
||||
$that->loop->cancelTimer($that->nextAttemptTimer);
|
||||
$that->nextAttemptTimer = null;
|
||||
}
|
||||
|
||||
if ($that->hasBeenResolved() && $that->ipsCount === 0) {
|
||||
$reject(new \RuntimeException(
|
||||
$that->error(),
|
||||
0,
|
||||
$e
|
||||
));
|
||||
}
|
||||
|
||||
// Exception already handled above, so don't throw an unhandled rejection here
|
||||
return array();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function check($resolve, $reject)
|
||||
{
|
||||
$ip = \array_shift($this->connectQueue);
|
||||
|
||||
// start connection attempt and remember array position to later unset again
|
||||
$this->connectionPromises[] = $this->attemptConnection($ip);
|
||||
\end($this->connectionPromises);
|
||||
$index = \key($this->connectionPromises);
|
||||
|
||||
$that = $this;
|
||||
$that->connectionPromises[$index]->then(function ($connection) use ($that, $index, $resolve) {
|
||||
unset($that->connectionPromises[$index]);
|
||||
|
||||
$that->cleanUp();
|
||||
|
||||
$resolve($connection);
|
||||
}, function (\Exception $e) use ($that, $index, $ip, $resolve, $reject) {
|
||||
unset($that->connectionPromises[$index]);
|
||||
|
||||
$that->failureCount++;
|
||||
|
||||
$message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
|
||||
if (\strpos($ip, ':') === false) {
|
||||
$that->lastError4 = $message;
|
||||
$that->lastErrorFamily = 4;
|
||||
} else {
|
||||
$that->lastError6 = $message;
|
||||
$that->lastErrorFamily = 6;
|
||||
}
|
||||
|
||||
// start next connection attempt immediately on error
|
||||
if ($that->connectQueue) {
|
||||
if ($that->nextAttemptTimer !== null) {
|
||||
$that->loop->cancelTimer($that->nextAttemptTimer);
|
||||
$that->nextAttemptTimer = null;
|
||||
}
|
||||
|
||||
$that->check($resolve, $reject);
|
||||
}
|
||||
|
||||
if ($that->hasBeenResolved() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($that->ipsCount === $that->failureCount) {
|
||||
$that->cleanUp();
|
||||
|
||||
$reject(new \RuntimeException(
|
||||
$that->error(),
|
||||
$e->getCode(),
|
||||
$e
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
// Allow next connection attempt in 100ms: https://tools.ietf.org/html/rfc8305#section-5
|
||||
// Only start timer when more IPs are queued or when DNS query is still pending (might add more IPs)
|
||||
if ($this->nextAttemptTimer === null && (\count($this->connectQueue) > 0 || $this->resolved[Message::TYPE_A] === false || $this->resolved[Message::TYPE_AAAA] === false)) {
|
||||
$this->nextAttemptTimer = $this->loop->addTimer(self::CONNECTION_ATTEMPT_DELAY, function () use ($that, $resolve, $reject) {
|
||||
$that->nextAttemptTimer = null;
|
||||
|
||||
if ($that->connectQueue) {
|
||||
$that->check($resolve, $reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function attemptConnection($ip)
|
||||
{
|
||||
$uri = Connector::uri($this->parts, $this->host, $ip);
|
||||
|
||||
return $this->connector->connect($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function cleanUp()
|
||||
{
|
||||
// clear list of outstanding IPs to avoid creating new connections
|
||||
$this->connectQueue = array();
|
||||
|
||||
// cancel pending connection attempts
|
||||
foreach ($this->connectionPromises as $connectionPromise) {
|
||||
if ($connectionPromise instanceof PromiseInterface && \method_exists($connectionPromise, 'cancel')) {
|
||||
$connectionPromise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// cancel pending DNS resolution (cancel IPv4 first in case it is awaiting IPv6 resolution delay)
|
||||
foreach (\array_reverse($this->resolverPromises) as $resolverPromise) {
|
||||
if ($resolverPromise instanceof PromiseInterface && \method_exists($resolverPromise, 'cancel')) {
|
||||
$resolverPromise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->nextAttemptTimer instanceof TimerInterface) {
|
||||
$this->loop->cancelTimer($this->nextAttemptTimer);
|
||||
$this->nextAttemptTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function hasBeenResolved()
|
||||
{
|
||||
foreach ($this->resolved as $typeHasBeenResolved) {
|
||||
if ($typeHasBeenResolved === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes an array of IP addresses into the connect queue in such a way they alternate when attempting to connect.
|
||||
* The goal behind it is first attempt to connect to IPv6, then to IPv4, then to IPv6 again until one of those
|
||||
* attempts succeeds.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc8305#section-4
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function mixIpsIntoConnectQueue(array $ips)
|
||||
{
|
||||
\shuffle($ips);
|
||||
$this->ipsCount += \count($ips);
|
||||
$connectQueueStash = $this->connectQueue;
|
||||
$this->connectQueue = array();
|
||||
while (\count($connectQueueStash) > 0 || \count($ips) > 0) {
|
||||
if (\count($ips) > 0) {
|
||||
$this->connectQueue[] = \array_shift($ips);
|
||||
}
|
||||
if (\count($connectQueueStash) > 0) {
|
||||
$this->connectQueue[] = \array_shift($connectQueueStash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return string
|
||||
*/
|
||||
public function error()
|
||||
{
|
||||
if ($this->lastError4 === $this->lastError6) {
|
||||
$message = $this->lastError6;
|
||||
} elseif ($this->lastErrorFamily === 6) {
|
||||
$message = 'Last error for IPv6: ' . $this->lastError6 . '. Previous error for IPv4: ' . $this->lastError4;
|
||||
} else {
|
||||
$message = 'Last error for IPv4: ' . $this->lastError4 . '. Previous error for IPv6: ' . $this->lastError6;
|
||||
}
|
||||
|
||||
if ($this->hasBeenResolved() && $this->ipsCount === 0) {
|
||||
if ($this->lastError6 === $this->lastError4) {
|
||||
$message = ' during DNS lookup: ' . $this->lastError6;
|
||||
} else {
|
||||
$message = ' during DNS lookup. ' . $message;
|
||||
}
|
||||
} else {
|
||||
$message = ': ' . $message;
|
||||
}
|
||||
|
||||
return 'Connection to ' . $this->uri . ' failed' . $message;
|
||||
}
|
||||
}
|
||||
80
vendor/react/socket/src/HappyEyeBallsConnector.php
vendored
Normal file
80
vendor/react/socket/src/HappyEyeBallsConnector.php
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\Dns\Resolver\ResolverInterface;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
|
||||
final class HappyEyeBallsConnector implements ConnectorInterface
|
||||
{
|
||||
private $loop;
|
||||
private $connector;
|
||||
private $resolver;
|
||||
|
||||
/**
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ConnectorInterface $connector
|
||||
* @param ResolverInterface $resolver
|
||||
*/
|
||||
public function __construct($loop = null, $connector = null, $resolver = null)
|
||||
{
|
||||
// $connector and $resolver arguments are actually required, marked
|
||||
// optional for technical reasons only. Nullable $loop without default
|
||||
// requires PHP 7.1, null default is also supported in legacy PHP
|
||||
// versions, but required parameters are not allowed after arguments
|
||||
// with null default. Mark all parameters optional and check accordingly.
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
if (!$connector instanceof ConnectorInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($connector) expected React\Socket\ConnectorInterface');
|
||||
}
|
||||
if (!$resolver instanceof ResolverInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #3 ($resolver) expected React\Dns\Resolver\ResolverInterface');
|
||||
}
|
||||
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
$this->connector = $connector;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$original = $uri;
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
$parts = \parse_url($uri);
|
||||
if (isset($parts['scheme'])) {
|
||||
unset($parts['scheme']);
|
||||
}
|
||||
} else {
|
||||
$parts = \parse_url($uri);
|
||||
}
|
||||
|
||||
if (!$parts || !isset($parts['host'])) {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $original . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$host = \trim($parts['host'], '[]');
|
||||
|
||||
// skip DNS lookup / URI manipulation if this URI already contains an IP
|
||||
if (@\inet_pton($host) !== false) {
|
||||
return $this->connector->connect($original);
|
||||
}
|
||||
|
||||
$builder = new HappyEyeBallsConnectionBuilder(
|
||||
$this->loop,
|
||||
$this->connector,
|
||||
$this->resolver,
|
||||
$uri,
|
||||
$host,
|
||||
$parts
|
||||
);
|
||||
return $builder->connect();
|
||||
}
|
||||
}
|
||||
203
vendor/react/socket/src/LimitingServer.php
vendored
Normal file
203
vendor/react/socket/src/LimitingServer.php
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Exception;
|
||||
use OverflowException;
|
||||
|
||||
/**
|
||||
* The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
|
||||
* for limiting and keeping track of open connections to this server instance.
|
||||
*
|
||||
* Whenever the underlying server emits a `connection` event, it will check its
|
||||
* limits and then either
|
||||
* - keep track of this connection by adding it to the list of
|
||||
* open connections and then forward the `connection` event
|
||||
* - or reject (close) the connection when its limits are exceeded and will
|
||||
* forward an `error` event instead.
|
||||
*
|
||||
* Whenever a connection closes, it will remove this connection from the list of
|
||||
* open connections.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\LimitingServer($server, 100);
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
class LimitingServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $connections = array();
|
||||
private $server;
|
||||
private $limit;
|
||||
|
||||
private $pauseOnLimit = false;
|
||||
private $autoPaused = false;
|
||||
private $manuPaused = false;
|
||||
|
||||
/**
|
||||
* Instantiates a new LimitingServer.
|
||||
*
|
||||
* You have to pass a maximum number of open connections to ensure
|
||||
* the server will automatically reject (close) connections once this limit
|
||||
* is exceeded. In this case, it will emit an `error` event to inform about
|
||||
* this and no `connection` event will be emitted.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\LimitingServer($server, 100);
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* You MAY pass a `null` limit in order to put no limit on the number of
|
||||
* open connections and keep accepting new connection until you run out of
|
||||
* operating system resources (such as open file handles). This may be
|
||||
* useful if you do not want to take care of applying a limit but still want
|
||||
* to use the `getConnections()` method.
|
||||
*
|
||||
* You can optionally configure the server to pause accepting new
|
||||
* connections once the connection limit is reached. In this case, it will
|
||||
* pause the underlying server and no longer process any new connections at
|
||||
* all, thus also no longer closing any excessive connections.
|
||||
* The underlying operating system is responsible for keeping a backlog of
|
||||
* pending connections until its limit is reached, at which point it will
|
||||
* start rejecting further connections.
|
||||
* Once the server is below the connection limit, it will continue consuming
|
||||
* connections from the backlog and will process any outstanding data on
|
||||
* each connection.
|
||||
* This mode may be useful for some protocols that are designed to wait for
|
||||
* a response message (such as HTTP), but may be less useful for other
|
||||
* protocols that demand immediate responses (such as a "welcome" message in
|
||||
* an interactive chat).
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\LimitingServer($server, 100, true);
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param ServerInterface $server
|
||||
* @param int|null $connectionLimit
|
||||
* @param bool $pauseOnLimit
|
||||
*/
|
||||
public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
|
||||
{
|
||||
$this->server = $server;
|
||||
$this->limit = $connectionLimit;
|
||||
if ($connectionLimit !== null) {
|
||||
$this->pauseOnLimit = $pauseOnLimit;
|
||||
}
|
||||
|
||||
$this->server->on('connection', array($this, 'handleConnection'));
|
||||
$this->server->on('error', array($this, 'handleError'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all currently active connections
|
||||
*
|
||||
* ```php
|
||||
* foreach ($server->getConnection() as $connection) {
|
||||
* $connection->write('Hi!');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @return ConnectionInterface[]
|
||||
*/
|
||||
public function getConnections()
|
||||
{
|
||||
return $this->connections;
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->server->getAddress();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
if (!$this->manuPaused) {
|
||||
$this->manuPaused = true;
|
||||
|
||||
if (!$this->autoPaused) {
|
||||
$this->server->pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
if ($this->manuPaused) {
|
||||
$this->manuPaused = false;
|
||||
|
||||
if (!$this->autoPaused) {
|
||||
$this->server->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->server->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection(ConnectionInterface $connection)
|
||||
{
|
||||
// close connection if limit exceeded
|
||||
if ($this->limit !== null && \count($this->connections) >= $this->limit) {
|
||||
$this->handleError(new \OverflowException('Connection closed because server reached connection limit'));
|
||||
$connection->close();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->connections[] = $connection;
|
||||
$that = $this;
|
||||
$connection->on('close', function () use ($that, $connection) {
|
||||
$that->handleDisconnection($connection);
|
||||
});
|
||||
|
||||
// pause accepting new connections if limit exceeded
|
||||
if ($this->pauseOnLimit && !$this->autoPaused && \count($this->connections) >= $this->limit) {
|
||||
$this->autoPaused = true;
|
||||
|
||||
if (!$this->manuPaused) {
|
||||
$this->server->pause();
|
||||
}
|
||||
}
|
||||
|
||||
$this->emit('connection', array($connection));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleDisconnection(ConnectionInterface $connection)
|
||||
{
|
||||
unset($this->connections[\array_search($connection, $this->connections)]);
|
||||
|
||||
// continue accepting new connection if below limit
|
||||
if ($this->autoPaused && \count($this->connections) < $this->limit) {
|
||||
$this->autoPaused = false;
|
||||
|
||||
if (!$this->manuPaused) {
|
||||
$this->server->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $error)
|
||||
{
|
||||
$this->emit('error', array($error));
|
||||
}
|
||||
}
|
||||
132
vendor/react/socket/src/SecureConnector.php
vendored
Normal file
132
vendor/react/socket/src/SecureConnector.php
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use BadMethodCallException;
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
final class SecureConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $streamEncryption;
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @param ConnectorInterface $connector
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
*/
|
||||
public function __construct(ConnectorInterface $connector, $loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
$this->connector = $connector;
|
||||
$this->streamEncryption = new StreamEncryption($loop ?: Loop::get(), false);
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
if (!\function_exists('stream_socket_enable_crypto')) {
|
||||
return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if (\strpos($uri, '://') === false) {
|
||||
$uri = 'tls://' . $uri;
|
||||
}
|
||||
|
||||
$parts = \parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
|
||||
return Promise\reject(new \InvalidArgumentException(
|
||||
'Given URI "' . $uri . '" is invalid (EINVAL)',
|
||||
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
|
||||
));
|
||||
}
|
||||
|
||||
$context = $this->context;
|
||||
$encryption = $this->streamEncryption;
|
||||
$connected = false;
|
||||
/** @var \React\Promise\PromiseInterface<ConnectionInterface> $promise */
|
||||
$promise = $this->connector->connect(
|
||||
\str_replace('tls://', '', $uri)
|
||||
)->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri, &$promise, &$connected) {
|
||||
// (unencrypted) TCP/IP connection succeeded
|
||||
$connected = true;
|
||||
|
||||
if (!$connection instanceof Connection) {
|
||||
$connection->close();
|
||||
throw new \UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
|
||||
}
|
||||
|
||||
// set required SSL/TLS context options
|
||||
foreach ($context as $name => $value) {
|
||||
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
|
||||
}
|
||||
|
||||
// try to enable encryption
|
||||
return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) {
|
||||
// establishing encryption failed => close invalid connection and return error
|
||||
$connection->close();
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(),
|
||||
$error->getCode()
|
||||
);
|
||||
});
|
||||
}, function (\Exception $e) use ($uri) {
|
||||
if ($e instanceof \RuntimeException) {
|
||||
$message = \preg_replace('/^Connection to [^ ]+/', '', $e->getMessage());
|
||||
$e = new \RuntimeException(
|
||||
'Connection to ' . $uri . $message,
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
|
||||
// Exception trace arguments are not available on some PHP 7.4 installs
|
||||
// @codeCoverageIgnoreStart
|
||||
foreach ($trace as $ti => $one) {
|
||||
if (isset($one['args'])) {
|
||||
foreach ($one['args'] as $ai => $arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
$r->setValue($e, $trace);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
});
|
||||
|
||||
return new \React\Promise\Promise(
|
||||
function ($resolve, $reject) use ($promise) {
|
||||
$promise->then($resolve, $reject);
|
||||
},
|
||||
function ($_, $reject) use (&$promise, $uri, &$connected) {
|
||||
if ($connected) {
|
||||
$reject(new \RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled during TLS handshake (ECONNABORTED)',
|
||||
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
}
|
||||
|
||||
$promise->cancel();
|
||||
$promise = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
210
vendor/react/socket/src/SecureServer.php
vendored
Normal file
210
vendor/react/socket/src/SecureServer.php
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace React\Socket;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use BadMethodCallException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* The `SecureServer` class implements the `ServerInterface` and is responsible
|
||||
* for providing a secure TLS (formerly known as SSL) server.
|
||||
*
|
||||
* It does so by wrapping a `TcpServer` instance which waits for plaintext
|
||||
* TCP/IP connections and then performs a TLS handshake for each connection.
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8000);
|
||||
* $server = new React\Socket\SecureServer($server, null, array(
|
||||
* // tls context options here…
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Whenever a client completes the TLS handshake, it will emit a `connection` event
|
||||
* with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
|
||||
*
|
||||
* ```php
|
||||
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
||||
* echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
|
||||
*
|
||||
* $connection->write('hello there!' . PHP_EOL);
|
||||
* …
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Whenever a client fails to perform a successful TLS handshake, it will emit an
|
||||
* `error` event and then close the underlying TCP/IP connection:
|
||||
*
|
||||
* ```php
|
||||
* $server->on('error', function (Exception $e) {
|
||||
* echo 'Error' . $e->getMessage() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See also the `ServerInterface` for more details.
|
||||
*
|
||||
* Note that the `SecureServer` class is a concrete implementation for TLS sockets.
|
||||
* If you want to typehint in your higher-level protocol implementation, you SHOULD
|
||||
* use the generic `ServerInterface` instead.
|
||||
*
|
||||
* @see ServerInterface
|
||||
* @see ConnectionInterface
|
||||
*/
|
||||
final class SecureServer extends EventEmitter implements ServerInterface
|
||||
{
|
||||
private $tcp;
|
||||
private $encryption;
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* Creates a secure TLS server and starts waiting for incoming connections
|
||||
*
|
||||
* It does so by wrapping a `TcpServer` instance which waits for plaintext
|
||||
* TCP/IP connections and then performs a TLS handshake for each connection.
|
||||
* It thus requires valid [TLS context options],
|
||||
* which in its most basic form may look something like this if you're using a
|
||||
* PEM encoded certificate file:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8000);
|
||||
* $server = new React\Socket\SecureServer($server, null, array(
|
||||
* 'local_cert' => 'server.pem'
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Note that the certificate file will not be loaded on instantiation but when an
|
||||
* incoming connection initializes its TLS context.
|
||||
* This implies that any invalid certificate file paths or contents will only cause
|
||||
* an `error` event at a later time.
|
||||
*
|
||||
* If your private key is encrypted with a passphrase, you have to specify it
|
||||
* like this:
|
||||
*
|
||||
* ```php
|
||||
* $server = new React\Socket\TcpServer(8000);
|
||||
* $server = new React\Socket\SecureServer($server, null, array(
|
||||
* 'local_cert' => 'server.pem',
|
||||
* 'passphrase' => 'secret'
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Note that available [TLS context options],
|
||||
* their defaults and effects of changing these may vary depending on your system
|
||||
* and/or PHP version.
|
||||
* Passing unknown context options has no effect.
|
||||
*
|
||||
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use for this object. You can use a `null` value
|
||||
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
* given event loop instance.
|
||||
*
|
||||
* Advanced usage: Despite allowing any `ServerInterface` as first parameter,
|
||||
* you SHOULD pass a `TcpServer` instance as first parameter, unless you
|
||||
* know what you're doing.
|
||||
* Internally, the `SecureServer` has to set the required TLS context options on
|
||||
* the underlying stream resources.
|
||||
* These resources are not exposed through any of the interfaces defined in this
|
||||
* package, but only through the internal `Connection` class.
|
||||
* The `TcpServer` class is guaranteed to emit connections that implement
|
||||
* the `ConnectionInterface` and uses the internal `Connection` class in order to
|
||||
* expose these underlying resources.
|
||||
* If you use a custom `ServerInterface` and its `connection` event does not
|
||||
* meet this requirement, the `SecureServer` will emit an `error` event and
|
||||
* then close the underlying connection.
|
||||
*
|
||||
* @param ServerInterface|TcpServer $tcp
|
||||
* @param ?LoopInterface $loop
|
||||
* @param array $context
|
||||
* @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
|
||||
* @see TcpServer
|
||||
* @link https://www.php.net/manual/en/context.ssl.php for TLS context options
|
||||
*/
|
||||
public function __construct(ServerInterface $tcp, $loop = null, array $context = array())
|
||||
{
|
||||
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
|
||||
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
|
||||
}
|
||||
|
||||
if (!\function_exists('stream_socket_enable_crypto')) {
|
||||
throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// default to empty passphrase to suppress blocking passphrase prompt
|
||||
$context += array(
|
||||
'passphrase' => ''
|
||||
);
|
||||
|
||||
$this->tcp = $tcp;
|
||||
$this->encryption = new StreamEncryption($loop ?: Loop::get());
|
||||
$this->context = $context;
|
||||
|
||||
$that = $this;
|
||||
$this->tcp->on('connection', function ($connection) use ($that) {
|
||||
$that->handleConnection($connection);
|
||||
});
|
||||
$this->tcp->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
});
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
$address = $this->tcp->getAddress();
|
||||
if ($address === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \str_replace('tcp://' , 'tls://', $address);
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->tcp->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->tcp->resume();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
return $this->tcp->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleConnection(ConnectionInterface $connection)
|
||||
{
|
||||
if (!$connection instanceof Connection) {
|
||||
$this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
|
||||
$connection->close();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->context as $name => $value) {
|
||||
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
|
||||
}
|
||||
|
||||
// get remote address before starting TLS handshake in case connection closes during handshake
|
||||
$remote = $connection->getRemoteAddress();
|
||||
$that = $this;
|
||||
|
||||
$this->encryption->enable($connection)->then(
|
||||
function ($conn) use ($that) {
|
||||
$that->emit('connection', array($conn));
|
||||
},
|
||||
function ($error) use ($that, $connection, $remote) {
|
||||
$error = new \RuntimeException(
|
||||
'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(),
|
||||
$error->getCode()
|
||||
);
|
||||
|
||||
$that->emit('error', array($error));
|
||||
$connection->close();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user