「GitHub」と一致するもの

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の終了はないものなのでしょうか。 興味はあるのでまたの機会に勉強しようと思います。

nodeで、というかexpressで、より正確にはconnectで、Railsのアセットパイプラインのようなことをする方法についてです。

意外と知られてないようなのですが、 connect-assets というモジュールがあります。

npm install connect-assets

でインストールできます。 Railsに慣れている人はいいのかもしれませんが、ちょっとexpressっぽくないというかconventionがあって戸惑うかもしれません。 個人的にはわかりにくいと思いました。 connect-cachifyの方がAPIとしては分かりやすい気がします。

さて、簡単な使い方です。expressには慣れているものとします。

var assets = require('connect-assets');
...
app.use(assets());

このオプション無しデフォルトだと、assetsディレクトリにjsファイルやcssファイルを置くことになります。さらに、jadeファイルに

!= css('hoge')
!= js('hoge')

と書いておくと、それぞれ、assets/hoge.cssとassets/hoge.jsが読み込まれることになります。

一応これだけなのですが、concatenationは直接はサポートされていなくて、lessのimport機能やjsの snoketsを使う必要があります。ここがややこしい。

具体的には、assets/hoge.lessに

@import 'foo.css';
@import 'bar.less';

のように書いたり、assets/hoge.jsに

//= require foo.js

のように書いたりするようです。統一感ないですね。

NODE_ENV=productionの時はminifyしてくれます。これは便利。

現状不満があるconnect-assetsですが、v3 branchの開発が進められているようです。クリーンに書き直したり、依存ライブラリを減らす計画のようですので、期待できそうです。

いつも思いますが、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!今後の発展に期待します。

以前書いた、 AngularJSを使ってRailsより簡単にTwitterクローンを作りたいと思って作ったnode.jsのライブラリ「social-cms-backend」をちょっとずつ改良しています。

今回は、通知機能です。通知機能というのは、例えば、自分のつぶやきに誰かが返信した場合にお知らせしてくれる機能です。今のところ、social-cms-backendはFacebookのみと連携しているので、通知機能もFacebook連携にしようと思いました。

Facebookアプリは何年か前にも一度作ったことがあって、そのときにapp requestsという機能で通知したのを覚えています。ところが、最近ではapp notificationsという機能もあるのですね。

http://developers.facebook.com/docs/games/notifications/

これです。このAPIの方がずっと素直で分かりやすい。難点は現状ではモバイルアプリに対応していないこと。また、通知先もCanvas対応アプリしか使えないようです。Betaとなっているので、将来的には使えることを期待してapp notificationsで行こうと思います。しかし、また数年後には使えなくなっていそう。Facebook依存はそのあたりがつらいので、social-cms-backendの通知機能としてはFacebook連携以外もサポートしていきたいところです。

APIは/<userid>/notificationsにHTTP POSTするだけです。

facebook-node-sdkを使った呼び出しサンプルコードは次のようになります。

FB.api(facebook_user_id + '/notifications', 'post', {
  href: href,
  template: template
}, function(res) { 
  //result handling
});

hrefはジャンプ先の相対URL、templateにはメッセージを入れます。これをちゃんと動かすにはapp access tokenが必要ですが、その方法は、

HerokuでFacebook APIを使う時の注意点

を参照してください。

いかがでしょうか、簡単でしょう?

RSS Pipesは、しばらく前に作ったRSSアグリゲーターで、WikiっぽくJavaScriptでフィルターアルゴリズムを書くものです。

ちょっと思い立って英語化しました。といってもコード上のJadeはすでに英語化されていて、今回は説明ページを英語にしただけです。

困ったこと。GitHub PagesはAccept-Languageヘッダーに対応していないようです。仕方ないので、index.htmlとindex-ja.htmlの二つのファイルを、herokuのアプリからリダイレクトするときに振り分けるようにしました。

これだけではネタ不足なので、Jadeで英語と日本語の両方を埋め込む方法を紹介します。

簡単にいうと、

p ようこそ

とするところを、

p= (lang === 'ja' ? 'ようこそ' : 'Welcome')

と書くようにしました。2ヶ国語だからできる技かもしれませんね。 Jadeのテンプレートを呼び出す方は、

var lang = '';
if (req.headers['accept-language']) {
  lang = req.headers['accept-language'].substring(0, 2);
}
res.render(view_name, {
  lang: lang
});

のようにします。 もうちょっとうまいやり方がないだろうか考えていますが、今のところ思いついていません。

ご参考になれば。

以前の記事で紹介したsocial-cms-backendについてです。

念のため、リンクを載せておきます。

さて、social-cms-backendはバックエンドにMongoDBを使っていて、MongoDBのAPIをほぼそのままRESTに乗っけたものになっています。より正確には、認証やアクセス制限などの機構が入っています。

ところで、MongoDBのAPIでは複数のクエリを一回の呼び出しで投げることができません。これがどういう問題を引き起こしたかというと、twitter-clone-sampleではLikeボタンがありますが、Like数の取得はAPIの一回分がかかるのです。MongoDBのcount関数を使っているからです。

social-cms-backendはなるべく学習コストがかからないように、MongoDBのAPIをいじらないのが基本方針なのですが、現状だとLike数を表示する分だけ、ブラウザからREST呼び出しがかかります。例えば、25件の投稿が表示されそれぞれにLike数を表示するとREST呼び出しは26回になってしまいます。いくらデータ転送量が少なくても、26回のXHRはなかなかの時間がかかります。

そこで、25件のクエリを一回のXHRで行えるように改良しました。APIはとてもシンプルで、MongoDBのクエリを配列にして渡すだけです。当然、同一のREST endpointにしか使えません。今回のケースではこれでよしです。

ちなみに、サーバ側のコードではasync.jsを使いました。配列かどうかをチェックして分岐するだけなので、数行の追加ですみました。コードのリファクタリングをした後のカウントですが。

twitter-clone-sampleの方のコードはまだいじってません。スクリーンキャストのコードからあまりいじらないほうがいいかなと思いまして。スクリーンキャストをもう一度取り直すのはちょっと手間がかかってしまうので、当面保留です。

MongoDBでちゃんと$andがあった件

  • 投稿日:
  • by

以前の記事でMongoDBで$orが複数使えないという件を書きましたが、最近になって$andがちゃんと用意されていることを知りました。

node.jsのドライバを使っていたので、

http://mongodb.github.io/node-mongodb-native/

のドキュメントを読んでいたのですが、これが敗因でした。 MongoDB本体のドキュメントである

http://www.mongodb.org/

にはちゃんと書いてありました。こちら。

http://docs.mongodb.org/manual/reference/operator/and/

なぜ、前回は見つけられなかったのでしょう。

これを使えば、 「(AまたはB)かつ(CまたはD)」のようなクエリを書くことができます。

{"$and": [{$or: [{"name": "orange"}, {"name": "apple"}]},
          {$or: [{"color": "red"}, {"color": "green"}]}]}

といったところ。ちなみに、この例は$inでも書けてしまいますが。

MongoDBには他にも色々機能がありそうなので、また調べてみようと思います。

久しぶりの記事です。

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と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って。