タグ「node.js」が付けられているもの

以前の記事で紹介した、 connect-cache-manifestですが、 実は自分でちゃんと使ったことがありませんでした。

今回、一通り動くところまで使ってみたので、参考までに紹介します。 Expressと Jadeを使う前提です。

多くの場合、ディレクトリの構造は次のようになるのではないでしょうか。

|-- app.js
|-- public
|    |-- js
|    |    |-- foo.js
|    |    |-- bar.js
|    |-- css
|    |    |-- foo.css
|    |    |-- bar.css
|    |-- images
|         |-- foo.png
|         |-- bar.png
|-- views
     |-- xxx.jade
     |-- yyy.jade

このような場合での、connect-cache-manifestの設定は次のようになります。

var express = require('express');
var cacheManifest = require('connect-cache-manifest');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(cacheManifest({
  manifestPath: '/application.manifest',
  files: [{
    dir: __dirname + '/public',
    prefix: '/static/'
  }, {
    file: __dirname + '/views/xxx.jade',
    path: '/html/xxx.html'
  }, {
    file: __dirname + '/views/yyy.jade',
    path: '/html/yyy.html'
  }],
  networks: ['*'],
  fallbacks: []
}));
app.use('/static', express.static(path.join(__dirname, 'public')));
app.get('/html/:name', function(req, res) {
  res.render(req.params.name);
});

これで、/application.manifestが生成されるようになります。 使ってみて不満が二つありました。一つは、emacsやvimで*~のバックアップファイルができた場合にそれもマニフェストに入ってしまうこと、もう一つは、jadeファイルが複数ある場合にリストアップしなければならないことです。そのうち気が向いたら改良しようかな。

これで、HTML5のマニフェストを使いつつ、ほとんど気にせずに普通にコーディングができます。

そうそう、ついでにもう一つ。ファイルを新規に追加したときは、nodeを再起動しないといけません。これは、node-supervisorやnodemonを使えば解決できるかもしれません。試していませんが。

JavaScriptの正規表現のパフォーマンスに興味があったので調べてみました。 現時点での考察であり、JavaScriptの処理系による違いもありますし、将来的に最適化が進んだ場合は結果が変わることがあると思いますので、ご注意を。

まず、初めに見つけた記事が、これ。

http://stackoverflow.com/questions/9750338/dynamic-vs-inline-regexp-performance-in-javascript

簡単に言うと、

/[a-z]/.exec(str);

と

new RegExp('[a-z]').exec(str);

で、パフォーマンスが違うけど何で?という質問でした。 結論から言うと、これは等価ではありません。 前者は正規表現を一度しか評価しないのに対し、後者は毎回RegExpオブジェクトを作ります。

どうやら、次の3つ関数はほぼ等価のようです。

function func1(str) {
  return /[a-z]/.exec(str);
}

var re2 = /[a-z]/;
function func2(str) {
  return re2.exec(str);
}

var re3 = new RegExp('[a-z]');
function func3(str) {
  return re3.exec(str);
}

「ほぼ」というのは、実際に中身を見たわけではないからです。ただ、処理時間から推測しただけです。

さて、ここまでは理解できていたのですが、実はこの状態だと正規表現のコンパイルは行われていないようなのです。

どういうことかと言うと、

var re4 = new RegExp('[a-z]');
re4.compile();
function func4(str) {
  return re4.exec(str);
}

とすると、処理時間が短くなるケースがあるのです。 これはちょっと意外でした。正規表現のコンパイルというのは最適化の過程で勝手に行われるのかと思っていました。もしかしたら、最適化が行われるのはもっと長い時間処理が回っていないからなのかもしれませんが。

まずは、nodeでテストをしてみました。

https://gist.github.com/dai-shi/7394062

このベンチマークを走らせたら、一つの書き方だけ明らかに速くなりました。それが上記の書き方です。nodeのバージョンをv0.8, v0.10, v0.11と試しましたが、傾向はどれも同じです。

次にjsperfでも試しました。

http://jsperf.com/classname-check-regex-vs-indexof

ちょっと目的が違うのでindexOfとの比較が入っていますが、RegExpの方は大体同じです。 Chromeでの実行結果は、compile()を実行したケースがわずかに速くなりました。試している正規表現が違うので、コンパイルが効きにくいのかもしれません。 Firefoxでの実行結果も、compile()のケースが速くなりました。こちらは10%以上。しかも、全体的にChromeの実行結果より速い。ちょっと意外です。

さて、別のベンチマークも見てみましょう。

http://jsperf.com/javascript-compiled-regex/15

これは、globalマッチのテストケースも入っているのでちょっと分かりにくいですが、それを除けばやっぱりcompile()しているケースが速いです。Chromeで20%ほどアップ。Firefoxで30%ほどアップ。

ちなみに、globalマッチはChromeの場合は最速で、Firefoxの場合はもっとも遅いようです。これは使い方を悩みますね。

これらのベンチマーク結果を踏まえてどういう方針にするかですが、 まず、クライアントサイドのコードの場合は、10%~30%程度速くなることにどれくらい意味があるかですが、ばらつきもあるので、よほどパフォーマンスにシビアでなければ、inline型の正規表現でよいように思います。読みやすいといのが最大のメリットです。つまり、

/[a-z]/.exec(str);

こういう感じです。

一方、サーバサイドのコード、つまり、nodeの場合は、2倍近く速くなるケースもあり、また通常サーバサイドのコードはパフォーマンスを重視することから、現時点ではcompile型の正規表現を使うことに意味があるかもしれません。つまり、

var re = new RegExp('[a-z]');
re.compile();
function func() {
  return re.exec(str);
}

こういう感じです。関数の外で定義しないといけないので、場合によっては気をつける必要があります。変数名の衝突や、意図しない書き換えなど。

var func = (function() {
  var re = new RegExp('[a-z]');
  re.compile();
  return function() {
    return re.exec(str);
  };
})();

のようにする手もありますが、ますます読みにくくなっていきますね。

本来なら、re.compile()がなくてもコンパイルしてくれたらいいように思うのですが、コンパイルそのものにコストがかかるケースもあるので一回しか使わないような場合はコンパイルすべきじゃないということですね。JITなどの最適化はあまり詳しくないのですが、もしかしたら、次第にコンパイルされていくのかもしれません。

と、ここまで書いたところで見つけたのですが、

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features

あれ、compile()は非推奨なのですね。ということは、やっぱり処理系が最適化の過程でコンパイルしていくのが筋ということですね。 非推奨のcompile()を使うとそれを強制的に動かせるのでベンチマークコードの場合は有利といったところでしょうか。

というわけで結論は、よほどの理由がなければ、inline型を使っておけばよいでしょう。new RegExp()を使ってもパフォーマンスが悪くなるわけではないので、場合によっては使ってもよいでしょう。

ぐるっと回って振り出しに戻った感じですが、個人的にはすっきりしました。

FacebookのGraph APIで写真を投稿する場合には、/ALBUM_ID/photosのエンドポイントにHTTP POSTするのですが、そのときにmultipart/form-data形式にする必要があります。

https://developers.facebook.com/docs/reference/api/publishing/

詳しくは上記公式ドキュメントを参照してください。

ところで、以前紹介したfacebook-node-sdkでは、form-dataがサポートされていません。

https://github.com/Thuzi/facebook-node-sdk/issues/28

このようにIssueにもなっていましたが、現時点ではサポートされていないようです。PRしてくれとか言っているので、ちょっとのぞいてみましたがrequestが奥の方に埋まっているので、ちょっと手間がかかりそうでした。

とりあえず、動くものを作ってみようと思ったので、上記ライブラリは使わずに普通にrequestだけでやってみたところ、簡単にできてしまいました。

コードを貼り付けておきます。

var request = require('request');

function postMyPhoto(accessToken, message, file, callback) {
  var form = request.post('https://graph.facebook.com/me/photos', function(err, res, data) {
    if (err) return callback(err);
    if (!data || data.error) return callback(data ? data.error : 'unknown error');
    callback(null, data);
  }).form();
  form.append('access_token', accessToken);
  form.append('message', message);
  form.append('source', fs.createReadStream(file));
}

実は、streamを初めて使いました。使ったとは言わないか。 しかし、フローが分かりにくいです。明示的にstreamの終了はないものなのでしょうか。 興味はあるのでまたの機会に勉強しようと思います。

だいぶマニアックな情報ですが備忘メモとして書いておきます。

socket.io 1.0系の話です。0.9系は分かりません。

おそらく、普通のブラウザでsocket.io-clientを使う場合はクッキーは共通なので困らないと思いますが、node.jsからsocket.io-clientを使う場合はどうやるのでしょうか。

ところで、node.jsからsocket.io-clientを使いたい理由は、テストコードのためです。socket.io-clientはよくできていて、ブラウザでもnode.jsでも動くようです。

なぜ、クッキーを設定したいかというと、セッション維持のためです。サーバ側でなんらかの方法で認証して、セッションIDをクッキーに格納するというのはよくある方法だと思いますが、それをテストコードで動作確認したいわけです。

さて、本題です。socket.io-clientはengine.io-clientに依存していて、engine.io-clientはxmlhttpreuestやws (WebSocket)のライブラリに依存しています。socket.io-clientからそれらのライブラリにクッキーの情報を明示的に渡す手段は見つかりません。そもそも、テストコードを書くときくらいしか使わないのでしょう。

いろいろ調べた結果、engine.io-clientのv0.7.0からagentをオプションで渡せるようになっていました。socket.io-clientからengine.io-clientへはそっくりそのままオプションが引き継がれます。

agentというのは、httpのソケットを管理する仕組みなのですが、ちょっとそこを用途外利用することにしました。つまり、リクエストを登録しにきたところをフックして、クッキーのヘッダを追加してしまうのです。

コードはこんな感じです。

var http = require('http');
var socket_io_client = require('socket.io-client');
var cookie = 'aaa=123';
var myAgent = new http.Agent();
myAgent._addRequest = myAgent.addRequest;
myAgent.addRequest = function(req, host, port, localAddress) {
  var old = req._headers.cookie;
  req._headers.cookie = cookie + (old ? '; ' + old : '');
  req._headerNames['cookie'] = 'Cookie';
  return myAgent._addRequest(req, host, port, localAddress);
};
var client = socket_io_client('http://localhost:12345/', {
  agent: myAgent
});

クッキードメインのチェックは省略されています。テストコード以外では使えないでしょう。また、将来的に仕様が変わったら使えなくなる可能性もあります。

backbone.jsのemulateHTTPというのは、PUTやDELETEをPOSTに変換するものです。なぜそんなことが必要かというと、PUTやDELETEが通らないHTTP Proxyがあるからです。

emulateHTTPでは、X-HTTP-Method-Overrideというヘッダーを使います。正確には、_methodというパラメータも追加されるのですが、それは対応しないものとします。X-HTTP-Method-Overrideはnode.jsのexpressやconnectでサポートされます。

app.use(express.methodOverride());

とすれば有効になります。

さて、こんな機能は当然angular.jsで用意されていて一行くらいの設定で使えるようになるだろうと思っていたら、そんなことはありませんでした。 検索すると、

http://manikanta.com/blog/2013/07/25/decorate-angularjs-http-service-to-convert-put/

がヒットしました。 これでできそうかと思ったのですが、実装が中途半端です。 具体的には、

$http.put({...});

のときは想定どおりに動くのですが、

$http({method: 'PUT', ...});

のような使い方ではうまく動きません。 そこで書き直してみることにします。

var App = angular.module('App');
App.config(['$provide', function($provide) {
  $provide.decorator('$http', ['$delegate', function($delegate) {
    var $http = function(config) {
      var method = config.method.toUpperCase();
      if (method === 'DELETE' || method === 'PUT') {
        config.headers || (config.headers = {});    
        config.headers['X-HTTP-Method-Override'] = method;
        config.method = 'POST';
      }
      return $delegate(config);
    };
    angular.extend($http, $delegate);
    $http.delete = function(url, config) {
      return $http(angular.extend(config || {}, {method: 'DELETE', url: url}));
    };
    $http.put = function(url, data, config) {
      return $http(angular.extend(config || {}, {method: 'PUT', url: url, data: data}));
    };
    return $http;
  }]);
}]);

こんな感じになりました。 もう少しスマートな方法はないかと、思ってしまいますね。 しかし、おかげで$provide.decoratorの使い方を覚えました。

上記コードは動作確認してないので、使用レポートをお待ちします。

いつも思いますが、nodeではモジュール探しが大変です。

今回は、icalフォーマットを出力するためのモジュールを探します。 icalというのは通称で、通称としては他にもicalendarとかicsとかあって紛らわしいです。iCalというソフトの名称もあるのでなおさら。RFC5545と言えば一意に特定できるでしょう。

npmで探しました。上記のようにタグがicalだったりicalendarだったり統一されていません。それでも、5個見つけました。

  • peterbraden/ical.js これはパーザしかなく、今回の目的には使えず。
  • tritech/node-icalendar Staræ•°44でトップ。しかしIssue#5が無視されているのが気になるところ。
  • sunrise/vobject-js vobjectという名前なので一瞬分からない。新しそう。
  • shanebo なんとイベント一つ分しか出力できない。
  • sebbo2002/ical-generator APIはシンプルで分かりやすそう。しかし、save, saveSync, serveは余計な機能ではないか。

セオリー通りにいくとStar数トップのnode-icalendarでいくのですが、Issue#5が気になるのとドキュメントが貧弱なのがマイナスポイントです。ドキュメントの貧弱さは自分のプロジェクトをみると、人のこと言えないですが。

今回は、vobject-jsで行くことにしました。現時点でStar数2!今後の発展に期待します。

node.jsで作ったサンプルアプリをOpenShiftで動かそうとして、苦労した話を以前紹介したのですが、その後プロセスが落ちてしまうことが2度ありました。クラウドなのに。

一度目は、

~/nodejs/configuration/node.env

の環境変数の設定が書き戻ってしまう問題でした。 おそらく、OpenShift側のシステム更新のタイミングかなにかで、~/nodejsが丸ごと置き換わっているのだろ思われます。 このときは、症状は分かったもののそんなに頻繁に起こらないだろうと思って、またそのファイルを手動でいじって、プロセスを再起動して動かしました。

ところが、二度目が来たのです。 しかも今回はnode.envが書き換わるだけでなく、他にも問題がありました。 色々調べた(結局、起動スクリプトを読んだ)結果、以前はpackage.jsonに

"script": { "start": "node app.js"}

と書いておけばよかったのですが、今回の更新(?)でその設定は読まれなくなり、代わりに、

"main": "app.js"

を書かなければいけなくなりました。 これで一件落着。

しかし、また次の更新タイミングで、node.envが書き換わってしまうことは目に見えています。そこで、別の方法を探しました。ドキュメントを読んでみると、どうやら、クライアント側のコマンドツールであるrhcを使うとrhc set-envのようにすることで、環境変数が設定できるらしいのです。でも、rhcを入れて試すのは面倒なので、なんとかsshでファイルを編集するだけでできないか調べました。

そして、見つけました。前回調べた時は見つからなかったのですが、ありました。

~/.env/user_vars/

です。ここに環境変数ごとにファイルを作成しておくと、ちゃんと読み込まれるようです。~/.env/直下のファイルを参照すると分かります。直下のファイルはroot権限でしか変更できないのですが、user_varsの下は書き込み権限があるのです。

ぜひ、お試しください。

ところで、起動スクリプトを読んでいるときに、node010という文字列を見つけました。どうやら、OPENSHIFT_NODEJS_VERSIONという環境変数が0.10のときはnode010になるようで、もしかしたらnode-v0.10.*が動くのか、と期待します。試してみる?どうする?またの機会にしましょう。

しかし、こういう話ってどこかのドキュメントをちゃんと読めば載っているのでしょうか。

久しぶりの記事です。

social-cms-backendのテストコードをMochaで書いていますが、Facebookのログイン周りのテストをするのに、Facebookのテストユーザを使いました。

Facebookのテストユーザについては、

http://developers.facebook.com/docs/test_users/

にドキュメントがあります。

手順は書いてある通りで、(1)アプリのアクセストークンを取得、(2)テストユーザを作成、(3)専用ログインURLを開いてログインする、だけです。

requestを使ってこれらのステップを実行していたのですが、たったこれだけのことをやるのに何時間もはまりました。

1と2は何の問題もなかったのですが、3がうまく行かず、Facebookのログインページにリダイレクトされてしまうという問題でした。

解決方法は、User-Agentを設定する、です。

ドキュメントには書かれていません。もし同様の問題を抱えている人に参考になればと思います。参考になるか分かりませんが、その時点でのテストコードは、

https://github.com/dai-shi/social-cms-backend/blob/c678043a2ad0ce45bc51b576d77e53c7eac658e5/test/80_facebook_login_test.js

です。

初めに断っておきますが、今回の調査は、node v0.11.5 (Linux)で試したもので、他の環境では異なるかもしれません。さらに、今後のバージョンアップによっては全く異なる結果となることも十分ありえます。ご注意を。

ES6にgeneratorsが入るとのことで、JavaScriptでcontinuationが使えないかなぁと思っていた自分としては、興味を持ちました。

ちなみに、generator, coroutine, continuationの順に記述力が上がるそうです。こちらが参考になるかもしれません。

さて、generatorsの説明は他に任せるとして、いきなりコードです。

function* es6_generator() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  return 5;
}

これを複数回実行して時間を計測するコードはこちら。

console.time('es6_generator');
for (i = 0; i < 1000000; i++) {
  iterator = es6_generator();
  obj = {};
  while (!obj.done) {
    obj = iterator.next();
  }
}
console.timeEnd('es6_generator');

実行結果は、

es6_generator: 360ms

でした。うちのマシンは非力なので、最近のマシンならもっと速いかもしれません。

これと同等のコードをgeneratorsを使わずに書くとどうなるでしょうか。おそらく、

function hand_generator1() {
  var iterator = {};
  iterator.next = function() {
    iterator.next = function() {
      iterator.next = function() {
        iterator.next = function() {
          iterator.next = function() {
            return {value: 5, done: true};
          };
          return {value: 4, done: false};
        };
        return {value: 3, done: false};
      };
      return {value: 2, done: false};
    };
    return {value: 1, done: false};
  };
  return iterator;
}

こんな感じになるのでしょう。 これを同じように実行したら、

hand_generator1: 4529ms

となりました。これが等価なコードなのかは問題ですが、 少なくともhand_generator1のように書くよりは、 es6_generatorの方が速いとうことですね。

しかし、この単純な例だとfunctionをイテレーション毎に生成する必要はなくて、もっと効率的に書けます。

function hand_generator2() {
  var array = [1, 2, 3, 4, 5];
  var index = 0;
  var iterator = {
    next: function() {
      return {value: array[index++], done: array.length <= index};
    }
  };
  return iterator;
}

この場合の実行結果は、

hand_generator2: 265ms

となりました。es6_generatorと比べてどちらが読みやすいと思うかは人次第でしょうか。できることが違うので比較の対象にならないですが。

さて、少し話がややこしくなりますが、もともとは末尾再帰をやりたかったのです。末尾再帰の説明は他に任せるとして、factorialをgeneratorsを使って書いてみました。

function fact_tail_loop1(x) {
  var fact_tail_sub = function* (x, r) {
    if (x === 0) {
      return r;
    } else {
      yield fact_tail_sub(x - 1, x * r);
    }
  };
  var c = fact_tail_sub(x, 1);
  var d = {};
  while (!d.done) {
    d = c.next();
    c = d.value;
  }
  return d.value;
}

これを見て、ふーん、とか、おー、とか思わない場合は、この先を読んでもつまらないかもしれません。

実行コードはこちら。

console.time('fact_tail_loop1');
console.log(fact_tail_loop1(3));
console.log(fact_tail_loop1(5));
console.log(fact_tail_loop1(10));
console.log(fact_tail_loop1(100));
console.log(fact_tail_loop1(1000));
console.log(fact_tail_loop1(10000));
console.log(fact_tail_loop1(100000));
console.log(fact_tail_loop1(1000000));
console.timeEnd('fact_tail_loop1');

実行すると次のようになりました。

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop1: 217ms

Infinityになってますが、ちゃんと計算はしているようです。ちなみに普通の再帰やproperでない末尾再帰の場合は、最後の方は計算できずにエラーになります。

さて、generatorsを使わないで書き直してみました。

function fact_tail_loop2(x) {
  var fact_tail_sub = function(x, r) {
    return {
      next: function() {
        if (x === 0) {
          return {
            value: r,
            done: true
          };
        } else {
          return {
            value: fact_tail_sub(x - 1, x * r),
            done: false
          };
        }
      }
    };
  };
  var c = fact_tail_sub(x, 1);
  var d = {};
  while (!d.done) {
    d = c.next();
    c = d.value;
  }
  return d.value;
}

同じように実行すると、

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop2: 796ms

になりました。しかし、このコードはオブジェクトの生成が多すぎます。改良版はこちら。

var LoopCont = function(f) {
  this.f = f;
};

function fact_tail_loop3(x) {
  var fact_tail_sub = function(x, r) {
    if (x === 0) {
      return r;
    } else {
      return new LoopCont(function() {
        return fact_tail_sub(x - 1, x * r);
      });
    }
  };
  var c = fact_tail_sub(x, 1);
  while (c instanceof LoopCont) {
    c = c.f();
  }
  return c;
}

これを実行すると、

6
120
3628800
9.332621544394418e+157
Infinity
Infinity
Infinity
Infinity
fact_tail_loop3: 63ms

となりました。いかがでしょう。

私見ですが、generatorsのパフォーマンスは状況次第といったところでしょうか。v8の最適化が進めば、様々なケースで改善されるかと思われます。

しかし、generatorsを使って末尾再帰やcontinuationを実装できないかなぁ、と期待したのでちょっと残念でした。やっぱり言語レベルでサポートしてほしいですね。

個人的には、yield expressionは構造が分かりにくく感じ、好きになれそうにないので、直接使うことはないかと思います。うまく隠蔽してくれるライブラリがあればいいのかもしれません。

ここまで読んでくれた方は、 https://github.com/dai-shi/continuation.js にも興味を持っていただける可能性がありますので、 よろしければご覧ください。

これまでnode.jsのアプリのデプロイにはHerokuを使っていたのですが、 無料アカウントでは上限があるらしいのと、そもそも一つのサービスに依存するのもよろしくないので、別の候補を探しました。

いくつかあるようなのですが、昔は無料でも今は終わっていたり、 今も無料だけどベータサービスなので期限があったりと、 あまりぱっとしません。ビジネスモデルとしてフリーミアムがちゃんと まわってないと持続性がないでしょう。

一番、有望かなと思ったのがRedHatの OpenShift です。

正式サービスしてからはまだ間もないですが、無料のアカウントがサポートなしという形で残っているので、今後も継続することが期待できます。 ただ、無料でホストできるアプリは2つだけのようで、厳しいですが。

さて、 NodeとAngularを使ってTwitterクローンを15分で作るスクリーンキャスト で作ったTwitterクローンをOpenShiftの上にのせることにしました。

まず、OpenShiftのアカウント登録。簡単でした。次に、sshの公開鍵の登録。これもコピペするだけ、簡単。

続いて、アプリの作成。 domainが空いてないと登録できないので、何度もトライしました。 動的にチェックしてくれればいいのに。このあたり、まだまだな感じですね。

続いて、ソースのアップロード。gitのURLが表示されるので、それを、リモートリポジトリとして登録します。

$ git remote add openshift <URL>

で、PUSHします。

$ git push openshift master

ここまで大きな問題なし。このあと大変でした。

まず、今回のアプリはFacebookと連携するのでその情報を入れないといけないのですが、それはリポジトリにはいれるものではありません。 Herokuでは環境変数を使っていました。 openshiftでも環境変数を使えるようで、

$ vi ~/app-root/data/.bash_profile

で追記すれば環境変数を設定できます。 しかし、このままだとnodeを起動するときにその設定は読み込まれないようです。

openshiftにはpre_startというスクリプトがあって、それを使って、 source .bash_profileするようにしたのですが、うまくいきませんでした。

調べているうちに別の方法を見つけました。nodeでしか使えないようなのですが、

$ vi ~/nodejs/configuration/node.env

で環境変数の設定をすることができます。中身は、.bash_profileでの環境変数の設定と同じです。 これは、うまく行きました。

次の問題は、EACCESエラーです。なぜか、webサーバの起動でエラーになるのです。これは、どんぴしゃの記事がありました。

https://www.openshift.com/forums/openshift/other-threads-didnt-help-503-service-unavailable

OpenShiftではINADDR_ANYでlistenできないようです。 app.jsを次のように変えて対処しました。

app.set('port', process.env.OPENSHIFT_NODEJS_PORT || process.env.PORT || 3000);
app.set('ipaddr', process.env.OPENSHIFT_NODEJS_IP);
....
http.createServer(app).listen(app.get('port'), app.get('ipaddr'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

これで、アプリが正常に動作しました。

慣れてしまえば、OpenShiftでもHerokuと同様の使用感でnode.jsのアプリをデプロイできそうです。ただ、困りそうなことが一つだけあります。OpenShiftで用意されているのは、node v0.6なのです。現在の安定版がv0.10なので2世代も前です。Herokuではv0.10もv0.8も使えます。開発スピードが速いnode.jsエコシステムでは致命的かもしれません。npmとかパッケージによっては動かないケースがでてきそうです。

今回は試しませんでしたが、node.jsカートリッジとは別に、DIYカートリッジというのがあるらしく、それを使えば好きなバージョンを入れることができるそうです。そもそも、Herokuではできないsshができるのでいろいろ自由度は高そうですね。

もう一つ、Herokuと比べて不便を感じたのは、デプロイの遅さです。pushしてから待つこと数分かかったような気がします。Herokuってすごいんだなぁ、と思った瞬間。

実際に今回デプロイしたアプリはこちらです。

http://twitterclonesample-nodeangularapp.rhcloud.com/

なんにしてもPaaSの選択肢が増えることはよいことです。 興味ある方は試してみると良いでしょう。


9/10追記。 ~/nodejs/configuration/node.envのファイルですが、いつのまにか勝手に戻っていて、プロセスが8月後半から動いていなかったことが判明しました。とりあえず、再度書いておきましたが、また、勝手に上書きされてしまうかもしれません。もっといい方法があるのでしょうか。

昨日、 NodeとAngularを使ってTwitterクローンを15分で作るスクリーンキャスト を書いたのですが、ライブラリの紹介をし損ねたので紹介します。

そもそもの動機は、Twitterクローンを簡単に作りたいと思ったことが発端です。 そこで、なにかいいライブラリはないかと色々調べたら、Railsもどきのフルスタックフレームワークがいくつか見つかりました。

他にもいっぱいあるのですが、気になったのがこのあたりです。

ところが、なんと言うかAngularJSを使おうとするとこれらのライブラリはオーバースペックな感じがするのです。Ruby on Railsのようにデファクトが一つある状態であればいいですが、もどきがいっぱいある状態ではコンベンションも統一感がなさそうです。

Angularを前提とするのであれば、フレームワークではなくもっとシンプルなライブラリがあればいいのではないかと思いました。それでもって、面倒なことはそのライブラリが引き受けてくれるような。

そこで、TwitterやFacebookのようなSNSのサイトを作ることに限定したライブラリを作りました。node.jsではexpress.jsがほぼデファクトなのでexpressのmiddlewareとして作りました。middlewareにすることで、単一のフレームワークに依存することなく、他のアプリにアドオンする形で導入できます。

面倒な、認証や権限管理の機能はライブラリが面倒みてくれます。 現状では、Facebook認証と、標準的な権限管理(ownership based)の機能が用意されているだけですが、プラグインとして他の認証や権限管理の機能を追加できるようになっています。 また、通知(メールアラートなど)の機能も今後追加してきたいと考えています。

このライブラリを使うと、フロントエンドを作るだけですぐサービスできます。Railsのようにデフォルトがないので作らないとなにもできないのですが、Angularに慣れていればコンベンションを意識してコーディングするよりもずっと楽かもしれません。

まずは、 こちら からスクリーンキャストを見てみると雰囲気が分かるかもしれません。 もしかしたら、分からないかもしれません。

ソースコードは、

https://github.com/dai-shi/social-cms-backend

にあります。


8/7追記。

最近知ったのですが、http://sproute.io/というフレームワークもあるようですね。 こちらは比較的SNSに特化したようなフレームワークであるようです。 ほとんどコーディングなしでTwitterクローンが作れるようです。 でも、$99って。

みなさん、AngularJS使ってますか?

Angularを使うと、お手軽にWebアプリ(と言っていいのかな)が作れます。

そのお手軽さをさらに助けるのが、Node.jsとそのライブラリ達です。(いや、Nodeに限った話でもないですが。) 細かい説明は他にお任せしますが、Connect/Expressやその様々なmiddlewareを使うと機能を色々追加できます。 今回、そのmiddlewareの一つとして、SNSのバックエンドライブラリを作りました。 これを使うと、フロントエンドを作るだけで簡単にSNSのサイトができあがります。

試しに、Twitterクローンを作ってみました。今回はフォロー機能などなしの単機能版です。ちなみに、フォローやグループの機能はライブラリには入ってます。

Ruby on Railsのまねをして15分でコーディングするスクリーンキャストを作りました。NodeやAngularの知識を前提としているので、見るだけそれらが理解できるようになるわけではありませんが、どうぞご覧ください。音もテロップもなし、それどころかマウスポインタもなしです。シンプルに。

スクリーンキャストを別ウインドウで開く

コーディングした結果のソースコードはこちらに置いてあります。

https://github.com/dai-shi/twitter-clone-sample/tree/20130804_recorded

実は一文字だけ打ち間違いがあってあとから修正しました。

せっかくなので作ったTwitterクローンを動くようにアップしました。

http://twitterclonesample-nodeangularapp.rhcloud.com/

もしよろしければ試してみてください。

jsdomへのpull request: XHRサポート

  • 投稿日:
  • by

connect-prerendererをちゃんと動かすためにやったjsdomの修正その2をpull requestにしました。

https://github.com/tmpvar/jsdom/pull/654

実はXHR自体は、 node-XMLHttpRequest をそのまま使うだけでほとんど苦労はありませんでした。

大変だったのは、クッキーを引き継ぐところでした。 そもそもXHRを使わない場合も、jsdomはクッキーの引継ぎをサポートしていませんでした。XHRを使わない場合というのは、JavaScriptやCSSや画像ファイルなどを読み込む場合です。

まず、JavaScriptのロード時にもクッキーを引き継ぐようにするコーディングをしてから、XHRでもクッキーを引き継ぐようにしました。XHRはnode-XMLHttpRequestの内部のコードまで理解しなくてはならず、苦労しました。ちょっと強引に実装したため、もしかしたら将来のバージョンのnode-XMLHttpRequestでは動かないかもしれません。

pull requestは無事マージされて、jsdom v0.8.1がリリースされました。興味がある方はお試しください。

https://npmjs.org/package/jsdom

connect-prerenderer もこのバージョンを使うように修正しました。

https://npmjs.org/package/connect-prerenderer

今回は、npm周りのTIPSです。特に新しい知見ではないのですが、メモのために書いておきます。

node.jsのパッケージマネージャであるnpmは依存パッケージの解決をしてくれます。依存パッケージは、package.jsonに書いておきます。

例えば、expressを使う場合、

"dependencies": {
  "express": "*"
}

のように書きます。バージョン番号を指定しておきたい場合もあります。

"dependencies": {
  "express": "3.1.0"
}

のように書きます。他にも不等号やチルダを使ってバージョン番号の範囲を書くこともできます。今回はそのあたりは詳しく書きませんが、package.jsonにはバージョン番号の範囲を書いておくことがおすすめです。node.js関連のパッケージはまだ発展途上で仕様が変わることがよくあるからです。そうでなくても、">=3.0.0"などと書いておけば、"3.0.0"では動いたのだということが分かってよいかもしれません。

さて、本題です。依存パッケージがnpmに登録されていない場合はどうすればよいでしょう? 一つの答えは、登録してしまえばよいのです。が、登録したくない場合もあります。今回そのようなケースがありました。それは、GitHubでforkした場合です。将来的にはPull Requestして取り込んでもらうつもりなら、npmに独自バージョンを登録するのはうまくありません。そこで、npmに登録されていないけれど、依存パッケージとして使いたいときにどうするかという話です。

GitHubを使うとしても方法は2通りあります。一つは、tarballで取得する方法、もう一つは、gitプロトコルで取得する方法です。

前者は、

"dependencies": {
  "jsdom": "https://github.com/dai-shi/jsdom/tarball/3bb5b24c5e"
}

のように書き、後者は、

"dependencies": {
  "jsdom": "git://github.com/dai-shi/jsdom.git#3bb5b24c5e"
}

のように書きます。

どちらがいいのでしょう? よく分かりません。 試しに一回だけ、npm installの時間を計測してみました。 結果、前者が11秒、後者が14秒でした。 gitプロトコルの方がオーバヘッドがあるのかもしれません。 httpプロキシしか使えない場合は、前者に決まりですね。 GitHubがtarballのURLを廃止したら(ZIPのURLはリンクがありますが、tarballってどこまでオフィシャルなのでしょう?)、後者に決まりですね。

今のところはどちらでもよいのかもしれません。


10/20追記。

"https://github.com/dai-shi/jsdom/tarball/3bb5b24c5e"

の代わりに、

"https://github.com/dai-shi/jsdom/archive/3bb5b24c5e.tar.gz"

と書く方法もあるようです。こっちの方がオフィシャルな感じがしますね。

connect-prerendererをちゃんと動かすためにやった 修正をjsdomのpull requestにして欲しいと依頼されました。

unit testを書いて欲しいということなので、書いてみたのですが動かなくて苦労しました。 __defineGetter__と__defineSetter__を初めて使いました。 ECMAScriptではこれらは入っていない(definePropertyを使う)そうですが、nodeでは__defineGetter__と__defineSetter__は使えるようです。

結局、当初やっていた修正では全くダメ(なぜconnect-prerendererが動いたのか不明)で、window.locationまわりのコードを全部書き直しました。 おかげで、全体的により仕様に合った動きをするようになり、立派なpull requestができあがりました。

ちゃんとマージされたようで、めでたしめでたし。

https://github.com/tmpvar/jsdom/pull/650

ちょっと新しいことに挑戦しようと思って、MongoDBを勉強中です。 node.jsから使うモジュールとして、 native driver と、 mongoose をみました。

Railsに慣れている人はODMのmongooseのが受け入れやすいかもしれませんが、 自分は生のJSONをいじりたかったので、native driverを使うことにしました。

http://christkv.github.com/node-mongodb-native/

を読み始めましたが、初めはとってもとっつきにくかったです。 どこから読んでいいか分からなかったです。

チュートリアルの一つ目から読むのがおすすめです。

全体像を理解してからは、Manualが読みやすかったです。 http://mongodb.github.io/node-mongodb-native/api-generated/collection.html が一例ですが、URLにapi-generatedと有るので、ソースコードから生成しているのでしょうか。ちなみに、ページの最初にあるUsageも読めなくはないですが、内容も古く中途半端な感じがしました。

MongoDBのAPIには満足です。JSONをそのまま保存でき、クエリで検索できます。Collectionを事前に生成しておく必要もありません、オンディマンドで作ってくれます。このあたりを便利に使うと、ODMには戻れないかも。まあ、自由度が高すぎるのでそれを嫌うケースもあるとは思います。

ところで、自分がやろうとしたことでできないことがこれまでに一つありました。

クエリもJSONで書くのですが、例えば、

{"product_name":"apple", "color":"red"}

のように書くと、product_nameがappleで、かつ、colorがredのデータを検索することになります。「または」の指定も可能で、

{"product_name":"apple", "$or":{"color":"red", "name":"ringo"}}

のように書けます。しかし、この書き方だと、 「(AまたはB)かつ(CまたはD)」のようなクエリが書けないのです。 うーん、惜しい。

Issueになっているのかと調べようと思いましたが、Issue Trackerの使い方がよく分かりません。nodeのドライバーの問題ではなく、本体の問題だろうとは思うので本体のIssueを探そうとは試みました。

https://jira.mongodb.org/secure/IssueNavigator.jspa

これで合ってますよね。"or"という検索キーワードが論理式と認識されているのですかね。とりあえず、あきらめることにします。

そのうち実装されるといいですね。

connect-prerendererのIssue #3を解決

  • 投稿日:
  • by

DailyJSに取り上げてもらって、 一気にStar数が増えたconnect-prerendererですが、 Issueが一つ残ってました。

https://github.com/dai-shi/connect-prerenderer/issues/3

jsdomがらみなので、難しいなぁと思っていたのですが、 こっちの方 でも使ってくれそうな様子なので、ちょっとがんばってみました。

ヒントは、Issue #3で報告してくれた、

http://stackoverflow.com/questions/10054071/jsdom-hashchange-event

と、jsdomの古いIssueの

https://github.com/tmpvar/jsdom/issues/433

でした。

そもそも、hashchangeイベントの発火が実装されていないとのことでした。 みようみまねでjsdomに手を加えて、コードを追加しました。

結果、動くようになりました。まだ簡単なe2eテストが動いただけですが、 不具合があれば誰か報告してくれることでしょう。それまで、待つ。

AngularJSがnode.jsと通信するときは、RESTでJSONを返すのが便利です。標準的なやり方にしておくと、コードを書く量が減らせます。

node.jsとexpress.jsでRESTを書くときは、

res.json({foo: 'hoge', bar: 123});

のようにします。

これまで、真偽値を返したい場合は、

res.json(true);

としていました。node.jsはこれで正しいです。しかし、これだとAngular的にうまくありません。

Angularはプリミティブな値が返ってくることは想定していないようで、オブジェクトかオブジェクトの配列の形で返してあげる必要があります。文字列の配列もダメです。

今回の例では、

res.json({result: true});

とすることで解決できました。

実は、この話は半年くらい前にも気づいて調べたことがあったのですが、すっかり忘れていました。今日も全く同じことをして1時間ほどはまりました。忘れないように、ということでメモでした。

JavaScriptでランダムに色を生成したいと思いました。

乱数はMath.random()で生成できます。 これを使って、RGBの値を生成することはできますが、 安直に生成すると灰色っぽい色になってしまいます。

ここはやはり、明るさや彩度は固定して色相だけどランダムにしたいところでしょう。

JavaScriptで色を操作するライブラリを探しました。

http://stackoverflow.com/questions/8022885/rgb-to-hsv-color-in-javascript

を参照して、見つけたよさそうなライブラリ2つ。

TinyColorは現在もアクティブに開発されているようで、安心できます。 一方、color.jsは3年前のコミットが最後です。正確には、README.mdは更新されているので、放置されているわけではなく、コードが枯れているのだと思います。

今回は、color.jsを使いました。理由は、今回やりたことについては十分機能があり、コードもきれいで短いからです。

多機能を求める場合は、TinyColorを使うと思います。こちらは、node.jsでも使えるように書かれています。

ランダムに色を生成するには、次のようにしました。

function genRandomColor() {
  var hue = Math.random();
  var saturation = 1.0;
  var lightness = 0.5;
  return Color.hsl(hue, saturation, lightness).hexTriplet();
}

色操作のライブラリはもっと探せばいろいろあるかもしれません。また、機会があれば。

connect-prerendererのgooglebot対応

  • 投稿日:
  • by

connect-prerendererのStart数が20に到達。すごいのかすごくないのかよく分かりませんが。

今回、Issueが報告されたので、対応しました。

https://github.com/dai-shi/connect-prerenderer/issues/6

AJAXページをクロールするための、Googleの仕様があるらしいです。

https://developers.google.com/webmasters/ajax-crawling/docs/getting-started

仕様は簡単で、hashのすぐあとにエクスクラメーションマークをつけておくと、_escaped_fragment_というクエリパラメータをつけてクロールしてくれるということです。

つまり、

http://aaa.bbb/ccc#!ddd

が

http://aaa.bbb/ccc?_escaped_fragment_=ddd

に変換されて、googlebotからアクセスされることになります。

これは、connect-prerendererの出番です。この変換スキームに対応するように修正しました。詳しくはソースコードを参照することになりますが、使い方は下記のようになります。

var prerenderer = require('connect-prerenderer');
app.use(prerenderer({
  targetGenerator: 'googlebot'
});

この使い方は、まだドキュメントに書いていません。テストができていないからです。

興味ある人いませんか~?