ExpressとSocket.ioを使ったチャットサンプル
このエントリはリアルタイムWebハッカソンのハンズオン資料その4です。
前回の続きです。それでは次に簡単なチャットアプリのコードを見てみましょう。かなりの部分(特にデザイン面)をSocket.ioのチャットサンプルをパクって参考にしています。
サーバ側であるapp.jsはこんな感じです。
var express = require('express'), io = require('socket.io'), json = JSON.stringify; var app = module.exports = express.createServer(); // Configuration app.configure(function(){ app.set('views', __dirname + '/views'); app.use(express.bodyDecoder()); app.use(express.methodOverride()); app.use(express.compiler({ src: __dirname + '/public', enable: ['less'] })); app.use(app.router); app.use(express.staticProvider(__dirname + '/public')); app.use(express.logger()); }); app.configure('development', function(){ express.logger("development mode"); app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); }); app.configure('production', function(){ express.logger("production mode"); app.use(express.errorHandler()); }); // Routes app.get('/', function(req, res){ res.render('index.jade', { locals: { title: 'リアルタイムWebハッカソン Chat Room' } }); }); // Only listen on $ node app.js if (!module.parent) { app.listen(3000); console.log("Express server listening on port %d", app.address().port) } var socket = io.listen(app); var count = 0; socket.on('connection', function(client) { count++; client.broadcast(json({count: count})); client.send(json({count: count})); client.on('message', function(message) { // message client.broadcast(message); client.send(message); }); client.on('disconnect', function() { // disconnect count--; client.broadcast(json({count: count})); }); });
やりとりする内容をJSON形式に変えてます。人数のカウント情報だけでなく、チャット内容もやりとりするため、ただの文字列ではどちらが来てるのかを判断しづらいからです。
messageを受け取るところの処理では受け取った内容をそのまま配信してます。
次にクライアント側を見ましょう。views/layout.jadeはこんな感じです。
!!! 5 html head title= title link(rel='stylesheet', href='/stylesheets/style.css') script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js') script(type='text/javascript', src='http://cdn.socket.io/stable/socket.io.js') script(type='text/javascript', src='/javascripts/client.js') body!= body
先頭に5が加わってHTML5の宣言にした事以外前回から変わりありません。HTML5にしてるのは下にあるindex.jadeのテキストボックスにplaceholder属性の設定をしているからです。
views/index.jadeは次のようになっています。
h1= title p 現在接続している人は span#count 人います #chat form#form(onsubmit='send(); return false;') input#name(type='text', placeholder='Twitter ID') input#text(type='text', autocomplete='off') input(type='submit', value='送信')
チャット表示欄#chatと入力フォーム#formが追加されています。#chatのように要素を省略すると自動的にdivだと判断されます。
public/stylesheets/style.lessは次のようになっています。
body { padding: 10px 50px 5px 50px; font: 14px "Lucida Grande", "Helvetica Nueue", Arial, sans-serif; } #chat { height: 500px; overflow: auto; width: 800px; border: 1px solid #eee; p { padding: 0px; margin: 0px; } div { padding: 5px; margin: 0; } div:nth-child(odd) { background: #F6F6F6 } .permalink { font: 3px; color: #AEAEAE; } } #form { width: 782px; background: #333; padding: 5px 10px; input[type=text] { padding: 5px; background: #fff; border: 1px solid #eee; } #name { width: 85px; } #text { width: 620px; } input[type=checkbox] { } input[type=submit] { cursor: pointer; background: #999; border: none; padding: 6px 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; margin-left: 5px; text-shadow: 0 1px 0 #fff; } input[type=submit]:hover { background: #A2A2A2; } input[type=submit]:active { position: relative; top: 2px; } }
最後にclient.jsを見てみましょう。
var socket = new io.Socket('localhost'), json = JSON.stringify; socket.connect(); socket.on('message', function(message) { message = JSON.parse(message); if (message.count) { $('#count').text(message.count); } if (message.message) { var data = message.message; var date = new Date(); date.setTime(data.time); $('#chat').append('<div class="chatlog"><p><a name=' + data.time + '></a><a href="http://twitter.com/' + data.name + '"><img src="http://api.dan.co.jp/twicon/' + data.name + '/mini" /></a> ' + data.text + '</p><a class="permalink" href="#' + data.time + '">' + date.toString() + '</a></div>') $('#chat').scrollTop(1000000); } }); function send() { var name = $('#name').val(); var text = $('#text').val(); if (text && name && name != "Twitter ID") { var time = new Date().getTime(); socket.send(json({message: {name: name, text: text, time: time}})); $('#text').val(''); } }
特に難しいところはないと思います!
ExpressとSocket.ioを使ったWebSocketのサンプルを作る
このエントリはリアルタイムWebハッカソンのハンズオン資料その3です。
前回の続きです。まずはSocket.ioを使えるようにしましょう。
app.jsの先頭を
var express = require('express');
から
var express = require('express'), io = require('socket.io');
にする。
また、ファイルの最後に
var socket = io.listen(app); socket.on('connection', function(client) { // connect client.on('message', function(message) { // message }); client.on('disconnect', function() { // disconnect }); });
を追加する。
クライアントから接続がきたらconnectに書かれた処理が、メッセージがきたらmessageに書かれた処理が、切断されたらdisconnectにかかれた処理が実行される。
次にクライアント側の処理。
sample/views/layout.jadeを開いて
!!! html head title= title link(rel='stylesheet', href='/stylesheets/style.css') script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js') script(type='text/javascript', src='http://cdn.socket.io/stable/socket.io.js') script(type='text/javascript', src='/javascripts/client.js') body!= body
と記述。jQueryを使い、socket.ioのクライアントライブラリは公式サイトCDNを使い、自分で書くクライアント側のJavaScriptファイルはclient.jsという名前にする。
sample/public/javascripts/client.jsを以下の内容で作る。
var socket = new io.Socket('localhost'); socket.connect(); socket.on('message', function(message) { //message });
見てのとおり2行目で接続し、サーバからメッセージがきたらmessageに書かれた処理が実行される。
んじゃとりあえず、現在接続している人数をリアルタイムに表示するプログラムでも書いてみましょう。
まずはapp.js。最後のsocket.ioのところを以下のようにする
var socket = io.listen(app); var count = 0; socket.on('connection', function(client) { count++; client.broadcast(count); client.send(count); client.on('message', function(message) { // message }); client.on('disconnect', function() { // disconnect count--; client.broadcast(count); }); });
- countの初期値を0にしておく
- 接続がきたらcountを+1して、その値をbroadcast(現在のクライアント以外にメッセージを送信)とsend(現在のクライアントにメッセージを送信)
- 切断したらcountを-1して、その値をbroadcast
わかりやすいですね。
次にsample/views/index.jadeに以下のように追記します。
h1= title p Welcome to #{title} p 現在接続している人は span#count 人います
そしてsample/public/javascripts/client.jsを以下のように修正します。
var socket = new io.Socket('localhost'); socket.connect(); socket.on('message', function(message) { $('#count').text(message); });
以上で終了です。
それでは
$ node app.js
で起動してみましょう。
localhost:3000にアクセスすると「現在接続している人は1人います」と出るはずです。コンソールには
Express server listening on port 3000 18 Oct 18:44:52 - socket.io ready - accepting connections xxx.xxx.xxx.xxx - - [Mon, 18 Oct 2010 09:44:56 GMT] "GET /favicon.ico HTTP/1.1" 404 - "" "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3" 18 Oct 18:44:56 - Initializing client with transport "websocket" 18 Oct 18:44:56 - Client 14417221560142934 connected
こんな感じでログが出てるはずです。ちなみにfaviconがないよっていう404はウザいので適当にfavicon.icoを作ってpublic直下に置いとけばいいと思います。
では、現在のタブを開いたまま新しいタブでもlocalhost:3000にアクセスしてみましょう。「現在接続している人は2人います」と表示され、コンソールにも
18 Oct 18:50:14 - Initializing client with transport "websocket" 18 Oct 18:50:14 - Client 2343847642187029 connected
みたいに接続が増えたログが出てるはずです。
さらに、最初の「1人」と表示されていたタブを見てください。リロードしなくてもすでに「2人」になっているはずです!
タブを増やすたびに人数が増えていき、また閉じるたびに減っていくことを確認してください。
これでnode.jsを使った基本的なWebSocketの使い方は終了です。
ExpressとWebSocketを使ったWebSocketのサンプルを作る準備
このエントリはリアルタイムWebハッカソンのハンズオン資料その2です。
前回の記事でnode.jsとnpmのインストールは完了しているものとします。
まずは必要なライブラリのインストール
$ npm install express jade less socket.io npm info it worked if it ends with ok npm info using npm@0.2.3-6 npm info fetch http://registry.npmjs.org/express/-/express@1.0.0rc4.tgz npm info fetch http://registry.npmjs.org/less/-/less-1.0.36.tgz npm info fetch http://registry.npmjs.org/socket.io/-/socket.io-0.5.3.tgz npm info fetch http://registry.npmjs.org/jade/-/jade@0.5.3.tgz npm info install express@1.0.0rc4 npm info install socket.io@0.5.3 npm info install less@1.0.36 npm info install jade@0.5.3 npm info activate socket.io@0.5.3 npm info activate less@1.0.36 npm info activate jade@0.5.3 npm info activate express@1.0.0rc4 npm info build Success: socket.io@0.5.3 npm info build Success: less@1.0.36 npm info build Success: jade@0.5.3 npm info build Success: express@1.0.0rc4 npm ok
ExpressはSinatraライクなnode.jsのWebアプリケーションフレームワーク。
JadeはHamlライクなnode.jsのテンプレートエンジンでlessはCSS拡張lessのnode.js版。両方ともExpressが標準でサポートしている。
Expressがサポートしているテンプレートエンジンは他にもEJSやHamlやCoffeeKupがあり、CSS拡張もsassのnode.js実装をサポートしてる。でまあ、好きなの使えばいいんだけど後述のジェネレータで作られたアプリでデフォルトで使われてるのがjadeとlessなので、ここではjadeとlessを使うことにする。
で、おもむろに以下を実行する
$ express sample create : sample create : sample/app.js create : sample/public/javascripts create : sample/logs create : sample/pids create : sample/public/images create : sample/public/stylesheets create : sample/public/stylesheets/style.less create : sample/test create : sample/test/app.test.js create : sample/views/partials create : sample/views/layout.jade create : sample/views/index.jade
すると、必要なファイルとディレクトリが作られアプリ基盤が完成する。
publicが静的ファイル置き場。(クライアントサイドの)JavaScriptやCSSや画像なんかはここに置く。viewsはテンプレートファイル置き場。静的なHTMLならpublicに置けばいいんじゃないかな。testはテストコード置き場。logsはログファイルが作られる場所。pidsは複数プロセス立ち上げた時の管理用…だと思う。(ぶっちゃけ前まではpublicとviewsしかなくて、このエントリ書くために今実行してみたらその他のが出来てたので戸惑ってるところ)。
この状態ですでに実行可能になっているので実行してみます。
$ cd sample $ node app.js Express server listening on port 3000
ブラウザでlocalhost:3000にアクセスし、Welcome to Expressと表示されたら成功。
app.jsの中身はこんな感じ。
/** * Module dependencies. */ var express = require('express'); var app = module.exports = express.createServer(); // Configuration app.configure(function(){ app.set('views', __dirname + '/views'); app.use(express.bodyDecoder()); app.use(express.methodOverride()); app.use(express.compiler({ src: __dirname + '/public', enable: ['less'] })); app.use(app.router); app.use(express.staticProvider(__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('/', function(req, res){ res.render('index.jade', { locals: { title: 'Express' } }); }); // Only listen on $ node app.js if (!module.parent) { app.listen(3000); console.log("Express server listening on port %d", app.address().port) }
まあ、見たら大体分かると思うけどちょっと補足。
- 16行目でCSS拡張としてlessを使うことを指定している。
- 21,25行目でdevelopmentモードのときとproductionモードのときのエラー処理を設定している。基本はdevelopmentモードなんだけど、以下のようにNODE_ENVにproductionを指定して動かせばproductionモードになる。
$ NODE_ENV=production node app.js
- 31行目で'/'以下にアクセスされたときの動作を設定している。見てのとおりjadeのテンプレートファイルのindex.jadeを変数titleに'Express'を束縛して描画する。テンプレートファイルの拡張子を見てどのエンジンを使うかを自動的に判断するので複数のテンプレートエンジンを共存させることも可能。だけどまあ1つのエンジンしか使わないなら、12行目のconfigureの中で次のように指定してやって拡張子を省略することもできる。
app.set('view engine', 'jade');
Cygwinや仮想化なしでnode.jsをWindowsへインストール
このエントリはリアルタイムWebハッカソンのハンズオン資料その1補足です。
どうしてもCygwinが嫌いだ。だけどVirtualBoxとかVMwareとかも嫌だって人もいるかもしれません。そういう人もなんとかまあnode.jsを使えるようにできないわけではありません。
注)茨の道です。普通にCygwin使うかVirtualBoxでUbuntu入れることを推奨します。
1. msysgitをインストールしてGit Bashを使えるようにします。
2. Git Bash上で
$ git clone git://github.com/ajaxorg/node-builds
をして、node-builds/win32/binにパスを通す。これで一応nodeコマンドは使えるようになります。
ただ、必要なライブラリをnpmを使って取得することができません。なので……
3. Git Bash上で
$ git clone git://github.com/robrighter/node-boilerplate.git hoge
$ cd hoge
$ ./bin/initproject.sh
とすれば、express, Socket.ioなどをhoge/lib内に取得し、hoge/server.jsの先頭に
require(__dirname + "/lib/setup").ext( __dirname + "/lib").ext( __dirname + "/lib/express/support");
を記述してくれるのでこれで普通にrequireして使うことができるようになります。
expressやSocket.io以外のライブラリが使いたければ自分でlib内にgit cloneすればいいんじゃないかな。とはいえやったことはないので出来るかどうか知りません!誰かやってみて教えてくださいっ
node.jsとnpmのインストール
このエントリの記述は既に古いです。最新のインストール方法は下記のエントリを参照してください。
http://d.hatena.ne.jp/t_43z/20110503/1304421488
このエントリはリアルタイムWebハッカソンのハンズオン資料その1です。
Linux
普通に入れる
後述のnaveを使う方がいいと思う。っていうかnpmのインストール方法をどうしたいかによってnode.js本体のインストール方法が変わる。なので(naveを使わないにしても)最後まで読んでからやることを推奨。
普通にやるとこんな感じ。
$ sudo apt-get install build-essential libssl-dev curl
$ mkdir tmp
$ cd tmp
$ wget http://nodejs.org/dist/node-v0.2.3.tar.gz
$ tar -xvzf node-v0.2.3.tar.gz
$ cd node-v0.2.3
$ ./configure
$ make
$ sudo make install
必要なパッケージはもちろん環境によって違う。上記はUbuntu10.04の初期状態の場合。make testをする場合には、さらにapache2-utilsをinstallしておく必要がある。
npmは作者が推奨しているやり方が3通り、推奨していないやり方(sudoで実行しちゃう方法)が1通りある。
1. /usr/local以下をrootではなく自分のものにしてしまう方法。自分しか触らないマシンならいいんじゃねって話。
$ sudo chown -R $USER /usr/local
node本体をインストール
$ curl http://npmjs.org/install.sh | sh
一行目をnode本体のインストール前にやり、node本体のインストールの最後のsudoは付けない。
2. node本体を$HOME/local以下にインストールしておく方法
上述のnodeのインストールで./configureに--prefix=~/localのオプションを指定してやる。そうやってnode本体をインストールした後に
$ curl http://npmjs.org/install.sh | sh
を実行すればいい。
3. npmのインストール先を指定しておく方法
node本体は上述の方法でインストールしておく。
まず設定
$ cat >>~/.npmrc <
naveを使う
naveっていうのは、複数バージョンのnode.js環境を切り替えて使うためのツール。RubyのrvmとかPythonのvirtualenv的なもの。同じ趣旨のものにnvmもあるが、naveはnpmの作者自らが作っているのでその安心感がある。
$ sudo apt-get install build-essential libssl-dev git-core
$ mkdir work
$ cd work
$ git clone http://github.com/isaacs/nave.git
$ cd nave
$ ./nave.sh install latest
でnode本体のインストールが完了。
$ ./nave.sh use latest
$ curl http://npmjs.org/install.sh | sh
でnpmのインストールが完了。これでいれるとnode.js本体は$HOME/work/nave/installed/0.2.3以下に、npmは$HOME/work/nave/installed/0.2.3/lib/node/npmに入っている。
use
$HOME/work/nave/nave.sh use latest
を.bash_profileとかに入れとくといい。
追記) オフライン状態でnave.sh use latestすると、nave/installed以下が全て削除される現象(バグ?)が発生するようです。なので、ローカルのマシンだと.bash_profileに入れるのは危険かもしれません。
Windows
CygwinにしろVirtualBoxにしろ、環境ができたら上記のLinuxと同じ手順。素で入れるよりnave使う方がいいってのも同じ。
Cygwin好きな方
http://boxysystems.com/index.php/step-by-step-instructions-to-install-nodejs-on-windows/とかがかなり丁寧なガイドかな。
Cygwin嫌いな方は
VirtualBoxとかでUbuntu等を入れるのがいいでしょう。
Mac
MacPorts
node本体はあるんだけど、npmはない。
http://www.florian-kubis.de/2010/09/howto-install-node-js-on-mac-snow-leopard-with-express/が参考になりそう。
さくらのVPSの初期設定
Ubuntu 64bit32bti版入れました(64bit版は素で400MB近くメモリ食ってたので32bitに変えた)。
とりあえずアップデート(インストール時に最後にやってくれてるけど一応)
$ sudo apt-get update
$ sudo apt-get upgrade
SSH公開鍵を追加
ローカルマシンで
$ scp .ssh/id_rsa.pub ***.***.***.***:/home/mesoサーバで
$ mkdir .ssh
$ cat id_rsa.pub >> .ssh/authorized_keys
$ rm id_rsa.pub
$ chmod 600 .ssh/authorized_keys
$ sudo apt-get install vim
$ sudo vim /etc/ssh/sshd_config
で
PermitRootLogin yes
- > PermitRootLogin no
#AuthorizedKeysFile %h/.ssh/authorized_keys
- > AuthorizedKeysFile %h/.ssh/authorized_keys
#PasswordAuthentication yes
- > PasswordAuthentication no
$ sudo /etc/init.d/ssh restart
Node.js Knockout に参加しました
要は、node.jsを使ったアプリケーションを48時間で作成してね。っていうコンテスト。コードはgithubにコミットし、herokuかjoyentにデプロイするのがルール。
僕は1人チームで参加しました。やっぱり、node.jsの特徴であるイベント駆動I/Oを生かしたものが作りたいと思い、WebSocketを使ってリアルタイムに複数人との対戦ができるタイピングゲームを作ることにしました。
herokuは、WebSocketの使用に制限があったので(同時接続64人までとか接続時間30秒までとか)、joyentにデプロイしました。っていうかこのjoyentのnode.js用のプラットフォームはすごく使い勝手が良かった。
完成品はhttp://mesolabs.no.de/です。ぶっちゃけ、テストも不十分だしコードも汚いand拙いものなんですが、なんとか形にはなりました(Windows 7 + Google Chromeな人が参加するとバグるっぽいです。Windows 7の人は是非Safariで)。
審査員やら一般の方の投票も終わりました。http://nodeknockout.com/teams/mesolabsで点数とコメントを見ることが出来ます。点数は伸び悩んだのですが、コメントを見ると勿体無いようなありがたいコメントを多く頂けました。
他のチームのアプリケーションを見ても、やはりWebSocketを使ったリアルタイムコミュニケーションを重視したものが多く、node.jsの向いている方向性がはっきりと示唆された大会であったようにも思います。
当初はほとんど触ったこともなかった状態だったので、参加するかどうか若干迷ったりもしたのですが、思い切って参加してみて本当によかったと思います。たくさん勉強になりました。
実装よりの話は、また次回にでもまとめて書きます。