8000 [HttpClient] Added TraceableHttpClient and WebProfiler panel · symfony/symfony@6291ef0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6291ef0

Browse files
jeremyFreeAgentnicolas-grekas
authored andcommitted
[HttpClient] Added TraceableHttpClient and WebProfiler panel
1 parent b88f950 commit 6291ef0

File tree

7 files changed

+337
-4
lines changed

7 files changed

+337
-4
lines changed

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
use Symfony\Component\Form\FormTypeInterface;
6363
use Symfony\Component\HttpClient\Psr18Client;
6464
use Symfony\Component\HttpClient\ScopingHttpClient;
65+
use Symfony\Component\HttpClient\TraceableHttpClient;
6566
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
6667
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
6768
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
@@ -1874,6 +1875,13 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder
18741875
{
18751876
$loader->load('http_client.xml');
18761877

1878+
$collectorDefinition = $container->getDefinition('data_collector.http_client');
1879+
1880+
if (!$debug = $container->getParameter('kernel.debug')) {
1881+
$collectorDefinition->clearTag('data_collector');
1882+
$container->removeDefinition('debug.http_client');
1883+
}
1884+
18771885
$container->getDefinition('http_client')->setArguments([$config['default_options'] ?? [], $config['max_host_connections'] ?? 6]);
18781886

18791887
if (!$hasPsr18 = interface_exists(ClientInterface::class)) {
@@ -1898,6 +1906,19 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder
18981906
->setArguments([new Reference('http_client'), [$scope => $scopeConfig], $scope]);
18991907
}
19001908

1909+
if ($debug) {
1910+
$innerId = '.'.$name.'.inner';
1911+
1912+
$container->setDefinition($innerId, $container->getDefinition($name)
1913+
->replaceArgument(0, new Reference('debug.http_client.inner'))
1914+
);
1915+
1916+
$container->register($name, TraceableHttpClient::class)
1917+
->setArguments([new Reference($innerId)]);
1918+
1919+
$collectorDefinition->addMethodCall('addClient', [$name, new Reference($name)]);
1920+
}
1921+
19011922
$container->registerAliasForArgument($name, HttpClientInterface::class);
19021923

19031924
if ($hasPsr18) {

src/Symfony/Bundle/FrameworkBundle/Resources/config/http_client.xml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@
1616
</service>
1717
<service id="Symfony\Contracts\HttpClient\HttpClientInterface" alias="http_client" />
1818

19-
<service id="psr18.http_client" class="Symfony\Component\HttpClient\Psr18Client">
19+
<service id="psr18.http_client" class="Symfony\Component\HttpClient\Psr18Client" autowire="true">
2020
<argument type="service" id="http_client" />
21-
<argument type="service" id="Psr\Http\Message\ResponseFactoryInterface" on-invalid="ignore" />
22-
<argument type="service" id="Psr\Http\Message\StreamFactoryInterface" on-invalid="ignore" />
2321
</service>
2422
<service id="Psr\Http\Client\ClientInterface" alias="psr18.http_client" />
23+
24+
<service id="data_collector.http_client" class="Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector">
25+
<tag name="data_collector" template="@WebProfiler/Collector/http_client.html.twig" id="http_client" priority="240" />
26+
<call method="addClient">
27+
<argument>http_client</argument>
28+
<argument type="service" id="debug.http_client" />
29+
</call>
30+
</service>
31+
32+
<service id="debug.http_client" class="Symfony\Component\HttpClient\TraceableHttpClient" decorates="http_client">
33+
<argument type="service" id="debug.http_client.inner" />
34+
</service>
2535
</services>
2636
</container>

src/Symfony/Bundle/FrameworkBundl 10000 e/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"symfony/polyfill-intl-icu": "~1.0",
4040
"symfony/form": "^4.3|^5.0",
4141
"symfony/expression-language": "^3.4|^4.0|^5.0",
42-
"symfony/http-client": "^4.3|^5.0",
42+
"symfony/http-client": "^4.4|^5.0",
4343
"symfony/mailer": "^4.3|^5.0",
4444
"symfony/messenger": "^4.3|^5.0",
4545
"symfony/mime": "^4.3|^5.0",
@@ -71,6 +71,7 @@
7171
"symfony/console": "<4.3",
7272
"symfony/dotenv": "<4.2",
7373
"symfony/dom-crawler": "<4.3",
74+
"symfony/http-client": "<4.4",
7475
"symfony/form": "<4.3",
7576
"symfony/messenger": "<4.3",
7677
"symfony/property-info": "<3.4",
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
2+
3+
{% block toolbar %}
4+
{% if collector.requestCount %}
5+
{% set icon %}
6+
{{ include('@WebProfiler/Icon/http-client.svg') }}
7+
{% set status_color = '' %}
8+
<span class="sf-toolbar-value">{{ collector.requestCount }}</span>
9+
{% endset %}
10+
11+
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }}
12+
{% endif %}
13+
{% endblock %}
14+
15+
{% block menu %}
16+
<span class="label {{ collector.requestCount == 0 ? 'disabled' }}">
17+
<span class="icon">{{ include('@WebProfiler/Icon/http-client.svg') }}</span>
18+
<strong>HTTP Client</strong>
19+
{% if collector.requestCount %}
20+
<span class="count">
21+
{{ collector.requestCount }}
22+
</span>
23+
{% endif %}
24+
</span>
25+
{% endblock %}
26+
27+
{% block panel %}
28+
<h2>HTTP Client</h2>
29+
{% if collector.requestCount == 0 %}
30+
<div class="empty">
31+
<p>No HTTP requests were made.</p>
32+
</div>
33+
{% else %}
34+
<div class="metrics">
35+
<div class="metric">
36+
<span class="value">{{ collector.requestCount }}</span>
37+
<span class="label">Total requests</span>
38+
</div>
39+
<div class="metric">
40+
<span class="value">{{ collector.errorCount }}</span>
41+
<span class="label">HTTP errors</span>
42+
</div>
43+
</div>
44+
<h2>Clients</h2>
45+
<div class="sf-tabs">
46+
{% for name, client in collector.clients %}
47+
<div class="tab {{ client.traces|length == 0 ? 'disabled' }}">
48+
<h3 class="tab-title">{{ name }} <span class="badge">{{ client.traces|length }}</span></h3>
49+
<div class="tab-content">
50+
{% if client.traces|length == 0 %}
51+
<div class="empty">
52+
<p>No requests were made with the "{{ name }}" service.</p>
53+
</div>
54+
{% else %}
55+
<h4>Requests</h4>
56+
{% for trace in client.traces %}
57+
<table>
58+
<thead>
59+
<tr>
60+
<th>
61+
<span class="label">{{ trace.method }}</span>
62+
</th>
63+
<th>
64+
{{ trace.url }}
65+
{% if trace.options is not empty %}
66+
{{ profiler_dump(trace.options, maxDepth=1) }}
67+
{% endif %}
68+
</th>
69+
</tr>
70+
</thead>
71+
<tbody>
72+
<tr>
73+
<th>
74+
{% if trace.http_code >= 500 %}
75+
{% set responseStatus = 'error' %}
76+
{% elseif trace.http_code >= 400 %}
77+
{% set responseStatus = 'warning' %}
78+
{% else %}
79+
{% set responseStatus = 'success' %}
80+
{% endif %}
81+
<span class="label status-{{ responseStatus }}">
82+
{{ trace.http_code }}
83+
</span>
84+
</th>
85+
<td>
86+
{{ profiler_dump(trace.info, maxDepth=1) }}
87+
</td>
88+
</tr>
89+
</tbody>
90+
</table>
91+
{% endfor %}
92+
{% endif %}
93+
</div>
94+
</div>
95+
{% endfor %}
96+
{% endif %}
97+
</div>
98+
{% endblock %}
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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\DataCollector;
13+
14+
use Symfony\Component\HttpClient\TraceableHttpClient;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
18+
19+
/**
20+
* @author Jérémy Romey <jeremy@free-agent.fr>
21+
*/
22+
final class HttpClientDataCollector extends DataCollector
23+
{
24+
/**
25+
* @var TraceableHttpClient[]
26+
*/
27+
private $clients = [];
28+
29+
public function addClient(string $name, TraceableHttpClient $client)
30+
{
31+
$this->clients[$name] = $client;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function collect(Request $request, Response $response, \Exception $exception = null)
38+
{
39+
$this->data = [
40+
'clients' => [],
41+
'request_count' => 0,
42+
'error_count' => 0,
43+
];
44+
45+
foreach ($this->clients as $name => $client) {
46+
$traces = $client->getTraces();
47+
48+
$this->data['request_count'] += \count($traces);
49+
$errorCount = 0;
50+
51+
foreach ($traces as $i => $trace) {
52+
if (400 <= ($trace['info']['http_code'] ?? 0)) {
53+
++$errorCount;
54+
}
55+
56+
$info = $trace['info'];
57+
$traces[$i]['http_code'] = $info['http_code'];
58+
59+
unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']);
60+
61+
if ($trace['method'] === $info['http_method']) {
62+
unset($info['http_method']);
63+
}
64+
65+
if ($trace['url'] === $info['url']) {
66+
unset($info['url']);
67+
}
68+
69+
foreach ($info as $k => $v) {
70+
if (!$v || (is_numeric($v) && 0 > $v)) {
71+
unset($info[$k]);
72+
}
73+
}
74+
75+
$traces[$i]['info'] = $this->cloneVar($info);
76+
$traces[$i]['options'] = $this->cloneVar($trace['options']);
77+
}
78+
79+
$this->data['clients'][$name] = [
80+
'traces' => $traces,
81+
'error_count' => $errorCount,
82+
];
83+
$this->data['error_count'] += $errorCount;
84+
}
85+
}
86+
87+
public function getClients(): array
88+
{
89+
return $this->data['clients'] ?? [];
90+
}
91+
92+
public function getRequestCount(): int
93+
{
94+
return $this->data['request_count'] ?? 0;
95+
}
96+
97+
public function getErrorCount(): int
98+
{
99+
return $this->data['error_count'] ?? 0;
100+
}
101+
102+
/**
103+
* {@inheritdoc}
104+
*/
105+
public function reset()
106+
{
107+
$this->data = [];
108+
foreach ($this->clients as $client) {
109+
$client->clearTraces();
110+
}
111+
}
112+
113+
/**
114+
* {@inheritdoc}
115+
*/
116+
public function getName(): string
117+
{
118+
return 'http_client';
119+
}
120+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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\Contracts\HttpClient\HttpClientInterface;
15+
use Symfony\Contracts\HttpClient\ResponseInterface;
16+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
17+
18+
/**
19+
* @author Jérémy Romey <jeremy@free-agent.fr>
20+
*/
21+
final class TraceableHttpClient implements HttpClientInterface
22+
{
23+
private $client;
24+
private $traces = [];
25+
26+
public function __construct(HttpClientInterface $client)
27+
{
28+
$this->client = $client;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function request(string $method, string $url, array $options = []): ResponseInterface
35+
{
36+
$traceInfo = [];
37+
$this->traces[] = [
38+
'method' => $method,
39+
'url' => $url,
40+
'options' => $options,
41+
'info' => &$traceInfo,
42+
];
43+
$onProgress = $options['on_progress'] ?? null;
44+
45+
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use (&$traceInfo, $onProgress) {
46+
$traceInfo = $info;
47+
48+
if ($onProgress) {
49+
$onProgress($dlNow, $dlSize, $info);
50+
}
51+
};
52+
53+
return $this->client->request($method, $url, $options);
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function stream($responses, float $timeout = null): ResponseStreamInterface
60+
{
61+
return $this->client->stream($responses, $timeout);
62+
}
63+
64+
public function getTraces(): array
65+
{
66+
return $this->traces;
67+
}
68+
69+
public function clearTraces()
70+
{
71+
$this->traces = [];
72+
}
73+
}

0 commit comments

Comments
 (0)
0