Курс языка программирования 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 | Второй маршрут →
💪 Или перейдите сразу к
упражнениям этого раздела.