Tomcat＋JSPでの405 Method Not Allowedエラーへの対応方法

Tomcat8以降でJSPを動かす場合PUTとDELETEメソッドが使えないという問題があります。

https://stackoverflow.com/questions/23886941/http-status-405-jsps-only-permit-get-post-or-head

この制限により、JSPに対してPUTまたはDELETEメソッドによるリクエストを実行すると405 Method Not Allowedが返ってきます。コンテナレベルで遮断しているため、サーブレットの処理や設定を変えても効果はありません。

この問題はHTTPメソッドオーバーライドで掻い潜ることができます。ただしPUT,DELETEを許可することでセキュリティの問題が生じる場合がありますので本手法の適用は自己責任で判断してください。

# HTTPメソッドオーバーライド

やり方は簡単で、ヘッダにx-http-methodを付与し、POSTメソッドでアクセスするだけです。

#### PUTの発行例
```console

curl -X POST -H "Content-Type:application/json" -H "x-http-method:PUT" -d "<データ>" "<APIのURL>"

```

ただしHTTPクライアントによってはヘッダを書き換える機能が無い場合があります。その時は次に紹介するサーブレット・フィルタをサーバ側に追加することで対応できます。

# サーブレット・フィルタ

サーブレット・フィルタはサーブレットコンテナとサーブレットとの間に処理を噛ませることができる仕組みで、Webアプリケーションに対するリクエストの前処理やレスポンスの後処理を実行できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185900/8d6684d3-d895-4261-9b34-abd94a82cffb.png" width="30%" />


これに前述のHTTPメソッドオーバライドを実装します。これによりPUTやDELETEをPOSTとしてコンテナで受け付け、PUTやDELETEとしてサーブレットに渡すことができます。

## サーブレット・フィルタの作成

#### フィルタのサンプルソース

以下にHTTPメソッドオーバライドを実行するフィルタのサンプルソースコードを載せます。

```java
package test;

import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class MethodConvertingFilter implements Filter {
    @Override
    public void init(FilterConfig config) throws ServletException {
        // do nothing
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        chain.doFilter(wrapRequest((HttpServletRequest) request), response);
    }

    @Override
    public void destroy() {
        // do nothing
    }

    private static HttpServletRequestWrapper wrapRequest(HttpServletRequest request) {
        return new HttpServletRequestWrapper(request) {
            @Override
            public String getMethod() {
            	// リクエストメソッドがPUTまたはDELETEの場合に、このリクエストがPOSTであることを返す
            	if(request.getMethod().equalsIgnoreCase("PUT") || request.getMethod().equalsIgnoreCase("DELETE")) {
            		return "POST";
            	}
                return request.getMethod();
            }

            @Override
            public String getHeader(String name) {
            	// x-http-methodヘッダにオリジナルのメソッドを設定
            	if(name.equalsIgnoreCase("x-http-method")) {
            		return request.getMethod();
            	}
            	return super.getHeader(name);
            }

            @Override
            public Enumeration getHeaderNames() {
                List<String> names = Collections.list(super.getHeaderNames());
                // x-http-methodヘッダを追加
                names.add("x-http-method");
                return Collections.enumeration(names);
            }
        };
    }
}
```

#### フィルタクラスの配置

フィルタのソースをコンパイルし、生成されたクラスファイルをWebアプリケーションのWEB-INF/classesに配置してください。

#### フィルタ定義の追加

web.xmlに以下のフィルタ定義を追加します。複数のフィルタが定義されている場合、定義された順番に処理が適用されるため定義位置に注意してください。

```xml

  <filter>
    <filter-name>MethodConvertingFilter</filter-name>
    <filter-class>test.MethodConvertingFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>MethodConvertingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher> 
  </filter-mapping>

```

これで実装は完了です。普通にPUTとDELETEが実行できるようになります。