8000 [FrameworkBundle] Add integration of http-client component · symfony/symfony@0023a71 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 0023a71

Browse files
Thomas Talbotnicolas-grekas
authored andcommitted
[FrameworkBundle] Add integration of http-client component
1 parent 3abf9eb commit 0023a71

19 files changed

+512
-3
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Bundle\FullStack;
1818
use Symfony\Component\Asset\Package;
1919
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
20+
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
2021
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
2122
use Symfony\Component\Config\Definition\ConfigurationInterface;
2223
use Symfony\Component\DependencyInjection\Exception\LogicException;
@@ -109,6 +110,7 @@ public function getConfigTreeBuilder()
109110
$this->addLockSection($rootNode);
110111
$this->addMessengerSection($rootNode);
111112
$this->addRobotsIndexSection($rootNode);
113+
$this->addHttpClientSection($rootNode);
112114

113115
return $treeBuilder;
114116
}
@@ -1170,4 +1172,124 @@ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode)
11701172
->end()
11711173
;
11721174
}
1175+
1176+
private function addHttpClientSection(ArrayNodeDefinition $rootNode)
1177+
{
1178+
$subNode = $rootNode
1179+
->children()
1180+
->arrayNode('http_client')
1181+
->info('HTTP Client configuration')
1182+
->canBeEnabled()
1183+
->fixXmlConfig('client')
1184+
->children();
1185+
1186+
$this->addHttpClientOptionsSection($subNode);
1187+
1188+
$subNode = $subNode
1189+
->arrayNode('clients')
1190+
->useAttributeAsKey('name')
1191+
->normalizeKeys(false)
1192+
->arrayPrototype()
1193+
->children();
1194+
1195+
$this->addHttpClientOptionsSection($subNode);
1196+
1197+
$subNode = $subNode
1198+
->end()
1199+
->end()
1200+
->end()
1201+
->end()
1202+
->end()
1203+
->end()
1204+
;
1205+
}
1206+
1207+
private function addHttpClientOptionsSection(NodeBuilder $rootNode)
1208+
{
1209+
$rootNode
1210+
->integerNode('max_host_connections')
1211+
->info('The maximum number of connections to a single host.')
1212+
->end()
1213+
->arrayNode('default_options')
1214+
->fixXmlConfig('header')
1215+
->children()
1216+
->scalarNode('auth')
1217+
->info('An HTTP Basic authentication "username:password".')
1218+
->end()
1219+
->arrayNode('query')
1220+
->info('Associative array of query string values merged with URL parameters.')
1221+
->useAttributeAsKey('key')
1222+
->normalizeKeys(false)
1223+
->scalarPrototype()->end()
1224+
->end()
1225+
->arrayNode('headers')
1226+
->info('Associative array: header => value(s).')
1227+
->useAttributeAsKey('name')
1228+
->normalizeKeys(false)
1229+
->variablePrototype()->end()
1230+
->end()
1231+
->integerNode('max_redirects')
1232+
->info('The maximum number of redirects to follow.')
1233+
->end()
1234+
->scalarNode('http_version')
1235+
->info('The default HTTP version, typically 1.1 or 2.0. Leave to null for the best version.')
1236+
->end()
1237+
->scalarNode('base_uri')
1238+
->info('The URI to resolve relative URLs, following rules in RFC 3986, section 2.')
1239+
->end()
1240+
->booleanNode('buffer')
1241+
->info('Indicates if the response should be buffered or not.')
1242+
->end()
1243+
->arrayNode('resolve')
1244+
->info('Associative array: domain => IP.')
1245+
->useAttributeAsKey('host')
1246+
->normalizeKeys(false)
1247+
->scalarPrototype()->end()
1248+
->end()
1249+
->scalarNode('proxy')
1C6A
1250+
->info('The URL of the proxy to pass requests through or null for automatic detection.')
1251+
->end()
1252+
->scalarNode('no_proxy')
1253+
->info('A comma separated list of hosts that do not require a proxy to be reached.')
1254+
->end()
1255+
->floatNode('timeout')
1256+
->info('Defaults to "default_socket_timeout" ini parameter.')
1257+
->end()
1258+
->scalarNode('bindto')
1259+
->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')
1260+
->end()
1261+
->booleanNode('verify_peer')
1262+
->info('Indicates if the peer should be verified in a SSL/TLS context.')
1263+
->end()
1264+
->booleanNode('verify_host')
1265+
->info('Indicates if the host should exist as a certificate common name.')
1266+
->end()
1267+
->scalarNode('cafile')
1268+
->info('A certificate authority file.')
1269+
->end()
1270+
->scalarNode('capath')
1271+
->info('A directory that contains multiple certificate authority files.')
1272+
->end()
1273+
->scalarNode('local_cert')
1274+
->info('A PEM formatted certificate file.')
1275+
->end()
1276+
->scalarNode('local_pk')
1277+
->info('A private key file.')
1278+
->end()
1279+
->scalarNode('passphrase')
1280+
->info('The passphrase used to encrypt the "local_pk" file.')
1281+
->end()
1282+
->scalarNode('ciphers')
1283+
->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC4-SHA:TLS13-AES-128-GCM-SHA256"...)')
1284+
->end()
1285+
->arrayNode('peer_fingerprint')
1286+
->info('Associative array: hashing algorithm => hash(es).')
1287+
->useAttributeAsKey('algo')
1288+
->normalizeKeys(false)
1289+
->variablePrototype()->end()
1290+
->end()
1291+
->end()
1292+
->end()
1293+
;
1294+
}
11731295
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Doctrine\Common\Annotations\Reader;
1616
use Psr\Cache\CacheItemPoolInterface;
1717
use Psr\Container\ContainerInterface as PsrContainerInterface;
18+
use Psr\Http\Client\ClientInterface;
1819
use Psr\Log\LoggerAwareInterface;
1920
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
2021
use Symfony\Bridge\Twig\Extension\CsrfExtension;
@@ -57,6 +58,9 @@
5758
use Symfony\Component\Form\FormTypeExtensionInterface;
5859
use Symfony\Component\Form\FormTypeGuesserInterface;
5960
use Symfony\Component\Form\FormTypeInterface;
61+
use Symfony\Component\HttpClient\HttpClient;
62+
use Symfony\Component\HttpClient\HttpClientTrait;
63+
use Symfony\Component\HttpClient\Psr18Client;
6064
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
6165
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
6266
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
@@ -110,6 +114,8 @@
110114
use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
111115
use Symfony\Component\Yaml\Yaml;
112116
use Symfony\Contracts\Cache\CacheInterface;
117+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
118+
use Symfony\Contracts\HttpClient\HttpClientInterface;
113119
use Symfony\Contracts\Service\ResetInterface;
114120
use Symfony\Contracts\Service\ServiceSubscriberInterface;
115121

@@ -301,6 +307,10 @@ public function load(array $configs, ContainerBuilder $container)
301307
$this->registerLockConfiguration($config['lock'], $container, $loader);
302308
}
303309

310+
if ($this->isConfigEnabled($container, $config['http_client'])) {
311+
$this->registerHttpClientConfiguration($config['http_client'], $container, $loader);
312+
}
313+
304314
if ($this->isConfigEnabled($container, $config['web_link'])) {
305315
if (!class_exists(HttpHeaderSerializer::class)) {
306316
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
17471757
}
17481758
}
17491759

1760+
private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
1761+
{
1762+
if (!class_exists(HttpClient::class)) {
1763+
throw new LogicException('HttpClient support cannot be enabled as the component is not installed. Try running "composer require symfony/http-client".');
1764+
}
1765+
1766+
$loader->load('http_client.xml');
1767+
1768+
$merger = new class() {
1769+
use HttpClientTrait;
1770+
1771+
public function merge(array $options, array $defaultOptions)
1772+
{
1773+
try {
1774+
[, $options] = $this->prepareRequest(null, null, $options, $defaultOptions);
1775+
1776+
return $options;
1777+
} catch (TransportExceptionInterface $e) {
1778+
throw new InvalidArgumentException($e->getMessage(), 0, $e);
1779+
}
1780+
}
1781+
};
1782+
1783+
$defaultOptions = $merger->merge($config['default_options'] ?? [], []);
1784+
$container->getDefinition('http_client')->setArguments([$defaultOptions, $config['max_host_connections'] ?? 6]);
1785+
1786+
foreach ($config['clients'] as $name => $clientConfig) {
1787+
$options = $merger->merge($clientConfig['default_options'] ?? [], $defaultOptions);
1788+
1789+
$container->register($name, HttpClientInterface::class)
1790+
->setFactory([HttpClient::class, 'create'])
1791+
->setArguments([$options, $clientConfig['max_host_connections'] ?? $config['max_host_connections'] ?? 6]);
1792+
1793+
$container->register('psr18.'.$name, Psr18Client::class)
1794+
->setAutowired(true)
1795+
->setArguments([new Reference($name)]);
1796+
1797+
$container->registerAliasForArgument($name, HttpClientInterface::class);
1798+
$container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name);
1799+
}
1800+
}
1801+
17501802
/**
17511803
* Returns the base path for the XSD files.
17521804
*
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="http_client" class="Symfony\Contracts\HttpClient\HttpClientInterface">
9+
<factory class="Symfony\Component\HttpClient\HttpClient" method="create" />
10+
<argument type="collection" /> <!-- default options -->
11+
<argument /> <!-- max host connections -->
12+
</service>
13+
<service id="Symfony\Contracts\HttpClient\HttpClientInterface" alias="http_client" />
14+
15+
<service id="psr18.http_client" class="Symfony\Component\HttpClient\Psr18Client" autowire="true">
16+
<argument type="service" id="http_client" />
17+
</service>
18+
<service id="Psr\Http\Client\ClientInterface" alias="psr18.http_client" />
19+
</services>
20+
</container>

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<xsd:element name="php-errors" type="php-errors" minOccurs="0" maxOccurs="1" />
3333
<xsd:element name="lock" type="lock" minOccurs="0" maxOccurs="1" />
3434
<xsd:element name="messenger" type="messenger" minOccurs="0" maxOccurs="1" />
35+
<xsd:element name="http_client" type="http_client" minOccurs="0" maxOccurs="1" />
3536
</xsd:choice>
3637

3738
<xsd:attribute name="http-method-override" type="xsd:boolean" />
@@ -444,4 +445,64 @@
444445
</xsd:sequence>
445446
<xsd:attribute name="id" type="xsd:string" use="required"/>
446447
</xsd:complexType>
448+
449+
<xsd:complexType name="http_client">
450+
<xsd:sequence>
451+
<xsd:element name="max_host_connections" type="xsd:integer" minOccurs="0" />
452+
<xsd:element name="default_options" type="http_client_options" minOccurs="0" />
453+
<xsd:element name="client" type="http_client_client" minOccurs="0" maxOccurs="unbounded" />
454+
</xsd:sequence>
455+
<xsd:attribute name="enabled" type="xsd:boolean" />
456+
</xsd:complexType>
457+
458+
<xsd:complexType name="http_client_options">
459+
<xsd:sequence>
460+
<xsd:element name="auth" type="xsd:string" minOccurs="0" />
461+
<xsd:element name="query" type="http_query" minOccurs="0" />
462+
<xsd:element name="headers" type="http_headers" minOccurs="0" />
463+
<xsd:element name="max_redirects" type="xsd:integer" minOccurs="0" />
464+
<xsd:element name="http_version" type="xsd:string" minOccurs="0" />
465+
<xsd:element name="base_uri" type="xsd:string" minOccurs="0" />
466+
<xsd:element name="buffer" type="xsd:boolean" minOccurs="0" />
467+
<xsd:element name="resolve" type="metadata" minOccurs="0" maxOccurs="unbounded" />
468+
<xsd:element name="proxy" type="xsd:string" minOccurs="0" />
469+
<xsd:element name="no_proxy" type="xsd:string" minOccurs="0" />
470+
<xsd:element name="timeout" type="xsd:float" minOccurs="0" />
471+
<xsd:element name="bindto" type="xsd:string" minOccurs="0" />
472+
<xsd:element name="verify_peer" type="xsd:boolean" minOccurs="0" />
473+
<xsd:element name="verify_host" type="xsd:boolean" minOccurs="0" />
474+
<xsd:element name="cafile" type="xsd:string" minOccurs="0" />
475+
<xsd:element name="capath" type="xsd:string" minOccurs="0" />
476+
<xsd:element name="local_cert" type="xsd:string" minOccurs="0" />
477+
<xsd:element name="local_pk" type="xsd:string" minOccurs="0" />
478+
<xsd:element name="passphrase" type="xsd:string" minOccurs="0" />
479+
<xsd:element name="ciphers" type="xsd:string" minOccurs="0" />
480+
<xsd:element name="peer_fingerprint" type="fingerprint" minOccurs="0" maxOccurs="unbounded" />
481+
</xsd:sequence>
482+
</xsd:complexType>
483+
484+
<xsd:complexType name="http_client_client">
485+
<xsd:sequence>
486+
<xsd:element name="default_options" type="http_client_options" minOccurs="0" />
487+
</xsd:sequence>
488+
<xsd:attribute name="name" type="xsd:string" />
489+
</xsd:complexType>
490+
491+
<xsd:complexType name="fingerprint">
492+
<xsd:sequence>
493+
<xsd:any minOccurs="0" processContents="lax" maxOccurs="unbounded" />
494+
</xsd:sequence>
495+
</xsd:complexType>
496+
497+
<xsd:complexType name="http_query">
498+
<xsd:sequence>
499+
<xsd:any minOccurs="0" processContents="lax" maxOccurs="unbounded" />
500+
</xsd:sequence>
501+
</xsd:complexType>
502+
503+
<xsd:complexType name="http_headers">
504+
<xsd:sequence>
505+
<xsd:any minOccurs="0" processContents="lax" maxOccurs="unbounded" />
506+
</xsd:sequence>
507+
</xsd:complexType>
447508
</xsd:schema>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,11 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
331331
'buses' => ['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]],
332332
],
333333
'disallow_search_engine_index' => true,
334+
'http_client' => [
335+
'enabled' => false,
336+
'max_host_connections' => 6,
337+
'clients' => [],
338+
],
334339
];
335340
}
336341
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'http_client' => [
5+
'max_host_connections' => 4,
6+
'default_options' => null,
7+
'clients' => [
8+
'foo' => [
9+
'default_options' => null,
10+
],
11+
],
12+
],
13+
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'http_client' => [
5+
'default_options' => [
6+
'auth' => 'foo:bar',
7+
'query' => ['foo' => 'bar', 'bar' => 'baz'],
8+
'headers' => ['X-powered' => 'PHP'],
9+
'max_redirects' => 2,
10+
'http_version' => '2.0',
11+
'base_uri' => 'http://example.com',
12+
'buffer' => true,
13+
'resolve' => ['localhost' => '127.0.0.1'],
14+
'proxy' => 'proxy.org',
15+
'timeout' => 3.5,
16+
'bindto' => '127.0.0.1',
17+
'verify_peer' => true,
18+
'verify_host' => true,
19+
'cafile' => '/etc/ssl/cafile',
20+
'capath' => '/etc/ssl',
21+
'local_cert' => '/etc/ssl/cert.pem',
22+
'local_pk' => '/etc/ssl/private_key.pem',
23+
'passphrase' => 'password123456',
24+
'ciphers' => 'RC4-SHA:TLS13-AES-128-GCM-SHA256',
25+
'peer_fingerprint' => [
26+
'pin-sha256' => ['14s5erg62v1v8471g2revg48r7==', 'jsda84hjtyd4821bgfesd215bsfg5412='],
27+
'md5' => 'sdhtb481248721thbr=',
28+
],
29+
],
30+
],
31+
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'http_client' => [
5+
'default_options' => [
6+
'headers' => ['foo' => 'bar'],
7+
],
8+
'clients' => [
9+
'foo' => [
10+
'default_options' => [
11+
'headers' => ['bar' => 'baz'],
12+
],
13+
],
14+
],
15+
],
16+
]);

0 commit comments

Comments
 (0)
0