2007-10-31

RedHat, CentOSで SELinuxをオフにする

新しくセットアップしたRedHat(CentOS)環境でなんだか上手く動かないものがある。
straceかけてみるとrootなのに permission deniedとか出てる。

という場合 SELinuxに引っかかっている。

SELinuxを飼い慣らす方法を知るのが本来ベストだけど、多分面倒きわまりないのでとりあえずオフ(前のエントリで言ってることと話が違うぞ?)にする方法のメモ。

■今すぐSELinuxを切る
echo 0 > /selinux/enforce

■次回起動時からずっとSELinuxを切る
/etc/selinux/config で SELINUX=disabled

2007-10-29

マルチキャストDNS, IPv6, PostgreSQL

マルチキャストDNSでローカルネットワークの名前解決を行っていると、IPv6のリンクローカルアドレスで目的のサービスに接続してしまうことがある。

PostgreSQLに「そんなアドレスしらねー」と怒られたので、pg_hba.confに下記のような設定を追記することでリンクローカルのホストをtrustするようにした。(必要に応じてもっとマシなセキュリティ設定を用いること)

host all all fe80::/16 trust

ここで単純にIPv6を切ってしまうような後ろ向きな姿勢がテクノロジの進歩を邪魔するのだ。

2007-10-28

RailsアプリケーションのホットスポットをJavaへリプレースする

Railsを使ったサーバーサイド開発は Rubyの記述力や標準ライブラリが優れていることもあいまって非常に速く、アジャイル的なイテレーションを伴う開発スタイルに適している。
その代わりに、実行性能はお世辞にも高いとは言えない。というかむしろ低い。しかしそれは高い生産性との正当なトレードオフである。

というわけで、求められる性能要件を満たすというミッションに対する RoRの典型的な解としては、「多くのハードウェアを投入して解決する」「節約できた開発時間を性能の向上に割り当てる」などが挙げられると思う。後者を選んだプロジェクトは地球に優しいエコロジーな意志決定に基づいており大変素晴らしい。よもや、まさか、万が一にも、金がないなどという理由ではないに違いない。なおTwitterなどは前者と後者のハイブリッドと思われる(後者寄り?)。

ここではエコロジーな方針を選択する前提で話を進める。一般に、プログラムの実行に必要な時間の8割は、2割のプログラムコードに費やされているとされる。最小の労力でソフトウェアの性能を大きく向上させたいのであれば、問題の2割となる部分を探し出して高速なコードに置き換えれば良い。

以下、事例

RoRと ActionWebServiceを使って実装したあるサービスでは、120件ほどのレコードを出力するオペレーションの実行におおよそ 1800ミリ秒かかった(※)。このサービスで最も多く呼び出されるばかりか、都度CPU時間を多く使うこのオペレーションの実装をより高速なものへ置き換えれば、労力に見合った成果を得られるだろう。

※普通いくら RoRが遅いと言っても 120件を表示するページの表示に 1.8秒もかからない。この事例は SOAPベースの RPCでの話であり、サーバ側・クライアント側双方におけるマーシャル・アンマーシャル処理および通信にかかる時間を含めた計測である。

とはいえ高速化すると言っても、この時既に DBMSの負荷は無視できる程度だということは確認済みであり、find_by_sqlでダイレクトに SQLを実行しているため ActiveRecordにおける O/Rマッピングのコストも少ないと予想されることから、CPUを使っている時間の殆どは SOAPの荷造りと荷解きに使われてしまっていることが明白だった。与えられた主な選択肢はふたつ。

  • このオペレーションだけ、SOAPをやめて生XMLやJSONのようなものを使ってプロトコルオーバーヘッドを軽減する

  • このオペレーションだけ、Javaに置き換えることで根本的に高速化する


ここでは、Railsと JEEサーバの両方を起動しておく必要があり運用の面倒はあるものの、JEEがシングルインスタンスで同時に複数リクエストの処理を行えるというオマケがあるため経済効果が将来的に大きいことや、クライアント側のコード変更量が少ないことを考慮し後者を選択した。(というわけで本日の題目になる)

手はずはこうだ。問題となっている重いオペレーションと全く同じ処理を Javaで記述しなおし、Apache CXFを使って SOAP化したものを Tomcat にデプロイする。クライアント側では起動時に WSDLを2種類(Rails側、Java側)ロードし、可能な場合は Java側をコールする。

大きな問題がひとつあった。このサービスではユーザを特定するのに必要な若干の情報をいわゆるセッション変数に保持しているのだ。Rails側で使われているCookieを JEE側でも読み出すことは可能だが(HTTPクライアントから見て同じ Webサーバに見えるようURLのマッピングを工夫すれば良い)、CookieはただのUIDでしかなく肝心のセッションデータ本体はデフォルトだと Rails配下のバイナリファイルに書き出されているためこれを読みに行かなければならない。

プレーンファイルを読みに行くよりもマシな方法がある。Railsではセッションデータストアを切り替えることが出来るのだが、それをファイルでなくデータベースにしてやれば Java側からも JDBCを使って読み出すことが出来るのだ。これだとアプリケーションのインスタンスをノードにまたがって複数起動したくなった場合にも対応出来るためスケーラビリティというオマケもつく。
(RDBのスループットが問題になるようなら代わりにmemcachedという選択肢もある)

設定変更は比較的簡単だ。environment.rbで
config.action_controller.session_store = :active_record_store
という行をコメントインし、
rake db:sessions:create
でセッションテーブル作成のマイグレーションを生成して rake db:migrate すれば良い。

データベースに保存されたセッションデータは
select data from sessions where session_id = ?
で読み込むことが出来る。ここで得られる data 列の内容は、RubyのHashをシリアライズしたデータを Base64でエンコードしたものである。

Java側でこれを解くには、commons-codecの Base64クラスを使って下記のようにデータベースから取得した data 列をデコードし、
byte[] rawData = Base64.decodeBase64(data.getBytes());
さらにデコードされたバイナリをデシリアライズしてやる必要がある。

Rubyでシリアライズされたデータを Javaでデシリアライズするためのライブラリが何かあれば教えて欲しい。私は上手く見つけられなかったので、Rubyリファレンスマニュアル - Marshalフォーマットを参照しながらサブセットのデシリアライザを自分で書き、RubyDeserializerという名前にした。

RubyDeserializerを使ってセッションデータ user_id (Integer) を取り出したコードを示す。
RubyDeserializer rd = new RubyDeserializer(rawData);
Map sessionData = (Map)rd.deserialize();
if (sessionData == null) return null;
return (Integer)sessionData.get(new RubyDeserializer.Symbol("user_id"));

またこれは余所で役に立つ情報とはあまり思えないのであくまで個人的なメモのために書いておくが、CXFのAegis-Bindingでは構造体メンバの名前を制御するためのアノテーションを使うと Rubyのネーミングルールと互換を取ることが出来る。

@XmlElement(name="article_id") // Javaのルールだと articleId になる
public int getArticleId() {
return articleId;
}
public void setArticleId(int articleId) {
this.articleId = articleId;
}

上記のような工夫の末、Javaへリプレースされた問題のオペレーションは元の1800ms前後から 600ms前後まで高速化されると共に、(シングルインスタンスのアプリケーションサーバで)同時に複数のリクエストを処理できるようになった。

注意してほしいのは、サービスの機能全てを Javaで書き直したわけではないということだ。単に Rubyよりも Javaが速いという当たり前の話をここでしたいのではない。今回 Javaで書き直した部分以外の機能は依然として RoRで動作している。なぜか?性能上問題になっていないので、さしあたり書き直す必要がないからだ。言語やプラットフォームの性能論争は実にくだらない。トレードオフを考慮し、要件を満たすものを都度選択すれば良いだけである。制約上そうできないケースも当然ある(というか多くの場合そうだろう)が、それは案件個別の問題であって一般的な議論の中で持ち出しても何も生み出さない。

これにより以後 RailsとTomcatの両方を同時に運用していかなければならなくなったというデメリットは確かにあるが、必要ならサービスの仕様がある段階まで成熟してプロトコルの変更がほとんど無くなった時点でまとまったバジェットを用意して Javaへのフル・リライトを行い一本化することも可能だろう(これも運用性と予算とのトレードオフである)。

その時には入出力仕様が既に明確でかつFixされているのだから、つまらない Javaのコーディングはコストの安い単なる製造担当のエンジニアに任せることが出来る。アーキテクトは創造的な仕事を Ruby on Railsで行い、そうでない仕事を他人に任せることでより創造的な仕事に時間を割く(※)。そんな構図を夢に描いてみた。

※フルAjax, フルFlashといった完全に RPCベースのアーキテクチャを持つアプリケーションならUIとロジックの分離が真に実現しているわけなので実際に可能だと思う

■おまけ■

いわゆる WebサービスをSOAPで実装した時、HTTPペイロードの肥大化という問題がつきまとう。普通だと正味データ容量の3-4倍(それ以上?)といった長さのレスポンスボディが平気で返ってくるが、Apacheに deflateさせたら 1/16ほどに圧縮されたので、これならまあ許せると思う。

2007-10-23

Railsアプリケーションを日本の携帯電話対応にする

開発者、とりわけアーキテクトは、ガキのおもちゃを作るためにもはやガラパゴス進化の末にカオスとなった日本の携帯3+1キャリアそれぞれの事情へ対応する仕事に工数など割きたくないのである。割きたくないけど、開発者もご飯を食べなければ餓死してしまうので仕方ないのである。

そのようなカオスと戦うためのノウハウが詰まったすごいRailsプラグイン jpmobileを使うしかない。作者のYohji Shidara氏は神。

プラグインのインストールは rubyforgeの svnリポジトリからダイレクトに行える。

./script/plugin install -x svn://rubyforge.org/var/svn/jpmobile/trunk/jpmobile

-xオプションは、このリポジトリを svn:externalsで自分のソースツリーに接ぎ木するという意味。(自分のプロジェクトを既に svnへ格納している場合)

これで、各HTTPリクエストについて、request.mobileに携帯電話特有の各種情報が格納されてくるようになる。また、コントローラクラスを下記のようにすることでさらにケータイサイトに都合の良い動作が出来る。
class MyController
mobile_filter :hankaku=>true # 表示コンテンツのうち半角に変換できるものを変換する
transit_sid # リンクにセッションIDをURLパラメータとして付与する
end

transit_sidをセットすると link_to や form_tagでのジャンプ先URLにセッションIDがつくようになる。(当然だが直にaタグを書いた場合などは聞かないので注意)

上記は最小限の情報にすぎないので詳しくは開発元のサイトを参照されたし。

2007-10-22

Gentoo Linux(など)で RocketRAID 2322を使う

RocketRAID2322は、Windowsの他 Linuxや Macにも対応していて高コストパフォーマンスなマルチレーン外付け用のハードウェア RAIDコントローラ。殿様商売のアダプ○ックと違ってLinux用にはソース版のデバイスドライバも提供しているので、カスタムカーネル使用者にはお勧めの一品。

あっそこの君!巷で売ってる1万円くらいのRAIDカードは全部ソフトウェア RAIDだぞ。騙されるなよ!

というわけで、RocketRAID 2322のドライバをカーネルソースに組み込むのと、RAID管理ユーティリティの使用法をメモ(※RedHat系ならバイナリのドライバが提供されているしユーティリティも RPMなのでこういう面倒なことはしなくて良い)。

High Point Technology社のサイト からBIOS+Driver→RocketRAID 2322を辿り、Open Source driverをダウンロード。
展開したら rr232x-linux-src-v1.06/product/rr232x/linux(バージョン番号は適当に読み替える) へ移動し、
make patchkernel KERNELDIR=/usr/src/linux KERNEL_VER=2.6
で /usr/src/linux以下のカーネルソースツリーに RocketRAID2322のドライバが組み込まれるので、menucondigで Mなり Yなりにしてカーネルをビルドする。

ドライバを組み込んだカーネルでブートすると、RocketRAID 2322に接続されたディスクアレイが Linuxカーネルから SCSIディスクとして見える。

ここまででも一応使えるが、コントローラやアレイの設定を動的に変更するには RAID Management Utilityが必要なので入れておく。

Management Utilityはクライアント - サーバ方式をとっている。サーバ側は hptsvr というプログラムで、これは i386か x86_64のいずれか自分の環境に合った方を使う必要がある。クライアントは hptraidconfというコマンドラインUIで、i386用のみ用意されているがこれは x86_64環境でも動作する。
(なお Web UI版のManagement Utilityもあるが、ここではコマンドライン版のみ解説する)

HighPoint社のドライバページへ再度訪れ、CLI版の RAID Management Utilityをダウンロードし展開する。サーバ・クライアント共に RPMで提供されているので、RPM系でないディストリビューションを使っている場合には rpm2targzで一度ただの tar.gzに変換してから依存関係無視・問答無用で展開するのが簡単である。

rpm2targz hptsvr-3.13-4.x86_64.rpm
rpm2targz hptraidconf-2.3-3.i386.rpm

tar zxvf hptsvr-3.13-4.x86_64.tar.gz -C /
tar zxvf hptraidconf-2.3-3.i386.tar.gz -C /

サーバを動作させるには /etc/hptcfg ファイルにドライバ名を書いておく必要がある。RocketRAID2322の場合 rr232x と書く。これを書いておかないと hptsvrが起動しない。

hptsvrを起動し(必要なら自動起動にしておくこと)、hptraidconfを実行すると RAIDコントローラやアレイの管理をするテキストベースのユーティリティが利用出来る。なお初期ログインIDは RAID パスワードは hpt になっている。

RocketRAID2322以外の RAIDコントローラでもほとんど同じである。RocketRAID 2302あたりは安価だしポートマルチプライヤにも対応していてお買い得だと思う。

RocketRAID 2322 (MAXSERVE)
RocketRAID 2302 (MAXSERVE)

さよならmgetty先生 - OMRON ME5614U2を Linuxと efaxで使う

LinuxホストをFAXサーバとして使っている全国の皆さんこんにちは。
パソコン通信時代の 14400bpsモデムはお元気ですか。
だってしょうがないよね、class2 FAXモデムがもう何処にも売ってないんだから。
mgetty先生の class1 FAX対応コードはメンテナンスが放棄されてて動きません。

先日FAXサーバ(にもなっているマシン)のマザーボードを交換したんです。そしたらシリアルポートが無くて、FAXモデムがつなげられないんです。まさにファック!
おかげで大家さんからの請求書が届かなくて家賃の払いが遅れてしまったじゃないか!

というわけで、しょうがないので PCIか USBのモデムを購入せねばと思っていた所、秋葉原のOTTO ネットワーク専門店で OMRONの ME5614U2なる USB接続モデムが 5000円くらいで売られているのを発見したので使えるかどうかよくわからないけどゲット。もちろんメーカーが Linux対応を謳っているわけがない。でも標準のCDCドライバで動けばもうけ物である。

結果、カーネル2.6標準のドライバですんなり使えた。オムロン最高。安物のソフトウェアモデム(Linuxでは特別にドライバが提供されてない限り使えない)ばっか市場に流通して、まともなモデムを滅多に見かけない今日に一筋の光が見えた気分だ。しかもUSBバスパワー動作なので配線がスッキリする点も見逃せない。
USB Modem (CDC ACM) support → Y ね。
このモデムは /dev/ttyACM0 として利用できる。
Linuxサーバ用モデム難民は今すぐ秋葉のOTTOネットワーク専門店に走れ!まだ何台かあるぞ!(店員さんへ。箱あけてマニュアルを確認させて頂いてどうもでした。こんなとこ見てないと思うけど)

さて、残念ながらこんな良いモノ(注:壺ではない)でも大人の事情かなんだか知らないが class2 FAXには対応していない。もうこれは諦めるしかないので class2 FAXにしか対応していないmgetty先生は本日をもってクビ。

かわりに efaxというものを使えば class1 FAXモデムでも FAX受信が出来るので、そちらを使うようにしてみた。
gentooなら emerge efax でインストールは完了。
/usr/bin/fax というシンプルな名前のファイルが実はフロントエンド・シェルスクリプトになっていて、ユーザー設定もここを書き換えるようになっている。できれば /etc 以下に移して欲しいところだがそこはそれ。私が設定を書き換えたのは下記の行(注:受信に必要な設定のみ)。

DEV=ttyACM0
CLASSINIT="-o1" # class1の指定
ANSRINGS=1 # RING1回で応答
FAXMGR=自分のメールアドレス

/var/log/faxと /var/spool/fax を mkdir(なければ)して、inittabに下記を加え init q する。

s0:345:respawn:/bin/sh /usr/bin/fax answer

うまく動けば、FAXの着信に自動で応答が行われ TIFF画像の添付されたメールになって届くはずだ。

さよなら mgetty
さよなら MC14400FX(←パソコン通信時代に活躍したモデム)

Windows上のApache2.2で ODBCデータソースを使った Basic認証を行う

最初に断っておくけど、よっぽどの物好きにしかお勧めできないよ、こんなの。

Apache 2.2には RDBへのアクセスを提供するインターフェイス mod_dbdと、それを Basic認証のバックエンドとして使う mod_authn_dbdがあって、ドライバさえあれば好きなリレーショナルデータベースをパスワードデータベースとして使える。

ここで PostgreSQLや MySQLのやりかたを紹介するのは、ありふれた普通の技術ブログ。

Oracleのやりかたを紹介するのはちょっと変わった技術ブログ。

うちは、Microsoft Accessの MDBファイル(いわゆるJetな)や Microsoft SQL Serverを使うやりかたを紹介する変態技術ブログ。ということにしてみた。

さて、Apacheには残念ながら個別のRDBMS用ドライバが含まれていないのだが、Google codeから ODBC用のmod_dbdドライバが入手出来るのでそれを使ってみる。これなら AccessやSQL Serverといわず Paradoxだろうが dBASEだろうが FoxProだろうが Basic認証のパスワードデータベースとして使えるわけだ。Cool! そんな奴いないとおもうけど。

なお、これからしばらくVisualStudioが入ってる Windows環境を前提に話を進める。
既に Apache 2.2.6がmsi形式のインストーラでインストール済みなのも前提。

インストーラ形式で配布されている Windows版 Apacheに付属している libaprutilは DSOサポートがオフになっているのでこのままだとデータベースドライバがロードできない。なのでApacheのbinフォルダにある libaprutil-1.dllを DSO有効バージョンに置き換えてやる必要がある。以下、駆け足で手順を示す。

httpd-2.2.6 の Windows用ソースをダウンロードして展開

srclib\apr-util\include\apu.hw の最後にある #endifの前あたりに
#define APR_DSO_BUILD 1
を追加

srclib\apr-utilに移動し、
nmake -f libaprutil.mak CFG="libaprutil - Win32 Release"
で、リリースビルドの libaprutil-1.dllを作成 (Releaseフォルダに出来る)

問題の libaprutil-1.dllをリプレースすれば DSO対応化は完了。
次に mod_dbdを ODBC対応にさせるためのドライバ apr_dbd_odbc.dllをビルドする。

http://code.google.com/p/odbc-dbd/からソースをダウンロードして展開後、下記のコマンドでビルドする(APR= には Apacheのインストールフォルダを指定すること)。

nmake -f Makefile.win APR="c:\Program Files\Apache Software Foundation\Apache2.2"

これで Release\apr_dbd_odbc.dllが出来るので、Apacheの binフォルダへコピーする。

httpd.confに下記のモジュール読み込み設定を追加(デフォルトではコメントですら入っていないので書き足す)

LoadModule dbd_module modules/mod_dbd.so
LoadModule authn_dbd_module modules/mod_authn_dbd.so

さらに、mod_dbdのディレクティブ DBDriverと DBDParamsを設定する。下記は localhostで動作している Microsoft SQL Server上のデータベース mydb にユーザー名 sa, パスワード secretで接続する設定の例。

DBDriver odbc
DBDParams "CONNECT='Driver={SQL Server};Server=localhost;Uid=sa;Pwd=secret;Database=mydb''"

Accessの .mdbファイルを使うなら CONNECT='DRIVER={Microsoft Access Driver (*.mdb)};DBQ=C:/path/to/users.mdb;' のようになる。

CONNECT=の代わりにDATASOURCE=,USER=,PASSWORD=で設定済みのODBCデータソースを使う事も出来る。

あとは普通の Basic認証と同様の設定を行うが、AuthBasicProviderディレクティブで dbdを指定し、AuthDBDUserPWQueryでパスワード列を引いてくる SQLをセットすることでパスワードデータベースとして mod_dbdが用いられるようになる。
<Location /myprivatearea>
AuthType basic
AuthName "private area"
AuthBasicProvider dbd
AuthDBDUserPWQuery "SELECT Password from Users WHERE User_ID = ?"
Require valid-user
</Location>
これで Windows上の Apache2.2で ODBCデータベースを使ったユーザー認証が出来るようになった。
こんな面倒くさいことしないで IISを使えばいいんじゃ・・・って言うの禁止。

■おまけ■
最近知った機能なんだけど、Satisfy っていうディレクティブを使うと 「特定ホストからのアクセスはパスワード不要・それ以外は要パスワード」というアクセス制御が出来るらしい。今までずっとApacheの機能では出来ないと思ってた・・・。
先の例にくっつけるなら Require valid-userの後に

Satisfy any
order deny,allow
allow from 127.0.0.1 169.254. 192.168.
deny from all

みたいなふうにすると localhostやプライベートネットワークからのアクセスは認証をパス出来る。
社内LANからはパスワード無しでアクセスできるが外出先からのアクセスは要パスワード、のような運用に使えるだろう。

2007-10-20

Microsoft SQL Serverの like検索が優秀な件について


この例は like '%ちゅーぶ%' なので全表検索で性能要件が満たせるケース専用だが、何の小細工もなしにこれが出来ることにはそれなりの価値があると思う。ずっと MS系でやってる人には当たり前なのかもしれないけど、オープンソースRDBMSではそれなりに頑張らないと出来ないんだよねコレ(というか like では出来ない, 多分)。

ちなみに索引検索をしたければSQL Serverには全文検索エンジンも付属している(明示的に選択しないとセットアップされない)が、現状(少なくとも日本語トークナイザは)N-GRAMに対応しておらず、分かち書きアルゴリズムに手を入れることも出来ないようなので、精度が微妙。だけど doc, pdf, ppt, vsdなどといった文書のバイナリオブジェクトに対しても索引が作れるため結構面白いことに使える。

Apach2.2 を使って Windowsでも Tomcatを80番ポートから利用する

Tomcatを Windowsで動かしている時、大抵は URLに :8080 とつけて 8080番ポートから利用されていると思うが、普通このシチュエーションは開発用や社内のちょっとしたツールとしての利用であって、いっぽうプロダクション環境は Linuxや Sun で Apache - mod_jk - Tomcat という構成で 80番ポートを使って公開・運用されるのが一般的だろう。

が、Windows上の Tomcatをプロダクション環境として使いたい時もある。普段そのホストを管理するスタッフが Windowsの操作しか出来ない場合がそうだ。コンシューマ向けの公開サービスを運用するならそのようなシチュエーションは稀だと思うが、イントラネットの場合はむしろ非Windowsのコンピュータを操作出来るスタッフが居るケースの方が珍しかろう。とりわけ中小企業では。

そういったケースではそもそも Javaではなく IIS+ASP.NETを用いるのが妥当という考えもないではないが、それでも Javaで開発するほうが都合の良い場合もある。というか、そういうのは開発リソースやライセンス、ミドルウェア要件など案件個別の問題なので詳しく議論する価値なし。よって以下略。

というわけでいつものように能書きが長くなったが、ここでは Apache+Tomcatという普通の組み合わせを、だが Windows用にセットアップする(学校でUNIXを習わなかったのにうっかり Java開発の道へ入ってしまった文系エンジニアの諸兄には悪いものではなかろう)ための方法をメモしておく。なお Tomcatは既に稼働しており、8009番ポートでAJPを待ち受けている(デフォルトではそのようになっている)状態を想定している。

私は、公式のミラーから apache_2.2.6-win32-x86-no_ssl.msi をダウンロードしてただダブルクリックで開き、ハイハイと答えて Apache 2.2.6のインストールを完了した。デフォルトでは、80番ポートを待ち受けるように設定された Apacheが自動起動のサービスとしてインストールされる。Apacheのインストールだけなら、ここまでであっけなく終了。

さて、Apache 2.2には mod_proxy_ajpが含まれているため、Tomcatと AJPで接続するためにモジュールを調達してくる必要がない。Apache 2.2用に mod_jkをセットアップしようとして悩んでいる人をよく見かけるが、(mod_jkに特有の込み入った機能を使いたいのでなければ)これは全くの徒労である。
(そしてこの状況は、標準でコンパイラを搭載していない Windows環境にとって都合が良い)

Apache2.2は何も考えずにインストールすると C:\Program Files\Apache Software Foundation\Apache2.2 に展開される。デフォルトだと、Tomcatとの連携に必要なふたつのモジュール mod_proxy, mod_proxy_ajpはロードされないようになっているため、Apacheのメイン設定ファイル conf\httpd.conf で下記の行をコメントインすることによってモジュールを有効にする。

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so

モジュールを有効にしたら、Tomcatへのリクエスト転送を設定する。これは(特にこだわりがないのなら)httpd.confの末尾にでも書けば良い。下記は、Tomcatに標準搭載の managerアプリケーションと、自分でしこしこ作ってデプロイした mywebappアプリケーションに対して処理を転送する例。

ProxyPass /manager ajp://localhost/manager
ProxyPass /mywebapp ajp://localhost/mywebapp

ajp:// ... の所には、Tomcatが別のホストで動作している場合はlocalhostの代わりにそのホスト名又はIPアドレスを、8009番以外のポートでAJPの待ち受けをしているのであればホスト名の後にコロンで区切ってポート番号を記述すること。

httpd.confを書き換えたら Apacheを再起動して設定を反映させる。Apacheの再起動はタスクバーに出ているApacheアイコンをいじると出てくる画面でやるか、「管理ツール」→「サービス」あたりから通常のサービスと同様の手順で行う。

これで URLに :8080 と書かなくても利用できる Webアプリケーションを Windowsでサービスさせることが出来るようになった。
Linuxで同じ事をするよりも遙かに簡単で、マニュアル化もしやすいのではないだろうか。
学校でUNIXをやってない(しつこいな、そのくだり)技術スタッフを現地に遣ってセットアップしてもらうのにはもってこいだ。
viの操作方法を他人に説明するのほどつまらん仕事は他にあまりない。

ついでに、Tomcat単独では出来なかった(出来るのかもしれないが、やり方を調べる価値を見いだせなかった)細かいログ採取やアクセス制御も Apache側の設定で出来るだろう。

■おまけ■
LoadModule deflate_module modules/mod_deflate.so
をコメントインし、
AddOutputFilterByType DEFLATE text/html text/plain text/xml
を httpd.confの末尾に追加することで ApacheがHTTPレスポンスを圧縮してくれるようになる。
企業のイントラなんかで、地方の支店がADSL回線につながっているケースだと支店側のWebアプリケーションへアクセスするのが速くなるかもね。(ADSLは上り方向の帯域が狭いため)
#支店に何故わざわざサーバを置くのかって?じゃあ本社との通信が途絶えたら業務が完全ストップするような支店でいいのか?
#納得いかないなら、「本社が地方にある場合」とかに適当に読み替えろや。

2007-10-16

ローカル名前解決をDNSでやる

プライベートなネットワークの中でホスト名を管理する簡単な方法は /etc/hostsを使うことだが、台数が増えてくるとそれじゃあんまりなので DNSを使って名前を管理してみる。ついでに、ローカル以外の名前解決は外のちゃんとした DNSにフォワードさせる。

ローカルな名前解決とはいえ DNSに扱わせるためには(多分)一応ドメイン名が必要なため、ここでは .local を使う。

/etc/named.conf

options {
// 前略
forwarders { x.x.x.x; y.y.y.y; }; // 外のちゃんとしたDNSをここで指定
};

// 正引き
zone "local" {
type master;
file "local.zone";
forwarders {}; // こうしておくことでローカルな解決を外に漏らさない
};

// 逆引き(ここでは 172.16.0.0/16)
zone "16.172.in-addr.arpa" {
type master;
file "172.16.zone";
forwarders {};
};

/var/named/local.zone

$TTL 3600
@ IN SOA mydnshost.local. root.local. (
1
8H
1H
1W
1D)
;
IN NS mydnshost.local.
;
mydnshost IN A 172.16.0.2
apple IN A 172.16.0.3
orange IN A 172.16.0.4
grape IN A 172.16.0.5

/var/named/172.16.zone

$TTL 3600
@ IN SOA mydnshost.local. root.local. (
1
8H
1H
1W
1D)
;
IN NS mydnshost.local.
;
2.0 PTR mydnshost.local.
3.0 PTR apple.local.
4.0 PTR orange.local.
5.0 PTR grape.local.

各ホストのresolv.confを編集し、ローカルの DNSを参照させる。domain local としておくとドメイン名を省略して名前を引いた際に自動で補完される。

domain local
nameserver 172.16.0.2

2007-10-13

ActiveRecordにおける、"URLなんちゃら"のマイグレーション

$ rake db:migrate

rake aborted!
uninitialized constant CreateUrlMetadatas

あれ?おかしい。
しばらく悩んだ末、どうやら
× script/generate model URLMetadata
○ script/generate model UrlMetadata
のようで。

PersonがPeopleになるわりには data が datasになるのはどうかね。
PeopleもPeoplesになるってことかな?

あと、ActiveRecordのカラム名に type は使っちゃだめ。

2007-10-10

RedHatとsyslog収集

RedHatだとデフォルトで syslogdはネットワーク経由で着信するログを記録しない。
/etc/sysconfig/syslogのSYSLOGD_OPTIONS行に -r オプションを追加して "-m 0 -r"のようにし、syslogをrestartすればUDP/514で着信するsyslogを/var/log/以下に記録してくれるようになる。
他のホストからのログがそれでも記録されない場合、tcpdump port syslog のようにして本当にパケットが来ているか確認すると良いだろう。

RIAのちから

技術デモとして、ちょっとしたSNSを作ってみた。
トップページでアナウンスしている。
(デモだというわりには、招待されなきゃ入れないのが謎)

RailsのActionMailerで iso-2022-jpな日本語メールを送る

iso-2022-jp(いわゆるJIS)しか解釈できない時代錯誤っぽいMUAも相手にしてやらなければならないのが日本人の宿命。

Ruby-GetText-Packageをインストールして、ActionMailer::Base(を継承した)クラスの前に

require 'gettext/rails'
GetText.locale = 'ja'

と書いてやり、クラスの中で @@charset='iso-2022-jp' を指定すると、送信メールがそういったMUAでも読めるように変換されたのち送られるようになる。

gettextの本来の使い方とは違うけど、とにかくrailsで手っ取り早くメールをiso-2022-jp対応にしたい人むけ。

2007-10-07

ActionWebServiceと BigDecimal

Apache CXF + Aegis Bindingでは Webサービスのオペレーション引数・返値に java.math.BigDecimal型を使うことが出来る。
Railsの ActionWebServiceはどうかと思って調べたら、intか floatしかない。オーノー

ブログや掲示板やSNSみたいなおもちゃならintがあれば十分なわけなのでともかく、業務システムの場合しばしば「小数を」「ユーザの常識により期待される形で」「わりと多くの箇所に」表示する必要があるわけで、それに floatを用いるのは結構きびしい。
(rubyでは名前こそFloatであるものの実装は doubleであるらしいが、精度の高さは問題ではない)

銅線 計19.99999999kg 入荷済みとか画面に表示されたら普通困るよね。
かといって0.000000001とか(桁数適当。念のため)足してから文字列表現に直す、っていう処理を各所にばらまくという非生産的な仕事はなるべく作りたくない。
データベースからNUMERIC型のフィールドを読み込む時に floatを使うとデータベース側で設定した有効桁数より下の方には適当な値が入ってたりするし。

悲しいけどこれ、仕様なのよね。たまにバグだって騒ぐ輩が沸くみたいだけど(さらにバグfixだって言って意味不明な仕様変更をする開発者もいるみたいだけど)。

あるていどfloatの性質をわかっている者が実装をするなら対処できるかもしれないが、途中から実装を他人任せにする案件ではカオスの元凶としかいいようがないので、こういうところでは最初から BigDecimalを使っておきたい。

しかしながら、railsのリポジトリを眺めてみたところ trunkでは :decimal で BigDecimal型が使えるようになっているようだったので、とりあえず今だけ我慢すればいいかということで終了。

ラベル:

2007-10-03

SpringとApache CXFで簡単に Webサービスを作る

Rails + ActionWebServiceほどではないが、Javaでも Apache CXFを使ってそこそこ簡単に Webサービスを作って公開することが出来る。

ここでは、ActionWebServiceの例で作ったのと同じ Hello Webサービスを実装してみる。

HelloService.java

import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public interface HelloService {
String sayHello(@WebParam(name="name") String name);
}

HelloServiceImpl.java

import javax.jws.WebService;

@WebService(endpointInterface = "com.acme.HelloService")
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "Hello, " + name;
}
}

Webサービスのインターフェイスには @WebServiceアノテーションをつけ、各メソッドの引数に名前が必要な場合は @WebParamアノテーションをつける。

Webサービスの実装クラスにも @WebServiceアノテーションをつけるが、こちらでは endpointInterface属性で自身を Webサービスとして公開するためのインターフェイスを明示する。

CXFServletを使って、このサービスを HTTPで利用可能な Webサービスとして公開することができる。さらに Springを組み合わせて使うことが有用なため、ここでは Springとのインテグレーションを前提に例を示す。

クラスパスに追加するもの
cxf-2.0.2-incubator.jar
geronimo-ws-metadata_2.0_spec-1.1.1.jar
geronimo-annotation_1.0_spec-1.1.jar
geronimo-activation_1.1_spec-1.0-M1.jar
jaxb-api-2.0.jar
jaxb-impl-2.0.5.jar
jaxws-api-2.0.jar
jdom-1.0.jar
wsdl4j-1.6.1.jar
XmlSchema-1.2.jar
xml-resolver-1.2.jar
stax-api-1.0.1.jar
wstx-asl-3.2.1.jar
saaj-api-1.3.jar
saaj-impl-1.3.jar
そのほか、springを使うのに必要なjarファイル群。

web.xmlに CXFServletを登録する。

<web-app>
<!--
SpringをWebアプリケーションで使うときにいつも必要なもの
-->

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<!--
Apache CXFで Webサービスを公開するためのサーブレット
-->

<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<!--
url-patternで指定したパスの下に Webサービスの
エンドポイントがぶらさがる形になる
-->

<url-pattern>/services/*</url-pattern>
</servlet-mapping>
</web-app>

Springの Bean定義ファイル WEB-INF/applicationContext.xml に必要な記述は下記のとおり。

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws=
"http://cxf.apache.org/jaxws"
xsi:schemaLocation=
"
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd"
>

<!--
CXFのために必要ないくつかの設定
-->

<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<!--
CXFによって公開される Webサービスを生成するファクトリBeanを
定義する。
この例は、Aegisバインディングを使用するための設定
-->

<bean id="serviceFactory"
class=
"org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean">
<property name="dataBinding">
<bean class="org.apache.cxf.aegis.databinding.AegisDatabinding"/>
</property>
<property name="serviceConfigurations">
<list>
<bean class="org.apache.cxf.jaxws.support.JaxWsServiceConfiguration"/>
<bean class="org.apache.cxf.aegis.databinding.AegisServiceConfiguration"/>
<bean class="org.apache.cxf.service.factory.DefaultServiceConfiguration"/>
</list>
</property>
</bean>

<!--
Helloサービスのインスタンス
ここではPOJOと同様の扱い
-->

<bean id="helloService" class="com.acme.HelloServiceImpl"/>

<!--
Helloサービスのエンドポイントを定義
implementorにはクラスの完全修飾名を指定するか、
#に続けて Springの Bean IDを指定する。
addressは エンドポイントのURIとなる
-->

<jaxws:endpoint id="helloWebService" implementor="#helloService"
address=
"/Hello">
<jaxws:serviceFactory>
<ref bean="serviceFactory"/>
</jaxws:serviceFactory>
</jaxws:endpoint>

</beans>


上記のように構成した Webアプリケーションをデプロイすると、http://myhost:port/services/Hello をエンドポイントとする Hello Webサービスが呼び出し可能になる。このサービスの WSDLは /services/Hello?wsdl で 取得できる。

2007-10-02

CXFのAegis-Bindingと java.sql.Date

Apache CXFにはAegis-Bindingという機能があって、リフレクションで型を調査し SOAPで使う型へのマッピングを自動的に考えてくれる。配列や階層にも対応しているので何も考えずに Webサービスとしてバリューオブジェクトを入出力出来る。

が、java.sql.Date型の getterメソッドを持つ JavaBeansについて正しくそれが日付型として処理されないような気配があった。(CXF 2.0.2)
これを java.util.Dateに変更したところ問題なくなった。

ところが、Map<Date, Integer> みたいな型が dateTime2dateTimeMap にバインドされてしまう問題を発見。
孵化にはまだまだ時間がかかるようで。

Apache CXFと Flexの mx:WebService

Flexの WebServiceコンポーネントを使って CXFServletで公開されているサービスを呼び出そうとするとき、サービス側が rpc/encodedでないとオペレーションの引数に nullを渡したときにサーバ側のパーサ(StAX)がエラーになってしまうようだ。
サーバ側でXMLパーサがエラーになるということは Flex側の問題だろうか?

JAX-WSでは、Webサービスのデフォルト形式が document/literalになっている。これを rpc/encodedに変更するには

@SOAPBinding(style=SOAPBinding.Style.RPC,
use=SOAPBinding.Use.ENCODED,
parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)

のようなアノテーションをインターフェイスにつけてやる。