Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
namespace GuzzleHttp;
4
 
5
use GuzzleHttp\Cookie\CookieJar;
6
use GuzzleHttp\Exception\GuzzleException;
7
use GuzzleHttp\Exception\InvalidArgumentException;
8
use GuzzleHttp\Promise as P;
9
use GuzzleHttp\Promise\PromiseInterface;
10
use Psr\Http\Message\RequestInterface;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Http\Message\UriInterface;
13
 
14
/**
15
 * @final
16
 */
17
class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
18
{
19
    use ClientTrait;
20
 
21
    /**
22
     * @var array Default request options
23
     */
24
    private $config;
25
 
26
    /**
27
     * Clients accept an array of constructor parameters.
28
     *
29
     * Here's an example of creating a client using a base_uri and an array of
30
     * default request options to apply to each request:
31
     *
32
     *     $client = new Client([
33
     *         'base_uri'        => 'http://www.foo.com/1.0/',
34
     *         'timeout'         => 0,
35
     *         'allow_redirects' => false,
36
     *         'proxy'           => '192.168.16.1:10'
37
     *     ]);
38
     *
39
     * Client configuration settings include the following options:
40
     *
41
     * - handler: (callable) Function that transfers HTTP requests over the
42
     *   wire. The function is called with a Psr7\Http\Message\RequestInterface
43
     *   and array of transfer options, and must return a
44
     *   GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
45
     *   Psr7\Http\Message\ResponseInterface on success.
46
     *   If no handler is provided, a default handler will be created
47
     *   that enables all of the request options below by attaching all of the
48
     *   default middleware to the handler.
49
     * - base_uri: (string|UriInterface) Base URI of the client that is merged
50
     *   into relative URIs. Can be a string or instance of UriInterface.
51
     * - **: any request option
52
     *
53
     * @param array $config Client configuration settings.
54
     *
55
     * @see \GuzzleHttp\RequestOptions for a list of available request options.
56
     */
57
    public function __construct(array $config = [])
58
    {
59
        if (!isset($config['handler'])) {
60
            $config['handler'] = HandlerStack::create();
61
        } elseif (!\is_callable($config['handler'])) {
62
            throw new InvalidArgumentException('handler must be a callable');
63
        }
64
 
65
        // Convert the base_uri to a UriInterface
66
        if (isset($config['base_uri'])) {
67
            $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']);
68
        }
69
 
70
        $this->configureDefaults($config);
71
    }
72
 
73
    /**
74
     * @param string $method
75
     * @param array  $args
76
     *
77
     * @return PromiseInterface|ResponseInterface
78
     *
79
     * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
80
     */
81
    public function __call($method, $args)
82
    {
83
        if (\count($args) < 1) {
84
            throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
85
        }
86
 
87
        $uri = $args[0];
88
        $opts = $args[1] ?? [];
89
 
90
        return \substr($method, -5) === 'Async'
91
            ? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
92
            : $this->request($method, $uri, $opts);
93
    }
94
 
95
    /**
96
     * Asynchronously send an HTTP request.
97
     *
98
     * @param array $options Request options to apply to the given
99
     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
100
     */
101
    public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
102
    {
103
        // Merge the base URI into the request URI if needed.
104
        $options = $this->prepareDefaults($options);
105
 
106
        return $this->transfer(
107
            $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
108
            $options
109
        );
110
    }
111
 
112
    /**
113
     * Send an HTTP request.
114
     *
115
     * @param array $options Request options to apply to the given
116
     *                       request and to the transfer. See \GuzzleHttp\RequestOptions.
117
     *
118
     * @throws GuzzleException
119
     */
120
    public function send(RequestInterface $request, array $options = []): ResponseInterface
121
    {
122
        $options[RequestOptions::SYNCHRONOUS] = true;
123
        return $this->sendAsync($request, $options)->wait();
124
    }
125
 
126
    /**
127
     * The HttpClient PSR (PSR-18) specify this method.
128
     *
129
     * @inheritDoc
130
     */
131
    public function sendRequest(RequestInterface $request): ResponseInterface
132
    {
133
        $options[RequestOptions::SYNCHRONOUS] = true;
134
        $options[RequestOptions::ALLOW_REDIRECTS] = false;
135
        $options[RequestOptions::HTTP_ERRORS] = false;
136
 
137
        return $this->sendAsync($request, $options)->wait();
138
    }
139
 
140
    /**
141
     * Create and send an asynchronous HTTP request.
142
     *
143
     * Use an absolute path to override the base path of the client, or a
144
     * relative path to append to the base path of the client. The URL can
145
     * contain the query string as well. Use an array to provide a URL
146
     * template and additional variables to use in the URL template expansion.
147
     *
148
     * @param string              $method  HTTP method
149
     * @param string|UriInterface $uri     URI object or string.
150
     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
151
     */
152
    public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
153
    {
154
        $options = $this->prepareDefaults($options);
155
        // Remove request modifying parameter because it can be done up-front.
156
        $headers = $options['headers'] ?? [];
157
        $body = $options['body'] ?? null;
158
        $version = $options['version'] ?? '1.1';
159
        // Merge the URI into the base URI.
160
        $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
161
        if (\is_array($body)) {
162
            throw $this->invalidBody();
163
        }
164
        $request = new Psr7\Request($method, $uri, $headers, $body, $version);
165
        // Remove the option so that they are not doubly-applied.
166
        unset($options['headers'], $options['body'], $options['version']);
167
 
168
        return $this->transfer($request, $options);
169
    }
170
 
171
    /**
172
     * Create and send an HTTP request.
173
     *
174
     * Use an absolute path to override the base path of the client, or a
175
     * relative path to append to the base path of the client. The URL can
176
     * contain the query string as well.
177
     *
178
     * @param string              $method  HTTP method.
179
     * @param string|UriInterface $uri     URI object or string.
180
     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
181
     *
182
     * @throws GuzzleException
183
     */
184
    public function request(string $method, $uri = '', array $options = []): ResponseInterface
185
    {
186
        $options[RequestOptions::SYNCHRONOUS] = true;
187
        return $this->requestAsync($method, $uri, $options)->wait();
188
    }
189
 
190
    /**
191
     * Get a client configuration option.
192
     *
193
     * These options include default request options of the client, a "handler"
194
     * (if utilized by the concrete client), and a "base_uri" if utilized by
195
     * the concrete client.
196
     *
197
     * @param string|null $option The config option to retrieve.
198
     *
199
     * @return mixed
200
     *
201
     * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
202
     */
203
    public function getConfig(?string $option = null)
204
    {
205
        return $option === null
206
            ? $this->config
207
            : ($this->config[$option] ?? null);
208
    }
209
 
210
    private function buildUri(UriInterface $uri, array $config): UriInterface
211
    {
212
        if (isset($config['base_uri'])) {
213
            $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
214
        }
215
 
216
        if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
217
            $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
218
            $uri = Utils::idnUriConvert($uri, $idnOptions);
219
        }
220
 
221
        return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
222
    }
223
 
224
    /**
225
     * Configures the default options for a client.
226
     */
227
    private function configureDefaults(array $config): void
228
    {
229
        $defaults = [
230
            'allow_redirects' => RedirectMiddleware::$defaultSettings,
231
            'http_errors'     => true,
232
            'decode_content'  => true,
233
            'verify'          => true,
234
            'cookies'         => false,
235
            'idn_conversion'  => false,
236
        ];
237
 
238
        // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
239
 
240
        // We can only trust the HTTP_PROXY environment variable in a CLI
241
        // process due to the fact that PHP has no reliable mechanism to
242
        // get environment variables that start with "HTTP_".
243
        if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) {
244
            $defaults['proxy']['http'] = $proxy;
245
        }
246
 
247
        if ($proxy = Utils::getenv('HTTPS_PROXY')) {
248
            $defaults['proxy']['https'] = $proxy;
249
        }
250
 
251
        if ($noProxy = Utils::getenv('NO_PROXY')) {
252
            $cleanedNoProxy = \str_replace(' ', '', $noProxy);
253
            $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
254
        }
255
 
256
        $this->config = $config + $defaults;
257
 
258
        if (!empty($config['cookies']) && $config['cookies'] === true) {
259
            $this->config['cookies'] = new CookieJar();
260
        }
261
 
262
        // Add the default user-agent header.
263
        if (!isset($this->config['headers'])) {
264
            $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()];
265
        } else {
266
            // Add the User-Agent header if one was not already set.
267
            foreach (\array_keys($this->config['headers']) as $name) {
268
                if (\strtolower($name) === 'user-agent') {
269
                    return;
270
                }
271
            }
272
            $this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
273
        }
274
    }
275
 
276
    /**
277
     * Merges default options into the array.
278
     *
279
     * @param array $options Options to modify by reference
280
     */
281
    private function prepareDefaults(array $options): array
282
    {
283
        $defaults = $this->config;
284
 
285
        if (!empty($defaults['headers'])) {
286
            // Default headers are only added if they are not present.
287
            $defaults['_conditional'] = $defaults['headers'];
288
            unset($defaults['headers']);
289
        }
290
 
291
        // Special handling for headers is required as they are added as
292
        // conditional headers and as headers passed to a request ctor.
293
        if (\array_key_exists('headers', $options)) {
294
            // Allows default headers to be unset.
295
            if ($options['headers'] === null) {
296
                $defaults['_conditional'] = [];
297
                unset($options['headers']);
298
            } elseif (!\is_array($options['headers'])) {
299
                throw new InvalidArgumentException('headers must be an array');
300
            }
301
        }
302
 
303
        // Shallow merge defaults underneath options.
304
        $result = $options + $defaults;
305
 
306
        // Remove null values.
307
        foreach ($result as $k => $v) {
308
            if ($v === null) {
309
                unset($result[$k]);
310
            }
311
        }
312
 
313
        return $result;
314
    }
315
 
316
    /**
317
     * Transfers the given request and applies request options.
318
     *
319
     * The URI of the request is not modified and the request options are used
320
     * as-is without merging in default options.
321
     *
322
     * @param array $options See \GuzzleHttp\RequestOptions.
323
     */
324
    private function transfer(RequestInterface $request, array $options): PromiseInterface
325
    {
326
        $request = $this->applyOptions($request, $options);
327
        /** @var HandlerStack $handler */
328
        $handler = $options['handler'];
329
 
330
        try {
331
            return P\Create::promiseFor($handler($request, $options));
332
        } catch (\Exception $e) {
333
            return P\Create::rejectionFor($e);
334
        }
335
    }
336
 
337
    /**
338
     * Applies the array of request options to a request.
339
     */
340
    private function applyOptions(RequestInterface $request, array &$options): RequestInterface
341
    {
342
        $modify = [
343
            'set_headers' => [],
344
        ];
345
 
346
        if (isset($options['headers'])) {
347
            if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) {
348
                throw new InvalidArgumentException('The headers array must have header name as keys.');
349
            }
350
            $modify['set_headers'] = $options['headers'];
351
            unset($options['headers']);
352
        }
353
 
354
        if (isset($options['form_params'])) {
355
            if (isset($options['multipart'])) {
356
                throw new InvalidArgumentException('You cannot use '
357
                    . 'form_params and multipart at the same time. Use the '
358
                    . 'form_params option if you want to send application/'
359
                    . 'x-www-form-urlencoded requests, and the multipart '
360
                    . 'option to send multipart/form-data requests.');
361
            }
362
            $options['body'] = \http_build_query($options['form_params'], '', '&');
363
            unset($options['form_params']);
364
            // Ensure that we don't have the header in different case and set the new value.
365
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
366
            $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
367
        }
368
 
369
        if (isset($options['multipart'])) {
370
            $options['body'] = new Psr7\MultipartStream($options['multipart']);
371
            unset($options['multipart']);
372
        }
373
 
374
        if (isset($options['json'])) {
375
            $options['body'] = Utils::jsonEncode($options['json']);
376
            unset($options['json']);
377
            // Ensure that we don't have the header in different case and set the new value.
378
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
379
            $options['_conditional']['Content-Type'] = 'application/json';
380
        }
381
 
382
        if (!empty($options['decode_content'])
383
            && $options['decode_content'] !== true
384
        ) {
385
            // Ensure that we don't have the header in different case and set the new value.
386
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
387
            $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
388
        }
389
 
390
        if (isset($options['body'])) {
391
            if (\is_array($options['body'])) {
392
                throw $this->invalidBody();
393
            }
394
            $modify['body'] = Psr7\Utils::streamFor($options['body']);
395
            unset($options['body']);
396
        }
397
 
398
        if (!empty($options['auth']) && \is_array($options['auth'])) {
399
            $value = $options['auth'];
400
            $type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
401
            switch ($type) {
402
                case 'basic':
403
                    // Ensure that we don't have the header in different case and set the new value.
404
                    $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
405
                    $modify['set_headers']['Authorization'] = 'Basic '
406
                        . \base64_encode("$value[0]:$value[1]");
407
                    break;
408
                case 'digest':
409
                    // @todo: Do not rely on curl
410
                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
411
                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
412
                    break;
413
                case 'ntlm':
414
                    $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
415
                    $options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
416
                    break;
417
            }
418
        }
419
 
420
        if (isset($options['query'])) {
421
            $value = $options['query'];
422
            if (\is_array($value)) {
423
                $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
424
            }
425
            if (!\is_string($value)) {
426
                throw new InvalidArgumentException('query must be a string or array');
427
            }
428
            $modify['query'] = $value;
429
            unset($options['query']);
430
        }
431
 
432
        // Ensure that sink is not an invalid value.
433
        if (isset($options['sink'])) {
434
            // TODO: Add more sink validation?
435
            if (\is_bool($options['sink'])) {
436
                throw new InvalidArgumentException('sink must not be a boolean');
437
            }
438
        }
439
 
440
        $request = Psr7\Utils::modifyRequest($request, $modify);
441
        if ($request->getBody() instanceof Psr7\MultipartStream) {
442
            // Use a multipart/form-data POST if a Content-Type is not set.
443
            // Ensure that we don't have the header in different case and set the new value.
444
            $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
445
            $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
446
                . $request->getBody()->getBoundary();
447
        }
448
 
449
        // Merge in conditional headers if they are not present.
450
        if (isset($options['_conditional'])) {
451
            // Build up the changes so it's in a single clone of the message.
452
            $modify = [];
453
            foreach ($options['_conditional'] as $k => $v) {
454
                if (!$request->hasHeader($k)) {
455
                    $modify['set_headers'][$k] = $v;
456
                }
457
            }
458
            $request = Psr7\Utils::modifyRequest($request, $modify);
459
            // Don't pass this internal value along to middleware/handlers.
460
            unset($options['_conditional']);
461
        }
462
 
463
        return $request;
464
    }
465
 
466
    /**
467
     * Return an InvalidArgumentException with pre-set message.
468
     */
469
    private function invalidBody(): InvalidArgumentException
470
    {
471
        return new InvalidArgumentException('Passing in the "body" request '
472
            . 'option as an array to send a request is not supported. '
473
            . 'Please use the "form_params" request option to send a '
474
            . 'application/x-www-form-urlencoded request, or the "multipart" '
475
            . 'request option to send a multipart/form-data request.');
476
    }
477
}