先に結論を書いておくと、W3CからDTDファイルのリクエストに対して503 Service Unavailableが返されていたため、DOM周りのプログラムが上手く動かなくなったことが原因でした。

関連

W3C Systeam's blog - W3C's Excessive DTD Traffic

状況

XHTMLを読み込んで、DOM操作している部分が上手く動かなくなって修正にかなり手間取りました。

問題のコードはこんな感じ。

$doc = new DOMDocument();
$doc->loadXML($xhtml_source);
if(!$doc->validate()){
  return false;
}
$str = $doc->getelementById('content');


始め、読み込んでいる$xhtml_sourceに何か問題のある文字列が紛れ込んだんだろうと思って色々調べてみたのだけど、目視しても、Validator使っても全然問題が出てこない。それどころか、殆ど空要素に近いような要素を投入しても動かない。なんだそれは。


マニュアルに目を通してみると、DOMDocument::validateについてはこんなことが書いてある。

DTD に基づいてドキュメントを検証します。

なるほど。DTDってのは、ドキュメントの一番最初に指定するやつね。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

そして返り値については。

成功した場合に TRUE を、失敗した場合に FALSE を返します。 ドキュメントに DTD が添付されていない場合は、このメソッドは FALSE を返します。

なん、だと…


慌ててデバッグモードでログをあさってみると…あー確かに、こんな感じのログが返ってきてる。

PHP Warning:  DOMDocument::validate(http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd) [<a href='domdocument.validate'>domdocument.validate</a>]: failed to open stream: HTTP request failed! HTTP/1.1 503 Service Unavailable due to Unknown abuse from requesting IP

要約すると、「あんたのところからのリクエストには、悪いけど503返させてもらったよ」てな感じでしょうかね…一番最初に上げたW3Cのブログにはこんなことが書かれてます。

Note that these are not hyperlinks; these URIs are used for identification. This is a machine-readable way to say "this is HTML". In particular, software does not usually need to fetch these resources, and certainly does not need to fetch the same one over and over! Yet we receive a surprisingly large number of requests for such resources: up to 130 million requests per day, with periods of sustained bandwidth usage of 350Mbps, for resources that haven't changed in years.

The vast majority of these requests are from systems that are processing various types of markup (HTML, XML, XSLT, SVG) and in the process doing something like validating against a DTD or schema.

Handling all these requests costs us considerably: servers, bandwidth and human time spent analyzing traffic patterns and devising methods to limit or block excessive new request patterns. We would much rather use these assets elsewhere, for example improving the software and services needed by W3C and the Web Community.

A while ago we put a system in place to monitor our servers for abusive request patterns and send 503 Service Unavailable responses with custom text depending on the nature of the abuse. Our hope was that the authors of misbehaving software and the administrators of sites who deployed it would notice these errors and make the necessary fixes to the software responsible.

超簡単にまとめると、「http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd」というのはただの定義表示であってハイパーリンクでないにもかかわらず、このURLにリクエストを投げる人が後を絶たず、サーバヤバイから503返すようにしたよ、って感じでしょうか。別にそんなに頻繁にリクエスト投げるようなことはしてませんでしたが、サーバが重くて何度も試行した人もいたみたいだし、目を付けられたのか。(ブラウザ経由のアクセスならアクセスできるんだよね)

もしくは、リクエストを投げるとこちらのチェックのためにクローラーが走るとか。こちらのプログラムは管理用のものだったので制限が掛かっており、それだと多分401が返るんだよね。それがよくないのか。


まぁいずれにせよ、DOMDocument::validateが上手く動かず解決も困難であるらしいことがわかりました。

…じゃ、検証スキップすればいいんじゃね?

そう思って適当にコメントアウトしてみたのだけど動かない。なんなんだと思ったら、その次のDOMDocument::getelementByIdにも関連してました。

マニュアルにはこうあります。

この関数を動作させるには、何らかの ID 属性を DOMElement::setIdAttribute で設定するか、あるいは DTD で ID 型の属性を定義する必要があります。 後者の場合は、 DOMDocument::validate あるいは DOMDocument->validateOnParse を使用してドキュメントを検証する必要があります。


えええ。

つまりデータがきちんとしたデータであると保証されていない限り、DOM操作は行えないぜ坊や?って感じでしょうか。理屈はわかるし必要なのも理解できるけど勘弁してくれないか、ハニー?



さて、どうしよう。

結局のところ、何をどう変えてもDTDを取得できない限り話が終わらないことがわかりました。でも逆にDTDさえ取得できてしまえば良いんだと言うこともわかりました。

というわけで、DTDをW3Cからダウンロードしてきてローカルサーバ上に配置し、XHTMLのソースを書き換えてそのDTDを参照するようにしたところ無事解決。こんなことして良いのかどうかよく解りませんが。


DTDの構築は、次のファイル群を同じディレクトリに配置してやればOKです。

次のうちの必要なもの。

次のもの全て。


容易に予想できるデメリットとしては、負荷とファイルの変更に対応できないという点が上げられますが、そうそう人目につかない部分であり、またクローラーは絶対にアクセスしないところだからまぁいいかという感じで。



…一応動くようにはなったけど、こんなんで良かったのかなぁ…



追記

読み返していて思いましたが、DOMElement::setIdAttributeで明示的にIDを指定してやればDOMDocument::getelementByIdの前の検証が必要ないわけなので、上手く動作するのかも知れませんな(検証無しで良いのかどうかはともかくとして)。

明日ちょっと試してみる。