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すればいいんじゃないかな。とはいえやったことはないので出来るかどうか知りません!誰かやってみて教えてくださいっ
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');
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と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を使ったチャットサンプルを永続化
このエントリはリアルタイムWebハッカソンのハンズオン資料その5です。
今回は、前回のチャットサンプルのチャットログを永続化してみましょう。
node.jsは様々なデータストアと連携することができます。
MySQL, PostgreSQLなどのRDBMSももちろん可能ですが、やはりCouchDB, MongoDB, RedisなどのいわゆるNoSQLと呼ばれるものと連携させるのが人気のようです。
特にJSON形式でそのまま格納することのできるデータストアは、JavaScriptから扱うのに都合がいいというのは言うまでもないと思います。
もちろん、node.js製のデータストアエンジンもいくつかあります。有名なのはnStoreでしょう。
今回はdirtyというライブラリを使います。理由は僕が使ってみたかったからです。
インストールは
$ npm install dirty
で終わり。
……のはずなんだけど、現状(dirty v0.9.0)だと最新のnode v0.2.3で使おうとしたら'constrants'ってモジュールが見つからないよってエラーが出てしまう。
もうパッチはあたっていて、npmにその最新のが登録されていないだけの状態なのでgithubから直接とってくりゃいい。なので
$ cd tmp $ git clone git://github.com/felixge/node-dirty.git $ cd node-dirty $ npm install
でOK
んじゃさっそくdirtyを使ったapp.jsを見てみましょう。変わったのは先頭のrequireの部分
var express = require('express'), io = require('socket.io'), db = require('dirty')('log.db'), json = JSON.stringify;
とsocket.onの引数のfunctionの部分
socket.on('connection', function(client) { count++; client.broadcast(json({count: count})); client.send(json({count: count})); db.forEach(function(key, val) { client.send(val); }); client.on('message', function(message) { // message db.set(new Date().getTime(), message); client.broadcast(message); client.send(message); }); client.on('disconnect', function() { // disconnect count--; client.broadcast(json({count: count})); }); });
こんだけです。見ての通り、メッセージを受け取ったらそれを現在時刻のミリ秒をキーにsetしています。
で、新規接続したらそいつに全てのログをforEachで回しながらsendしています。単純ですね。
果たしてこれでどんだけのパフォーマンスが出るかわかりませんが、それはこのチャットを実際に使うハッカソンの会場で判明することでしょう。