Skip to content

[HttpClient] add StreamableInterface to ease turning responses into PHP streams #37443

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
Jun 30, 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
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpClient/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ CHANGELOG

* added `AsyncDecoratorTrait` to ease processing responses without breaking async
* added support for pausing responses with a new `pause_handler` callable exposed as an info item
* added `StreamableInterface` to ease turning responses into PHP streams

5.1.0
-----
Expand Down
5 changes: 2 additions & 3 deletions src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Symfony\Component\HttpClient\Response\CommonResponseTrait;
use Symfony\Component\HttpClient\Response\StreamableInterface;
use Symfony\Component\HttpClient\Response\StreamWrapper;
use Symfony\Component\HttpClient\Response\TraceableResponse;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
Expand Down Expand Up @@ -120,7 +119,7 @@ public function createPsr7Response(ResponseInterface $response, bool $buffer = f
}
}

if ($response instanceof TraceableResponse || isset(class_uses($response)[CommonResponseTrait::class])) {
if ($response instanceof StreamableInterface) {
$body = $this->streamFactory->createStreamFromResource($response->toStream(false));
} elseif (!$buffer) {
$body = $this->streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $this->client));
Expand Down
5 changes: 2 additions & 3 deletions src/Symfony/Component/HttpClient/Psr18Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpClient\Response\CommonResponseTrait;
use Symfony\Component\HttpClient\Response\StreamableInterface;
use Symfony\Component\HttpClient\Response\StreamWrapper;
use Symfony\Component\HttpClient\Response\TraceableResponse;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

Expand Down Expand Up @@ -105,7 +104,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface
}
}

$body = $response instanceof TraceableResponse || isset(class_uses($response)[CommonResponseTrait::class]) ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
$body = $response instanceof StreamableInterface ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
$body = $this->streamFactory->createStreamFromResource($body);

if ($body->isSeekable()) {
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpClient/Response/AmpResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
*
* @internal
*/
final class AmpResponse implements ResponseInterface
final class AmpResponse implements ResponseInterface, StreamableInterface
{
use CommonResponseTrait;
use TransportResponseTrait;
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/HttpClient/Response/AsyncResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class AsyncResponse implements ResponseInterface
final class AsyncResponse implements ResponseInterface, StreamableInterface
{
use CommonResponseTrait;

Expand Down Expand Up @@ -95,7 +95,7 @@ public function toStream(bool $throw = true)
}

$handle = function () {
$stream = StreamWrapper::createResource($this->response);
$stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't this bypass changes done by the passthru callable ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope: this only extracts any potential handle for stream_select(), but the stream is always read through the resource created L103 below, which goes through the AsyncResponse


return stream_get_meta_data($stream)['wrapper_data']->stream_cast(STREAM_CAST_FOR_SELECT);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
use Symfony\Component\HttpClient\Exception\RedirectionException;
use Symfony\Component\HttpClient\Exception\ServerException;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;

/**
* Implements common logic for response classes.
Expand Down Expand Up @@ -123,14 +119,7 @@ public function toArray(bool $throw = true): array
}

/**
* 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
* {@inheritdoc}
*/
public function toStream(bool $throw = true)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpClient/Response/CurlResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*
* @internal
*/
final class CurlResponse implements ResponseInterface
final class CurlResponse implements ResponseInterface, StreamableInterface
{
use CommonResponseTrait {
getContent as private doGetContent;
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpClient/Response/MockResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class MockResponse implements ResponseInterface
class MockResponse implements ResponseInterface, StreamableInterface
{
use CommonResponseTrait;
use TransportResponseTrait {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
* @internal
*/
final class NativeResponse implements ResponseInterface
final class NativeResponse implements ResponseInterface, StreamableInterface
{
use CommonResponseTrait;
use TransportResponseTrait;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class StreamWrapper
*/
public static function createResource(ResponseInterface $response, HttpClientInterface $client = null)
{
if ($response instanceof TraceableResponse || (\is_callable([$response, 'toStream']) && isset(class_uses($response)[CommonResponseTrait::class]))) {
if ($response instanceof StreamableInterface) {
$stack = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 2);

if ($response !== ($stack[1]['object'] ?? null)) {
Expand Down
35 changes: 35 additions & 0 deletions src/Symfony/Component/HttpClient/Response/StreamableInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;

/**
* @author Nicolas Grekas <p@tchwork.com>
*/
interface StreamableInterface
{
/**
* 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*
* @internal
*/
class TraceableResponse implements ResponseInterface
class TraceableResponse implements ResponseInterface, StreamableInterface
{
private $client;
private $response;
Expand Down Expand Up @@ -99,7 +99,7 @@ public function toStream(bool $throw = true)
$this->response->getHeaders(true);
}

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

Expand Down