GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 5cefd1...492078 )
by Anton
04:08
created

Browser::requestMayBeStreaming()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 4
nop 4
dl 0
loc 13
rs 10
1
<?php
2
3
namespace React\Http;
4
5
use Psr\Http\Message\ResponseInterface;
6
use RingCentral\Psr7\Request;
7
use RingCentral\Psr7\Uri;
8
use React\EventLoop\Loop;
9
use React\EventLoop\LoopInterface;
10
use React\Http\Io\ReadableBodyStream;
11
use React\Http\Io\Sender;
12
use React\Http\Io\Transaction;
13
use React\Promise\PromiseInterface;
14
use React\Socket\ConnectorInterface;
15
use React\Stream\ReadableStreamInterface;
16
use InvalidArgumentException;
17
18
/**
19
 * @final This class is final and shouldn't be extended as it is likely to be marked final in a future release.
20
 */
21
class Browser
22
{
23
    private $transaction;
24
    private $baseUrl;
25
    private $protocolVersion = '1.1';
26
27
    /**
28
     * The `Browser` is responsible for sending HTTP requests to your HTTP server
29
     * and keeps track of pending incoming HTTP responses.
30
     *
31
     * ```php
32
     * $browser = new React\Http\Browser();
33
     * ```
34
     *
35
     * This class takes two optional arguments for more advanced usage:
36
     *
37
     * ```php
38
     * // constructor signature as of v1.5.0
39
     * $browser = new React\Http\Browser(?ConnectorInterface $connector = null, ?LoopInterface $loop = null);
40
     *
41
     * // legacy constructor signature before v1.5.0
42
     * $browser = new React\Http\Browser(?LoopInterface $loop = null, ?ConnectorInterface $connector = null);
43
     * ```
44
     *
45
     * If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
46
     * proxy servers etc.), you can explicitly pass a custom instance of the
47
     * [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
48
     *
49
     * ```php
50
     * $connector = new React\Socket\Connector(array(
51
     *     'dns' => '127.0.0.1',
52
     *     'tcp' => array(
53
     *         'bindto' => '192.168.10.1:0'
54
     *     ),
55
     *     'tls' => array(
56
     *         'verify_peer' => false,
57
     *         'verify_peer_name' => false
58
     *     )
59
     * ));
60
     *
61
     * $browser = new React\Http\Browser($connector);
62
     * ```
63
     *
64
     * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
65
     * pass the event loop instance to use for this object. You can use a `null` value
66
     * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
67
     * This value SHOULD NOT be given unless you're sure you want to explicitly use a
68
     * given event loop instance.
69
     *
70
     * @param null|ConnectorInterface|LoopInterface $connector
71
     * @param null|LoopInterface|ConnectorInterface $loop
72
     * @throws \InvalidArgumentException for invalid arguments
73
     */
74
    public function __construct($connector = null, $loop = null)
75
    {
76
        // swap arguments for legacy constructor signature
77
        if (($connector instanceof LoopInterface || $connector === null) && ($loop instanceof ConnectorInterface || $loop === null)) {
78
            $swap = $loop;
79
            $loop = $connector;
80
            $connector = $swap;
81
        }
82
83
        if (($connector !== null && !$connector instanceof ConnectorInterface) || ($loop !== null && !$loop instanceof LoopInterface)) {
84
            throw new \InvalidArgumentException('Expected "?ConnectorInterface $connector" and "?LoopInterface $loop" arguments');
85
        }
86
87
        $loop = $loop ?: Loop::get();
88
        $this->transaction = new Transaction(
89
            Sender::createFromLoop($loop, $connector),
90
            $loop
91
        );
92
    }
93
94
    /**
95
     * Sends an HTTP GET request
96
     *
97
     * ```php
98
     * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
99
     *     var_dump((string)$response->getBody());
100
     * }, function (Exception $e) {
101
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
102
     * });
103
     * ```
104
     *
105
     * See also [GET request client example](../examples/01-client-get-request.php).
106
     *
107
     * @param string $url     URL for the request.
108
     * @param array  $headers
109
     * @return PromiseInterface<ResponseInterface>
110
     */
111
    public function get($url, array $headers = array())
112
    {
113
        return $this->requestMayBeStreaming('GET', $url, $headers);
114
    }
115
116
    /**
117
     * Sends an HTTP POST request
118
     *
119
     * ```php
120
     * $browser->post(
121
     *     $url,
122
     *     [
123
     *         'Content-Type' => 'application/json'
124
     *     ],
125
     *     json_encode($data)
126
     * )->then(function (Psr\Http\Message\ResponseInterface $response) {
127
     *     var_dump(json_decode((string)$response->getBody()));
128
     * }, function (Exception $e) {
129
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
130
     * });
131
     * ```
132
     *
133
     * See also [POST JSON client example](../examples/04-client-post-json.php).
134
     *
135
     * This method is also commonly used to submit HTML form data:
136
     *
137
     * ```php
138
     * $data = [
139
     *     'user' => 'Alice',
140
     *     'password' => 'secret'
141
     * ];
142
     *
143
     * $browser->post(
144
     *     $url,
145
     *     [
146
     *         'Content-Type' => 'application/x-www-form-urlencoded'
147
     *     ],
148
     *     http_build_query($data)
149
     * );
150
     * ```
151
     *
152
     * This method will automatically add a matching `Content-Length` request
153
     * header if the outgoing request body is a `string`. If you're using a
154
     * streaming request body (`ReadableStreamInterface`), it will default to
155
     * using `Transfer-Encoding: chunked` or you have to explicitly pass in a
156
     * matching `Content-Length` request header like so:
157
     *
158
     * ```php
159
     * $body = new React\Stream\ThroughStream();
160
     * Loop::addTimer(1.0, function () use ($body) {
161
     *     $body->end("hello world");
162
     * });
163
     *
164
     * $browser->post($url, array('Content-Length' => '11'), $body);
165
     * ```
166
     *
167
     * @param string                         $url      URL for the request.
168
     * @param array                          $headers
169
     * @param string|ReadableStreamInterface $body
170
     * @return PromiseInterface<ResponseInterface>
171
     */
172
    public function post($url, array $headers = array(), $body = '')
173
    {
174
        return $this->requestMayBeStreaming('POST', $url, $headers, $body);
175
    }
176
177
    /**
178
     * Sends an HTTP HEAD request
179
     *
180
     * ```php
181
     * $browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
182
     *     var_dump($response->getHeaders());
183
     * }, function (Exception $e) {
184
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
185
     * });
186
     * ```
187
     *
188
     * @param string $url     URL for the request.
189
     * @param array  $headers
190
     * @return PromiseInterface<ResponseInterface>
191
     */
192
    public function head($url, array $headers = array())
193
    {
194
        return $this->requestMayBeStreaming('HEAD', $url, $headers);
195
    }
196
197
    /**
198
     * Sends an HTTP PATCH request
199
     *
200
     * ```php
201
     * $browser->patch(
202
     *     $url,
203
     *     [
204
     *         'Content-Type' => 'application/json'
205
     *     ],
206
     *     json_encode($data)
207
     * )->then(function (Psr\Http\Message\ResponseInterface $response) {
208
     *     var_dump(json_decode((string)$response->getBody()));
209
     * }, function (Exception $e) {
210
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
211
     * });
212
     * ```
213
     *
214
     * This method will automatically add a matching `Content-Length` request
215
     * header if the outgoing request body is a `string`. If you're using a
216
     * streaming request body (`ReadableStreamInterface`), it will default to
217
     * using `Transfer-Encoding: chunked` or you have to explicitly pass in a
218
     * matching `Content-Length` request header like so:
219
     *
220
     * ```php
221
     * $body = new React\Stream\ThroughStream();
222
     * Loop::addTimer(1.0, function () use ($body) {
223
     *     $body->end("hello world");
224
     * });
225
     *
226
     * $browser->patch($url, array('Content-Length' => '11'), $body);
227
     * ```
228
     *
229
     * @param string                         $url      URL for the request.
230
     * @param array                          $headers
231
     * @param string|ReadableStreamInterface $body
232
     * @return PromiseInterface<ResponseInterface>
233
     */
234
    public function patch($url, array $headers = array(), $body = '')
235
    {
236
        return $this->requestMayBeStreaming('PATCH', $url , $headers, $body);
237
    }
238
239
    /**
240
     * Sends an HTTP PUT request
241
     *
242
     * ```php
243
     * $browser->put(
244
     *     $url,
245
     *     [
246
     *         'Content-Type' => 'text/xml'
247
     *     ],
248
     *     $xml->asXML()
249
     * )->then(function (Psr\Http\Message\ResponseInterface $response) {
250
     *     var_dump((string)$response->getBody());
251
     * }, function (Exception $e) {
252
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
253
     * });
254
     * ```
255
     *
256
     * See also [PUT XML client example](../examples/05-client-put-xml.php).
257
     *
258
     * This method will automatically add a matching `Content-Length` request
259
     * header if the outgoing request body is a `string`. If you're using a
260
     * streaming request body (`ReadableStreamInterface`), it will default to
261
     * using `Transfer-Encoding: chunked` or you have to explicitly pass in a
262
     * matching `Content-Length` request header like so:
263
     *
264
     * ```php
265
     * $body = new React\Stream\ThroughStream();
266
     * Loop::addTimer(1.0, function () use ($body) {
267
     *     $body->end("hello world");
268
     * });
269
     *
270
     * $browser->put($url, array('Content-Length' => '11'), $body);
271
     * ```
272
     *
273
     * @param string                         $url      URL for the request.
274
     * @param array                          $headers
275
     * @param string|ReadableStreamInterface $body
276
     * @return PromiseInterface<ResponseInterface>
277
     */
278
    public function put($url, array $headers = array(), $body = '')
279
    {
280
        return $this->requestMayBeStreaming('PUT', $url, $headers, $body);
281
    }
282
283
    /**
284
     * Sends an HTTP DELETE request
285
     *
286
     * ```php
287
     * $browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
288
     *     var_dump((string)$response->getBody());
289
     * }, function (Exception $e) {
290
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
291
     * });
292
     * ```
293
     *
294
     * @param string                         $url      URL for the request.
295
     * @param array                          $headers
296
     * @param string|ReadableStreamInterface $body
297
     * @return PromiseInterface<ResponseInterface>
298
     */
299
    public function delete($url, array $headers = array(), $body = '')
300
    {
301
        return $this->requestMayBeStreaming('DELETE', $url, $headers, $body);
302
    }
303
304
    /**
305
     * Sends an arbitrary HTTP request.
306
     *
307
     * The preferred way to send an HTTP request is by using the above
308
     * [request methods](#request-methods), for example the [`get()`](#get)
309
     * method to send an HTTP `GET` request.
310
     *
311
     * As an alternative, if you want to use a custom HTTP request method, you
312
     * can use this method:
313
     *
314
     * ```php
315
     * $browser->request('OPTIONS', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
316
     *     var_dump((string)$response->getBody());
317
     * }, function (Exception $e) {
318
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
319
     * });
320
     * ```
321
     *
322
     * This method will automatically add a matching `Content-Length` request
323
     * header if the size of the outgoing request body is known and non-empty.
324
     * For an empty request body, if will only include a `Content-Length: 0`
325
     * request header if the request method usually expects a request body (only
326
     * applies to `POST`, `PUT` and `PATCH`).
327
     *
328
     * If you're using a streaming request body (`ReadableStreamInterface`), it
329
     * will default to using `Transfer-Encoding: chunked` or you have to
330
     * explicitly pass in a matching `Content-Length` request header like so:
331
     *
332
     * ```php
333
     * $body = new React\Stream\ThroughStream();
334
     * Loop::addTimer(1.0, function () use ($body) {
335
     *     $body->end("hello world");
336
     * });
337
     *
338
     * $browser->request('POST', $url, array('Content-Length' => '11'), $body);
339
     * ```
340
     *
341
     * @param string                         $method   HTTP request method, e.g. GET/HEAD/POST etc.
342
     * @param string                         $url      URL for the request
343
     * @param array                          $headers  Additional request headers
344
     * @param string|ReadableStreamInterface $body     HTTP request body contents
345
     * @return PromiseInterface<ResponseInterface,\Exception>
346
     */
347
    public function request($method, $url, array $headers = array(), $body = '')
348
    {
349
        return $this->withOptions(array('streaming' => false))->requestMayBeStreaming($method, $url, $headers, $body);
350
    }
351
352
    /**
353
     * Sends an arbitrary HTTP request and receives a streaming response without buffering the response body.
354
     *
355
     * The preferred way to send an HTTP request is by using the above
356
     * [request methods](#request-methods), for example the [`get()`](#get)
357
     * method to send an HTTP `GET` request. Each of these methods will buffer
358
     * the whole response body in memory by default. This is easy to get started
359
     * and works reasonably well for smaller responses.
360
     *
361
     * In some situations, it's a better idea to use a streaming approach, where
362
     * only small chunks have to be kept in memory. You can use this method to
363
     * send an arbitrary HTTP request and receive a streaming response. It uses
364
     * the same HTTP message API, but does not buffer the response body in
365
     * memory. It only processes the response body in small chunks as data is
366
     * received and forwards this data through [ReactPHP's Stream API](https://github.com/reactphp/stream).
367
     * This works for (any number of) responses of arbitrary sizes.
368
     *
369
     * ```php
370
     * $browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
371
     *     $body = $response->getBody();
372
     *     assert($body instanceof Psr\Http\Message\StreamInterface);
373
     *     assert($body instanceof React\Stream\ReadableStreamInterface);
374
     *
375
     *     $body->on('data', function ($chunk) {
376
     *         echo $chunk;
377
     *     });
378
     *
379
     *     $body->on('error', function (Exception $e) {
380
     *         echo 'Error: ' . $e->getMessage() . PHP_EOL;
381
     *     });
382
     *
383
     *     $body->on('close', function () {
384
     *         echo '[DONE]' . PHP_EOL;
385
     *     });
386
     * }, function (Exception $e) {
387
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
388
     * });
389
     * ```
390
     *
391
     * See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
392
     * and the [streaming response](#streaming-response) for more details,
393
     * examples and possible use-cases.
394
     *
395
     * This method will automatically add a matching `Content-Length` request
396
     * header if the size of the outgoing request body is known and non-empty.
397
     * For an empty request body, if will only include a `Content-Length: 0`
398
     * request header if the request method usually expects a request body (only
399
     * applies to `POST`, `PUT` and `PATCH`).
400
     *
401
     * If you're using a streaming request body (`ReadableStreamInterface`), it
402
     * will default to using `Transfer-Encoding: chunked` or you have to
403
     * explicitly pass in a matching `Content-Length` request header like so:
404
     *
405
     * ```php
406
     * $body = new React\Stream\ThroughStream();
407
     * Loop::addTimer(1.0, function () use ($body) {
408
     *     $body->end("hello world");
409
     * });
410
     *
411
     * $browser->requestStreaming('POST', $url, array('Content-Length' => '11'), $body);
412
     * ```
413
     *
414
     * @param string                         $method   HTTP request method, e.g. GET/HEAD/POST etc.
415
     * @param string                         $url      URL for the request
416
     * @param array                          $headers  Additional request headers
417
     * @param string|ReadableStreamInterface $body     HTTP request body contents
418
     * @return PromiseInterface<ResponseInterface,\Exception>
419
     */
420
    public function requestStreaming($method, $url, $headers = array(), $body = '')
421
    {
422
        return $this->withOptions(array('streaming' => true))->requestMayBeStreaming($method, $url, $headers, $body);
423
    }
424
425
    /**
426
     * Changes the maximum timeout used for waiting for pending requests.
427
     *
428
     * You can pass in the number of seconds to use as a new timeout value:
429
     *
430
     * ```php
431
     * $browser = $browser->withTimeout(10.0);
432
     * ```
433
     *
434
     * You can pass in a bool `false` to disable any timeouts. In this case,
435
     * requests can stay pending forever:
436
     *
437
     * ```php
438
     * $browser = $browser->withTimeout(false);
439
     * ```
440
     *
441
     * You can pass in a bool `true` to re-enable default timeout handling. This
442
     * will respects PHP's `default_socket_timeout` setting (default 60s):
443
     *
444
     * ```php
445
     * $browser = $browser->withTimeout(true);
446
     * ```
447
     *
448
     * See also [timeouts](#timeouts) for more details about timeout handling.
449
     *
450
     * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
451
     * method actually returns a *new* [`Browser`](#browser) instance with the
452
     * given timeout value applied.
453
     *
454
     * @param bool|number $timeout
455
     * @return self
456
     */
457
    public function withTimeout($timeout)
458
    {
459
        if ($timeout === true) {
460
            $timeout = null;
461
        } elseif ($timeout === false) {
462
            $timeout = -1;
463
        } elseif ($timeout < 0) {
464
            $timeout = 0;
465
        }
466
467
        return $this->withOptions(array(
468
            'timeout' => $timeout,
469
        ));
470
    }
471
472
    /**
473
     * Changes how HTTP redirects will be followed.
474
     *
475
     * You can pass in the maximum number of redirects to follow:
476
     *
477
     * ```php
478
     * $browser = $browser->withFollowRedirects(5);
479
     * ```
480
     *
481
     * The request will automatically be rejected when the number of redirects
482
     * is exceeded. You can pass in a `0` to reject the request for any
483
     * redirects encountered:
484
     *
485
     * ```php
486
     * $browser = $browser->withFollowRedirects(0);
487
     *
488
     * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
489
     *     // only non-redirected responses will now end up here
490
     *     var_dump($response->getHeaders());
491
     * }, function (Exception $e) {
492
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
493
     * });
494
     * ```
495
     *
496
     * You can pass in a bool `false` to disable following any redirects. In
497
     * this case, requests will resolve with the redirection response instead
498
     * of following the `Location` response header:
499
     *
500
     * ```php
501
     * $browser = $browser->withFollowRedirects(false);
502
     *
503
     * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
504
     *     // any redirects will now end up here
505
     *     var_dump($response->getHeaderLine('Location'));
506
     * }, function (Exception $e) {
507
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
508
     * });
509
     * ```
510
     *
511
     * You can pass in a bool `true` to re-enable default redirect handling.
512
     * This defaults to following a maximum of 10 redirects:
513
     *
514
     * ```php
515
     * $browser = $browser->withFollowRedirects(true);
516
     * ```
517
     *
518
     * See also [redirects](#redirects) for more details about redirect handling.
519
     *
520
     * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
521
     * method actually returns a *new* [`Browser`](#browser) instance with the
522
     * given redirect setting applied.
523
     *
524
     * @param bool|int $followRedirects
525
     * @return self
526
     */
527
    public function withFollowRedirects($followRedirects)
528
    {
529
        return $this->withOptions(array(
530
            'followRedirects' => $followRedirects !== false,
531
            'maxRedirects' => \is_bool($followRedirects) ? null : $followRedirects
532
        ));
533
    }
534
535
    /**
536
     * Changes whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.
537
     *
538
     * You can pass in a bool `false` to disable rejecting incoming responses
539
     * that use a 4xx or 5xx response status code. In this case, requests will
540
     * resolve with the response message indicating an error condition:
541
     *
542
     * ```php
543
     * $browser = $browser->withRejectErrorResponse(false);
544
     *
545
     * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
546
     *     // any HTTP response will now end up here
547
     *     var_dump($response->getStatusCode(), $response->getReasonPhrase());
548
     * }, function (Exception $e) {
549
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
550
     * });
551
     * ```
552
     *
553
     * You can pass in a bool `true` to re-enable default status code handling.
554
     * This defaults to rejecting any response status codes in the 4xx or 5xx
555
     * range:
556
     *
557
     * ```php
558
     * $browser = $browser->withRejectErrorResponse(true);
559
     *
560
     * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
561
     *     // any successful HTTP response will now end up here
562
     *     var_dump($response->getStatusCode(), $response->getReasonPhrase());
563
     * }, function (Exception $e) {
564
     *     if ($e instanceof React\Http\Message\ResponseException) {
565
     *         // any HTTP response error message will now end up here
566
     *         $response = $e->getResponse();
567
     *         var_dump($response->getStatusCode(), $response->getReasonPhrase());
568
     *     } else {
569
     *         echo 'Error: ' . $e->getMessage() . PHP_EOL;
570
     *     }
571
     * });
572
     * ```
573
     *
574
     * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
575
     * method actually returns a *new* [`Browser`](#browser) instance with the
576
     * given setting applied.
577
     *
578
     * @param bool $obeySuccessCode
579
     * @return self
580
     */
581
    public function withRejectErrorResponse($obeySuccessCode)
582
    {
583
        return $this->withOptions(array(
584
            'obeySuccessCode' => $obeySuccessCode,
585
        ));
586
    }
587
588
    /**
589
     * Changes the base URL used to resolve relative URLs to.
590
     *
591
     * If you configure a base URL, any requests to relative URLs will be
592
     * processed by first resolving this relative to the given absolute base
593
     * URL. This supports resolving relative path references (like `../` etc.).
594
     * This is particularly useful for (RESTful) API calls where all endpoints
595
     * (URLs) are located under a common base URL.
596
     *
597
     * ```php
598
     * $browser = $browser->withBase('http://api.example.com/v3/');
599
     *
600
     * // will request http://api.example.com/v3/users
601
     * $browser->get('users')->then(…);
602
     * ```
603
     *
604
     * You can pass in a `null` base URL to return a new instance that does not
605
     * use a base URL:
606
     *
607
     * ```php
608
     * $browser = $browser->withBase(null);
609
     * ```
610
     *
611
     * Accordingly, any requests using relative URLs to a browser that does not
612
     * use a base URL can not be completed and will be rejected without sending
613
     * a request.
614
     *
615
     * This method will throw an `InvalidArgumentException` if the given
616
     * `$baseUrl` argument is not a valid URL.
617
     *
618
     * Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
619
     * actually returns a *new* [`Browser`](#browser) instance with the given base URL applied.
620
     *
621
     * @param string|null $baseUrl absolute base URL
622
     * @return self
623
     * @throws InvalidArgumentException if the given $baseUrl is not a valid absolute URL
624
     * @see self::withoutBase()
625
     */
626
    public function withBase($baseUrl)
627
    {
628
        $browser = clone $this;
629
        if ($baseUrl === null) {
630
            $browser->baseUrl = null;
631
            return $browser;
632
        }
633
634
        $browser->baseUrl = new Uri($baseUrl);
635
        if (!\in_array($browser->baseUrl->getScheme(), array('http', 'https')) || $browser->baseUrl->getHost() === '') {
636
            throw new \InvalidArgumentException('Base URL must be absolute');
637
        }
638
639
        return $browser;
640
    }
641
642
    /**
643
     * Changes the HTTP protocol version that will be used for all subsequent requests.
644
     *
645
     * All the above [request methods](#request-methods) default to sending
646
     * requests as HTTP/1.1. This is the preferred HTTP protocol version which
647
     * also provides decent backwards-compatibility with legacy HTTP/1.0
648
     * servers. As such, there should rarely be a need to explicitly change this
649
     * protocol version.
650
     *
651
     * If you want to explicitly use the legacy HTTP/1.0 protocol version, you
652
     * can use this method:
653
     *
654
     * ```php
655
     * $browser = $browser->withProtocolVersion('1.0');
656
     *
657
     * $browser->get($url)->then(…);
658
     * ```
659
     *
660
     * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
661
     * method actually returns a *new* [`Browser`](#browser) instance with the
662
     * new protocol version applied.
663
     *
664
     * @param string $protocolVersion HTTP protocol version to use, must be one of "1.1" or "1.0"
665
     * @return self
666
     * @throws InvalidArgumentException
667
     */
668
    public function withProtocolVersion($protocolVersion)
669
    {
670
        if (!\in_array($protocolVersion, array('1.0', '1.1'), true)) {
671
            throw new InvalidArgumentException('Invalid HTTP protocol version, must be one of "1.1" or "1.0"');
672
        }
673
674
        $browser = clone $this;
675
        $browser->protocolVersion = (string) $protocolVersion;
676
677
        return $browser;
678
    }
679
680
    /**
681
     * Changes the maximum size for buffering a response body.
682
     *
683
     * The preferred way to send an HTTP request is by using the above
684
     * [request methods](#request-methods), for example the [`get()`](#get)
685
     * method to send an HTTP `GET` request. Each of these methods will buffer
686
     * the whole response body in memory by default. This is easy to get started
687
     * and works reasonably well for smaller responses.
688
     *
689
     * By default, the response body buffer will be limited to 16 MiB. If the
690
     * response body exceeds this maximum size, the request will be rejected.
691
     *
692
     * You can pass in the maximum number of bytes to buffer:
693
     *
694
     * ```php
695
     * $browser = $browser->withResponseBuffer(1024 * 1024);
696
     *
697
     * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
698
     *     // response body will not exceed 1 MiB
699
     *     var_dump($response->getHeaders(), (string) $response->getBody());
700
     * }, function (Exception $e) {
701
     *     echo 'Error: ' . $e->getMessage() . PHP_EOL;
702
     * });
703
     * ```
704
     *
705
     * Note that the response body buffer has to be kept in memory for each
706
     * pending request until its transfer is completed and it will only be freed
707
     * after a pending request is fulfilled. As such, increasing this maximum
708
     * buffer size to allow larger response bodies is usually not recommended.
709
     * Instead, you can use the [`requestStreaming()` method](#requeststreaming)
710
     * to receive responses with arbitrary sizes without buffering. Accordingly,
711
     * this maximum buffer size setting has no effect on streaming responses.
712
     *
713
     * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
714
     * method actually returns a *new* [`Browser`](#browser) instance with the
715
     * given setting applied.
716
     *
717
     * @param int $maximumSize
718
     * @return self
719
     * @see self::requestStreaming()
720
     */
721
    public function withResponseBuffer($maximumSize)
722
    {
723
        return $this->withOptions(array(
724
            'maximumSize' => $maximumSize
725
        ));
726
    }
727
728
    /**
729
     * Changes the [options](#options) to use:
730
     *
731
     * The [`Browser`](#browser) class exposes several options for the handling of
732
     * HTTP transactions. These options resemble some of PHP's
733
     * [HTTP context options](http://php.net/manual/en/context.http.php) and
734
     * can be controlled via the following API (and their defaults):
735
     *
736
     * ```php
737
     * // deprecated
738
     * $newBrowser = $browser->withOptions(array(
739
     *     'timeout' => null, // see withTimeout() instead
740
     *     'followRedirects' => true, // see withFollowRedirects() instead
741
     *     'maxRedirects' => 10, // see withFollowRedirects() instead
742
     *     'obeySuccessCode' => true, // see withRejectErrorResponse() instead
743
     *     'streaming' => false, // deprecated, see requestStreaming() instead
744
     * ));
745
     * ```
746
     *
747
     * See also [timeouts](#timeouts), [redirects](#redirects) and
748
     * [streaming](#streaming) for more details.
749
     *
750
     * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
751
     * method actually returns a *new* [`Browser`](#browser) instance with the
752
     * options applied.
753
     *
754
     * @param array $options
755
     * @return self
756
     * @see self::withTimeout()
757
     * @see self::withFollowRedirects()
758
     * @see self::withRejectErrorResponse()
759
     */
760
    private function withOptions(array $options)
761
    {
762
        $browser = clone $this;
763
        $browser->transaction = $this->transaction->withOptions($options);
764
765
        return $browser;
766
    }
767
768
    /**
769
     * @param string                         $method
770
     * @param string                         $url
771
     * @param array                          $headers
772
     * @param string|ReadableStreamInterface $body
773
     * @return PromiseInterface<ResponseInterface,\Exception>
774
     */
775
    private function requestMayBeStreaming($method, $url, array $headers = array(), $body = '')
776
    {
777
        if ($this->baseUrl !== null) {
778
            // ensure we're actually below the base URL
779
            $url = Uri::resolve($this->baseUrl, $url);
780
        }
781
782
        if ($body instanceof ReadableStreamInterface) {
783
            $body = new ReadableBodyStream($body);
784
        }
785
786
        return $this->transaction->send(
787
            new Request($method, $url, $headers, $body, $this->protocolVersion)
788
        );
789
    }
790
}
791