afnf.net

Spring MVCやSpring BootでRequest method 'POST' not supportedになる場合

Spring MVC Spring Boot 2015/03/01 01:25

ちょっとハマったのでメモ。

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先が静的リソースだからです。

20150212_static_post

無情な405 Request method 'POST' not supportedエラー。

mapping間違いでこのエラーになったことがあったので、気付くのに時間がかかりました。

原因

ResourceHttpRequestHandlerのコンストラクタで、POSTを許可していないのが原因です。許可していない理由は良く分かりません。セキュリティーリスクがあるんでしょうか。

明示的にforwardする場合は、許してくれても良いような気がしますけど。

対処法

困ったときのstackoverflow先生。

http://stackoverflow.com/questions/15588001/spring-not-accept-post-request-under-mvcresources-how-to-fix-that

まんまですね。でも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

Spring MVC Spring Boot 2015/03/01 01:25
comments (0)

blog-java2 engine (build:2017-09-13 21:46 JST)