Rachet, websocket, comment lancer le serveur ?

Avatar du membre
Mammouth du PHP | 1564 Messages

12 déc. 2023, 12:35

Tout nouveau dans le monde du websocket, je tente de lancer le serveur websocket avec la librairie Rachet.

J'ai installé Composer, mis en place Rachet et ses dépendances.

Je tente maintenant de lancer bin/chat-server.php.

Quand je le lance j'ai l'erreur Uncaught InvalidArgumentException: Given URI "tcp://example.com:8080" does not contain a valid host IP (EINVAL). (example.com est bien sûr remplacé par mon domaine)

Vu le message, je me doute bien qu'il faille une IP à la place d'un nom de domaine, le problème étant que je suis un serveur mutualisé, j'ai mis l'adresse IP du serveur mutualisé mais il ne veut pas non plus. je suis chez OVH.

Voilà mon bin/chat-server.php :
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Chat;

		
		require_once dirname(__DIR__) . '/vendor/autoload.php';

    $server = IoServer::factory(
        new HttpServer(
            new WsServer(
                new Chat()
            )
        ),
        8080,
        'example.com'
    );

    $server->run();
Et mon src/Chat.php :
<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Chat implements MessageComponentInterface {
    protected $clients;

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

    public function onOpen(ConnectionInterface $conn) {
        // Store the new connection to send messages to later
        $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 other connection%s' . "\n"
            , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');

        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // The sender is not the receiver, send to each client connected
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {
        // The connection is closed, remove it, as we can no longer send it messages
        $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();
    }
}

Avatar du membre
Mammouth du PHP | 1609 Messages

13 déc. 2023, 17:44

Salut two3d, je connais pas du tout mais ça devrait pas être du localhost ? en tout cas en regardant vite fait ça semble planter dans React\Socket\Server le test à la ligne 156.
if (false === filter_var(trim($parts['host'], '[]'), FILTER_VALIDATE_IP)) {
    throw new InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP');
}
Tu peux déjà vérifier par la. Avec une ip le filtre devrait passer non ?
Développeur web depuis + de 20 ans

Avatar du membre
Mammouth du PHP | 1564 Messages

13 déc. 2023, 18:19

Salut Saian, merci pour ton aide.

Je me dis que localhost est pour des tests en local, n'est ce pas ?

Sinon, je veux pas réinventer la roue, mais peut on le faire nous même sans que ce soit complexe ? J'ai regardé les sockets PHP. Utilise-tu les websockets ? Si oui, quelle libraire ?

ynx
Mammouth du PHP | 586 Messages

14 déc. 2023, 14:39

Bonjour,

Le 3ème paramètre de IoServer::factory est l'adresse ip du socket client.
La valeur par défaut est 0.0.0.0 pour recevoir les connexions depuis n'importe quelle adresse.
https://github.com/ratchetphp/Ratchet/b ... er.php#L54

L'utilisation de Ratchet sera surement plus simple et plus sûr que d'implémenter soi-même les WS en php.

Une alternative plus moderne aux websockets sont les Server-Sent Events ainsi que le protocole Mercure (qui est basé sur les SSE). Pas eu l'occasion de tester mais j'avais vu 2 conférences intéressantes à ce sujet sur la chaine de l'AFUP :
https://afup.org/talks/3077-mercure-et- ... temps-reel
https://afup.org/talks/3541-propulser-d ... hp-en-2020

Avatar du membre
Mammouth du PHP | 1564 Messages

14 déc. 2023, 14:52

Merci ynx pour ton aide.

J'ai testé les Server Sent Events, très simple à mettre en œuvre mais ça fonctionne que dans une direction (serveur > client). On ne peut pas envoyer d'évènements du client vers le serveur.

Je crois comprendre qu'on pourrait se connecter à n'importe quelle IP mais à nous de sécuriser les échanges via une clé secrète dans l'en-tête de nos échanges, via Sec-WebSocket-Key ?

ynx
Mammouth du PHP | 586 Messages

15 déc. 2023, 13:37

Oui les SSE ne fonctionnent que dans le sens serveur > client, http lui est un protocole client > serveur.
Tu peux toujours envoyer une requête http depuis le client vers le serveur (via fetch en javascript par exemple).

Sec-WebSocket-Key est une entête utilisée par le client et le serveur pour établir la connexion ws mais ne concerne pas la sécurité ou l'authentification.

WebSocket est un simple protocole qui ne gère pas l'authentification.
Pour analogie, ton serveur web http répond par défaut à toutes les connexions pour la partie publique de ton site. Pour les parties privées (admin), en http ou ws il faut implémenter une authentification coté application (via les sessions PHP, un token JWT, etc).

La sécurité des WS est un long sujet que je ne maitrise pas, mais la base est similaire au protocole http : utiliser WebSocket Secure (wss) pour chiffrer les données (comme https), filtrer les entrées et sorties pour éviter les injections, contrôler l'origine pour contrer les attaques Cross-Site, etc.

Concernant ce dernier point, Ratchet propose justement le composant OriginCheck à utiliser pour créer ton serveur afin de restreindre les connexions uniquement depuis ton domaine : http://socketo.me/docs/origin

Avatar du membre
Mammouth du PHP | 1564 Messages

15 déc. 2023, 13:56

Je te remercie pour ces précisions.

J'utilise déjà XHR pour les requêtes clients > server avec setInterval() mais je pense que c'est plus gourmand en ressource que les websockets car il doit faire une requête HTTP + connexion à la BDD à chaque fois. ;)

ynx
Mammouth du PHP | 586 Messages

15 déc. 2023, 14:57

Oui le polling (une requête ajax régulière) est la technique la plus gourmande.
Le long polling est un peu moins gourmand : https://javascript.info/long-polling
Les SSE sont justement là pour éviter l'utilisation du polling ou long polling : c'est le serveur qui notifie le client et pas le client qui interroge régulièrement le serveur.
La conférence "Propulser du temps réel avec PHP" explique et compare ces différentes techniques.

La technique la plus adaptée va dépendre du contexte.
Si les push serveur > client sont rares, le long polling peut être suffisant.
Si les push serveur > client sont fréquent et pas ou peu de requête client > serveur, les SSE sont recommandées.
Pour une connexion bidirectionnelle, les websockets sont plus adaptés.

Une application peut utiliser plusieurs techniques : sur une boutique en ligne, on pourrait par exemple utiliser les SSE pour mettre à jour les quantités de stock et utiliser les websockets pour intégrer un chat.