Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6bc396e

Browse filesBrowse files
author
Anthony MARTIN
committed
[HttpClient] Add a ConditionalHttpClient
1 parent 2278d4c commit 6bc396e
Copy full SHA for 6bc396e

File tree

3 files changed

+181
-0
lines changed
Filter options

3 files changed

+181
-0
lines changed
+69Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 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 <anthony.martin@sensiolabs.com>
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+
if (!parse_url($url, PHP_URL_SCHEME) || !parse_url($url, PHP_URL_HOST)) {
48+
throw new InvalidArgumentException(sprintf('Unsupported URL: host or scheme is missing in "%s".', $url));
49+
}
50+
51+
foreach ($this->options as $regexp => $defaultOptions) {
52+
$preparedOptions = self::mergeDefaultOptions($options, $defaultOptions, true);
53+
54+
if (preg_match($regexp, $url)) {
55+
return $this->client->request($method, $url, $preparedOptions);
56+
}
57+
}
58+
59+
return $this->client->request($method, $url, $options);
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
*/
65+
public function stream($responses, float $timeout = null): ResponseStreamInterface
66+
{
67+
return $this->client->stream($responses, $timeout);
68+
}
69+
}

‎src/Symfony/Component/HttpClient/Response/MockResponse.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/MockResponse.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class MockResponse implements ResponseInterface
2727
use ResponseTrait;
2828

2929
private $body;
30+
private $requestOptions;
3031

3132
private static $mainMulti;
3233
private static $idSequence = 0;
@@ -60,6 +61,11 @@ protected function close(): void
6061
$this->body = [];
6162
}
6263

64+
public function getRequestOptions(): array
65+
{
66+
return $this->requestOptions;
67+
}
68+
6369
/**
6470
* @internal
6571
*/
@@ -87,6 +93,8 @@ public static function fromRequest(string $method, string $url, array $options,
8793
$response->info['user_data'] = $options['user_data'] ?? null;
8894
$response->info['url'] = $url;
8995

96+
$response->requestOptions = $options;
97+
9098
self::writeRequest($response, $options, $mock);
9199
$response->body[] = [$options, $mock];
92100

+104Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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\Exception\InvalidArgumentException;
17+
use Symfony\Component\HttpClient\MockHttpClient;
18+
use Symfony\Component\HttpClient\Response\MockResponse;
19+
20+
class ConditionalHttpClientTest extends TestCase
21+
{
22+
public function testInvalidUrl()
23+
{
24+
$defaultOptions = [
25+
'#^/foo-bar$#' => [
26+
'headers' => ['Content-Type' => 'application/json'],
27+
],
28+
'#^.*$#' => [
29+
'headers' => ['Content-Type' => 'application/json'],
30+
],
31+
];
32+
33+
$mockClient = new MockHttpClient(null);
34+
$client = new ConditionalHttpClient($mockClient, $defaultOptions);
35+
36+
$this->expectException(InvalidArgumentException::class);
37+
$client->request('GET', '/foo');
38+
}
39+
40+
public function getUrlsAndDefaultOptions()
41+
{
42+
$defaultOptions = [
43+
'#^.*/foo-bar$#' => [
44+
'headers' => ['content-type' => 'application/json'],
45+
],
46+
'#^.*$#' => [
47+
'headers' => ['content-type' => 'text/html'],
48+
],
49+
];
50+
51+
yield ['regexp' => '#^.*/foo-bar$#', 'url' => 'http://example.com/foo-bar', 'default_options' => $defaultOptions];
52+
yield ['regexp' => '#^.*$#', 'url' => 'http://example.com/bar-foo', 'default_options' => $defaultOptions];
53+
yield ['regexp' => '#^.*$#', 'url' => 'http://example.com/foobar', 'default_options' => $defaultOptions];
54+
}
55+
56+
/**
57+
* @dataProvider getUrlsAndDefaultOptions
58+
*/
59+
public function testMatchingUrls(string $regexp, string $url, array $options)
60+
{
61+
$mockClient = new MockHttpClient(new MockResponse($url, $options[$regexp]), 'http://example.com');
62+
$client = new ConditionalHttpClient($mockClient, $options);
63+
64+
$response = $client->request('GET', $url);
65+
$responseInfo = $response->getInfo('headers');
66+
67+
$this->assertEquals([], array_diff_assoc($responseInfo, $options[$regexp]['headers']));
68+
}
69+
70+
public function testMatchingUrlsAndOptions()
71+
{
72+
$defaultOptions = [
73+
'#^/foo-bar$#' => ['headers' => ['x-app' => 'unit-test-foo-bar']],
74+
'#^.*$#' => ['headers' => ['content-type' => 'text/html']],
75+
];
76+
77+
$mockResponses = [
78+
new MockResponse('http://example.com/foo-bar', $defaultOptions['#^/foo-bar$#']),
79+
new MockResponse('http://example.com/bar-foo', $defaultOptions['#^.*$#']),
80+
new MockResponse('http://example.com/', $defaultOptions['#^.*$#']),
81+
];
82+
83+
$mockClient = new MockHttpClient($mockResponses, 'http://example.com');
84+
$client = new ConditionalHttpClient($mockClient, $defaultOptions);
85+
86+
$response = $client->request('GET', 'http://example.com/foo-bar', ['json' => ['url' => 'http://example.com']]);
87+
$requestOptions = $response->getRequestOptions();
88+
$responseInfos = $response->getInfo('headers');
89+
$this->assertEquals($requestOptions['json']['url'], 'http://example.com');
90+
$this->assertEquals($responseInfos['x-app'], $defaultOptions['#^/foo-bar$#']['headers']['x-app']);
91+
92+
$response = $client->request('GET', 'http://example.com/bar-foo', ['headers' => ['x-app' => 'unit-test']]);
93+
$requestOptions = $response->getRequestOptions();
94+
$responseInfos = $response->getInfo('headers');
95+
$this->assertEquals($requestOptions['headers']['x-app'][0], 'unit-test');
96+
$this->assertEquals($responseInfos['content-type'], 'text/html');
97+
98+
$response = $client->request('GET', 'http://example.com/bar-foo', ['headers' => ['x-app' => 'unit-test']]);
99+
$requestOptions = $response->getRequestOptions();
100+
$responseInfos = $response->getInfo('headers');
101+
$this->assertEquals($requestOptions['headers']['x-app'][0], 'unit-test');
102+
$this->assertEquals($responseInfos['content-type'], 'text/html');
103+
}
104+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.