| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858 |
- <?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)
- );
- }
- }
|