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

Actualize examples #42

Closed Alarmfifa closed 11 years ago

Alarmfifa commented 11 years ago

Hello guys. Please, actualize your examples.

It doesn't work, because it tries to register a demo application: $server->registerApplication('demo', \Wrench\Application\DemoApplication::getInstance()); But there are no demo application in the folder: $ ls Application.php EchoApplication.php ServerTimeApplication.php

There are no jsmaker.php (for coffescript convertion) and js files (json2 and jquery). But it isn't main problem. I can convert coffescript's files and add missing js. But it tries to connect to serverUrl = 'ws://127.0.0.1:8000/demo'; Could you help me and show a demo server script which I need to launch for this examples?

In summary I didn't find a good working example, therefore the understanding of your websocket realization is extremely difficult =(

Rupert-RR commented 11 years ago

The examples certainly need updating - sorry about that. I have never got SSL to work, despite spending a lot of time trying to find the cause - maybe one day I will spend more time on it - in the meantime I installed stunnel on the server and had it pipe from an SSL enabled port to an unencrypted one only reachable on localhost, and that works fine.

As for an example, ignore the demo application and use the existing ones.

$server = new \Wrench\Server('ws://localhost:8000', array( )); $server->registerApplication('echo', new \Wrench\Application\EchoApplication()); $server->run();

and you should be able to connect from your browser to ws://localhost:8000/echo and the server will echo back anything you send to it.

Alarmfifa commented 11 years ago

Thanks for your quick answer =) Yep, I've tried to start a server as you told above and it works with coffeescript example. But when I tried to launch echo-server at first, I used this instruction and declared server like:

use Wrench\BasicServer;
$server = new BasicServer('ws://localhost:8000');

Unfortunately It doesn't work with echo application =( By the way, I need some more functionality than echo-app can give me. That's why I tried to launch your coffescript example. I want to handle clients connection (with different application url), collect all clients to array and send some message to each of them. I would like to see more examples or more rich documentation.

Rupert-RR commented 11 years ago

I haven't really got much time to look into things at the moment, but maybe use Server instead of BasicServer. Obviously the EchoApplication doesn't have much in the way of functionality - it is just a demo - but it does at least show you how to respond to client input events. There won't be anything more in the way of functionality simply because that part is up to you - the Wrench library handles the WebSockets protocol part for you, and you choose what you want to do with this two-way communication. For you example, you don't make it clear whether you used the code I gave or not - once you have a Server instance you need to register an application. An application basically equates to the url path, so you can have multiple "applications" running on the one "server" (eg an 'echo' application at ws://localhost:8000/echo, a 'chat' application at ws://localhost:8000/chat etc). I have a much more complicated system running on top of this (with some slight modifications to use request cookies and parameters), so it does work. I should add that I am not the author - I have just spent some time understanding and using this.

Alarmfifa commented 11 years ago

but maybe use Server instead of BasicServer.

I thought the same =) That's why I wrote about it. May be the documentation needs to update too? =)

it is just a demo - but it does at least show you how to respond to client input events. There won't be anything more in the way of functionality simply because that part is up to you - the Wrench library handles the WebSockets protocol part for you, and you choose what you want to do with this two-way communication.

I understand. But as a newbie in this library I want to find more examples or rich documentation (not methods listing like there ). For example I have a script file which includes an application file and starts a server. Where do I need to place a business logic of my daemon? How can I use API or call library methods? This info I would like to find =) But as far as I understand, the best way is explore code =)

Anyway, thanks!

Rupert-RR commented 11 years ago

For your business logic, like I said you'll need an application for each url end point you want. Basically you should write a class for each, extending Application and deal with all the communication logic in the Application methods - onConnect is called every time a client makes a new connection to the server (so you'll probably want to keep an array of client connections for those who are connected); onData is called every time the server receives data from the client, so you can deal with communication from the client there; onDisconnect is called every time a client disconnects...; and onUpdate is called every time the sockets listener loops, to allow you to perhaps check conditions on the server to determine whether you need to send any messages to the clients. Depending on how complicated your system is, you'll probably want to add some kind of RPC protocol over the websockets protocol - I implemented JSONRPC. Hope this helps.

dmolsen commented 11 years ago

Not saying I've done this correctly but I seem to have a working example that you could pick apart if you like. The first one is a service that allows the syncing of browsing across multiple browsers or devices so it uses onData (hence the use of 0.0.0.0):

The second is just a simple way for making the browser auto-reload when files are updated so it uses onUpdate (my app is a simple static site generator):

Rupert-RR commented 11 years ago

I'll have a look a bit later and get back to you.

Rupert-RR commented 11 years ago

Ok - what you've done seems to be fine to me - I think that should work. One thing that you don't seem to be considering (and this maybe on purpose) is authentication - any client can connect to your websockets regardless of whether they are using your web page or not. If this is a concern, then you will need to deal with authentication somehow.

dmolsen commented 11 years ago

I apologize. I didn't mean to insinuate that you had to check my work :) I was just offering it up as a working example in case you or @Alarmfifa still needed one.

The services are meant to run locally on a laptop. One could connect a device for testing across a local WiFi network but that's supposed to be the extent of it. Thanks for the tip though and I'll keep it in the back of my head in case of here of anyone throwing the project up on the web :)

Rupert-RR commented 11 years ago

My apologies - I didn't pay attention to who was posting and just assumed that it was @Alarmfifa!

Alarmfifa commented 11 years ago

@dmolsen , @Rupert-RR thanks for your advice and examples. Also, I found a previous version of Wrench lib with working examples here. So, now I am on the way to solve my task. If it is useful, I will make an example and send pull request to this repo.

Alarmfifa commented 11 years ago

@dmolsen , @Rupert-RR you are guru of this lib. I've made a websocket server and now it seems working. Thanks again!

Now I want to send a message from php script to websocket server, as I prepared before (using Wrench). I'm trying to create this websocket client without results. Can I solve my task using this lib?

Rupert-RR commented 11 years ago

Do you mean that you are also writing a web sockets client using PHP, or just that you are using PHP to generate a web page which will be a Javascript web sockets client? If it's the former, then I think you might be out of luck (without actually having verified) - I think the client part of this library was previously deprecated - if it's the latter then you just need to insert the appropriate Javascript on your web page:

var ws = new WebSocket('yoururl');
if (!ws) {
    console.log('websocket didnt work');
}
ws.onopen = function(msg) {
    console.log('opening');
};
ws.onmessage = function(msg) {
    console.log('message' + msg);
};```
Alarmfifa commented 11 years ago

Unfortunately I need the first variant. I want to use script from CLI which will send massages to websocket server.

Rupert-RR commented 11 years ago

May I enquire as to why you need to use web sockets then? Surely if both your client and server are PHP scripts you could just use normal PHP sockets or streams (which this library is based on anyway - simply adding in the overhead of the web sockets protocol)?

Alarmfifa commented 11 years ago

No, I have a big strange structure =) I have several web-clients (browsers) which connect (via javascript) with websocket server (wrench). I want to send any data/notification from php script (for example it will execute by cron regularly) to clients (browsers). So my php script needs to send websocket to our wrench sebsocket server.

Rupert-RR commented 11 years ago

Is the CRON job not running on the same server as the web sockets server then? If it is, then it might be better putting the CRON script logic into the web sockets server so that the onUpdate method checks the current time and executes the CRON script logic if needed. In all cases, it seems to me that the CRON job would not be a proper web sockets client (it would only be connecting punctually and only sending data rather than actual two-way communication), so in terms of logic it would probably be better if this CRON job updated a file or database to which the web sockets server has access, and the web sockets server checks that file/database for changes on each run loop and broadcasts any changes to its clients

dmolsen commented 11 years ago

My project works in the way that @Rupert-RR describes. onupdate checks a file which contains a time stamp. The time stamp gets updated when someone runs a command line script. If the time stamp changes the web socket server pings all connected devices so they reload their web pages. It's a fairly painless implementation.

Alarmfifa commented 11 years ago

Guys, unfortunately it is no so easy. =( I can't delegate all business logic to the server script. Several different php scripts following their logic prepare a text for notification and need to send it via websocket script. Moreover, server is situated on another physical machine than my scripts. If I can connect php scripts with websocket server using other protocol it will be ok. Can I tune wrench to catch another connections (not only websocket)?

Rupert-RR commented 11 years ago

Hmmm... problematic. Of course it should be possible to modify Wrench to accept other protocols on the same socket, but I suspect it would be a little complicated. It might be worth trying to find a different PHP library that implements a web sockets client for this - you could still keep Wrench on the server if you wanted - it shouldn't matter which client library connects to it.

Alarmfifa commented 11 years ago

=( it's fail. I decided, that I can solve all my problems using this powerful library... Thanks!

Alarmfifa commented 11 years ago

Hi guys, it's me again =) I'm rummaging in the code and looking on Client.php now. It seems not bad, and it works somehow. But I faced a new problem. My server is fallen down by socket->disconnect() from php client. Moreover server doesn't show any errors or exceptions. It just stops. =( I tried with a simple php interpretation (open socket, write a header and etc), and server script failed too ((

Did anybody face with the same behaviour? Or could you give an advice how to catch this fall?

Rupert-RR commented 11 years ago

So you're saying that on one server you have a Wrench web sockets server running, and on another physical machine you are running a Wrench web sockets client which is connecting to the server, and when the client disconnects this causes the server to stop? If that is indeed the case then there is obviously a problem with the server (perhaps the client part doesn't use the latest version of the protocol and the server handles this badly), which is something we would need to address. If this is the problem then please paste the code you are using for the client side here (I assume you haven't actually modified any of the library files server-side).

Alarmfifa commented 11 years ago

Now I'm testing this on my machine, but yep you are right about the structure. I've changed a one thing in Protocol.php (I added a support of query part in the header) but it doesn't relate with my problem. I can undo it =) So, my server script is a simple example:

<?php
require_once 'lib/SplClassLoader.php';
$classLoader = new \SplClassLoader('Wrench', __DIR__ . '/lib');
$classLoader->register();
$server = new \Wrench\Server('ws://localhost:8000');
$server->registerApplication('websocket', new \Wrench\Application\EchoApplication());
$server->run();
?>

My client script:

<?php
require_once 'wrench/lib/SplClassLoader.php';
$libpath = 'wrench';

$classLoader = new \SplClassLoader('Wrench', $libpath . '/lib');
$classLoader->register();
$client = new \Wrench\Client('ws://localhost:8000/websocket?act=send', 'http://localhost');

$client->connect();
// echo "\nconnect=".$client->isConnected();
// $ret = $client->sendData('{"action":[""],"data":""}');
// echo "\nreturn=".$ret;
sleep(3);
$client->disconnect();
?>
Rupert-RR commented 11 years ago

I'll try and have a look at that a little bit later. In the mean time, if it is any help, have a look at the branch that I have just pushed to my repository - it contains server-side functionality for getting both the HTTP headers and the query parameters that come with the initial web sockets request (before upgrading), which I used in one of my projects to help with authentication - it may be useful to you if you are using query parameters. Be aware that while it works for me I have not yet had time to work out whether it is implemented in the best way so far, which is why I have not merged it into the library as it is.

Alarmfifa commented 11 years ago

I found a problem! =) In Connection.php there is suppressed error message. When I removed symbol @, I got:

Fatal error: Call to undefined function Wrench\Socket\socket_last_error()

The reason of it was my php.in configuration. I uncommented this line:

extension=sockets.so

So, now my server doesn't fall down, but writes an exception:

notice: Wrench\ConnectionManager: Client connection closed: exception 'Wrench\Exception\CloseException' with message 'Error reading data from socket: Unknown error' in wrench/lib/Wrench/Connection.php:405

When you disconect from browser you send a special message, which catched here. But when my php script closes the socket it sends nothing (empty message). Server doesn't understand, that it is a simple disconnection and sends exception. I think it isn't right behaviour.

Does somebody maintain this lib? =)

Rupert-RR commented 11 years ago

Glad you found it - I certainly wouldn't have been able to help then! ;) From what I understand from the RFC, a client must send a CLOSE frame to terminate a connection, so if the client simply closes the connection without sending the CLOSE frame then that is an abnormal disconnection. In this case it seems to me that you are right in unsuppressing the socket error, but the result that this gives (throwing an exception) is also correct - an abnormal disconnection probably should trigger an exception. I think (and this is certainly how I do it) that you should probably have all your web server logic in a try... catch block anyway, since for the most part it is unlikely that you would want the server simply to stop on an exception.

As for maintaining this library, it is not mine, and has passed through several hands I believe. However, I have been given access to the repository, so I can (and do intend to) update it. Unfortunately, at the moment, I am in the middle of several projects, so cannot devote any time to it.

Alarmfifa commented 11 years ago

Thanks =) I'm continuing investigation. I tried to find any solution about close frame, but without success =( php just closes connection. Moreover, I sniffed traffic via wireshark and if you close a client connection from wrench server ($client->close();), server won't send this frame too. It works only with browsers.

Rupert-RR commented 11 years ago

If the server is not sending a CLOSE frame on a clean server disconnect then that would certainly seem to be a bug. Would you open a separate issue for that, and I'll have a look at it at some stage.

dominics commented 11 years ago

@Rupert-RR Thanks for your continuing awesome help.

@Alarmfifa Just wading in here to say that no, as you can see, Wrench\Client doesn't support proper close frame handling. (And php-websockets, the library that became Wrench, didn't for the client side, either). The server, however, should. The contract is:

    /**
     * Closes the connection according to the WebSocket protocol
     *
     * If an endpoint receives a Close frame and that endpoint did not
     * previously send a Close frame, the endpoint MUST send a Close frame
     * in response.  It SHOULD do so as soon as is practical.  An endpoint
     * MAY delay sending a close frame until its current message is sent
     * (for instance, if the majority of a fragmented message is already
     * sent, an endpoint MAY send the remaining fragments before sending a
     * Close frame).  However, there is no guarantee that the endpoint which
     * has already sent a Close frame will continue to process data.

     * After both sending and receiving a close message, an endpoint
     * considers the WebSocket connection closed, and MUST close the
     * underlying TCP connection.  The server MUST close the underlying TCP
     * connection immediately; the client SHOULD wait for the server to
     * close the connection but MAY close the connection at any time after
     * sending and receiving a close message, e.g. if it has not received a
     * TCP close from the server in a reasonable time period.
     *
     * @param int|Exception $statusCode
     * @return boolean
     */
    public function close($code = Protocol::CLOSE_NORMAL)

Please let us know if that's not working.

Pull requests are very welcome, and will be reviewed and merged promptly (I can promise that much)

Alarmfifa commented 11 years ago

I was wrong. I looked more careful on the network traffic and it seems to be working correctly in server case. As you said above a server created a closing frame here. If we want to send CLOSE_NORMAL it assembles the following frame: 03:e8:6e:6f:72:6d:61:6c:20:63:6c:6f:73:65. This frame contains a constant 03:e8 = 1000 = CLOSE_NORMAL and a message 6e:6f:72:6d:61:6c:20:63:6c:6f:73:65 = normal close. But if we send this frame from the client script it isn't detected as close by server here. That's why I wrote about unsupported 'close' messages. I sniffed a traffic from a web-browser again and found, that it sent different messages, but it had a common begining part: 88:80:b0:b8:79:47 88:80:e2:71:a4:d1 88:80:d6:75:74:74 I added a simple method to Client.php class:

public function disconnect()
{       
    $code = Protocol::CLOSE_NORMAL;
    $body = pack('n', 0x8880) . Protocol::$closeReasons[$code];
        $this->socket->send($body);

        $this->socket->disconnect();
}

And now it works correctly! Wrench server detects this message as closing. I know, that it's only draft or example, but it would be perfectly to implement this behaviour in Client class.

Rupert-RR commented 11 years ago

As @dominics said, pull requests are welcome! If you can update the client part of the library to make it work as it should, then I'm sure @dominics will review and merge it (as long as the code is consistent with the library, and works ;) !), and if he can't then I will also be happy to whenever I get some time (unfortunately not right now).

Alarmfifa commented 11 years ago

@dmolsen My initial post was about examples and documentation. I think you closed this issue to early. I can try to edit existing or create a new simple example and make a pull request, if it is useful.

Alarmfifa commented 11 years ago

@Rupert-RR thanks again. Unfortunately my code is not so careful and well thought =( Look at Protocol.php. There are a lot of different constants, rfc support and other awesome things. It is very powerful lib. If I make a pull request with method that I showed above, it will be unprofessional.