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

Fix of stream saving #176

Closed
wants to merge 3 commits into from
Closed

Conversation

Ryllaz
Copy link

@Ryllaz Ryllaz commented Mar 24, 2021

Using rewind for correct stream reading - you can get empty body in stream_get_contents

@Ryllaz
Copy link
Author

Ryllaz commented Mar 25, 2021

The problem is in the case of multiple requests. For example, if you need use OAuth you need do several requests.
And if you're using php://temp stream you need rewind for every request because second call of stream_get_contents will return empty body.

@Zegnat
Copy link
Collaborator

Zegnat commented Mar 27, 2021

I am not sure what behaviour this is fixing. PSR-7 defines two ways to read from a Stream instance: cast to string, or call the getContents-method. I believe both are correctly implemented in this lib. Could you add a unit test showing what it is that is fixed by this PR?

If you have code using PSR-7 Streams for some form of output, is there some reason you cannot cast to string to always get the full contents? Wondering if it is a behavioural error on the part of this implementation, or if it is just the fact that it is not super clear that PSR-7 has two ways to get Stream contents.

Note that this PR breaks backwards compatibility and will neccessitate a major version bump for semver. As Stream instances will have the output of a number of methods changed (e.g. tell() and getContents()).

@jaapromijn
Copy link

We have encountered an issue after upgrading a project. The body we send to an API is suddenly empty. Debugging it lead to this class (Stream) and stumbled upon this issue. I don't have extra context, but just wanted to say that we encountered it too :)

Also I tried the code in this PR and that indeed fixed it; the request has a body again.

@Ryllaz
Copy link
Author

Ryllaz commented Mar 31, 2021

I discovered the possible ways of getting response body.

First way:

...
$result = (string)$response->getBody()->getContents(); // resut string will NOT be empty

Second way:

...
$body = $response->getBody();
$body->rewind();
$result = $body->getContents(); // resut string will NOT be empty

The problem is that if you try to read the stream data without rewind but just with getContents() - at the start reading process current cursor's position is just after the last byte of the data just written. And stream_get_contents starts reading from the end of data to the end of the stream.

That's why result is empty.

This PR should fix this problem.

Also I think that php://temp stream should be closed just after receiving of the response - I can not imagine any correct cases to use it again without reopen. Thats why I hope that this PR will not break the logic in projects it will be used in.

@sunkan
Copy link
Contributor

sunkan commented Mar 31, 2021

@Zegnat
I did a bit of research and the other popular psr-7/psr-17 library all do the rewind when creating the temporary resource

And here is the most basic failing test

$this->assertTrue(Stream::create('test string')->getContents() === 'test string'); // at the moment this fails

Also this change is not really connected to psr-7 it tries to fix a problem with the psr-17 factory

It's just that this library have the factory part on the Stream class

And sure the specification doesn't specify if you should rewind the stream when creating it or not

@Zegnat
Copy link
Collaborator

Zegnat commented Mar 31, 2021

Also this change is not really connected to psr-7 it tries to fix a problem with the psr-17 factory

Very true. But what I am questioning is it being a problem 😉 PSR-7 does not give any guarantees that getContents() reads from the start of the stream content. For interoperability, if someone is consuming a PSR-7 object, they should never assume that is does. You either rewind(), check tell(), or cast to string if you do want to get the full stream contents. Those are the only things where PSR-7 guarantees interoperability for.

I did a bit of research and the other popular psr-7/psr-17 library

I could have saved you some time, I already did this research, with 13 different PSR implementations, back in 2018. It was almost a 50/50 split in results. There is a full discussion on the PHP-FIG mailing list started by me, a PR under discussion to ammend PSR-17 by another contributor, and a PR to the interoperability unit tests to check for cursor positions also by me.

Back in late 2018 / start 2019 we decided not to update the Nyholm/psr7 library because there was no consensus and we were hoping to get the PSR ammended instead. We would then follow wherever the PSR goes. Which would introduce a breaking change and would mean a version 2.0.0 release on our end.

Maybe I need to repeat the research and see if libraries are more in favour of one solution over the other.

Wondering if we should document this somewhere…


But to reitterate: if you need to know the full contents of a Stream instance, you should never assume getContents() gives it to you. That is not what PSR-7 says. Any middleware could have changed the cursor position, no matter which implementation you are using. So for correct interop work, please always rewind it yourself 😊

@sunkan
Copy link
Contributor

sunkan commented Mar 31, 2021

I would be fine with the idea of just updating the documentation.

I remember being bitten by this when change implementation a couple of years ago.

After that I use __toString() when I need the entire content because the documentation around what it does is clear.

@Nyholm
Copy link
Owner

Nyholm commented May 10, 2021

Hm. @Zegnat and I have been spending so much time telling people these exact arguments. PSR-7 nor PSR-17 does not specify this and you should never assume where the read pointer is.

I think we can save us some time (and the users the headache) to just be pragmatic instead.

I'll merge #177 because it has tests and a changelog entry.

Thank you @Ryllaz

@Nyholm Nyholm closed this May 10, 2021
liu21st pushed a commit to top-think/framework that referenced this pull request Mar 10, 2022
`$response->getBody()->getContents()` 并不能保证拿到完整内容,PSR-7 里的原话:

> Each stream instance will have various capabilities: it can be read-only, write-only, or read-write. It can also allow arbitrary random access (seeking forwards or backwards to any location), or only sequential access (for example in the case of a socket, pipe, or callback-based stream).
>
> Finally, StreamInterface defines a __toString() method to simplify retrieving or emitting the entire body contents at once.
> -- https://www.php-fig.org/psr/psr-7/#13-streams

相关讨论:
- Nyholm/psr7#176
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 this pull request may close these issues.

5 participants