ajaxでサーバーにアクセスするWebアプリケーションをデバッグしていたら、なぜかレスポンスが変。
レスポンスボディもHTTPヘッダも想定したものが返ってこない。
原因を調べたのでまとめ。
実行環境
- Tomcat8.0
- SpringMVC
- EclipseでTomcatを動かしてデバッグ
- とりあえず動作確認的にajaxでWebアプリにアクセスして固定値のレスポンスを取ってくるだけの処理にしていた
Test.java
@RestController
public class TestController {
@RequestMapping(value = "/test.do", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String test(HttpServletRequest request) throws Exception {
return "{ \"success\" : true }";
}
}
ajax.js
var req = new XMLHttpRequest();
req.open("POST", "test.do");
req.send();
状況
- TestControllerのtestメソッド内でブレークできたのでURL間違いなどはない
- HTTPステータスは200が返ってきている
- FilterやInterceptorも調べたがレスポンスを変えるような処理はない。むしろresponseオブジェクトの中には想定した内容が格納されていた
- ブラウザによる差異はない。どのブラウザでも変なレスポンスが返ってきている
- たまに正常なレスポンスが返ってくることもある
原因
ステップ実行で深く深く潜っていくとtomcat-coyote.jar内のNioChannel.class
にて例外をスローしていた。
NioChannel.class
/**
* This method should be used to check the interrupt status before
* attempting a write.
*
* If a thread has been interrupted and the interrupt has not been cleared
* then an attempt to write to the socket will fail. When this happens the
* socket is removed from the poller without the socket being selected. This
* results in a connection limit leak for NIO as the endpoint expects the
* socket to be selected even in error conditions.
*/
protected void checkInterruptStatus() throws IOException {
if (Thread.interrupted()) {
throw new IOException(sm.getString("channel.nio.interrupted"));
}
}
この例外が上層でキャッチされ、エラー用のレスポンスに書き換えられていた。
Thread.interrupted()はスレッドに割り込みが発生しているときにtrue
を返すメソッド。
特に割り込みが発生するような処理はないので、この状況で発生し得る割り込みと言えば・・・
デバッガによるブレーク。
試してみると確かにブレークしたときにだけ変なレスポンスになっていた。
あとがき
この例外のスタックトレースが毎回コンソールに出ていたんですが、200が返ってきてたので無視してました。
ちょっとでもエラーっぽかったら無視したらダメですね。
でもひどい罠だと思う。