Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[HttpClient] Fix nesteed stream in AsyncResponse #38520

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
Oct 12, 2020

Conversation

jderusse
Copy link
Member

Q A
Branch? 5.x
Bug fix? yes
New feature? no
Deprecations? no
Tickets #38509
License MIT
Doc PR /

When streaming twice (streaming inside streaming) an AsycResponse the second stream will yield the LastChunk, but the first Stream won't have access to it and ends with an exception A chunk passthru must yield an "isLast()" chunk
This PR adds a state in AsyncRsponse to remember if the lastChunk has been yielded.

Reproducer:

// a Simple Client that return an AsyncResponse
$client = new class(HttpClient::create()) implements HttpClientInterface {
    use AsyncDecoratorTrait;
    private $client;
    public function __construct(HttpClientInterface $client)
    {
        $this->client = $client;
    }

    public function request(string $method, string $url, array $options = []): ResponseInterface
    {
        return new AsyncResponse($this->client, $method, $url, $options, null);
    }
};

$response = $client->request('GET', 'https://httpbin.org/status/200');

foreach ($client->stream($response) as $chunk) { // will end in a FirstChunk <== bug 
    foreach ($client->stream($response) as $chunk) { // This will correctly handle the LastChunk

    }
}

@jderusse jderusse changed the title Fix nesteed stream in AsyncResponse [HttpClient] Fix nesteed stream in AsyncResponse Oct 11, 2020
Copy link
Member

@Nyholm Nyholm left a comment

Choose a reason for hiding this comment

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

I can confirm that it fixes the bug.

@nicolas-grekas nicolas-grekas added this to the 5.2 milestone Oct 12, 2020
@nicolas-grekas
Copy link
Member

Good catch, thanks @jderusse.

@nicolas-grekas nicolas-grekas merged commit 2a70f68 into symfony:5.x Oct 12, 2020
@fabpot fabpot mentioned this pull request Oct 14, 2020
@jderusse jderusse deleted the fix-nesteed-stream branch October 15, 2020 10:01
@bohanyang
Copy link

Hi, just found here since I met this error while doing something like "streaming inside streaming" with RetryableHttpClient:

PHP Warning:  Undefined variable $chunk in /home/debian/wpdl/vendor/symfony/http-client/Response/AsyncResponse.php on line 284
PHP Fatal error:  Uncaught Error: Call to a member function getError() on null in /home/debian/wpdl/vendor/symfony/http-client/Response/AsyncResponse.php:284
Stack trace:
#0 /home/debian/wpdl/vendor/symfony/http-client/Response/StreamWrapper.php(126): Symfony\Component\HttpClient\Response\AsyncResponse::stream()

The response stream generator here is empty (->valid() is false)

foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) {
$r = $asyncMap[$response];
if (null === $chunk->getError()) {
if ($chunk->isFirst()) {
// Ensure no exception is thrown on destruct for the wrapped response
$r->response->getStatusCode();
} elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) {
$r->content = fopen('php://memory', 'w+');
}
}
if (!$r->passthru) {
if (null !== $chunk->getError() || $chunk->isLast()) {
unset($asyncMap[$response]);
} elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) {
$chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
$r->info['error'] = $chunk->getError();
$r->response->cancel();
}
yield $r => $chunk;
continue;
}
foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) {
yield $r => $chunk;
}
if ($r->response !== $response && isset($asyncMap[$response])) {
break;
}
}
if (null === $chunk->getError() && $chunk->isLast()) {
$r->lastYielded = true;
}

Some details

I'm using 2 different RetryableHttpClient for the inside and outside stream respectively,
but they are using the same underlying CurlHttpClient.
It seems that the error no longer raises if initializing them with separate CurlHttpClient.

@nicolas-grekas
Copy link
Member

@bohanyang please don't comment on closed PRs/issues, we cannot track them and we'll forget about it despite our efforts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants