8000 [WIP][HttpClient] Add a ConditionalHttpClient · symfony/symfony@a1b5933 · GitHub
[go: up one dir, main page]

Skip to content

Commit a1b5933

Browse files
author
Anthony MARTIN
committed
[WIP][HttpClient] Add a ConditionalHttpClient
1 parent ac93c9e commit a1b5933

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 phpDocumentor\Reflection\Types\Boolean;
15+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
17+
use Symfony\Contracts\HttpClient\ResponseInterface;
18+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
19+
20+
/**
21+
* Auto-configure the default options based on the requested URL.
22+
*
23+
* @author Anthony Martin <anthony.martin@sensiolabs.com>
24+
*
25+
* @experimental in 4.3
26+
*/
27+
class ConditionalHttpClient implements HttpClientInterface
28+
{
29+
use HttpClientTrait;
30+
31+
private $client;
32+
private $options;
33+
private $absoluteUrls;
34+
35+
/**
36+
* @param array[] $options the default options to use when the regexp provided as key matches the requested URL
37+
* @param bool $absoluteUrls indicates if at least one rtegexp url given in $options would be on absolute url, in this case we need to check the url give to request() with use of prepareRequest to parse tu url etc...
38+
*/
39+
public function __construct(HttpClientInterface $client, array $options, bool $absoluteUrls = false)
40+
{
41+
$this->client = $client;
42+
$this->options = $options;
43+
$this->absoluteUrls = $absoluteUrls;
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function request(string $method, string $url, array $options = []): ResponseInterface
50+
{
51+
foreach ($this->options as $regexp => $defaultOptions) {
52+
if ($this->absoluteUrls) {
53+
try {
54+
[$preparedUrl, $preparedOptions] = self::prepareRequest($method, $url, $options, $defaultOptions, true);
55+
$preparedUrl = implode('', $preparedUrl);
56+
} catch (InvalidArgumentException $e) {
57+
return $this->client->request($method, $url, $options);
58+
}
59+
} else {
60+
$preparedUrl = $url;
61+
$preparedOptions = self::mergeDefaultOptions($options, $this->options, true);
62+
}
63+
64+
if (preg_match($regexp, $preparedUrl)) {
65+
return $this->client->request($method, $preparedUrl, $preparedOptions);
66+
}
67+
}
68+
69+
return $this->client->request($method, $url, $options);
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function stream($responses, float $timeout = null): ResponseStreamInterface
76+
{
77+
return $this->client->stream($responses, $timeout);
78+
}
79+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpClient\ConditionalHttpClient;
16+
use Symfony\Component\HttpClient\Tests\Mocks\SimpleHttpClientMock;
17+
use Symfony\Contracts\HttpClient\ResponseInterface;
18+
19+
class ConditionalHttpClientTest extends TestCase
20+
{
21+
public function getUrlsAndDefaultOptions()
22+
{
23+
$defaultOptions = [
24+
'#^/foo-bar$#' => [
25+
'base_uri' => 'http://example.com',
26+
'headers' => ['Content-Type' => 'application/json'],
27+
],
28+
'#^.*$#' => [
29+
'base_uri' => 'http://example.com',
30+
'headers' => ['Content-Type' => 'application/json'],
31+
],
32+
];
33+
34+
yield ['url' => '/foo-bar', 'default_options' => $defaultOptions];
35+
yield ['url' => '/bar-foo', 'default_options' => $defaultOptions];
36+
yield ['url' => '/', 'default_options' => $defaultOptions];
37+
}
38+
39+
/**
40+
* @dataProvider getUrlsAndDefaultOptions
41+
*/
42+
public function testMatchingUrls(string $url, array $options)
43+
{
44+
$client = new ConditionalHttpClient(new SimpleHttpClientMock($this->createMock(ResponseInterface::class), $options, []), $options);
45+
46+
$this->assertInstanceOf(ResponseInterface::class, $client->request('GET', $url));
47+
}
48+
49+
public function getUrlsAndDefaultOptionsAndRequestsOptions()
50+
{
51+
$defaultOptions = [
52+
'#^/foo-bar$#' => [
53+
'base_uri' => 'http://example.com',
54+
'headers' => [
55+
'X-App' => 'unit-test-foo-bar',
56+
],
57+
],
58+
'#^.*$#' => [
59+
'base_uri' => 'http://example.com',
60+
'headers' => ['Content-Type' => 'application/json'],
61+
],
62+
];
63+
64+
$requestOptionsByReg = [
65+
'#^/foo-bar$#' => ['json' => ['url' => 'http://example.com']],
66+
'#^.*$#' => ['headers' => ['X-App' => 'unit-test']],
67+
];
68+
69+
yield [
70+
'url' => '/foo-bar',
71+
'default_options' => $defaultOptions,
72+
'request_options_by_reg' => $requestOptionsByReg,
73+
'request_options' => ['json' => ['url' => 'http://example.com']],
74+
];
75+
yield [
76+
'url' => '/bar-foo',
77+
'default_options' => $defaultOptions,
78+
'request_options_by_reg' => $requestOptionsByReg,
79+
'request_options' => [],
80+
];
81+
yield [
82+
'url' => '/',
83+
'default_options' => $defaultOptions,
84+
'request_options_by_reg' => $requestOptionsByReg,
85+
'request_options' => ['headers' => ['X-App' => 'unit-test']],
86+
];
87+
}
88+
89+
/**
90+
* @dataProvider getUrlsAndDefaultOptionsAndRequestsOptions
91+
*/
92+
public function testMatchingUrlsAndOptions(string $url, array $defaultOptions, array $requestOptionsByReg, array $requestOptions)
93+
{
94+
$client = new ConditionalHttpClient(new SimpleHttpClientMock($this->createMock(ResponseInterface::class), $defaultOptions, $requestOptionsByReg), $defaultOptions);
95+
96+
$this->assertInstanceOf(ResponseInterface::class, $client->request('GET', $url, $requestOptions));
97+
}
98+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: anthonymartin
5+
* Date: 19/03/19
6+
* Time: 12:09.
7+
*/
8+
9+
namespace Symfony\Component\HttpClient\Tests\Mocks;
10+
11+
use Symfony\Component\HttpClient\Exception\TransportException;
12+
use Symfony\Component\HttpClient\HttpClientTrait;
13+
use Symfony\Component\HttpClient\Response\ResponseStream;
14+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
15+
use Symfony\Contracts\HttpClient\HttpClientInterface;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
18+
19+
/**
20+
* A class to mock a simple http client, to test ConditionalHttpClient class.
21+
*
22+
* @author Anthony Martin <anthony.martin@sensiolabs.com>
23+
*/
24+
class SimpleHttpClientMock implements HttpClientInterface
25+
{
26+
use HttpClientTrait;
27+
28+
private $response;
29+
private $defaultOptionsByRegexp;
30+
private $requestsOptionsByRegexp;
31+
32+
public function __construct(ResponseInterface $response, array $defaultOptionsByRegexp, array $requestsOptionsByRegexp)
33+
{
34+
$this->response = $response;
35+
$this->defaultOptionsByRegexp = $defaultOptionsByRegexp;
36+
$this->requestsOptionsByRegexp = $requestsOptionsByRegexp;
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*
42+
* @throws TransportExceptionInterface When an unsupported option is passed
43+
*/
44+
public function request(string $method, string $url, array $options = []): ResponseInterface
45+
{
46+
foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) {
47+
if (preg_match($regexp, $url)) {
48+
[$preparedUrl, $preparedOptions] = self::prepareRequest($method, $url, $this->requestsOptionsByRegexp[$regexp] ?? [], $defaultOptions, true);
49+
50+
if (isset($preparedOptions['headers']['x-app']) && isset($options['headers']['x-app'])
51+
&& $preparedOptions['headers']['x-app'] === $options['headers']['x-app']) {
52+
return $this->response;
53+
}
54+
55+
throw new TransportException('options doesn\'t matches');
56+
}
57+
}
58+
59+
throw new TransportException('No url matches');
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
*/
65+
public function stream($responses, float $timeout = null): ResponseStreamInterface
66+
{
67+
if ($responses instanceof ResponseInterface) {
68+
$responses = [$responses];
69+
} elseif (!\is_iterable($responses)) {
70+
throw new \TypeError(sprintf('%s() expects parameter 1 to be an iterable of ResponseInterface objects, %s given.', __METHOD__, \is_object($responses) ? \get_class($responses) : \gettype($responses)));
71+
}
72+
73+
return new ResponseStream($responses);
74+
}
75+
}

0 commit comments

Comments
 (0)
0