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

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