<?php 
 
declare(strict_types=1); 
 
namespace Sentry\HttpClient; 
 
use GuzzleHttp\RequestOptions as GuzzleHttpClientOptions; 
use Http\Adapter\Guzzle6\Client as GuzzleHttpClient; 
use Http\Client\Common\Plugin\AuthenticationPlugin; 
use Http\Client\Common\Plugin\DecoderPlugin; 
use Http\Client\Common\Plugin\ErrorPlugin; 
use Http\Client\Common\Plugin\HeaderSetPlugin; 
use Http\Client\Common\Plugin\RetryPlugin; 
use Http\Client\Common\PluginClient; 
use Http\Client\Curl\Client as CurlHttpClient; 
use Http\Client\HttpAsyncClient as HttpAsyncClientInterface; 
use Http\Discovery\HttpAsyncClientDiscovery; 
use Http\Message\ResponseFactory as ResponseFactoryInterface; 
use Http\Message\StreamFactory as StreamFactoryInterface; 
use Http\Message\UriFactory as UriFactoryInterface; 
use Sentry\HttpClient\Authentication\SentryAuthentication; 
use Sentry\HttpClient\Plugin\GzipEncoderPlugin; 
use Sentry\Options; 
use Symfony\Component\HttpClient\HttpClient as SymfonyHttpClient; 
use Symfony\Component\HttpClient\HttplugClient as SymfonyHttplugClient; 
 
/** 
 * Default implementation of the {@HttpClientFactoryInterface} interface that uses 
 * Httplug to autodiscover the HTTP client if none is passed by the user. 
 */ 
final class HttpClientFactory implements HttpClientFactoryInterface 
{ 
    /** 
     * @var int The timeout of the request in seconds 
     */ 
    private const DEFAULT_HTTP_TIMEOUT = 5; 
 
    /** 
     * @var int The default number of seconds to wait while trying to connect 
     *          to a server 
     */ 
    private const DEFAULT_HTTP_CONNECT_TIMEOUT = 2; 
 
    /** 
     * @var UriFactoryInterface The PSR-7 URI factory 
     */ 
    private $uriFactory; 
 
    /** 
     * @var ResponseFactoryInterface The PSR-7 response factory 
     */ 
    private $responseFactory; 
 
    /** 
     * @var StreamFactoryInterface The PSR-17 stream factory 
     */ 
    private $streamFactory; 
 
    /** 
     * @var HttpAsyncClientInterface|null The HTTP client 
     */ 
    private $httpClient; 
 
    /** 
     * @var string The name of the SDK 
     */ 
    private $sdkIdentifier; 
 
    /** 
     * @var string The version of the SDK 
     */ 
    private $sdkVersion; 
 
    /** 
     * Constructor. 
     * 
     * @param UriFactoryInterface           $uriFactory      The PSR-7 URI factory 
     * @param ResponseFactoryInterface      $responseFactory The PSR-7 response factory 
     * @param StreamFactoryInterface        $streamFactory   The PSR-17 stream factory 
     * @param HttpAsyncClientInterface|null $httpClient      The HTTP client 
     * @param string                        $sdkIdentifier   The SDK identifier 
     * @param string                        $sdkVersion      The SDK version 
     */ 
    public function __construct( 
        UriFactoryInterface $uriFactory, 
        ResponseFactoryInterface $responseFactory, 
        StreamFactoryInterface $streamFactory, 
        ?HttpAsyncClientInterface $httpClient, 
        string $sdkIdentifier, 
        string $sdkVersion 
    ) { 
        $this->uriFactory = $uriFactory; 
        $this->responseFactory = $responseFactory; 
        $this->streamFactory = $streamFactory; 
        $this->httpClient = $httpClient; 
        $this->sdkIdentifier = $sdkIdentifier; 
        $this->sdkVersion = $sdkVersion; 
    } 
 
    /** 
     * {@inheritdoc} 
     */ 
    public function create(Options $options): HttpAsyncClientInterface 
    { 
        if (null === $options->getDsn(false)) { 
            throw new \RuntimeException('Cannot create an HTTP client without the Sentry DSN set in the options.'); 
        } 
 
        $httpClient = $this->httpClient; 
 
        if (null !== $httpClient && null !== $options->getHttpProxy()) { 
            throw new \RuntimeException('The "http_proxy" option does not work together with a custom HTTP client.'); 
        } 
 
        if (null === $httpClient) { 
            if (class_exists(SymfonyHttplugClient::class)) { 
                $symfonyConfig = [ 
                    'max_duration' => self::DEFAULT_HTTP_TIMEOUT, 
                ]; 
 
                if (null !== $options->getHttpProxy()) { 
                    $symfonyConfig['proxy'] = $options->getHttpProxy(); 
                } 
 
                /** @psalm-suppress UndefinedClass */ 
                $httpClient = new SymfonyHttplugClient( 
                    SymfonyHttpClient::create($symfonyConfig) 
                ); 
            } elseif (class_exists(GuzzleHttpClient::class)) { 
                /** @psalm-suppress UndefinedClass */ 
                $guzzleConfig = [ 
                    GuzzleHttpClientOptions::TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, 
                    GuzzleHttpClientOptions::CONNECT_TIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, 
                ]; 
 
                if (null !== $options->getHttpProxy()) { 
                    /** @psalm-suppress UndefinedClass */ 
                    $guzzleConfig[GuzzleHttpClientOptions::PROXY] = $options->getHttpProxy(); 
                } 
 
                /** @psalm-suppress InvalidPropertyAssignmentValue */ 
                $httpClient = GuzzleHttpClient::createWithConfig($guzzleConfig); 
            } elseif (class_exists(CurlHttpClient::class)) { 
                $curlConfig = [ 
                    \CURLOPT_TIMEOUT => self::DEFAULT_HTTP_TIMEOUT, 
                    \CURLOPT_CONNECTTIMEOUT => self::DEFAULT_HTTP_CONNECT_TIMEOUT, 
                ]; 
 
                if (null !== $options->getHttpProxy()) { 
                    $curlConfig[\CURLOPT_PROXY] = $options->getHttpProxy(); 
                } 
 
                /** @psalm-suppress InvalidPropertyAssignmentValue */ 
                $httpClient = new CurlHttpClient(null, null, $curlConfig); 
            } elseif (null !== $options->getHttpProxy()) { 
                throw new \RuntimeException('The "http_proxy" option requires either the "php-http/curl-client" or the "php-http/guzzle6-adapter" package to be installed.'); 
            } 
        } 
 
        if (null === $httpClient) { 
            $httpClient = HttpAsyncClientDiscovery::find(); 
        } 
 
        $httpClientPlugins = [ 
            new HeaderSetPlugin(['User-Agent' => $this->sdkIdentifier . '/' . $this->sdkVersion]), 
            new AuthenticationPlugin(new SentryAuthentication($options, $this->sdkIdentifier, $this->sdkVersion)), 
            new RetryPlugin(['retries' => $options->getSendAttempts()]), 
            new ErrorPlugin(), 
        ]; 
 
        if ($options->isCompressionEnabled()) { 
            $httpClientPlugins[] = new GzipEncoderPlugin($this->streamFactory); 
            $httpClientPlugins[] = new DecoderPlugin(); 
        } 
 
        return new PluginClient($httpClient, $httpClientPlugins); 
    } 
}