2010年2月22日月曜日

mod_websocket for Lighty:動作概要編

やっと動作概要のエントリだ。はぁはぁ。
さて、mod_websocket for Lightyですが、以下のように動いています。


終わり。ごめんなさい、ごめんなs(ry
いや、上図だけで分かるとは思いますが、ちゃんと説明します。
まず、lighttpd.confに、以下のように記述したとします。

server.port = 8000
websocket.server = ( "/demo" => ( "host" => "192.168.0.2", port => 9000 ) )

そして、lighttpdを192.168.0.1で起動したとします。すると、lighttpdは、192.168.0.1:8000でListenします。
この後は、以下の図を用いて説明します。


  1. まず、ブラウザから、http://192.168.0.1:8000/ws_demo.htmlを要求します。
  2. すると、lighttpdは、server.document-rootで指定したフォルダから、ws_demo.htmlを配布します。
  3. 次に、ブラウザはws_demo.htmlに記述してあるjavascript、ws = new WebSocket("ws://192.168.0.1:8000/demo");を実行します。
  4. WebSocket handshakeを受け取ったlighttpd内のmod_websocketは、そのリソース"/demo"およびmandatory headersをcheckし、正しいWebSocketリクエストだと判断したら、lighttpd.confに記述されたdemo server = 192.168.0.2:9000に接続しに行きます。(不正なHandshakeの場合、現状、404 Not Foundを返します)
  5. 接続に成功すると、mod_websocketは、ブラウザに対して、WebSocket Handshake 101 Responseを返答します。(なお、失敗すると503 Service Unavailableを返しますが、このあたりは仕様に規定がまだ無かった気がします…)
  6. ブラウザからws.send("Hello");を実行した場合、'0x00' + "Hello" + '0xff'といったメッセージがmod_websocketに届きます。
  7. mod_websocketは、'0x00'と'0xff'で囲まれたメッセージを、demo serverに送りつけます。(つまり、'0x00'/'0xff'を削除します)
  8. demo serverは、受け取ったメッセージを操作して、例えば、"How r U?"と送りかえします。('0x00'および'0xff'などは付けません)
  9. 送り返されたメッセージは、mod_websocketにより、'0x00' + "How r U?" + '0xff'とWebSocket Frame Formatに変換され、ブラウザまで配信されます。
  • 利点
  1. backendサーバと、WebSocketの動作が完全に切り離されているため、lighttpdを落とすこと無く、WebSocketで提供するサービスだけ再起動が可能となります。
  2. backendサーバは、通常のTCP Serverならば、どんな言語で作成されてもかまいません。現状githubには、C言語で作成したecho severとchat server、rubyで作成したecho severのサンプルを入れてあります。
  3. また、backendサーバはIP Reachableな環境で起動していればよいので、WebSocketで提供するサービスのscalabilityが向上します。(一概には言えませんが)
  • 難点
  1. mod_websocketとbackendサーバの間の通信は、全くメッセージフォーマットの無いTCP connectionが張られます。つまり、メッセージの開始と終了の判断がつかないこととなります。
    その為、Javascript内部で送信するメッセージと、backendサーバの間でちゃんとメッセージフォーマットを決めないと、メッセージのハンドルに困ります。(githubのhtml/ws_echo_ruby.htmlと、src/sample/ws_echo.rbを見ていただくと理由が分かるかと思います)
  2. 多分…ですが、この設計ではSub Protocolを扱うことが出来ません。もし、Sub Protocolが、「同一WebSocketリソースに対する引き数(argument)」という仕様だった場合、mod_websocketとbackendサーバの間で引き数を受け渡すプロトコルが必要となってしまいます。
    まだSub Protocolの仕様がはっきりしないため放置していますが、もし、上述のような仕様となった場合、mod_websocketではリソースを分けてご利用ください、と言うexcuseが発生すると思います。
# 2について、わかりづらいかと思いましたので、少し追記を。
まず、Sub Protocolについては、
http://jsgt.org/mt/archives/01/002777.html
@toshirotさんが、ちょうど翻訳してくださっていますのでご参照を。

例えば。
'/chat'と言うWebSocketリソースがあるとします。そのリソースでは、chatサービスを提供しているのですが、実はサーバ1つで、chat_aサービスとchat_bサービスといった2つのサービスを提供できる。つまり、
switch (sub_proto) {
case 'chat_a':
    ....
    break;
case 'chat_b':
    ....
    break;
default:
    ....
    break;
}
こんなことが出来るサービスなんです、と。

この場合、pywebsocketなど、Service用のApplicationがmoduleと一体になっているような設計では、WebSocket-Protocolヘッダの中身を見ることによって、Applicationを分岐させることができます。
しかし、Reverse Proxyっぽい設計では、Sub ProtocolをApplicationに通知できません。(もちろん、通信手順を決めれば出来ますけど)
その為、以下の様にリソースを分けて対処するしかないかなぁ、と思っています。(良い解決手段があれば教えてください。(^-^;)
<例>
WebSocketリソースが'/chat'かつ、WebSocket-Protoが'chat_a'の場合は、192.168.0.2:9000へ。
WebSocketリソースが'/chat'かつ、WebSocket-Protoが'chat_b'の場合は、192.168.0.2:9001へ。
こうするしかないかな、と。

以上、mod_websocketの動作概要でした。

とりあえず、あとはlighttpd.confのparse -> hctxに変数作ってallowed origine, sub-protoを叩き込む(そして、handshake時に参照する)、と言うTODOが残っているのですが、ちょーめんどくさいです…。
そんなに難しくはないと思いますので、誰か作って!…誰も居なければ頑張りますけど(T_T。

さて、今後は更新が滞ると思いますが、HTML5を推進されていらっしゃる皆様、頑張ってください!心より、応援しております。

ではでは。

mod_websocket for Lighty:インストール編

前回はRev.75でのWebSocket Protocolの概要と、一般的な動作を記述させていただきました。今回は、lighttpd(lighty)用のWebSocketモジュールについて、まずは簡単なインストール方法と設定方法、そしてどのように動いているかと、その利点について記述してみますね。

# と、思ったのですが、インストール方法&設定方法が以外に長くなっちゃったので、mod_websocket for lightyの動作概要についてはまた別のエントリで…

まず、MAC OSへのインストールについては、@tnantokaさんが詳細に紹介してくださっています(ありがとうございました!)ので、そちらをご参照ください。
http://blog.bornneet.com/
http://blog.bornneet.com/Entry/271/

また、解決にあたっては、@nmiyoさん(http://blogger.tempus.org/ )に多大なご協力を頂いたことにお礼を…こんなところで申し訳ないのですが。

  • Lighttpdをソースからインストール
    mod_websocket.cを利用する場合、connections.c他に少し手を加えます。その為、portsやyum、aptなどでインストールした状態では使えません。申し訳ありませんが、ソースファイルからmakeを行ってください。
  • Lighttpdを改変せずにmake出来るかどうかを確認
    まずは、お手元の環境でlighttpdがmake出来るかどうかを確認してください。http://www.lighttpd.net/download/ から1.4系のソースコードをダウンロード、

    $ tar xzvf lighttpd-1.4.x.tar.gz

    として、ソースコードを展開、展開したディレクトリに入り、以下のコマンドを実行してみてください。

    $ ./autogen.sh

    ここでエラーが出る場合、あなたの環境にはautotoolsが入っていませんorバージョンが古いかもしれません。

    autotoolsとはソースコードのmakeやtestを楽にするツール群の総称なのですが、時々バージョン毎に大変更があったり、またちょっとしたBUG…と言って良いのかは不明ですが、今まで許されていた記法が許されなくなったりしてて、それまでのconfigure.inとかMakefile.amではconfigureが通らない場合が時々あります。ですので、autotoolsは出来るだけ最新のバージョン、かつstableなものを使うのが良いでしょう。
    また、autotoolsに含まれる、autoconf、automake、libtoolは相互に依存関係が発生しますので、autotoolsをソースからインストールされる方は、インストール順にもご注意ください。(autotoolsをソースからインストールされる方にそんな心配はいらないと思いますけれど...(^-^;

    Ubuntu上では、INSTALLの通り、以下の用にコマンドを実行します。

    $ sudo apt-get install automake-1.x
    $ sudo apt-get install libtool

    このコマンドを実行すると、automake 1.x、及び依存するツール群が自動的にインストールされます。ただし、Ubuntuだと、何故かlibtoolだけはインストールされない(まぁ、automakeには依存してないけれども…)ので、別途インストールしてあげる必要があります。

    なお、他のOSをお使いの方は、以下のコマンドを含むパッケージをインストールしてみるとよろしいかと思います。

    $ autoheader
    $ autoconf
    $ automake
    $ libtoolize

    さて、ここまでが終わると、次はlighttpdをインストールするために必要なライブラリの入手です。
    1. pcre lib( http://www.pcre.org/ )
      自身のconfigファイルのparseに用いています。パッケージもしくはソースからライブラリをmakeし、インストールしておきましょう。
    2. 圧縮アルゴリズムライブラリ
      configure時に、option指定することによって回避は出来るのですが、zip/bzip2など圧縮アルゴリズムライブラリは入れておいても損は無いので入れておきましょう。
    3. openssl
      SSLを利用する際には必須となるライブラリです。INSTALLに記載し忘れていますが、$ssh-keygenコマンドなども叩かないといけないので、通常のopensslパッケージも必要となります。(記載しておこう…(^ー^;)

      以上があれば、lighttpdのconfigure/makeは出来るようになっているはずです。確かめてみましょう。

      $ ./autogen.sh
      $ ./configure --with-your-options
      $ make

      ここで、--with-your-optionsを指定することによって、あなたが利用したいオプションの取捨選択、またインストール先などを決定することが出来ます。詳しい使い方は、

      $ ./configure --help

      これで一覧が出てきますので、確認してから指定してみると良いでしょう。さぁ、これでlighttpdがmake出来るようになりました。次は、いよいよmod_websocketのmakeです。
  • mod_websocketのmake
    まずはpatch当てとなります。mod_websocketをgithubから取得してください。

    $ git clone git://github.com/nori0428/mod_websocket.git

    ここで取得した、src/mod_websocket.patchを先にダウンロード、展開したlighttpdに適用することになります。
    ここは、INSTALLの手順どおり、lighttpdを展開したディレクトリに移動しpatchを当ててください。

    $ cd lighttpd-1.4.x
    $ patch -p1 < mod_websocket.patch

    なお、patchコマンドで何をしているのか、については、$man patchを参照してくださいね:)

    さて、最後に再度lighttpdのmakeとなります。INSTALLに記された手順どおりに、mod_websocket.cをコピーします。

    $ cd lighttpd-1.4.x
    $ cp /path_to/mod_websocket.c src/

    その後、もう一度、autogen.shを実行してください。

    $ ./autogen.sh

    なおこれは、src/Makefile.amにpatchを当てたため、もう一度automakeコマンドを実行する必要があるためです。
    autogen.shが正常に終わったら、configure, makeとなります。

    $ ./configure --with-your-options
    $ make

    さて、どうでしたでしょうか?configure, makeが正常に終了し、lighttpd-1.4.x/src/.libs/の配下にmod_websocket.soが出来ていれば成功です。
    出来ていない場合、もしくはconfigureなどでエラーが出ている場合は、今までの手順をもう一度見直してください。
    それでも駄目な場合は、ここのコメント欄や私宛のメールでお気軽にご相談ください
  • 起動方法について
    lighttpdソースを解凍した場合、lighttpd-1.4.x/docの下に、lighttpd.confと言うファイルがあるかと思います。これが、lighttpdを起動する際に必要となる設定ファイルとなります。
    なお、詳しい設定方法については、WEB上に多くの方が公開してくださっていますので、

    http://www.kozupon.com/lighttpd/

    書き換えるポイントだけ少し。

  • server.modules
    lighttpdで利用するモジュールを指定します。つまり、mod_websocketを利用する際は、ここに追記が必要です。
  • server.document-root
    lighttpdがhtmlファイルを公開するディレクトリです。通常だと'/var/www/htdocs'とかでしょうか。なお、このディレクトリはlighttpdを起動するユーザ(例えばnobody)に対して読み取り/実行権限が無いといけません。
  • server.errorlog
    lighttpdがエラーログを記録するファイルです。このファイルはlighttpdを起動するユーザ(例えばnobody)に対して書き込み権限が無いといけません。
  • accesslog.filename
    lighttpdがアクセスログを記録するファイルです。このファイルはlighttpdを起動するユーザ(例えばnobody)に対して書き込み権限が無いといけません。
  • server.port
    lighttpdが起動するポート番号です。コメントアウトされていると80番を利用します。
  • websocket.server = (
    "/chat" => ( "host" => "127.0.0.1" , "port" => 9001 )
    )
    パス'/chat'を、websocketリソースとして規定します。

    http://lighttpd_addr:lighttpd_port/chat へアクセスされた場合、WebSocket handshake等を行ったのち、host:portへパケットを転送します。hostはIPアドレスもしくはFQDNを表す文字列です。ローカルループバック以外も使えます。portはbackendのサーバが待ち受けるport番号を表す数値です。stringだと思って””で囲むと寂しいことになりますのでご注意を。

    注意
    現状、allowed origin/sub-protocolが扱えないのですが、扱えるようになった場合、websocket.serverの記法が変わります。(確実に変わります)
以上、無事にWebSocketがご利用できる環境がセットアップできる事を祈ります。ではでは。

2010年2月18日木曜日

Inside Web Socket

HTML5関連で、Web Socketが話題になることが増えてきました。

その中で、Web Socketはブラウザで生のTCPが扱えるようになった!いや、違うよ。生のTCPじゃないよ!と言ったことをよく目にします。
lighttpd 1.4.x用のmod_websocketを個人的に作成している為、そういった疑問に対しての参考になりましたら、と。勿論、間違ってたらご指摘ください。(^-^;

  • Web Socketとは
    ブラウザ(上のJavascript Program)で生のTCPを扱えるように策定されたプロトコルです。
    なぜ、そう言うのか、順を追って説明します。(ごめんなさい、やっぱりちょっと修正しておきます)
  • ブラウザがどう動いているのか?
    まず、Web Socketを利用する際、ブラウザがどのように動いているのかを説明します。また、Web Socketには、ws schemeとwss schemeの2種類がありますので、それらを別々に説明しますね。(ちなみに、PROXY裏、と言うような環境に於いても、ブラウザは頑張ってくれてますが、ここでは説明を割愛させていただきます。)


    • ws schemeの場合

      1. var ws = new WebSocket(“ws://ip_addr:port_num/resource”);
      2. ブラウザは今までのhttp connectionとは別のsocket fdを作成します。
      3. そのsocket fdを用いて、ip_addr:port_numへとconnect(2)を行います。なお、port_numが省略された場合は80番が使われます。
      4. connectが成功したら、Web Socketのhandshakeを行います。
      5. handshakeが成功したら、そのsocket fdを利用してdataを送受信します。(http connectionとは全く別物)
      6. 送り出す場合には、ws.send(“hoge”);
      7. 受け取る場合はws.onmessage = function(msg) {}

        これはつまり、http connection/protocolとは全く関係ないTCP Connection上で、data送受信を行っているに過ぎません。

    注:connect(2)など、()内で示されている数字は、unix上でmanualを参照する際のセクション番号です。私の覚書だけで、特に意味はありません。

    また、ブラウザですので、実際のapi名は違います。同等の処理だと思ってください。(以降も同じ)


    • wss schemeの場合

      1. var ws = new WebSocket(“wss://ip_addr:port_num/resource”);
      2. ブラウザは今までのhttp connectionとは別のsocket fdを作成します。
      3. そのsocket fdを用いて、ip_addr:port_numへとSSL_connect(3SSL)を行います。
        なお、port_numが省略された場合は443番が使われます。
      4. SSL handshakeが終わり、SSL_Connectが成功したら、Web Socketのhandshakeを行います。
      5. Handshakeが成功したら、そのsocket fdを利用してSSL_read(3SSL)/SSL_write(3SSL)を行い、dataを送受信します。
      6. 送り出す場合には、ws.send(“hoge”);
      7. “hoge”は、SSL_sendによってブラウザで暗号化され、送信されます。
      8. 受け取る場合はws.onmessage = function(msg) { … }
      9. msgは、SSL_readによってブラウザで復号化され、受信されます。その為、javascript内部では勿論、復号化する必要はありません。

        これもまた、http connection/protocolとは関係ないSSL Connection上で、data送受信を行っているに過ぎません。
  • Web Socketサーバはどう動いているのか?
    では次に、Web Socketサーバはどのような動きをするのでしょうか?まず、単体Web Socketサーバを動かして把握しましょう。


    • ws scheme
      非常に多くの方が、ruby, goなどで実装を公開して下さっています。

      http://blog.liris.org/2009/12/websocketchat.html
      http://github.com/igrigorik/em-websocket
      http://github.com/gimite/web-socket-ruby

      以上の方々のサンプルを動かしてみてください。(他にも多々ありますが、ごめんなさい。私が参考にさせて頂いた方のコードだけ参照させて頂いています)
      では、流れを見てみましょう。


      1. serverは、お好きなportでlisten(2)してください。
      2. listenしているportにconnectが来たら、accept(2)してあげましょう。
      3. acceptが成功したら、新たなsocket fdが出来ているはずです。そのsocket fdを利用して、Web Socketのhandshakeを行います。
      4. handshakeが成功したら、そのsocket fdを利用してdataを送受信します。
      5. 送り出す場合には、send(2)/write(2)などを用います。
      6. 受け取る場合は、recv(2)/read(2)などを用います。

        やはりこれも、純粋なTCP Serverの動きです。

    • wss scheme
      残念ながら単体サーバでwss schemeを正しくサポートしているprogramは、私はその存在を見つけられていません。(あれば教えてください。(^^;)ですので、実際に実装するとどうなるか、を見てみましょう。


      1. serverは、お好きなportでlistenしてください。
      2. listenしているportにSSL_connect(3SSL)が来たら、SSL_accept(3SSL)してあげましょう。(勿論、SSL_do_handshake(3SSL)なども必要です)
      3. SSL_acceptが成功したら、新たなsocket fdが出来ているはずです。そのsocket fdを利用して、Web Socketのhandshakeを行います。
      4. handshakeが成功したら、そのsocket fdを利用してdataを送受信します。
      5. 送り出す場合には、SSL_read(3SSL)などを用います。
      6. 受け取る場合は、SSL_write(3SSL)などを用います。

        はい、純粋なSSL Serverの動きです。

  • Web Socketを利用するには?
    さて、ここまでで大体、Web Socketは通常のTCP/SSL connectionをブラウザで利用することを可能とするプロトコルだと言うことがわかってもらえたのではないでしょうか?
    つまり、ブラウザがサポートするWeb Socketを利用するには、以下の点を抑えれば大丈夫です。


    • port == 80/443以外の通信を利用できる環境であれば、別にhttpサーバは必要ありません。先の単体サーバに、SSL通信を行える機能を追加してあげれば、Web Socketをサポートするブラウザとws/wss schemeでの通信が可能になります。



しかしながら、F/W、NAT、PROXYなど様々な理由で、port == 80/443で通信を行いたい場合。その時に、このプロトコルの策定意義があります。

  • httpサーバ経由でWeb Socket!
    まず、ブラウザ側の動きを思い出してください。


    • ブラウザは今までのhttp connectionとは別のsocket fdを作成します。
    • そのsocket fdを用いて、ip_addr:port_numへとconnectを行います。
      port_numが省略された場合は80番が使われます。
    • connectが成功したら、Web Socketのhandshakeを行います

    そう、つまり、http protocolではないmessageが、httpサーバに届くことになります。
    httpサーバにhttpでは無いprotocolを理解させる機能を追加しなければなりません!!(大変だぁあ!)

そうです。結局このプロトコルの存在意義、と言うのは、

新たな仕様を策定し、ブラウザそしてhttpサーバ双方でhttp以外のプロトコルを用いた通信を可能とし、http/httpsで利用しているポートでメッセージのリアルタイム送受信を可能にしようよ!(勿論http handshake回数の削減とか他にも色々意味合いはあります)

(良いことか悪いことかは別として)と、言うことだと思うわけです。

  • httpサーバにWeb Socket Protocolを話させるために必要な追加機能
    正直、細かい所まで説明しだすと、仕様そのものになっちゃいますので、簡単に。詳細を知りたい方は、http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75を読んでください。
    (随時改版されますので、最新のものを読まれることをお勧めいたします。また、@toshirotさんなど有志の方々が日本語訳もされてるはずですので(ごめんなさい、url失念)、英語が苦手な方は、バージョンが若干古くなると思いますが、そちらから読まれてもよいでしょう。)


    1. WebSocket handshake
      とても簡単です。これからWebSocket通信したいよ、と言う要求がbrowserから届きますので、返事を返してあげる。これだけです。



    2. WebSocket Frame Format
      今のところ、2種類のFrame Formatが規定されています。但し、MLでの議論を見たり、@komasshuさんとお話しさせていただいたりしていると、今後は2だけになりそうな流れだね、と意見が一致してますけれど。(理由としては、security issue/binary dataの送受信など、色々ありそうです)


      1. (0x00 to 0x7f) → Data → (0xFF)
      2. (0x80 to 0xFF) → Length → Data

      handshakeが終わった後のsocket fdには、上述のformatを持ったmessageが流れてきます。上述2種類のFrame Formatを解読し、Data部分だけをserver側applicationに引き渡してあげる必要があります。

  • httpサーバがWeb Socket Protocolを話せるとどうなるのか?
    最後に。
    上述の機能をhttpサーバに追加してあげた場合、いったいどのように動くのでしょうか?これは、言葉で説明するよりも、図の方がわかりやすいでしょう。
    ただし、ここではprocessがどうの、とかsocketの引渡しはどうする、とか言う点には言及しません。また、そこはserver側実装をされる方々が考える(設計する)べき点です。
  • 概念図
    基本的には以下のように動きます。勿論、server側のApplicationでFrame Formatを扱えるようにしてもOKです。(不親切ですが)


  • pywebsocketはどう動いているか
    apacheのmoduleである、pywebsocketは以下のように動いています。



以上、私の理解の範囲で記述させていただきましたが、質問、間違いなどございましたら気軽にご連絡ください。ただし、ご回答が遅れるかもしれない点だけご了承頂きたく。

いつになるかわかりませんが、次回はlighty用のmod_websocketがどう動いているかを書いてみます。
簡単に説明しておきますと、reverse proxy likeな動作をします。と書くと、わかっていただける方も多いのではないでしょうか?

ではでは。