「RSS」と一致するもの

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オブジェクトだけを渡しているうちはそこまで悪いことはできないような気もしますが、どうなんでしょう。

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をちゃんと理解していればもっとうまく書けるのかもしれません。どなたか改良してくれたら、うれしいです。

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

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の不具合は誰も気づいていなかったのでしょうか。 それはそれでいいような、さびしいような。

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

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

ご参考になれば。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

Googleリーダがなくなるから、それの代替として作ろうと始めましたが、Googleリーダの期限までには完成しそうにありません。

今日は、RSSの記事のリンクを開く機能を作りました。 CanvasではHTMLは表示できません。そこで、Canvasの外側に独立して iframeを使うことで表示しました。 iframeの表示はjQueryを使いました。KineticJSのようなpure JavaScriptを使っていると、DOM操作はちょっと古びたものに感じます。

さて、RSSのリンクを開くためのボタンも作ってみました。 右上の方に矢印のアイコンを置いて、それを押すとiframeが開きます。 その矢印アイコンですが、Kineticのshapeで作成するのは面倒だったので、inkscapeで作成してSVG DataをKineticのpathで読み込みました。

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

var path = new Kinetic.Path({
  x: 0,
  y: -6,
  data: 'M 23.618434,50.171286 41.144607,36.346769 23.23155,22.400304 l 0.0092,8.18132 c 0,0 -7.445838,-1.03921 -11.864782,3.095364 -4.4188829,4.134559 -4.3930939,13.742678 -4.3930939,13.742678 0,0 2.442808,-4.269676 6.9240349,-5.841822 4.481182,-1.572147 9.583341,-0.746586 9.583341,-0.746586 l 0.127874,9.340028 z',
  fill: '#f0f0f0',
  scale: 0.43
});

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

実行サンプルはこちらです。

http://canvas-rss-reader.herokuapp.com/

未読既読の機能までできたら、とりあえずは使えるようになるかと思うのですが、どうでしょう。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

今回は、フリックで横スライドに挑戦しました。 しばらく時間が空いてしまったのは、どうやって実装するかイメージがわかなかったからです。 試行錯誤した結果、比較的満足いくものになりました。

実装するときに気にしているのは、 普通のスマホアプリと同じくらいの操作性を目指しつつ、KineticJSの機能をフル活用して少ないコーディングで実現することです。 横スライドも、縦スクロールと同様にドラッグ&ドロップの機能を活用しました。 なので、正確にはフリックを判定しているわけではなく、ドロップ後の位置でスライドするかどうか判定しています。 ちょっと使ってみると、直感と異なることもあるのですが、それは今後の課題ということで。

今回、Canvasでアプリを作る時に意識をしているのは、軽快さです。 普通に作ったアプリより軽く感じるWebアプリを作れるのかがポイントです。 現状はまだ機能が少ない(そもそも、まだRSSのURLが開けない)ですが、 自分のスマホではかなり軽快にスライド動作ができるようになっています。

他のスマホではどうなのか気になります。 ぜひ、お持ちのスマホで試してみてください。 下記の実行サンプルをスマホのブラウザで開くだけです。

http://canvas-rss-reader.herokuapp.com/

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

この時点のコードを参照するには、本日のコミットを参照してください。 上記実行サンプルは、今後最新版に変わっていってしまいますので、ご注意を。


6/23追記。

大切な発見を書くのを忘れていました。TweenのonFinishは呼ばれないことがあってはまりました。Tweenでノードを動かしている時に、ドラッグ&ドロップをすると、終了時にonFinishが呼ばれませんでした。setTimeoutを使うことで解決しました。KineticJSのバージョンは、4.5.2です。この仕様が将来のバージョンでどうなるかは不明です。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

以前、RSS取得を実装したので、今回はそれっぽく表示してみることにしました。

まずは、RSSのタイトルと概要をリスト表示するだけです。 一つのアイテムあたりに80pxの高さを使いました。

KineticJSなのかCanvasなのか分かりませんが、フォントサイズをpxで指定するため、手軽にレイアウトできました。

スクロール処理もちゃんと動き、上々です。

しかし、改行の処理が甘いですね。甘いというレベルではなくおかしいですね。このあたり、Canvasでアプリを作るときのつらさかもしれません。はまりそうなので、今回は手を出さないことにしました。

今回は実行結果のスクリーンショットをとりました。

canvas-scn.png

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

実行サンプルはこちらから。

http://canvas-rss-reader.herokuapp.com/

どのRSSフィードを表示するかもクエリパラメータでURL指定できるのですが、詳しい話はまた今度にします。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

まだ、スクロールの処理を実装しています。KineticJSでできるだけ簡単にやろうと考えて、ドラッグ&ドロップのイベントをつかってやりました。

スマホで使うには、やはり慣性スクロールが重要です。正しい呼び方は知りませんが、あの指を離してもしばらくスクロールしているあれです。

まじめに物理シミュレーションをしようかとも思ったのですが、使いやすければいいだろうということで感覚的にパラメータ調整しました。KineticJSではここらあたりが限界でしょうか。

ちなみに、iPhoneの複数のアプリでスクロール操作を試してみましたが、アプリによって挙動が違うことに気づきました。例えば、Safariではスクロールが止まるのが早いですが、Twitterではしばらくスクロールしています。今まであまり意識していなかったので気づかなかったです。

あと、スクロール途中にタッチして止めることが相当重要だとうことを再認識しました。

コードは少し長くなってしまったので載せません。あまり特記するポイントはないです。バウンスするところと組み合わせるのがすこしやっかいでした。

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

実行サンプルはこちらから。

http://canvas-rss-reader.herokuapp.com/

うまくいったらライブラリ化したいと思いつつも、今は決めうちのコードになっています。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

今回は、スクロール時のバウンスに挑戦です。スクロールの端で戻るやつです。

米特許商標局、アップルのバウンススクロール特許は無効と予備的判断。対サムスンでも争点

こんなニュースになっているやつです。

さて、KineticJSにはTweenという機能があって、スムーズな動きが簡単に表現できます。ちなみに、Tweenというのは、

http://en.wikipedia.org/wiki/Inbetweening

のことのようですね。 例によって、 KineticJSのチュートリアル を見ながらやったのですが、一つはまったことがあります。

最初にどのドキュメントをみたのか定かではありませんが、今日までKineticJSのv4.4.2を使ってました。しかし、このバージョンではチュートリアル通りのコードを実行するとエラーになります。最新のv4.5.2を使ったら解決しました。

コードはこんな簡単に書けてしまいました。すごいお手軽。

layer.on('dragend', function() {
  var pos = layer.getPosition();
  var newY;
  if (pos.y > 0) {
    newY = 0;
  } else if (pos.y < -layerHeight + stage.getHeight()) {
    newY = -layerHeight + stage.getHeight();
  } else {
    return;
  }
  var tween = new Kinetic.Tween({
    node: layer,
    easing: Kinetic.Easings.StrongEaseOut,
    duration: 0.3,
    y: newY
  });
  tween.play();
});

今回も、実行結果はスクリーンショットでは見せられないので、下記からどうぞ。アニメーション系はスクリーンショットは使えませんね。動画にするのは手間がかかりすぎる気がします。

http://canvas-rss-reader.herokuapp.com/

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/tree/adfe56808e98da9936c690b85bd169aabca4bb13

コミットの日付を見れば分かるでしょうということで、次回からはコミット指定のURLの記載は省略します。