hakobera's blog

技術メモ。たまに雑談

さくらのクラウドで Node.js v0.6 を動かすメモ

サーバの作成とSSH公開鍵の登録は済んでいるものとする。
OS は Ubuntu 10.04 LTS 64bit

(随時追記予定)

初期設定

ログイン

普通の Ubuntu と違って root ユーザでログインする点に注意
なんとなく気持ち悪いので、一般ユーザを作成。sudo 権限を設定。SSH公開鍵の設定。

$ useradd hakobera
$ gpasswd -a hakobera admin
$ mkdir /home/hakobera/.ssh
$ cp ~/.ssh/authorized_keys /home/hakobera/.ssh/
$ chown -R hakobera:hakobera /home/hakobera/.ssh

SSH 鍵は新規に作ってもOK。
root でログインしたまま、別のコンソールで新規作成ユーザでログインできることを確認

ssh の root ログインの禁止
$ sudo vi /etc/ssh/sshd_config 

PermitRootLogin no

$ sudo service ssh restart
ファイヤーウォール設定

とりあえず 22番ポート以外を塞ぐ

ufw default DENY
ufw allow ssh
ufw enable
nvm で Node をインストール

あとは普通の Ubuntu と同じ。新規に作成したユーザでインストールする

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install git-core curl build-essential libssl-dev
$ git clone https://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh
$ nvm install v0.6.7

スケールアウト周りの設定

(あとで試す。別記事で書くかも。)

  • スナップショットを使ってサーバの増やし方
  • ロードバランサを利用しての振り分け
  • Redis を使った Session 共有
  • 何台までスケールするか?(ネットワークの限界を探る)

ベンチマーク

以下の記事と同じソースを利用

Node.js の Cluster のベンチマークをとってみた - Scalaとlift のはずだった ・・・

プラン1 (仮想1Core, 2GB)

2000 req/sec くらいで、最低プランの割には結構速い気がする。
ping 打ってみたら、TTL は 20ms くらいだった。

$ ab -n 10000 -c 200 http://xxx.xxx.xxx.xxx:8000/
...
Document Path:          /
Document Length:        12 bytes

Concurrency Level:      200
Time taken for tests:   4.564 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      500050 bytes
HTML transferred:       120012 bytes
Requests per second:    2190.95 [#/sec] (mean)
Time per request:       91.285 [ms] (mean)
Time per request:       0.456 [ms] (mean, across all concurrent requests)
Transfer rate:          106.99 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       19   38  57.8     34    1271
Processing:    22   50  21.9     48     394
Waiting:       22   50  20.7     47     394
Total:         43   88  62.4     81    1334

Percentage of the requests served within a certain time (ms)
  50%     81
  66%     87
  75%     92
  80%     95
  90%    108
  95%    118
  98%    138
  99%    156
 100%   1334 (longest request)
(他のプランも試す予定)

Titanium で ImageView の Cache をアプリ起動時に削除する方法

Titanium 1.8 では ImageView に表示したリモートの画像は自動的に Cache されて、オフラインでも閲覧できて、とても便利なのですが、致命的な欠点があります。

  • 永遠に削除されない
  • キャッシュをクリアするメソッドが提供されていない

もしかしたらバグで今後直るかも知れませんが、現状は自力で対応しなければいけません。

画像のキャッシュなので起動時(もしくは終了時)に全部消してしまってもよいのですが、ある一定期間は保持したいという要望もあったので、指定期間更新が無い画像キャッシュファイルを削除するコードを書いてみました。

Titanium で ImageView のキャッシュを起動時に消す (for iOS only) — Gist

/**
 * 指定した時間更新されていない画像キャッシュを削除する。
 * iOS にのみ対応。
 * 
 * @param {Number} expiredTime キャッシュ保持期限 (ms)
 */
module.exports = function(expiredTime) {
  var cachePath = Ti.Filesystem.applicationDataDirectory + '../Library/Caches/',
      cacheDir = Ti.Filesystem.getFile(cachePath),
      now = new Date().getTime();
    
  if (cacheDir) {
    var files = cacheDir.getDirectoryListing();
    if (files) {
      for (var i = 0, l = files.length; i < l; ++i) {
        var file = Ti.Filesystem.getFile(cachePath, files[i]),
            filename = file.name;
            
        // 画像キャッシュは '.' で終わるファイル名で保存されている
        if (filename[filename.length - 1] === '.') {
          if (now - file.modificationTimestamp() > expiredTime) {
            Ti.API.debug(files[i]);
            file.deleteFile();
          }
        }        
      }
    }
  }
};

あとは起動時か、終了時に以下のようにして呼び出してください。

// 7日間更新がない画像キャッシュを削除する場合
require('deleteImageCache')(7 * 24 * 60 * 60 * 1000);

とりあえず手元の iOS 5.0.1 の iPhone4S の実機で動作することは確認しました。

Node.js と Titanium で SlideShare リモコンを作ってみた

2012/1/11 に開催した 「Node塾 講義その5 Node.js × Titanium」の発表用に SlideConcert というアプリを作って、実際にそれを利用して発表してきました。

個人的にLTをする時に便利だなと思うを機能を実装してみました。

  • スライド発表時は結構歩きまわるたちなのでスライドを操作できるリモコンが欲しい
  • 発表前に「今日のスライドです」と URL をハッシュタグ付きでつぶやくことが多いのでこれを簡単にしたい
  • スライドの内容を手元で確認したい
  • スライドの進行を見ている人で共有したい(進行も含めてスライド共有したい)
  • 聞いている人の感想をリアルタイムで知りたい

言葉で説明するのも難しいので、実際の動作は以下の動画でご確認ください。

サーバは Node.js 、クライアントは Titanium という組み合わせで、構想1時間、実装がアイコンの作成なども含めて年末年始の休日4日間くらいで作りました。全て JavaScript なのでサクサク作れました。

Node塾の発表で使った資料

ソースコード

Titanium のアプリのフルのソースコードはビジネス上の理由もあって当然なんですが、あまり公開されていません。自分もそれで色々と悩んだので、あまり難しいことはしていませんが、これから Titanium を勉強する人の参考になれば、と思って公開しておきます。

サーバ:
hakobera / slideconcert — Bitbucket

クライアント:
hakobera / slideconcert-client — Bitbucket

ちなみに動かすのに設定必要ですが、そこ書き忘れたのであとで追記しておきます。

Node塾その5 Toggter まとめ

Node塾 講義その5 - Togetter

Play framework の writeChunk で UTF-8 以外の文字列を送信するとレスポンスが止まってしまう問題の回避策

2012/7/9 追記 この問題は Playframework 1.2.5 で修正されました。以下は 1.2.4 以下を使う場合にのみ適用して下さい。

最近、仕事で Play framework を本格的に使い始めて、結構惚れ込んでおります。
で、既存のアプリをどんどん Play で書きなおしているんですが、CSV ファイルの文字エンコーディングの所で躓いたので、共有しておきます。

Play で大容量のデータをストリームでダウンロードさせるには response.writeChunk() メソッドが便利です。しかし、このメソッド、response.encoding を UTF-8 以外にすると止まってしまいます。(最初の chunk を送信する所で止まってしまう)

世の中には「Excel で読める CSV ファイルを出力する」というお仕事が多々あるわけで、ようするに MS932 でレスポンスを送信しなければならないので、この状態だと困ってしまいます。

調べていくと、PlayHandler.java の LazyChunkedInput#nextChunk() メソッドでレスポンスを書き出す所で String#getByte() を引数なしで呼出し = 強制的に UTF-8 で出力、となってしまっているからでした。

ソースの該当箇所としてはここ ( LazyChunkedInput#nextChunk() )
https://github.com/playframework/play/blob/1.2.x/framework/src/play/server/PlayHandler.java#L1014

このメソッドを以下のように修正して、Play をビルドしなおせばOK。

public Object nextChunk() throws Exception {
    if (nextChunks.isEmpty()) {
        return null;
    }
    return wrappedBuffer(((String) nextChunks.poll()).getBytes(Response.current().encoding));
}

同じクラスの writeChunk() メソッドの方ではエンコーディンを意識しているのでバグ(考慮漏れ)だと思います。ここで設定しているデータサイズと、nextChunk で送信しているデータサイズがずれるのが、転送が止まってしまう原因だと思われます。

public void writeChunk(Object chunk) throws Exception {
    String message = chunk == null ? "" : chunk.toString();
    StringWriter writer = new StringWriter();
    // ここではエンコーディングを見ている
    Integer l = message.getBytes(Response.current().encoding).length + 2;
    writer.append(Integer.toHexString(l)).append("\r\n").append(message).append("\r\n\r\n");
    nextChunks.offer(writer.toString());
}

Play 使っている人は MS932 の呪縛から逃れられている人も多いと思いますが、参考になれば幸いです。もう少し使ってみて問題がなさそうなら、pull リクエストを送ってみようと思います。

1/7 追記
pull リクエスト送ってみました。
[#1363] Fix reponse.writeChunk() cannot send multibyte string data when response.encoding is not UTF-8 by hakobera · Pull Request #430 · playframework/play · GitHub

2012年の目標

みなさま、あけましておめでとうございます。
忘れないうちに、今年の目標を書いておきたいと思います。

  • Node.js でサービスを作る
    • 個人でも仕事でも良いのですが、仕事として実現できる方向で頑張っていきたいと思います。
  • Node塾 を一年間継続して開催する
    • とりあえず、2月前半(5,6,7回目)までは予定を立てました。
  • ハードウェアと組み合わせた何かを作る
    • arduino あたりから初めてネットワーク経由でなにかハードを操りたいです
  • 本を全て電子書籍化する
    • 裁断機とスキャナを買ったけど、3割くらいで止まっているので頑張る
    • 新しく買う場合、電子書籍を優先的に買う

状況次第で色々と変わるとは思いますが、今年末に振り返ってどれだけ実現できているか、楽しみです。

2011 年の振り返り

今年は自分にとって色々と転換点だったので、2011年を振り返ってみて、やったこと、思ったことなどをメモとして書いておこうと思います。

MacBook Air

今までメインで使っていた Thinkpad X61 の HDD が壊れたために、Apple Store の初売りセールで MacBook Air を購入しました。実はこれが自分にとって一番の大きな変化であり、これがなかったら今年の流れはなかったはず。

今では Mac なしの開発なんて考えられず、会社の PC も Windows から Mac に変えてもあるくらい好きになっていますが、実は1年前はまだ Mac 持っていなかったんですね、しみじみ。

Kinect

今年の前半の個人的な話題は Kinect であり、自分の勉強会 LT デビューも Kinect 関連でしたし、この後に書く Node.js との出会いもきっかけは Kinect でした。

去年から HTML5CanvasWebGL などのビジュアライゼーション系の技術に興味があって、WebGLChrome で有効になったのを機に触っていたのですが、その時に目にしたのが以下の ndruger さんのブログです。

Kinectを使ってブラウザのWebGL内を動いてみた - 最高のコンピューティング環境とは?

上記のブログでは、Windows 環境でのデモだったので、これの Mac 版を作りたいと思ったのが Kinect + Mac の始まり。で作ったら、なんだか誰かに聞いて欲しくて、発表したのが以下です。

WebGL勉強会 第5回で発表して来ました - Scalaとlift のはずだった ・・・

Mac 買わなかったらこれ作らなかっただろうし、これ作らなかったら勉強会で LT とかすることはなかったと思うので、なんだか運命だなぁ、と思っています。最近 Kinect に関してはご無沙汰していますが、来年はハードを使った何かを作りたいと思っているので、触る機会を増やしたい思っています。

Node.js

今年の後半は完全に Node.js 一色でした。Node.js は去年までは「WebSocket サーバがすごく簡単に作れるもの」という認識でしかなく、Kinect のデモを作った時に、たまたま ndruger さんのデモで Node が使われていたのをきっかけに深く触るようになりました。で、ついには Kinect 枠で東京Node学園 1時限目で LT したのが、Node.js コミュニティの方と交流するきっかけになりました。

東京Node学園 1時限目で LT してきました - Scalaとlift のはずだった ・・・

あと、最初に作った npm モジュールは Kinect 連携の Native Module なので、意外と無茶してたと思います。

Kinect を操作できる Node.js モジュールを作ってみた - Scalaとlift のはずだった ・・・

この時点で Node.js の面白さに魅せられてしまって、いつのまにか 東京Node学園祭2011 の実行委員になっていました。こうした流れで実行委員の方を中心に Node.js に興味を持つ優秀なエンジニアの方と交流ができたのはとても有意義でした。

コミニティ活動には密かに憧れていたのですが、実際の活動としては参加できていなかったので、その夢が1つ実現できたことは、とても良かったと思っていますし、今後も継続していきたいと思います。

勉強会

去年から勉強会に参加するようになったのですが、今年は上記のように LTの発表、そして勉強会そのものの主催にもやりました。勉強会を主催するために、発表者の方に突撃したり、会場を借りるために色々模索したり、会社の会議室を使えるように上長と交渉したり、なんだか技術以外でも面白かったです。また、勉強会を通じて色々なエンジニアの方と交流が持てたのはとても良かったと思っています。

勉強会のLTや開催は、本当に世界が広がるので、みなさんホントに挑戦してみると良いと思います。

以下、一部重複も含みますが、今年に自分がやった LTと主催した勉強会に関連するリンクです。

まとめ

というわけで、今年は「HTML5」への興味から始まって、Node.js までの流れは以下のような感じでした。

(HTML5) -> JavaScript -> WebGL -> Kinect -> WebSocket -> Socket.IO -> Node.js

来年は Node.js から何につながっていくのか楽しみです。

mocha と Jenkins で Node.js の CI 環境を構築する

 最近、mocha をつかってテストを書くのが楽しくなってきました。でも、テストの数が増えてくるとローカルでの実行だけでなく、CI 環境が欲しくなりますよね。github にあげられるようなプロジェクトだったら、Travis CI も良いですが、実際に仕事で使うとなると、既存の Jenkins と組み合わせてやる必要ができてきたので、実際にやってみました。

 基本的な手順は以下の通りです。

  1. mocha でテスト結果を TAP 形式でファイルに出力する
  2. 出力したファイルを Jenkins の TAP Plugin に読み込ませる

 簡単ですね。

実際にやってみた

というわけで、以下のような最小構成で試してみます。

myapp
 |- lib
 |    |- calc.js
 |
 |- test
 |    |- calc.test.js
 |
 |- package.json

ここには書いていませんが、実際は git で管理していて、node_modules を .gitignore で管理外に指定しています。

calc.js
exports.add = function(x, y) {
  return x+ y;
};
calc.test.js

結果を見るためにわざと2つめのテストは失敗させています。

var calc = require('../lib/calc');

var should = require('should');

describe('calc', function() {
  describe('.add', function() {
    it('should return sum of 2 arguments', function() {
      var result = calc.add(1, 2);
      result.should.equal(3);
    });

    it('error', function() {
      var result = calc.add(1, 3);
      result.should.equal(3);
    });
  });
});
package.json
{
  "name": "myapp", 
  "version": "0.0.1", 
  "devDependencies": {
    "mocha": "0.7.0", 
    "should": "0.4.2"
  }
}
Jenkins の事前準備
  • node と npm を Jenkins の実行ユーザから実行できるように PATH を設定しておく
  • Jenkins に TAP Plugin をインストールしておく
Jenkins の設定

フリースタイルプロジェクトを作成し、SCM の設定をし、そしてビルドでシェルの実行を選択し、以下を入力します。

npm install -d
$WORKSPACE/node_modules/mocha/bin/mocha --reporter tap > mocha-test-results.txt

f:id:scalar:20111219215447p:image

 npm install をテストの前に実行しておくことで、依存ライブラリが自動的にインストールされます。テストに使う mocha 自体も node_modules 配下にローカルインストールされたものを利用します。(複数プロジェクトで異なる Node のバージョンを使っている場合は、npm install の前に nvm などを使って明示的に Node のバージョンを切り替える処理をすることもできます。)

 最後にビルド後の処理で、Publish TAP Results にチェックを入れて、上記で出力したファイル名を指定します。

f:id:scalar:20111219215448p:image

実行結果

プロジェクトの状態
f:id:scalar:20111219221431p:image

TAP Test Result
f:id:scalar:20111219221433p:image

課題とまとめ

 mocha と TAP plugin の制限としては、結果ファイルが1つしか取れないので、複数のテストファイルがあった場合、どのファイルのテストが失敗したのかわかりづらい、というのがあります。

 この辺は、テストをファイルごとに実行するツールを書いて、tap2junit を使って JUnit 形式に変換したものを Jenkins に読み込ませれば解決するのですが、そこまで大規模でない場合は今回紹介した手法で十分かと思っています。

 Node.js で CI できるようになったので、仕事でも安心して使えるようになりました。mocha と Jenkins いいね!