@aanoaa - 홍형석, 사당동 펠프스, github:aanoaa
Mojolicious은 인기있는 펄의 경량 웹 프레임워크입니다. 경량의 MVC 프레임워크임에도 불구하고 HTTP 클라이언트 및 서버의 거의 풀 스택을 구현한 웹 프레임워크로 지원하지 못하는 기능을 찾기가 더 어려울 정도인 잘 만들어진 모듈입니다. 웹소켓 역시 대표적인 예로 Mojolicious는 이 웹소켓을 아주 잘 지원합니다. 실시간으로 상태를 갱신한다던가 등의 동작을 단순 HTTP만으로 구현하려면 자바스크립트 및 웹응용의 컨트롤러에서 처리해야 할 내용이 꽤 많죠. 이번 기사에서는 Mojolicious에서 손쉽게 웹소켓을 다루는 방법을 소개합니다.
필요한 모듈은 다음과 같습니다.
Mojo::Redis2 모듈을 사용하려면 Redis 서버를 설치해야 합니다. 데비안 계열의 리눅스를 사용하고 있다면 다음 명령을 이용해서 패키지를 설치합니다.
$ sudo apt-get install redis-server
직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.
$ sudo cpan Mojolicious Mojo::Redis2
사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.
$ cpan Mojolicious Mojo::Redis2
Mojolicious 저장소의 위키에는 유용한 정보가 많은데, 그 중 Writing websocket chat using Mojolicious Lite 문서를 조금 변경해 보았습니다.
#!/usr/bin/env perl use Mojolicious::Lite; use Time::HiRes 'time'; get '/' => 'index'; my %clients; websocket '/echo' => sub { my $self = shift; my $log = $self->app->log; my $id = time; $clients{$id} = $self->tx; $log->debug('[ws] client connected'); $self->on( message => sub { my ( $self, $msg ) = @_; $log->debug("[ws] < $msg"); for my $key ( keys %clients ) { $clients{$key}->send($msg); $log->debug("[ws] > ($id) $msg"); } } ); $self->on( finish => sub { $log->debug('[ws] client disconnected'); delete $clients{$id}; } ); }; app->start; __DATA__ @@ index.html.ep <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Echo client</title> <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script> <script> $(function() { var sock; var port = location.port; sock = new WebSocket('ws://localhost:' + port + '/echo'); sock.onopen = function(e) { console.log('Connected'); } sock.onmessage = function(e) { $('<p>' + e.data + '</p>').appendTo('#msg'); }; $('#txt').keydown(function (e) { $this = $(this) if (e.keyCode == 13 && $this.val()) { sock.send($this.val()); $this.val(''); } }); }); </script> </head> <body> <h1>Mojolicious + WebSocket</h1> <div id="msg"> </div> <div> <input id="txt" type="text" /> </div> </body> </html>
정말 간단한 예제죠? 놀랍지만 이 짧은 코드가 웹소켓 채팅 서버로써의 최소한의 코드인 셈입니다. Mojolicious의 모든 기능을 지원하려면 morbo나 hypnotoad로 작성한 웹 응용을 실행해야 한다는 점에 유의하세요. Starman이나 Starlet으로는 Mojolicious의 웹소켓 기능이 제대로 동작하지 않습니다.
웹소켓 서버를 구동하기 위해 명령줄에서 다음 명령을 실행하세요.
$ morbo echo
앞서 살펴본 예제는 마치 잘 동작하는 것처럼 보이지만 사실 서버로 띄운 웹 응용이 하나일 때, 즉 워커 프로세스가 하나 일 때만 정상적으로 동작합니다. morbo는 하나의 워커로 동작하는 비동기 서버이기 때문에 문제가 없지만 hypnotoad처럼 여러개의 워커를 띄울 수 있는 비동기 서버일 경우 메세지를 받은 워커에 연결된 클라이언트에게만 전달하기 때문입니다.
이런 자원 공유 문제를 해결하는 여러가지 방법이 있겠지만, 그 중 Redis의 Pub/Sub 모델을 활용하면 너무나도 간단하게 문제를 해결할 수 있습니다. 메세지를 받은 워커가 출판(publish)하고 이를 구독(subscribe)하는 워커가 메세지를 받아서 연결되어있는 클라이언트에게 전달하면 되겠죠!
Redis를 사용한 예제는 다음과 같습니다.
여러 개의 사용하기 위해 hypnotoad
관련 설정에서 띄울 워커 개수를 5개를 설정합니다.
__DATA__
섹션은 앞선 예제와 동일하므로 생략합니다.
#!/usr/bin/env perl use Mojolicious::Lite; use Mojo::Redis2; use Scalar::Util; use Time::HiRes 'time'; my $config = plugin 'Config' => { default => { hypnotoad => { listen => [ "http://*:5000" ], workers => 5, }, }, }; my $REDIS_CHANNEL = 'echo'; helper redis => sub { shift->stash->{redis} ||= Mojo::Redis2->new; }; get '/' => 'index'; websocket '/echo' => sub { my $self = shift; my $log = $self->app->log; my $name = time; $log->debug('[ws] client connected'); $self->inactivity_timeout(60); # 60 seconds Scalar::Util::weaken($self); $self->on( message => sub { my ( $self, $msg ) = @_; $log->debug("[ws] < $msg"); $self->redis->publish( $REDIS_CHANNEL => $msg ); } ); $self->on( finish => sub { my ( $self, $code, $reason ) = @_; $log->debug("[ws] client disconnected with status $code"); delete $self->stash->{redis}; } ); $self->redis->on( message => sub { my ( $redis, $message, $ch ) = @_; return if $ch ne $REDIS_CHANNEL; return unless $self; $log->debug("[ws] > ($name) $message"); $self->send($message); } ); $self->redis->subscribe( $REDIS_CHANNEL => sub { my ( $redis, $err ) = @_; $log->error("[REDIS ERROR] subscribe error: $err") if $err; } ); }; app->start;
웹소켓 서버를 구동하기 위해 명령줄에서 다음 명령을 실행하세요.
$ hypnotoad -f echo-redis
웹소켓과 Redis에 대한 자세한 부분을 설명한 것은 아닙니다만, 펄을 사용하면 이런 느낌으로, 이 정도의 코드로 웹에서 비동기 프로그램이 가능하고, 골치아픈 자원 공유의 문제를 손쉽게 해결할 수 있다는 것을 이해하기만 해도 큰 수확이라고 생각합니다. 묵묵히 펄을 사용해서 웹 프로그래밍을 하는 몽거스 분들에게 조금이나마 도움이 되었으면 합니다. :)
X-mas tree
& Llama
ASCII Art by ASCII Art Farts.
Computer ASCII Art by Chris.com.
Font ASCII Art by TAAG.
Artwork by
Inkyung Park
& Keedi Kim.
Designed by
Hojung Youn
& Keedi Kim.
Articles by
Seoul Perl Mongers.
Edited by
Luzluna Park
& Keedi Kim.
Hosting generously sponsored by
Yuni Kim.
Sponsored by
SILEX.
.-''' __ __ / \/ \/ \ =-_- | \. -____- / \ // /|| '' //| //|| == = == ==