diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:38:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:38:42 +0000 |
commit | c3ca98e1b35123f226c7f4c596b5dee78caa4223 (patch) | |
tree | 9b6eb109283da55e7d9064baa9fac795a40264cb /vendor/clue/buzz-react/src | |
parent | Initial commit. (diff) | |
download | icinga-php-thirdparty-c3ca98e1b35123f226c7f4c596b5dee78caa4223.tar.xz icinga-php-thirdparty-c3ca98e1b35123f226c7f4c596b5dee78caa4223.zip |
Adding upstream version 0.11.0.upstream/0.11.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/clue/buzz-react/src')
-rw-r--r-- | vendor/clue/buzz-react/src/Browser.php | 867 | ||||
-rw-r--r-- | vendor/clue/buzz-react/src/Io/ChunkedEncoder.php | 93 | ||||
-rw-r--r-- | vendor/clue/buzz-react/src/Io/Sender.php | 161 | ||||
-rw-r--r-- | vendor/clue/buzz-react/src/Io/Transaction.php | 305 | ||||
-rw-r--r-- | vendor/clue/buzz-react/src/Message/MessageFactory.php | 139 | ||||
-rw-r--r-- | vendor/clue/buzz-react/src/Message/ReadableBodyStream.php | 153 | ||||
-rw-r--r-- | vendor/clue/buzz-react/src/Message/ResponseException.php | 43 |
7 files changed, 1761 insertions, 0 deletions
diff --git a/vendor/clue/buzz-react/src/Browser.php b/vendor/clue/buzz-react/src/Browser.php new file mode 100644 index 0000000..8f1a751 --- /dev/null +++ b/vendor/clue/buzz-react/src/Browser.php @@ -0,0 +1,867 @@ +<?php + +namespace Clue\React\Buzz; + +use Clue\React\Buzz\Io\Sender; +use Clue\React\Buzz\Io\Transaction; +use Clue\React\Buzz\Message\MessageFactory; +use InvalidArgumentException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\UriInterface; +use React\EventLoop\LoopInterface; +use React\Promise\PromiseInterface; +use React\Socket\ConnectorInterface; +use React\Stream\ReadableStreamInterface; + +class Browser +{ + private $transaction; + private $messageFactory; + private $baseUrl; + private $protocolVersion = '1.1'; + + /** + * The `Browser` is responsible for sending HTTP requests to your HTTP server + * and keeps track of pending incoming HTTP responses. + * It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage). + * + * ```php + * $loop = React\EventLoop\Factory::create(); + * + * $browser = new Clue\React\Buzz\Browser($loop); + * ``` + * + * 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($loop, 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 Clue\React\Buzz\Browser($loop, $connector); + * ``` + * + * @param LoopInterface $loop + * @param ConnectorInterface|null $connector [optional] Connector to use. + * Should be `null` in order to use default Connector. + */ + public function __construct(LoopInterface $loop, ConnectorInterface $connector = null) + { + $this->messageFactory = new MessageFactory(); + $this->transaction = new Transaction( + Sender::createFromLoop($loop, $connector, $this->messageFactory), + $this->messageFactory, + $loop + ); + } + + /** + * Sends an HTTP GET request + * + * ```php + * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { + * var_dump((string)$response->getBody()); + * }); + * ``` + * + * See also [example 01](../examples/01-google.php). + * + * > For BC reasons, this method accepts the `$url` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * @param string|UriInterface $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())); + * }); + * ``` + * + * See also [example 04](../examples/04-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); + * ``` + * + * > For BC reasons, this method accepts the `$url` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * @param string|UriInterface $url URL for the request. + * @param array $headers + * @param string|ReadableStreamInterface $contents + * @return PromiseInterface<ResponseInterface> + */ + public function post($url, array $headers = array(), $contents = '') + { + return $this->requestMayBeStreaming('POST', $url, $headers, $contents); + } + + /** + * Sends an HTTP HEAD request + * + * ```php + * $browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) { + * var_dump($response->getHeaders()); + * }); + * ``` + * + * > For BC reasons, this method accepts the `$url` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * @param string|UriInterface $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())); + * }); + * ``` + * + * 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); + * ``` + * + * > For BC reasons, this method accepts the `$url` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * @param string|UriInterface $url URL for the request. + * @param array $headers + * @param string|ReadableStreamInterface $contents + * @return PromiseInterface<ResponseInterface> + */ + public function patch($url, array $headers = array(), $contents = '') + { + return $this->requestMayBeStreaming('PATCH', $url , $headers, $contents); + } + + /** + * 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()); + * }); + * ``` + * + * See also [example 05](../examples/05-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); + * ``` + * + * > For BC reasons, this method accepts the `$url` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * @param string|UriInterface $url URL for the request. + * @param array $headers + * @param string|ReadableStreamInterface $contents + * @return PromiseInterface<ResponseInterface> + */ + public function put($url, array $headers = array(), $contents = '') + { + return $this->requestMayBeStreaming('PUT', $url, $headers, $contents); + } + + /** + * Sends an HTTP DELETE request + * + * ```php + * $browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) { + * var_dump((string)$response->getBody()); + * }); + * ``` + * + * > For BC reasons, this method accepts the `$url` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * @param string|UriInterface $url URL for the request. + * @param array $headers + * @param string|ReadableStreamInterface $contents + * @return PromiseInterface<ResponseInterface> + */ + public function delete($url, array $headers = array(), $contents = '') + { + return $this->requestMayBeStreaming('DELETE', $url, $headers, $contents); + } + + /** + * 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()); + * }); + * ``` + * + * 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); + * ``` + * + * > Note that this method is available as of v2.9.0 and always buffers the + * response body before resolving. + * It does not respect the deprecated [`streaming` option](#withoptions). + * If you want to stream the response body, you can use the + * [`requestStreaming()`](#requeststreaming) method instead. + * + * @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,Exception> + * @since 2.9.0 + */ + 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 $error) { + * echo 'Error: ' . $error->getMessage() . PHP_EOL; + * }); + * + * $body->on('close', function () { + * echo '[DONE]' . 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); + * ``` + * + * > Note that this method is available as of v2.9.0 and always resolves the + * response without buffering the response body. + * It does not respect the deprecated [`streaming` option](#withoptions). + * If you want to buffer the response body, use can use the + * [`request()`](#request) method instead. + * + * @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,Exception> + * @since 2.9.0 + */ + public function requestStreaming($method, $url, $headers = array(), $contents = '') + { + return $this->withOptions(array('streaming' => true))->requestMayBeStreaming($method, $url, $headers, $contents); + } + + /** + * [Deprecated] Submits an array of field values similar to submitting a form (`application/x-www-form-urlencoded`). + * + * ```php + * // deprecated: see post() instead + * $browser->submit($url, array('user' => 'test', 'password' => 'secret')); + * ``` + * + * This method will automatically add a matching `Content-Length` request + * header for the encoded length of the given `$fields`. + * + * > For BC reasons, this method accepts the `$url` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * @param string|UriInterface $url URL for the request. + * @param array $fields + * @param array $headers + * @param string $method + * @return PromiseInterface<ResponseInterface> + * @deprecated 2.9.0 See self::post() instead. + * @see self::post() + */ + public function submit($url, array $fields, $headers = array(), $method = 'POST') + { + $headers['Content-Type'] = 'application/x-www-form-urlencoded'; + $contents = http_build_query($fields); + + return $this->requestMayBeStreaming($method, $url, $headers, $contents); + } + + /** + * [Deprecated] Sends an arbitrary instance implementing the [`RequestInterface`](#requestinterface) (PSR-7). + * + * 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 + * $request = new Request('OPTIONS', $url); + * + * // deprecated: see request() instead + * $browser->send($request)->then(…); + * ``` + * + * 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`). + * + * @param RequestInterface $request + * @return PromiseInterface<ResponseInterface> + * @deprecated 2.9.0 See self::request() instead. + * @see self::request() + */ + public function send(RequestInterface $request) + { + if ($this->baseUrl !== null) { + // ensure we're actually below the base URL + $request = $request->withUri($this->messageFactory->expandBase($request->getUri(), $this->baseUrl)); + } + + return $this->transaction->send($request); + } + + /** + * 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 + * $new = $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()); + * }); + * ``` + * + * 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')); + * }); + * ``` + * + * 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()); + * }); + * ``` + * + * 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 Clue\React\Buzz\Message\ResponseException) { + * // any HTTP response error message will now end up here + * $response = $e->getResponse(); + * var_dump($response->getStatusCode(), $response->getReasonPhrase()); + * } else { + * var_dump($e->getMessage()); + * } + * }); + * ``` + * + * 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 prepending this absolute base URL. Note that this + * merely prepends the base URL and does *not* resolve any relative path + * references (like `../` etc.). This is mostly 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/example + * $browser->get('/example')->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. + * + * > For BC reasons, this method accepts the `$baseUrl` as either a `string` + * value or as an `UriInterface`. It's recommended to explicitly cast any + * objects implementing `UriInterface` to `string`. + * + * > Changelog: As of v2.9.0 this method accepts a `null` value to reset the + * base URL. Earlier versions had to use the deprecated `withoutBase()` + * method to reset the base URL. + * + * @param string|null|UriInterface $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 = $this->messageFactory->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 + * $newBrowser = $browser->withProtocolVersion('1.0'); + * + * $newBrowser->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 + * @since 2.8.0 + */ + 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()); + * }); + * ``` + * + * 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 + )); + } + + /** + * [Deprecated] 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 + * @deprecated 2.9.0 See self::withTimeout(), self::withFollowRedirects() and self::withRejectErrorResponse() instead. + * @see self::withTimeout() + * @see self::withFollowRedirects() + * @see self::withRejectErrorResponse() + */ + public function withOptions(array $options) + { + $browser = clone $this; + $browser->transaction = $this->transaction->withOptions($options); + + return $browser; + } + + /** + * [Deprecated] Removes the base URL. + * + * ```php + * // deprecated: see withBase() instead + * $newBrowser = $browser->withoutBase(); + * ``` + * + * Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method + * actually returns a *new* [`Browser`](#browser) instance without any base URL applied. + * + * See also [`withBase()`](#withbase). + * + * @return self + * @deprecated 2.9.0 See self::withBase() instead. + * @see self::withBase() + */ + public function withoutBase() + { + return $this->withBase(null); + } + + /** + * @param string $method + * @param string|UriInterface $url + * @param array $headers + * @param string|ReadableStreamInterface $contents + * @return PromiseInterface<ResponseInterface,Exception> + */ + private function requestMayBeStreaming($method, $url, array $headers = array(), $contents = '') + { + return $this->send($this->messageFactory->request($method, $url, $headers, $contents, $this->protocolVersion)); + } +} diff --git a/vendor/clue/buzz-react/src/Io/ChunkedEncoder.php b/vendor/clue/buzz-react/src/Io/ChunkedEncoder.php new file mode 100644 index 0000000..3b74e0c --- /dev/null +++ b/vendor/clue/buzz-react/src/Io/ChunkedEncoder.php @@ -0,0 +1,93 @@ +<?php + +namespace Clue\React\Buzz\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 + * @link https://github.com/reactphp/http/blob/master/src/Io/ChunkedEncoder.php Originally from react/http + */ +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(); + } + } +} diff --git a/vendor/clue/buzz-react/src/Io/Sender.php b/vendor/clue/buzz-react/src/Io/Sender.php new file mode 100644 index 0000000..06c1212 --- /dev/null +++ b/vendor/clue/buzz-react/src/Io/Sender.php @@ -0,0 +1,161 @@ +<?php + +namespace Clue\React\Buzz\Io; + +use Clue\React\Buzz\Message\MessageFactory; +use Psr\Http\Message\RequestInterface; +use React\EventLoop\LoopInterface; +use React\HttpClient\Client as HttpClient; +use React\HttpClient\Response as ResponseStream; +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($loop); + * $sender = \Clue\React\Buzz\Io\Sender::createFromLoop($loop, $connector); + * ``` + * + * @param LoopInterface $loop + * @param ConnectorInterface|null $connector + * @return self + */ + public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector = null, MessageFactory $messageFactory) + { + return new self(new HttpClient($loop, $connector), $messageFactory); + } + + private $http; + private $messageFactory; + + /** + * [internal] Instantiate Sender + * + * @param HttpClient $http + * @internal + */ + public function __construct(HttpClient $http, MessageFactory $messageFactory) + { + $this->http = $http; + $this->messageFactory = $messageFactory; + } + + /** + * + * @internal + * @param RequestInterface $request + * @return PromiseInterface Promise<ResponseInterface, Exception> + */ + public function send(RequestInterface $request) + { + $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 && $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; + } + + $headers = array(); + foreach ($request->getHeaders() as $name => $values) { + $headers[$name] = implode(', ', $values); + } + + $requestStream = $this->http->request($request->getMethod(), (string)$request->getUri(), $headers, $request->getProtocolVersion()); + + $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); + }); + + $messageFactory = $this->messageFactory; + $requestStream->on('response', function (ResponseStream $responseStream) use ($deferred, $messageFactory, $request) { + // apply response header values from response stream + $deferred->resolve($messageFactory->response( + $responseStream->getVersion(), + $responseStream->getCode(), + $responseStream->getReasonPhrase(), + $responseStream->getHeaders(), + $responseStream, + $request->getMethod() + )); + }); + + 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(); + } +} diff --git a/vendor/clue/buzz-react/src/Io/Transaction.php b/vendor/clue/buzz-react/src/Io/Transaction.php new file mode 100644 index 0000000..adb5796 --- /dev/null +++ b/vendor/clue/buzz-react/src/Io/Transaction.php @@ -0,0 +1,305 @@ +<?php + +namespace Clue\React\Buzz\Io; + +use Clue\React\Buzz\Message\ResponseException; +use Clue\React\Buzz\Message\MessageFactory; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; +use React\EventLoop\LoopInterface; +use React\Promise\Deferred; +use React\Promise\PromiseInterface; +use React\Stream\ReadableStreamInterface; + +/** + * @internal + */ +class Transaction +{ + private $sender; + private $messageFactory; + 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, MessageFactory $messageFactory, LoopInterface $loop) + { + $this->sender = $sender; + $this->messageFactory = $messageFactory; + $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->messageFactory, $this->loop); + $value = $default->$name; + } + + $transaction->$name = $value; + } + } + + return $transaction; + } + + public function send(RequestInterface $request) + { + $deferred = new Deferred(function () use (&$deferred) { + if (isset($deferred->pending)) { + $deferred->pending->cancel(); + unset($deferred->pending); + } + }); + + $deferred->numRequests = 0; + + // 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)->then( + function (ResponseInterface $response) use ($deferred, $loop, &$timeout) { + if (isset($deferred->timeout)) { + $loop->cancelTimer($deferred->timeout); + unset($deferred->timeout); + } + $timeout = -1; + $deferred->resolve($response); + }, + function ($e) use ($deferred, $loop, &$timeout) { + if (isset($deferred->timeout)) { + $loop->cancelTimer($deferred->timeout); + unset($deferred->timeout); + } + $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, &$timeout) { + if ($timeout >= 0) { + $that->applyTimeout($deferred, $timeout); + } + }); + } else { + $this->applyTimeout($deferred, $timeout); + } + + return $deferred->promise(); + } + + /** + * @internal + * @param Deferred $deferred + * @param number $timeout + * @return void + */ + public function applyTimeout(Deferred $deferred, $timeout) + { + $deferred->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred) { + $deferred->reject(new \RuntimeException( + 'Request timed out after ' . $timeout . ' seconds' + )); + if (isset($deferred->pending)) { + $deferred->pending->cancel(); + unset($deferred->pending); + } + }); + } + + private function next(RequestInterface $request, Deferred $deferred) + { + $this->progress('request', array($request)); + + $that = $this; + ++$deferred->numRequests; + + $promise = $this->sender->send($request); + + if (!$this->streaming) { + $promise = $promise->then(function ($response) use ($deferred, $that) { + return $that->bufferResponse($response, $deferred); + }); + } + + $deferred->pending = $promise; + + return $promise->then( + function (ResponseInterface $response) use ($request, $that, $deferred) { + return $that->onResponse($response, $request, $deferred); + } + ); + } + + /** + * @internal + * @param ResponseInterface $response + * @return PromiseInterface Promise<ResponseInterface, Exception> + */ + public function bufferResponse(ResponseInterface $response, $deferred) + { + $stream = $response->getBody(); + + $size = $stream->getSize(); + if ($size !== null && $size > $this->maximumSize) { + $stream->close(); + return \React\Promise\reject(new \OverflowException( + 'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes', + \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0 + )); + } + + // body is not streaming => already buffered + if (!$stream instanceof ReadableStreamInterface) { + return \React\Promise\resolve($response); + } + + // buffer stream and resolve with buffered body + $messageFactory = $this->messageFactory; + $maximumSize = $this->maximumSize; + $promise = \React\Promise\Stream\buffer($stream, $maximumSize)->then( + function ($body) use ($response, $messageFactory) { + return $response->withBody($messageFactory->body($body)); + }, + function ($e) use ($stream, $maximumSize) { + // try to close stream if buffering fails (or is cancelled) + $stream->close(); + + if ($e instanceof \OverflowException) { + $e = new \OverflowException( + 'Response body size exceeds maximum of ' . $maximumSize . ' bytes', + \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0 + ); + } + + throw $e; + } + ); + + $deferred->pending = $promise; + + return $promise; + } + + /** + * @internal + * @param ResponseInterface $response + * @param RequestInterface $request + * @throws ResponseException + * @return ResponseInterface|PromiseInterface + */ + public function onResponse(ResponseInterface $response, RequestInterface $request, $deferred) + { + $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); + } + + // 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 + * @return PromiseInterface + * @throws \RuntimeException + */ + private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred) + { + // resolve location relative to last request URI + $location = $this->messageFactory->uriRelative($request->getUri(), $response->getHeaderLine('Location')); + + $request = $this->makeRedirectRequest($request, $location); + $this->progress('redirect', array($request)); + + if ($deferred->numRequests >= $this->maxRedirects) { + throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded'); + } + + return $this->next($request, $deferred); + } + + /** + * @param RequestInterface $request + * @param UriInterface $location + * @return RequestInterface + */ + private function makeRedirectRequest(RequestInterface $request, UriInterface $location) + { + $originalHost = $request->getUri()->getHost(); + $request = $request + ->withoutHeader('Host') + ->withoutHeader('Content-Type') + ->withoutHeader('Content-Length'); + + // Remove authorization if changing hostnames (but not if just changing ports or protocols). + if ($location->getHost() !== $originalHost) { + $request = $request->withoutHeader('Authorization'); + } + + // naïve approach.. + $method = ($request->getMethod() === 'HEAD') ? 'HEAD' : 'GET'; + + return $this->messageFactory->request($method, $location, $request->getHeaders()); + } + + 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; + } +} diff --git a/vendor/clue/buzz-react/src/Message/MessageFactory.php b/vendor/clue/buzz-react/src/Message/MessageFactory.php new file mode 100644 index 0000000..8a3dd6d --- /dev/null +++ b/vendor/clue/buzz-react/src/Message/MessageFactory.php @@ -0,0 +1,139 @@ +<?php + +namespace Clue\React\Buzz\Message; + +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; +use RingCentral\Psr7\Request; +use RingCentral\Psr7\Response; +use RingCentral\Psr7\Uri; +use React\Stream\ReadableStreamInterface; + +/** + * @internal + */ +class MessageFactory +{ + /** + * Creates a new instance of RequestInterface for the given request parameters + * + * @param string $method + * @param string|UriInterface $uri + * @param array $headers + * @param string|ReadableStreamInterface $content + * @param string $protocolVersion + * @return Request + */ + public function request($method, $uri, $headers = array(), $content = '', $protocolVersion = '1.1') + { + return new Request($method, $uri, $headers, $this->body($content), $protocolVersion); + } + + /** + * Creates a new instance of ResponseInterface for the given response parameters + * + * @param string $protocolVersion + * @param int $status + * @param string $reason + * @param array $headers + * @param ReadableStreamInterface|string $body + * @param ?string $requestMethod + * @return Response + * @uses self::body() + */ + public function response($protocolVersion, $status, $reason, $headers = array(), $body = '', $requestMethod = null) + { + $response = new Response($status, $headers, $body instanceof ReadableStreamInterface ? null : $body, $protocolVersion, $reason); + + if ($body instanceof ReadableStreamInterface) { + $length = null; + $code = $response->getStatusCode(); + if ($requestMethod === 'HEAD' || ($code >= 100 && $code < 200) || $code == 204 || $code == 304) { + $length = 0; + } elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') { + $length = null; + } elseif ($response->hasHeader('Content-Length')) { + $length = (int)$response->getHeaderLine('Content-Length'); + } + + $response = $response->withBody(new ReadableBodyStream($body, $length)); + } + + return $response; + } + + /** + * Creates a new instance of StreamInterface for the given body contents + * + * @param ReadableStreamInterface|string $body + * @return StreamInterface + */ + public function body($body) + { + if ($body instanceof ReadableStreamInterface) { + return new ReadableBodyStream($body); + } + + return \RingCentral\Psr7\stream_for($body); + } + + /** + * Creates a new instance of UriInterface for the given URI string + * + * @param string $uri + * @return UriInterface + */ + public function uri($uri) + { + return new Uri($uri); + } + + /** + * Creates a new instance of UriInterface for the given URI string relative to the given base URI + * + * @param UriInterface $base + * @param string $uri + * @return UriInterface + */ + public function uriRelative(UriInterface $base, $uri) + { + return Uri::resolve($base, $uri); + } + + /** + * Resolves the given relative or absolute $uri by appending it behind $this base URI + * + * The given $uri parameter can be either a relative or absolute URI and + * as such can not contain any URI template placeholders. + * + * As such, the outcome of this method represents a valid, absolute URI + * which will be returned as an instance implementing `UriInterface`. + * + * If the given $uri is a relative URI, it will simply be appended behind $base URI. + * + * If the given $uri is an absolute URI, it will simply be returned as-is. + * + * @param UriInterface $uri + * @param UriInterface $base + * @return UriInterface + */ + public function expandBase(UriInterface $uri, UriInterface $base) + { + if ($uri->getScheme() !== '') { + return $uri; + } + + $uri = (string)$uri; + $base = (string)$base; + + if ($uri !== '' && substr($base, -1) !== '/' && substr($uri, 0, 1) !== '?') { + $base .= '/'; + } + + if (isset($uri[0]) && $uri[0] === '/') { + $uri = substr($uri, 1); + } + + return $this->uri($base . $uri); + } +} diff --git a/vendor/clue/buzz-react/src/Message/ReadableBodyStream.php b/vendor/clue/buzz-react/src/Message/ReadableBodyStream.php new file mode 100644 index 0000000..eac27f7 --- /dev/null +++ b/vendor/clue/buzz-react/src/Message/ReadableBodyStream.php @@ -0,0 +1,153 @@ +<?php + +namespace Clue\React\Buzz\Message; + +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(); + } +} diff --git a/vendor/clue/buzz-react/src/Message/ResponseException.php b/vendor/clue/buzz-react/src/Message/ResponseException.php new file mode 100644 index 0000000..081103a --- /dev/null +++ b/vendor/clue/buzz-react/src/Message/ResponseException.php @@ -0,0 +1,43 @@ +<?php + +namespace Clue\React\Buzz\Message; + +use RuntimeException; +use Psr\Http\Message\ResponseInterface; + +/** + * The `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. + */ +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 [`ResponseInterface`](#responseinterface) object. + * + * @return ResponseInterface + */ + public function getResponse() + { + return $this->response; + } +} |