Javaのサーバアプリにクライアント側からPOSTで文字列でXMLを送りつけたらXMLの解析時にパースエラーになって1時間ほどハマった時の話。
結論を先に言いますと、BOM付きのまま文字列としてサーバに送信してしまったのでJAXBでコケた。ということです。対策としてはファイルをBOMなしにしました。
当初Python内で文字列を書いてそれをJavaのサーバアプリ側にPOSTしていたときは問題が起こらなかったが、XMLファイルをあらかじめつくっておいてそれをPythonで読み込んでPOSTしたときにエラーになってしまった。
XMLのパースにつかっていたライブラリはJAXBというもの。
// クライアントからPOSTで受け取ったXML文字列をパース JAXBContext jaxbContext = JAXBContext.newInstance(Foo.class); Foo foo = (Foo) jaxbContext.createUnmarshaller(). unmarshal(new StringReader(clientDataString));
エラーは以下の内容
Caused by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
このエラーの原因はいくつか考えられるのですが…
たとえばこんな単純なXMLでも問題が起きました。
utf8.txt
<?xml version="1.0" encoding="UTF-8"?> <content> ... more stuff here ... </content>
Eclipseでブレークポイントを設定してクライアントから受け取った文字列を見ると、期待通りに出てる。なんで??
と思い、調べるとJavaはutf8はBOM付きがどうのこうのと出てくる。
ファイルを調べるとBOM付きで保存していました。
ん~JAXBがBOMに対応していないのはわかったとして、そもそもなんでSystem.out.printlnでだしても先頭2バイトのゴミが出ないの?
下記のテストコードを書いて上記のXML(utf8.txt)を読み込んでみた。
package utf8stream; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; public class TestUtf8Bom { public static void main(String args[]) { try { FileInputStream fis = new FileInputStream("./utf8.txt"); BufferedReader r = new BufferedReader(new InputStreamReader(fis, "UTF8")); for (String s = ""; (s = r.readLine()) != null;) { System.out.println(s); } r.close(); System.exit(0); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } }
Eclipse上で確認結果
<?xml version="1.0" encoding="UTF-8"?> <content> ... more stuff here ... </content>
うーん?先頭のゴミが入らないぞ?
もしやと思い、コマンドプロンプト上でプロジェクトのある場所に移動して下記を実行すると……
D:\Program\pleiades\workspace\utf8stream>java -classpath .\bin utf8stream.TestUtf8Bom ?<?xml version="1.0" encoding="UTF-8"?> <content> ... more stuff here ... </content>
ゴミが出た。つまりeclipseのコンソールがうまいこと処理してBOMを消してくれてたっぽい。
そんなわけでファイルをBOMなしで保存してやると最初に書いたJAXBでコケることはなくなりました。
JavaでBOM付きUTF-8を扱うときは要注意ですね。
参考リンク
最後にUTF-8のBOMとJavaの取り扱いについてすごくわかりやすく書いてあるサイトを紹介します。
Handle UTF8 file with BOM - Real's Java How-to