ブラウザ間直接双方向通信をどうやって実装するか?

ブラウザのセキュリティポリシーをどうする?

NaClは"安全性"を手放さず、ネイティブアプリケーションのパワーを手に入れるという事を求めていました。


今回の場合、最低限許さなければならないのはブラウザの周りでネットワークを使う。Socketのlisten,accept,connectが出来る様にするという事です。


以前の実装ではActionScript3.0をJavaScriptから呼べる様にしていました。これはExternalInterfaceで可能です。


しかしAS3.0にはSocketのlisten,acceptをする事が出来ません。
というわけで、あの時はローカルにProxy-Serverというある意味でのRelayServerを立てていました。

この問題点が何であったかというと、先にエンドユーザを組込むという話をしていましたが、そもそもエンドユーザは多分こんなものを立ててくれないだろう・・・という事で、更に言うとローカルにRelayServerを立てさせると今回は出来ないから仕方なく導入した事によるRelayコストが掛かるのです。


通常のWebProxyServerやReverseProxyの様に理由があって立てたわけでは有りませんし、それらの機能をこのProxyServerに盛り込むには無理が有ります。


なんとかしてWebブラウザ上でSocketの全操作を許さなければいけません。極端な話だと、Webブラウザがサーバになるのです。


これを解決するのは単純に、ブラウザ上で(クロスドメインをも超える)connect,listen,acceptが出来る様にするという事です。これはuntrustedなcodeの不正を防ぐとかでは無く、許可しないとやりようも無いので許可しなければなりません。

さてどうする??

ActionScript3.0は使えません。


どうするか??方法は幾つか有ります。

  1. 新しくWebブラウザを作る
  2. NaClの様に、Webブラウザ毎に力を与える何かを使う。それはActiveXでありNetscape Pluginでありetc
  3. Java Appletを使う。


さて、現在私が実装を行ったのは、JavaAppletを使う方法でした。
次にその事を述べます。

Java Appletの場合

ここでは簡潔に十分に述べる事にします。別のエントリかなんかで詳しく書いた方が良い気もしますので(w


そもそも何故Java Appletを用いるのか・・・という事ですが、皆さんLiveConnect(今はXPConnectの方が良いかも)という技術をご存知でしょうか??


コイツはJavaScript <-> Javaで双方向にやりとりする技術です。
JavaScriptからJavaの関数を呼び出したり、JavaからJavaScriptの関数を呼び出したり・・・なんていう事が出来るわけですね。
さて、何故Java Appletを用いるのかを述べる事にします。簡単な話ですが、Java Appletでは、java.policyというconfigファイルの中でSocketPermissionを弄る事で、Webブラウザがサーバに成ります。


今現在LiveConnectを使う事でJavaScript側からJavaといちゃいちゃ出来るという実装にしています。


なぜJavaScriptなのか・・・という話なのですが、ここは結構重要な話になります。

何故 JavaScript ??

要するにWebブラウザとの親和性が一番高くて、HTMLというリソースをDOMを介して弄れる・・・という事に尽きます。


Webアプリケーションの開発者は、最近はJavaScriptを好んで使うようです。
私としてはSchemeとかHaskellの方が良いんでは無いかなーと思うのですが、今いる人達を取り込む必要性があるので、違和感無くJavaScriptのコードを書けば実はブラウザ間で直接双方向通信出来ちゃった!!という事をやる必要があるだろうな、という事でJavaScriptから色々出来る様にしました。


実際にコードを張ります。

receive = function(mes)//今の所の実装では、Stringが入る事に成っている。
{
  document.my_receive.receive.value += mes +"\n";
}

getNTS1 = function(obj)
{
  obj.initStream();
  obj.setCallBackFunc("receive");//受信時に呼ばれるコールバック関数をregistする。
  obj.startReceive();
  nts.push(obj);
  window.console.log(nts.length);
}

send = function()
{
  for(var i=0; i<nts.length; ++i)
  {
    nts[i].writeString(document.my_input.my_word.value);//皆さんに直接文字列を飛ばす。
  }
  document.my_receive.receive.value += (document.my_input.my_word.value) + "\n";
}

これはチャットシステムのHTMLソースから引っ張って来た物です。


ボタンに対してsendを割り当てているので、何か書き込んでボタンを押すとメッセージがぶっ飛んで相手側のreceive関数が呼ばれて、text-boxに入る・・・という事です。


全体のコードを出すのは今度のエントリぐらいで。


このコールバックモデルにしたのは、Ajaxを使って開発してきた開発者に取っての親和性を考えたものです。


とこんな感じで書ける様になっています。

#余談ですが、receiveに関しては別Threadで走っている為に非同期です。
#そもそもググるChromeの様にマルチプロセスで無いブラウザを使った場合LiveConnect経由でSocket#receiveをすると固まってブラウザがハングした様に見えます。
#タブブラウザFirefoxさんでもブラウザごと固まります。この辺のバッドノウハウについても次回あたり。

その他の方法。

1.ブラウザを作るというのは一番やりやすい方法かもしれませんが、まず面倒くさい。次に、作った所で誰も使ってくれないのではないか。そもそもシステムを作りたいんだから、それをやるのは最終的な手段では無いか・・・などと考えるに至ってまだ作っていません。
なので、この先はどうなってくるか分かりませんが、仮に作るとしてもWebブラウザという形では無い気がします。


2.ActiveXやPluginを使う方法。
これは理想的だと思います。そもそもこれの何が嬉しいかと言うと、JavaScriptが何らかの形で関わる言語処理系を作らなくて済むという事です。
先にも述べましたが、CLispSchemeHaskellが使いたい人はソレで書けば良いんですよ。わざわざJavaScriptを使う必要も無い。
2番目の方法ではこれらの事も可能に成りますし、普通にSocket周りも出来ますね。
なぜPluginを作る方向で最初から作らなかったのか・・・という事ですが、それには幾つか訳が有ります。

  • プラットフォーム毎に書くのが面倒くさい。(NaClを見てやる気になったので、昨日少なくともLeopardで動くNetscape Pluginを書きましたが、結構面倒臭かった・・・。) JavaVMの上で動くJavaAppletなら、JVM Byte Code準備して上げれば終わりですからね。
  • そもそもPluginとか書いた事が無かった。

とこんな感じです。まぁ面倒くさかったというのが1つでした。
それと同時に、やっぱりVMをあちこちで走る様にしているJVM恐ろしい子とか思ったりもしました。


Javaやべぇな・・・!!
ちなみに、JavaだとClojure使ったりScala使ってAppletを作る事が出来ます。JavaFXも出ましたし、Java7も・・・。なんて恐ろしい子っ!!

NATを超える

セキュリティの話は一旦置いておくと、Webブラウザの上でSocketのアレソレが出来る様に成りました!
やったね!!


で、次に"広域分散"だとか"P2P"をやる際につきまとう問題が出てきます。
それはNAT(NAPT)をどうやって超えるかという事です。


これはまた別の記事でNat Traversalについて書こうと思うのですが、簡単に今の所実装している事を紹介します。
また実際にNATを超えています。先に見せた例のJavaScriptWebブラウザ上で双方向通信をNATを超えてやっています。

どうやるの??

今回はUDP Hole PunchingとTCP Hole Punchingを行いました。


手元にあるJavaScriptライブラリではUDPでもTCPでもNATを超える事が出来ます。やりましたね!!

という事で

未踏ユースで提出した書類に、プラスとして今現在出来ている事を追加しながら色々と書いてみました。


これはもうクローズドでも何でも有りませんので、現時点のソースコードなりなんなりを公開しようと思います。
それと、落ちはしましたがこの方向に何かはあると考えているので、この先も進めて行くつもりでは有ります。
(ちなみに、どこで公開すると良いんですかねー。SourceForgeとかかな?)


あと、完全に負け惜しみではありますが、これで落ちたのが残念でした。色々とタイムリーでウハウハなんじゃねーかなとか勝手に思っていたんですが、調子乗ってただけっぽいですね。