「node.js」と一致するもの

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();
}

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

Node.jsで困るのはある機能を満たすためにどのパッケージを使っていいか分からないことです。発展途上ということで納得しましょう。発展途上というか生態系。

今回、Facebook認証をするためのモジュールを探しました。 stackoverflowで色々比較コメントがあり、それらやREADMEを参考にしました。

connect-authというのは名前もいいし、それなりのStar数もあります。シンプルでよさそうなのですが、よく使い方が分かりません。人気があるのは後発のeveryauthのようです。しかし、ドキュメントを読んでみてもどうもピンときません。なにか、感性が合わないような気がしました。

そこで、Passportを試すことにしました。 http://passportjs.org/guide/facebook/にドキュメントがあります。everyauthと比較するとコーディング量は多いかもしれませんが、なんとなくこちらの方が合う気がします。それでも、accessTokenが欲しいだけの場合は、userオブジェクトなんて作らなくてもいいのですけど。これはeveryauthも同じ(?)なので我慢するとします。

簡単に今使ってみた方法を紹介します。

まずは、ライブラリのロードです。サンプルをコピペしただけです。

var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;

続いて、FacebookStrategyの設定です。accessTokenだけ欲しかったので、それをuserオブジェクトにします。必要であれば、profileとかも入れればよいでしょう。userオブジェクトは、req.userで参照できるので、accessTokenはreq.user.accessTokenになります。

passport.use(new FacebookStrategy({
  clientID: process.env.FACEBOOK_APP_ID,
  clientSecret: process.env.FACEBOOK_SECRET,
  callbackURL: process.env.CALLBACK_URL
}, function(accessToken, refreshToken, profile, done) {
  done(null, {
    accessToken: accessToken
  });
}));

サンプルにあったシリアライザも入れておきます。これは書かなくてもよさそうと思ったのですが、ソースみてもデフォルトがあるように見えず、念のためいれておきます。

passport.serializeUser(function(user, done) {
  done(null, user);
});
passport.deserializeUser(function(obj, done) {
  done(null, obj);
});

強制的にログインさせるmiddlewareです。一度、ログインを促すページを表示するほうが親切かもしれません。

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    next();
  } else if (req.url.lastIndexOf('/auth/', 0) >= 0) {
    next();
  } else {
    res.redirect('/auth/facebook');
  }
}

expressの設定です。セッションを使うのでその設定が必要です。

var app = express();
app.use(express.cookieParser());
app.use(express.session({ secret: 'foobar' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(ensureAuthenticated);

最後に、expressのルートの設定をします。failureRedirectは暫定です。

app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback', passport.authenticate('facebook', {
  successRedirect: '/',
  failureRedirect: '/auth/loginfailed'
}));
app.get('/auth/loginfailed', function(req, res) {
  res.send('login failed');
});

以上、こんな感じで使うようです。/auth/facebookのようなルートを設定しなければならないのを面倒と考えるか、分かりやすいと考えるかが、Passportを受け入れられるかの境目かもしれません。

久しぶりにFacebookアプリを作ることにしました。以前作った時から仕様がちょっと変わったので、勉強し直しです。

ちなみに、以前作ったアプリは修正していないので、動かなくなってしまっています。Java & JSPでGAE向けに作ったのであまりやる気が出なくなってしまいました。Node.jsで書き直そうかしらと思いつつも、作り直すならもうちょっと全体設計から見直したいと思って手がつけられていません。

話を戻すと、FacebookのGraph APIを使うためのライブラリを調べました。

http://developers.facebook.com/tools/third-party-sdks/#nodejs

にいくつかリストアップされています。

リポジトリの名前が同じものがあり少しややこしいです。NPMのパッケージ名は違います。Star数が同じなのはたまたまのようです。

さて、少しずつ特徴があります。amachang/facebook-node-sdkとnode-facebook-sdkはオリジナルのFacebook PHP SDKを再現したライブラリになっています。Thuzi/facebook-node-sdkはブラウザ向けのFacebook JavaScript SDKと同じAPIをサーバ側でも実現したものです。これはとてもnode.jsらしいと思いました。fbgraphは、また路線が違ってJavaScriptっぽくJSON APIに書き直したような感じです。

今回はFacebookアプリと言ってもあまり複雑なことをするつもりがなかったのと、以前PHP SDKを見たことがある(そして、わざわざJavaに移植した)ので、PHP SDK互換のものを使おうと思いました。二者択一ならとりあえずStar数を信じてみようということで、amachang/facebook-node-sdkを入れてみました。

ところが、初めに作ったサンプルでつまずきました。applicationのaccess tokenを使ってGraph APIを呼ぶのですが、エラーになるのです。うーん、なぜだ、と思ってソースを眺めると、あれ?application access tokenを取得するAPIが想像していたもの(以前、Rubyのライブラリを使ったことがあった)と違います。こりゃ、動かないわけです。もしかしたら、Facebookの仕様が変わったのかもしれません。深追いするのは止めました。たぶん、user access tokenを使っている範囲では正常なのでしょう。

やはり、Star数の多いものから使おうと、Thuzi/facebook-node-sdkとfbgraphを天秤にかけます。README.mdを読んで比較しました。結果、fbgraphにはapplication access tokenを取得する方法が書いてなく、できるとしてもすっきりしないと予想しました。一方、Thuzi/facebook-node-sdkにはapplication access tokenを取得する方法が書いてありました。application access tokenはサーバ側のコーディングのみで使用可能なため、Facebook JavaScript SDKには入っていないnon-standardな機能です。

作ったサンプルも動作し、満足です。application access tokenを使おうとしている人には参考になるかもしれません。ただし、現時点での話であることをお忘れなく。将来的には状況は変わるかもしれません。

参考までに、コードの抜粋はこちらに。

RSS Pipesは、JavaScriptでフィルターを書く、RSSアグリゲーターです。

フィルターJavaScriptはブラウザ上で誰でも書くことができます。すなわち、バグ入りのコードや、悪意のあるコードも書くことができます。 そのような不正なコードにどう対処するかがポイントになります。

RSS PipesはNode.jsで動いています。Node.jsにはvmというモジュールがあって、サンドボックス環境を作ることができます。 Node.jsを使ったことがある方は分かると思いますが、モジュールを書いてrequireするとexportsしたもの以外は名前が衝突しません。 これはrequireでモジュールをロードする時にサンドボックス環境で評価しているからです。 RSS Pipesでもこの仕組みを使ってJavaScriptのコードを評価しています。 よって、ブラウザ上から入力されたJavaScriptコードによって、サーバ側のNode.jsにアクセスされることはありません。

ところが、vmモジュールのマニュアルをよく読むと書いてあるように、このモジュールを使うことで安全になるわけではありません。安全にコードを走らせるためにはサブプロセスを使うように、と書いてあります。

どういうことかと言うと、Node.jsはシングルスレッドで動作するため、vmでサンドボックス環境を作ったとしてもそこで使用されるリソース(CPUパワーやメモリ)を制限することはできず、リソースを不用意に使われてしまう可能性があります。簡単な例では、

while(true) {}

というJavaScriptを走らせるとNode.js全体が固まってしまいます。 とは言っても、herokuで動いているRSS Pipesでサブプロセスを使うわけにはいきません。(もしかして、使うこと自体はできるのかも?)

そこで、無限ループを判定するような仕組みをいれています。 先ほどの例では、

while(true) {
  counter++;
}

のようにして、counterが一定値を越えた場合に強制的に止めればよいのです。これで、どの程度の危険が回避できたかは定かではありません。正確にCPUの使用率を測っているわけでもありませんし、メモリについてはノータッチです。それでも、無限ループで固まることは回避できているので、少なくとも悪意のないコードでしたら、問題のない範囲と思っています。

将来的に、vmモジュールのサンドボックス環境でリソース制限までしてくれるようにはならないでしょうかね。ちょっと興味あります。

昨日、見つけてissueの登録をしておいた、connect-offlineの件です。

https://github.com/dustMason/connect-offline/issues/1

早速返事が来ました、Pull Requestにして欲しいと。仕方ないやるかな、と思ってみたものの、やっぱりCoffeeScriptでは書く気になれません。また、パッケージ名も分かりにくいのではないのかと考え、新しく作ることにしました。

初めは、完全互換のパッケージにしようと考えていたのですが、process.cwd()で相対パスを使っているのが気に入らず、__dirnameを使うようにしたかったので、パスの指定の互換性がなくなってしまいました。

作っているうちに他の改善案(ディレクトリの再帰探索)も思いつき、実装方法もだいぶ変わってしまったので、互換の方向性はやめました。とは言っても、基本的には同じように使えるはずです。

https://github.com/dai-shi/connect-cache-manifest

から参照できます。npmにも登録済みです。

express.jsでHTML5のキャッシュマニフェストを使おう考えています。どうせならconnectのmiddlewareにしたら便利だろうと思って調べました。

GitHubでmanifestをキーワードに色々検索したのですが、見つかりませんでした。connect-manifestという空のプロジェクトがあったくらい。

あきらめて、自分で作ろうかと思ったところで、npmで検索してみました。結果、見つけました。

https://github.com/dustMason/connect-offline

offlineという名前だから、GitHubの検索では見つからなかったようです。 Node.jsはパッケージ探しが難しいですね。

このconnect-offlineはStar数が4しかありません。あまり、キャッシュマニフェストをmiddlewareで欲しいと思う人はいないのでしょうか。それとも、名前が悪くてみんな見つけられないのでしょうか。

READMEを読むと、以前のconnectにはcacheManifestというmiddlewareがバンドルされていたようです。connectのリポジトリを探りましたが、確かに、version 1.0より前のタグには存在します。なぜ、やめたのかは分かりませんでした。(消えたファイルのgit logを見ればいいのも)

さて、connect-offlineはちょっと想像していた機能が足りなかったので、自分で修正しようと思ったのですが、ソースがCoffeeScriptだったので手を出しませんでした。(CoffeeScriptはなぜか好きになれないので)

代わりに、issueにしておきました。

https://github.com/dustMason/connect-offline/issues/1

あまり使うことはないかなと思っていたのですが、ちょっと使うことにしました、PostgreSQL。

Rubyにはdata_mapperがありましたが、Node.jsでは何があるんだろうと思って調べてみました。

とりあえず、見つけたのは3つです。

これが現時点でのGitHubでのStar数順です。

でも、JugglingDBを使ってみることにしました。理由は、adapterにRedisやMongoDBなどのNoSQL系が入っていてちょっとわくわくしたからです。もしかして、将来新しいadapterを作れるのかもしれないと思いました。

しかし、このJugglingDBですが、ドキュメントが見つかりません。RailwayJSのサブプロジェクトみたいで、あまり外に目が向いていないのでしょうか。ところで、RailwayJSはcompound.jsに変わったのですね、client-sideで動くと書いてあります。面白そうですね。

さて、READMEを見ながらなんとなくやってみたら動きました。

var Schema = require('jugglingdb').Schema;
var schema = new Schema('sqlite3', { database: 'xxx.db' });
var SomeObject = schema.define('SomeObject', {
  name: {
    type: String
  },
  content: {
    type: String
  },
  flag: {
    type: Boolean
  }
});
schema.autoupdate();
SomeObject.create();

これで合っていますかね。

他の2つとの比較も知りたいですね。探せば誰かがやっているでしょう、きっと。

http://www.sequelizejs.com/はだいぶ詳しく書いてありますね。少し心が揺らぎます。


docsの下に少しドキュメントがありました。しかし、READMEに書いてあるfindOneが載っていなかったりと、網羅されていないようにも見えます。

JavaScriptにはJavaで言うところのString.startsWithがないです。みんなどうやっているかというと、

if (str.indexOf('prefix') === 0) {
    console.log('prefix found');
}

みたいな感じのコードをよく見ます。AngularJSのソースでも見ました。でも、これって良く考えると無駄ですよね。indexOfというのは文字列を最初から最後までなめて、一致する位置を探すのです。見つかれば一発ですが、見つからなければ最後まで行って-1を返します。本来、prefixを判定するのなら最初だけ検査すればいいのです。

str.substring(0, 'prefix'.length) === 'prefix'

という書き方も見ますが、これもいまひとつです。substringは新たに文字列を作ってしまうので。結論としては、

str.lastIndexOf('prefix', 0) === 0

が一番いいと思います。lastIndexOfは文字列の後ろから検査するのですが、検査開始位置を0にしているので、判定するのは一回だけです。Javaの内部実装には詳しくないですが、String.startsWithも同じような実装でしょう。

さて、これが本当かを確かめるために、benchmark.jsでベンチマークを書こうかと思いました。いや、せっかくなのでjsperfで書いてみようと思って、書き始めたら、あれ、既に存在する。世の中同じこと考える人はいますね。

http://jsperf.com/string-startswith/3

最後の一つのテストケースは私が追加したものです。 想定通り、lastIndexOfが速いです。ん?しかし、Chromeだとcompiled regexの方が速い。いや、そんなはずは、、。compiled regexが速いのは事実ですが、lastIndexOfが遅いのです。こんな単純な関数でChromeがFirefoxより遅いのは想定外でした。

Chromeで遅いということは、v8が遅いのかと思って、node.jsでもテストしてみました。

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

案の定、node.jsでも同様の結果です。 それでも、私はlastIndexOfを使うことをおすすめします。 v8で遅いのはv8が直るべきです。ところで、これはissueになっているのかな?

http://code.google.com/p/v8/issues/list

どうやらなっていないようです。ちょっとソースを覗いてみますか。v8はsubversionです。bleeding edgeブランチが最新のようです。

http://v8.googlecode.com/svn/branches/bleeding_edge/

checkoutしてStringLastIndexOfのソースコードを追ってみましたが、特に単純な問題があるようには見えません。久しぶりのCのソースで頭が疲れます。そりゃそうですよね、天下のGoogleが作っているものがそんな自明なバグを残しているわけないですよね。というわけで、しばらくは様子をみることにします。

ところで、jsperf.comにstring-startswithがあったのが悔しかったので、真似してstring-endswithを作ってみました。

http://jsperf.com/string-endswith

傾向はstartsWithと同様です。ここまで読んでくれたみなさん、suffixチェックするときに、間違ってもnaive indexOfのような書き方はしないように。

JavaScriptのお話です。node.jsに限らない話だと思いますが、node.jsでの動作を説明します。

一言で言うと、"g"オプションをつけたRegExpのtest()の呼び出しはループ(?)します。

説明するよりも、実際の動きを見てみましょう。

% node
> re = new RegExp('xyz', 'g');
/xyz/g
> s = 'aaaxyzbbbxyz';
'aaaxyzbbbxyz'
> re.test(s)
true
> re.lastIndex
6
> re.test(s)
true
> re.lastIndex
12
> re.test(s)
false
> re.lastIndex
0
> re.test(s)
true
> re.lastIndex
6

という感じで、re.lastIndexがマッチを開始するインデックスを保持しているようです。で、最後まで行ったら初めに戻ると。

"g"オプションをつけなければこんなことにはならず、lastIndexも常に0のままです。


ついでに、RegExp.test()とRegExp.exec()とString.match()とString.search()のベンチマークもしておきました。

一つは、node.js用。
https://gist.github.com/dai-shi/5169296

もう一つは、ブラウザ用。自分で作ろうかと思ったら、既にありました。
http://jsperf.com/regex-test-or-exec-or-string-search-or-match

やはり、正規表現のマッチを確認するだけなら、RegExp.test()が一番よさそうですね。

ちょっと面白い結果だったのはFirefoxのケースで、RegExp.test()とRegExp.exec()の速さがほとんど変わりませんでした。つまり、test()の内部でexec()を呼び出しちゃってる感じです。Chromeとnode.jsでは(どちらもv8だけど)差が出ているので、FirefoxのJavaScriptエンジンは改善の余地があるということでしょう。

前から思っていましたが、Chromeの正規表現の処理は速いですねぇ。Firefoxとは比べものにならないです。