« July 2010 | Main | September 2010 »

August 2010

August 27, 2010

テストケースの実行にあわせて Apache を起動・終了する方法

ウェブアプリケーションやライブラリの結合テストを行う段階になると、実際に Apache を起動してテストを実行したくなります。しかし、そのためにいちいち Apache の設定ファイルを修正して httpd を再起動して、とやっていては面倒です。特に複数のプログラムを同時に開発していると、あっちをテストしたらこっちが動かなくなって… なんてなったりして嫌気がさしてきます。

そこで、テストを実行する際に、環境毎に異なる以下のような問題を吸収しつつ、テスト専用に設定された Apache を自動的に起動終了してくれる Perl モジュール:Test::Httpd::Apache2 を書きました。

  • 環境によって、インストールパスが違う (/usr/local/apache/bin だったり /usr/sbin だったり)
  • 環境によって LoadModule の要不要や、ロードするパスが違う
  • 環境によってプログラム名が違う (Debian 系では httpd ではなく apache2)
  • Apache/2.0 と 2.2 でモジュール名が変わっているケースがある (mod_access と mod_authz_host)

使い方は簡単。テストコードの先頭で、Test::Httpd::Apache2 のインスタンスを生成すると、自動的に TCP の空きポートを探して Apache が起動するので、そのアドレスに HTTP クライアントからアクセスするだけです。起動された Apache は、Test::Httpd::Apache2 のインスタンスが解放されるタイミングで終了され、一時ファイルも自動的に消去されます。

use Test::Httpd::Apache2;

my $httpd = Test::Httpd::Apache2->new(
    required_modules => [ qw(perl) ],  # mod_perl をロード
    custom_conf => << 'EOT',
<Location />
  SetHandler perl-script

my $root_url = "http://" . $httpd->listen . "/"; # 起動した host:port から URL 生成

# あとはテスト

そして、テスト本体が Perl のコードである必要はないので、PHP や他の言語のテストを実行するためのラッパーとして使うこともできます。

use Test::Httpd::Apache2;

my $httpd = Test::Httpd::Apache2->new(
    required_modules => [ qw(php5) ],  # mod_php をロード
    custom_conf => << 'EOT',
DocumentRoot "docroot"
SetHandler php5-script

# 起動した host:port から URL を生成して、環境変数に登録
$ENV{ROOT_URL} = "http://" . $httpd->listen . "/";

# my_test.php を実行
system('php', 'my_test.php') == 0
    or die "my_test.php failed: $?";

Test::mysqldTest::postgresql と組み合わせると、データベースを使うウェブアプリの結合テストが気軽に書けそうで夢が広がりまくりんぐ。

まだ荒削りなモジュールなので、改善提案やパッチ等お待ちしております。インストールは cpan -i Test::Httpd::Apache2、リポジトリは github.com/kazuho/p5-test-httpd-apache2 にあります。

それでは、have fun!

August 17, 2010

Releasing Starlet 0.10, now runs faster than ever

Last Sunday my friend told me that Starman is now way faster than Starlet and I started to wonder why.  There should not be such a difference between the two servers, sicne they share a lot in common.  So I looked into the code yesterday, found the bottleneck and fixed them, as well as adding some tweaks.  Here's what I did, and the chart[1] shows how the performance changed.


1) optimize the calculation of Content-Length

Plack::Middleware::ContentLength was the larget bottleneck.  Instead of calling the module Starlet now calculates the Content-Length header by itself in a faster way, when and only when necessary.

2) use TCP_DEFER_ACCEPT on linux

TCP_DEFER_ACCEPT is a flag that suppresses the return of accept(2) until any data arrives on a newly-established TCP socket.  By using the flag, same number of HTTP requests can be handled by a smaller set of workers, since they do not need to poll waiting for HTTP requests to arrive on non-keepalive connections.  The fact leads to less memory pressure and higher performance.  Starlet turns on the flag by default.

3) switch from alarm to select-based polling

Apps can now use alarm for their own purposes.

4) reuse the fake psgi.input

An optimization copied from Starman.

5) merge short content-length with response headers

It is sometimes faster to merge data within perl and send them as a single packet than sending the two separately.  BTW, anyone going to write a wrapper for writev(2)?

The conclusion is that Starlet is still a good choice if you are looking for an application server that runs behind a reverse proxy, maybe better than Starman until miyagawa-san copies the optimizations back :-p

NOTE 1: The benchmark was taken on linux-2.6.32 (x86_64), using a single core of Core 2 Duo running at 3GHz.  The options used were "ab -c 50", "plackup -s Starlet --max-reqs-per-child=10000", "--workers=64" for keepalive and "--workers=16" for non-keepalive.