Mojolicious+Angularjsでwebsocket
angularjsでwebsocket
Controllerだけではうまくいかないことがあるのでfactoryなどが必要になってくる
実装
default.html.ep
<!DOCTYPE html> <!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel="shortcut icon" href="<%= url_for '/favicon.ico' %>"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="utf-8"> <link rel='stylesheet prefetch' href='/css/bootstrap.min.css'> <link rel='stylesheet prefetch' href='/css/main.css'> <script src='/js/jquery-2.1.1.min.js'></script> <script src='/js/bootstrap.min.js'></script> <script src='/js/angular.min.js'></script> <script src='/js/ui-bootstrap-tpls-0.12.0.min.js'></script> <script src='/js/main.js'></script> </head> <body> <div class="header"> <div class="title"> <a href="/"><%= title %></a> </div> </div> <div class="content"> <%= content %> </div> <div class="footer"> <div class="copyright"> @m_shige1979 </div> </div> </body> </html>
このへんはいままでのものを流用しているのでぶっちゃけ手抜きです
index.html.ep
% layout 'default'; % title 'おれおれつぶやき君'; <div class="index" ng-app="myApp"> <div ng-controller="MainCtrl"> <div class="row head"> さみしくなるまでなんかをつぶやけ </div> <div class="row detail"> <input type="hidden" ng-model="EchoScoket.path" ng-init="EchoScoket.path='<%= url_for('echo')->to_abs %>'" /> <div class="col-md-12 block1"> <form class="form" role="form"> <div class="form-group"> <div class="input-group" style="width: 100%;"> <input type="text" class="form-control" placeholder="Message" ng-model="message" ng-keydown="handleKeydown($event)"> </div> </div> <button type="button" class="btn btn-default" ng-click="messageClear()">clear</button> <button type="button" class="btn btn-primary" ng-click="messageSend()">send</button> </form> </div> </div> <div class="row detail"> <div class="col-md-12 block1"> <div class="message_block"> <div class="message_item" ng-repeat="item in EchoScoket.messageList"> 俺:{{item.text}} </div> </div> </div> </div> </div> </div>
※echoのURLをどのようにしてng-modelに割り当てようかと思ったけどng-initでなんとかなりました。
main.js
var myApp = angular.module('myApp', ['ui.bootstrap']); myApp.factory('EchoScoket', function($rootScope, $log){ var service = {}; var ws; service = { // websocket path path: "", // messageList: [], // 接続 connect: function(){ // オブジェクト生成 ws = new WebSocket(service.path); $log.log(ws); // 接続 ws.onopen = function(){ $log.log("websocket connect"); }; // メッセージ受信 ws.onmessage = function(message){ $log.log("websocket connect") var res = JSON.parse(message.data); // 先頭に追加する service.messageList.unshift(res); // すぐに反映されないのでここで同期する $rootScope.$apply(service.messageList); }; // 切断 ws.onclose = function(){ $log.log("websocket disconnect") } }, // メッセージ送信 send: function(message){ if(message != ""){ ws.send(message); } } }; return service; }); myApp.controller('MainCtrl', function($scope, $log, EchoScoket){ $scope.EchoScoket = EchoScoket; $scope.message = ""; // message clear $scope.messageClear = function(){ $scope.message = ""; } // message send $scope.messageSend = function(){ EchoScoket.send($scope.message); $scope.message = ""; } // enter key send $scope.handleKeydown = function(e) { if (e.which == 13) { EchoScoket.send($scope.message); $scope.message = ''; } } // 初回にのみ実行され、websocketの接続処理を実施 $scope.$watch('EchoScoket.path', function(newVal, oldVal) { EchoScoket.connect(); }); });
factoryをシングルトンパターンにしてscopeにすることで値を変更されたことを監視する$watchと連携できるようにしています。
websocketの通信部分は基本そのままで値の反映に誤差があるので$rootScope.$applyで同期をとるようにしました。
app.psgi
use Mojolicious::Lite; use DateTime; use Encode; use JSON; use utf8; # 接続人数 my $clients = {}; # Template with browser-side code get '/' => 'index'; # WebSocket echo service websocket '/echo' => sub { my $c = shift; my $id = sprintf "%s", $c->tx; $clients->{$id} = $c->tx; # Opened $c->app->log->debug('WebSocket opened.'); # Increase inactivity timeout for connection a bit $c->inactivity_timeout(300); # Incoming message $c->on(message => sub { my ($c, $msg) = @_; $c->app->log->debug('mesage->' . $msg); my $dt = DateTime->now( time_zone => 'Asia/Tokyo'); for (keys %$clients) { my $_msg = "$msg"; $clients->{$_}->send(JSON->new->utf8(0)->encode({ hms => $dt->hms, text => $_msg, })); } }); # Closed $c->on(finish => sub { my ($c, $code, $reason) = @_; $c->app->log->debug("WebSocket closed with status $code."); }); }; app->start;
※ベースとしたものは
http://mojolicio.us/perldoc/Mojolicious/Guides/Cookbook#WebSocket-web-service
であとは以前作成したものを流用しました。