Skip to content

[HttpClient] collect the body of responses when possible #35407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\VarDumper\Caster\ImgStub;

/**
* @author Jérémy Romey <jeremy@free-agent.fr>
Expand Down Expand Up @@ -128,8 +129,29 @@ private function collectOnClient(TraceableHttpClient $client): array
}
}

if (\is_string($content = $trace['content'])) {
$contentType = 'application/octet-stream';

foreach ($info['response_headers'] ?? [] as $h) {
if (0 === stripos($h, 'content-type: ')) {
$contentType = substr($h, \strlen('content-type: '));
break;
}
}

if (0 === strpos($contentType, 'image/') && class_exists(ImgStub::class)) {
$content = new ImgStub($content, $contentType, '');
} else {
$content = [$content];
}

$k = 'response_content';
} else {
$k = 'response_json';
}

$debugInfo = array_diff_key($info, $baseInfo);
$info = array_diff_key($info, $debugInfo) + ['debug_info' => $debugInfo];
$info = ['info' => $debugInfo] + array_diff_key($info, $debugInfo) + [$k => $content];
unset($traces[$i]['info']); // break PHP reference used by TraceableHttpClient
$traces[$i]['info'] = $this->cloneVar($info);
$traces[$i]['options'] = $this->cloneVar($trace['options']);
Expand Down
122 changes: 122 additions & 0 deletions src/Symfony/Component/HttpClient/Response/TraceableResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpClient\Response;

use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\HttpClient\Exception\RedirectionException;
use Symfony\Component\HttpClient\Exception\ServerException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class TraceableResponse implements ResponseInterface
{
private $client;
private $response;
private $content;

public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content)
{
$this->client = $client;
$this->response = $response;
$this->content = &$content;
}

public function getStatusCode(): int
{
return $this->response->getStatusCode();
}

public function getHeaders(bool $throw = true): array
{
return $this->response->getHeaders($throw);
}

public function getContent(bool $throw = true): string
{
$this->content = $this->response->getContent(false);

if ($throw) {
$this->checkStatusCode($this->response->getStatusCode());
}

return $this->content;
}

public function toArray(bool $throw = true): array
{
$this->content = $this->response->toArray(false);

if ($throw) {
$this->checkStatusCode($this->response->getStatusCode());
}

return $this->content;
}

public function cancel(): void
{
$this->response->cancel();
}

public function getInfo(string $type = null)
{
return $this->response->getInfo($type);
}

/**
* Casts the response to a PHP stream resource.
*
* @return resource
*
* @throws TransportExceptionInterface When a network error occurs
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
* @throws ClientExceptionInterface On a 4xx when $throw is true
* @throws ServerExceptionInterface On a 5xx when $throw is true
*/
public function toStream(bool $throw = true)
{
if ($throw) {
// Ensure headers arrived
$this->response->getHeaders(true);
}

if (\is_callable([$this->response, 'toStream'])) {
return $this->response->toStream(false);
}

return StreamWrapper::createResource($this->response, $this->client);
}

private function checkStatusCode($code)
{
if (500 <= $code) {
throw new ServerException($this);
}

if (400 <= $code) {
throw new ClientException($this);
}

if (300 <= $code) {
throw new RedirectionException($this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,18 @@ public function testItTracesRequest()
return true;
})
)
->willReturn(MockResponse::fromRequest('GET', '/foo/bar', ['options1' => 'foo'], new MockResponse()))
->willReturn(MockResponse::fromRequest('GET', '/foo/bar', ['options1' => 'foo'], new MockResponse('hello')))
;
$sut = new TraceableHttpClient($httpClient);
$sut->request('GET', '/foo/bar', ['options1' => 'foo']);
$sut->request('GET', '/foo/bar', ['options1' => 'foo'])->getContent();
$this->assertCount(1, $tracedRequests = $sut->getTracedRequests());
$actualTracedRequest = $tracedRequests[0];
$this->assertEquals([
'method' => 'GET',
'url' => '/foo/bar',
'options' => ['options1' => 'foo'],
'info' => [],
'content' => 'hello',
], $actualTracedRequest);
}

Expand Down
21 changes: 19 additions & 2 deletions src/Symfony/Component/HttpClient/TraceableHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Response\TraceableResponse;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
Expand All @@ -36,12 +37,14 @@ public function __construct(HttpClientInterface $client)
*/
public function request(string $method, string $url, array $options = []): ResponseInterface
{
$content = '';
$traceInfo = [];
$this->tracedRequests[] = [
'method' => $method,
'url' => $url,
'options' => $options,
'info' => &$traceInfo,
'content' => &$content,
];
$onProgress = $options['on_progress'] ?? null;

Expand All @@ -53,15 +56,29 @@ public function request(string $method, string $url, array $options = []): Respo
}
};

return $this->client->request($method, $url, $options);
return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content);
}

/**
* {@inheritdoc}
*/
public function stream($responses, float $timeout = null): ResponseStreamInterface
{
return $this->client->stream($responses, $timeout);
if ($responses instanceof TraceableResponse) {
$responses = [$responses];
} elseif (!is_iterable($responses)) {
throw new \TypeError(sprintf('%s() expects parameter 1 to be an iterable of TraceableResponse objects, %s given.', __METHOD__, \is_object($responses) ? \get_class($responses) : \gettype($responses)));
}

return $this->client->stream(\Closure::bind(static function () use ($responses) {
foreach ($responses as $k => $r) {
if (!$r instanceof TraceableResponse) {
throw new \TypeError(sprintf('%s() expects parameter 1 to be an iterable of TraceableResponse objects, %s given.', __METHOD__, \is_object($r) ? \get_class($r) : \gettype($r)));
}

yield $k => $r->response;
}
}, null, TraceableResponse::class), $timeout);
}

public function getTracedRequests(): array
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/VarDumper/Caster/ImgStub.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
class ImgStub extends ConstStub
{
public function __construct(string $data, string $contentType, string $size)
public function __construct(string $data, string $contentType, string $size = '')
{
$this->value = '';
$this->attr['img-data'] = $data;
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/VarDumper/Dumper/CliDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut)
'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0,
'binary' => $bin,
];
$str = explode("\n", $str);
$str = $bin && false !== strpos($str, "\0") ? [$str] : explode("\n", $str);
if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) {
unset($str[1]);
$str[0] .= "\n";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function testGet()
6 => {$intMax}
"str" => "déjà\\n"
7 => b"""
é\\x00test\\t\\n
é\\x01test\\t\\n
ing
"""
"[]" => []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function testGet()
<span class=sf-dump-key>6</span> => <span class=sf-dump-num>{$intMax}</span>
"<span class=sf-dump-key>str</span>" => "<span class=sf-dump-str title="5 characters">d&%s;j&%s;<span class="sf-dump-default sf-dump-ns">\\n</span></span>"
<span class=sf-dump-key>7</span> => b"""
<span class=sf-dump-str title="11 binary or non-UTF-8 characters">&eacute;<span class="sf-dump-default">\\x00</span>test<span class="sf-dump-default">\\t</span><span class="sf-dump-default sf-dump-ns">\\n</span></span>
<span class=sf-dump-str title="11 binary or non-UTF-8 characters">&eacute;<span class="sf-dump-default">\\x01</span>test<span class="sf-dump-default">\\t</span><span class="sf-dump-default sf-dump-ns">\\n</span></span>
<span class=sf-dump-str title="11 binary or non-UTF-8 characters">ing</span>
"""
"<span class=sf-dump-key>[]</span>" => []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DumbFoo
$var = [
'number' => 1, null,
'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX,
'str' => "déjà\n", "\xE9\x00test\t\ning",
'str' => "déjà\n", "\xE9\x01test\t\ning",
'[]' => [],
'res' => $g,
'obj' => $foo,
Expand Down