varspool / Wrench

A simple PHP WebSocket implementation for PHP 7.1
Do What The F*ck You Want To Public License
596 stars 210 forks source link

Use of metadata['unread_bytes'] in Socket.php may cause a receive() call to return too early #107

Open sylbru opened 7 years ago

sylbru commented 7 years ago

I'm trying this library and using version 2 because the application I'm working on is written for PHP 5.4 (I know...), and while the move to PHP7 is planned, it's less urgent than my need to integrate WebSockets.

After spending a lot of time on trying to make all of this work, I stumbled on a weird issue where the script crashed because of this:

warning: Wrench\ConnectionManager: Wrong input arguments: exception 'InvalidArgumentException' with message 'Invalid request line' The stack trace below this message showed that the onData method was called with the string "G" as the $data argument. I noticed the cause of this was that the Socket::receive() method returned prematurely because of the 'unread_bytes' value of $metadata being equal to 0 at that point.

Not knowing much about this, but reading in the PHP documentation for stream_get_meta_data() that "You shouldn't use this value in a script," I tried commenting out this whole if ($metadata && isset($metadata['unread_bytes'])) block.

And to my surprise, it worked flawlessly, at least for now. I can now connect to the server, send and receive messages.

Is that a bug, of simply me using the library the wrong way? Is this method (removing the block) a proper patch or will it likely cause other issues?

dominics commented 7 years ago

If it still passes the tests, and works in functional/manual testing, I'd be more than happy to consider a change. Certainly your fix seems reasonable; if we can't justify the use of unread_bytes with a test or documentation it shouldn't be there.

A lot of the weird stuff going on in the stream handling is cargo-culted or dates from the original implementation.

nexen2 commented 7 years ago

You was lucky to send a message packet of bytes count equal to inner buffer length, so 'unread_bytes' value was zero.

I'll try to find out a solution. Just some free time needed.

martijnpolak commented 7 years ago

I have the exact same issue. The probleem only seems to occur for me when using SSL; for some reason when using SSL fread only reads 1 byte at a time and ignores the $length parameter, whereas when we don't use SSL it reads the entire $length correctly. Then unread_bytes is always 0 causing the loop to break prematurely.

If I comment out the entire metadata section it works. Note that this does require the "Workaround Chrome behavior" around line 302, otherwise the loop would still break prematurely (because strlen($result) != $length). Because of this comment I did try Firefox but it still ignored $length.

Would it be considered safe if we remove the metadata section for the time being? It seems like a fallback to prevent getting stuck in a loop, though feof() should suffice for this.

nexen2 commented 7 years ago

All people who had problems connected with "Chrome behavior" or 'unread_bytes' check: plz download and try commit "82811a9e197b4ff11cccaba62e88c28ecfd907c6". Tested with SSL, both Chrome and Firefox. I hope all issues were solved.

Your reviews will be welcomed.

martijnpolak commented 7 years ago

Looking at it (https://github.com/varspool/Wrench/commit/82811a9e197b4ff11cccaba62e88c28ecfd907c6) it wont fix my problem.

nexen2 commented 7 years ago

unread_bytes does not reflect real unread data length, just part in the system cache. Real code change is just check it for > 0, without using this value directly. So I don't mind delete this check. May be its a wild guess by i think it is correct now.

Strange behavior about Chrome. Can you describe your environment such like OS, php build, Chrome version? BTW how did you tried to connect socket, directly or apache/nginx proxy?

martijnpolak commented 7 years ago

Alright, but mind you my problem has nothing to do with Chrome, I'm just calling it the "Chrome fix" because thats what it says in the code comment (line 302 "Workaround Chrome behaviour (still needed?)").

What I'm doing is:

Now after this the following happens:

I have implemented a custom socket class in which I've removed the section and it seems to work just fine :)