@aanoaa - 홍형석, 사당동 펠프스, github:aanoaa
Mojolicious은 인기있는 펄의 경량 웹 프레임워크입니다. 경량의 MVC 프레임워크임에도 불구하고 HTTP 클라이언트 및 서버의 거의 풀 스택을 구현한 웹 프레임워크로 지원하지 못하는 기능을 찾기가 더 어려울 정도인 잘 만들어진 모듈입니다. 웹소켓 역시 대표적인 예로 Mojolicious는 이 웹소켓을 아주 잘 지원합니다. 실시간으로 상태를 갱신한다던가 등의 동작을 단순 HTTP만으로 구현하려면 자바스크립트 및 웹응용의 컨트롤러에서 처리해야 할 내용이 꽤 많죠. 이번 기사에서는 Mojolicious에서 손쉽게 웹소켓을 다루는 방법을 소개합니다.
필요한 모듈은 다음과 같습니다.
Mojo::Redis2 모듈을 사용하려면 Redis 서버를 설치해야 합니다. 데비안 계열의 리눅스를 사용하고 있다면 다음 명령을 이용해서 패키지를 설치합니다.
1 | $ sudo apt-get install redis-server |
직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.
1 | $ sudo cpan Mojolicious Mojo::Redis2 |
사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.
1 | $ cpan Mojolicious Mojo::Redis2 |
Mojolicious 저장소의 위키에는 유용한 정보가 많은데, 그 중 Writing websocket chat using Mojolicious Lite 문서를 조금 변경해 보았습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #!/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> $(function() { var sock; var port = location.port; 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의 웹소켓 기능이 제대로 동작하지 않습니다.
웹소켓 서버를 구동하기 위해 명령줄에서 다음 명령을 실행하세요.
1 | $ morbo echo |
앞서 살펴본 예제는 마치 잘 동작하는 것처럼 보이지만 사실 서버로 띄운 웹 응용이 하나일 때, 즉 워커 프로세스가 하나 일 때만 정상적으로 동작합니다. morbo는 하나의 워커로 동작하는 비동기 서버이기 때문에 문제가 없지만 hypnotoad처럼 여러개의 워커를 띄울 수 있는 비동기 서버일 경우 메세지를 받은 워커에 연결된 클라이언트에게만 전달하기 때문입니다.
이런 자원 공유 문제를 해결하는 여러가지 방법이 있겠지만, 그 중 Redis의 Pub/Sub 모델을 활용하면 너무나도 간단하게 문제를 해결할 수 있습니다. 메세지를 받은 워커가 출판(publish)하고 이를 구독(subscribe)하는 워커가 메세지를 받아서 연결되어있는 클라이언트에게 전달하면 되겠죠!
Redis를 사용한 예제는 다음과 같습니다.
여러 개의 사용하기 위해 hypnotoad
관련 설정에서 띄울 워커 개수를 5개를 설정합니다.
__DATA__
섹션은 앞선 예제와 동일하므로 생략합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #!/usr/bin/env perl use Mojolicious::Lite; use Mojo::Redis2; use Scalar ::Util; use Time ::HiRes 'time' ; my $config = plugin 'Config' => { default => { hypnotoad => { 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; |
웹소켓 서버를 구동하기 위해 명령줄에서 다음 명령을 실행하세요.
1 | $ 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.
.-''' __ __ / \/ \/ \ =-_- | \. -____- / \ // /|| '' //| //|| == = == ==