Socket.IOを用いたマウスカーソルの共有

このエントリは、「東京Node学園 4時限目」の資料その4です。

app.js

var io = require('socket.io').listen(80);

io.of('/index').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/speakers').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/session').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/sponsors').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

io.of('/ticket').on('connection', function(socket) {
  socket.on('location', function(data) {
    data.id = socket.id;
    socket.broadcast.emit('location', data);
  });
});

client.js

function cursorShare(ns) {
  var socket = io.connect('http://example.com/' + ns);
  socket.on('location', function(data) {
    var cursor = $('#' + data.id);
    if (!cursor.attr('id')) {
      cursor = $('<img>');
      cursor.attr('class', 'cursor');
      cursor.attr('id', data.id);
      cursor.attr('src', 'images/cursor.png');
      cursor.css('position', 'absolute');
      cursor.css('width', '18px');
      cursor.css('height', '24px');
      $('#wrapper').append(cursor);
    }
    cursor.css('left', data.x + 'px');
    cursor.css('top', data.y + 'px');
    cursor.show();
    setTimeout(function() {
      cursor.hide();
    }, 10000);
  });
  $('#wrapper').mousemove(function(e) {
    socket.emit('location', {
      x: e.pageX,
      y: e.pageY
    });
  });
}

各ページ

  <script type="text/javascript">
    $(function() {
      cursorShare('session');
    });
  </script>

見たらだいたい分かるべ

ExpressとSocket.IOを使ったカウンターのサンプル

このエントリは、「東京Node学園 4時限目」の資料その3です。

Expressを使ったWebアプリケーションの動きが大体つかめたところで、次はSocket.IOを使ったリアルタイムWebアプリケーションを作ってみましょう。
Socket.IOとはWebSocketをNodeで扱うためのサーバ及びクライアントのライブラリです。WebSocketが使える環境であればWebSocketを、そうでなければ自動的にXHR-polling等に切り替えてくれるのが最大の特徴です。

まずは、Socket.IOをインストールします。package.jsonを以下の様に書き換えてください。

 {
     "name": "application-name"
   , "version": "0.0.1"
   , "private": true
   , "dependencies": {
       "express": "2.5.8"
     , "jade": ">= 0.0.1"
+    , "socket.io": "*"
   }
 }

socket.ioの記述を追加しました。"*"は最新バージョンを意味します。それではsampleディレクトリで再度 npm install をしましょう。

$ npm install

Socket.IOがインストールされたはずです。このように、使用するパッケージをpackage.jsonに記述し管理することで、node_modules ディレクトリをバージョン管理システムの対象外にしておいても同じ環境をコマンド一発で再現することができます。
それでは実際にSocket.IOを使って、現在ページを見ている人数をリアルタイムに表示するプログラムを書いてみましょう。まず、app.jsでSocket.IOを読み込みます。

 var express = require('express')
+  , socketio = require('socket.io')
   , routes = require('./routes');

またファイルの末尾に以下のコードを追加します。

var io = socketio.listen(app);
var count = 0;
io.sockets.on('connection', function(socket) {
  //connect
  count++;
  io.sockets.emit('count change', count);
  socket.on('disconnect', function() {
    //disconnect
    count--;
    socket.broadcast.emit('count change', count);
  });
});

クライアント(ブラウザ)から接続がきたらconnectに書かれた処理が、切断されたらdisconnectに書かれた処理がそれぞれ実行されます。今回の場合、

  1. countの初期値を0にしておく
  2. 接続がきたらcountを+1して、その値を自分を含めた接続している全員に'count change'というイベントとして通知する
  3. 切断したらcountを-1して、その値を自分以外の全員に通知する

という動作になります。

クライアント側にも処理を記述しましょう。./views/layout.jadeを以下のように修正します。

 !!!
 html
   head
     title= title
     link(rel='stylesheet', href='/stylesheets/style.css')
+    script(type='text/javascript', src='http://code.jquery.com/jquery.min.js')
+    script(type='text/javascript', src='/socket.io/socket.io.js')
+    script(type='text/javascript')
+      var socket = io.connect();
+      socket.on('count change', function(count) {
+        // event
+        $('#count').text(count);
+      });
 body!= body

jQueryとSocket.IOのクライアントライブラリを読み込んでいます。Socket.IOのクライアントライブラリは、app.js内でlisten()したことによって自動的に上記のパスに提供されます。
io.connect()をした段階で、サーバ側の'connection'イベントが発生します。また、'count change'というイベントがきたら、eventに記述された処理を実行します。今回の場合、人数が変わるたびにjQueryを用いてcountというidの要素のテキストを書き換えています。
今回の場合、記述するJavaScriptのコード量が少ないため直接layout.jadeに記述しましたが、JavaScriptは本来別ファイルに切り出して、public/javascripts/内に置くほうが良いでしょう。

最後に views/index.jade を以下のように修正します。

 h1= title
 p Welcome to #{title}
+p 現在このページを見ている人は
+  span#count
+  人います。

それでは実行してみましょう。

$ node app.js

で起動し、http://localhost:3000/ にアクセスすると、「現在このページを見ている人は1人います。」と表示されるはずです。現在のタブを開いたまま他のタブでもアクセスしてみましょう。「現在このページを見ている人は2人います」と表示されるはずです。
さらに、最初に「1人」と表示されていたタブも、リロードしなくてもすでに「2人」になっているはずです!タブを増やすたびに人数が増えて行き、閉じるたびに減っていくことを確認してください。

今回は、接続イベントと切断イベント以外のイベントを発生させるのはサーバ側でイベントを受信するのはクライアント側のみという作りになっていますが、もちろん、クライアント側で任意のイベントを発生(emit)させることや、サーバ側で受信(on)することができます。また、Socket.IOにはnamespaceという概念があり、特定のnamespaceに属する相手にのみ通知することも可能です。その例として、その4では昨年の「東京Node学園祭」のWebサイトで用いたマウスカーソルの共有を紹介します。

Expressを使ったWebアプリケーション

このエントリは、「東京Node学園 4時限目」の資料その2です。

最初にexpressをインストールをしましょう。

$ npm install -g express

Nodeの場合、必要なパッケージのインストールは npm を用いて行います。-gはグローバルオプションで、これがあるとパッケージは node コマンドがある場所と同じ階層にインストールされます。これがない場合は、現在のディレクトリに node_modules というディレクトリを作成し、その中にインストールされます。
Expressの場合、ひな形を作成する express コマンドが提供されるため、パスの通った場所にそのコマンドを配置する必要があるため、上記のように -g をつけてインストールしています。また、この際、Macでインストーラを使ってインストールした人は sudo を付ける必要があるでしょう。

それでは実際に express コマンドでひな形を作成してみましょう。

$ express sample

   create : sample
   create : sample/package.json
   create : sample/app.js
   create : sample/public
   create : sample/public/javascripts
   create : sample/public/images
   create : sample/public/stylesheets
   create : sample/public/stylesheets/style.css
   create : sample/routes
   create : sample/routes/index.js
   create : sample/views
   create : sample/views/layout.jade
   create : sample/views/index.jade

   dont forget to install dependencies:
   $ cd sample && npm install

で、言われたとおり sample に入って npm install するんですが、その前にディレクトリ構造を確認しましょう。

sample
 ├ app.js 
 ├ package.json
 ├ public
 │  ├ images
 │  ├ javascripts
 │  └ stylesheets
 │       └ style.css
 ├ routes
 │  └ index.js
 └ views
      ├ index.jade
      └ layout.jade

app.js がメインのプログラムで、package.jsonが npm のための設定ファイル。publicが静的ファイル置き場で、routesがルーティングごとのプログラム置き場で、viewsがテンプレート置き場です。
それでは実際に sample に入って npm install しましょう。

$ cd sample && npm install

今回は、Expressのインストール時とは違って、 npm install の後に特定のパッケージ名を指定しませんでした。この場合、 npm は package.json の dependencies に書かれているパッケージをインストールします。インストールされたパッケージは node_modules ディレクトリ内に格納され、プログラムから参照されます。
今回の場合、 Express と jade 及びそれらの依存パッケージがインストールされました。Expressは最初にも(グローバルに)インストールしたじゃないかと思われるかもしれませんが、最初にインストールしたExpressは express コマンドのためだけに利用しており、実際にプログラム中から参照されるのはこの node_modules の中にあるパッケージの方です。

では、メインプログラムである app.js を見てみましょう。


/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes');

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// Routes

app.get('/', routes.index);

app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);

最初にrequireで必要なパッケージを読み込んでいます。 express がパッケージ名だけで ./routes は相対パス指定になっています。requireは相対パス指定の時はそのファイルもしくはそのディレクトリにある index.js or index.json を読み込み、そうでない場合は コアモジュール もしくは node_modules 内にあるモジュールを読み込みます(実際にはもうちょい細かいです)。

次に express のサーバオブジェクトを作成し、それをモジュールとして公開しています。これによって、例えばテストコードなどからrequireすることでこのサーバオブジェクトを参照することができるようになります。

その後は、設定が続きます。テンプレートの置き場所やテンプレートエンジンの種類、静的ファイルの置き場所等を設定しています。また、developmentモードとproductionモードでの設定の振り分けもしています。ちなみに、デフォルトではdevelopmentモードで起動しますが、環境変数NODE_ENVにproductionを設定すればproductionモードで起動できます。

最後に、ルーティングの設定をして port 3000 を待ち受けています。それでは早速実行してみましょう。

$ node app.js
Express server listening on port 3000 in development mode

ブラウザで http://localhost:3000/ にアクセスして Welcome to Express と表示されたら成功です。
実際にアクセスされた時の動きを順に追っていきましょう。 http://localhost:3000/ にアクセスされると、ルーティングの設定であるように routes.index が実行されます。routes.index は require('./routes').index であり、require('./routes') は ./routes/index.js を読み込むので、./routes/index.js を見てみましょう。

/*
 * GET home page.
 */

exports.index = function(req, res){
  res.render('index', { title: 'Express' })
};

require('./routes') にはこのexportsが入るため、 routes.index は結局このfunctionになります。
このfunctionでは、レスポンスを描画する際のテンプレートファイルの指定および、変数のバインドをおこなっています。この場合、テンプレートの置き場は./viewsでテンプレートエンジンはjadeだと設定されているので、使用されるテンプレートファイルは ./views/index.jade になります。デフォルトでは同じ場所にあるlayout.jadeが読み込まれ、その body 変数に index.jade の中身がバインドされます。

layout.jade は以下のようになっています。

!!!
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body!= body

title要素としてtitleにバインドした値を表示し、CSSファイルを読み込んでいます。body要素として表示するのは、次のindex.jadeの中身です(!=はescapeしないで表示するという意味)。

h1= title
p Welcome to #{title}

こちらでも title にバインドした値を2ヶ所に表示しています。jadeの細かい文法は https://github.com/visionmedia/jade をご覧下さい。

Nodeとnpmのインストール

このエントリは、「東京Node学園 4時限目」の資料その1です。

Windows

公式サイトのトップページに最新安定版のダウンロードリンクがあるので、そこからWindows向けインストーラをダウンロードしましょう。ちなみに、過去バージョンや最新開発版は http://nodejs.org/dist/ に置いてあります。
現在の最新安定版は v0.6.11 で、最新開発版は v0.7.5 です。1個目の「.」と2個目の「.」の間の数字が偶数なら安定版、奇数なら開発版です。
インストーラをダウンロードしたら実行しましょう。基本的には画面の指示に従えば問題ありません。インストールが終了すると

C:\Program Files (x86)\nodejs

の中に node.exe や npm.cmd が配置されます。また、上記フォルダが環境変数Pathに追加されます。
コマンドプロンプトを開いて

node --version

が正常に表示されることを確認しましょう。
コマンドが見つからない等のエラーが出た場合は、パスの追加が反映されてない可能性があります。その場合は、Windowsの再起動をするのが手っ取り早いです。

Mac

インストーラを使う

自分のhomeディレクトリ外(/usr/local/bin)に実行ファイルを置くことを厭わないのであれば、これが楽ちんです。ただし、npm で実行コマンドを提供するようなパッケージをインストールする際には、(/usr/local/binにファイルを置くために)sudoでnpmコマンドを実行する必要があります。
公式サイトのトップページに最新安定版のダウンロードリンクがあるので、そこからWindows向けインストーラをダウンロードしましょう。ちなみに、過去バージョンや最新開発版は http://nodejs.org/dist/ に置いてあります。
現在の最新安定版は v0.6.11 で、最新開発版は v0.7.5 です。1個目の「.」と2個目の「.」の間の数字が偶数なら安定版、奇数なら開発版です。
インストーラをダウンロードしたら実行しましょう。基本的には画面の指示に従えば問題ありません。インストールが終了すると

/usr/local/bin

の中に node や npm のコマンドが配置されます。ターミナルを開いて

node --version

が正常に表示されることを確認しましょう。

インストーラを使わない

Linuxと同様なのでLinuxをご覧ください。ただしXCodeが必要になるので、XCodeをインストール済みでないMacの場合は、時間的な都合により上記のインストーラを使う方法にしたほうがよいでしょう。

Linux

aptやyumでインストールすることができなくはないですが、かなり古いバージョンであったりイレギュラーなインストールのされ方だったりすることがあるので、オススメはしません。
今一番のオススメなインストール方法は nodebrew を使うことです。詳しくは id:Jxckd:id:Jxck:20120224:1330035058 をご覧下さい。
どのような方法にせよ、Nodeをビルドする必要があるので

  1. Python 2.6以降3.0未満
  2. g++ などのコンパイラ
  3. libssl-dev などのSSL系の開発パッケージ

が必要になります。

「東京Node学園祭 2011」を終えて

仕事が忙しくて今まで更新できなかった……。

というわけで、アジア初のNode.jsのカンファレンス「東京Node学園祭 2011」を終えて1月近く経とうとしておりますが、ここらでようやく感想を書かせていただきたいと思います。

個人的には、とても体調が悪い中での司会だったので、ちょっと辛くもあったのですが、特に問題も起こさずに終えることができたかな、と思っています。

セッションの中で特に印象に残ったのは、やはりなんといってもGuillermoのライブコーディングですね。これが世界レベルのWizardか、とその差をまじまじと見せつけられた気がします。

タイピング速度が早いってのももちろん凄いんですが、それ以上にライブコーディングであれだけのものを創り上げる、しかもコメントや型チェックまで当然のように行い、エスケープコードで文字色までつける、ということを当たり前のようにやってのけるのが驚きでした。

補完がどうのとかIDEの使い勝手がどうのとか、彼らには関係がないんですね。全部正確に覚えてるから。彼らの生産性の高さの理由が垣間見えた瞬間でした。

他にはやはり、@ndrugerさんのLTが面白かったなー。KinectとNode.jsを組み合わせてブラウザ上で3Dモデルを動かして、さらにiPhoneでタップするとそのタップした手を動かしてるところにブロックが出現するというもの。まあ見てもらうのが一番わかりやすいですね。

講演内容は全て、ニコニコ動画にUPしてあります。生放送のタイムシフトは期限が限られてしまうので、ドワンゴ研究開発チャンネル上に動画としておいてありますので、いつでもご覧いただけます。

参加できなかった方も、参加して見直したい方も、是非ご覧ください。

最後に、講演者/来場者/スポンサー/後援/スタッフのすべての皆さまに感謝を申し上げます。皆さまのお力添えがあって、無事に終えることができました。

さあ次は、東京Node学園 3時限目です。

Node.jsのカンファレンスを開催します

既に公式Webサイトをオープンしているのでご存知の方もいらっしゃると思いますが、Node.js日本ユーザグループ主催のアジア初のNode.jsのカンファレンス「東京Node学園祭2011」を開催いたします。

なんと、Node.jsの作者であるRyan Dahl(@ryah)氏と、Socket.IOの作者であるGuillermo Rauch(@rauchg)氏に来日していただき、講演してもらいます。

公式サイトは http://nodefest.jp/2011/

サイト上では今同時にサイトを見ている他の人のカーソルを表示するというギミックが仕込んであります。もちろんNode.js + WebSocket(Socket.IO)です。

数日中にチケットの販売も開始する予定です。最新の情報は @nodefest で流すので是非フォローしてください。

LL Planetsで発表してきました

Blogで告知するの忘れてましたが、LL Planetsの「Node.jsとは何だったのか」というセッションにパネラーとして参加しました。

発表資料はこちら

発表自体はまあ慣れてるので滞りなくできたとはおもうのですが、パネルディスカッション的なものって何がでてくるか分からないのでちょっとドキドキしてしまいますね。ググればすぐ出てくるようなことでも、ちゃんと覚えておかないとその場ですぐ説明できなかったりするので、なかなか難しい。でもとても有意義な討論ができたと思います。ありがとうございました。

最後に抽選があって、そこで発表者なのに自重せずにボール拾ったら、なんと

Advanced Programming in the UNIX® Environment (Addison-Wesley Professional Computing Series)

Advanced Programming in the UNIX® Environment (Addison-Wesley Professional Computing Series)


が当たりました!大当たりっぽいです。

あとは、id:badatmathid:Constellationの間に確かな友情が芽生えたのを見守りながら懇親会を楽しみました。

実行委員会の皆さま、参加者の皆さま、とてもいいイベントをありがとうございました。