vendor/guzzlehttp/guzzle/src/Client.php line 168

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