ちょっとハマったのでメモ。
Spring MVCやSpring Bootでは、静的リソースに対してPOSTできません。
org.springframework.web.servlet.PageNotFound handleHttpRequestMethodNotSupported
警告: Request method 'POST' not supported
直接POSTできないのは分からなくもないですが、forwardによる間接的な遷移も上記のエラーになってしまいます。
mvc:resourcesを以下のように定義しているとします。
<!-- mvc-dispatcher-servlet.xml -->
<mvc:resources mapping="/static/**" location="/static/" />
/static/以下のファイルが、static resourceになります。
問題になる擬似コードはこんな感じです。
@RequestMapping(value = "/post", method = RequestMethod.POST)
public String post() {
if (service.exec()) {
return "next";
}
else {
return "forward:/static/error.html";
}
}
サービスが成功した場合はnext.jspに遷移できますが、失敗した場合のelseブロックはうまくいきません。forward先が静的リソースだからです。
無情な405 Request method 'POST' not supported
エラー。
mapping間違いでこのエラーになったことがあったので、気付くのに時間がかかりました。
ResourceHttpRequestHandlerのコンストラクタで、POSTを許可していないのが原因です。許可していない理由は良く分かりません。セキュリティーリスクがあるんでしょうか。
明示的にforwardする場合は、許してくれても良いような気がしますけど。
困ったときのstackoverflow先生。
まんまですね。でもXMLにゴリゴリ書くのは好きじゃないので、別の方法を探しました。
奥さん・・・見つかりましたよ。
DispatcherServletのinitの後でResourceHttpRequestHandlerを取ってきて、setSupportedMethodsで書き換えてしまう方法です。
public class MyDispatcherServlet extends DispatcherServlet implements ApplicationListener<ContextRefreshedEvent> {
private static ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
applicationContext = contextRefreshedEvent.getApplicationContext();
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ResourceHttpRequestHandler handler =
applicationContext.getBean(ResourceHttpRequestHandler.class); // {
handler.setSupportedMethods(ResourceHttpRequestHandler.METHOD_GET,
ResourceHttpRequestHandler.METHOD_HEAD,
ResourceHttpRequestHandler.METHOD_POST);
}
}
詳しくはこれを見てください。
この方法は、<mvc:resources>
なSpring MVCでしか使えません。例えばSpring Bootの場合、ResourceHttpRequestHandlerが普通にnewでインスタンス化されてしまうので、ApplicationContextから取得できません。
ResourceHandlerRegistryをコピーしてしまう方法しか、良い対処法が思いつきませんでした。
まあそうですねw
相手側がredirectを追ってくれるなら、という条件が付きますが。
例えばapache.httpcomponentsのhttpclientは、POSTリクエストでのリダイレクトを追いかけてくれません。検証コードはこれ。
LaxRedirectStrategyを使えば良いのですが、まあ気付かないっすよねw