`. * @type callable $authHttpHandler A handler used to deliver PSR-7 * requests specifically for authentication. Function signature * should match: * `function (RequestInterface $request, array $options = []) : ResponseInterface`. * @type callable $httpHandler A handler used to deliver PSR-7 requests. * Function signature should match: * `function (RequestInterface $request, array $options = []) : ResponseInterface`. * @type array $restOptions HTTP client specific configuration options. * @type bool $shouldSignRequest Whether to enable request signing. * @type callable $restRetryFunction Sets the conditions for whether or * not a request should attempt to retry. * @type callable $restDelayFunction Sets the conditions for determining * how long to wait between attempts to retry. * } */ public function __construct(array $config = []) { $this->setCommonDefaults($config); $config += ['accessToken' => null, 'asyncHttpHandler' => null, 'authHttpHandler' => null, 'httpHandler' => null, 'restOptions' => [], 'shouldSignRequest' => true, 'componentVersion' => null, 'restRetryFunction' => null, 'restDelayFunction' => null]; $this->componentVersion = $config['componentVersion']; $this->accessToken = $config['accessToken']; $this->restOptions = $config['restOptions']; $this->shouldSignRequest = $config['shouldSignRequest']; $this->retryFunction = $config['restRetryFunction'] ?: $this->getRetryFunction(); $this->delayFunction = $config['restDelayFunction'] ?: function ($delay) { usleep($delay); }; $this->httpHandler = $config['httpHandler'] ?: \DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\HttpHandler\HttpHandlerFactory::build(); $this->authHttpHandler = $config['authHttpHandler'] ?: $this->httpHandler; $this->asyncHttpHandler = $config['asyncHttpHandler'] ?: $this->buildDefaultAsyncHandler(); if ($this->credentialsFetcher instanceof AnonymousCredentials) { $this->shouldSignRequest = false; } } /** * Deliver the request. * * @param RequestInterface $request A PSR-7 request. * @param array $options [optional] { * Request options. * * @type float $requestTimeout Seconds to wait before timing out the * request. **Defaults to** `0`. * @type int $retries Number of retries for a failed request. * **Defaults to** `3`. * @type callable $restRetryFunction Sets the conditions for whether or * not a request should attempt to retry. * @type callable $restDelayFunction Sets the conditions for determining * how long to wait between attempts to retry. * @type array $restOptions HTTP client specific configuration options. * } * @return ResponseInterface */ public function send(\DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\RequestInterface $request, array $options = []) { $retryOptions = $this->getRetryOptions($options); $backoff = $this->configureBackoff($retryOptions['retries'], $retryOptions['retryFunction'], $retryOptions['delayFunction']); try { return $backoff->execute($this->httpHandler, [$this->applyHeaders($request), $this->getRequestOptions($options)]); } catch (\Exception $ex) { throw $this->convertToGoogleException($ex); } } /** * Deliver the request asynchronously. * * @param RequestInterface $request A PSR-7 request. * @param array $options [optional] { * Request options. * * @type float $requestTimeout Seconds to wait before timing out the * request. **Defaults to** `0`. * @type int $retries Number of retries for a failed request. * **Defaults to** `3`. * @type callable $restRetryFunction Sets the conditions for whether or * not a request should attempt to retry. * @type callable $restDelayFunction Sets the conditions for determining * how long to wait between attempts to retry. * @type array $restOptions HTTP client specific configuration options. * } * @return PromiseInterface * @experimental The experimental flag means that while we believe this method * or class is ready for use, it may change before release in backwards- * incompatible ways. Please use with caution, and test thoroughly when * upgrading. */ public function sendAsync(\DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\RequestInterface $request, array $options = []) { // Unfortunately, the current ExponentialBackoff implementation doesn't // play nicely with promises. $retryAttempt = 0; $fn = function ($retryAttempt) use(&$fn, $request, $options) { $asyncHttpHandler = $this->asyncHttpHandler; $retryOptions = $this->getRetryOptions($options); return $asyncHttpHandler($this->applyHeaders($request), $this->getRequestOptions($options))->then(null, function (\Exception $ex) use($fn, $retryAttempt, $retryOptions) { $shouldRetry = $retryOptions['retryFunction']($ex); if ($shouldRetry === false || $retryAttempt >= $retryOptions['retries']) { throw $this->convertToGoogleException($ex); } $retryOptions['delayFunction'](\DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\ExponentialBackoff::calculateDelay($retryAttempt)); $retryAttempt++; return $fn($retryAttempt); }); }; return $fn($retryAttempt); } /** * Applies headers to the request. * * @param RequestInterface $request A PSR-7 request. * @return RequestInterface */ private function applyHeaders(\DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\RequestInterface $request) { $headers = ['User-Agent' => 'gcloud-php/' . $this->componentVersion, 'x-goog-api-client' => 'gl-php/' . PHP_VERSION . ' gccl/' . $this->componentVersion]; if ($this->shouldSignRequest) { $headers['Authorization'] = 'Bearer ' . $this->getToken(); } return \DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Psr7\modify_request($request, ['set_headers' => $headers]); } /** * Gets the access token. * * @return string */ private function getToken() { if ($this->accessToken) { return $this->accessToken; } return $this->fetchCredentials()['access_token']; } /** * Fetches credentials. * * @return array */ private function fetchCredentials() { $backoff = new \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\ExponentialBackoff($this->retries, $this->getRetryFunction()); try { return $backoff->execute([$this->getCredentialsFetcher(), 'fetchAuthToken'], [$this->authHttpHandler]); } catch (\Exception $ex) { throw $this->convertToGoogleException($ex); } } /** * Convert any exception to a Google Exception. * * @param \Exception $ex * @return Exception\ServiceException */ private function convertToGoogleException(\Exception $ex) { switch ($ex->getCode()) { case 400: $exception = \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\BadRequestException::class; break; case 404: $exception = \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\NotFoundException::class; break; case 409: $exception = \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ConflictException::class; break; case 412: $exception = \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\FailedPreconditionException::class; break; case 500: $exception = \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ServerException::class; break; case 504: $exception = \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\DeadlineExceededException::class; break; default: $exception = \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ServiceException::class; break; } return new $exception($this->getExceptionMessage($ex), $ex->getCode(), $ex); } /** * Gets the exception message. * * @param \Exception $ex * @return string */ private function getExceptionMessage(\Exception $ex) { if ($ex instanceof RequestException && $ex->hasResponse()) { $res = (string) $ex->getResponse()->getBody(); try { $this->jsonDecode($res); return $res; } catch (\InvalidArgumentException $e) { // no-op } } return $ex->getMessage(); } /** * Configures an exponential backoff implementation. * * @param int $retries * @param callable $retryFunction * @param callable $delayFunction * @return ExponentialBackoff */ private function configureBackoff($retries, callable $retryFunction, callable $delayFunction) { $backoff = new \DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\ExponentialBackoff($retries, $retryFunction); if ($delayFunction) { $backoff->setDelayFunction($delayFunction); } return $backoff; } /** * Gets a set of request options. * * @param array $options * @return array */ private function getRequestOptions(array $options) { $restOptions = isset($options['restOptions']) ? $options['restOptions'] : $this->restOptions; $timeout = isset($options['requestTimeout']) ? $options['requestTimeout'] : $this->requestTimeout; if ($timeout && !array_key_exists('timeout', $restOptions)) { $restOptions['timeout'] = $timeout; } return $restOptions; } /** * Gets a set of retry options. * * @param array $options * @return array */ private function getRetryOptions(array $options) { return ['retries' => isset($options['retries']) ? $options['retries'] : $this->retries, 'retryFunction' => isset($options['restRetryFunction']) ? $options['restRetryFunction'] : $this->retryFunction, 'delayFunction' => isset($options['restDelayFunction']) ? $options['restDelayFunction'] : $this->delayFunction]; } /** * Builds the default async HTTP handler. * * @return callable */ private function buildDefaultAsyncHandler() { $isGuzzleHandler = $this->httpHandler instanceof Guzzle6HttpHandler || $this->httpHandler instanceof Guzzle5HttpHandler; return $isGuzzleHandler ? [$this->httpHandler, 'async'] : [\DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\HttpHandler\HttpHandlerFactory::build(), 'async']; } }