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

先日の記事で、 ブログシステムとしてGhostを使ってherokuにデプロイする話を紹介しました。

ところが使い始めてみるといくつか問題がありました。

  1. herokuはアクセスが無いとスリープするので、サイトを初めて開くときに時間がかかる
  2. 常にアクセスが来るとスリープはしなくなるが、Freeプランの範囲を越えてしまい、Hobbyにアップグレードするように催促が来る
  3. 画像ファイルを置けないのはやはり多少不便である

そこで、hexoに移行してみることにしました。デプロイ先はgithub.ioです。 github.ioがどこまで使えるのかは分かりませんが、少なくともherokuのfree planよりはよさそうです。

hexoはghostと違ってスタティックなサイトを生成するだけですが、markdown形式のファイルを入力とするので、 移行は比較的楽かと期待しました。 Ghostのmarkdownプレビュー機能は便利だったのですが、それは残念ながら諦めることにしました。ちゃんと探していませんが、オーサリング側の拡張もありそうです。

移行時に気づいたことをまとめておきます。 全体的に、Ghostの方が出来が良くて、hexoでやりたいことを再現するのに苦労した感じです。

markdownの変換が不完全な場合がある

Ghostでは問題なかったmarkdownの変換がhexoではうまく行かないケースがいくつかありました。 今回テンプレートにhandlebarsを使ったのでそれが原因かもしれません。 例えば、{{foo}}のような記述がうまく変換できませんでした。&#x7b;{foo}}とすることで解決。 他にも、<code>の変換結果が異なりました。これは問題という訳でもないですが。

handlebarsがサポートされていない

Ghostで標準のhandlebarsがhexoではコミュニティサポートであり、サポートされていませんでした。 どうやら、hexo v2からhexo v3で大幅な変更があったようで、プラグインなどが追従できていないようです。 これはなんとかしました。

Casperの移植が大変だった

Ghostの標準テーマであるCasperががシンプルで使いやすくて気に入っていたのですが、 hexoのテーマには似たようなものがありませんでした。以前のCasperを移植したものはありましたが、 機能やデザインがいまいちでした。 Ghostに戻ろうかと何度も挫折しましたが、結果的になんとか自分が使いたいCasperの機能とデザインは移植できました。

まとめ

当初の目的であった、herokuの課題への対応は、github.ioになったことで解消されました。 hexoで苦労したのはどれもCasperを使おうとしたことに起因するので、もっと素直に使う分には問題ないかもしれません。 苦労したおかげでテーマのテンプレートやCSSは理解が進んでいじりやすくなりました。

最後に一つ学んだこと、Ghostすごい。WordPressを置き換えることができるかもしれません。 (ただ、単純に代替として使うという意識ではきっとダメです、新世代に移行するという意識でないと)

機会があったらどこかでGhostもまた使いたいです。

色々考えた結果、herokuを使うのが一番手軽だと思い、Ghostをデプロイしてみることにしました。 file storage問題は既に取り組まれていて、storage moduleでfile以外の方法も使えるようになったようです。 具体的には、Ghost S3 Storageというのがあるようです。 個人的には、mongodbをバックエンドにしたstorageとかできないかなと期待します。

さて、Ghostをherokuにデプロイする簡単な方法はないか調べたところ、 https://github.com/cobyism/ghost-on-heroku というのがありました。 これは便利と思って早速試そうとしたところ、メール機能の有料アドオンを使う設定になっていて それを外す方法が分かりませんでした。

そこで、通常の方法でデプロイすることにしようと思いました。 参考にしたサイトはhttp://www.autodidacts.io/host-a-ghost-blog-on-heroku/です。 ところが、作業を開始してから思いの外手順が多いことに気づき、 作業メモの利用価値が低くなってしまいそうでした。 これでは再利用性がなくなってしまいます。

そこで、方針を戻して、ghost-on-herokuをforkすることにしました。 有料アドオンを使っているところをやめて、GMailでメール機能を使えるようにします。 Ghostのソースを調べると思っていたように、NodeMailerが使われていたため、簡単そうです。 ghost-on-herokuをみようみまねで編集して、ボタンひとつでデプロイできるようにしました。

こちらです。

https://github.com/dai-shi/ghost-on-heroku

ボタンひとつでデプロイできます。動作確認はこれからですが。

このherokuのテンプレート機能便利だと思いました。他にも使えそうです。

node.jsのデプロイ先としてはherokuを使うことが多いのですが、 他にはどういう候補があるのか調べてみようと思いました。 昔の記事を見ると手軽に使えそうだった、Nodejitsu, AppFog, Node Ninjaなど 使えなくなった(もしくは、以前より使いにくくなった)ものもあります。 また、最近Ghostを試してみたいと思ったのですが、 herokuがサポートされていない(ファイルアクセスができない)こともあり、 他の候補を調べてみることにしました。

条件は、無料で使えるプランがある(一定期間でも)ことです。 網羅はできていないと思います。順不同です。 実際に試したわけではなく、サイトから得られる情報をまとめただけです。

flow.ch

Flow App Engineは、Java, PHP, Ruby, Node.js, Python or .NET (Beta) を動作させることができて、dockerのイメージも動かせるそうです。 各種データベースも用意されているとのこと。 webベースのdashboardで簡単に操作できるようです。

free trialは14日間です。pricingは、usage-basedとfixedの2種類とのこと。

modulus.io

Modulusは、Node.js,PHP, Javaが使えます。Meteorがサポートされているのも特徴のようです。 modulusというコマンドをインストールして、デプロイ作業をするようです。 その他必要そうなものはひと通り揃っているように見えます。

sign upすると30日間使える$15分のクレジットがもらえるようです。 しかし、一番安い構成でも月に$28.80になるので、30日連続運用はできないことになります。

dotCloud

dotCloudは、Python, Java, Node.js, PHPが使えるようです。 デフォルトはMySQLですが、他のストレージのadd-onもあります。MongoDBもMongoSoupというのがあるようです。 特徴的なのは、プラットフォームへのアクセスで、 CLIのツールと、Webコンソールと、REST APIが用意されているとのこと。

Sign up for Freeとありますが、実際無料でどこまで使えるか分かりませんでした。 一番小さい構成では、月$9.90のようです。

cloud.google.com

Google Cloud Platformは、IaaSに近い感じですが、 Cloud Launcherを使って簡単にStackを構成できるようです。MEANも使えるようです。

Free trialでは、$300分を60日間使えるそうです。

bluemix

IBM Bluemixは、OSSのCloud FoundryをベースとしたPaaSです。 色々使えるようですが、一例として、Node.js, PHP, Python, Rubyがあげられています。

フリートライアルでは、30日間使えるそうです。

現在、アプリ開発コンテストが開催中とのこと。

まとめ

残念ながら、期間限定なしでfreeで使えるサービスは見つけられませんでした。 freemiumは最近は流行りではないのでしょうかね。 herokuもfreeの範囲が狭まることになりましたし、厳しい情勢です。

以前の記事で、 x-rayというライブラリを使ってスクレイピングをしてみた話を書きました。 その時に、ページネーションだけでなく、リンク先取得もできればいいと思ったのですが、 先日バージョンアップしていた2.0.0ではすばらしいことに複合的な処理が書けるようになっていました。

さっそく、試してみましょう。 サンプルををちょっといじって実行してみます。

var xray = require('x-ray')();
xray('https://www.google.com/', {
  title: 'title',
  image: xray('#gbar a:nth-child(2)@href', 'title'),
  map: xray('#gbar a:nth-child(3)@href', 'title'),
  play: xray('#gbar a:nth-child(4)@href', 'title'),
  youtube: xray('#gbar a:nth-child(5)@href', 'title')
})(function(err, obj) {
  console.log(obj);
});

ちゃんとそれぞれのページのタイトルが取れました。 「画像検索」はなぜか文字化けしました。

これは便利です。しかも、delayやthrottleの設定までできます。 今度勉強がてらコードも読んでみるといいかもしれません。

また、小ネタでした。

ところで、x-rayってだけだと検索キーワードとしてはあいまいすぎますね。

Gunosy RSSのURL変更その他

  • 投稿日:
  • by

久しぶりにgunosy-rssのコードをいじりました。

まず、URLを変更しました。新しいURLは、

http://gunosy-rss.axlight.com/

です。以前のURLでアクセスした場合は301 Redirectされます。 RSSリーダーがちゃんとURLを書き換えてくれればよいのですが、 おそらくすべてのRSSリーダー/アグリゲータが対応しているということはないでしょう。

今後、旧URLではアクセスできなくなることがあるかもしれません。 というのも、herokuの無料枠で動かしているのですが、その条件が変わるためです。 将来的にDNSでアクセス先を変更したりすることを想定してのことです。

しかし、当面はそんな面倒なことをするつもりはありません。 とりあえず、無料枠で動かし続けるためには、6時間スリープさせればよいようなので、 rssのttlを設定してみました。今後RSSリーダー/アグリゲータがttl正しく解釈してくれれば、朝の5時以降と夕方の5時以降に取得するようになるはずです。 しかし、これはもっと早く実装しておけばよかったですね。 ちょっと様子を見た感じだとアクセス頻度がだいぶ減ったように思います。 503 Retry-Afterが返ることも減ったようですし、本家Gunosyへのアクセス負荷も下がるかもしれません。

JavaScriptのDateのタイムゾーンが手元とherokuで異なっていて、はまりました。 ちょっとスマートでない方法で解決したのと、もともとのコードがスマートでないので見苦しいですが、ttlの算出周りに興味があればコードを見てみてください。 たぶんきっともっといい書き方があるはずです。

他にも今から見ると書き直したい部分があったり、そもそもライブラリのアップデータもしたいのですが、そこまでモチベーションがわきません。まあそのうち、ついでがあれば。


5/24追記。

タイムゾーンを設定する方法がありました。

$ env TZ=Asia/Tokyo node app.js

これでコードはシンプルになりました。

https://github.com/dai-shi/gunosy-rss

今さらながら、node-v0.12.3を入れました。 vmが新しくなったことはどこかで聞いたのですが、timeoutが指定できるようになっているというのは認識できていませんでした。

これまで、 rss-pipes や codeonmobile で、vmモジュールを使ってなんちゃってsandbox環境を作っていました。 その際、無限ループだけは避けたかったため、safeCode.jsというのを作って ループのカウントをしてexceptionを投げるようにしていました。

ところが、node-v0.12で新しくなったvmはtimeoutの指定ができるので、簡単にできるようになりました。 今後はこれを使うことにします。

サンプルコードはこちら。

var vm = require('vm');
var x = vm.runInNewContext('x=0;for(i=0;i<100;i++){x++;}x', {}, {
  timeout: 10
});
console.log(x);
x = vm.runInNewContext('x=0;for(i=0;i<10000;i++){x++;}x', {}, {
  timeout: 10
});
console.log(x);

実行するとこんな感じ。

$ node vm-test.js
100

vm.js:38
  return this.runInContext(context, options);
              ^
Error: Script execution timed out.
    at Error (native)
    at ContextifyScript.Script.runInNewContext (vm.js:38:15)
    at Object.exports.runInNewContext (vm.js:69:17)
    at Object.<anonymous> (/tmp/vm-test.js:6:8)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)

というわけで、なんちゃってsandboxが簡単に作れるようになりました。 ドキュメントにはuntrusted codeは別processで走らせるべきだというように書いてあります。確かに、メモリを大量に消費するようなmalicious codeには対処できないと思います。他にはどういう穴があるのでしょうか。globalオブジェクトだけを渡しているうちはそこまで悪いことはできないような気もしますが、どうなんでしょう。

http://dailyjs.com/2015/02/05/xray/ で紹介されていたx-rayというツールを試してみました。

https://github.com/lapwinglabs/x-ray のサンプルをほぼそのまま実行しました。

すこし脱線しますが、yieldableというのを知りました。利用者がcallbackタイプとyieldタイプを使い分けられるのはいいですね。

x-rayはpaginationがサポートされているのが便利でした。もう一歩進んで、一つリンクを進んで詳細ページも扱えるといいのですが。

さて、プロクシの設定をしたかったのですが、x-ray自体にはサポートされていません。しかし、内部で使われているsuperagentを差し替えることはできます。superagent-proxyというライブラリがあるのでそれを使えばできそうです。 今回はいつも使っているrequestを使ってみました。下記にコードスニペットを載せておきます。

var xray = require('x-ray');
xray.prototype.request = function(url, callback) {
  require('request').get(url, function(err, response, body) {
    callback(err, body);
  });
};

あまりうまいやり方ではないですが、一応こんな感じで動いています。

小ネタでした。

今まで、Chromeアプリにはそれほど興味を持っていなかったのですが、 下記の記事でangular.jsと組み合わせているのを見て興味が湧きました。

https://developer.chrome.com/apps/angular_framework

これは、手軽に作れそうです。 Chromeのディベロッパーモードをオンにすると、 Chromeアプリをパッケージする前にフォルダ指定でアプリを実行できます。 しかし、コードをいじるたびにリロードボタンを押さないといけないです。

これは不便、なんとかして先日作った easy-livereload を使いたいと思って、Chromeアプリをexpressと組み合わせて 開発してみることにしました。

manifest.jsonやbackground.jsはサンプル通りに作って、 jadeはパッケージ化するときにコンパイルするようにしました。 package.jsonのscriptsはこんな感じです。

"build": "mkdir -p build; cp manifest.json public/*.js public/*.css build; jade views/main.jade --out build"

Chromeアプリのパスと合わせるようにexpessのルートを設定します。 ひと手間必要だったのは、httpで外部サイトにアクセスする場合です。 Chromeアプリではmanifest.jsonに書けば制限を外せるのですが、 expressでブラウザで動作させる場合はcross domainで引っかかります。 そこで、express側で中継するようにしました。

app.get(/^\/proxy-(http|https)\/(.*)/, function(req, res) {
  request.get(req.params[0] + '://' + req.params[1], {
    encoding: null,
    qs: req.query
  }, function(error, response, body) {
    if (!error && response.statusCode === 200) {
      res.type(response.headers['content-type']);
      res.send(body);
    } else {
      console.log(error, body);
      res.status(500).send('proxy error');
    }
  });
});

こんな感じです。場合によっては、ヘッダーなども中継する必要があるでしょう。 これをクライアント側から呼び出すときは、例えば次のようにしました。

var serverMode = $location.host() === 'localhost';
$http.get((serverMode ? 'proxy-http/' : 'http://') + 'www.foo.bar/hoge.json')

もっといい書き方があるかもしれませんが、まあとりあえずはいいでしょう。これで開発効率が格段に上がりました。

LiveReloadをご存知でしょうか。Web開発をするときに、ブラウザのリロードを省く機能を提供するものです。 類似のものとしては、Live.jsというのもあります。 また、Meteorでも実装されています。

一言で言うと、websocketでコネクションを維持して、サーバ側のファイル変更を通知して自動でリロードするものです。 CSSの場合はリロードせずに変更を適用することもできます。 livereloadは商用のようですが、そのコアのソースコードはMITライセンスで公開されています。

livereloadはブラウザ拡張を使うことがメインのようですが、javascript版のクライアントもあります。 javascript版だと、mobile safariでも動きます。 livereloadのnode.js用のサーバは、https://github.com/livereload/livereload-serverで公開されていますが、 これはそのまますぐには使うことができません。 平たく言うとプロトコルが実装されているだけで、実際に更新通知する機能は含まれていません。

そこで、簡単に使えるライブラリとしてまとめてみました。特徴をまとめると、

  • livereloadのサーバコードの最新版を使用(現在npmに登録されているlivereload系のモジュールはほとんど旧バージョン)
  • livereloadのクライアントコードの最新版を使用(npm化されていないのでnapaというモジュールで導入)
  • fs.watch()を使ってファイル変更を迅速に監視(node-watchというモジュールを使用)
  • node-devを使うことでサーバ側のファイル変更を監視して、クライアントを自動リロードする機能を追加
  • これらの機能をたった一行を加えるだけで使えるようにパッケージ化

となります。

プロジェクトページは下記です。

https://github.com/dai-shi/easy-livereload

最低限の使い方は、

app.use(require('easy-livereload')())

ですが、細かい設定はオプションを引数に与えることでできます。

本質的には他人が作ったライブラリを結合して簡単に使えるようにしただけですが、 相当便利だと思いますので、ぜひお試しください。

夏ごろに「暑さをみんなでふっとばせ!」というアプリを作りました。

1bitで使い捨てで匿名のコミュニケーションツールを作ってみた

に経緯が書いてあります。

当時は、Famo.usやF/Aのライブラリに手を入れないといけなかったのですが、その後バージョンアップして不具合が解消されました。 新しいバージョンでは読み込み速度なども改善しています。

せっかくなので、GitHubにソースをアップロードしました。 Famo.us/Angularとsocket.ioの組み合わせでこんなことができるというサンプルによいかと思います。

ついでに、「暑」を「寒」に変更する機能もつけました。タイトルはそのままですが。

GitHubのページはこちらです。

https://github.com/dai-shi/atsufut

実際に動作しているアプリのページはこちらです。

http://atsufut.axlight.com/#?color=blue&label=%E5%AF%92

自分が興味あるものの一つにコミュニケーションツールがあります。 と言っても普通のコミュニケーションではないです。 例えば、見知らぬ人と一時的なつながりを感じられるようなそんなツールがいいと思います。言語も使わず、世界のどこかの誰かと。

FacebookのPokeはよかったです。1bitで行うコミュニケーションとでも呼びましょうか。日本では「あいさつ」となり、意味が分からなくなりました。 ちなみに、Pokeは一度機能が隠れてしまったのですが、また復活しました。 最近は、Yoが流行っているらしいです。これも1bitコミュニケーションだと思います。どこまで流行るのか、気になるところです。

流行っているついでに、Snapchatについても気になるところです。 実は使ったことありませんが、これは使い捨てコミュニケーションだと思っています。 使い捨てコミュニケーションができたら面白いだろうなぁとは思っていましたが、 まさかそんなものが流行るとは想像していませんでした。 写真だからうまくいったのでしょうか。

あとは、匿名コミュニケーションにも興味あります。 最近は実名指向で、あまり匿名を重視したコミュニケーションツールは登場していないのでしょうか。

と言うわけで、前置きが長くなりましたが、1bitで使い捨てで匿名のコミュニケーションツールを作ってみようと思いました。 1bitで伝えたいことはなんだろう、と考えたその日が暑い日だったので、「暑い」にしました。 「暑い」を伝え合うだけでは、どんどん暑苦しくなるだけですので、 ストレス発散をできるような「吹き飛ばし」機能をつけることにしました。

atsufut_title.png

これがタイトル画面です。

atsufut_main.png

これがメイン画面です。これだけです。 画面の上半分をクリック(タップ)すると、丸が落ちてきます。 何度も押すと、丸がたまってきます。 次に画面の下半分をクリックすると、丸を吹き飛ばします。 それだけです。

それでは、やってみましょう。

http://atsufut.axlight.com/

このURLをスマホで開きましょう。PCでも大丈夫です。モダンブラウザ必須です。

一人でやると、すぐに飽きます。 複数人で同時にやるとまあまあ面白いとは思います。 しかし、たまたま同時に開いている人がいることは稀です。 ここをどうするかが悩みどころです。

今回、開発に利用したライブラリは、 node.js, socket.io, angular.js, famo.us あたりです。

自作のライブラリsocial-cms-backendでは、socket.ioのmiddlewareが使いたかったのでv1.0を先取りして使っていました。

socket.ioのv1.0はengine.ioという低レイヤから書き直しているらしいですが、一向にリリースされないなぁと思っていました。計画も分かりません。

socket.ioのの代替としてSockJSがあることを知りました。メインサイトのhttp://sockjs.org/がhttps://github.com/sockjs/sockjs-clientにリダイレクトされるのがなんかさびしいですが、そのうちかっこいいページを作るつもりなのでしょうか。

比較記事を探しましょう。

http://www.quora.com/Sock-js/What-are-the-pros-and-cons-of-socket-io-vs-sockjs

両方を実装した経験というのは説得力あります。 SockJSはWebSocketのAPIを中心にしていてシンプルなようですね。 今のところコミュニティもアクティブだそうで、なによりです。

http://stackoverflow.com/questions/22134061/engine-io-or-sockjs-which-one-to-choose

こちらもいい質問です。回答は集まっていませんが。 WebSocketを前提としてない場合はemulationするSockJSか、全ブラウザで動く確実な方式からアップグレードするengine.ioか、方針が逆ですね。時代の流れとしてはSockJSの方がいい気もします。

さて、SockJSのnodeのサーバはというと、 https://github.com/sockjs/sockjs-node ですね。 しかし、中身を読もうとしたら、、、

CoffeeScript

でした。うーん、なぜかワクワクしない。

もともとnode.jsを始めたきっかけもsocket.ioを手軽に使いたかったからなので、socket.ioも応援したいところです。と思って、https://github.com/learnboost/socket.ioを久しぶりに見てみると、おお、最近コミットがあるじゃないですか、1.0.0-preとかなっていていい感じです。ちょっと期待できるかなと思いつつも、このあとコミュニティがアクティブになるかどうかが気になります。

というわけで、しばらく様子を見ましょう。

mongodbのaggregateがパワフルで便利

  • 投稿日:
  • by

mongodbはドキュメントベースのデータベースですが、APIがシンプルでよいです。これまで、find()とcount()で足りていたのですが、ちょっと複雑なことをやろうとするとクライアント側で処理するはめになってしまいました。

mongodbの集計機能にはいくつかの種類があります。

http://docs.mongodb.org/manual/core/aggregation/

によると、Aggregation Pipeline, Map-Reduce, Single Purpose Aggregation Operationsとあります。 最後のSingle Purpose Aggregation Operationsはまさにcount()のようなものなのですが、他にもdistinct()やgroup()もあります。SQLから類推すれば機能は分かりやすいでしょう。 Map-Reduceは並列処理にはいいでしょう。ただ、JavaScriptの関数を記述することになります。なんでもできるといえばなんでもできるのですが。 残るは、Aggregation Pipelineです。これは処理を逐次的に書くので分かりやすいのが利点ですが、注目したのはJSONで記述できることです。JavaScriptの関数と違ってシリアライズができるのがうれしい。Pipeline Operatorsというのがあって、そこそこ柔軟に書けます。

今回は詳しく述べませんが、参考までにパイプラインのサンプルを載せておきます。

var pipeline = [{
  $project: {
    record: '$records'
  }
}, {
  $unwind: '$record'
}, {
  $match: {
    'record.name': target_name
  }
}, {
  $group: {
    _id: 'all',
    count: {
      $sum: '$record.score'
    }
  }
}];

この$unwindがいいですね。ドキュメントを展開してくれるのです。

欲を言えば、パイプラインではなく、処理を分岐できる有効グラフを記述できたらさらに強力だったろうに。

さて、なぜJSONで記述したかったかというと、social-cms-backendで使いたかったからです。aggregateをサポートしてバージョンアップしました。

https://www.npmjs.org/package/social-cms-backend

facebookはアプリを開発するときにテストユーザを作ることができます。これは非常に便利で、複数のユーザでの操作などもテストできます。

これまで、facebookアプリ(というかライブラリ)を自動でテストするときにテストユーザも自動で作っていました。過去記事を参照。他にも例えば、Qiitaの投稿に手順が載っています。

Facebookアプリのテストユーザーの作り方手順

ところが、このlogin_urlが使えなくなっているのに気づきました。404エラーになってしまうのです。Google検索すると、

http://stackoverflow.com/questions/22583963/404-error-trying-to-use-facebook-test-user-login-url

がヒットしました。今回は、stackoverflowでいい投稿を見つけました。すばらしい。stackoverflowは便利なときは便利です。facebookにバグ報告していて、

https://developers.facebook.com/bugs/276245435872240/

これによると、ポリシーが変わって使えなくなったと。「ドキュメントもアップデートしてくれ」と要望がでてますね。さて、stackoverflowのAnswerにはworkaroundも出ていて、簡単に回避できました。この方法が止められなければいいのですが。

参考までに、node.jsでのテストユーザのログインのコードの抜粋を載せておきます。

request.get('https://www.facebook.com', function() {
  request.post('https://www.facebook.com/login.php', {
    followAllRedirects: true,
    form: {
      email: facebook_user_email,
      pass: facebook_user_password
    }
  }, function(error, response) {
    //check response
  });
});

完全なコードはこちらです。

RSS Pipesを使って、実用的なことをしよう、というお話です。

以前、RSS Pipesで広告エントリの除去という記事を書いたのですが、あまり自分でも使っていませんでした。今回、どうしてもやりたいことがあったので、久しぶりにRSS Pipesをいじりました。

やりたいことというのは、RSSのURLの修正です。RSSのURLにリダイレクトURLが使われていることがあります。例えば、bit.lyなどの短縮URLが使われている場合です。通常は困らないのですが、そのRSSをプログラムで処理しようとする場合に、短縮URLではなく最終的なURLが欲しい場合があります。めずらしいケースだとは思います。

URLのリダイレクトをたどるにはこれまでのRSS Pipesの仕組みの上だけではできず、新たな関数を作り込む必要がありました。具体的には、getRedirectURL()という関数を作りました。

ところが、このgetRedirectURL()は同期的には結果を返すことができません。nodeでは、通常callbackを使って非同期処理を書くのですが、RSS Pipesのフィルターは同期的に書くように作られていたので困りました。仕方なく、promiseを使うことにしました。

フィルターの方でもpromiseを扱えるようにする必要があるため、Qを使えるようにしました。しかし、これでブロックするようなコードも書けてしまうので、悪意コード対策がいずれ必要になりますね。promiseにタイムアウトの概念を持ち込めれば簡単にできるかしら。

さて、新しくなったRSS Pipesで書いたフィルターがこちらです。

function rssPipesFilterFunction(articles) {
  var promises = [];
  articles.forEach(function(article) {
    var maxRedirects = 5;
    var loop = function() {
      var promise = getRedirectURL(article.link).then(function(link) {
        if (article.link === link || --maxRedirects < 0) {
          return article;
        } else {
          article.link = link;
          return loop();
        }
      });
      return promise;
    };
    promises.push(loop());
  });
  return Q.all(promises);
}

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

にも同じものを同じものを置いてあります。

リダイレクトが多重になっている場合を想定してループにしてみましたが、promiseをちゃんと理解していればもっとうまく書けるのかもしれません。どなたか改良してくれたら、うれしいです。

しかしこれでサーバ側の処理がますます重くなるので、キャッシュ機構を入れる必要がでてきそうです。そのうち、気が向いたらやりますか。今のところ、それほどユーザがいないのでほっておいても大丈夫です。それはそれでさびしいものの。

BMEANスタックというのは、BreezeJS, MongoDB, Express, AngularJS, Node.jsを使ったソフトエアスタックのことです。

Breezeを簡単に使いたいと思って、social-cms-backendを大幅に改修しました。 APIは変わっていないのですが、breeze-mongodbを使えるように工夫しました。反面、処理が複雑になりオーバヘッドが増えています。

その新しいsocial-cms-backendを使う段階で一つ気づいたことがあるので、メモ代わりに書いておきます。詳細は省きますが、クライアントサイドのコードでpromiseを使いたくなりました。そもそも、BreezeのAPIがpromiseを返します。Angularにも$qというのがあってpromiseが使えます。

ところが、Breezeのpromiseのメソッドが、

promise.then()
promise.fail()
promise.fin()

なのに対して、Angularの方は、

promise.then()
promise.catch()
promise.finally()

になっているのです。ちなみに、catch,finallyはjsBeautifierでは予約語とみなされて不便です。

wrapすればいいだけですが、なんとなく不満でした。それだけです。

Angularは、https://github.com/kriskowal/qをもとにしているとドキュメントに書いてありますが、どうしてメソッド名を変えたのでしょう。きっと、なにか理由があるのだとは思いますが、読み取れなかったです。

タイトルにある「困った」はこの事実にしばらく気づかず、コーディングではまってしまった、ということでした。


2/16追記。

q.jsのソースコードを読んでいたら、catch,finallyも一応サポートされていました。いずれにしても常に、thenだけを使っておくほうが安心な気がします。

これまでにPassportでFacebook認証をやったことはありますが、OAuth2.0のプロバイダを提供するためにどんなライブラリがあるのか、というのが今回の話です。

OAuth2.0のクライアントライブラリは多くありますが、プロバイダライブラリはどうでしょうか。思ったよりは多かったです。条件は、express/connectのmiddlewareとして使えることです。

現時点でのStar数順に並べます。

今回は、一つずつコメントして行こうと思います。

まず、oauth2orizeですが、これはだいぶリッチな感じです。toolkitとあるように、色々な機能があります。connect middlewareにもなるようですが、standaloneでも動かすこともできる様子。testやexampleも豊富で信頼感あります。

node-oauth2-providerは歴史がある感じです。機能は多くなく、シンプルです。ストレージもコーディングしないといけませんが、個人的には一番好みのライブラリです。

oauth2_server_nodeは、最近更新されていません。oauth2の仕様もdraft10とのことです。

node-auth2-serverは、まだactive developmentということでドキュメントが追いついていないようです。しかし、ドキュメントを見る限りでは機能も豊富で、シンプルなmiddlewareとしても使えそうなので、stableになれば、oauth2orizeよりはexpressで使いやすそうです。

OAuthも、READMEによるとdraftの仕様を参照しており、その後メンテされていない様子です。

oauth2-expressは、READMEが空です。よく見たらコードも未完成でした。

connect-oauth2も、WIPとなっていて1年ほどコミットがないので、中断したプロジェクトでしょうか。いいパッケージ名なのに惜しい。

node-oauth20-providerは、StarもFolkもゼロというプロジェクトですが、見た感じはだいぶしっかりしています。最近コーディングを始めた様子。コミットログをよく見ると、node-oauth2-providerの存在を知らなかったようですね。node-oauth2-serverと比較すると、redis storageの対応が進んでいますが、全体的には劣る感じがします。また、コミュニティがついていないのが痛いですね。今後、作者の進め方次第では状況は変わることはあるでしょう。

以上、まとめると、現時点では、 シンプルで安定しているけど自分でコーディングしないといけないnode-oauth2-providerか、機能豊富でアクティブなnode-oauth2-serverがよさそうです。

でも、どっちも使ってみたわけではないので、ほんの参考程度です。

Gunosy RSS がしばらく前から調子悪く、RSSが取得できていなかったので調べました。

初めの原因は、GunosyページのHTML構造が変わったからでした。 (ところで、何で変わったのでしょう。前バージョンのクリーンで先進的な感じでした。)

HTML構造はすぐに対応できたのですが、大変なのはそれからでした。 なぜか、リクエストがタイムアウトしてしまう現象が発生しました。

https://devcenter.heroku.com/articles/request-timeout

に書いてあるように、herokuは30秒しか待ってくれません。 それを大幅にオーバーしているようなのです。

試しに、30秒ごとに1バイトずつデータを送ってタイムアウトしないようにしてみました。しかし、そのうちメモリがあふれてしまいました。

次に、リクエストを並列で処理しないようにしました。処理中にリクエストが来た場合には503レスポンスを返します。親切にRetry-Afterヘッダもつけました。

これで多少改善されましたが、それでも1つの処理に20秒くらいかかることもあり、30秒を超えることもしばしばでした。

ところで、なぜ、この問題が簡単に解決できなかったかと言うと、 ローカル環境では再現しないからです。ローカルでは数秒で処理が終わるものが、herokuにデプロイした状態では数十秒から場合によっては1分以上かかってました。

これは仕方ないと思い、本番環境でデバッグしました。直るまでは正常に使えないことには変わりないという割り切りで。

で、発見しました。遅さの原因はJSONPathというライブラリでした。これが数十秒もかかってました。ローカルでは数秒。

https://github.com/s3u/JSONPath/issues/14

で報告されているのを発見し、古いバージョンを使うことにしました。 そうしたら、なんと数秒で処理が終わるようになりました。 ローカル環境では、1秒もかかりません。めでたし、めでたし。

しかし、ローカル環境とherokuで10倍から100倍くらいの差があるのは、なぜでしょう。単なるスペックの問題でしょうか。そうであれば納得はしますが、その場合はローカルでも低スペックを再現できるような環境が欲しいですね。

リクエストを並列で走らせないようにする処理は残したままです。完全に取ってしまうと、また不具合があったときに波及が大きいと考えたためです。ただ、せめて数本同時に走らせるようにしないと、503になるケースが多すぎる気もするので、それは今後対応できたらよいかと思います。#2

ところで、Gunosy RSSの不具合は誰も気づいていなかったのでしょうか。 それはそれでいいような、さびしいような。

Railsより簡単にSNSを作れるようにすることを目指したライブラリ social-cms-backend を改良しました。

これまでは、認証機構はFacebook連携のみが実装されていたのですが、 Facebookに依存したくないアプリを作るケースも考え、 今回HTTP Digest認証も入れました。 Formによるパスワード認証も一応あったのですが、これは動作確認用で、not for production扱いです。 やはり生のパスワードがネット上を流れるのはよくないということで。 (SSLなしの想定)

Digest認証のサーバ側は passport-http を使いました。 Digest認証をそのまま使うのは不便なので、2つ工夫をしました。 一つは、RememberMeクッキーを併用することで、 もう一つは、JavaScriptでDigestを生成して認証することです。 Digest認証の最大の問題はUIがブラウザ依存だということです。 realmで指定する文字列しか指定できません。 また、ブラウザがパスワードを覚えてしまうので、アプリ側からログアウトできません。 そこで、DigestのヘッダーをJavaScriptで生成しました。 これは、完全な仕様通りではありません。細かい話は省きますが、 今回のサーバ実装ではたまたまnonceのサーバ側のチェックを省略していたのでできてしまったまでです。 ただ、もしチェックではじかれた場合でも通常のブラウザのDigest認証に fallbackするだけなので、リスクが増えるわけではないはずです。 逆に、パスワードを一回目で間違えた場合もfallbackしてしまいます。 このあたり、いい解決法がないようです。 (そもそもDigest認証の仕様に従わず、なんちゃってDigestにしてしまえばいいのかも)

Digest認証のクライアント側として request をunit testで使いました。使おうとしてから気づいたのですが、 Digest認証の実装が不十分だったので、直してPull Requestしておきました。 無事マージされました。

social-cms-backendはangular.jsでTwitterっぽいアプリを作るのに 便利なのですが、ちょっと使い方にくせがあります。 もっとチュートリアルを増やさないと使えるレベルにならないかも。

Node.jsでオフラインで動くHTML5のWebアプリを作るときに、connect-cache-manifestを使う に書いた2つの不満を解消するために connect-cache-manifest を改良しました。

今回の修正点は2つ。 1つはファイルを除外するオプション、もう一つはファイル名とURLのパスが同一でない場合に書き換えるオプションです。

説明よりコードを読んだほうが分かりやすいと思ます。

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/',
    ignore: function(x) {
      return (/(?:\.swp|~)$/).test(x);
    }
  }, {
    dir: __dirname + '/views',
    prefix: '/html/',
    ignore: function(x) {
      return (/(?:\.swp|~)$/).test(x);
    },
    replace: function(x) {
      return x.replace(/\.jade$/, '.html');
    }
  }],
  networks: ['*'],
  fallbacks: []
}));
app.use('/static', express.static(path.join(__dirname, 'public')));
app.get('/html/:name', function(req, res) {
  res.render(req.params.name);
});

このように、ignoreとreplaceがオプション指定できるようになりました。

これで、バックアップファイルは除外されるし、jadeファイルが増えてもリストアップする必要がありません。とても便利です。