Курс Raku / Функційне, конкурентне, реактивне та веб-програмування / Веб-програмування / Основи Cro
WebSocket
Розмова через HTTP є одноразовою: клієнт питає, сервер відповідає, і з’єднання завершено. WebSocket підносить це до довготривалого двобічного каналу — будь-яка сторона може надіслати повідомлення будь-якої миті, а саме цього потребують чати, ігри та живі панелі. Cro підтримує WebSockets із коробки, і обробник WebSocket збудовано з реактивних засобів, які ви вже знаєте: потік повідомлень є потоком значень.
Ось крихітний сервер чату. Кожне повідомлення, яке надсилає будь-який клієнт, сервер переводить у верхній регістр і транслює всім приєднаним — тож те, що повертається, видимо не є тим, що виходило, і це доводить, що дані справді зробили повний оберт:
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 там закриває весь потік
з’єднання, відписуючи його від $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 | Другий маршрут →
💪 Або перейдіть одразу до вправ цього розділу.