Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7a1a8a0

Browse files
author
Anthony MARTIN
committed
[WIP] [HttpClient] Add a ConditionalHttpClient
1 parent 6fdc1b4 commit 7a1a8a0

10 files changed

+382
-20
lines changed

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,9 +1193,9 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode)
11931193
->arrayPrototype()
11941194
->children();
11951195

1196-
$this->addHttpClientOptionsSection($subNode);
1196+
$this->addHttpClientOptionsSection($subNode, true);
11971197

1198-
$subNode = $subNode
1198+
$subNode
11991199
->end()
12001200
->end()
12011201
->end()
@@ -1205,13 +1205,15 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode)
12051205
;
12061206
}
12071207

1208-
private function addHttpClientOptionsSection(NodeBuilder $rootNode)
1208+
private function addHttpClientOptionsSection(NodeBuilder $rootNode, bool $isSubClient = false)
12091209
{
1210-
$rootNode
1211-
->integerNode('max_host_connections')
1210+
if (!$isSubClient) {
1211+
$rootNode = $rootNode->integerNode('max_host_connections')
12121212
->info('The maximum number of connections to a single host.')
1213-
->end()
1214-
->arrayNode('default_options')
1213+
->end();
1214+
}
1215+
1216+
$rootNode = $rootNode->arrayNode('default_options')
12151217
->fixXmlConfig('header')
12161218
->children()
12171219
->scalarNode('auth_basic')
@@ -1251,8 +1253,13 @@ private function addHttpClientOptionsSection(NodeBuilder $rootNode)
12511253
->info('The default HTTP version, typically 1.1 or 2.0. Leave to null for the best version.')
12521254
->end()
12531255
->scalarNode('base_uri')
1254-
->info('The URI to resolve relative URLs, following rules in RFC 3986, section 2.')
1255-
->end()
1256+
->info('The URI to resolve relative URLs, following rules in RFC 3986, section 2.');
1257+
1258+
if ($isSubClient) {
1259+
$rootNode = $rootNode->isRequired()->cannotBeEmpty();
1260+
}
1261+
1262+
$rootNode = $rootNode->end()
12561263
->arrayNode('resolve')
12571264
->info('Associative array: domain => IP.')
12581265
->useAttributeAsKey('host')

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@
5858
use Symfony\Component\Form\FormTypeExtensionInterface;
5959
use Symfony\Component\Form\FormTypeGuesserInterface;
6060
use Symfony\Component\Form\FormTypeInterface;
61+
use Symfony\Component\HttpClient\ConditionalHttpClient;
6162
use Symfony\Component\HttpClient\HttpClient;
6263
use Symfony\Component\HttpClient\HttpClientTrait;
6364
use Symfony\Component\HttpClient\Psr18Client;
65+
use Symfony\Component\HttpClient\ScopedHttpClient;
6466
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
6567
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
6668
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
@@ -1791,19 +1793,24 @@ public function merge(array $options, array $defaultOptions)
17911793

17921794
$defaultOptions = $merger->merge($config['default_options'] ?? [], []);
17931795
$container->getDefinition('http_client')->setArguments([$defaultOptions, $config['max_host_connections'] ?? 6]);
1796+
$httpClient = $container->get('http_client');
17941797

17951798
if (!$hasPsr18 = interface_exists(ClientInterface::class)) {
17961799
$container->removeDefinition('psr18.http_client');
17971800
$container->removeAlias(ClientInterface::class);
17981801
}
17991802

18001803
foreach ($config['clients'] as $name => $clientConfig) {
1801-
$options = $merger->merge($clientConfig['default_options'] ?? [], $defaultOptions);
1804+
$container->register('conditionnal_client_'.$name, ConditionalHttpClient::class)
1805+
->setArguments([new Reference('http_client'), $clientConfig['default_options'] ?? []]);
18021806

1803-
$container->register($name, HttpClientInterface::class)
1804-
->setFactory([HttpClient::class, 'create'])
1805-
->setArguments([$options, $clientConfig['max_host_connections'] ?? $config['max_host_connections'] ?? 6]);
1807+
$container->registerAliasForArgument('conditionnal_client_'.$name, ConditionalHttpClient::class);
1808+
$container->registerAliasForArgument('conditionnal_client_'.$name, HttpClientInterface::class);
18061809

1810+
$container->register($name, ScopedHttpClient::class)
1811+
->setArguments([new Reference('conditionnal_client_'.$name), $clientConfig['default_options'] ? $clientConfig['default_options']['base_uri'] ?? '' : '']);
1812+
1813+
$container->registerAliasForArgument($name, ScopedHttpClient::class);
18071814
$container->registerAliasForArgument($name, HttpClientInterface::class);
18081815

18091816
if ($hasPsr18) {

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_override_default_options.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
],
99
'clients' => [
1010
'foo' => [
11-
'max_host_connections' => 5,
1211
'default_options' => [
12+
'base_uri' => 'http://example.com',
1313
'headers' => ['bar' => 'baz'],
1414
],
1515
],

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_override_default_options.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
<framework:default-options>
1111
<framework:header name="foo">bar</framework:header>
1212
</framework:default-options>
13-
<framework:client name="foo" max-host-connections="5">
13+
<framework:client name="foo">
1414
<framework:default-options>
15+
<framework:base-uri>http://example.com</framework:base-uri>
1516
<framework:header name="bar">baz</framework:header>
1617
</framework:default-options>
1718
</framework:client>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_override_default_options.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ framework:
55
headers: {'foo': 'bar'}
66
clients:
77
foo:
8-
max_host_connections: 5
98
default_options:
9+
base_uri: http://example.com
1010
headers: {'bar': 'baz'}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
3636
use Symfony\Component\DependencyInjection\Reference;
3737
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
38+
use Symfony\Component\HttpClient\ScopedHttpClient;
3839
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
3940
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
4041
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
@@ -51,7 +52,6 @@
5152
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
5253
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
5354
use Symfony\Component\Workflow;
54-
use Symfony\Contracts\HttpClient\HttpClientInterface;
5555

5656
abstract class FrameworkExtensionTest extends TestCase
5757
{
@@ -1381,7 +1381,7 @@ public function testHttpClientDefaultOptions()
13811381
$this->assertSame([$defaultOptions, 4], $container->getDefinition('http_client')->getArguments());
13821382

13831383
$this->assertTrue($container->hasDefinition('foo'), 'should have the "foo" service.');
1384-
$this->assertSame(HttpClientInterface::class, $container->getDefinition('foo')->getClass());
1384+
$this->assertSame(ScopedHttpClient::class, $container->getDefinition('foo')->getClass());
13851385
$this->assertSame([$defaultOptions, 4], $container->getDefinition('foo')->getArguments());
13861386
}
13871387

@@ -1391,8 +1391,8 @@ public function testHttpClientOverrideDefaultOptions()
13911391

13921392
$this->assertSame(['foo' => ['bar']], $container->getDefinition('http_client')->getArgument(0)['headers']);
13931393
$this->assertSame(4, $container->getDefinition('http_client')->getArgument(1));
1394-
$this->assertSame(['bar' => ['baz'], 'foo' => ['bar']], $container->getDefinition('foo')->getArgument(0)['headers']);
1395-
$this->assertSame(5, $container->getDefinition('foo')->getArgument(1));
1394+
$this->assertSame(['bar' => 'baz'], $container->getDefinition($container->getDefinition('foo')->getArgument(0))->getArgument(1)['headers']);
1395+
$this->assertSame('http://example.com', $container->getDefinition('foo')->getArgument(1));
13961396
}
13971397

13981398
public function testHttpClientFullDefaultOptions()
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient;
13+
14+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
15+
use Symfony\Contracts\HttpClient\HttpClientInterface;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
18+
19+
/**
20+
* Auto-configure the default options based on the requested absolute URL.
21+
*
22+
* @author Anthony Martin <[email protected]>
23+
*
24+
* @experimental in 4.3
25+
*/
26+
class ConditionalHttpClient implements HttpClientInterface
27+
{
28+
use HttpClientTrait;
29+
30+
private $client;
31+
private $options;
32+
33+
/**
34+
* @param array[] $options the default options to use when the regexp provided as key matches the requested URL
35+
*/
36+
public function __construct(HttpClientInterface $client, array $options)
37+
{
38+
$this->client = $client;
39+
$this->options = $options;
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function request(string $method, string $url, array $options = []): ResponseInterface
46+
{
47+
$url = $this->checkAndFormatUrl($url, $options['base_uri'] ?? null);
48+
49+
foreach ($this->options as $regexp => $defaultOptions) {
50+
if (preg_match(sprintf('{%s}A', $regexp), $url)) {
51+
$options = self::mergeDefaultOptions($options, $defaultOptions, true);
52+
53+
return $this->client->request($method, $url, $options);
54+
}
55+
}
56+
57+
return $this->client->request($method, $url, $options);
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function stream($responses, float $timeout = null): ResponseStreamInterface
64+
{
65+
return $this->client->stream($responses, $timeout);
66+
}
67+
68+
private function checkAndFormatUrl(string $url, string $baseUri = null): string
69+
{
70+
// Normalize $url
71+
$url = self::parseUrl($url);
72+
73+
if (null === $url['scheme'] || null === $url['authority']) {
74+
$url = implode('', $url);
75+
76+
if (null === $baseUri) {
77+
foreach ($this->options as $regexp => $defaultOptions) {
78+
if (!isset($defaultOptions['base_uri'])) {
79+
continue;
80+
}
81+
82+
$baseUri = self::parseUrl($defaultOptions['base_uri']);
83+
if (null === $baseUri['scheme'] || null === $baseUri['authority']) {
84+
throw new InvalidArgumentException(sprintf('Unvalid URL: the base_uri configured "%s" is not a valid base URI ', implode('', $baseUri)));
85+
}
86+
87+
$baseUri['path'] = rtrim($baseUri['path']);
88+
89+
if (preg_match(sprintf('{%s}A', $regexp), $baseUri['scheme'].$baseUri['authority'].$baseUri['path'].'/'.ltrim($url, '/'))) {
90+
return $baseUri['scheme'].$baseUri['authority'].$baseUri['path'].'/'.ltrim($url, '/');
91+
}
92+
}
93+
94+
throw new InvalidArgumentException(sprintf('Unsupported URL: host or scheme is missing in "%s" and no option "base_uri" set/given.', $url));
95+
}
96+
97+
$baseUri = self::parseUrl($baseUri);
98+
if (null === $baseUri['scheme'] || null === $baseUri['authority']) {
99+
throw new InvalidArgumentException(sprintf('Unvalid URL: the base_uri configured "%s" is not a valid base URI ', implode('', $baseUri)));
100+
}
101+
102+
return $baseUri['scheme'].$baseUri['authority'].rtrim($baseUri['path'], '/').'/'.ltrim($url, '/');
103+
}
104+
105+
if (null !== $baseUri && false !== strpos($url, 'http://') && false === strpos($url, $baseUri)) {
106+
throw new InvalidArgumentException(sprintf('Unvalid URL: the base_uri configured "%s" is not the same then the one given in the url "%s"', $baseUri, $url));
107+
}
108+
109+
return implode('', $url);
110+
}
111+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient;
13+
14+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
15+
use Symfony\Contracts\HttpClient\HttpClientInterface;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
18+
19+
/**
20+
* Transforms relative urls given to an.
21+
*
22+
* @author Anthony Martin <[email protected]>
23+
*
24+
* @experimental in 4.3
25+
*/
26+
class ScopedHttpClient implements HttpClientInterface
27+
{
28+
use HttpClientTrait;
29+
30+
private $client;
31+
private $baseUri;
32+
33+
public function __construct(HttpClientInterface $client, $baseUri = null)
34+
{
35+
$this->client = $client;
36+
37+
if (null === $baseUri || '' === $baseUri) {
38+
throw new InvalidArgumentException(sprintf('Unvalid URL: the base_uri configured "%s" is not a valid base URI ', $baseUri));
39+
}
40+
41+
$baseUri = self::parseUrl($baseUri);
42+
if (null === $baseUri['scheme'] || null === $baseUri['authority']) {
43+
throw new InvalidArgumentException(sprintf('Unvalid URL: the base_uri configured "%s" is not a valid base URI ', implode('', $baseUri)));
44+
}
45+
46+
$this->baseUri = $baseUri['scheme'].$baseUri['authority'].rtrim($baseUri['path'], '/');
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function request(string $method, string $url, array $options = []): ResponseInterface
53+
{
54+
if (!parse_url($url, PHP_URL_SCHEME) || !parse_url($url, PHP_URL_HOST)) {
55+
$url = $this->baseUri.'/'.ltrim($url, '/');
56+
} elseif (false !== strpos($url, 'http://') && false === strpos($url, $this->baseUri)) {
57+
throw new InvalidArgumentException(sprintf('Unvalid URL: the base_uri configured "%s" is not the same then the one given in the url "%s"', $this->baseUri, $url));
58+
}
59+
60+
return $this->client->request($method, $url, $options);
61+
}
62+
63+
/**
64+
* {@inheritdoc}
65+
*/
66+
public function stream($responses, float $timeout = null): ResponseStreamInterface
67+
{
68+
return $this->client->stream($responses, $timeout);
69+
}
70+
}

0 commit comments

Comments
 (0)