ProgWebAv WebSocket

Objectif: implémenter un serveur de WebSocket.
Afin de rendre la chose ludique, cela sera un serveur WS pour un chat. Pour le client nous allons reprendre le code du TP5 du cours de base de programmation client .

1) Installation

Pour utiliser les WebSocket (WS) en PHP nous allons utiliser Ratchet. Commencez donc par installer Ratchet dans votre projet grâce au composer (à installer précédement pour ceux qui ne l'ont pas encore: composer). Installez simplement la librairie WebSocket avec la commande suivante:

composer require cboden/ratchet

Avec mac essayez la commande :


php composer.phar require cboden/ratchet

2) Création d’une commande pour l’artisan Laravel

Afin de lancer le serveur WS, une commande artisan serait pratique. Cela permettra ainsi d’accéder au framework Laravel depuis notre serveur WS. Commencez donc par créer une nouvelle commande pour l’artisan:

php artisan make:command ChatServer 

Le fichier de code pour votre nouvelle commande sera créé dans app/Console/Commands/ChatServer.php. Afin de la rendre accessible à l’artisan, il vous faut éditer le fichier app/Console/Kernel.php pour y ajouter sa référence dans le tableau des commandes artisans:

protected $commands = [
    Commands\ChatServer::class
];

Dans la commande, vous pouvez y spécifier sa syntaxte et une description. Le code à éxécuter pour cette commande se placera dans la méthode handle(). Essayez de mettre un simple echo “Hello World\n”; dans la méthode handle() et testez votre commande avec artisan (si vous avez spécifié "chat:server" comme syntaxe, sinon modifiez le nom):

php artisan chat:server 

Comme vous pouvez le lire dans la documentation, les commandes que vous créez pour l'artisan peuvent accepter des paramètres. Essayez donc d'ajouter un paramètre à votre commande pour que l'on puisse spécifier un port d'écoute pour le serveur de WebSocket. Testez à nouveau votre commande avec:

php artisan chat:server 8989

3) Création du serveur WS
Afin de bien organiser le code source, créez un dossier nommé Ws dans le dossier app de votre projet. Puis un dossier Controllers à l’intérieur du dossier Ws. Dans le dossier Ws créez un fichier de classe PHP nommé Router.php, puis copiez le code suivant:

<?php

namespace App\Ws;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Router implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage();
    }

    public function onOpen(ConnectionInterface $conn) {        
        $this->clients->attach($conn);
        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        $numRecv = count($this->clients) - 1;
        echo sprintf(
            "Connection %d sending message '%s' to %d others conn.\n"
            , $from->resourceId, $msg, $numRecv);
        foreach ($this->clients as $client) {
            if ($from !== $client) {                
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {        
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }
}

Enfin copiez l’exemple de WS disponible sur le site de Ratchet comme reste du code de cette classe. Modifiez votre commande chat:serve pour que sa méthode handle() lance le serveur WS du chat:


$server = \Ratchet\Server\IoServer::factory(
     new \Ratchet\Http\HttpServer(
         new \Ratchet\WebSocket\WsServer(
             new \App\Ws\Router()
         )
     )
     , $port, '0.0.0.0'
);
$server<run();

Puis démarrez votre serveur avec votre commande.

Testez si tout fonctionne bien en vous connectant au serveur WS dans une console de votre browser grâce au code javascript suivant:

var conn = new WebSocket('ws://localhost:8989'); //ou un autre port
conn.onopen = function(e) {
    console.log("Connection established!");
};
conn.onmessage = function(e) {
    console.log(e.data);
};

Puis envoyer un message WebSocket avec :

conn.send('Hello World!');

4) Un controller pour un WS ?

Le protocole WS ne spécifie pas comment l’information transmise doit être structurée, ni comment elle doit être dispatcher (router) coté serveur. Nous somme donc libre de choisir un langage de description ainsi qu’une méthode de dispatch efficace. Nous pourrions prendre une proposition de normalisation disponible comme WAMP (qui est d’ailleurs déjà implémentée dans Ratchet), mais pour un but pédagogique, nous allons créer notre propre méthode de dispatch. L’idée de la méthode est la suivante: chaque requête WS sera transmise en JSON. Ces requêtes devront être des objets JSON dont un des attributs porte le nom action. Lorsque la requête provient du client, les autres attributs seront les noms des paramètres à transmettre. Et lorsque la réponse vient du serveur, un unique paramètre en plus de action nommé data contenant les données. Exemple d’un message en provenance du client:

{
    'action' : 'send',
    'msg': 'Hello World'
}

Et un exemple de réponse du serveur (pour un broadcast à tous les clients):

{
    'action' : 'send',
    'data': {
        'email': 'foo@bar.com'
        'msg': 'Hello World' 
    }
}

Le code client comprend déjà cette structure JSON, mais il reste à l’implémenter coté serveur. Pour ce faire, créez un controller pour votre chat et essayer de trouver une méthode de routage vers les actions de ce controller depuis la méthode onMessage de votre serveur WS.

Les actions que vous devez router sont:

login: permet l’authentification de l’utilisateur (paramètres: email, password). Vous devez retourner à l’utilisateur nouvellement authentifié sur le chat le Json suivant:

{
        'action' : 'login',
        'data': 'login successfull'
}

Et à tous les utilisateurs authentifiés sur le chat le Json suivant:

 {
    'action' : 'connection',
    'data': {
        'email': 'email_ici'
        'id': id_du_user_nouvellement_connecte 
    }
}

send : permet l’envoi d’un message (paramètre: msg). Vous devez retourner à tous les utilisateurs authentifié sur le chat le Json suivant:

{
    'action' : 'send',
    'data': {
        'email': 'email_ici'
        'msg': 'msg_ici' 
    }
}

usersList : permet de recevoir la liste des utilisateurs connectés. Vous devez retourner au client qui l'a demandé la liste de tous les utilisateurs authentifiés sur le chat. La méthode enverra un message par utilisateur. Le message sera le Json suivant:

 {
    'action' : 'connection',
    'data': {
        'email': 'email_ici'
        'id': id_du_user_nouvellement_connecte 
    }
}

logout : déconnexion. Vous devez retourner à l’utilisateur qui se déconnecte le Json suivant:

 {
    'action' : 'logout',
    'data': 'logout successfull'
}

Et à tous les utilisateurs authentifiés sur le chat le Json suivant:

 {
    'action' : 'disconnection',
    'data': {
        'email': 'email_ici'
        'id': id_du_user
    }
}