蟲探し
この前設置したAjaxチャットのlaceなんだけれども、サーバ上で実用してみると、どうも挙動が怪しい。
テストプレイ時に不定期に名前が化ける。それだけならまだしも、化けた名前のところから出力結果を乱したりすることさえあった(Firefoxでやってた自分のところでは影響が目に見えなかったので認識が遅れた)。しかも、スクリプトの上では表示できているログが、FTPでダウンロードしたデータファイルを開けるとどうやってもまともに読めない(文字コードが違う、という次元の問題ではない)。ローカルテスト用のcoLinux環境では問題を起こさないのがまた厄介だ。
さすがにこりゃイカンということで、デバッグに乗り出すことに。しかしこのスクリプト、Ajaxなもんでクライアントとサーバを別々に(あるいは連動させて)デバッグしなければならない。面倒。面倒なことは喉元過ぎると綺麗に忘れそうなのでメモ。
まずは現象と再現条件を把握しないと話が始まらない。クライアント側の挙動はIEerBugを仕込んで見張ることにし、サーバサイドは挙動記録用のログファイルに値を吐かせることにする。
名前の流れに絞って追いかけると、フォーム->(POST,cookie)->サーバ->Result(+cookie)と引き回しているようだ。で、サーバ内で文字化けを起こした結果、ユーザが名前を変更したと誤認して名前変更メッセージが発生した上、化け文字がシステムメッセージを修飾しているstrongタグの閉じタグの先頭(<)を巻き込んだ結果タグが破損、閉じタグを失って以降の全文に意図しない修飾がかかったと思われる。
さて、文字化けというからには、当然文字コードの間違いが一番怪しい。結果的にはその通りだったのだが、ちょっと回り道。
設置の段階でスクリプトのエンコードはUTF-8に統一しているし、スクリプト中とphp.iniで内部エンコーディングは"UTF-8"と指定していたが、それでも問題が起こったこと、POSTがcookieに引き継がれてループしている(名称変更の捕捉のため)ことから、今まで手付かずだったcookieの挙動を疑ってみる。英語圏生まれのスクリプトなので、URLエンコードなしにマルチバイト文字がぶち込まれて化けるというのはありがちな話に思えたからだ。さて、クライアントのJavaScriptはencodeURIComponent,decodeURIComponentを使っていて、こちらは問題なさそう。ではサーバ側はどうかと思って明示的にURLエンコードさせてみたが、これは完全に蛇足だった。PHPはURLエンコード/デコードを勝手にやってくれるのを忘れていた。二重のURLエンコードが発生し、%xxが%25xxとなり、%25xxは%2525xxとなり…と駄目なスパイラルが発生する。まさに藪蛇。orz
ともあれ、クライアントとサーバで実際にやりとりされた値を突き合わせることで、URLエンコードとcookieの挙動自体には問題がないことが判った。どうもその前段階の文字そのものに問題があるようだ。とすると、やはり内部エンコードがEUCのままで、POSTの入力がそれに引きずられてEUCで寄越されていると考えると辻褄が合う(ただ、不定期にそれが発生する理由は謎)。
試しに入力文字をわざとEUCに変換して実験したところ、問題の現象に酷似した化け方をすることが確認できた。ということで、根本的な対処として、POST値を取得する部分($_POSTのラップ関数と、$_POSTを直接参照している部分)にmb_convert_encodingを噛ませて明示的に文字コードをUTF-8に変換することにした。
が、これでも化ける。エンコードの化けと異なり、先頭の文字にだけ欠損が発生するケースがある。何でだ。
調べてみると、スーパーグローバル変数そのものに、array_mapでstripslashesをかける関数が割り当てられている。エンコード変換前にこれをやったら、バックスラッシュと誤認して文字の一部を除去して壊してしまう可能性は充分あるじゃないか。……というわけで、stripslashesの前にエンコード判定を行い、UTF-8でない場合にはエンコード変換をかける処理を追加することにした。
これでほとんど問題はなくなったのだが、チャット画面そのものを別画面へ移動させてから戻すと、ユーザ一覧にゴミのように見える行ができる。ユーザ一覧用のファイルに何故かEUCのデータが書き込まれているせいだ。
ログを見ると、ページ読み込み後のリクエスト群の中で、一件だけEUCのデータを寄越すものがある。しかし何故か、書き込み時にエンコードをいくら検査させても引っ掛けられない。何故だ?結局最後まで原因の特定ができなかったので、力技だがユーザ一覧ファイルの参照時にEUCのデータを無視するようにした(この時はエンコード判定が正常にできた)。参照後にファイル内容はリフレッシュされるから、結果的にはこれでEUCデータを排除できる。
あと、最悪の場合に備えてシステムメッセージの修飾タグ、その終了タグの手前にスペースを仕込んでおいた。タグさえ壊れなれば、被害はその場所の文字化けだけで済むはずだ。
これで、今のところ動作は正常。
うーん、面倒臭いなぁ。あと、Ajaxなスクリプトにはもっと慣れとかないとこの先マズい気がする。