diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 2788cf019f5be..1e52ce9322486 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -17,10 +17,12 @@ use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\Form\Form; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; @@ -109,6 +111,7 @@ public function getConfigTreeBuilder() $this->addLockSection($rootNode); $this->addMessengerSection($rootNode); $this->addRobotsIndexSection($rootNode); + $this->addHttpClientSection($rootNode); return $treeBuilder; } @@ -1170,4 +1173,151 @@ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) ->end() ; } + + private function addHttpClientSection(ArrayNodeDefinition $rootNode) + { + $subNode = $rootNode + ->children() + ->arrayNode('http_client') + ->info('HTTP Client configuration') + ->{!class_exists(FullStack::class) && class_exists(HttpClient::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->fixXmlConfig('client') + ->children(); + + $this->addHttpClientOptionsSection($subNode); + + $subNode = $subNode + ->arrayNode('clients') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->arrayPrototype() + ->children(); + + $this->addHttpClientOptionsSection($subNode); + + $subNode = $subNode + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addHttpClientOptionsSection(NodeBuilder $rootNode) + { + $rootNode + ->integerNode('max_host_connections') + ->info('The maximum number of connections to a single host.') + ->end() + ->arrayNode('default_options') + ->fixXmlConfig('header') + ->children() + ->scalarNode('auth_basic') + ->info('An HTTP Basic authentication "username:password".') + ->end() + ->scalarNode('auth_bearer') + ->info('A token enabling HTTP Bearer authorization.') + ->end() + ->arrayNode('query') + ->info('Associative array of query string values merged with URL parameters.') + ->useAttributeAsKey('key') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['key'])) { + return $config; + } + + return [$config['key'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->arrayNode('headers') + ->info('Associative array: header => value(s).') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->integerNode('max_redirects') + ->info('The maximum number of redirects to follow.') + ->end() + ->scalarNode('http_version') + ->info('The default HTTP version, typically 1.1 or 2.0. Leave to null for the best version.') + ->end() + ->scalarNode('base_uri') + ->info('The URI to resolve relative URLs, following rules in RFC 3986, section 2.') + ->end() + ->arrayNode('resolve') + ->info('Associative array: domain => IP.') + ->useAttributeAsKey('host') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['host'])) { + return $config; + } + + return [$config['host'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->scalarNode('proxy') + ->info('The URL of the proxy to pass requests through or null for automatic detection.') + ->end() + ->scalarNode('no_proxy') + ->info('A comma separated list of hosts that do not require a proxy to be reached.') + ->end() + ->floatNode('timeout') + ->info('Defaults to "default_socket_timeout" ini parameter.') + ->end() + ->scalarNode('bindto') + ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') + ->end() + ->booleanNode('verify_peer') + ->info('Indicates if the peer should be verified in a SSL/TLS context.') + ->end() + ->booleanNode('verify_host') + ->info('Indicates if the host should exist as a certificate common name.') + ->end() + ->scalarNode('cafile') + ->info('A certificate authority file.') + ->end() + ->scalarNode('capath') + ->info('A directory that contains multiple certificate authority files.') + ->end() + ->scalarNode('local_cert') + ->info('A PEM formatted certificate file.') + ->end() + ->scalarNode('local_pk') + ->info('A private key file.') + ->end() + ->scalarNode('passphrase') + ->info('The passphrase used to encrypt the "local_pk" file.') + ->end() + ->scalarNode('ciphers') + ->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC4-SHA:TLS13-AES-128-GCM-SHA256"...)') + ->end() + ->arrayNode('peer_fingerprint') + ->info('Associative array: hashing algorithm => hash(es).') + ->normalizeKeys(false) + ->children() + ->variableNode('sha1')->end() + ->variableNode('pin-sha256')->end() + ->variableNode('md5')->end() + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 391408a348f4c..673c89f9fe16a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -15,6 +15,7 @@ use Doctrine\Common\Annotations\Reader; use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; +use Psr\Http\Client\ClientInterface; use Psr\Log\LoggerAwareInterface; use Symfony\Bridge\Monolog\Processor\DebugProcessor; use Symfony\Bridge\Twig\Extension\CsrfExtension; @@ -57,6 +58,9 @@ use Symfony\Component\Form\FormTypeExtensionInterface; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeInterface; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpClient\HttpClientTrait; +use Symfony\Component\HttpClient\Psr18Client; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; @@ -110,6 +114,8 @@ use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; use Symfony\Component\Yaml\Yaml; use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; @@ -301,6 +307,10 @@ public function load(array $configs, ContainerBuilder $container) $this->registerLockConfiguration($config['lock'], $container, $loader); } + if ($this->isConfigEnabled($container, $config['http_client'])) { + $this->registerHttpClientConfiguration($config['http_client'], $container, $loader); + } + if ($this->isConfigEnabled($container, $config['web_link'])) { if (!class_exists(HttpHeaderSerializer::class)) { throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".'); @@ -1747,6 +1757,63 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con } } + private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + { + if (!class_exists(HttpClient::class)) { + throw new LogicException('HttpClient support cannot be enabled as the component is not installed. Try running "composer require symfony/http-client".'); + } + + $loader->load('http_client.xml'); + + $merger = new class() { + use HttpClientTrait; + + public function merge(array $options, array $defaultOptions) + { + try { + [, $mergedOptions] = $this->prepareRequest(null, null, $options, $defaultOptions); + + foreach ($mergedOptions as $k => $v) { + if (!isset($options[$k]) && !isset($defaultOptions[$k])) { + // Remove options added by prepareRequest() + unset($mergedOptions[$k]); + } + } + + return $mergedOptions; + } catch (TransportExceptionInterface $e) { + throw new InvalidArgumentException($e->getMessage(), 0, $e); + } + } + }; + + $defaultOptions = $merger->merge($config['default_options'] ?? [], []); + $container->getDefinition('http_client')->setArguments([$defaultOptions, $config['max_host_connections'] ?? 6]); + + if (!$hasPsr18 = interface_exists(ClientInterface::class)) { + $container->removeDefinition('psr18.http_client'); + $container->removeAlias(ClientInterface::class); + } + + foreach ($config['clients'] as $name => $clientConfig) { + $options = $merger->merge($clientConfig['default_options'] ?? [], $defaultOptions); + + $container->register($name, HttpClientInterface::class) + ->setFactory([HttpClient::class, 'create']) + ->setArguments([$options, $clientConfig['max_host_connections'] ?? $config['max_host_connections'] ?? 6]); + + $container->registerAliasForArgument($name, HttpClientInterface::class); + + if ($hasPsr18) { + $container->register('psr18.'.$name, Psr18Client::class) + ->setAutowired(true) + ->setArguments([new Reference($name)]); + + $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name); + } + } + } + /** * Returns the base path for the XSD files. * diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.xml new file mode 100644 index 0000000000000..c21d115828fa5 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 4c48fe0a58819..c1242a1e08a92 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -32,6 +32,7 @@ + @@ -444,4 +445,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 589ddf50a63fe..56be70050ccf5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -17,6 +17,7 @@ use Symfony\Bundle\FullStack; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Messenger\MessageBusInterface; @@ -331,6 +332,10 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'buses' => ['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]], ], 'disallow_search_engine_index' => true, + 'http_client' => [ + 'enabled' => !class_exists(FullStack::class) && class_exists(HttpClient::class), + 'clients' => [], + ], ]; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_default_options.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_default_options.php new file mode 100644 index 0000000000000..bd36ab1f03d15 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_default_options.php @@ -0,0 +1,13 @@ +loadFromExtension('framework', [ + 'http_client' => [ + 'max_host_connections' => 4, + 'default_options' => null, + 'clients' => [ + 'foo' => [ + 'default_options' => null, + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php new file mode 100644 index 0000000000000..59e7f85d03c23 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php @@ -0,0 +1,30 @@ +loadFromExtension('framework', [ + 'http_client' => [ + 'default_options' => [ + 'auth_basic' => 'foo:bar', + 'query' => ['foo' => 'bar', 'bar' => 'baz'], + 'headers' => ['X-powered' => 'PHP'], + 'max_redirects' => 2, + 'http_version' => '2.0', + 'base_uri' => 'http://example.com', + 'resolve' => ['localhost' => '127.0.0.1'], + 'proxy' => 'proxy.org', + 'timeout' => 3.5, + 'bindto' => '127.0.0.1', + 'verify_peer' => true, + 'verify_host' => true, + 'cafile' => '/etc/ssl/cafile', + 'capath' => '/etc/ssl', + 'local_cert' => '/etc/ssl/cert.pem', + 'local_pk' => '/etc/ssl/private_key.pem', + 'passphrase' => 'password123456', + 'ciphers' => 'RC4-SHA:TLS13-AES-128-GCM-SHA256', + 'peer_fingerprint' => [ + 'pin-sha256' => ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='], + 'md5' => 'sdhtb481248721thbr=', + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php new file mode 100644 index 0000000000000..5482f2903e6c9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php @@ -0,0 +1,16 @@ +loadFromExtension('framework', [ + 'http_client' => [ + 'default_options' => [ + 'headers' => ['foo' => 'bar'], + ], + 'clients' => [ + 'foo' => [ + 'default_options' => [ + 'headers' => ['bar' => 'baz'], + ], + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_default_options.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_default_options.xml new file mode 100644 index 0000000000000..5a16c54914c3a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_default_options.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml new file mode 100644 index 0000000000000..6f889ba6e8715 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml @@ -0,0 +1,39 @@ + + + + + + + bar + baz + PHP + 127.0.0.1 + + 14s5erg62v1v8471g2revg48r7== + jsda84hjtyd4821bgfesd215bsfg5412= + sdhtb481248721thbr= + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml new file mode 100644 index 0000000000000..33c201ef9f6e1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml @@ -0,0 +1,20 @@ + + + + + + + bar + + + + baz + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_default_options.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_default_options.yml new file mode 100644 index 0000000000000..4abf1b897380d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_default_options.yml @@ -0,0 +1,7 @@ +framework: + http_client: + max_host_connections: 4 + default_options: ~ + clients: + foo: + default_options: ~ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml new file mode 100644 index 0000000000000..3d18286820e05 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml @@ -0,0 +1,25 @@ +framework: + http_client: + default_options: + auth_basic: foo:bar + query: {'foo': 'bar', 'bar': 'baz'} + headers: + X-powered: PHP + max_redirects: 2 + http_version: 2.0 + base_uri: 'http://example.com' + resolve: {'localhost': '127.0.0.1'} + proxy: proxy.org + timeout: 3.5 + bindto: 127.0.0.1 + verify_peer: true + verify_host: true + cafile: /etc/ssl/cafile + capath: /etc/ssl + local_cert: /etc/ssl/cert.pem + local_pk: /etc/ssl/private_key.pem + passphrase: password123456 + ciphers: 'RC4-SHA:TLS13-AES-128-GCM-SHA256' + peer_fingerprint: + pin-sha256: ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='] + md5: 'sdhtb481248721thbr=' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml new file mode 100644 index 0000000000000..37516441720a3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml @@ -0,0 +1,8 @@ +framework: + http_client: + default_options: + headers: {'foo': 'bar'} + clients: + foo: + default_options: + headers: {'bar': 'baz'} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 1bbd048319b5a..6a98f7c1841ee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -51,6 +51,7 @@ use Symfony\Component\Translation\DependencyInjection\TranslatorPass; use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Workflow; +use Symfony\Contracts\HttpClient\HttpClientInterface; abstract class FrameworkExtensionTest extends TestCase { @@ -1353,6 +1354,61 @@ public function testRobotsTagListenerIsRegisteredInDebugMode() $this->assertFalse($container->has('disallow_search_engine_index_response_listener'), 'DisallowRobotsIndexingListener should NOT be registered'); } + public function testHttpClientDefaultOptions() + { + $container = $this->createContainerFromFile('http_client_default_options'); + $this->assertTrue($container->hasDefinition('http_client'), '->registerHttpClientConfiguration() loads http_client.xml'); + + $defaultOptions = [ + 'query' => [], + 'headers' => [], + 'resolve' => [], + ]; + $this->assertSame([$defaultOptions, 4], $container->getDefinition('http_client')->getArguments()); + + $this->assertTrue($container->hasDefinition('foo'), 'should have the "foo" service.'); + $this->assertSame(HttpClientInterface::class, $container->getDefinition('foo')->getClass()); + $this->assertSame([$defaultOptions, 4], $container->getDefinition('foo')->getArguments()); + } + + public function testHttpClientOverrideDefaultOptions() + { + $container = $this->createContainerFromFile('http_client_override_default_options'); + + $this->assertSame(['foo' => ['bar']], $container->getDefinition('http_client')->getArguments()[0]['headers']); + $this->assertSame(['bar' => ['baz'], 'foo' => ['bar']], $container->getDefinition('foo')->getArguments()[0]['headers']); + } + + public function testHttpClientFullDefaultOptions() + { + $container = $this->createContainerFromFile('http_client_full_default_options'); + + $defaultOptions = $container->getDefinition('http_client')->getArguments()[0]; + + $this->assertSame('foo:bar', $defaultOptions['auth_basic']); + $this->assertSame(['foo' => 'bar', 'bar' => 'baz'], $defaultOptions['query']); + $this->assertSame(['x-powered' => ['PHP']], $defaultOptions['headers']); + $this->assertSame(2, $defaultOptions['max_redirects']); + $this->assertSame(2.0, (float) $defaultOptions['http_version']); + $this->assertSame('http://example.com', $defaultOptions['base_uri']); + $this->assertSame(['localhost' => '127.0.0.1'], $defaultOptions['resolve']); + $this->assertSame('proxy.org', $defaultOptions['proxy']); + $this->assertSame(3.5, $defaultOptions['timeout']); + $this->assertSame('127.0.0.1', $defaultOptions['bindto']); + $this->assertTrue($defaultOptions['verify_peer']); + $this->assertTrue($defaultOptions['verify_host']); + $this->assertSame('/etc/ssl/cafile', $defaultOptions['cafile']); + $this->assertSame('/etc/ssl', $defaultOptions['capath']); + $this->assertSame('/etc/ssl/cert.pem', $defaultOptions['local_cert']); + $this->assertSame('/etc/ssl/private_key.pem', $defaultOptions['local_pk']); + $this->assertSame('password123456', $defaultOptions['passphrase']); + $this->assertSame('RC4-SHA:TLS13-AES-128-GCM-SHA256', $defaultOptions['ciphers']); + $this->assertSame([ + 'pin-sha256' => ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='], + 'md5' => 'sdhtb481248721thbr=', + ], $defaultOptions['peer_fingerprint']); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new ParameterBag(array_merge([ diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index cdf40d12a21f1..06d16c65d46fd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -42,6 +42,7 @@ "symfony/security": "~3.4|~4.0", "symfony/form": "^4.3", "symfony/expression-language": "~3.4|~4.0", + "symfony/http-client": "^4.3", "symfony/messenger": "^4.2", "symfony/mime": "^4.3", "symfony/process": "~3.4|~4.0", diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index f5b81485292ee..5cbb839ead3e4 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -53,7 +53,7 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections if (\defined('CURLPIPE_MULTIPLEX')) { curl_multi_setopt($mh, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); } - curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, $maxHostConnections); + curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX); // Use an internal stdClass object to share state between the client and its responses $this->multi = $multi = (object) [ diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 223eba3e010fc..cbc08f40e2788 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -141,7 +141,9 @@ private static function mergeDefaultOptions(array $options, array $defaultOption // Option "query" is never inherited from defaults $options['query'] = $options['query'] ?? []; - $options += $defaultOptions; + foreach ($defaultOptions as $k => $v) { + $options[$k] = $options[$k] ?? $v; + } if ($defaultOptions['resolve'] ?? false) { $options['resolve'] += array_change_key_case($defaultOptions['resolve']); diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index afd8fbd0897b2..bea3fe755b068 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -52,7 +52,7 @@ public function __construct(array $defaultOptions = [], int $maxHostConnections 'openHandles' => [], 'handlesActivity' => [], 'pendingResponses' => [], - 'maxHostConnections' => $maxHostConnections, + 'maxHostConnections' => 0 < $maxHostConnections ? $maxHostConnections : PHP_INT_MAX, 'responseCount' => 0, 'dnsCache' => [], 'handles' => [], pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy