経緯
先日Spring BootでAPIを作成していた際に、呼び出し側から
「お前のところのAPIどうやって叩いても正常なレスポンスを返さない」
と連絡を受けました。
こちらではHttp Status に200を返しているログも残っているしネットワークの問題かしらん?と思い詳しい話を聞いてみると
「Http Status Codeの200 OKの"OK"が出力されていないから正常じゃない!」
とのことでした。
RFC的にはどうなっているか
RFC 7230
https://tools.ietf.org/html/rfc7230#section-3.1.2
A client SHOULD ignore the reason-phrase content.
クライアントはreason-phrase(OKの部分)を無視すべきと記載があるのですが。。。
でも、呼び出し側は社会的信用の高いところなので、こちらが折れるしかないですね!
原因調査
Spring Boot 1.4.0: HTTP Status Issue
https://github.com/spring-projects/spring-boot/issues/6548
SpringのIssueを見てみると、Spring Boot 1.4からTomcatを8.5系に上げたのが原因だけどTomcatの問題(?)だからSpring側ではどうしょもないよ。とのこと
missing reason phrase in tomcat 8.5.x
https://bz.apache.org/bugzilla/show_bug.cgi?id=60183
Tomcatの開発チームも、RFC7230でも無視してって書いてあるし無駄だから8.5でなくしたよ。
と修正するつもりは無さそうです。
対応
Tomcatのソースコードを読んで自分でなんとかする事にしました。
調査の結果org.apache.coyote.http11.ConstantsにStatus Codeの文字列が定義されいる事がわかりました。
Class loadingの順番を変えて定義されている内容を置き換える事も検討しましたが、
バージョンアップの事を考えると単純なclass置き換えは不安が残ります。
(この対応を忘れてバージョンアップして問題あるけど動くには動くみたいな状況は危険です。)
幸いStatus Codeは定数として定義されていたため、必要な箇所だけSpring起動時にリフレクションで書き換える事にしました。
ただし、対象がfinalなフィールドだったため、リフレクションでも簡単には変更する事はできませんでした。
そのあたりのナレッジは別記事として投稿しています。
サンプルコード
@SpringBootApplication
public class SpringBootApplication {
public static void main(String[] args) throws Exception {
Field statusBytes200 = org.apache.coyote.http11.Constants.class.getDeclaredField("_200_BYTES");
byte[] _200_OK = "200 OK".getBytes();
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(statusBytes200, statusBytes200.getModifiers() & ~Modifier.FINAL);
statusBytes200.set(null, _200_OK);
SpringApplication.run(SpringBootApplication.class, args);
}
}