diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 1b23702913404..b30d86169b43b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -17,12 +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\HttpClient\HttpClientTrait; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; @@ -43,6 +43,8 @@ */ class Configuration implements ConfigurationInterface { + use HttpClientTrait; + private $debug; /** @@ -1232,144 +1234,231 @@ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) private function addHttpClientSection(ArrayNodeDefinition $rootNode) { - $subNode = $rootNode + $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') + ->fixXmlConfig('scoped_client') + ->children() + ->integerNode('max_host_connections') + ->info('The maximum number of connections to a single host.') + ->end() + ->arrayNode('default_options') + ->fixXmlConfig('header') + ->children() + ->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() + ->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. "RC3-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() + ->arrayNode('scoped_clients') ->useAttributeAsKey('name') ->normalizeKeys(false) ->arrayPrototype() - ->children(); + ->fixXmlConfig('header') + ->beforeNormalization() + ->always() + ->then(function ($config) { + $config = \is_array($config) ? $config : ['base_uri' => $config]; - $this->addHttpClientOptionsSection($subNode); + if (!isset($config['scope']) && isset($config['base_uri'])) { + $config['scope'] = preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($config['base_uri'])))); + } - $subNode = $subNode + return $config; + }) ->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; - } + ->validate() + ->ifTrue(function ($v) { return !isset($v['scope']); }) + ->thenInvalid('either "scope" or "base_uri" should be defined.') + ->end() + ->validate() + ->ifTrue(function ($v) { return isset($v['query']) && !isset($v['base_uri']); }) + ->thenInvalid('"query" applies to "base_uri" but no base URI is defined.') + ->end() + ->children() + ->scalarNode('scope') + ->info('The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead.') + ->cannotBeEmpty() + ->end() + ->scalarNode('base_uri') + ->info('The URI to resolve relative URLs, following rules in RFC 3985, section 2.') + ->cannotBeEmpty() + ->end() + ->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 the base URI.') + ->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['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() + ->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() + 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. "RC3-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() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 71c02ad80e3ad..3b59605d16609 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -61,8 +61,8 @@ 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\HttpClient\ScopingHttpClient; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; @@ -117,7 +117,6 @@ 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; @@ -1803,42 +1802,23 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder $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]); + $container->getDefinition('http_client')->setArguments([$config['default_options'] ?? [], $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); + foreach ($config['scoped_clients'] as $name => $scopeConfig) { + if ('http_client' === $name) { + throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name)); + } + + $scope = $scopeConfig['scope']; + unset($scopeConfig['scope']); - $container->register($name, HttpClientInterface::class) - ->setFactory([HttpClient::class, 'create']) - ->setArguments([$options, $clientConfig['max_host_connections'] ?? $config['max_host_connections'] ?? 6]); + $container->register($name, ScopingHttpClient::class) + ->setArguments([new Reference('http_client'), [$scope => $scopeConfig], $scope]); $container->registerAliasForArgument($name, HttpClientInterface::class); 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 0415fa9559c54..38e60f6516846 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 @@ -458,30 +458,55 @@ - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + - + + + + @@ -491,14 +516,6 @@ - - - - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index bc1ee582fc081..aa0a2fc921853 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -336,7 +336,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'disallow_search_engine_index' => true, 'http_client' => [ 'enabled' => !class_exists(FullStack::class) && class_exists(HttpClient::class), - 'clients' => [], + 'scoped_clients' => [], ], 'mailer' => [ 'dsn' => 'smtp://null', 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 index bd36ab1f03d15..5f71a92847f34 100644 --- 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 @@ -4,9 +4,9 @@ 'http_client' => [ 'max_host_connections' => 4, 'default_options' => null, - 'clients' => [ + 'scoped_clients' => [ 'foo' => [ - 'default_options' => null, + 'base_uri' => 'http://example.com', ], ], ], 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 59e7f85d03c23..04a227c24cb14 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,12 +3,9 @@ $container->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, 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 index 26b76359da3fb..8ba8dd7b92ec8 100644 --- 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 @@ -6,12 +6,10 @@ 'default_options' => [ 'headers' => ['foo' => 'bar'], ], - 'clients' => [ + 'scoped_clients' => [ 'foo' => [ - 'max_host_connections' => 5, - 'default_options' => [ - 'headers' => ['bar' => 'baz'], - ], + 'base_uri' => 'http://example.com', + '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 index 5a16c54914c3a..c00eb314415b9 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 @@ -8,9 +8,10 @@ - - - + 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 6f889ba6e8715..2ea78874d2176 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 @@ -12,10 +12,8 @@ bindto="127.0.0.1" timeout="3.5" verify-peer="true" - auth-basic="foo:bar" max-redirects="2" http-version="2.0" - base-uri="http://example.com" verify-host="true" cafile="/etc/ssl/cafile" capath="/etc/ssl" @@ -24,8 +22,6 @@ passphrase="password123456" ciphers="RC4-SHA:TLS13-AES-128-GCM-SHA256" > - bar - baz PHP 127.0.0.1 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 085b4721cc7d8..8dd84123ca4b5 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 @@ -10,11 +10,9 @@ bar - - - baz - - + + 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 index 4abf1b897380d..6828f8ec231fb 100644 --- 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 @@ -2,6 +2,6 @@ framework: http_client: max_host_connections: 4 default_options: ~ - clients: + scoped_clients: foo: - default_options: ~ + base_uri: http://example.com 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 3d18286820e05..5993be1778fe6 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,13 +1,10 @@ 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 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 index 9a3d69e3585b4..1528a313d64e3 100644 --- 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 @@ -3,8 +3,7 @@ framework: max_host_connections: 4 default_options: headers: {'foo': 'bar'} - clients: + scoped_clients: foo: - max_host_connections: 5 - default_options: - headers: {'bar': 'baz'} + base_uri: http://example.com + headers: {'bar': 'baz'} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index e62651a40fced..acc7fbad156e7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -35,6 +35,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpClient\ScopingHttpClient; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage; @@ -53,7 +54,6 @@ use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Validation; use Symfony\Component\Workflow; -use Symfony\Contracts\HttpClient\HttpClientInterface; abstract class FrameworkExtensionTest extends TestCase { @@ -1406,25 +1406,34 @@ public function testHttpClientDefaultOptions() $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()); + $this->assertSame(ScopingHttpClient::class, $container->getDefinition('foo')->getClass()); } public function testHttpClientOverrideDefaultOptions() { $container = $this->createContainerFromFile('http_client_override_default_options'); - $this->assertSame(['foo' => ['bar']], $container->getDefinition('http_client')->getArgument(0)['headers']); + $this->assertSame(['foo' => 'bar'], $container->getDefinition('http_client')->getArgument(0)['headers']); $this->assertSame(4, $container->getDefinition('http_client')->getArgument(1)); - $this->assertSame(['bar' => ['baz'], 'foo' => ['bar']], $container->getDefinition('foo')->getArgument(0)['headers']); - $this->assertSame(5, $container->getDefinition('foo')->getArgument(1)); + + $expected = [ + 'http\://example\.com/' => [ + 'base_uri' => 'http://example.com', + 'headers' => [ + 'bar' => 'baz', + ], + 'query' => [], + 'resolve' => [], + ], + ]; + + $this->assertSame($expected, $container->getDefinition('foo')->getArgument(1)); } public function testHttpClientFullDefaultOptions() @@ -1433,12 +1442,9 @@ public function testHttpClientFullDefaultOptions() $defaultOptions = $container->getDefinition('http_client')->getArgument(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(['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']); diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 1e7581a5c1f53..9ca47e6624290 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -240,7 +240,7 @@ private static function readResponse(self $response, array $options, ResponseInt $info = $mock->getInfo() ?: []; $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode(false) ?: 200; $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); - $dlSize = (int) ($response->headers['content-length'][0] ?? 0); + $dlSize = isset($response->headers['content-encoding']) ? 0 : (int) ($response->headers['content-length'][0] ?? 0); $response->info = [ 'start_time' => $response->info['start_time'], @@ -282,7 +282,7 @@ private static function readResponse(self $response, array $options, ResponseInt // "notify" completion $onProgress($offset, $dlSize, $response->info); - if (isset($response->headers['content-length']) && $offset !== $dlSize) { + if ($dlSize && $offset !== $dlSize) { throw new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $dlSize - $offset)); } } diff --git a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php index 9e07221933096..cf44d0eceba8e 100644 --- a/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ b/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -122,8 +122,7 @@ public function set($key, $values, $replace = true) parent::set($key, $values, $replace); // ensure the cache-control header has sensible defaults - if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true)) { - $computed = $this->computeCacheControlValue(); + if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true) && '' !== $computed = $this->computeCacheControlValue()) { $this->headers['cache-control'] = [$computed]; $this->headerNames['cache-control'] = 'Cache-Control'; $this->computedCacheControl = $this->parseCacheControl($computed); diff --git a/src/Symfony/Component/HttpKernel/HttpClientKernel.php b/src/Symfony/Component/HttpKernel/HttpClientKernel.php index 29a6a97cefe22..2c04e670cc05f 100644 --- a/src/Symfony/Component/HttpKernel/HttpClientKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpClientKernel.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Mime\Part\AbstractPart; use Symfony\Component\Mime\Part\DataPart; use Symfony\Component\Mime\Part\Multipart\FormDataPart; @@ -60,7 +61,16 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ $this->logger->debug(sprintf('Response: %s %s', $response->getStatusCode(), $request->getUri())); - return new Response($response->getContent(!$catch), $response->getStatusCode(), $response->getHeaders(!$catch)); + $response = new Response($response->getContent(!$catch), $response->getStatusCode(), $response->getHeaders(!$catch)); + + $response->headers = new class($response->headers->all()) extends ResponseHeaderBag { + protected function computeCacheControlValue() + { + return $this->getCacheControlHeader(); // preserve the original value + } + }; + + return $response; } private function getBody(Request $request): ?AbstractPart 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