まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2007/04/30(月)Microsoft-IIS がダメダメな件(303 See Otherを無視する件)

●対象 Microsoft-IIS/5.0(Windows 2000系付属)

IE で数々のダメを露呈している MS ですが、案の定IISもダメダメでした。adiaryは、IISサーバ + perlis.dll でも動作することになっているのですが、IISサーバ + perl.exe で動かないという報告がありまして、対応外なのですが調査してみました。どうも IIS の Cookie 処理が怪しいようです。

perlis.dll だと Image::Magick を使えない*1のだそうで。試しに動作させたらメモリを食う暴走して危険でした(汗)

*1 : IISの仕様でShellのパイプが使えない

303 See Otherとは何であるか

原因を説明する前に、adiaryのログイン時の動きを確認しておきましょう。

(1)adiary/?loginlogin フォーム
(2)adiary/パスワード認証をして set-cookie
(3)adiary/?login_authcookieがきちんとブラウザに保存されているか確認
(4)adiary/ログイン後の画面

IISだと (3) の時点で「Cookieが有効になっていません」とエラーになります。

HTTPヘッダには 303 See Other というものがありまして、これはHTTP/1.1対応ならば使えます。HTTP/1.1対応かどうかというのは、HTTPクライアントとサーバが両方対応しているときに環境変数 SERVER_PROTOCOL=HTTP/1.1 が設定されます。

adiary(というより Satsuki-system)ではリクエストを redirect するとき、SERVER_PROTOCOL を参照して適切な HTTP ヘッダを返します。というのも、ログインのような POST をリダイレクトするとき、Location ヘッダを出せばいいと思っている cgi 作者もたくさんいらっしゃるようですが、事はそう単純ではありません。

Location ヘッダを同時に出せる(出力してもいい) HTTP/1.1ヘッダ には3種類あります。

ヘッダ移動後のメソッド意味
301 Moved Permanentlyそのままコンテンツが永久的に移動した
302 Foundそのままコンテンツの一時的な移動
303 See OtherGETに変更コンテンツの一時的な移動

HTTP/1.1 においては、302で redirect してしまうと、フォームのデータを再度送信されてしまい永久にログインし続ける恐れがあります。クライアントの実装がマチマチですので難しいところですが、HTTP/1.1対応を名乗るクライアントはこのように実装されているハズです。

303 See Otherを無視するIIS

論より証拠、Microsoft-IIS/5.0 の実装を確認してみましょう。HTTP/1.0クライアント。

[console~]$ telnet 192.168.1.1 80
POST /adiary-1.30/adiary.cgi HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

action=login&id=test&pass=test
--------------------------------------------------
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Mon, 30 Apr 2007 06:17:02 GMT
Set-Cookie: session=%00%02%00sid%00aJXNNh2FJGd3%00id%00test; path=/adiary-1.30/;
Content-Type: text/html; charset=EUC-JP;

Locationヘッダは出ていません。これは正しい実装です。続いて HTTP/1.1 の接続です。この場合 SERVER_PROTOCOL=HTTP/1.1 が設定されていますので、cgiは303 See Other を返します

[console~]$ telnet 192.168.1.1 80
POST /adiary-1.30/adiary.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Host: 192.168.1.1

action=login&id=test&pass=test
--------------------------------------------------
HTTP/1.1 100 Continue
Server: Microsoft-IIS/5.0
Date: Mon, 30 Apr 2007 06:18:47 GMT

HTTP/1.1 302 Object Moved
Location: http://192.168.17.104/adiary-1.30/adiary.cgi/nabe?login_auth
Server: Microsoft-IIS/5.0
Content-Type: text/html
Connection: close
Content-Length: 183
  • 303 をなぜか 302 に書き換えている。
  • Locationヘッダはきちんと出力されている。
  • Set-Cookieヘッダを出力しない(cgi側ではきちんと出力している)

解決策は?

Microsoft-IIS で cgi 動作させるときは、SERVER_PROTOCOL を HTTP/1.0 と見せかけるしかないでしょう。*2

ほんとIEといい、IISといい、まったくデタラメなソフトばかりです。AN HTTPDの方が幸せなになるんじゃないかなぁ。perlis.dll でも普通に ImageMagick 使えましたし。

*2 : やだなぁ……。だいたい perlis.dll より .exe を使う方が遅くなるんですよね。

2007/04/26(木)perl の Encode で find_encode は使えるのか?

perl tips - Encodeを速く使う方法によると、find_encoding を使うと、Encode による文字変換が早くなるらしいということで実験してみました。

実験条件

  • use utf8 しない(use utf8 はトラブルが多すぎてやってられないので)。
  • 変換前も変換後も utf8 フラグは立っていないこと。
  • 実験用データとして、42KBのテキストファイル(EUC-JP, 日本語半分ぐらい)を用意。

ソースはこんな感じです。引数に処理したい euc-jp のテキストファイル名を与えます。

use strict;
use Benchmark;
use Encode;
use Encode::Guess qw(euc-jp shiftjis iso-2022-jp);

my $euc = join('', <>);
my $utf = $euc;
Encode::from_to($utf, "euc-jp", "utf8");

my $euc_obj = find_encoding('euc-jp');
my $utf_obj = find_encoding('utf8');

my %test;
$test{fromto_u2e} = sub {
	my $x = $utf;
	Encode::from_to($x, "utf8", "euc-jp");
};
$test{fromto_e2u} = sub {
	my $x = $euc;
	Encode::from_to($x, "euc-jp", "utf8");
};
$test{find1_u2e} = sub {
	my $x = $euc_obj->encode( $utf_obj->decode($utf) );
};
$test{find1_e2u} = sub {
	my $x = $utf_obj->encode( $euc_obj->decode($euc) );
};
$test{find2_u2e} = sub {
	my $x = $utf;
	Encode::_utf8_on($x);
	$x = $euc_obj->encode( $x );
};
$test{find2_e2u} = sub {
	my $x = $euc_obj->decode($euc);
	Encode::_utf8_off($x);
};
$test{encode_u2e} = sub {
	my $x = $utf;
	Encode::_utf8_on($x);
	$x = encode('euc-jp', $x);
	print $x;
};
$test{decode_e2u} = sub {
	my $x = decode('euc-jp', $euc);
	Encode::_utf8_off($x);
};
timethese(1000,\%test);

結果は次のとおりになりました。

方式utf8 to euceuc to utf8
from_to100.26/s336.84/s
find_encoding103.92/s380.20/s
find_encoding + _utf_on/off357.21/s397.93/s
encode/decode304.76/s350.68/s

utf8からutf8(フラグ付き)への変換=utf8文字列の検証が入らないので速くなりましたが、ほかは大して変わりません。use utf8 しない環境では find_encodeを使うほどでもないなぁーという感じもします。

つまり、utf8を他の文字コードに変換する場合は、Encode::_utf8_on() + encode を使うと速い。短い文字列を多量に処理するのでなければ、ほかは大して変わらない。ということだと思います。

というわけで

早速adiaryに組み込んでみよう(笑)