Skip to content
This repository has been archived by the owner on Sep 20, 2021. It is now read-only.

Problem when the connection is closed to early for the client to know it #70

Open
shulard opened this issue Aug 2, 2016 · 5 comments
Open

Comments

@shulard
Copy link
Contributor

shulard commented Aug 2, 2016

Hello,

I'm currently writing a WebSocket daemon to notify users when some data are updating. I want to retrieve some context from the WebSocket connection so I use the socket url to pass parameters. For example: ws://127.0.0.1:8889/campaign/1/user/1

To check that URL contains valid informations, I wrote a simple onOpen handler on my server. If the check does not pass, I close the connection. The next time the client need to read through the socket, it's already closed and I got the following php error :

[ErrorException]
stream_socket_shutdown(): supplied resource is not a valid stream resource

Server code

$server = new Websocket\Server(
    new Socket\Server($url)
);
$server->getConnection()->setNodeName(UserNode::class);
$server->on('open', function() {
    $request = $bucket->getSource()->getRequest();
    if (!preg_match('#/campaign/([0-9]+)/user/([0-9]+)#', $request->getUrl(), $matches)) {
        $bucket->getSource()->send('Go away hacker !');
        $bucket->getSource()->close(Websocket\Connection::CLOSE_POLICY_ERROR);

        return;
    }
    try {
        $campaign = Campaign::findOrFail((int)$matches[1]);
        $user     = User::findOrFail((int)$matches[2]);
    } catch (\Exception $e) {
        $bucket->getSource()->send('Invalid User / Campaign given');
        $bucket->getSource()->close(Websocket\Connection::CLOSE_DATA_ERROR);
        return;
    }

    $node = $bucket->getSource()->getConnection()->getCurrentNode();
    $node->setUser($user);
    $node->setCampaign($campaign);
    $this->line(sprintf('server::onopen > %s: %s', $user->id, $campaign->name));

    $this->triggerFirstStep($node, $bucket->getSource());
});
$server->run();

Client code

$client = new Websocket\Client(
    new Socket\Client($url)
);
$client->getConnection()->setNodeName(UserNode::class);
$client->setHost($host);

$client->run();

If I gave a valid URL to the client, no problem but if I gave a wrong one, the connection is closed by the server and the client know it only the next time he try to read on it.

I've checked in library code and it seems that the getStream method on the socket connection return null (so the ErrorException thrown is just logic here...).

Is it the right way to handle that kind of verification ?

@Hywan
Copy link
Member

Hywan commented Aug 4, 2016

Hello,

Did you try to listen the close event on the client?
Also, a side-note, Websocket\Connection::close has a second argument for the “reason”, so:

$bucket->getSource()->close(
    Websocket\Connection::CLOSE_POLICY_ERROR,
    'Invalid user or campaign given.'
);

should do the trick and it saves one TCP message over the network.

Just curious: How did you implement the getRequest method?

@shulard
Copy link
Contributor Author

shulard commented Aug 4, 2016

About the getRequest method I found it in Websocket\Server implementation :

public function getRequest()

It allowed me to retrieve HTTP Request details from the incoming connection.

At the beginning I've used the second parameter for the "reason" on Websocket\Connection::close method and removed it I can't remember why 😄. Of course it's simpler and save a TCP message, thanks for the tip !

I already listen to close and error event on the client. The close event is called and it's when the client socket try to perform the Websocket\Client::close that the ErrorException is thrown.

The full error stack trace here :

[ErrorException]
stream_socket_shutdown(): supplied resource is not a valid stream resource

Exception trace:
 () at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/hoa/socket/Connection/Connection.php:337
 Illuminate\Foundation\Bootstrap\HandleExceptions->handleError() at n/a:n/a
 stream_socket_shutdown() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/hoa/socket/Connection/Connection.php:337
 Hoa\Socket\Connection\Connection->mute() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/hoa/websocket/Client.php:258
 Hoa\Websocket\Client->close() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/hoa/websocket/Connection.php:240
 Hoa\Websocket\Connection->_run() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/hoa/socket/Connection/Handler.php:195
 Hoa\Socket\Connection\Handler->run() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/hoa/websocket/Client.php:131
 Hoa\Websocket\Client->run() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/app/Console/Commands/Socket/Client.php:54
 App\Console\Commands\Socket\Client->handle() at n/a:n/a
 call_user_func_array() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/laravel/framework/src/Illuminate/Container/Container.php:507
 Illuminate\Container\Container->call() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/laravel/framework/src/Illuminate/Console/Command.php:169
 Illuminate\Console\Command->execute() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/symfony/console/Command/Command.php:256
 Symfony\Component\Console\Command\Command->run() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/laravel/framework/src/Illuminate/Console/Command.php:155
 Illuminate\Console\Command->run() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/symfony/console/Application.php:791
 Symfony\Component\Console\Application->doRunCommand() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/symfony/console/Application.php:186
 Symfony\Component\Console\Application->doRun() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/symfony/console/Application.php:117
 Symfony\Component\Console\Application->run() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:107
 Illuminate\Foundation\Console\Kernel->handle() at /Users/shulard/Sites/Clients/MonQuiz.fr/back/artisan:35

After some researchs I found that there are some operations performed on the socket stream inside Websocket\Client::close method. In my case, these operations are not allowed because the stream is already closed and doesn't exists anymore :

$connection->mute();
$connection->setStreamTimeout(0, 2 * 15000); // 2 * MLS (on FreeBSD)
$connection->read(1);

Each of these calls are throwing an exception because : supplied resource is not a valid stream resource

@Hywan
Copy link
Member

Hywan commented Aug 8, 2016

Hmm, so the connection can be disconnected at this particular place too. How do you feel trying a patch?

@Hywan
Copy link
Member

Hywan commented Aug 8, 2016

I guess this is related to #71. Can you reproduce your issue with this PR? If yes, then it does not fix your issue but this is related and I can update this PR.

@shulard
Copy link
Contributor Author

shulard commented Aug 8, 2016

Hello !

I think I can try a patch, I know that library code better now 😄. I'm gonna try to reproduce with the #71 PR and post results here.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests

2 participants