223 Software

主に勉強したことなどを記録するログです。 最近はRuby, Railsなどが多め。

Node - 223 Software

SocketStreamのデモを動かそう

西村賢(@knsmr)さんの記事を読んで、面白そう!と思い、早速デモを動かしてみました。
Rails Hub情報局: Node.jsに強烈に個性的な「SocketStream」が登場!

デモはgithubのリポジトリから3つほどリンクされています。

とりあえず動かしながら、特徴っぽいところを自分なりに書いてみます。
動かし方については、Mac + Homebrewしか想定していません。というか自分の環境しかわかりません。
あと、Node(Node.js), npm, gitは入っている前提で。
他の環境で動かした方はぜひ教えてください。リンクします。

最初にSocketStream自身をグローバルにインストールしておきましょう。

$ npm install socketstream -g

Coffee-Scriptとか、Socket.ioとかもごっそり入ります。

SocketChat

WebSocketを使ったチャット。app/client/app.coffeeからSS.server.app.sendMessageのようにクライアントからサーバーのメソッドを直接呼んでいる(ように書ける)ところがポイントかな?

動かし方

まず、Redisをインストールしていない人はインストール。

$ brew install redis
$ mkdir -p ~/Library/LaunchAgents
$ cp /usr/local/Cellar/redis/2.2.10/io.redis.redis-server.plist ~/Library/LaunchAgents/
$ launchctl load -w ~/Library/LaunchAgents/io.redis.redis-server.plist

mkdir -p以降は、brew installが終わったときに自動的に案内が出ますので、言われるとおりに従えばOK。

蛇足
バージョンアップのときにも、起動用の.plistをコピーしてlaunchctlやり直す必要がありますが、バージョンアップの時に出る案内を見逃さなければ大丈夫。見逃しても $ brew info redis で再度見られます。 /蛇足

では動かします。

$ git clone https://github.com/addyosmani/socketchat.git
$ cd socketchat
$ npm install
$ mkdir public/assets # directoryが無いよ、って次の手順で言われたので。
$ socketstream s
$ open http://localhost:3000/

はい、終わりです。

ここでherokuにデプロイしようと、小一時間ほど試したのですがちょっとわからなかったのでパス。
具体的には、heroku上で動かすにはprocess.env.PORTでアプリケーションを動かすPORTを取得するのですが、socketstreamではconfig/app.jsonのような.jsonでその辺の設定をするようで、それを動的に取り出す方法がわかりませんでした。

Dashboard

Widgetを設定し、配置できます。APIからWidgetの中身を操作できるようになっています。
APIから操作し、それがリアルタイムにWidgetに反映されるというのがポイント。(多分)

動かし方

MongoDBをいれていない場合は以下。

$ brew install mongodb
$ mkdir -p ~/Library/LaunchAgents
$ cp /usr/local/Cellar/mongodb/1.8.2-x86_64/org.mongodb.mongod.plist ~/Library/LaunchAgents/
$ launchctl load -w ~/Library/LaunchAgents/org.mongodb.mongod.plist

では動かします。

$ git clone https://github.com/paulbjensen/socketstream_dashboard_example
$ cd socketstream_dashboard_example
$ npm install
$ socketesteam s

以上

SocketRacer

レースゲームです。
チャット機能や衝突判定などにのメインロジックにCoffeeScriptを使っているのが売りらしいですが、
すごいんですけれどもsocketstreamならではの売りがちょっとよくわかりませんでした。しっかりソースも追っていません。

見所をどなたか教えてください。

動かし方
$ git clone https://github.com/alz/socketracer.git
$ cd socketracer
$ npm install
$ socketstream s

以上

まとめ

公式のREADMEが充実しているので、ひと通り呼んでサンプル作ってみたいと思います。
対応ブラウザは限定されてしまうかもしれませんが、注目に値するフレームワークではないでしょうか。

CoffeeScriptはじめの一歩

Rails勉強会@東京第63回のあまりのCoffeeScript人気に便乗して。

インストール

CoffeeScriptを動かす、あるいはJavaScriptにコンパイルする方法も色々あるようですが、Node入れてやるのがいいと思うのでその方法で。
Node使う予定の無い人も、きっとそのうち一度はExpressやらSocket.ioやら使うときが来ます。

まずはNode(Node.js)を入れます。MacでHomebrewな人はおめでとうございます。簡単です。

$ brew install node

rubyでいうrvmみたいなものもあるのですが、僕はbrewで十分だと思っています。

引き続き、Node用のライブラリを管理するnpmを入れます。rubyでいうRubyGemsのようなものです。

$ curl http://npmjs.org/install.sh | sh

多分/usr/local/bin/以下にnpmが入ります。そのままcoffee-scriptを入れます。

$ npm install -g coffee-script

オプションの-gをお忘れなく。npmは、デフォルトではカレントディレクトリ以下にnode_modules/というディレクトリを作って、その中にライブラリを入れます。
今回はカレントではなくglobalにインストールするので、-gを付けます。これで/usr/local/bin/coffeeが入ったことと思います。

Hello World

hello_world.coffeeというファイル名で以下のようなスクリプトを作ってください。

console.log 'Hello, world'

jsにコンパイルし、nodeで動かしてみます。

$ coffee -c hello_world.coffee
$ node hello_world.js    # => Hello, world!

また、jsにコンパイルせずに直接動かすこともできます。

$ coffee hello_world.coffee    # => Hello, world!

.coffeeを更新したら自動的にjsにコンパイルする方法

その1 coffeeコマンドの-wオプションを使う。
$ coffee -wc hello_world.coffee  # これでファイルを監視開始
その2 watchrを使う。

参考:Haml+SCSS+CoffeeScriptでモダンな静的ページ制作を

その3 shadow.vimを使う。

参考:Vim-users.jp - Hack #192: 任意の言語で任意のマクロを使う

効率よくCoffeeScriptを勉強する方法

公式サイトを写経するといいと思います。
JavaScriptと違う書き方やキーワードも多くて、なかなか覚えられないですが一度写経してあとはひたすら書くといいのだと思います。

正直なところ、まだCoffeeScriptよりもJavaScriptを直接書く方が楽です。いちいち頭の中にあるjsのコードをCoffeeScriptに翻訳しながら書いている感じです。

CloudFoundryでSocket.ioを試した(失敗)

CloudFoundryでNodeを試してる続きです。(前回:CloudFoundryでNode+MongoDBを動かした

app.jsに以下を追加して、とりあえず接続者数を表示するようにしてみました。

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){
    client.broadcast(message);
    client.send(message);
  });

  client.on('disconnect', function(){
    count--;
    client.broadcast(json({count: count}));
  });
});

クライアント側のJavaScriptはこんな感じ。

var socket = new io.Socket(host_url.split(':')[0]);
socket.connect();
socket.on('message', function(message){
  $('#count').text(JSON.parse(message).count);
});

ほとんどExpressとSocket.ioを使ったWebSocketのサンプルを作る - 自分の感受性くらいのパクりです。

実際にhttp://instanode.cloudfoundry.com/にデプロイしてみましたが、どうもxhr-pollingになってしまっています。

やはりnginxの後ろにいるからWebSocketはNGということでFAでしょうか。
やり方があったら知りたいなぁ。

CloudFoundryでNode+MongoDBを動かした

先週末の開発コンテスト24の合間にチャレンジしていたのですがうまく動かず、さきほどようやく動かせました。

コツは3つ。

  1. npm bundleで必要なライブラリをインストールする
  2. 環境変数からMongoDBへの接続情報をとり出す
  3. vmc envでNODE_ENVを設定する

npm bundleで必要なライブラリをインストールする

今回動かしてみたのはexpressのアプリです。(NodeでMongoDBをいじってみた参照)
ちなみに"$ express -s -c sass"のコマンドで生成しています。

Nodeアプリを動かすには動作に必要なライブラリも一緒にCloudfoundryにアップする必要があり、その方法がnpm bundleです。
ちょっと手間がかかるのですが、まずはpackage.jsonというファイルを用意します。

{
  "name": "instanode",
  "version": "0.0.1",
  "dependencies": {
    "express":"",
    "jade":"",
    "less":"",
    "mongodb":"",
    "socket.io":"",
    "instagram":""
  }
}

dependenciesの中に必要なライブラリを列挙しました。バージョンの指定などもできますが、今回はしていません。 このファイルをapp.jsと同じディレクトリに置いて、$ npm bundle と打てば、node_modules/内にライブラリがインストールされます。

これが終わると以下のような構成になります。

$ tree
.
├── app.js
├── logs
├── node_modules
│   ├── connect -> ./connect@1.3.0
│   ├── connect@1.3.0 -> ./.npm/connect/1.3.0/package
│   ├── express -> ./express@2.2.2
│   ├── express@2.2.2 -> ./.npm/express/2.2.2/package
│   ├── instagram -> ./instagram@0.0.4
│   ├── instagram@0.0.4 -> ./.npm/instagram/0.0.4/package
│   ├── jade -> ./jade@0.10.4
│   ├── jade@0.10.4 -> ./.npm/jade/0.10.4/package
│   ├── less -> ./less@1.0.41
│   ├── less@1.0.41 -> ./.npm/less/1.0.41/package
│   ├── mime -> ./mime@1.2.1
│   ├── mime@1.2.1 -> ./.npm/mime/1.2.1/package
│   ├── mongodb -> ./mongodb@0.9.3
│   ├── mongodb@0.9.3 -> ./.npm/mongodb/0.9.3/package
│   ├── qs -> ./qs@0.1.0
│   ├── qs@0.1.0 -> ./.npm/qs/0.1.0/package
│   ├── socket.io -> ./socket.io@0.6.17
│   └── socket.io@0.6.17 -> ./.npm/socket.io/0.6.17/package
├── package.json
├── pids
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       ├── style.css
│       └── style.sass
├── test
│   └── app.test.js
└── views
    ├── index.jade
    └── layout.jade

27 directories, 7 files

あとはapp.jsに以下を書き足して、requireできるようにします。

require.paths.unshift('./node_modules');

環境変数からMongoDBへの接続情報をとり出す

vmc pushとか、MongoDBを有効にする方法とか、そのへんは他にも書いてある記事がありますし指示に従えばできるので省略しちゃいます。

MongoDBへの接続情報は環境変数で渡されます。 以下のようなコードでその値を取り出してMongoDBに接続します。

app.configure('production', function(){
  app.use(express.errorHandler()); 
  var env = JSON.parse(process.env.VCAP_SERVICES);
  var mongo = env['mongodb-1.8'][0]['credentials'];
  client = new Db('db', new Server(mongo.hostname, mongo.port, {}));
  client.open(function(err, p_client){
    client.authenticate(mongo.username, mongo.password, function(){});
  });
});

vmc envでNODE_ENVを設定する

先ほど挙げたコードを見ると、sinatraに慣れた人なら'production'の指定に気づくと思います。

これがまたちょっと曲者で、自分でNODE_ENVを設定しておかないと'production'の方のコードが実行されませんでした。

$ vmc env-add instanode env-add NODE_ENV=production

heroku configと似ていますね。これでOK。

app.jsの全体

最後にapp.jsの全体を載せます。ご参考になれば幸いです。

require.paths.unshift('./node_modules');

/**
 * Module dependencies.
 */

var express = require('express'),
    instagram = require('instagram').createClient(
      'YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET'),
    io = require('socket.io'),
    json = JSON.stringify,
    Db = require('mongodb').Db,
    Server = require('mongodb').Server;

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(express.cookieParser());
  app.use(express.session({ secret: 'YOUR_SECRET' }));
  app.use(express.compiler({ src: __dirname + '/public', enable: ['sass'] }));
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

var client = {};
app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 
  client = new Db('instanode', new Server('127.0.0.1', 27017, {}));
  client.open(function(err, p_client){});
});

app.configure('production', function(){
  app.use(express.errorHandler()); 
  var env = JSON.parse(process.env.VCAP_SERVICES);
  var mongo = env['mongodb-1.8'][0]['credentials'];
  client = new Db('db', new Server(mongo.hostname, mongo.port, {}));
  client.open(function(err, p_client){
    client.authenticate(mongo.username, mongo.password, function(){});
  });
});

// Routes

app.get('/', function(req, res){
  client.collection('images', function(err, collection){
    collection.find({}, {limit: 10}).toArray(function(err, images){
      res.render('index', {
        title: 'index',
        images: images
      });
    });
  });
});

app.get('/popular', function(req, res){
  instagram.media.popular(function(images, error) {
    client.collection('images', function(err, collection){
      collection.remove();
      for (var i=0, l=images.length; i < l; i++) {
        collection.insert(images[i]);
      }
      res.redirect('/');
    });
  });
});

// Only listen on $ node app.js

if (!module.parent) {
  app.listen(process.env.VMC_APP_PORT || 3000);
}

http://instanode.cloudfoundry.com/popularにアクセスすると、

  1. instagramからpopularの写真を取ってきて
  2. MongoDBにそのまま格納し
  3. http://instanode.cloudfoundry.com/にリダイレクトし、
  4. MongoDBから写真のデータを取り出し
  5. 画面に表示します。

次はSocket.ioを試そうと思います。(ライブラリはインストールしていますが、上記コードではまだ使っていません。)

NodeでMongoDBをいじってみた

Node(Node.js)とMongoDBは相性がいいらしいので、試してみました。

とりあえず今回やってみたのはWebフレームワークにexpressを使い、

  1. InstagramのPopularを取ってきて
  2. それをMongoDBにそのまま突っ込んで
  3. あとはその内容を表示するだけ

という簡単なプログラムです。

ローカルだけではなく、サーバーはNodeのPaaSであるJoyent Node(no.de)にも試しにデプロイしてみました。
no.deはてっきりherokuのようなものだと思っていたのですが、SSHでログインしたりMongoDBをインストールしたり、なんてこともできるようです。
実際、今回のサンプルもno.de上にMongoDBをインストールして動かしてみました。

ごめんなさい、no.deの登録とかデプロイの仕方とかは省きます。

ライブラリのインストール

必要なライブラリをインストールします。

(no.deで公開する場合は、SSHでログインして同じように実行する必要があるのかな。
それともライブラリも./lib/あたりに置いて一緒にgit pushしちゃう方がいいのか、そのあたりの作法がわかりません。
今回はSSHでログインしてnpm installしました。)

($ brew install mongodb # Macでbrewな人はこれでMongoDBが入ります。)
$ npm install jade express instagram mongodb

ひな形の生成

まずはexpressコマンドで基本となるコードを生成します。

$ express testapp

メインになるスクリプトはapp.jsというファイル名なのですが、no.deにアップしたときにserver.jsというファイルじゃないと自動的に起動しないみたいなので、server.jsにリネームしました。

express?

expressはRubyのSinatraっぽいフレームワークです。
Sinatraで言う以下にあたるのが、

get '/' do
  # do something...
end

expressではこうです。

app.get('/', function(req, res){
  // do something...
});

今話題のCoffeeScriptで書くともっとSinatraに近くなると思いますが、
それをno.de上で動かすやり方が分からなかったのでとりあえずJavaScriptで書いています。

今回書いたコード(一部)

MongoDBとやり取りするコードは以下のようになりました。
どんどんコールバックでコードが右に伸びてしまいます。もっとかっこいい書き方をぜひ覚えたいところ。

server.js
/**
 * Module dependencies.
 */

var express = require('express'),
    instagram = require('instagram').createClient(
      'Your-Client-Id', 'Your-Client-Secret'),
    Db = require('mongodb').Db,
    Server = require('mongodb').Server;

var app = module.exports = express.createServer();
var client = new Db('popular', new Server('127.0.0.1', 27017, {}));

// 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('/', function(req, res){
  client.open(function(err, p_client){
    client.collection('images', function(err, collection){
      collection.find({}, {limit: 10}).toArray(function(err, images){
        res.render('index', {
          title: 'index',
          images: images
        });
      });
    });
  });
});

app.get('/popular', function(req, res){
  instagram.media.popular(function(images, error) {
    client.open(function(err, p_client){
      client.collection('images', function(err, collection){
        collection.remove();
        for (var i=0, l=images.length; i < l; i++) {
          collection.insert(images[i]);
        }
        res.redirect('/');
      });
    });
  });
});

// Only listen on $ node app.js

if (!module.parent) {
  app.listen(process.env.PORT || 3000);
}

最低限のやり方はなんとなくわかったので、実際にひとつアプリを作ってみています。
せっかくなのでCoffeeScriptで書きたいのでもうちょっと調べてみたいと思います。

Node(Node.js)の環境を整えた

いよいよ4月、新年度が始まりました。
せっかくなので新しいことをやりたいと思い、Node(Node.js)の環境を整えてみました。

環境

  • マシン: MacBook Air (11インチ)
  • Mac OS X 10.6.7 (Snow Leopard)
  • MacPortsではなく、Homebrewを使って環境を作っています。

まずはNode自身のインストール

とても簡単です。

$ brew install node

しばし待つと出来上がりです。

$ which node
/usr/local/bin/node
$ node -v
v0.4.5

npmのインストール

RubyでいうRubyGemsにあたる、パッケージマネジメントシステムです。
これはhomebrewでインストールするのではなく、公式の手順にしたがってインストールします。

$ curl http://npmjs.org/install.sh | sh

僕の環境では以下のようになりました。

$ which npm
/usr/local/bin/npm
$ npm -v
0.3.18

その後、NODE_PATHの設定をします。 (これをやらないとrequire()したときにパッケージが見つかりません。) 僕はzshを使っているので、.zshrcに以下を足しました。

export NODE_PATH=/usr/local/lib/node:$NODE_PATH

設定すべきパスは環境によって異なるかもしれません。 以下のコマンドで確認できます。

$ npm config get root
/usr/local/lib/node
npmの使い方メモ
$ npm help # ヘルプ
$ npm search <文字列> # 検索
$ npm install <パッケージ>
$ npm uninstall <パッケージ>
$ npm update
$ npm view <パッケージ> # パッケージの情報を見る
$ npm ls installed # インストールされているパッケージ一覧

とりあえずはこのくらい覚えておけば足りるかな。
bundleとか、気になるコマンドもあるけれど、おいおい勉強していきたい。

ハンズオンを試す

Node.js 日本ユーザグループからNode.js ハンズオンにリンクが貼られています。
とりあえず@mesoさんのハンズオンをひと通りやってみました。

expressというフレームワークを使ってチャットを作っているのですが、sinatraっぽくていいです。
Socket.ioを使ってリアルタイムの部分まで一気に作れてしまいますので、こないだ作ったTokyo Real-time Photosなんかは、もしかするとNodeで作るほうが簡単かも!?

JavaScript自体はクライアントサイドで慣れたものなので、今後はサーバーサイドやNode特有のものを学んでいきたいと思います。
同じく日本ユーザグループのサイトからリンクされている@yssk22さんのハンズオンやドキュメントを読むのが近道でしょうか。

ホスティング

RubyでいうherokuにあたるサービスはJoyent Nodeが有名みたいです。
とりあえず登録してみましたが、アプリを動かすためのサブドメインをもらうにはCoupon Codeが必要らしく、ただいまそれの発行待ちとなっています。

試してみたらまた書きたいと思います。