vendor/stripe/stripe-php/lib/ApiRequestor.php line 638

Open in your IDE?
  1. <?php
  2. namespace Stripe;
  3. /**
  4.  * Class ApiRequestor.
  5.  */
  6. class ApiRequestor
  7. {
  8.     /**
  9.      * @var null|string
  10.      */
  11.     private $_apiKey;
  12.     /**
  13.      * @var string
  14.      */
  15.     private $_apiBase;
  16.     /**
  17.      * @var null|array
  18.      */
  19.     private $_appInfo;
  20.     /**
  21.      * @var HttpClient\ClientInterface
  22.      */
  23.     private static $_httpClient;
  24.     /**
  25.      * @var HttpClient\StreamingClientInterface
  26.      */
  27.     private static $_streamingHttpClient;
  28.     /**
  29.      * @var RequestTelemetry
  30.      */
  31.     private static $requestTelemetry;
  32.     private static $OPTIONS_KEYS = ['api_key''idempotency_key''stripe_account''stripe_context''stripe_version''api_base'];
  33.     /**
  34.      * ApiRequestor constructor.
  35.      *
  36.      * @param null|string $apiKey
  37.      * @param null|string $apiBase
  38.      * @param null|array $appInfo
  39.      */
  40.     public function __construct($apiKey null$apiBase null$appInfo null)
  41.     {
  42.         $this->_apiKey $apiKey;
  43.         if (!$apiBase) {
  44.             $apiBase Stripe::$apiBase;
  45.         }
  46.         $this->_apiBase $apiBase;
  47.         $this->_appInfo $appInfo;
  48.     }
  49.     /**
  50.      * Creates a telemetry json blob for use in 'X-Stripe-Client-Telemetry' headers.
  51.      *
  52.      * @static
  53.      *
  54.      * @param RequestTelemetry $requestTelemetry
  55.      *
  56.      * @return string
  57.      */
  58.     private static function _telemetryJson($requestTelemetry)
  59.     {
  60.         $payload = [
  61.             'last_request_metrics' => [
  62.                 'request_id' => $requestTelemetry->requestId,
  63.                 'request_duration_ms' => $requestTelemetry->requestDuration,
  64.             ],
  65.         ];
  66.         if (\count($requestTelemetry->usage) > 0) {
  67.             $payload['last_request_metrics']['usage'] = $requestTelemetry->usage;
  68.         }
  69.         $result \json_encode($payload);
  70.         if (false !== $result) {
  71.             return $result;
  72.         }
  73.         Stripe::getLogger()->error('Serializing telemetry payload failed!');
  74.         return '{}';
  75.     }
  76.     /**
  77.      * @static
  78.      *
  79.      * @param ApiResource|array|bool|mixed $d
  80.      *
  81.      * @return ApiResource|array|mixed|string
  82.      */
  83.     private static function _encodeObjects($d)
  84.     {
  85.         if ($d instanceof ApiResource) {
  86.             return Util\Util::utf8($d->id);
  87.         }
  88.         if (true === $d) {
  89.             return 'true';
  90.         }
  91.         if (false === $d) {
  92.             return 'false';
  93.         }
  94.         if (\is_array($d)) {
  95.             $res = [];
  96.             foreach ($d as $k => $v) {
  97.                 $res[$k] = self::_encodeObjects($v);
  98.             }
  99.             return $res;
  100.         }
  101.         return Util\Util::utf8($d);
  102.     }
  103.     /**
  104.      * @param 'delete'|'get'|'post'     $method
  105.      * @param string     $url
  106.      * @param null|array $params
  107.      * @param null|array $headers
  108.      * @param 'v1'|'v2' $apiMode
  109.      * @param string[] $usage
  110.      * @param null|int $maxNetworkRetries
  111.      *
  112.      * @return array tuple containing (ApiReponse, API key)
  113.      *
  114.      * @throws Exception\ApiErrorException
  115.      */
  116.     public function request($method$url$params null$headers null$apiMode 'v1'$usage = [], $maxNetworkRetries null)
  117.     {
  118.         $params $params ?: [];
  119.         $headers $headers ?: [];
  120.         list($rbody$rcode$rheaders$myApiKey)
  121.             = $this->_requestRaw($method$url$params$headers$apiMode$usage$maxNetworkRetries);
  122.         $json $this->_interpretResponse($rbody$rcode$rheaders$apiMode);
  123.         $resp = new ApiResponse($rbody$rcode$rheaders$json);
  124.         return [$resp$myApiKey];
  125.     }
  126.     /**
  127.      * @param 'delete'|'get'|'post' $method
  128.      * @param string     $url
  129.      * @param callable $readBodyChunkCallable
  130.      * @param null|array $params
  131.      * @param null|array $headers
  132.      * @param 'v1'|'v2' $apiMode
  133.      * @param string[] $usage
  134.      * @param null|int $maxNetworkRetries
  135.      *
  136.      * @throws Exception\ApiErrorException
  137.      */
  138.     public function requestStream($method$url$readBodyChunkCallable$params null$headers null$apiMode 'v1'$usage = [], $maxNetworkRetries null)
  139.     {
  140.         $params $params ?: [];
  141.         $headers $headers ?: [];
  142.         list($rbody$rcode$rheaders$myApiKey)
  143.             = $this->_requestRawStreaming($method$url$params$headers$apiMode$usage$readBodyChunkCallable$maxNetworkRetries);
  144.         if ($rcode >= 300) {
  145.             $this->_interpretResponse($rbody$rcode$rheaders$apiMode);
  146.         }
  147.     }
  148.     /**
  149.      * @param string $rbody a JSON string
  150.      * @param int $rcode
  151.      * @param array $rheaders
  152.      * @param array $resp
  153.      * @param 'v1'|'v2' $apiMode
  154.      *
  155.      * @throws Exception\UnexpectedValueException
  156.      * @throws Exception\ApiErrorException
  157.      */
  158.     public function handleErrorResponse($rbody$rcode$rheaders$resp$apiMode)
  159.     {
  160.         if (!\is_array($resp) || !isset($resp['error'])) {
  161.             $msg "Invalid response object from API: {$rbody} "
  162.                 "(HTTP response code was {$rcode})";
  163.             throw new Exception\UnexpectedValueException($msg);
  164.         }
  165.         $errorData $resp['error'];
  166.         $error null;
  167.         if (\is_string($errorData)) {
  168.             $error self::_specificOAuthError($rbody$rcode$rheaders$resp$errorData);
  169.         }
  170.         if (!$error) {
  171.             $error 'v1' === $apiMode self::_specificV1APIError($rbody$rcode$rheaders$resp$errorData) : self::_specificV2APIError($rbody$rcode$rheaders$resp$errorData);
  172.         }
  173.         throw $error;
  174.     }
  175.     /**
  176.      * @static
  177.      *
  178.      * @param string $rbody
  179.      * @param int    $rcode
  180.      * @param array  $rheaders
  181.      * @param array  $resp
  182.      * @param array  $errorData
  183.      *
  184.      * @return Exception\ApiErrorException
  185.      */
  186.     private static function _specificV1APIError($rbody$rcode$rheaders$resp$errorData)
  187.     {
  188.         $msg = isset($errorData['message']) ? $errorData['message'] : null;
  189.         $param = isset($errorData['param']) ? $errorData['param'] : null;
  190.         $code = isset($errorData['code']) ? $errorData['code'] : null;
  191.         $type = isset($errorData['type']) ? $errorData['type'] : null;
  192.         $declineCode = isset($errorData['decline_code']) ? $errorData['decline_code'] : null;
  193.         switch ($rcode) {
  194.             case 400:
  195.                 // 'rate_limit' code is deprecated, but left here for backwards compatibility
  196.                 // for API versions earlier than 2015-09-08
  197.                 if ('rate_limit' === $code) {
  198.                     return Exception\RateLimitException::factory($msg$rcode$rbody$resp$rheaders$code$param);
  199.                 }
  200.                 if ('idempotency_error' === $type) {
  201.                     return Exception\IdempotencyException::factory($msg$rcode$rbody$resp$rheaders$code);
  202.                 }
  203.                 // fall through in generic 400 or 404, returns InvalidRequestException by default
  204.                 // no break
  205.             case 404:
  206.                 return Exception\InvalidRequestException::factory($msg$rcode$rbody$resp$rheaders$code$param);
  207.             case 401:
  208.                 return Exception\AuthenticationException::factory($msg$rcode$rbody$resp$rheaders$code);
  209.             case 402:
  210.                 return Exception\CardException::factory($msg$rcode$rbody$resp$rheaders$code$declineCode$param);
  211.             case 403:
  212.                 return Exception\PermissionException::factory($msg$rcode$rbody$resp$rheaders$code);
  213.             case 429:
  214.                 return Exception\RateLimitException::factory($msg$rcode$rbody$resp$rheaders$code$param);
  215.             default:
  216.                 return Exception\UnknownApiErrorException::factory($msg$rcode$rbody$resp$rheaders$code);
  217.         }
  218.     }
  219.     /**
  220.      * @static
  221.      *
  222.      * @param string $rbody
  223.      * @param int    $rcode
  224.      * @param array  $rheaders
  225.      * @param array  $resp
  226.      * @param array  $errorData
  227.      *
  228.      * @return Exception\ApiErrorException
  229.      */
  230.     private static function _specificV2APIError($rbody$rcode$rheaders$resp$errorData)
  231.     {
  232.         $msg = isset($errorData['message']) ? $errorData['message'] : null;
  233.         $code = isset($errorData['code']) ? $errorData['code'] : null;
  234.         $type = isset($errorData['type']) ? $errorData['type'] : null;
  235.         switch ($type) {
  236.             case 'idempotency_error':
  237.                 return Exception\IdempotencyException::factory($msg$rcode$rbody$resp$rheaders$code);
  238.                 // The beginning of the section generated from our OpenAPI spec
  239.             case 'temporary_session_expired':
  240.                 return Exception\TemporarySessionExpiredException::factory(
  241.                     $msg,
  242.                     $rcode,
  243.                     $rbody,
  244.                     $resp,
  245.                     $rheaders,
  246.                     $code
  247.                 );
  248.                 // The end of the section generated from our OpenAPI spec
  249.             default:
  250.                 return self::_specificV1APIError($rbody$rcode$rheaders$resp$errorData);
  251.         }
  252.     }
  253.     /**
  254.      * @static
  255.      *
  256.      * @param bool|string $rbody
  257.      * @param int         $rcode
  258.      * @param array       $rheaders
  259.      * @param array       $resp
  260.      * @param string      $errorCode
  261.      *
  262.      * @return Exception\OAuth\OAuthErrorException
  263.      */
  264.     private static function _specificOAuthError($rbody$rcode$rheaders$resp$errorCode)
  265.     {
  266.         $description = isset($resp['error_description']) ? $resp['error_description'] : $errorCode;
  267.         switch ($errorCode) {
  268.             case 'invalid_client':
  269.                 return Exception\OAuth\InvalidClientException::factory($description$rcode$rbody$resp$rheaders$errorCode);
  270.             case 'invalid_grant':
  271.                 return Exception\OAuth\InvalidGrantException::factory($description$rcode$rbody$resp$rheaders$errorCode);
  272.             case 'invalid_request':
  273.                 return Exception\OAuth\InvalidRequestException::factory($description$rcode$rbody$resp$rheaders$errorCode);
  274.             case 'invalid_scope':
  275.                 return Exception\OAuth\InvalidScopeException::factory($description$rcode$rbody$resp$rheaders$errorCode);
  276.             case 'unsupported_grant_type':
  277.                 return Exception\OAuth\UnsupportedGrantTypeException::factory($description$rcode$rbody$resp$rheaders$errorCode);
  278.             case 'unsupported_response_type':
  279.                 return Exception\OAuth\UnsupportedResponseTypeException::factory($description$rcode$rbody$resp$rheaders$errorCode);
  280.             default:
  281.                 return Exception\OAuth\UnknownOAuthErrorException::factory($description$rcode$rbody$resp$rheaders$errorCode);
  282.         }
  283.     }
  284.     /**
  285.      * @static
  286.      *
  287.      * @param null|array $appInfo
  288.      *
  289.      * @return null|string
  290.      */
  291.     private static function _formatAppInfo($appInfo)
  292.     {
  293.         if (null !== $appInfo) {
  294.             $string $appInfo['name'];
  295.             if (\array_key_exists('version'$appInfo) && null !== $appInfo['version']) {
  296.                 $string .= '/' $appInfo['version'];
  297.             }
  298.             if (\array_key_exists('url'$appInfo) && null !== $appInfo['url']) {
  299.                 $string .= ' (' $appInfo['url'] . ')';
  300.             }
  301.             return $string;
  302.         }
  303.         return null;
  304.     }
  305.     /**
  306.      * @static
  307.      *
  308.      * @param string $disableFunctionsOutput - String value of the 'disable_function' setting, as output by \ini_get('disable_functions')
  309.      * @param string $functionName - Name of the function we are interesting in seeing whether or not it is disabled
  310.      *
  311.      * @return bool
  312.      */
  313.     private static function _isDisabled($disableFunctionsOutput$functionName)
  314.     {
  315.         $disabledFunctions \explode(','$disableFunctionsOutput);
  316.         foreach ($disabledFunctions as $disabledFunction) {
  317.             if (\trim($disabledFunction) === $functionName) {
  318.                 return true;
  319.             }
  320.         }
  321.         return false;
  322.     }
  323.     /**
  324.      * @static
  325.      *
  326.      * @param string     $apiKey the Stripe API key, to be used in regular API requests
  327.      * @param null       $clientInfo client user agent information
  328.      * @param null       $appInfo information to identify a plugin that integrates Stripe using this library
  329.      * @param 'v1'|'v2' $apiMode
  330.      *
  331.      * @return array
  332.      */
  333.     private static function _defaultHeaders($apiKey$clientInfo null$appInfo null$apiMode 'v1')
  334.     {
  335.         $uaString "Stripe/{$apiMode} PhpBindings/" Stripe::VERSION;
  336.         $langVersion \PHP_VERSION;
  337.         $uname_disabled self::_isDisabled(\ini_get('disable_functions'), 'php_uname');
  338.         $uname $uname_disabled '(disabled)' \php_uname();
  339.         // Fallback to global configuration to maintain backwards compatibility.
  340.         $appInfo $appInfo ?: Stripe::getAppInfo();
  341.         $ua = [
  342.             'bindings_version' => Stripe::VERSION,
  343.             'lang' => 'php',
  344.             'lang_version' => $langVersion,
  345.             'publisher' => 'stripe',
  346.             'uname' => $uname,
  347.         ];
  348.         if ($clientInfo) {
  349.             $ua \array_merge($clientInfo$ua);
  350.         }
  351.         if (null !== $appInfo) {
  352.             $uaString .= ' ' self::_formatAppInfo($appInfo);
  353.             $ua['application'] = $appInfo;
  354.         }
  355.         return [
  356.             'X-Stripe-Client-User-Agent' => \json_encode($ua),
  357.             'User-Agent' => $uaString,
  358.             'Authorization' => 'Bearer ' $apiKey,
  359.             'Stripe-Version' => Stripe::getApiVersion(),
  360.         ];
  361.     }
  362.     /**
  363.      * @param 'delete'|'get'|'post' $method
  364.      * @param string $url
  365.      * @param array $params
  366.      * @param array $headers
  367.      * @param 'v1'|'v2' $apiMode
  368.      */
  369.     private function _prepareRequest($method$url$params$headers$apiMode)
  370.     {
  371.         $myApiKey $this->_apiKey;
  372.         if (!$myApiKey) {
  373.             $myApiKey Stripe::$apiKey;
  374.         }
  375.         if (!$myApiKey) {
  376.             $msg 'No API key provided.  (HINT: set your API key using '
  377.                 '"Stripe::setApiKey(<API-KEY>)".  You can generate API keys from '
  378.                 'the Stripe web interface.  See https://stripe.com/api for '
  379.                 'details, or email support@stripe.com if you have any questions.';
  380.             throw new Exception\AuthenticationException($msg);
  381.         }
  382.         // Clients can supply arbitrary additional keys to be included in the
  383.         // X-Stripe-Client-User-Agent header via the optional getUserAgentInfo()
  384.         // method
  385.         $clientUAInfo null;
  386.         if (\method_exists(self::httpClient(), 'getUserAgentInfo')) {
  387.             $clientUAInfo self::httpClient()->getUserAgentInfo();
  388.         }
  389.         if ($params && \is_array($params)) {
  390.             $optionKeysInParams \array_filter(
  391.                 self::$OPTIONS_KEYS,
  392.                 static function ($key) use ($params) {
  393.                     return \array_key_exists($key$params);
  394.                 }
  395.             );
  396.             if (\count($optionKeysInParams) > 0) {
  397.                 $message \sprintf('Options found in $params: %s. Options should '
  398.                     'be passed in their own array after $params. (HINT: pass an '
  399.                     'empty array to $params if you do not have any.)'\implode(', '$optionKeysInParams));
  400.                 \trigger_error($message\E_USER_WARNING);
  401.             }
  402.         }
  403.         $absUrl $this->_apiBase $url;
  404.         if ('v1' === $apiMode) {
  405.             $params self::_encodeObjects($params);
  406.         }
  407.         $defaultHeaders $this->_defaultHeaders($myApiKey$clientUAInfo$this->_appInfo$apiMode);
  408.         if (Stripe::$accountId) {
  409.             $defaultHeaders['Stripe-Account'] = Stripe::$accountId;
  410.         }
  411.         if (Stripe::$enableTelemetry && null !== self::$requestTelemetry) {
  412.             $defaultHeaders['X-Stripe-Client-Telemetry'] = self::_telemetryJson(self::$requestTelemetry);
  413.         }
  414.         $hasFile false;
  415.         foreach ($params as $k => $v) {
  416.             if (\is_resource($v)) {
  417.                 $hasFile true;
  418.                 $params[$k] = self::_processResourceParam($v);
  419.             } elseif ($v instanceof \CURLFile) {
  420.                 $hasFile true;
  421.             }
  422.         }
  423.         if ($hasFile) {
  424.             $defaultHeaders['Content-Type'] = 'multipart/form-data';
  425.         } elseif ('v2' === $apiMode) {
  426.             $defaultHeaders['Content-Type'] = 'application/json';
  427.         } elseif ('v1' === $apiMode) {
  428.             $defaultHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
  429.         } else {
  430.             throw new Exception\InvalidArgumentException('Unknown API mode: ' $apiMode);
  431.         }
  432.         $combinedHeaders \array_merge($defaultHeaders$headers);
  433.         $rawHeaders = [];
  434.         foreach ($combinedHeaders as $header => $value) {
  435.             $rawHeaders[] = $header ': ' $value;
  436.         }
  437.         return [$absUrl$rawHeaders$params$hasFile$myApiKey];
  438.     }
  439.     /**
  440.      * @param 'delete'|'get'|'post' $method
  441.      * @param string $url
  442.      * @param array $params
  443.      * @param array $headers
  444.      * @param 'v1'|'v2' $apiMode
  445.      * @param string[] $usage
  446.      * @param null|int $maxNetworkRetries
  447.      *
  448.      * @return array
  449.      *
  450.      * @throws Exception\AuthenticationException
  451.      * @throws Exception\ApiConnectionException
  452.      */
  453.     private function _requestRaw($method$url$params$headers$apiMode$usage$maxNetworkRetries)
  454.     {
  455.         list($absUrl$rawHeaders$params$hasFile$myApiKey) = $this->_prepareRequest($method$url$params$headers$apiMode);
  456.         // for some reason, PHP users will sometimes include null bytes in their paths, which leads to cryptic server 400s.
  457.         // we'll be louder about this to help catch issues earlier.
  458.         if (false !== \strpos($absUrl"\0") || false !== \strpos($absUrl'%00')) {
  459.             throw new Exception\InvalidRequestException("URLs may not contain null bytes ('\\0'); double check any IDs you're including with the request.");
  460.         }
  461.         $requestStartMs Util\Util::currentTimeMillis();
  462.         list($rbody$rcode$rheaders) = self::httpClient()->request(
  463.             $method,
  464.             $absUrl,
  465.             $rawHeaders,
  466.             $params,
  467.             $hasFile,
  468.             $apiMode,
  469.             $maxNetworkRetries
  470.         );
  471.         if (
  472.             isset($rheaders['request-id'])
  473.             && \is_string($rheaders['request-id'])
  474.             && '' !== $rheaders['request-id']
  475.         ) {
  476.             self::$requestTelemetry = new RequestTelemetry(
  477.                 $rheaders['request-id'],
  478.                 Util\Util::currentTimeMillis() - $requestStartMs,
  479.                 $usage
  480.             );
  481.         }
  482.         return [$rbody$rcode$rheaders$myApiKey];
  483.     }
  484.     /**
  485.      * @param 'delete'|'get'|'post' $method
  486.      * @param string $url
  487.      * @param array $params
  488.      * @param array $headers
  489.      * @param string[] $usage
  490.      * @param callable $readBodyChunkCallable
  491.      * @param 'v1'|'v2' $apiMode
  492.      * @param int $maxNetworkRetries
  493.      *
  494.      * @return array
  495.      *
  496.      * @throws Exception\AuthenticationException
  497.      * @throws Exception\ApiConnectionException
  498.      */
  499.     private function _requestRawStreaming($method$url$params$headers$apiMode$usage$readBodyChunkCallable$maxNetworkRetries)
  500.     {
  501.         list($absUrl$rawHeaders$params$hasFile$myApiKey) = $this->_prepareRequest($method$url$params$headers$apiMode);
  502.         $requestStartMs Util\Util::currentTimeMillis();
  503.         list($rbody$rcode$rheaders) = self::streamingHttpClient()->requestStream(
  504.             $method,
  505.             $absUrl,
  506.             $rawHeaders,
  507.             $params,
  508.             $hasFile,
  509.             $readBodyChunkCallable,
  510.             $maxNetworkRetries
  511.         );
  512.         if (
  513.             isset($rheaders['request-id'])
  514.             && \is_string($rheaders['request-id'])
  515.             && '' !== $rheaders['request-id']
  516.         ) {
  517.             self::$requestTelemetry = new RequestTelemetry(
  518.                 $rheaders['request-id'],
  519.                 Util\Util::currentTimeMillis() - $requestStartMs
  520.             );
  521.         }
  522.         return [$rbody$rcode$rheaders$myApiKey];
  523.     }
  524.     /**
  525.      * @param resource $resource
  526.      *
  527.      * @return \CURLFile|string
  528.      *
  529.      * @throws Exception\InvalidArgumentException
  530.      */
  531.     private function _processResourceParam($resource)
  532.     {
  533.         if ('stream' !== \get_resource_type($resource)) {
  534.             throw new Exception\InvalidArgumentException(
  535.                 'Attempted to upload a resource that is not a stream'
  536.             );
  537.         }
  538.         $metaData \stream_get_meta_data($resource);
  539.         if ('plainfile' !== $metaData['wrapper_type']) {
  540.             throw new Exception\InvalidArgumentException(
  541.                 'Only plainfile resource streams are supported'
  542.             );
  543.         }
  544.         // We don't have the filename or mimetype, but the API doesn't care
  545.         return new \CURLFile($metaData['uri']);
  546.     }
  547.     /**
  548.      * @param string $rbody
  549.      * @param int    $rcode
  550.      * @param array  $rheaders
  551.      * @param 'v1'|'v2'  $apiMode
  552.      *
  553.      * @return array
  554.      *
  555.      * @throws Exception\UnexpectedValueException
  556.      * @throws Exception\ApiErrorException
  557.      */
  558.     private function _interpretResponse($rbody$rcode$rheaders$apiMode)
  559.     {
  560.         $resp \json_decode($rbodytrue);
  561.         $jsonError \json_last_error();
  562.         if (null === $resp && \JSON_ERROR_NONE !== $jsonError) {
  563.             $msg "Invalid response body from API: {$rbody} "
  564.                 "(HTTP response code was {$rcode}, json_last_error() was {$jsonError})";
  565.             throw new Exception\UnexpectedValueException($msg$rcode);
  566.         }
  567.         if ($rcode 200 || $rcode >= 300) {
  568.             $this->handleErrorResponse($rbody$rcode$rheaders$resp$apiMode);
  569.         }
  570.         return $resp;
  571.     }
  572.     /**
  573.      * @static
  574.      *
  575.      * @param HttpClient\ClientInterface $client
  576.      */
  577.     public static function setHttpClient($client)
  578.     {
  579.         self::$_httpClient $client;
  580.     }
  581.     /**
  582.      * @static
  583.      *
  584.      * @param HttpClient\StreamingClientInterface $client
  585.      */
  586.     public static function setStreamingHttpClient($client)
  587.     {
  588.         self::$_streamingHttpClient $client;
  589.     }
  590.     /**
  591.      * @static
  592.      *
  593.      * Resets any stateful telemetry data
  594.      */
  595.     public static function resetTelemetry()
  596.     {
  597.         self::$requestTelemetry null;
  598.     }
  599.     /**
  600.      * @return HttpClient\ClientInterface
  601.      */
  602.     public static function httpClient()
  603.     {
  604.         if (!self::$_httpClient) {
  605.             self::$_httpClient HttpClient\CurlClient::instance();
  606.         }
  607.         return self::$_httpClient;
  608.     }
  609.     /**
  610.      * @return HttpClient\StreamingClientInterface
  611.      */
  612.     public static function streamingHttpClient()
  613.     {
  614.         if (!self::$_streamingHttpClient) {
  615.             self::$_streamingHttpClient HttpClient\CurlClient::instance();
  616.         }
  617.         return self::$_streamingHttpClient;
  618.     }
  619. }