Выпуск 26. Апрель 2015
← Анонс конференции YAPC::Russia 2015 | Содержание | Промисы в Perl 6 →Работа с WebSocket в Perl
Рассмотрены несколько подоходов при работе с технологией WebSocket из Perl
Технология WebSocket в современных браузерах уже широко поддерживается и используется во многих интернет-приложениях. В Perl поддержка WebSocket появилась еще на этапе разработки самого протокола во фреймворке Mojolicious. Затем появился модуль общего назначения Protocol::WebSocket и несколько оберток вокруг библиотек на других языках. На данный момент из-за сложности самого протокола в основном все приложения используют Protocol::WebSocket.
Что такое WebSocket?
Технология WebSocket позволяет установить двусторонний постоянный канал между клиентом и сервером используя HTTP-протокол. Вначале клиент посылает заголовок Upgrade
и некоторый набор специальных заголовков, сервер отвечает своим набором заголовков и соединение устанавливается. Далее данные упаковываются в пакеты и отправляются. Кроме пакетов с данными существуют управляющие пакеты, например, для закрытия соединения.
1/ /1.12:3:4: .5: = ; =6: ://.7--: ==8--: 13910/1.1 10111:12:13--: +=
Существует множество нюансов в зависимости от браузера, версии WebSocket, прокси-серверов, стоящих на пути запроса. Поэтому всегда стоит воспользоваться реализацией модуля Protocol::WebSocket, который обрабатывает все специальные случаи, скрывая подробности под простым интерфейсом.
Protocol::WebSocket позволяет написать как клиентскую, так и серверную часть. Рассмотрим низкоуровневую реализацию серверной части с помощью AnyEvent.
Вначале напишем html-файл. После подключения к серверу посылаем ему сообщение, а получив сообщение, выводим его в консоль.
<! >1<!>2< ="utf-8" />3<> </>4< ="javascript" ="text/javascript">5= new ('ws://localhost:3000');6. = ( ) {7.log('opened');8.send('echo');9};10. = ( ) { .log('closed') };11. = ( ) {12.log('message=' + . );13};14. = ( ) { .log('error') };15</>16<> </>
Затем напишем серверную часть:
#!/usr/bin/env perl1#!/usr/bin/env perl
23use ;4use ;56use :: ;7use :: ;89use :::::: ;10use :::: ;1112my $cv = -> ;1314my $hdl;1516:::: undef, 3000, sub {17my ($clsock, $host, $port) = @_;1819my $hs = ::::::->new;20my $frame = ::::->new;2122$hdl = ::->new( => $clsock);2324$hdl-> (25sub {
26my $hdl = shift;2728my $chunk = $hdl->{rbuf};29$hdl->{rbuf} = undef;3031if (!$hs-> ) {32$hs-> ($chunk);3334if ($hs-> ) {35$hdl-> ($hs-> );36return;
37}38}3940$frame-> ($chunk);4142while (my $message = $frame->next) {43$hdl-> ($frame->new($message)-> );44}45}46);47};4849$cv->wait;
Все можно сильно упростить, если приложение будет запускаться в PSGI-среде. Protocol::WebSocket позволяет получить все необходимые заговки напрямую из окружения PSGI, например:
#!/usr/bin/env perl1#!/usr/bin/env perl
23use ;4use ;56use :: ;7use :::::: ;8use :::: ;910my $psgi_app = sub {11my $env = shift;1213my $fh = $env->{'psgix.io'} or return [500, [], []];1415my $hs = ::::::-> ($env);16$hs-> ($fh) or return [400, [], [$hs-> ]];1718return sub {19my $respond = shift;2021my $h = ::->new( => $fh);22my $frame = ::::->new;2324$h-> ($hs-> );2526$h-> (sub {});27$h-> (28sub {
29$frame-> ($_[0]-> );3031while (my $message = $frame->next) {32$h-> (::::->new($message)-> );33}34}35);36};37};3839$psgi_app;
Запустив это приложение под Twiggy или Feersum или каким-либо другим PSGI-сервером на AnyEvent, получим аналогичное предыдущему примеру поведение.
Написание низкоуровневых WebSocket приложений может быть несколько утомительно, рассмотрим высокоуровный подход к написанию WebSocket-клиента. В модуле Protocol::WebSocket находится утилита wsconsole
, которая позволяет очень удобно тестировать или отлаживать WebSocket-серверы. Далее представлен ее упрощенный вид:
#!/usr/bin/env perl1#!/usr/bin/env perl
23use ;4use ;56use ;7use :: ;8use :: ;9use :::: ;1011my $cv = -> ;1213my $client = ::::->new( => 'ws://localhost:3000');14my $ws_handle;1516'localhost', '3000', sub {17my ($fh) = @_ or return $cv->send("Connect failed: $!");1819$ws_handle = ::->new(20=> $fh,21=> sub {22$cv->send;23},24=> sub {25$cv->send;26},27=> sub {28my ($handle) = @_;2930my $buf = delete $handle->{rbuf};3132$client->read($buf);33}34);3536$client-> (37write => sub {38my $client = shift;39my ($buf) = @_;4041$ws_handle-> ($buf);42}43);44$client-> (45read => sub {46my $self = shift;47my ($buf) = @_;4849print "message=$buf\n";50}51);52$client->connect;53};5455my $stdin = ::->new(56=> \*STDIN,57=> sub {58my $handle = shift;5960my $buf = delete $handle->{rbuf};6162$client->write($buf);63},64=> sub {65$client-> ;6667$ws_handle-> ;68$cv->send;69}70);7172$cv->wait;
В данном случае не нужно заботиться об обработке подключения, парсинге пакетов и прочего. Достаточно зарегистрировать нужные события и обработать их.
Сама утилита wsconsole
позволяет отправлять сообщения прямо из терминала, видеть полный формат пакетов, сообщать с какой версией подключаться. Так можно проверить, насколько хорошо и полно реализован WebSocket-сервер.
12>34[0000] 81 86 9 17 72 8 1 .... ... ....56^^^^^^^^^^
7<89[0000] 81 06 68 65 6 6 6 0 .. .
Когда нет WebSocket
Не каждый браузер поддерживает WebSocket, иногда они специально отключаются, но все же приложение должно как-то работать. Есть несколько библиотек, которые имитируя интерфейс WebSocket при отсутствии последних переключаются на другие способы двусторонней передачи данных.
WebSocketJS
WebSocketJS представляет собой реализацию WebSocket с помощью технологии Flash. Исходный код библиотеки доступен на GitHub. Для полноценной работы Flash необходим запуск специального Policy-сервера отдельно или же можно воспользоваться специальным inline-сервером Fliggy, который на Flash-специфичный запрос формирует нужный ответ, а в остальном работает как Twiggy.
Socket.IO
Socket.IO в зависимости от возможностей браузера использует различные подходы для реализации двусторонней связи: Polling, Streaming, Htmlfile и другие. Socket.IO в Perl реализован PSGI-приложением PocketIO. К сожалению, недавно выпущенная версия 1.0 не поддерживается PocketIO, в связи с сильными изменениями протокола. На сегодняшний день использование Socket.IO в Perl затруднительно.
SockJS
SockJS был написан как альтернатива Socket.IO. Данная библиотека также поддерживает несколько альтернативных WebSocket-технологий. Для Perl есть реализация SockJS-perl. Преимущество SockJS — в наличии удобных утилит для тестирования в независимости от языка реализации самого протокола.
Автор советует использовать именно эту библиотеку на сегодняшний день.
Альтернативы WebSocket
Когда нет необходимости в дуплексной связи, например, для уведомления пользователя или обновления новостной ленты, можно воспользоваться технологией Server Side Events или EventSource. EventSource работает поверх HTTP и не требует значительной переработки серверной части. Реализация EventSource крайне проста и занимает несколько десятков строк, в этом можно убедиться, посмотрев на исходный код Plack-реализации EventSource.
← Анонс конференции YAPC::Russia 2015 | Содержание | Промисы в Perl 6 →