From 0023a712604decb5ce3de9fe17128c8e2b6f55cc Mon Sep 17 00:00:00 2001 From: Thomas Talbot Date: Fri, 22 Feb 2019 16:10:59 +0100 Subject: [PATCH 1/2] [FrameworkBundle] Add integration of http-client component --- .../DependencyInjection/Configuration.php | 122 ++++++++++++++++++ .../FrameworkExtension.php | 52 ++++++++ .../Resources/config/http_client.xml | 20 +++ .../Resources/config/schema/symfony-1.0.xsd | 61 +++++++++ .../DependencyInjection/ConfigurationTest.php | 5 + .../php/http_client_default_options.php | 13 ++ .../php/http_client_full_default_options.php | 31 +++++ .../http_client_override_default_options.php | 16 +++ .../xml/http_client_default_options.xml | 17 +++ .../xml/http_client_full_default_options.xml | 45 +++++++ .../http_client_override_default_options.xml | 24 ++++ .../yml/http_client_default_options.yml | 7 + .../yml/http_client_full_default_options.yml | 26 ++++ .../http_client_override_default_options.yml | 8 ++ .../FrameworkExtensionTest.php | 59 +++++++++ .../Bundle/FrameworkBundle/composer.json | 1 + .../Component/HttpClient/CurlHttpClient.php | 2 +- .../Component/HttpClient/HttpClientTrait.php | 4 +- .../Component/HttpClient/NativeHttpClient.php | 2 +- 19 files changed, 512 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_default_options.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_default_options.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_default_options.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 2788cf019f5be..1d882101d5046 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -17,6 +17,7 @@ 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; @@ -109,6 +110,7 @@ public function getConfigTreeBuilder() $this->addLockSection($rootNode); $this->addMessengerSection($rootNode); $this->addRobotsIndexSection($rootNode); + $this->addHttpClientSection($rootNode); return $treeBuilder; } @@ -1170,4 +1172,124 @@ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) ->end() ; } + + private function addHttpClientSection(ArrayNodeDefinition $rootNode) + { + $subNode = $rootNode + ->children() + ->arrayNode('http_client') + ->info('HTTP Client configuration') + ->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') + ->info('An HTTP Basic authentication "username:password".') + ->end() + ->arrayNode('query') + ->info('Associative array of query string values merged with URL parameters.') + ->useAttributeAsKey('key') + ->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() + ->booleanNode('buffer') + ->info('Indicates if the response should be buffered or not.') + ->end() + ->arrayNode('resolve') + ->info('Associative array: domain => IP.') + ->useAttributeAsKey('host') + ->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).') + ->useAttributeAsKey('algo') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 391408a348f4c..99970c3d5433a 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,48 @@ 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 { + [, $options] = $this->prepareRequest(null, null, $options, $defaultOptions); + + return $options; + } 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]); + + 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->register('psr18.'.$name, Psr18Client::class) + ->setAutowired(true) + ->setArguments([new Reference($name)]); + + $container->registerAliasForArgument($name, HttpClientInterface::class); + $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..add9b19771e03 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,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 589ddf50a63fe..3aee994484696 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -331,6 +331,11 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'buses' => ['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]], ], 'disallow_search_engine_index' => true, + 'http_client' => [ + 'enabled' => false, + 'max_host_connections' => 6, + '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..2fd7822c4ef35 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_full_default_options.php @@ -0,0 +1,31 @@ +loadFromExtension('framework', [ + 'http_client' => [ + 'default_options' => [ + 'auth' => 'foo:bar', + 'query' => ['foo' => 'bar', 'bar' => 'baz'], + 'headers' => ['X-powered' => 'PHP'], + 'max_redirects' => 2, + 'http_version' => '2.0', + 'base_uri' => 'http://example.com', + 'buffer' => true, + '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..92d35bf9d0d7f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_default_options.xml @@ -0,0 +1,17 @@ + + + + + + 4 + + + + + + + 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..e1353fb6c021f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_full_default_options.xml @@ -0,0 +1,45 @@ + + + + + + + foo:bar + + bar + baz + + + PHP + + 2 + 2.0 + http://example.com + true + + 127.0.0.1 + + proxy.org + 3.5 + 127.0.0.1 + true + true + /etc/ssl/cafile + /etc/ssl + /etc/ssl/cert.pem + /etc/ssl/private_key.pem + password123456 + RC4-SHA:TLS13-AES-128-GCM-SHA256 + + 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..af8760a7fd038 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml @@ -0,0 +1,24 @@ + + + + + + + + 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..946472b894d87 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_full_default_options.yml @@ -0,0 +1,26 @@ +framework: + http_client: + default_options: + auth: foo:bar + query: {'foo': 'bar', 'bar': 'baz'} + headers: + X-powered: PHP + max_redirects: 2 + http_version: 2.0 + base_uri: 'http://example.com' + buffer: true + 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..a851d8930477b 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,64 @@ 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' => [], + 'peer_fingerprint' => [], + 'http_version' => '', + ]; + $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']); + $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->assertTrue($defaultOptions['buffer']); + $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' => [], From f2d2cf3021f9a86b8603d3c6696fd428feea606f Mon Sep 17 00:00:00 2001 From: nicoweb Date: Wed, 13 Mar 2019 18:23:40 +0100 Subject: [PATCH 2/2] work with attributes for xml config --- .../DependencyInjection/Configuration.php | 42 ++++++++-- .../FrameworkExtension.php | 29 +++++-- .../Resources/config/schema/symfony-1.0.xsd | 80 ++++++++++--------- .../DependencyInjection/ConfigurationTest.php | 4 +- .../php/http_client_full_default_options.php | 3 +- .../xml/http_client_default_options.xml | 9 +-- .../xml/http_client_full_default_options.xml | 58 ++++++-------- .../http_client_override_default_options.xml | 20 ++--- .../yml/http_client_full_default_options.yml | 3 +- .../FrameworkExtensionTest.php | 5 +- 10 files changed, 141 insertions(+), 112 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 1d882101d5046..1e52ce9322486 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -22,6 +22,7 @@ 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; @@ -1179,7 +1180,7 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode) ->children() ->arrayNode('http_client') ->info('HTTP Client configuration') - ->canBeEnabled() + ->{!class_exists(FullStack::class) && class_exists(HttpClient::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->fixXmlConfig('client') ->children(); @@ -1213,12 +1214,27 @@ private function addHttpClientOptionsSection(NodeBuilder $rootNode) ->arrayNode('default_options') ->fixXmlConfig('header') ->children() - ->scalarNode('auth') + ->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() @@ -1237,12 +1253,21 @@ private function addHttpClientOptionsSection(NodeBuilder $rootNode) ->scalarNode('base_uri') ->info('The URI to resolve relative URLs, following rules in RFC 3986, section 2.') ->end() - ->booleanNode('buffer') - ->info('Indicates if the response should be buffered or not.') - ->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() @@ -1284,9 +1309,12 @@ private function addHttpClientOptionsSection(NodeBuilder $rootNode) ->end() ->arrayNode('peer_fingerprint') ->info('Associative array: hashing algorithm => hash(es).') - ->useAttributeAsKey('algo') ->normalizeKeys(false) - ->variablePrototype()->end() + ->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 99970c3d5433a..673c89f9fe16a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1771,9 +1771,16 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder public function merge(array $options, array $defaultOptions) { try { - [, $options] = $this->prepareRequest(null, null, $options, $defaultOptions); + [, $mergedOptions] = $this->prepareRequest(null, null, $options, $defaultOptions); - return $options; + 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); } @@ -1783,6 +1790,11 @@ public function merge(array $options, array $defaultOptions) $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); @@ -1790,12 +1802,15 @@ public function merge(array $options, array $defaultOptions) ->setFactory([HttpClient::class, 'create']) ->setArguments([$options, $clientConfig['max_host_connections'] ?? $config['max_host_connections'] ?? 6]); - $container->register('psr18.'.$name, Psr18Client::class) - ->setAutowired(true) - ->setArguments([new Reference($name)]); - $container->registerAliasForArgument($name, HttpClientInterface::class); - $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name); + + if ($hasPsr18) { + $container->register('psr18.'.$name, Psr18Client::class) + ->setAutowired(true) + ->setArguments([new Reference($name)]); + + $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name); + } } } 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 add9b19771e03..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,7 +32,7 @@ - + @@ -448,61 +448,63 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + - - - - + + - - - - + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 3aee994484696..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; @@ -332,8 +333,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor ], 'disallow_search_engine_index' => true, 'http_client' => [ - 'enabled' => false, - 'max_host_connections' => 6, + 'enabled' => !class_exists(FullStack::class) && class_exists(HttpClient::class), 'clients' => [], ], ]; 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 index 2fd7822c4ef35..59e7f85d03c23 100644 --- 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 @@ -3,13 +3,12 @@ $container->loadFromExtension('framework', [ 'http_client' => [ 'default_options' => [ - 'auth' => 'foo:bar', + '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', - 'buffer' => true, 'resolve' => ['localhost' => '127.0.0.1'], 'proxy' => 'proxy.org', 'timeout' => 3.5, 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 index 92d35bf9d0d7f..5a16c54914c3a 100644 --- 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 @@ -6,12 +6,11 @@ http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - - 4 - + + - + - + 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 index e1353fb6c021f..6f889ba6e8715 100644 --- 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 @@ -6,40 +6,34 @@ http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - - - foo:bar - - bar - baz - - - PHP - - 2 - 2.0 - http://example.com - true - - 127.0.0.1 - - proxy.org - 3.5 - 127.0.0.1 - true - true - /etc/ssl/cafile - /etc/ssl - /etc/ssl/cert.pem - /etc/ssl/private_key.pem - password123456 - RC4-SHA:TLS13-AES-128-GCM-SHA256 - + + + 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 index af8760a7fd038..33c201ef9f6e1 100644 --- 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 @@ -6,19 +6,15 @@ http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - - - - bar - - + + + bar + - - - baz - - + + baz + - + 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 index 946472b894d87..3d18286820e05 100644 --- 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 @@ -1,14 +1,13 @@ framework: http_client: default_options: - auth: foo:bar + 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' - buffer: true resolve: {'localhost': '127.0.0.1'} proxy: proxy.org timeout: 3.5 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index a851d8930477b..6a98f7c1841ee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1363,8 +1363,6 @@ public function testHttpClientDefaultOptions() 'query' => [], 'headers' => [], 'resolve' => [], - 'peer_fingerprint' => [], - 'http_version' => '', ]; $this->assertSame([$defaultOptions, 4], $container->getDefinition('http_client')->getArguments()); @@ -1387,13 +1385,12 @@ public function testHttpClientFullDefaultOptions() $defaultOptions = $container->getDefinition('http_client')->getArguments()[0]; - $this->assertSame('foo:bar', $defaultOptions['auth']); + $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->assertTrue($defaultOptions['buffer']); $this->assertSame(['localhost' => '127.0.0.1'], $defaultOptions['resolve']); $this->assertSame('proxy.org', $defaultOptions['proxy']); $this->assertSame(3.5, $defaultOptions['timeout']); 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