「JavaScript」と一致するもの

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モジュールのサンドボックス環境でリソース制限までしてくれるようにはならないでしょうかね。ちょっと興味あります。

まだ誰にも使われていないRSS Pipesですが、機能を追加しました。

RSS PipesはRSSのアグリゲーターでフィルターをJavaScriptで編集します。編集はWebブラウザ上で行うので、いわゆるWikiのようなシステムです。

誰でも編集できてしまうので、そのうちSPAM行為をする利用者もでてくるかもしれません。まあ、そうなるくらいになったらうれしいわけですが。

SPAM行為をされてから対策すればよいと思っていたですが、SPAM対策がされていないことで、利用者が躊躇してしまうのは本望ではありません。そこで、簡単なロック機能を作りました。

編集するときにロックコードを入力すると、以降そのロックコードを入力しないと更新できなくなります。もちろん、ロックコードを忘れたら、本人でも更新できなくなります。そういうときは、あきらめて新しいフィルターとして再登録しましょう。「複製」ボタンがあるので、簡単です。

http://dai-shi.github.io/rss-pipes/

RSS Pipesを使ってJavaScriptの勉強をしよう、の第三弾です。

昨日、はてなブックマーク新着エントリからキーワードでフィルタリングするRSS Pipesの例を紹介しましたが、はてなブックマークの場合ははてなが提供するRSSでキーワードフィルタリングできてしまいます。

そこで今日は、キーワードが含まれていた場合タイトルを書き換える方法を例題にします。

フィードを流し読みするときは、タイトルに気になるキーワードが入っていると目に留まりませんか。自然と目でキーワードフィルタリングしているのかもしれません。そこで、それを助けるためにタイトルを強調表示してみましょう。★マークをつけるのはどうでしょうか。

次のようなJavaScriptコードになります。

function rssPipesFilterFunction(articles) {
  var keywords = ["JavaScript", "javascript"];
  var i, len = keywords.length;
  articles.forEach(function(article) {
    if (article.title) {
      for (i = 0; i < len; i++) {
        if (article.title.indexOf(keywords[i]) >= 0) {
          article.title = "★★★ " + article.title;
          break;
        }
      }
    }
  });
  return articles;
}

前回と同じように、indexOfでキーワードを探して、見つかった場合はarticle.titleに文字列を追加しています。

RSS Pipesで動くサンプルはこちらです。

RSS Pipes: hatena/JavaScript関連のブックマーク/タイトル強調表示

もう、ネタがなくなってきたかも。

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

既に存在するアイテムをコピーして新しく登録する「複製」機能が欲しいと思って実装方法を考えていたのですが、思いのほか簡単にできました。

備忘メモしておきます。

$routeProviderのコードは、

$routeProvider.when('/home', {...});
$routeProvider.when('/edit', {...});

のようになっています。/homeに複製ボタンがあり、それを押すと/editに遷移します。つまり、/homeから/editに複製元のデータを渡したいということです。

$rootScopeを使いました。

$rootScope.saved = {};

としておいて、/homeのコントローラで、

$scope.saved.data = data;

として保存し、/editのコントローラで、

$scope.data = $scope.saved.data;

として復元します。場合によりますが、今回は消したかったので、さらに、

delete $scope.saved.data;

としました。

もっとスマートな方法があるのかもしれませんが、今回はこれでよしとします。

RSS Pipesを使ってJavaScriptの勉強をしよう、の第二弾です。これ、連載にできるかしら。

フィードを流し読みするときは、タイトルに気になるキーワードが入っているかが重要だと思います。キーワードでフィルタリングするというのはありがちですね。

次のようなJavaScriptコードになります。

function rssPipesFilterFunction(articles) {
  var keywords = ["JavaScript", "javascript"];
  var i, len = keywords.length;
  var newArticles = [];
  articles.forEach(function(article) {
    if (article.title) {
      for (i = 0; i < len; i++) {
        if (article.title.indexOf(keywords[i]) >= 0) {
          newArticles.push(article);
          break;
        }
      }
    }
  });
  return newArticles;
}

キーワードが一つでも見つかったら、breakでforループを抜けて次に進むところがポイントです。Array.forEachを使うと、iとlenの変数が不要になるのですが、その場合JavaScriptだとループを抜け出せないのです。

今回はindexOfで書いてみましたが、ここを正規表現でマッチさせる方法もあります。今回の例だと、/javascript/iという簡単な正規表現で書けるので、そのほうが一般的かもしれません。正規表現を使う時はループの外側で事前に定義してprecompileするようにしましょう。

article.titleだけではなく、article.descriptionもチェックするとタイトルではなくdescriptionにキーワードが含まれているかもチェックできます。今回は、タイトルだけにとどめました。

実際これをRSS Pipesで適用した例が、下記にあります。

RSS Pipes: hatena/JavaScript関連のブックマーク

この例ははてなブックマークの新着エントリーを使いましたが、はてなブックマークを使うだけならはてなが提供するRSSで対応できましたね。

http://b.hatena.ne.jp/keyword/JavaScript?mode=rss

つづく。かな??

RSS Pipesで広告エントリの除去

  • 投稿日:
  • by

RSSを処理すると言えば、まずは広告エントリの除去でしょうか。 Yahoo! Pipesの利用例でもよくありそうです。

RSS Pipesの場合はJavaScriptでフィルターを書きます。

function rssPipesFilterFunction(articles) {
  var newArticles = [];
  articles.forEach(function(article) {
    if (article.title && article.title.lastIndexOf('AD:', 0) != 0 &&
        article.title.lastIndexOf('PR:', 0) != 0) {
      newArticles.push(article);
    }
  });
  return newArticles;
}

このような感じになります。

AD,PRエントリを除去するフィルター #rsspipes にも同じものを置いています。

今、思ったのですが、これってJavaScriptの勉強に結構使えるのではないでしょうか。比較的短いコードで実用的な機能が作れるので。

実際これをRSS Pipesで適用した例が、下記にあります。

RSS Pipes: news/itmedia&cnetjapan

このコードをコピペするだけで、他のRSSの処理もできるようになります。いかがでしょうか。

JQueryやAngularJSはGoogle Hosted Librariesが配信してくれて、 BootstrapはBootstrapCDNというのもあって 便利です。

しかし、マイナーなライブラリになるとどうしたものかと思っていました。手を入れないライブラリは自分でホスティングする理由もないので、CDNがないかなと思って探したところ、

を見つけました。cdnjsはGitHubのPull Requestsでライブラリの登録を依頼できるというのが面白いです。

しかし、どちらもcssファイルは登録されていない様子なので、結局すべてを外部CDNに頼ることはできないと。探せば他にもありそうですね。

Google Readerがなくなることが第一の理由ではないのですが、RSSってもっと便利にならないかと以前から考えていて、RSS PipesというWebサービスを作りました。

名前が似ているYahoo! Pipesから連想されるとちょっと困るような、困らないような感じですが、いわゆるRSSアグリゲーターです。フィードを表示するUIはなくて、RSSを出力します。 Feedweaver よりは汎用性があって、 Yahoo! Pipes よりは汎用性がないといったところです。

あまり比較することには意味がないですね。 RSS Pipesを作った理由は、

  • ログインせずにRSSを作れるアグリゲーターが欲しかった
  • JavaScriptでフィルターを書きたかった
  • みんなでRSSを作れるようにしたかった
  • 単に作りたかった

からです。まだできたてほやほやで、この段階でどれだけの人にアピールできるか分かりません。もし要望があればお知らせください。

RSS Pipes

上記リンクからどうぞ。

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で配列を結合したいと思いました。配列の個数は分からず、任意の場合です。

var x = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [], [10]];

を、

var xx = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

に変換したいということです。 SchemeだとSRFI-1にconcatenateというのがあるのですが、JavaScriptはどうするとよいのかと思って調べました。

結論から言うと、

var xx = [].concat.apply([], x);

がよさそうです。

jsperfにベンチマークがありました。

http://jsperf.com/multi-array-concat/9

Safariでは別の書き方(push.applyをループする)の方がよいケースもあるようですが、コーディングの長さも考えると、上記の書き方がベストでしょう。

ついでに、結合する配列が2個に限定される場合のベンチマークも見つけました。

http://jsperf.com/concatperftest/13

この場合は、Array.concatを使うより、Array.push.applyを使うほうがよさそうです。