Курс языка программирования Raku / Функциональное, конкурентное, реактивное и веб-программирование / Веб-программирование / Основы Cro

WebSocket

HTTP-разговор одноразов: клиент спросил, сервер ответил — и соединение закончено. WebSocket превращает его в долгоживущий двусторонний канал, где любая сторона может отправить сообщение в любой момент, — именно это нужно чатам, играм и живым панелям мониторинга. Cro поддерживает WebSocket из коробки, а обработчик WebSocket строится из уже знакомых вам реактивных инструментов: поток сообщений — это supply.

Вот крошечный чат-сервер. Каждое сообщение любого клиента сервер переводит в верхний регистр и рассылает всем подключённым, так что обратно приходит явно не то, что уходило, — доказательство того, что данные действительно проделали путь туда и обратно:

use Cro::HTTP::Router;
use Cro::HTTP::Router::WebSocket;
use Cro::HTTP::Server;

my $chat = Supplier.new;

my $application = route {
    get -> 'chat' {
        web-socket -> $incoming {
            supply {
                whenever $incoming -> $message {
                    $chat.emit(uc(await $message.body-text));
                    LAST { done }
                }
                whenever $chat -> $text {
                    emit $text;
                }
            }
        }
    }
}

my $server = Cro::HTTP::Server.new(
    :host('127.0.0.1'),
    :port(8080),
    :application($application),
);
$server.start;

say 'Chat server on ws://127.0.0.1:8080/chat — press Ctrl-C to stop';

react {
    whenever signal(SIGINT) {
        $server.stop;
        done;
    }
}

Посмотрите на блок web-socket — это реактивная часть курса в миниатюре. Каждый подключённый клиент получает supply с двумя whenever: один следит за $incoming, сообщениями, приходящими от этого клиента, переводит каждое в верхний регистр и проталкивает в общий поставщик $chat; другой следит за $chat и emit-ит каждый разосланный текст обратно клиенту. Один Supplier, к которому подключено каждое соединение, и есть вся чат-комната.

Маленький LAST { done } тут важен. Это фазер, с которым вы встречались у циклов; в блоке whenever он срабатывает, когда наблюдаемый поток заканчивается, то есть когда этот клиент отключается. Вызов done там закрывает весь supply соединения, отписывая его от $chat. Без него whenever $chat ушедшего клиента остался бы висеть, и следующая рассылка попыталась бы писать в закрытый сокет и напечатала бы ошибку на сервере.

Браузер общается с этим сервером несколькими строчками JavaScript, но точно так же может и другая программа на Raku — клиентскую сторону Cro тоже предоставляет:

use Cro::WebSocket::Client;

my $conn = await Cro::WebSocket::Client.connect('ws://127.0.0.1:8080/chat');

my $got = Promise.new;

$conn.messages.tap(-> $message {
    say 'Received: ', await $message.body-text;
    $got.keep;
});

say 'Sending:  Hello, chat!';
$conn.send('Hello, chat!');

await $got;
await $conn.close;

.send отправляет сообщение и тут же возвращает управление; ответ приходит позже, в другом потоке, в блок .tap. Поэтому основная ветвь программы должна подождать перед закрытием — и вместо того чтобы спать в надежде на лучшее, она дожидается Promise, который подключение исполняет в тот момент, когда приходит ответ: инструменты конкурентности из начала этой части занимаются ровно тем, для чего созданы.

Запустите сервер в одном терминале, а этот клиент — в другом. Клиент печатает обе стороны разговора:

Sending:  Hello, chat!
Received: HELLO, CHAT!

Ушедшее строчными вернулось заглавными: сообщение отправилось на сервер, было там преобразовано и вернулось обратно по тому же соединению. Вот он, двусторонний канал в действии. Запустите клиента сразу в нескольких терминалах — и каждое сообщение появится во всех: работающий чат меньше чем в тридцати строках Raku.

Этим примерам нужен установленный Cro (zef install cro).

Это подходящее место, чтобы завершить курс. Сервис, вызывающий его клиент и живой двусторонний канал между ними опираются на всё, что было прежде, — объекты и исключения, регулярные выражения и грамматики, функциональные, конкурентные и реактивные инструменты этой части, — а код при этом остаётся коротким и говорит ровно то, что вы имеете в виду. От байтов в сокете до чат-комнаты на один экран кода — вот что такое Raku.

Навигация по курсу

Запрос к публичному API   |   Второй маршрут


💪 Или перейдите сразу к упражнениям этого раздела.