Skip to content
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

Reading GzipDecodeStream causes Uncaught RuntimeException: Unable to perform operation on closed stream #90

Closed
iainconnor opened this issue Jan 18, 2018 · 5 comments · Fixed by #100

Comments

@iainconnor
Copy link

Q A
Bug? yes
New Feature? no
Version 1.0.1

Actual Behavior

What is the actual behavior?

PHP Fatal error: Uncaught RuntimeException: Unable to perform operation on closed stream in /Users/iconnor/Documents/PHP/php-http-message-bug/vendor/clue/stream-filter/src/functions.php:98

Expected Behavior

What is the behavior you expect?

The stream is rewindable. I should be able to read its contents, rewind it, and then read its contents again.

Steps to Reproduce

Checkout this repository https://github.com/iainconnor/php-http-message-bug/blob/master/demo.php run demo.php.

Or,

$streamFactory = new \Http\Message\StreamFactory\GuzzleStreamFactory();
$stream = $streamFactory->createStream(file_get_contents('http://httpbin.org/gzip'));
$gzipStream = new \Http\Message\Encoding\GzipDecodeStream($stream);

echo $gzipStream->getContents() . PHP_EOL;

if ( $gzipStream->isSeekable() ) {
    $gzipStream->rewind();
    echo $gzipStream->getContents();
}

Possible Solutions

If you have already ideas how to solve the issue, add them here.
(remove this section if not needed)

These lines -- https://github.com/php-http/message/blob/master/src/Encoding/FilteredStream.php#L116-L118 -- look to be the cause, though I'm not sure on the solution.

@joelwurtz
Copy link
Member

joelwurtz commented Jan 19, 2018

A gzip stream should not be seekable / rewindable, so i suspect it miss an exception here.

You can use a buffered stream to do that :

$gzipStream = new \Http\Message\Stream\BufferedStream(new \Http\Message\Encoding\GzipDecodeStream($stream));

@iainconnor
Copy link
Author

Thanks for the quick reply. That indeed does resolve this behaviour.
Do you think the DecodePlugin should be updated to wrap in a BufferedStream by default?

@joelwurtz
Copy link
Member

No it should not, as buffering the stream is not so useful (only when you need to read multiple times the body) and consume memory / io and will slow down applications when dealing with big body.

@iainconnor
Copy link
Author

iainconnor commented Jan 22, 2018

Thanks, I think that makes sense.
Though, I feel this behaviour should go into a document somewhere.

If you build a client like this...

return new PluginClient(
  $httpClient,
  [
    CachePlugin::clientCache($cache, $streamFactory),
    new DecoderPlugin([])
  ]
)

... which I think is reasonable, because you want your cache to be as efficient as possible, so putting it at the top of the chain makes it so it has the opportunity to satisfy the request from cache before doing unnecessary operations, then you'll run into this crash because CachePlugin will do...

$bodyStream = $response->getBody();
$body = $bodyStream->__toString();
if ($bodyStream->isSeekable()) {
  $bodyStream->rewind();
} else {
  $response = $response->withBody($this->streamFactory->createStream($body));
}

... and then, at some point, your application code will probably do a second $response->getBody()->getContents();, resulting in a crash.

Would the recommended behaviour be DecodePlugining first in the chain? Or should GzipDecodeStream return false for ->isSeekable()?

@joelwurtz
Copy link
Member

Filtered stream should return false in fact, fixed in #100

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

Successfully merging a pull request may close this issue.

2 participants