+
This commit is contained in:
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
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user