1. divip

UNIX USER 2003-9向けのdivipの紹介記事。 ちょっとだけ1.0.1の内容にしました。 Ajax解説はあとで追加します。

1.1 はじめに

divipはWebアプリケーションとして実装されたIP Messengerである。 本稿ではdivipのインストール/使用方法を説明し、 divipの実装のベースとなったDiv/dRuby/ERBを紹介する。

1.2 divip - Div IP Messengerとは

IP Messengerは白水啓章氏が開発した、LAN用インスタント・メッセージである。 IP Messengerはその使い勝手のよさ、導入の気軽さからオフィスなどで 広く利用されている。

オリジナルのIP MessengerはWindowsで開発されたが、ソース、プロトコルが 一般に公開されているためWindows以外のプラットフォームにおいても 開発されるようになった。 現在、MacOSX、X11R6、GTK、GNOME/GTK、Java版のIP Messengerが公開されている。 *1

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03.jpg

図1.1 スクリーンショット

divipもこういったIP Messengerの実装の一つである。 divipはオフィスでPC UNIXを利用していてなんとなく疎外感を感じている方々の ために開発された。 divipは次のような特徴を持つ。

  • 仕様の特徴
    • Webアプリケーションである。
    • 1ウィンドウ/3ペイン構成のインターフェイスである。
    • メッセージの履歴が表示される。
  • 実装の特徴
    • Rubyで書かれている。
    • WebアプリケーションフレームワークDivを使用している。

divipの最大の特徴は、Webアプリケーションとして実装されている点である。 多くのクライアントが自前のGUIを持つのに対し、 divipはWebブラウザをGUIとして利用するのである。

昨今、ユーザーインターフェイスとして、またGUIの代わりとして Webの仕組みを用いることは、一般的になっている。 HTMLによるページの記述とWebアプリケーションによる制御の組み合わせは 利用者の環境に依らずに同じアプリケーションを提供することができる。 もっとも普及しているGUIと考えることができるのではないだろうか。

他のIP Messengerではポップアップするマルチウィンドウを用いた GUIを採用することが多いが、Webアプリケーションであるdivipは、 Webのユーザーインターフェイスの制約から1つのウィンドウだけで 構成することとなった。

WebアプリケーションであるのでそのPCの前に座っていなくても HTTPが利用できるマシンからIP Messengerが利用できる。 別のオフィスからちょっと自席のPCに届いたメッセージを覗く、 といったことが可能である。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03office.jpg

図1.2 一つのdivipを別のオフィスから利用する。

divipを作成したもう一つの目的は WebアプリケーションフレームワークDivの紹介である。 フレームワークと名乗っていても応用例が出てこなければ、 それが実用に耐えるフレームワークであるかどうかは判断できない。 divipが一般的で実用的なアプリケーションと言えるかはともかくとして、 Divを使用するとデスクトップツールのようなWebアプリケーションが 簡単にできることを示しているのである。

1.3 動作環境

divipはRubyスクリプトであるので、Rubyインタプリタが必要である。 筆者はruby-1.8.2 CVS版で動作を確認している。

IP Messengerは、通常一つのマシンに一つのクライアントのみ おけるため、divipを実験するには相手となるマシンが必要である。 UDPが到達するネットワーク上にIP Messengerが稼働していなくてはならない。

divipに必要な環境は次の通りである。

  • ruby-1.8.2 - (ruby-1.8.2 CVS版が望ましい)
  • IP Messengerの稼働するもう一台のマシン

UNIXに限らずWindows上のRubyでも動作すると思うが実験できていない。 稼働させている方はいないだろうか?

1.4 必要なソフトウェアのインストール

divipを利用するにはRubyインタプリタと、 Rubyのクラスライブラリが必要である。

1.4.1 Ruby

Rubyはまつもとゆきひろ氏によるオブジェクト指向スクリプト言語である。 原稿執筆時の安定版はruby-1.8.2 *2 である。 divipは安定版のruby-1.8.2で動作が確認されている。

% tar xzvf ruby-1.8.2.tar.gz
% cd ruby-1.8.2
% ./configure
% make
% sudo make install

1.4.2 ライブラリのインストール

次にライブラリの概要を紹介しながらインストール方法を説明する。 幸運にも今回のライブラリのインストール方法はどれも似ている。

divipはRubyによって書かれていることは既に述べたが、Rubyに添付されるライブラリ 以外にも次のライブラリが必要である。

  • dRuby - rubyに標準添付
  • ERB - rubyに標準添付
  • Div - div-1.3.2
  • WEBrick - rubyに標準添付

これらのライブラリは全てRAA<URL:http://raa.ruby-lang.org/>からも 入手可能である。

1.4.3 Div

DivはdRubyとERBを用いたシンプルなWebアプリケーションフレームワークである。 dRubyを用いてセッション管理担当のサーバを準備し、 セッション管理の面倒な部分を隠している。 また、画面(HTML)の作成にはERBを用いる。 divipはこのWebアプリケーションフレームワークDivの上に構築された Webアプリケーションである。

% tar xzvf div-1.3.2.tar.gz
% cd div-1.3.2
% sudo ruby install.rb

1.5 divipのインストール

divipはライブラリ部分とWebアプリケーション部分から構成されている。 ライブラリ部、アプリケーション部の順にインストール方法を説明する。 divipもRAAより入手可能である。 *3

まず、divipのアーカイブを展開する。

% tar xzvf divip-1.0.1.tar.gz
% cd divip-1.0.1

1.5.1 ライブラリDIP

ライブラリ部分とはIP Messengerのプロトコルに関するクラスライブラリと、 アプリケーションのGUI部を除いた(MVCでいう)モデルである。 インストールスクリプトを実行し、インストールする。

% sudo ruby install.rb

site_rubyの下に dip/impsg.rb と dip/dip.rb の二つのファイルが インストールされる。

前者はIP Messengerプロトコルのコアな部分、 後者はメッセージの保持や設定ファイルの扱いといった アプリケーションのモデル部分である。 どちらもDIPというモジュールの中にクラスを定義する。 実はdivipの当初のプロジェクト名は「dip」であったが、 sourceforge.jpを利用する際に dipというプロジェクト名は使用できないことが判明し、 divipに改名したという経緯がある。 プロジェクト名は変更されたが、内部のモジュール名は当初のまま残っている。

以下DIPと表記した場合はこのクラスライブラリを指すこととする。 なお、DIP::IPMessengerはDIPモジュールのIP Messengerのプロトコル部分の クラスライブラリを指す。

DIPは特定のユーザインターフェイスを持たず、アプリケーションのモデル 部分だけを実装している。 DIPライブラリの利用者が任意のユーザインターフェイスを構築するのだ。 divipはDIPを利用してWebのインターフェイスをかぶせたものであるが、 TkやGtkなどのGUIをかぶせることも容易である。 実際、DIPの開発中はCUIによるインターフェイスを使用してテストを行なっていた。

なお、IP Messengerプロトコル部分の設計は竹内仁氏の IP Messenger Class for Ruby 1.0 *4 を大いに参考にさせていただいた。 dip/ipmsg.rbはIP Messenger Class for Ruby 1.0を元に改造したものである。

1.5.2 Webアプリケーションdivip

Webアプリケーション部分の設定について説明しよう。 アーカイブを展開すると、次のようなディレクトリ構成になっているだろう。

% pwd
divip-1.0.1
% ls
README  divip/  install.rb  lib/

lib/は先ほどインストールしたライブラリのソースが置かれているディレクトリ、 divip/がアプリケーションが置かれているディレクトリである。 以下の作業はdivip/で行なう。

% pwd
divip-1.0.1 
% cd divip

ライブラリDIPはアプリケーションのモデル部分だけを記述しており、 ユーザインターフェイスを持たない。 divipはDIPにWebアプリケーションフレームワークDivを用いた ユーザインターフェイスを実装し、 完結した「アプリケーション」としたものだ。

設定ファイル

この設定ファイルはどの構成も共通である。 設定ファイルはプログラムを実行するディレクトリに置く。 まずニックネームの設定について説明する。 プログラムを実行するディレクトリに .dip_nick というファイルを作成し ニックネームを設定する。次のように ':' で区切って設定する。

リアルネーム:ニックネーム:グループ

ファイルが存在しない場合のデフォルトは

Masatoshi SEKI:咳:ももぐみ

となっている。なお、設定ファイルはEUC-JPで保存してほしい。

UDPのブロードキャストのネットワークはデフォルトではIPADDR_BROADCASTであるが さらにブロードキャストする範囲を追加することができる。 追加するブロードキャストの範囲は.dip_broadcastに記述する。 .dip_broadcastの記述例を以下に示す。

10.0.2.0
10.0.3.1

ネットワーク/ホストの指定はRubyのSocketクラスライブラリの 「ホスト指定形式」で行なう。 ホスト指定形式についてはRubyリファレンスマニュアル *5 を参照されたい。

起動

1プロセス版divipは tofu-runner.rb というスクリプトを起動する。 なぜこのようなファイル名であるのかは後述する。

スクリプトを実行すると、「phrase:」というプロンプトが表示されるので、 Webブラウザからdivipを使用するためのフレーズを入力する。 ここではtest00としよう。

% ruby -Ke tofu-runner.rb -a
phrase:
test00

tofu-runner.rbにはHTTPサーバ、DIP、Divセッション管理(Tofu)、divipが 配置されている。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03login.jpg

図1.3 ログイン画面。フレーズを入力する。

Webブラウザから次のURLを開くと、フレーズを入力するページになるので 先ほどのフレーズ(test00)を入力する。

http://yourhost:2000/div/
       ======== 環境変数 HOSTNAME の値

図1のようなページが表示されるはずである。表示されたであろうか?

URLのホスト名やポート番号を変更するにはtofu-runner.rbを直接 変更してほしい。

# 22行目付近
   @server = WEBrick::HTTPServer.new(:Port => 2000,
                                     :AddressFamily => Socket::AF_INET,
                                     :BindAddress => ENV['HOSTNAME'],
                                     :Logger => @logger)

パラメータの:Portや:BindAddressを変更することで、 それぞれポート番号、ホスト名を変更できる。

divipの使い方

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03over.jpg

図1.4 2フレームで構成したdivip

次にdivipの使い方を画面に沿って説明する。 divipは一般的なIP MessengerのGUIとは大きく異なっているので注意が必要だ。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03right.jpg

図1.5 参加者リスト

右側のペインはネットワークに参加しているIP Messengerのリストである。 上のrecentは、最近メッセージをやりとりした相手がリストされる。 参加者の多いネットワークでは参加者リストが大きくなるので 目的の相手を選ぶのが面倒になる。 そのためよくやりとりする相手をrecentとして別リストに表示するようにした。 [recent]をたどるとLoginの再通知を行う。 ネットワーク上の参加者が多い場合、Login通知の返信を全て処理する前に 画面が更新されることがある。 このような場合、参加者リストを更新するために[nick][group][addr]など リストのソートを試してほしい。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03lefttop.jpg

図1.6 メッセージ作成ペイン

左上はメッセージ作成ペインである。 宛先は右側のリストから選択する。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03leftbtm.jpg

図1.7 メッセージリスト

左下は送受信したメッセージのリストである。 新しいものほど上に表示される。 divipではメッセージはメモリから消されることはなく保持され続け、 他のインスタントメッセージ系に近い。 メッセージの見出しが薄い水色で表示されるメッセージが送信したメッセージ、 ピンクで表示されるメッセージが受信したメッセージである。

見出しの[X]を押す(リンクを辿る)と該当のメッセージはリストから隠される。 隠されたメッセージがどうしても見たいときは、一旦[logout]してからもう 一度表示divipのページを表示させるとリストに復活させることができる。

見出しの[v]はそのメッセージの送受信したユーザとのメッセージだけを 表示させるモードになる。 他のインスタントメッセージのように 特定のユーザとのメッセージの履歴を時系列に見ることができるのだ。 この機能はリストの上にあるオプションメニューからも利用できる。 メニューより[All]を選択するとすべてのユーザとのメッセージを 表示することができる。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03open.jpg

図1.8 未開封メッセージ

受信したメッセージは閉じた状態でリストに表示される。 閉じたメッセージの見出しには[open]というリンクがあるのでそれを 辿って開封する。

送信メッセージの見出しにある「!」や「?」は開封確認通知に対応している はずだが、divip-1.0.1でもまだうまく動作していないようだ。 作者(筆者)がプロトコルを誤解しているのかもしれない。

Webアプリケーションには、能動的な画面の更新は得意であるが 受動的な画面の更新は苦手であるという特性がある。 このままでは他のユーザからのメッセージが到着したことを 直ちに知ることができない。 このためメッセージの到着を知らせるシンプルなページが準備されている。 次のURLにアクセスしてほしい。

http://yourhost:2000/div/biff
       ======== 環境変数 HOSTNAME の値

定期的(10秒)にメッセージの到着を問い合わせて「arrived!」を 表示するページである。 これを小さなウィンドウサイズで画面の隅に表示させておくのも良いだろう。 また、次のような2フレームを持つページを作成しておくのも便利である。

<html>
<head>
<title>divip</title>
</htead>
<frameset rows='5%,*'>
<frame src="http://yourhost:2000/div/biff" />
<frame src="http://yourhost:2000/div/" />
</frameset>
</html>

なお、divip-1.0.1からAjax風にJavaScriptを利用してページ遷移なしに 到着を表示できるようになった。この機能については後述する。たぶん。

実装されていない機能

divipの作者はIP Messengerをほとんど使ったことがないため、 IP Messengerそのものの機能をあまり理解していない。 さらに、divipの実質的なユーザはおそらく1名ということもあり divipにはIP Messengerの基本的な機能しか実装されていないのだ。 現在実装されていない機能を列挙する。

  • 不在モード
  • ファイル転送
  • 開封通知の受信

ファイル転送については実装する予定はないが、開封通知と不在モードは 是非とも実装したいと思っている。 パッチ(特にライブラリDIP、モジュールDIP::IPMessenger部分)を歓迎する。

1.6 Div

Divは簡単なWebアプリケーションを、簡単に記述するためのフレームワークである。 CGIでちょっとしたツールを書いたことのある方ならご存知であろう。 一見簡単そうに見えるWebアプリケーションでもいくつも面倒な部分がある。 Divが解決しようとする「面倒な部分」は次の点である。

  • 動的な画面の生成が面倒
  • フォーム/リンクとアプリケーションの接続が面倒
  • 状態の管理が面倒

これらの面倒くさい点をDiv::Divと呼ばれる画面の部品とTofuと呼ばれる セッション管理機構を用いて解決する。 divipの起動方法の説明で tofu-runner.rb というスクリプトを実行したのを 覚えているだろうか? Tofuによるセッション管理を実行するスクリプトであるため、 tofu-runnerとなっているのである。

1.6.1 Divの戦略

CGIのプロセスはリクエストにより起動され、処理を行い、 終了時にレスポンスを返すというとても短命なものである。 このため、リクエストを越えた状態を持つアプリケーションを作成するには 状態をどこかに保存し、復元する機構が必要となる。 状態を保存するにはデータベースやファイル、クッキーなどいくつかの戦略が 一般的である。 本来は(比較的)長命であるアプリケーションを、リクエストと同じ寿命の 幾世代ものプロセスと、プロセスの世代間で状態を伝える「遺言」で 実現しようとする戦略だ。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03typical.jpg

図1.9 状態の永続化によるWebアプリケーションの実装

この戦略は状態の永続化・復元が必要となり、大げさな仕組みが必要になってしまう。 この辺りに簡単なアプリケーションを書くのを躊躇させる面倒くささがありそうだ。

そもそも長い期間に渡って存在すべきもの、つまりアプリケーションの寿命を 分断してしまうところに問題があるのではないだろうか。 次のアプローチは、アプリケーションとCGIのプロセスを分離して、 アプリケーションをリクエストを越えてサーバとして起動しつづける戦略だ。 dRubyやWEBrickのサーブレットを用いるとアプリケーションをサーバ化し、 アプリケーションとCGIのプロセスと連携(プロセス間通信など)させることが とても簡単に記述できる。 状態の永続化ではなく、アプリケーションオブジェクトを 本当に長生きさせる戦略である。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03tofu0.jpg

図1.10 アプリケーションのサーバ化によるWebアプリケーションの実装

さらによく観察すると、サーバ化したアプリケーションは二つの種類のオブジェクトに 分けることができることに気付く。 一つはそれぞれのユーザインターフェイスに固有のオブジェクト、 もう一つはアプリケーションそのもののオブジェクトだ。 前者はセッションごと(画面ごと)に独立した状態を持つものである。 divipで言えば、作成中のメッセージの宛先や、参加者リストのソートキー、 メッセージ履歴のフィルタなどがそうである。 受信メッセージを引用してメッセージを作成するのはその画面でだけ 発生していることであり、前者の領域である。 後者はユーザインターフェイスとは独立したアプリケーション本来の状態である。 divipではメッセージの送受信、メッセージ履歴、参加者リストの更新などが これにあたる。そう。これはライブラリDIPが担当している領域である。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03tofu.jpg

図1.11 ユーザインターフェイスとアプリケーションの分離とdivip、DIPの領域

Divが支援するのは、前者のWebアプリケーションのユーザインターフェイス部分である。 アプリケーション本体の部分、divipで言えばDIPの部分はDivが支援する領域ではない。 といっても、アプリケーション本体のサーバ化はdRubyを使用するととても容易に 実現できるので心配する必要はない。 ただし、今回のようにdivipを-aオプション付きで起動する場合は、 アプリケーションもユーザインターフェイスも同じプロセスで起動することになるので、 dRubyは使用されない。

DivはWebアプリケーションフレームワークと呼ぶよりも、 Webユーザインターフェイスフレームワークと呼ぶ方が適切かもしれない。

1.6.2 Div::Div

Divの名前はHTMLのdiv要素を由来とする。 小さなHTML片で表現されるWebユーザインターフェイスの部品を 台紙に載せてアプリケーションを開発する様子から、Divという名前をつけた。 Divの中でも、特にこの部品を示す際にDiv::Divと呼ぶ。 Div::DivはERBによるビューと、リンクやフォームから呼び出されるコントローラを 持っている。

例えばdivipのログインフォームはDipLoginDivというクラスである。 その外観はdip_login.erbで定義される。

<% unless @dip.server %>
<%=form('login', context)%>
<p>
Phrase: <input type='password' name='phrase' value='' />
<input type="submit" name="login" value="login"/>
</p>
</form>
<% else %>
<small>[<%=a('login', {'phrase'=>'(bad phrase)'}, context)%>Logout</a>]</small>
<% end %>

リスト中のform('login', ...)やa('login', ...)メソッドは、 このDiv::Divへのコントローラ(do_login)を起動するための フォームやリンクを生成するメソッドである。

class DipLoginDiv < Div::Div
  set_erb('dip_login.erb')

  def initialize(session, dip_div)
    @dip = dip_div
    super(session)
  end

  def do_login(context, params)
    phrase, = params['phrase']
    begin
      @dip.server = @dip.front.server(phrase)
    rescue
      @dip.server = nil
      @message = "oops! bad pass-phrase."
    end
  end
end

これはDipLoginDivの定義である。do_loginメソッドがERBで生成した フォームやリンクから起動されるコントローラである。 適切なDiv::Divのコントローラを起動するのはDivのフレームワークの責任であるので アプリケーションは気にする必要はない。

Div::Divは別の部品を内側に持つことも可能である。 DipDivはdivipの部品が載る土台となるDiv::Divであり、 DipSendDivやDipListDivなど部品を載せている。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03divcls.jpg

図1.12 DipDivのインスタンス階層

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03divcls2.jpg

図1.13 画面とDiv::Divの対応

1.6.3 Tofu

TofuはCGIに依存していたDivのセッション管理機構を、 WEBrickでも利用できるように抽象化したものだ。 名前の由来はWEBrickのBrickからきている。 レンガのような形状でありながら脆いもの、という意味でTofuとなった。 JavaのBeanとはもちろん無関係である。

Tofuの役目はクッキーを用いたセッションIDとセッションオブジェクトを 関連づけることである。 Webブラウザからのリクエストを、そのクッキーのセッションIDをキーに 適切なセッションオブジェクトに伝える係である。

http://www2a.biglobe.ne.jp/%7eseki/ruby/dip03tofu2.jpg

図1.14 TofuとDivの役割

Tofuは通常のCGIだけでなく、WEBrickのサーブレットからも利用できる。 アプリケーションの変更をせずにhttpdを変更することが可能である。

Tofuの詳細は本稿の範囲を越えるので、解説はここまでにしようと思う。 興味を持たれた方は、参考文献やDivTofuSessionクラスを参照されたい。

1.7 おわりに

divipのインストール・使用方法を説明し、その基盤となっているフレームワーク Divを簡単に説明した。 Divに関する書籍には

などがあるので、読んでみてほしい。


*1IP Messenger開発研究室 - (<URL:http://www.asahi-net.or.jp/~VZ4H-SRUZ/ipmsg.html> よりリンクされているものを列挙した。
*2ruby-1.8.2 <URL:ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.8.2.tar.gz>
*3 <URL:http://www2a.biglobe.ne.jp/~seki/ruby/divip-1.0.1.tar.gz>
*4Ruby Scripts - <URL:http://www.namaraii.com/ruby/scripts.html#IPMSGWEB>
*5Rubyリファレンスマニュアル - <URL:http://www.ruby-lang.org/ja/man-1.6/index.cgi?cmd=view;name=socket.so#a.a5.db.a5.b9.a5.c8.bb.d8.c4.ea.b7.c1.bc.b0>